From d69f8d5616ff0a2120e021140c8a9bf47dc7f138 Mon Sep 17 00:00:00 2001 From: Fabian Groffen Date: Tue, 1 Apr 2008 18:13:57 +0000 Subject: [PATCH] Merged from trunk 9583:9619 | 9584 | Bug #214691 - Move the "slot collision" display so that it | | zmedico | is shown after the merge list where it is most likely to be | | | seen (along with other depgraph problems). | | 9585 | Add a new "java.eclassesnotused" check for cases where | | zmedico | DEPEND contains virtual/jdk and and the appropriate java | | | eclass has not been inherited (a violation of the java | | | team's policy). Thanks to Betelgeuse for the initial patch. | | 9586 | Don't trigger the 'java.eclassesnotused' on blocker atoms. | | zmedico | | | 9587 | Add a new "ebuild.patches" check for the PATCHES variable | | zmedico | that's used by base_src_unpack() from base.eclass. This | | | generates a warning if the variable is not defined as an | | | array, since this is required for white space safety. Thanks | | | to Betelgeuse for the initial patch. | | 9588 | Bug #215240 - Use an alarm signal to implement a 60 second | | zmedico | timeout in finalize() in case send_mail() blocks | | | indefinitely. | | 9589 | When a resume list contains packages that are no longer | | zmedico | available, automatically delete it. | | 9590 | Fix quoting for ${root} in install_mask(). | | zmedico | | | 9591 | Add a note about PKG_INSTALL_MASK. | | zmedico | | | 9592 | Bug #214619 - Run dyn_package() and preinst_mask() from ${T} | | zmedico | instead of ${D} in order to avoid leaving gmon.out files in | | | ${D} in case any tools were built with -pf in CFLAGS. Also, | | | call preinst_mask() after all other functions so that | | | INSTALL_MASK can be used to wipe out any gmon.out files | | | created during previous functions. | | 9606 | When dyn_unpack() automatically cleans ${WORKDIR}, also | | zmedico | remove things like .unpacked and .compiled so that the state | | | is consistent. | | 9607 | Bug #215308 - Simplify the greedy atoms logic so that it | | zmedico | behaves more like StaticFileSet and won't pull in lower | | | slots. | | 9611 | For bugs #197810 and #215308, pass the depgraph's | | zmedico | "selective" parameter down into dep_check() for better atom | | | preference selection when handling virtuals and other | | | disjunctive || dependencies. (branches/2.1.2 r9610) | | 9613 | Use enumerate() it iterate over an array. | | zmedico | | | 9615 | Bug #211365 - Use catpkgsplit() instead of pkgsplit() for a | | zmedico | cpv. | | 9616 | Make code a bit less prone to potentially hidden TypeError | | zmedico | like bugs. | | 9619 | Do not allow commit mode together with the --without-mask | | zmedico | options. | svn path=/main/branches/prefix/; revision=9666 --- bin/ebuild.sh | 1 + bin/misc-functions.sh | 14 ++-- bin/repoman | 26 ++++++-- man/make.conf.5 | 5 +- man/repoman.1 | 9 ++- pym/_emerge/__init__.py | 97 +++++++++++++++++----------- pym/portage/__init__.py | 12 ++-- pym/portage/elog/mod_mail_summary.py | 13 +++- pym/repoman/checks.py | 12 +++- pym/repoman/errors.py | 1 + 10 files changed, 133 insertions(+), 57 deletions(-) diff --git a/bin/ebuild.sh b/bin/ebuild.sh index 7811e439b..1cd140e3d 100755 --- a/bin/ebuild.sh +++ b/bin/ebuild.sh @@ -669,6 +669,7 @@ dyn_unpack() { fi if [ "${newstuff}" == "yes" ]; then # We don't necessarily have privileges to do a full dyn_clean here. + rm -rf "${PORTAGE_BUILDDIR}"/{.unpacked,.compiled,.tested,.packaged,build-info} rm -rf "${WORKDIR}" if [ -d "${T}" ] && \ ! hasq keeptemp $FEATURES && ! hasq keepwork $FEATURES ; then diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh index 7736974d2..90b3afb2f 100644 --- a/bin/misc-functions.sh +++ b/bin/misc-functions.sh @@ -496,7 +496,7 @@ install_mask() { set +o noglob quiet_mode || einfo "Removing ${no_inst}" # normal stuff - rm -Rf ${root}/${no_inst} >&/dev/null + rm -Rf "${root}"/${no_inst} >&/dev/null # we also need to handle globs (*.a, *.h, etc) find "${root}" -path "${no_inst}" -exec rm -fR {} \; >/dev/null @@ -529,6 +529,11 @@ preinst_mask() { eerror "${FUNCNAME}: D is unset" return 1 fi + + # Make sure $PWD is not ${D} so that we don't leave gmon.out files + # in there in case any tools were built with -pg in CFLAGS. + cd "${T}" + # remove man pages, info pages, docs if requested for f in man info doc; do if hasq no${f} $FEATURES; then @@ -649,7 +654,9 @@ preinst_selinux_labels() { } dyn_package() { - cd "${PORTAGE_BUILDDIR}/image" + # Make sure $PWD is not ${D} so that we don't leave gmon.out files + # in there in case any tools were built with -pg in CFLAGS. + cd "${T}" install_mask "${PORTAGE_BUILDDIR}/image" "${PKG_INSTALL_MASK}" local tar_options="" [ "${PORTAGE_QUIET}" == "1" ] || tar_options="${tar_options} -v" @@ -659,10 +666,9 @@ dyn_package() { [ -z "${PORTAGE_BINPKG_TMPFILE}" ] && \ PORTAGE_BINPKG_TMPFILE="${PKGDIR}/${CATEGORY}/${PF}.tbz2" mkdir -p "${PORTAGE_BINPKG_TMPFILE%/*}" || die "mkdir failed" - tar $tar_options -cf - $PORTAGE_BINPKG_TAR_OPTS . | \ + tar $tar_options -cf - $PORTAGE_BINPKG_TAR_OPTS -C "${D}" . | \ bzip2 -f > "$PORTAGE_BINPKG_TMPFILE" || \ die "Failed to create tarball" - cd .. export PYTHONPATH=${PORTAGE_PYM_PATH:-${EPREFIX}/usr/lib/portage/pym} python -c "from portage import xpak; t=xpak.tbz2('${PORTAGE_BINPKG_TMPFILE}'); t.recompose('${PORTAGE_BUILDDIR}/build-info')" if [ $? -ne 0 ]; then diff --git a/bin/repoman b/bin/repoman index 94802c4eb..5bbe86e15 100755 --- a/bin/repoman +++ b/bin/repoman @@ -187,7 +187,7 @@ def ParseArgs(args, qahelp): default=False, help='ignore masked packages (not allowed with commit mode)') parser.add_option('--without-mask', dest='without_mask', action='store_true', - default=False, help='behave as if no package.mask entries exist') + default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)') parser.add_option('--mode', type='choice', dest='mode', choices=modes.keys(), help='specify which mode repoman will run in (default=full)') @@ -220,8 +220,11 @@ def ParseArgs(args, qahelp): if not opts.mode: opts.mode = 'full' #default to full - if opts.mode == 'commit' and opts.ignore_masked: - parser.error('Commit mode and --ignore_masked are not compatable') + if opts.mode == 'commit' and not (opts.force or opts.pretend): + if opts.ignore_masked: + parser.error('Commit mode and --ignore-masked are not compatable') + if opts.without_mask: + parser.error('Commit mode and --without-mask are not compatable') # Use the verbosity and quiet options to fiddle with the loglevel appropriately for val in range(opts.verbosity): @@ -242,12 +245,14 @@ qahelp={ "changelog.missing":"Missing ChangeLog files", "ebuild.disjointed":"Ebuilds not added to cvs when the matching digest has been added", "ebuild.notadded":"Ebuilds that exist but have not been added to cvs", + "ebuild.patches":"PATCHES variable should be a bash array to ensure white space safety", "changelog.notadded":"ChangeLogs that exist but have not been added to cvs", "filedir.missing":"Package lacks a files directory", "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do note need the executable bit", "file.size":"Files in the files directory must be under 20k", "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars, "file.UTF8":"File is not UTF8 compliant", + "java.eclassesnotused":"With virtual/jdk in DEPEND you must inherit a java eclass", "KEYWORDS.dropped":"Ebuilds that appear to have dropped KEYWORDS for some arch", "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable", "KEYWORDS.stable":"Ebuilds that have been added directly with stable KEYWORDS", @@ -323,7 +328,9 @@ qawarnings=[ "RESTRICT.invalid", "ebuild.minorsyn", "ebuild.badheader", +"ebuild.patches", "file.size", +"java.eclassesnotused", "metadata.missing", "metadata.bad", "virtual.versioned", @@ -1025,6 +1032,7 @@ for x in scanlist: myaux = ebuild_metadata[y] eapi = myaux["EAPI"] + inherited = myaux["INHERITED"].split() # Test for negative logic and bad words in the RESTRICT var. #for x in myaux[allvars.index("RESTRICT")].split(): @@ -1157,6 +1165,7 @@ for x in scanlist: myflag = myflag[1:] myiuse.add(myflag) + inherited_java_eclass = "java-pkg" in inherited operator_tokens = set(["||", "(", ")"]) type_list, badsyntax = [], [] for mytype in ("DEPEND", "RDEPEND", "PDEPEND", "LICENSE", "PROVIDE"): @@ -1204,7 +1213,16 @@ for x in scanlist: if not portage.isvalidatom(token, allow_blockers=True): badsyntax.append("'%s' not a valid atom" % token) else: - atom = token.lstrip("!") + atom = token + is_blocker = atom.startswith("!") + if is_blocker: + atom = token.lstrip("!") + if mytype == "DEPEND" and \ + not is_blocker and \ + not inherited_java_eclass and \ + portage.dep_getkey(atom) == "virtual/jdk": + stats['java.eclassesnotused'] += 1 + fails['java.eclassesnotused'].append(relative_path) if eapi == "0": if portage.dep.dep_getslot(atom): stats['EAPI.incompatible'] += 1 diff --git a/man/make.conf.5 b/man/make.conf.5 index d5d21a6f8..7f9a49618 100644 --- a/man/make.conf.5 +++ b/man/make.conf.5 @@ -332,7 +332,10 @@ proxy server for internet access. Use this variable if you want to selectively prevent certain files from being copied into your file system tree. This does not work on symlinks, but only on actual files. Useful if you wish to filter out files like HACKING.gz and -TODO.gz. +TODO.gz. The \fBINSTALL_MASK\fR is processed just before a package is merged. +Also supported is a \fBPKG_INSTALL_MASK\fR variable that behaves exactly like +\fBINSTALL_MASK\fR except that it is processed just before creation of a binary +package. .TP .B MAKEOPTS Use this variable if you want to use parallel make. For example, if you diff --git a/man/repoman.1 b/man/repoman.1 index f21752425..1beb13065 100644 --- a/man/repoman.1 +++ b/man/repoman.1 @@ -41,7 +41,7 @@ Ignore arch-specific failures (where arch != host) Ignore masked packages (not allowed with commit mode) .TP \fB\-\-without\-mask\fR -Behave as if no package.mask entries exist +Behave as if no package.mask entries exist (not allowed with commit mode) .TP \fB-m\fR, \fB--commitmsg\fR Adds a commit message via the command line @@ -219,6 +219,9 @@ Ebuilds that exist but have not been added to cvs .B ebuild.output A simple sourcing of the ebuild produces output; this breaks ebuild policy. .TP +.B ebuild.patches +PATCHES variable should be a bash array to ensure white space safety +.TP .B ebuild.syntax Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure. @@ -238,6 +241,10 @@ Files in the files directory must be under 20k .B filedir.missing Package lacks a files directory .TP +.B java.eclassesnotused +With virtual/jdk in DEPEND you must inherit a java eclass. Refer to +\fIhttp://www.gentoo.org/proj/en/java/java\-devel.xml\fR for more information. +.TP .B metadata.bad Bad metadata.xml files .TP diff --git a/pym/_emerge/__init__.py b/pym/_emerge/__init__.py index cd49064bd..46d2e2fd4 100644 --- a/pym/_emerge/__init__.py +++ b/pym/_emerge/__init__.py @@ -1547,6 +1547,7 @@ class depgraph(object): self._pprovided_args = [] self._missing_args = [] self._masked_installed = [] + self._unsatisfied_deps_for_display = [] self._dep_stack = [] self._unsatisfied_deps = [] self._ignored_deps = [] @@ -1561,6 +1562,9 @@ class depgraph(object): already been selected) will be required in order to handle all possible cases.""" + if not self._slot_collision_info: + return + msg = [] msg.append("\n!!! Multiple versions within a single " + \ "package slot have been \n") @@ -1694,8 +1698,8 @@ class depgraph(object): if allow_unsatisfied: self._unsatisfied_deps.append(dep) continue - self._show_unsatisfied_dep(dep.root, dep.atom, - myparent=dep.parent) + self._unsatisfied_deps_for_display.append( + ((dep.root, dep.atom), {"myparent":dep.parent})) return 0 # In some cases, dep_check will return deps that shouldn't # be proccessed any further, so they are identified and @@ -2002,28 +2006,11 @@ class depgraph(object): myslots = set() for cpv in vardb.match(mykey): myslots.add(vardb.aux_get(cpv, ["SLOT"])[0]) - if myslots: - self._populate_filtered_repo(root, atom, - exclude_installed=True) - mymatches = filtered_db.match(atom) - best_pkg = portage.best(mymatches) - if best_pkg: - best_slot = filtered_db.aux_get(best_pkg, ["SLOT"])[0] - myslots.add(best_slot) - if len(myslots) > 1: - for myslot in myslots: - myslot_atom = "%s:%s" % (mykey, myslot) - self._populate_filtered_repo( - root, myslot_atom, - exclude_installed=True) - if filtered_db.match(myslot_atom): - yield myslot_atom - - # Since populate_filtered_repo() was called with the - # exclude_installed flag, these atoms will need to be processed - # again in case installed packages are required to satisfy - # dependencies. - self._filtered_trees[root]["atoms"].clear() + for myslot in myslots: + yield "%s:%s" % (mykey, myslot) + # In addition to any installed slots, also try to pull + # in the latest new slot that may be available. + yield atom def _iter_args_for_pkg(self, pkg): # TODO: add multiple $ROOT support @@ -2310,12 +2297,14 @@ class depgraph(object): if not pkg: if not (isinstance(arg, SetArg) and \ arg.name in ("system", "world")): - self._show_unsatisfied_dep(myroot, atom) + self._unsatisfied_deps_for_display.append( + ((myroot, atom), {})) return 0, myfavorites self._missing_args.append((arg, atom)) continue if pkg.installed and "selective" not in self.myparams: - self._show_unsatisfied_dep(myroot, atom) + self._unsatisfied_deps_for_display.append( + ((myroot, atom), {})) return 0, myfavorites self._dep_stack.append( @@ -2516,12 +2505,14 @@ class depgraph(object): myuse=myuse, strict=strict) if True: try: + self.trees[root]["selective"] = "selective" in self.myparams if not strict: portage.dep._dep_check_strict = False mycheck = portage.dep_check(depstring, None, pkgsettings, myuse=myuse, myroot=root, trees=trees) finally: + self.trees[root]["selective"] = False portage.dep._dep_check_strict = True if not mycheck[0]: raise portage.exception.InvalidDependString(mycheck[1]) @@ -3122,8 +3113,8 @@ class depgraph(object): # unresolvable blocks. for x in self.altlist(): if x[0] == "blocks": + self._slot_collision_info.clear() return True - self._show_slot_collision_notice() if not self._accept_collisions(): return False return True @@ -3716,7 +3707,7 @@ class depgraph(object): #we need to use "--emptrytree" testing here rather than "empty" param testing because "empty" #param is used for -u, where you still *do* want to see when something is being upgraded. - myoldbest = "" + myoldbest = [] myinslotlist = None installed_versions = vardb.match(portage.cpv_getkey(pkg_key)) if vardb.cpv_exists(pkg_key): @@ -3739,9 +3730,10 @@ class depgraph(object): portage.cpv_getkey(pkg_key): myinslotlist = None if myinslotlist: - myoldbest = portage.best(myinslotlist) + myoldbest = myinslotlist[:] addl = " " + fetch - if portage.pkgcmp(portage.pkgsplit(x[2]), portage.pkgsplit(myoldbest)) < 0: + if not portage.dep.cpvequal(pkg_key, + portage.best([pkg_key] + myoldbest)): # Downgrade in slot addl += turquoise("U")+blue("D") if ordered: @@ -3789,7 +3781,7 @@ class depgraph(object): cur_use = [flag for flag in cur_use if flag in cur_iuse] if myoldbest and myinslotlist: - pkg = myoldbest + pkg = myoldbest[0] else: pkg = x[2] if self.trees[x[1]]["vartree"].dbapi.cpv_exists(pkg): @@ -3950,17 +3942,17 @@ class depgraph(object): indent = " " * depth - if myoldbest: - if myinslotlist: - myoldbest = [myoldbest] - for key in myoldbest: - pos = myoldbest.index(key) - key = portage.pkgsplit(key)[1] + "-" + portage.pkgsplit(key)[2] + # Convert myoldbest from a list to a string. + if not myoldbest: + myoldbest = "" + else: + for pos, key in enumerate(myoldbest): + key = portage.catpkgsplit(key)[2] + \ + "-" + portage.catpkgsplit(key)[3] if key[-3:] == "-r0": key = key[:-3] myoldbest[pos] = key myoldbest = blue("["+", ".join(myoldbest)+"]") - pkg_cp = xs[0] root_config = self.roots[myroot] @@ -4079,6 +4071,20 @@ class depgraph(object): print bold('*'+revision) sys.stdout.write(text) + self.display_problems() + return os.EX_OK + + def display_problems(self): + """ + Display problems with the dependency graph such as slot collisions. + This is called internally by display() to show the problems _after_ + the merge list where it is most likely to be seen, but if display() + is not going to be called then this method should be called explicitly + to ensure that the user is notified of problems with the graph. + """ + + self._show_slot_collision_notice() + # TODO: Add generic support for "set problem" handlers so that # the below warnings aren't special cases for world only. @@ -4154,7 +4160,9 @@ class depgraph(object): msg.append("The best course of action depends on the reason that an offending\n") msg.append("package.provided entry exists.\n\n") sys.stderr.write("".join(msg)) - return os.EX_OK + + for pargs, kwargs in self._unsatisfied_deps_for_display: + self._show_unsatisfied_dep(*pargs, **kwargs) def calc_changelog(self,ebuildpath,current,next): if ebuildpath == None or not os.path.exists(ebuildpath): @@ -6883,6 +6891,10 @@ def action_build(settings, trees, mtimedb, merge_count = 0 pretend = "--pretend" in myopts fetchonly = "--fetchonly" in myopts or "--fetch-all-uri" in myopts + ask = "--ask" in myopts + tree = "--tree" in myopts + verbose = "--verbose" in myopts + quiet = "--quiet" in myopts if pretend or fetchonly: # make the mtimedb readonly mtimedb.filename = None @@ -6968,6 +6980,8 @@ def action_build(settings, trees, mtimedb, out.eerror("Error: The resume list contains packages that are no longer") out.eerror(" available to be emerged. Please restart/continue") out.eerror(" the merge operation manually.") + del mtimedb["resume"] + mtimedb.commit() return 1 if show_spinner: print "\b\b... done!" @@ -6987,9 +7001,14 @@ def action_build(settings, trees, mtimedb, portage.writemsg("\n!!! %s\n" % str(e), noiselevel=-1) return 1 if not retval: + mydepgraph.display_problems() return 1 if "--quiet" not in myopts and "--nodeps" not in myopts: print "\b\b... done!" + display = pretend or \ + ((ask or tree or verbose) and not (quiet and not ask)) + if not display: + mydepgraph.display_problems() if "--pretend" not in myopts and \ ("--ask" in myopts or "--tree" in myopts or \ diff --git a/pym/portage/__init__.py b/pym/portage/__init__.py index d0653e050..2310ed84b 100644 --- a/pym/portage/__init__.py +++ b/pym/portage/__init__.py @@ -4755,11 +4755,14 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, phase_retval = exit_status_check(phase_retval) if phase_retval == os.EX_OK: # Post phase logic and tasks that have been factored out of - # ebuild.sh. + # ebuild.sh. Call preinst_mask last so that INSTALL_MASK can + # can be used to wipe out any gmon.out files created during + # previous functions (in case any tools were built with -pg + # in CFLAGS). myargs = [_shell_quote(misc_sh_binary), - "preinst_bsdflags", "preinst_mask", + "preinst_bsdflags", "preinst_sfperms", "preinst_selinux_labels", - "preinst_suid_scan"] + "preinst_suid_scan", "preinst_mask"] _doebuild_exit_status_unlink( mysettings.get("EBUILD_EXIT_STATUS_FILE")) mysettings["EBUILD_PHASE"] = "" @@ -5363,6 +5366,7 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): if trees is None: global db trees = db + selective = trees[myroot].get("selective", False) writemsg("ZapDeps -- %s\n" % (use_binaries), 2) if not reduced or unreduced == ["||"] or dep_eval(reduced): return [] @@ -5426,7 +5430,7 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): has_mask = False if hasattr(mydbapi, "xmatch"): has_mask = bool(mydbapi.xmatch("match-all", atom)) - if (use_binaries or not has_mask): + if (selective or use_binaries or not has_mask): avail_pkg = best(vardb.match(atom)) if avail_pkg: avail_slot = "%s:%s" % (dep_getkey(atom), diff --git a/pym/portage/elog/mod_mail_summary.py b/pym/portage/elog/mod_mail_summary.py index 40a2484a6..8f7f86217 100644 --- a/pym/portage/elog/mod_mail_summary.py +++ b/pym/portage/elog/mod_mail_summary.py @@ -51,8 +51,19 @@ def _finalize(mysettings, items): mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, mybody, attachments=items.values()) + + def timeout_handler(signum, frame): + raise PortageException("Timeout in finalize() for elog system 'mail_summary'") + import signal + signal.signal(signal.SIGALRM, timeout_handler) + # Timeout after one minute in case send_mail() blocks indefinitely. + signal.alarm(60) + try: - portage.mail.send_mail(mysettings, mymessage) + try: + portage.mail.send_mail(mysettings, mymessage) + finally: + signal.alarm(0) except PortageException, e: writemsg("%s\n" % str(e), noiselevel=-1) diff --git a/pym/repoman/checks.py b/pym/repoman/checks.py index aa5bf1845..bc1c29115 100644 --- a/pym/repoman/checks.py +++ b/pym/repoman/checks.py @@ -18,8 +18,8 @@ class LineCheck(object): def check(self, num, line): """Run the check on line and return error if there is one""" - pass - + if self.re.match(line): + return self.error class EbuildHeader(LineCheck): """Ensure ebuilds have proper headers @@ -191,6 +191,11 @@ class EbuildUselessCdS(LineCheck): elif self.method_re.match(line): self.check_next_line = True +class EbuildPatches(LineCheck): + """Ensure ebuilds use bash arrays for PATCHES to ensure white space safety""" + repoman_check_name = 'ebuild.patches' + re = re.compile(r'^\s*PATCHES=[^\(]') + error = errors.PATCHES_ERROR class EbuildQuotedA(LineCheck): """Ensure ebuilds have no quoting around ${A}""" @@ -206,7 +211,8 @@ class EbuildQuotedA(LineCheck): _constant_checks = tuple((c() for c in ( EbuildWhitespace, EbuildQuote, EbuildAssignment, EbuildUselessDodoc, - EbuildUselessCdS, EbuildNestedDie, EbuildQuotedA))) + EbuildUselessCdS, EbuildNestedDie, + EbuildPatches, EbuildQuotedA))) def run_checks(contents, st_mtime): checks = list(_constant_checks) diff --git a/pym/repoman/errors.py b/pym/repoman/errors.py index d1aad1d8a..90fa83820 100644 --- a/pym/repoman/errors.py +++ b/pym/repoman/errors.py @@ -11,4 +11,5 @@ TRAILING_WHITESPACE_ERROR = 'Trailing whitespace error on line: %d' READONLY_ASSIGNMENT_ERROR = 'Ebuild contains assignment to read-only variable on line: %d' MISSING_QUOTES_ERROR = 'Unquoted Variable on line: %d' NESTED_DIE_ERROR = 'Ebuild calls die in a subshell on line: %d' +PATCHES_ERROR = 'PATCHES is not a bash array on line: %d' REDUNDANT_CD_S_ERROR = 'Ebuild has redundant cd ${S} statement on line: %d' -- 2.26.2