commit: 15cbe87076b512b318fac1729ec94e6d6674a95a Author: Zac Medico <zmedico <AT> gentoo <DOT> org> AuthorDate: Sat Mar 27 23:15:16 2021 +0000 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> CommitDate: Mon Mar 29 04:46:41 2021 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=15cbe870
repoman: add variable.phase check like pkgcheck VariableScopeCheck (bug 608664) The variable.phase check is inspired by pkgcheck's VariableScopeCheck, and uses essentially the same PMS data to drive the check. References: - https://projects.gentoo.org/pms/7/pms.html#x1-10900011.1 - https://pkgcore.github.io/pkgcheck/_modules/pkgcheck/checks/codingstyle.html#VariableScopeCheck - https://bugs.gentoo.org/775191 Bug: https://bugs.gentoo.org/608664 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org> repoman/cnf/qa_data/qa_data.yaml | 1 + repoman/cnf/repository/qa_data.yaml | 1 + repoman/cnf/repository/repository.yaml | 1 + .../repoman/modules/linechecks/phases/__init__.py | 6 + .../lib/repoman/modules/linechecks/phases/phase.py | 132 +++++++++++++++++++-- repoman/lib/repoman/tests/simple/test_simple.py | 28 ++++- repoman/man/repoman.1 | 5 +- 7 files changed, 162 insertions(+), 12 deletions(-) diff --git a/repoman/cnf/qa_data/qa_data.yaml b/repoman/cnf/qa_data/qa_data.yaml index 29a3d6e9f..530c8c806 100644 --- a/repoman/cnf/qa_data/qa_data.yaml +++ b/repoman/cnf/qa_data/qa_data.yaml @@ -129,6 +129,7 @@ qahelp: obsolete: "The ebuild makes use of an obsolete construct" variable: invalidchar: "A variable contains an invalid character that is not part of the ASCII character set" + phase: "Variable referenced found within scope of incorrect ebuild phase as specified by PMS" readonly: "Assigning a readonly variable" usedwithhelpers: "Ebuild uses D, ROOT, BROOT, ED, EROOT or EPREFIX with helpers" virtual: diff --git a/repoman/cnf/repository/qa_data.yaml b/repoman/cnf/repository/qa_data.yaml index 3fe6b53d5..2249000c3 100644 --- a/repoman/cnf/repository/qa_data.yaml +++ b/repoman/cnf/repository/qa_data.yaml @@ -80,6 +80,7 @@ qawarnings: - usage.obsolete - upstream.workaround - uri.https + - variable.phase - virtual.suspect - wxwidgets.eclassnotused diff --git a/repoman/cnf/repository/repository.yaml b/repoman/cnf/repository/repository.yaml index ad00d18c1..dbc1decaa 100644 --- a/repoman/cnf/repository/repository.yaml +++ b/repoman/cnf/repository/repository.yaml @@ -61,6 +61,7 @@ linechecks_modules: emakeparallel srccompileeconf srcunpackpatches + pmsvariablerefphasescope portageinternal portageinternalvariableassignment quote diff --git a/repoman/lib/repoman/modules/linechecks/phases/__init__.py b/repoman/lib/repoman/modules/linechecks/phases/__init__.py index 686c675d2..e166b31a3 100644 --- a/repoman/lib/repoman/modules/linechecks/phases/__init__.py +++ b/repoman/lib/repoman/modules/linechecks/phases/__init__.py @@ -29,6 +29,12 @@ module_spec = { 'class': "SrcUnpackPatches", 'description': doc, }, + 'pmsvariablerefphasescope-check': { + 'name': "pmsvariablerefphasescope", + 'sourcefile': "phase", + 'class': "PMSVariableReference", + 'description': doc, + }, }, 'version': 1, } diff --git a/repoman/lib/repoman/modules/linechecks/phases/phase.py b/repoman/lib/repoman/modules/linechecks/phases/phase.py index 74cf4608f..433e93601 100644 --- a/repoman/lib/repoman/modules/linechecks/phases/phase.py +++ b/repoman/lib/repoman/modules/linechecks/phases/phase.py @@ -1,7 +1,19 @@ +import fnmatch import re - -from portage.eapi import eapi_has_src_prepare_and_src_configure +import types + +from portage.eapi import ( + eapi_has_broot, + eapi_has_sysroot, + eapi_has_src_prepare_and_src_configure, + eapi_exports_AA, + eapi_exports_replace_vars, + eapi_exports_ECLASSDIR, + eapi_exports_PORTDIR, + eapi_supports_prefix, + eapi_exports_merge_type, +) from repoman.modules.linechecks.base import LineCheck @@ -9,11 +21,22 @@ class PhaseCheck(LineCheck): """ basic class for function detection """ func_end_re = re.compile(r'^\}$') - phases_re = re.compile('(%s)' % '|'.join(( - 'pkg_pretend', 'pkg_setup', 'src_unpack', 'src_prepare', - 'src_configure', 'src_compile', 'src_test', 'src_install', - 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', - 'pkg_config'))) + phase_funcs = ( + 'pkg_pretend', + 'pkg_setup', + 'src_unpack', + 'src_prepare', + 'src_configure', + 'src_compile', + 'src_test', + 'src_install', + 'pkg_preinst', + 'pkg_postinst', + 'pkg_prerm', + 'pkg_postrm', + 'pkg_config', + ) + phases_re = re.compile('(%s)' % '|'.join(phase_funcs)) in_phase = '' def check(self, num, line): @@ -69,3 +92,98 @@ class SrcUnpackPatches(PhaseCheck): if m is not None: return ("'%s'" % m.group(1)) + \ " call should be moved to src_prepare" + +# Refererences +# - https://projects.gentoo.org/pms/7/pms.html#x1-10900011.1 +# - https://pkgcore.github.io/pkgcheck/_modules/pkgcheck/checks/codingstyle.html#VariableScopeCheck +_pms_vars = ( + ("A", None, ("src_*", "pkg_nofetch")), + ("AA", eapi_exports_AA, ("src_*", "pkg_nofetch")), + ("FILESDIR", None, ("src_*",)), + ("DISTDIR", None, ("src_*",)), + ("WORKDIR", None, ("src_*",)), + ("S", None, ("src_*",)), + ("PORTDIR", eapi_exports_PORTDIR, ("src_*",)), + ("ECLASSDIR", eapi_exports_ECLASSDIR, ("src_*",)), + ("ROOT", None, ("pkg_*",)), + ("EROOT", eapi_supports_prefix, ("pkg_*",)), + ("SYSROOT", eapi_has_sysroot, ("src_*", "pkg_setup")), + ("ESYSROOT", eapi_has_sysroot, ("src_*", "pkg_setup")), + ("BROOT", eapi_has_broot, ("src_*", "pkg_setup")), + ("D", None, ("src_install", "pkg_preinst", "pkg_postint")), + ("ED", eapi_supports_prefix, ("src_install", "pkg_preinst", "pkg_postint")), + ("DESTTREE", None, ("src_install",)), + ("INSDESTTREE", None, ("src_install",)), + ("MERGE_TYPE", eapi_exports_merge_type, ("pkg_*",)), + ("REPLACING_VERSIONS", eapi_exports_replace_vars, ("pkg_*",)), + ("REPLACED_BY_VERSION", eapi_exports_replace_vars, ("pkg_prerm", "pkg_postrm")), +) + + +def _compile_phases(): + phase_vars = {} + for phase_func in PhaseCheck.phase_funcs: + for variable, eapi_filter, allowed_scopes in _pms_vars: + allowed = False + for scope in allowed_scopes: + if fnmatch.fnmatch(phase_func, scope): + allowed = True + break + + if not allowed: + phase_vars.setdefault(phase_func, []).append((variable, eapi_filter)) + + phase_info = {} + for phase_func, prohibited_vars in phase_vars.items(): + phase_func_vars = [] + for variable, eapi_filter in prohibited_vars: + phase_func_vars.append(variable) + phase_obj = phase_info[phase_func] = types.SimpleNamespace() + phase_obj.prohibited_vars = dict(prohibited_vars) + phase_obj.var_names = "(%s)" % "|".join( + variable for variable, eapi_filter in prohibited_vars + ) + phase_obj.var_reference = re.compile( + r"\$(\{|)%s(\}|\W)" % (phase_obj.var_names,) + ) + + return phase_info + + +class PMSVariableReference(PhaseCheck): + """Check phase scope for references to variables specified by PMS""" + + repoman_check_name = "variable.phase" + phase_info = _compile_phases() + + def new(self, pkg): + self._eapi = pkg.eapi + + def end(self): + self._eapi = None + + def phase_check(self, num, line): + try: + phase_info = self.phase_info[self.in_phase] + except KeyError: + return + + eapi = self._eapi + issues = [] + for m in phase_info.var_reference.finditer(line): + open_brace = m.group(1) + var_name = m.group(2) + close_brace = m.group(3) + # discard \W if matched by (\}|\W) + close_brace = close_brace if close_brace == "}" else "" + if bool(open_brace) != bool(close_brace): + continue + var_name = m.group(2) + eapi_filter = phase_info.prohibited_vars[var_name] + if eapi_filter is not None and not eapi_filter(eapi): + continue + issues.append( + "phase %s: EAPI %s: variable %s: Forbidden reference to variable specified by PMS" + % (self.in_phase, eapi, var_name) + ) + return issues diff --git a/repoman/lib/repoman/tests/simple/test_simple.py b/repoman/lib/repoman/tests/simple/test_simple.py index e64ba4f07..3a699a708 100644 --- a/repoman/lib/repoman/tests/simple/test_simple.py +++ b/repoman/lib/repoman/tests/simple/test_simple.py @@ -3,6 +3,7 @@ import collections import subprocess +import sys import time import types @@ -141,6 +142,12 @@ class SimpleRepomanTestCase(TestCase): """ % time.gmtime().tm_year + pkg_preinst_references_forbidden_var = """ +pkg_preinst() { + echo "This ${A} reference is not allowed. Neither is this $BROOT reference." +} +""" + repo_configs = { "test_repo": { "layout.conf": @@ -194,13 +201,14 @@ class SimpleRepomanTestCase(TestCase): "dev-libs/C-0": { "COPYRIGHT_HEADER" : copyright_header, "DESCRIPTION" : "Desc goes here", - "EAPI" : "4", + "EAPI" : "7", "HOMEPAGE" : "https://example.com", "IUSE" : "flag", # must be unstable, since dev-libs/A[flag] is stable masked "KEYWORDS": "~x86", "LICENSE": "GPL-2", "RDEPEND": "flag? ( dev-libs/A[flag] )", + "MISC_CONTENT": pkg_preinst_references_forbidden_var, }, } licenses = ["GPL-2"] @@ -292,7 +300,15 @@ class SimpleRepomanTestCase(TestCase): committer_name = "Gentoo Dev" committer_email = "[email protected]" - expected_warnings = {"returncode": 0} + expected_warnings = { + "returncode": 0, + "warns": { + "variable.phase": [ + "dev-libs/C/C-0.ebuild: line 15: phase pkg_preinst: EAPI 7: variable A: Forbidden reference to variable specified by PMS", + "dev-libs/C/C-0.ebuild: line 15: phase pkg_preinst: EAPI 7: variable BROOT: Forbidden reference to variable specified by PMS", + ] + }, + } git_test = ( ("", RepomanRun(args=["manifest"])), @@ -380,7 +396,7 @@ class SimpleRepomanTestCase(TestCase): # triggered by python -Wd will be visible. stdout = subprocess.PIPE - for cwd in ("", "dev-libs", "dev-libs/A", "dev-libs/B"): + for cwd in ("", "dev-libs", "dev-libs/A", "dev-libs/B", "dev-libs/C"): abs_cwd = os.path.join(test_repo_symlink, cwd) proc = await asyncio.create_subprocess_exec( @@ -413,7 +429,11 @@ class SimpleRepomanTestCase(TestCase): await cmd.run() if cmd.result["result"] != cmd.expected and cmd.result.get("stdio"): portage.writemsg(cmd.result["stdio"]) - self.assertEqual(cmd.result["result"], cmd.expected) + try: + self.assertEqual(cmd.result["result"], cmd.expected) + except Exception: + print(cmd.result["result"], file=sys.stderr, flush=True) + raise continue proc = await asyncio.create_subprocess_exec( diff --git a/repoman/man/repoman.1 b/repoman/man/repoman.1 index 0926e806c..5dbc41560 100644 --- a/repoman/man/repoman.1 +++ b/repoman/man/repoman.1 @@ -1,4 +1,4 @@ -.TH "REPOMAN" "1" "Aug 2020" "Repoman VERSION" "Repoman" +.TH "REPOMAN" "1" "March 2021" "Repoman VERSION" "Repoman" .SH NAME repoman \- Gentoo's program to enforce a minimal level of quality assurance in packages added to the ebuild repository @@ -445,6 +445,9 @@ The ebuild makes use of an obsolete construct A variable contains an invalid character that is not part of the ASCII character set. .TP +.B variable.phase +Variable referenced found within scope of incorrect ebuild phase as specified by PMS. +.TP .B variable.readonly Assigning a readonly variable .TP
