From ecdf47d893f4cad65cf248514a1b679eac41aaac Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Sat, 22 Feb 2025 23:30:55 +0100 Subject: [PATCH 1/4] update: do not crash on {env: } in paths in config.yml --- fdroidserver/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdroidserver/common.py b/fdroidserver/common.py index e6602134..21944364 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -361,7 +361,7 @@ def fill_config_defaults(thisconfig): # Expand paths (~users and $vars) def expand_path(path): - if path is None: + if not path or not isinstance(path, str): return None orig = path path = os.path.expanduser(path) From 56865f9ba665b85b08f03b9660e50cb2d20a73c4 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 24 Feb 2025 11:58:38 +0100 Subject: [PATCH 2/4] checkupdates: remove auto_author: config, it is no longer used checkupdates-runner sets the required values anyway. https://gitlab.com/fdroid/checkupdates-runner/-/blob/fe3cb890dbd543ff137908b4fd8020401def2938/.gitlab-ci.yml#L35 --- fdroidserver/checkupdates.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/fdroidserver/checkupdates.py b/fdroidserver/checkupdates.py index 9731c510..60e22347 100644 --- a/fdroidserver/checkupdates.py +++ b/fdroidserver/checkupdates.py @@ -675,8 +675,6 @@ def checkupdates_app(app: metadata.App, auto: bool, commit: bool = False) -> Non if commit: logging.info("Commiting update for " + app.metadatapath) gitcmd = ["git", "commit", "-m", commitmsg] - if 'auto_author' in config: - gitcmd.extend(['--author', config['auto_author']]) gitcmd.extend(["--", app.metadatapath]) if subprocess.call(gitcmd) != 0: raise FDroidException("Git commit failed") From 695d97e103464532ad457a9d0c0067d75b767539 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 24 Feb 2025 16:53:41 +0100 Subject: [PATCH 3/4] fix: "no new line character at the end of file" If yamllint is installed `fdroid lint` will run it, and what will output the (new-line-at-end-of-file) warning message. --- tests/test_lint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_lint.py b/tests/test_lint.py index 4056b2a6..89186676 100755 --- a/tests/test_lint.py +++ b/tests/test_lint.py @@ -345,7 +345,7 @@ class LintTest(unittest.TestCase): def test_check_categories_from_config_yml(self): """In config.yml, categories is a list.""" os.chdir(self.testdir) - Path('config.yml').write_text('categories: [foo, bar]') + Path('config.yml').write_text('categories: [foo, bar]\n') fdroidserver.lint.config = fdroidserver.common.read_config() fdroidserver.lint.load_categories_config() self.assertEqual(fdroidserver.lint.CATEGORIES_KEYS, ['foo', 'bar']) @@ -435,13 +435,13 @@ class LintTest(unittest.TestCase): def test_lint_invalid_config_keys(self): os.chdir(self.testdir) Path('config').mkdir() - Path('config/config.yml').write_text('repo:\n invalid_key: test') + Path('config/config.yml').write_text('repo:\n invalid_key: test\n') self.assertFalse(fdroidserver.lint.lint_config('config/config.yml')) def test_lint_invalid_localized_config_keys(self): os.chdir(self.testdir) Path('config/en').mkdir(parents=True) - Path('config/en/antiFeatures.yml').write_text('NonFreeNet:\n icon: test.png') + Path('config/en/antiFeatures.yml').write_text('NonFreeNet:\n icon: test.png\n') self.assertFalse(fdroidserver.lint.lint_config('config/en/antiFeatures.yml')) def test_check_certificate_pinned_binaries_empty(self): From 1ee9ea8cf9278768dd65bfb454f9ecc38160a228 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Sat, 22 Feb 2025 23:27:24 +0100 Subject: [PATCH 4/4] lint: implement for config.yml --- fdroidserver/lint.py | 112 +++++++++++++++++++++++++++++++++++++++++++ tests/test_lint.py | 59 +++++++++++++++++++++++ 2 files changed, 171 insertions(+) diff --git a/fdroidserver/lint.py b/fdroidserver/lint.py index 69b0ca91..f8589897 100644 --- a/fdroidserver/lint.py +++ b/fdroidserver/lint.py @@ -213,6 +213,83 @@ regex_checks = { ], } +# config keys that are currently ignored by lint, but could be supported. +ignore_config_keys = ( + 'github_releases', + 'java_paths', +) + +bool_keys = ( + 'allow_disabled_algorithms', + 'androidobservatory', + 'build_server_always', + 'deploy_process_logs', + 'keep_when_not_allowed', + 'make_current_version_link', + 'nonstandardwebroot', + 'per_app_repos', + 'rclone', + 'refresh_scanner', + 's3cmd', + 'scan_binary', + 'sync_from_local_copy_dir', +) + +check_config_keys = ( + 'ant', + 'archive', + 'archive_description', + 'archive_icon', + 'archive_name', + 'archive_older', + 'archive_url', + 'archive_web_base_url', + 'awsaccesskeyid', + 'awsbucket', + 'awssecretkey', + 'binary_transparency_remote', + 'cachedir', + 'char_limits', + 'current_version_name_source', + 'git_mirror_size_limit', + 'github_token', + 'gpghome', + 'gpgkey', + 'gradle', + 'identity_file', + 'install_list', + 'java_paths', + 'keyaliases', + 'keydname', + 'keypass', + 'keystore', + 'keystorepass', + 'lint_licenses', + 'local_copy_dir', + 'mirrors', + 'mvn3', + 'ndk_paths', + 'path_to_custom_rclone_config', + 'rclone_config', + 'repo', + 'repo_description', + 'repo_icon', + 'repo_keyalias', + 'repo_maxage', + 'repo_name', + 'repo_pubkey', + 'repo_url', + 'repo_web_base_url', + 'scanner_signature_sources', + 'sdk_path', + 'servergitmirrors', + 'serverwebroot', + 'smartcardoptions', + 'sync_from_local_copy_dir', + 'uninstall_list', + 'virustotal_apikey', +) + locale_pattern = re.compile(r"[a-z]{2,3}(-([A-Z][a-zA-Z]+|\d+|[a-z]+))*") versioncode_check_pattern = re.compile(r"(\\d|\[(0-9|\\d)_?(a-fA-F)?])[+]") @@ -791,6 +868,41 @@ def lint_config(arg): msg += ' ' msg += _('Did you mean {code}?').format(code=', '.join(sorted(m))) print(msg) + elif path.name == config_name and path.parent.name != 'config': + valid_keys = set(tuple(common.default_config) + bool_keys + check_config_keys) + for key in ignore_config_keys: + if key in valid_keys: + valid_keys.remove(key) + for key in data: + if key not in valid_keys: + passed = False + msg = _("ERROR: {key} not a valid key!").format(key=key) + m = difflib.get_close_matches(key.lower(), valid_keys, 2, 0.5) + if m: + msg += ' ' + msg += _('Did you mean {code}?').format(code=', '.join(sorted(m))) + print(msg) + continue + + if key in bool_keys: + t = bool + else: + t = type(common.default_config.get(key, "")) + + show_error = False + if t is str: + if type(data[key]) not in (str, dict): + passed = False + show_error = True + elif type(data[key]) != t: + passed = False + show_error = True + if show_error: + print( + _("ERROR: {key}'s value should be of type {t}!").format( + key=key, t=t.__name__ + ) + ) elif path.name in (config_name, categories_name, antifeatures_name): for key in data: if path.name == config_name and key not in ('archive', 'repo'): diff --git a/tests/test_lint.py b/tests/test_lint.py index 89186676..ff6c2247 100755 --- a/tests/test_lint.py +++ b/tests/test_lint.py @@ -529,3 +529,62 @@ class LintAntiFeaturesTest(unittest.TestCase): app = fdroidserver.metadata.App() app['Builds'] = [{'antifeatures': ['Ads', 'Tracker']}] self.assertEqual(1, len(list(fdroidserver.lint.check_antiFeatures(app)))) + + +class ConfigYmlTest(LintTest): + def setUp(self): + super().setUp() + self.config_yml = Path(self.testdir) / 'config.yml' + + def test_config_yml_int(self): + self.config_yml.write_text('repo_maxage: 1\n') + self.assertTrue(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_int_bad(self): + self.config_yml.write_text('repo_maxage: "1"\n') + self.assertFalse(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_str(self): + self.config_yml.write_text('sdk_path: /opt/android-sdk\n') + self.assertTrue(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_str_dict(self): + self.config_yml.write_text('sdk_path: {env: ANDROID_HOME}\n') + self.assertTrue(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_str_bad(self): + self.config_yml.write_text('sdk_path: 1.0\n') + self.assertFalse(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_bool(self): + self.config_yml.write_text("deploy_process_logs: true\n") + self.assertTrue(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_bool_bad(self): + self.config_yml.write_text('deploy_process_logs: 2342fe23\n') + self.assertFalse(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_dict(self): + self.config_yml.write_text("keyaliases: {com.example: '@com.foo'}\n") + self.assertTrue(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_dict_bad(self): + self.config_yml.write_text('keyaliases: 2342fe23\n') + self.assertFalse(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_bad_key_name(self): + self.config_yml.write_text('keyalias: 2342fe23\n') + self.assertFalse(fdroidserver.lint.lint_config(self.config_yml)) + + def test_config_yml_bad_value_for_all_keys(self): + """Check all config keys with a bad value.""" + for key in fdroidserver.lint.check_config_keys: + if key in fdroidserver.lint.bool_keys: + value = 'foobar' + else: + value = 'false' + self.config_yml.write_text(f'{key}: {value}\n') + self.assertFalse( + fdroidserver.lint.lint_config(self.config_yml), + f'{key} should fail on value of "{value}"', + )