| #!/usr/bin/env python |
| # Copyright 1999-2023 Gentoo Authors |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import os |
| import signal |
| |
| # For compatibility with Python < 3.8 |
| raise_signal = getattr( |
| signal, "raise_signal", lambda signum: os.kill(os.getpid(), signum) |
| ) |
| |
| |
| # Inherit from KeyboardInterrupt to avoid a traceback from asyncio. |
| class SignalInterrupt(KeyboardInterrupt): |
| def __init__(self, signum): |
| self.signum = signum |
| |
| |
| def signal_interrupt(signum, _frame): |
| raise SignalInterrupt(signum) |
| |
| |
| def debug_signal(_signum, _frame): |
| import pdb |
| |
| pdb.set_trace() |
| |
| |
| # Prevent "[Errno 32] Broken pipe" exceptions when writing to a pipe. |
| signal.signal(signal.SIGPIPE, signal.SIG_DFL) |
| signal.signal(signal.SIGTERM, signal_interrupt) |
| signal.signal(signal.SIGUSR1, debug_signal) |
| |
| import argparse |
| from os import path as osp |
| import sys |
| import textwrap |
| |
| if osp.isfile( |
| osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), ".portage_not_installed") |
| ): |
| sys.path.insert( |
| 0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "lib") |
| ) |
| import portage |
| |
| portage._internal_caller = True |
| from portage import os |
| from portage import _encodings |
| from portage import _shell_quote |
| from portage import _unicode_encode |
| from portage.const import VDB_PATH |
| from portage.exception import ( |
| PermissionDenied, |
| PortageKeyError, |
| PortagePackageException, |
| UnsupportedAPIException, |
| ) |
| from portage.localization import _ |
| import portage.util |
| from portage.util._eventloop.global_event_loop import global_event_loop |
| from _emerge.actions import apply_priorities |
| from _emerge.Package import Package |
| from _emerge.RootConfig import RootConfig |
| |
| |
| def main(): |
| portage.process.sanitize_fds() |
| description = "See the ebuild(1) man page for more info" |
| usage = "Usage: ebuild <ebuild file> <command> [command] ..." |
| parser = argparse.ArgumentParser(description=description, usage=usage) |
| |
| force_help = ( |
| "When used together with the digest or manifest " |
| + "command, this option forces regeneration of digests for all " |
| + "distfiles associated with the current ebuild. Any distfiles " |
| + "that do not already exist in ${DISTDIR} will be automatically fetched." |
| ) |
| |
| parser.add_argument("--force", help=force_help, action="store_true") |
| parser.add_argument( |
| "--color", help="enable or disable color output", choices=("y", "n") |
| ) |
| parser.add_argument("--debug", help="show debug output", action="store_true") |
| parser.add_argument("--version", help="show version and exit", action="store_true") |
| parser.add_argument( |
| "--ignore-default-opts", |
| action="store_true", |
| help="do not use the EBUILD_DEFAULT_OPTS environment variable", |
| ) |
| parser.add_argument( |
| "--skip-manifest", help="skip all manifest checks", action="store_true" |
| ) |
| |
| opts, pargs = parser.parse_known_args(args=sys.argv[1:]) |
| |
| def err(txt): |
| portage.writemsg(f"ebuild: {txt}\n", noiselevel=-1) |
| sys.exit(1) |
| |
| if opts.version: |
| print("Portage", portage.VERSION) |
| sys.exit(os.EX_OK) |
| |
| if len(pargs) < 2: |
| parser.error("missing required args") |
| |
| if not opts.ignore_default_opts: |
| default_opts = portage.util.shlex_split( |
| portage.settings.get("EBUILD_DEFAULT_OPTS", "") |
| ) |
| opts, pargs = parser.parse_known_args(default_opts + sys.argv[1:]) |
| |
| debug = opts.debug |
| force = opts.force |
| |
| if debug: |
| # Ensure that all config instances have this setting, |
| # including the one that's used by portdbapi for aux_get. |
| os.environ["PORTAGE_DEBUG"] = "1" |
| portage._reset_legacy_globals() |
| |
| # do this _after_ 'import portage' to prevent unnecessary tracing |
| if debug and "python-trace" in portage.features: |
| portage.debug.set_trace(True) |
| |
| if not opts.color == "y" and ( |
| opts.color == "n" |
| or portage.util.no_color(portage.settings) |
| or portage.settings.get("TERM") == "dumb" |
| or not sys.stdout.isatty() |
| ): |
| portage.output.nocolor() |
| |
| apply_priorities(portage.settings) |
| |
| ebuild = pargs.pop(0) |
| |
| pf = None |
| if ebuild.endswith(".ebuild"): |
| pf = os.path.basename(ebuild)[:-7] |
| |
| if pf is None: |
| err(f"{ebuild}: does not end with '.ebuild'") |
| |
| if not os.path.isabs(ebuild): |
| mycwd = os.getcwd() |
| # Try to get the non-canonical path from the PWD evironment variable, since |
| # the canonical path returned from os.getcwd() may may be unusable in |
| # cases where the directory stucture is built from symlinks. |
| pwd = os.environ.get("PWD", "") |
| if pwd and pwd != mycwd and os.path.realpath(pwd) == mycwd: |
| mycwd = portage.normalize_path(pwd) |
| ebuild = os.path.join(mycwd, ebuild) |
| ebuild = portage.normalize_path(ebuild) |
| # portdbapi uses the canonical path for the base of the ebuild repository, but |
| # subdirectories of the base can be built from symlinks (like crossdev does). |
| ebuild_portdir = os.path.realpath( |
| os.path.dirname(os.path.dirname(os.path.dirname(ebuild))) |
| ) |
| ebuild = os.path.join(ebuild_portdir, *ebuild.split(os.path.sep)[-3:]) |
| vdb_path = os.path.realpath(os.path.join(portage.settings["EROOT"], VDB_PATH)) |
| |
| # Make sure that portdb.findname() returns the correct ebuild. |
| if ebuild_portdir != vdb_path and ebuild_portdir not in portage.portdb.porttrees: |
| portdir_overlay = portage.settings.get("PORTDIR_OVERLAY", "") |
| os.environ["PORTDIR_OVERLAY"] = ( |
| portdir_overlay + " " + _shell_quote(ebuild_portdir) |
| ) |
| |
| print(f"Appending {ebuild_portdir} to PORTDIR_OVERLAY...") |
| portage._reset_legacy_globals() |
| |
| myrepo = None |
| if ebuild_portdir != vdb_path: |
| myrepo = portage.portdb.getRepositoryName(ebuild_portdir) |
| |
| if not os.path.exists(ebuild): |
| err(f"{ebuild}: does not exist") |
| |
| ebuild_split = ebuild.split("/") |
| cpv = f"{ebuild_split[-3]}/{pf}" |
| |
| with open( |
| _unicode_encode(ebuild, encoding=_encodings["fs"], errors="strict"), |
| encoding=_encodings["repo.content"], |
| errors="replace", |
| ) as f: |
| eapi = portage._parse_eapi_ebuild_head(f)[0] |
| if eapi is None: |
| eapi = "0" |
| if not portage.catpkgsplit(cpv, eapi=eapi): |
| err(f"{ebuild}: {cpv}: does not follow correct package syntax") |
| |
| if ebuild.startswith(vdb_path): |
| mytree = "vartree" |
| pkg_type = "installed" |
| |
| portage_ebuild = portage.db[portage.root][mytree].dbapi.findname( |
| cpv, myrepo=myrepo |
| ) |
| |
| if os.path.realpath(portage_ebuild) != ebuild: |
| err(f"Portage seems to think that {cpv} is at {portage_ebuild}") |
| |
| else: |
| mytree = "porttree" |
| pkg_type = "ebuild" |
| |
| portage_ebuild = portage.portdb.findname(cpv, myrepo=myrepo) |
| |
| if not portage_ebuild or portage_ebuild != ebuild: |
| err(f"{ebuild}: does not seem to have a valid PORTDIR structure") |
| |
| if len(pargs) > 1 and "config" in pargs: |
| other_phases = set(pargs) |
| other_phases.difference_update(("clean", "config", "digest", "manifest")) |
| if other_phases: |
| err('"config" must not be called with any other phase') |
| |
| def discard_digests(myebuild, mysettings, mydbapi): |
| """Discard all distfiles digests for the given ebuild. This is useful when |
| upstream has changed the identity of the distfiles and the user would |
| otherwise have to manually remove the Manifest and files/digest-* files in |
| order to ensure correct results.""" |
| try: |
| portage._doebuild_manifest_exempt_depend += 1 |
| pkgdir = os.path.dirname(myebuild) |
| fetchlist_dict = portage.FetchlistDict(pkgdir, mysettings, mydbapi) |
| mf = mysettings.repositories.get_repo_for_location( |
| os.path.dirname(os.path.dirname(pkgdir)) |
| ) |
| mf = mf.load_manifest( |
| pkgdir, mysettings["DISTDIR"], fetchlist_dict=fetchlist_dict |
| ) |
| mf.create( |
| requiredDistfiles=None, |
| assumeDistHashesSometimes=True, |
| assumeDistHashesAlways=True, |
| ) |
| distfiles = fetchlist_dict[cpv] |
| for myfile in distfiles: |
| try: |
| del mf.fhashdict["DIST"][myfile] |
| except KeyError: |
| pass |
| mf.write() |
| finally: |
| portage._doebuild_manifest_exempt_depend -= 1 |
| |
| portage.settings.validate() # generate warning messages if necessary |
| |
| build_dir_phases = { |
| "setup", |
| "unpack", |
| "prepare", |
| "configure", |
| "compile", |
| "test", |
| "install", |
| "package", |
| "rpm", |
| "merge", |
| "qmerge", |
| } |
| |
| # If the current metadata is invalid then force the ebuild to be |
| # sourced again even if ${T}/environment already exists. |
| ebuild_changed = False |
| if mytree == "porttree" and build_dir_phases.intersection(pargs): |
| ebuild_changed = ( |
| portage.portdb._pull_valid_cache(cpv, ebuild, ebuild_portdir)[0] is None |
| ) |
| |
| # Make configuration adjustments to portage.portdb.doebuild_settings, |
| # in order to enforce consistency for EBUILD_FORCE_TEST support |
| # (see bug 601466). |
| tmpsettings = portage.portdb.doebuild_settings |
| |
| tmpsettings["PORTAGE_VERBOSE"] = "1" |
| tmpsettings.backup_changes("PORTAGE_VERBOSE") |
| |
| if opts.skip_manifest: |
| tmpsettings["EBUILD_SKIP_MANIFEST"] = "1" |
| tmpsettings.backup_changes("EBUILD_SKIP_MANIFEST") |
| |
| if ( |
| opts.skip_manifest |
| or "digest" in tmpsettings.features |
| or "digest" in pargs |
| or "manifest" in pargs |
| ): |
| portage._doebuild_manifest_exempt_depend += 1 |
| |
| if "test" in pargs: |
| # This variable is a signal to config.regenerate() to |
| # indicate that the test phase should be enabled regardless |
| # of problems such as masked "test" USE flag. |
| tmpsettings["EBUILD_FORCE_TEST"] = "1" |
| tmpsettings.backup_changes("EBUILD_FORCE_TEST") |
| tmpsettings.features.add("test") |
| portage.writemsg(_("Forcing test.\n"), noiselevel=-1) |
| |
| tmpsettings.features.discard("fail-clean") |
| |
| # We don't implement merge-wait for the ebuild command, so discard |
| # it from FEATURES. This prevents premature WORKDIR removal. |
| tmpsettings.features.discard("merge-wait") |
| |
| if "merge" in pargs and "noauto" in tmpsettings.features: |
| print("Disabling noauto in features... merge disables it. (qmerge doesn't)") |
| tmpsettings.features.discard("noauto") |
| |
| if "digest" in tmpsettings.features: |
| if pargs and pargs[0] not in ("digest", "manifest"): |
| pargs = ["digest"] + pargs |
| # We only need to build digests on the first pass. |
| tmpsettings.features.discard("digest") |
| |
| # Now that configuration adjustments are complete, create a clone of |
| # tmpsettings. The current instance refers to portdb.doebuild_settings, |
| # and we want to avoid the possibility of unintended side-effects. |
| tmpsettings = portage.config(clone=tmpsettings) |
| |
| try: |
| metadata = dict( |
| zip( |
| Package.metadata_keys, |
| portage.db[portage.settings["EROOT"]][mytree].dbapi.aux_get( |
| cpv, Package.metadata_keys, myrepo=myrepo |
| ), |
| ) |
| ) |
| except PortageKeyError: |
| # aux_get failure, message should have been shown on stderr. |
| sys.exit(1) |
| |
| root_config = RootConfig( |
| portage.settings, portage.db[portage.settings["EROOT"]], None |
| ) |
| |
| cpv = portage.versions._pkg_str( |
| cpv, |
| metadata=metadata, |
| settings=portage.settings, |
| db=portage.db[portage.settings["EROOT"]][mytree].dbapi, |
| ) |
| |
| pkg = Package( |
| built=(pkg_type != "ebuild"), |
| cpv=cpv, |
| installed=(pkg_type == "installed"), |
| metadata=metadata, |
| root_config=root_config, |
| type_name=pkg_type, |
| ) |
| |
| # Apply package.env and repo-level settings. This allows per-package |
| # FEATURES and other variables (possibly PORTAGE_TMPDIR) to be |
| # available as soon as possible. Also, note that the only way to ensure |
| # that setcpv gets metadata from the correct repository is to pass in |
| # a Package instance, as we do here (previously we had to modify |
| # portdb.porttrees in order to accomplish this). |
| tmpsettings.setcpv(pkg) |
| |
| def stale_env_warning(): |
| if ( |
| "clean" not in pargs |
| and "noauto" not in tmpsettings.features |
| and build_dir_phases.intersection(pargs) |
| ): |
| portage.doebuild_environment( |
| ebuild, "setup", portage.root, tmpsettings, debug, 1, portage.portdb |
| ) |
| env_filename = os.path.join(tmpsettings["T"], "environment") |
| if os.path.exists(env_filename): |
| msg = ( |
| f"Existing ${{T}}/environment for '{tmpsettings['PF']}' will be sourced. " |
| "Run 'clean' to start with a fresh environment." |
| ) |
| msg = textwrap.wrap(msg, 70) |
| for x in msg: |
| portage.writemsg(f">>> {x}\n") |
| |
| if ebuild_changed: |
| open( |
| os.path.join( |
| tmpsettings["PORTAGE_BUILDDIR"], ".ebuild_changed" |
| ), |
| "w", |
| ).close() |
| |
| checked_for_stale_env = False |
| |
| for arg in pargs: |
| try: |
| if not checked_for_stale_env and arg not in ("digest", "manifest"): |
| # This has to go after manifest generation since otherwise |
| # aux_get() might fail due to invalid ebuild digests. |
| stale_env_warning() |
| checked_for_stale_env = True |
| |
| if arg in ("digest", "manifest") and force: |
| discard_digests(ebuild, tmpsettings, portage.portdb) |
| a = portage.doebuild( |
| ebuild, |
| arg, |
| settings=tmpsettings, |
| debug=debug, |
| tree=mytree, |
| vartree=portage.db[portage.root]["vartree"], |
| ) |
| except PortageKeyError: |
| # aux_get error |
| a = 1 |
| except UnsupportedAPIException as e: |
| msg = textwrap.wrap(str(e), 70) |
| del e |
| for x in msg: |
| portage.writemsg(f"!!! {x}\n", noiselevel=-1) |
| a = 1 |
| except PortagePackageException as e: |
| portage.writemsg(f"!!! {e}\n", noiselevel=-1) |
| a = 1 |
| except PermissionDenied as e: |
| portage.writemsg(f"!!! Permission Denied: {e}\n", noiselevel=-1) |
| a = 1 |
| if a is None: |
| print("Could not run the required binary?") |
| a = 127 |
| if a: |
| global_event_loop().close() |
| sys.exit(a) |
| |
| # Only close the event loop for __main__, |
| # since outside of __main__ it would close the |
| # event loop for child processes when using |
| # the multiprocessing spawn start method. |
| global_event_loop().close() |
| |
| |
| if __name__ == "__main__": |
| try: |
| main() |
| except KeyboardInterrupt as e: |
| # Prevent traceback on ^C |
| signum = getattr(e, "signum", signal.SIGINT) |
| signal.signal(signum, signal.SIG_DFL) |
| raise_signal(signum) |