From a8549ac6f627cacfbaa2c4b2e054e2ed6ced360c Mon Sep 17 00:00:00 2001 From: no92 Date: Wed, 9 Aug 2023 22:13:12 +0200 Subject: [PATCH] meta: implement `update` command for updating packages --- xbstrap/__init__.py | 90 +++++++++++++++++++++++++++++++++++++++++++-- xbstrap/base.py | 63 ++++++++++++++++++++++++++----- 2 files changed, 141 insertions(+), 12 deletions(-) diff --git a/xbstrap/__init__.py b/xbstrap/__init__.py index 34179eb..18d2774 100755 --- a/xbstrap/__init__.py +++ b/xbstrap/__init__.py @@ -33,7 +33,9 @@ def config_for_args(args): - return xbstrap.base.Config(args.build_dir, changed_source_root=args.source_dir) + return xbstrap.base.Config( + args.build_dir, changed_source_root=args.source_dir, prefer_pull=args.pull + ) def do_runtool(args): @@ -152,6 +154,9 @@ def handle_plan_args(cfg, plan, args): if args.progress_file is not None: plan.progress_file = xbstrap.cli_utils.open_file_from_cli(args.progress_file, "wt") + if args.command == "update": + plan.update = True + handle_plan_args.parser = argparse.ArgumentParser(add_help=False) handle_plan_args.parser.add_argument( @@ -166,6 +171,9 @@ def handle_plan_args(cfg, plan, args): handle_plan_args.parser.add_argument( "-u", "--update", action="store_true", help="check for package updates" ) +handle_plan_args.parser.add_argument( + "-p", "--pull", action="store_true", help="pull packages instead of building from source" +) handle_plan_args.parser.add_argument( "--recursive", action="store_true", help="when updating: also update requirements" ) @@ -408,12 +416,22 @@ def do_install_tool(args): def select_pkgs(cfg, args): if args.all: - return [pkg for pkg in cfg.all_pkgs() if pkg.is_default] + if args.installed: + return [pkg for pkg in cfg.all_pkgs() if not pkg.check_if_installed(cfg).missing] + else: + return [pkg for pkg in cfg.all_pkgs()] else: if args.command == "run": return [cfg.get_target_pkg(name) for name in args.pkg] else: - sel = [cfg.get_target_pkg(name) for name in args.packages] + if args.installed: + sel = [ + cfg.get_target_pkg(name) + for name in args.packages + if not cfg.get_target_pkg(name).check_if_installed(cfg).missing + ] + else: + sel = [cfg.get_target_pkg(name) for name in args.packages] if args.deps_of is not None: for pkg_name in args.deps_of: @@ -429,6 +447,12 @@ def select_pkgs(cfg, args): select_pkgs.parser = argparse.ArgumentParser(add_help=False) select_pkgs.parser.add_argument("--all", action="store_true") select_pkgs.parser.add_argument("--deps-of", type=str, action="append") +select_pkgs.parser.add_argument( + "-i", + "--installed", + action="store_true", + help="only select packages that are already installed", +) select_pkgs.parser.add_argument("packages", nargs="*", type=str) @@ -918,6 +942,64 @@ def resolve_host_paths(x): # ---------------------------------------------------------------------------------------- +def do_update(args): + # special behavior for this command: + # if --all is supplied, we ignore packages that are not installed + if args.all: + args.installed = True + + cfg = config_for_args(args) + pkgs = select_pkgs(cfg, args) + plan = xbstrap.base.Plan(cfg) + handle_plan_args(cfg, plan, args) + to_update = [] + + if args.verbose: + _util.log_info("Checking packages for updates:") + for pkg in pkgs: + if pkg.version == pkg.installed_version: + if args.verbose: + print(f"\t{pkg.name}: {pkg.version} installed (up-to-date)") + else: + if not pkg.installed_version: + if args.verbose: + print(f"\t{pkg.name}: {pkg.version} available, not installed") + else: + if args.verbose: + print( + f"\t{pkg.name}: {pkg.version} available, {pkg.installed_version} installed" + ) + to_update.append(pkg) + + _util.log_info(f"{len(to_update)} of {len(pkgs)} checked packages need updating:") + for pkg in to_update: + if pkg.installed_version: + print(f"\t{pkg.name} ({pkg.installed_version} -> {pkg.version})") + else: + print(f"\t{pkg.name} (new install of {pkg.version})") + + for pkg in to_update: + plan.wanted.add((xbstrap.base.Action.INSTALL_PKG, pkg)) + + if not args.dry_run: + plan.run_plan() + + +do_update.parser = main_subparsers.add_parser( + "update", + description=( + "Update packages to their newest version. " "When --all is used, --installed is implied." + ), + parents=[handle_plan_args.parser, select_pkgs.parser], +) +do_update.parser.add_argument( + "-v", "--verbose", action="store_true", help="print every version check performed" +) +do_update.parser.set_defaults(_impl=do_update) + +# ---------------------------------------------------------------------------------------- + + def do_execute_manifest(args): if args.c is not None: manifest = yaml.load(args.c, Loader=xbstrap.base.global_yaml_loader) @@ -993,6 +1075,8 @@ def main(): do_run_task(args) elif args.command == "lsp": do_lsp(args) + elif args.command == "update": + do_update(args) else: assert not "Unexpected command" except ( diff --git a/xbstrap/base.py b/xbstrap/base.py index 893ee85..e7a9fa4 100644 --- a/xbstrap/base.py +++ b/xbstrap/base.py @@ -181,9 +181,10 @@ def __init__(self, missing=False, updatable=False, timestamp=None): class Config: - def __init__(self, path, changed_source_root=None): + def __init__(self, path, changed_source_root=None, prefer_pull=False): self._build_root_override = None if path == "" else path self._config_path = path + self._prefer_pull = prefer_pull self._root_yml = None self._site_yml = dict() self._commit_yml = dict() @@ -364,6 +365,10 @@ def tool_archives_url(self): return None return self._root_yml["repositories"].get("tool_archives", None) + @property + def prefer_pull(self): + return self._prefer_pull + @property def use_xbps(self): if "pkg_management" not in self._site_yml: @@ -824,7 +829,7 @@ def rolling_id(self): commit_yml = self._cfg._commit_yml.get("commits", dict()).get(self._name, dict()) rolling_id = commit_yml.get("rolling_id") if rolling_id is None: - raise RollingIdUnavailableError(self._name) + return self.determine_rolling_id() return rolling_id def determine_rolling_id(self): @@ -1355,12 +1360,6 @@ def subject_id(self): def subject_type(self): return "package" - @property - def is_default(self): - if "default" not in self._this_yml: - return self._cfg.everything_by_default - return self._this_yml["default"] - @property def stability_level(self): return self._this_yml.get("stability_level", "stable") @@ -1404,6 +1403,45 @@ def compute_version(self, **kwargs): def version(self): return self.compute_version() + @property + def installed_version(self): + if self._cfg.use_xbps: + environ = os.environ.copy() + _util.build_environ_paths( + environ, "PATH", prepend=[os.path.join(_util.find_home(), "bin")] + ) + environ["XBPS_ARCH"] = self.architecture + + try: + out = ( + subprocess.check_output( + [ + "xbps-query", + "-r", + self._cfg.sysroot_dir, + self.name, + "--property", + "pkgver", + ], + env=environ, + ) + .decode() + .strip() + ) + if out.startswith(self.name): + return out[len(self.name) + 1 :] + except subprocess.CalledProcessError: + pass + + path = os.path.join(self._cfg.sysroot_dir, "etc", "xbstrap", self.name + ".installed") + if not os.path.isfile(path): + return None + with open(path, "r") as f: + data = yaml.load(f, Loader=yaml.SafeLoader) + if data and "version" in data: + return data["version"] + return None + def get_task(self, task): if task in self._tasks: return self._tasks[task] @@ -1489,7 +1527,11 @@ def mark_as_installed(self): _util.try_mkdir(os.path.join(self._cfg.sysroot_dir, "etc")) _util.try_mkdir(os.path.join(self._cfg.sysroot_dir, "etc", "xbstrap")) path = os.path.join(self._cfg.sysroot_dir, "etc", "xbstrap", self.name + ".installed") - touch(path) + with open(path, "w") as f: + data = { + "version": self.version, + } + yaml.safe_dump(data, f, sort_keys=False, default_flow_style=False) class PackageRunTask(RequirementsMixin): @@ -2587,6 +2629,7 @@ def install_pkg(cfg, pkg): ] _util.log_info("Running {}".format(args)) subprocess.check_call(args, env=environ, stdout=output) + pkg.mark_as_installed() else: installtree(pkg.staging_dir, cfg.sysroot_dir) pkg.mark_as_installed() @@ -3004,6 +3047,8 @@ def add_task_dependencies(s): elif action == Action.INSTALL_PKG: if self.build_scope is not None and subject not in self.build_scope: item.build_edges.add((action.WANT_PKG, subject)) + elif self._cfg.prefer_pull: + item.build_edges.add((action.PULL_PKG_PACK, subject)) elif self._cfg.use_xbps: item.build_edges.add((action.PACK_PKG, subject)) else: