diff --git a/xbstrap/__init__.py b/xbstrap/__init__.py index 34179eb..3375dfe 100755 --- a/xbstrap/__init__.py +++ b/xbstrap/__init__.py @@ -918,6 +918,104 @@ def resolve_host_paths(x): # ---------------------------------------------------------------------------------------- +def do_update(args): + cfg = config_for_args(args) + if args.all: + pkgs = cfg.all_pkgs() + else: + pkgs = [cfg.get_target_pkg(pkg) for pkg in args.packages] + to_update = [] + checked_packages = 0 + + if args.verbose: + _util.log_info("Checking packages for updates:") + for pkg in pkgs: + if pkg.check_if_installed(cfg).missing and args.uninstalled: + if args.verbose: + print(f"\t{pkg.name}: not installed") + to_update.append(pkg) + checked_packages += 1 + continue + + checked_packages += 1 + + if pkg.version == pkg.installed_version: + if args.verbose: + print(f"\t{pkg.name}: {pkg.version} (up-to-date)") + else: + if not pkg.installed_version: + if args.verbose: + print(f"\t{pkg.name}: available {pkg.version}, installed ?") + else: + if args.verbose: + print( + f"\t{pkg.name}: available {pkg.version}, installed {pkg.installed_version}" + ) + to_update.append(pkg) + + _util.log_info(f"{len(to_update)} of {checked_packages} 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})") + + if not args.dry_run: + plan = xbstrap.base.Plan(cfg) + plan.update = not args.pull + plan.reset = xbstrap.base.ResetMode.RESET if args.reset else None + + for pkg in to_update: + if args.pull: + plan.wanted.add((xbstrap.base.Action.PULL_PKG_PACK, pkg)) + else: + plan.wanted.add((xbstrap.base.Action.CONFIGURE_PKG, pkg)) + plan.wanted.add((xbstrap.base.Action.BUILD_PKG, pkg)) + if plan.cfg.use_xbps: + plan.wanted.add((xbstrap.base.Action.PACK_PKG, pkg)) + plan.wanted.add((xbstrap.base.Action.INSTALL_PKG, pkg)) + + plan.run_plan() + + +do_update.parser = main_subparsers.add_parser( + "update", + description=( + "Update packages to their newest version. " + "Unless told otherwise, packages are built from source. " + "Only listed packages (or all with --all) are checked. " + "Not installed packages are excluded by default, unless --uninstalled is supplied." + ), + parents=[], +) +do_update.parser.add_argument("packages", nargs="*") +do_update.parser.add_argument( + "-p", "--pull", action="store_true", help="pull packages instead of building from source" +) +do_update.parser.add_argument( + "-n", "--dry-run", action="store_true", help="only check for updates without actually updating" +) +do_update.parser.add_argument("-a", "--all", action="store_true", help="check all packages") +do_update.parser.add_argument( + "-u", + "--uninstalled", + action="store_true", + help="include packages that are not installed", +) +do_update.parser.add_argument( + "-r", + "--reset", + action="store_true", + help="reset repository state; risks loss of local commits!", +) +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 +1091,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..e17b5cd 100644 --- a/xbstrap/base.py +++ b/xbstrap/base.py @@ -824,7 +824,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): @@ -1404,6 +1404,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 +1528,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 +2630,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()