| # |
| # Copyright OpenEmbedded Contributors |
| # |
| # SPDX-License-Identifier: GPL-2.0-only |
| # |
| |
| import shutil |
| import subprocess |
| from oe.package_manager import * |
| |
| class RpmIndexer(Indexer): |
| def write_index(self): |
| self.do_write_index(self.deploy_dir) |
| |
| def do_write_index(self, deploy_dir): |
| if self.d.getVar('PACKAGE_FEED_SIGN') == '1': |
| signer = get_signer(self.d, self.d.getVar('PACKAGE_FEED_GPG_BACKEND')) |
| else: |
| signer = None |
| |
| createrepo_c = bb.utils.which(os.environ['PATH'], "createrepo_c") |
| result = create_index("%s --update -q %s" % (createrepo_c, deploy_dir)) |
| if result: |
| bb.fatal(result) |
| |
| # Sign repomd |
| if signer: |
| sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE') |
| is_ascii_sig = (sig_type.upper() != "BIN") |
| signer.detach_sign(os.path.join(deploy_dir, 'repodata', 'repomd.xml'), |
| self.d.getVar('PACKAGE_FEED_GPG_NAME'), |
| self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE'), |
| armor=is_ascii_sig) |
| |
| class RpmSubdirIndexer(RpmIndexer): |
| def write_index(self): |
| bb.note("Generating package index for %s" %(self.deploy_dir)) |
| # Remove the existing repodata to ensure that we re-generate it no matter what |
| bb.utils.remove(os.path.join(self.deploy_dir, "repodata"), recurse=True) |
| |
| self.do_write_index(self.deploy_dir) |
| for entry in os.walk(self.deploy_dir): |
| if os.path.samefile(self.deploy_dir, entry[0]): |
| for dir in entry[1]: |
| if dir != 'repodata': |
| dir_path = oe.path.join(self.deploy_dir, dir) |
| bb.note("Generating package index for %s" %(dir_path)) |
| self.do_write_index(dir_path) |
| |
| |
| class PMPkgsList(PkgsList): |
| def list_pkgs(self): |
| return RpmPM(self.d, self.rootfs_dir, self.d.getVar('TARGET_VENDOR'), needfeed=False).list_installed() |
| |
| class RpmPM(PackageManager): |
| def __init__(self, |
| d, |
| target_rootfs, |
| target_vendor, |
| task_name='target', |
| arch_var=None, |
| os_var=None, |
| rpm_repo_workdir="oe-rootfs-repo", |
| filterbydependencies=True, |
| needfeed=True): |
| super(RpmPM, self).__init__(d, target_rootfs) |
| self.target_vendor = target_vendor |
| self.task_name = task_name |
| if arch_var == None: |
| self.archs = self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS').replace("-","_") |
| else: |
| self.archs = self.d.getVar(arch_var).replace("-","_") |
| if task_name == "host": |
| self.primary_arch = self.d.getVar('SDK_ARCH') |
| else: |
| self.primary_arch = self.d.getVar('MACHINE_ARCH') |
| |
| if needfeed: |
| self.rpm_repo_dir = oe.path.join(self.d.getVar('WORKDIR'), rpm_repo_workdir) |
| create_packages_dir(self.d, oe.path.join(self.rpm_repo_dir, "rpm"), d.getVar("DEPLOY_DIR_RPM"), "package_write_rpm", filterbydependencies) |
| |
| self.saved_packaging_data = self.d.expand('${T}/saved_packaging_data/%s' % self.task_name) |
| if not os.path.exists(self.d.expand('${T}/saved_packaging_data')): |
| bb.utils.mkdirhier(self.d.expand('${T}/saved_packaging_data')) |
| self.packaging_data_dirs = ['etc/rpm', 'etc/rpmrc', 'etc/dnf', 'var/lib/rpm', 'var/lib/dnf', 'var/cache/dnf'] |
| self.solution_manifest = self.d.expand('${T}/saved/%s_solution' % |
| self.task_name) |
| if not os.path.exists(self.d.expand('${T}/saved')): |
| bb.utils.mkdirhier(self.d.expand('${T}/saved')) |
| |
| def _configure_dnf(self): |
| # libsolv handles 'noarch' internally, we don't need to specify it explicitly |
| archs = [i for i in reversed(self.archs.split()) if i not in ["any", "all", "noarch"]] |
| # This prevents accidental matching against libsolv's built-in policies |
| if len(archs) <= 1: |
| archs = archs + ["bogusarch"] |
| # This architecture needs to be upfront so that packages using it are properly prioritized |
| archs = ["sdk_provides_dummy_target"] + archs |
| confdir = "%s/%s" %(self.target_rootfs, "etc/dnf/vars/") |
| bb.utils.mkdirhier(confdir) |
| open(confdir + "arch", 'w').write(":".join(archs)) |
| distro_codename = self.d.getVar('DISTRO_CODENAME') |
| open(confdir + "releasever", 'w').write(distro_codename if distro_codename is not None else '') |
| |
| open(oe.path.join(self.target_rootfs, "etc/dnf/dnf.conf"), 'w').write("") |
| |
| |
| def _configure_rpm(self): |
| # We need to configure rpm to use our primary package architecture as the installation architecture, |
| # and to make it compatible with other package architectures that we use. |
| # Otherwise it will refuse to proceed with packages installation. |
| platformconfdir = "%s/%s" %(self.target_rootfs, "etc/rpm/") |
| rpmrcconfdir = "%s/%s" %(self.target_rootfs, "etc/") |
| bb.utils.mkdirhier(platformconfdir) |
| open(platformconfdir + "platform", 'w').write("%s-pc-linux" % self.primary_arch) |
| with open(rpmrcconfdir + "rpmrc", 'w') as f: |
| f.write("arch_compat: %s: %s\n" % (self.primary_arch, self.archs if len(self.archs) > 0 else self.primary_arch)) |
| f.write("buildarch_compat: %s: noarch\n" % self.primary_arch) |
| |
| open(platformconfdir + "macros", 'w').write("%_transaction_color 7\n") |
| if self.d.getVar('RPM_PREFER_ELF_ARCH'): |
| open(platformconfdir + "macros", 'a').write("%%_prefer_color %s" % (self.d.getVar('RPM_PREFER_ELF_ARCH'))) |
| |
| if self.d.getVar('RPM_SIGN_PACKAGES') == '1': |
| signer = get_signer(self.d, self.d.getVar('RPM_GPG_BACKEND')) |
| pubkey_path = oe.path.join(self.d.getVar('B'), 'rpm-key') |
| signer.export_pubkey(pubkey_path, self.d.getVar('RPM_GPG_NAME')) |
| rpm_bin = bb.utils.which(os.getenv('PATH'), "rpmkeys") |
| cmd = [rpm_bin, '--root=%s' % self.target_rootfs, '--import', pubkey_path] |
| try: |
| subprocess.check_output(cmd, stderr=subprocess.STDOUT) |
| except subprocess.CalledProcessError as e: |
| bb.fatal("Importing GPG key failed. Command '%s' " |
| "returned %d:\n%s" % (' '.join(cmd), e.returncode, e.output.decode("utf-8"))) |
| |
| def create_configs(self): |
| self._configure_dnf() |
| self._configure_rpm() |
| |
| def write_index(self): |
| lockfilename = self.d.getVar('DEPLOY_DIR_RPM') + "/rpm.lock" |
| lf = bb.utils.lockfile(lockfilename, False) |
| RpmIndexer(self.d, self.rpm_repo_dir).write_index() |
| bb.utils.unlockfile(lf) |
| |
| def insert_feeds_uris(self, feed_uris, feed_base_paths, feed_archs): |
| from urllib.parse import urlparse |
| |
| if feed_uris == "": |
| return |
| |
| gpg_opts = '' |
| if self.d.getVar('PACKAGE_FEED_SIGN') == '1': |
| gpg_opts += 'repo_gpgcheck=1\n' |
| gpg_opts += 'gpgkey=file://%s/pki/packagefeed-gpg/PACKAGEFEED-GPG-KEY-%s-%s\n' % (self.d.getVar('sysconfdir'), self.d.getVar('DISTRO'), self.d.getVar('DISTRO_CODENAME')) |
| |
| if self.d.getVar('RPM_SIGN_PACKAGES') != '1': |
| gpg_opts += 'gpgcheck=0\n' |
| |
| bb.utils.mkdirhier(oe.path.join(self.target_rootfs, "etc", "yum.repos.d")) |
| remote_uris = self.construct_uris(feed_uris.split(), feed_base_paths.split()) |
| for uri in remote_uris: |
| repo_base = "oe-remote-repo" + "-".join(urlparse(uri).path.split("/")) |
| if feed_archs is not None: |
| for arch in feed_archs.split(): |
| repo_uri = uri + "/" + arch |
| repo_id = "oe-remote-repo" + "-".join(urlparse(repo_uri).path.split("/")) |
| repo_name = "OE Remote Repo:" + " ".join(urlparse(repo_uri).path.split("/")) |
| open(oe.path.join(self.target_rootfs, "etc", "yum.repos.d", repo_base + ".repo"), 'a').write( |
| "[%s]\nname=%s\nbaseurl=%s\n%s\n" % (repo_id, repo_name, repo_uri, gpg_opts)) |
| else: |
| repo_name = "OE Remote Repo:" + " ".join(urlparse(uri).path.split("/")) |
| repo_uri = uri |
| open(oe.path.join(self.target_rootfs, "etc", "yum.repos.d", repo_base + ".repo"), 'w').write( |
| "[%s]\nname=%s\nbaseurl=%s\n%s" % (repo_base, repo_name, repo_uri, gpg_opts)) |
| |
| def _prepare_pkg_transaction(self): |
| os.environ['D'] = self.target_rootfs |
| os.environ['OFFLINE_ROOT'] = self.target_rootfs |
| os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs |
| os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs |
| os.environ['INTERCEPT_DIR'] = self.intercepts_dir |
| os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE') |
| |
| |
| def install(self, pkgs, attempt_only=False, hard_depends_only=False): |
| if len(pkgs) == 0: |
| return |
| self._prepare_pkg_transaction() |
| |
| bad_recommendations = self.d.getVar('BAD_RECOMMENDATIONS') |
| package_exclude = self.d.getVar('PACKAGE_EXCLUDE') |
| exclude_pkgs = (bad_recommendations.split() if bad_recommendations else []) + (package_exclude.split() if package_exclude else []) |
| |
| output = self._invoke_dnf((["--skip-broken"] if attempt_only else []) + |
| (["-x", ",".join(exclude_pkgs)] if len(exclude_pkgs) > 0 else []) + |
| (["--setopt=install_weak_deps=False"] if (hard_depends_only or self.d.getVar('NO_RECOMMENDATIONS') == "1") else []) + |
| (["--nogpgcheck"] if self.d.getVar('RPM_SIGN_PACKAGES') != '1' else ["--setopt=gpgcheck=True"]) + |
| ["install"] + |
| pkgs) |
| |
| failed_scriptlets_pkgnames = collections.OrderedDict() |
| for line in output.splitlines(): |
| if line.startswith("Error: Systemctl"): |
| bb.error(line) |
| |
| if line.startswith("Error in POSTIN scriptlet in rpm package"): |
| failed_scriptlets_pkgnames[line.split()[-1]] = True |
| |
| if len(failed_scriptlets_pkgnames) > 0: |
| failed_postinsts_abort(list(failed_scriptlets_pkgnames.keys()), self.d.expand("${T}/log.do_${BB_CURRENTTASK}")) |
| |
| def remove(self, pkgs, with_dependencies = True): |
| if not pkgs: |
| return |
| |
| self._prepare_pkg_transaction() |
| |
| if with_dependencies: |
| self._invoke_dnf(["remove"] + pkgs) |
| else: |
| cmd = bb.utils.which(os.getenv('PATH'), "rpm") |
| args = ["-e", "-v", "--nodeps", "--root=%s" %self.target_rootfs] |
| |
| try: |
| bb.note("Running %s" % ' '.join([cmd] + args + pkgs)) |
| output = subprocess.check_output([cmd] + args + pkgs, stderr=subprocess.STDOUT).decode("utf-8") |
| bb.note(output) |
| except subprocess.CalledProcessError as e: |
| bb.fatal("Could not invoke rpm. Command " |
| "'%s' returned %d:\n%s" % (' '.join([cmd] + args + pkgs), e.returncode, e.output.decode("utf-8"))) |
| |
| def upgrade(self): |
| self._prepare_pkg_transaction() |
| self._invoke_dnf(["upgrade"]) |
| |
| def autoremove(self): |
| self._prepare_pkg_transaction() |
| self._invoke_dnf(["autoremove"]) |
| |
| def remove_packaging_data(self): |
| self._invoke_dnf(["clean", "all"]) |
| for dir in self.packaging_data_dirs: |
| bb.utils.remove(oe.path.join(self.target_rootfs, dir), True) |
| |
| def backup_packaging_data(self): |
| # Save the packaging dirs for increment rpm image generation |
| if os.path.exists(self.saved_packaging_data): |
| bb.utils.remove(self.saved_packaging_data, True) |
| for i in self.packaging_data_dirs: |
| source_dir = oe.path.join(self.target_rootfs, i) |
| target_dir = oe.path.join(self.saved_packaging_data, i) |
| if os.path.isdir(source_dir): |
| shutil.copytree(source_dir, target_dir, symlinks=True) |
| elif os.path.isfile(source_dir): |
| shutil.copy2(source_dir, target_dir) |
| |
| def recovery_packaging_data(self): |
| # Move the rpmlib back |
| if os.path.exists(self.saved_packaging_data): |
| for i in self.packaging_data_dirs: |
| target_dir = oe.path.join(self.target_rootfs, i) |
| if os.path.exists(target_dir): |
| bb.utils.remove(target_dir, True) |
| source_dir = oe.path.join(self.saved_packaging_data, i) |
| if os.path.isdir(source_dir): |
| shutil.copytree(source_dir, target_dir, symlinks=True) |
| elif os.path.isfile(source_dir): |
| shutil.copy2(source_dir, target_dir) |
| |
| def list_installed(self): |
| output = self._invoke_dnf(["repoquery", "--installed", "--queryformat", "Package: %{name} %{arch} %{version} %{name}-%{version}-%{release}.%{arch}.rpm\nDependencies:\n%{requires}\nRecommendations:\n%{recommends}\nDependenciesEndHere:\n"], |
| print_output = False) |
| packages = {} |
| current_package = None |
| current_deps = None |
| current_state = "initial" |
| for line in output.splitlines(): |
| if line.startswith("Package:"): |
| package_info = line.split(" ")[1:] |
| current_package = package_info[0] |
| package_arch = package_info[1] |
| package_version = package_info[2] |
| package_rpm = package_info[3] |
| packages[current_package] = {"arch":package_arch, "ver":package_version, "filename":package_rpm} |
| current_deps = [] |
| elif line.startswith("Dependencies:"): |
| current_state = "dependencies" |
| elif line.startswith("Recommendations"): |
| current_state = "recommendations" |
| elif line.startswith("DependenciesEndHere:"): |
| current_state = "initial" |
| packages[current_package]["deps"] = current_deps |
| elif len(line) > 0: |
| if current_state == "dependencies": |
| current_deps.append(line) |
| elif current_state == "recommendations": |
| current_deps.append("%s [REC]" % line) |
| |
| return packages |
| |
| def update(self): |
| self._invoke_dnf(["makecache", "--refresh"]) |
| |
| def _invoke_dnf(self, dnf_args, fatal = True, print_output = True ): |
| os.environ['RPM_ETCCONFIGDIR'] = self.target_rootfs |
| |
| dnf_cmd = bb.utils.which(os.getenv('PATH'), "dnf") |
| standard_dnf_args = ["-v", "--rpmverbosity=info", "-y", |
| "-c", oe.path.join(self.target_rootfs, "etc/dnf/dnf.conf"), |
| "--setopt=reposdir=%s" %(oe.path.join(self.target_rootfs, "etc/yum.repos.d")), |
| "--installroot=%s" % (self.target_rootfs), |
| "--setopt=logdir=%s" % (self.d.getVar('T')) |
| ] |
| if hasattr(self, "rpm_repo_dir"): |
| standard_dnf_args.append("--repofrompath=oe-repo,%s" % (self.rpm_repo_dir)) |
| cmd = [dnf_cmd] + standard_dnf_args + dnf_args |
| bb.note('Running %s' % ' '.join(cmd)) |
| try: |
| output = subprocess.check_output(cmd,stderr=subprocess.STDOUT).decode("utf-8") |
| if print_output: |
| bb.debug(1, output) |
| return output |
| except subprocess.CalledProcessError as e: |
| if print_output: |
| (bb.note, bb.fatal)[fatal]("Could not invoke dnf. Command " |
| "'%s' returned %d:\n%s" % (' '.join(cmd), e.returncode, e.output.decode("utf-8"))) |
| else: |
| (bb.note, bb.fatal)[fatal]("Could not invoke dnf. Command " |
| "'%s' returned %d:" % (' '.join(cmd), e.returncode)) |
| return e.output.decode("utf-8") |
| |
| def dump_install_solution(self, pkgs): |
| open(self.solution_manifest, 'w').write(" ".join(pkgs)) |
| return pkgs |
| |
| def load_old_install_solution(self): |
| if not os.path.exists(self.solution_manifest): |
| return [] |
| with open(self.solution_manifest, 'r') as fd: |
| return fd.read().split() |
| |
| def _script_num_prefix(self, path): |
| files = os.listdir(path) |
| numbers = set() |
| numbers.add(99) |
| for f in files: |
| numbers.add(int(f.split("-")[0])) |
| return max(numbers) + 1 |
| |
| def save_rpmpostinst(self, pkg): |
| bb.note("Saving postinstall script of %s" % (pkg)) |
| cmd = bb.utils.which(os.getenv('PATH'), "rpm") |
| args = ["-q", "--root=%s" % self.target_rootfs, "--queryformat", "%{postin}", pkg] |
| |
| try: |
| output = subprocess.check_output([cmd] + args,stderr=subprocess.STDOUT).decode("utf-8") |
| except subprocess.CalledProcessError as e: |
| bb.fatal("Could not invoke rpm. Command " |
| "'%s' returned %d:\n%s" % (' '.join([cmd] + args), e.returncode, e.output.decode("utf-8"))) |
| |
| # may need to prepend #!/bin/sh to output |
| |
| target_path = oe.path.join(self.target_rootfs, self.d.expand('${sysconfdir}/rpm-postinsts/')) |
| bb.utils.mkdirhier(target_path) |
| num = self._script_num_prefix(target_path) |
| saved_script_name = oe.path.join(target_path, "%d-%s" % (num, pkg)) |
| open(saved_script_name, 'w').write(output) |
| os.chmod(saved_script_name, 0o755) |
| |
| def _handle_intercept_failure(self, registered_pkgs): |
| rpm_postinsts_dir = self.target_rootfs + self.d.expand('${sysconfdir}/rpm-postinsts/') |
| bb.utils.mkdirhier(rpm_postinsts_dir) |
| |
| # Save the package postinstalls in /etc/rpm-postinsts |
| for pkg in registered_pkgs.split(): |
| self.save_rpmpostinst(pkg) |
| |
| def extract(self, pkg): |
| output = self._invoke_dnf(["repoquery", "--queryformat", "%{location}", pkg]) |
| pkg_name = output.splitlines()[-1] |
| if not pkg_name.endswith(".rpm"): |
| bb.fatal("dnf could not find package %s in repository: %s" %(pkg, output)) |
| pkg_path = oe.path.join(self.rpm_repo_dir, pkg_name) |
| |
| cpio_cmd = bb.utils.which(os.getenv("PATH"), "cpio") |
| rpm2cpio_cmd = bb.utils.which(os.getenv("PATH"), "rpm2cpio") |
| |
| if not os.path.isfile(pkg_path): |
| bb.fatal("Unable to extract package for '%s'." |
| "File %s doesn't exists" % (pkg, pkg_path)) |
| |
| tmp_dir = tempfile.mkdtemp() |
| current_dir = os.getcwd() |
| os.chdir(tmp_dir) |
| |
| try: |
| cmd = "%s %s | %s -idmv" % (rpm2cpio_cmd, pkg_path, cpio_cmd) |
| output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) |
| except subprocess.CalledProcessError as e: |
| bb.utils.remove(tmp_dir, recurse=True) |
| bb.fatal("Unable to extract %s package. Command '%s' " |
| "returned %d:\n%s" % (pkg_path, cmd, e.returncode, e.output.decode("utf-8"))) |
| except OSError as e: |
| bb.utils.remove(tmp_dir, recurse=True) |
| bb.fatal("Unable to extract %s package. Command '%s' " |
| "returned %d:\n%s at %s" % (pkg_path, cmd, e.errno, e.strerror, e.filename)) |
| |
| bb.note("Extracted %s to %s" % (pkg_path, tmp_dir)) |
| os.chdir(current_dir) |
| |
| return tmp_dir |