summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChen Qi <Qi.Chen@windriver.com>2025-09-24 16:42:33 +0800
committerRichard Purdie <richard.purdie@linuxfoundation.org>2025-10-04 15:16:04 +0100
commit88c7fb652943872ebff240f05bea5bea1f120a25 (patch)
tree8c700a9d8b49a73c751dac4bbdfd83cfc5d15d13
parent600c5897302d8dabaf37f50f6090e1042de0691b (diff)
downloadpoky-88c7fb652943872ebff240f05bea5bea1f120a25.tar.gz
package_manager/oe-pkgdata-util: fix complementary package installation
We currently have a problem regarding complementary package installation, that is, if 'oe-pkgdata-util glob' maps out packages that are not in the oe-rootfs-repo, we will get error like below: No match for argument: lib32-glibc-locale-en-gb Error: Unable to find a match: lib32-glibc-locale-en-gb Here are the steps to reproduce the issue: 1. Add the following lines to local.conf: require conf/multilib.conf MULTILIBS ?= "multilib:lib32" DEFAULTTUNE:virtclass-multilib-lib32 ?= "core2-32" IMAGE_INSTALL:append = " lib32-sysstat" 2. bitbake lib32-glibc-locale && bitbake core-image-full-cmdline This problem appears because: 1) At do_rootfs time, we first contruct a repo with a filtering mechanism to ensure we don't pull in unneeded packages.[1] 2) oe-pkgdata-util uses the pkgdata without filtering. In order to avoid any hardcoding that might grow in the future[2], we need to give 'oe-pkgdata-util glob' some filtering ability. So this patch does the following things: 1) Add a new option, '-a/--allpkgs', to 'oe-pkgdata-util glob'. This gives it a filtering mechanism. As it's an option, people who use 'oe-pkgdata-util glob' command could use it as before. 2) Add to package_manager 'list_all' function implementations which list all available functions in our filtered repo. [1] https://git.openembedded.org/openembedded-core/commit/?id=85e72e129362db896b0d368077033e4a2e373cf9 [2] https://lists.openembedded.org/g/openembedded-core/message/221449 (From OE-Core rev: 16c52f992cf35769eecb3e3863e1ad14d4cb9848) Signed-off-by: Chen Qi <Qi.Chen@windriver.com> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--meta/lib/oe/package_manager/__init__.py78
-rw-r--r--meta/lib/oe/package_manager/deb/__init__.py26
-rw-r--r--meta/lib/oe/package_manager/ipk/__init__.py20
-rw-r--r--meta/lib/oe/package_manager/rpm/__init__.py8
-rwxr-xr-xscripts/oe-pkgdata-util14
5 files changed, 111 insertions, 35 deletions
diff --git a/meta/lib/oe/package_manager/__init__.py b/meta/lib/oe/package_manager/__init__.py
index 88bc5ab195..b9a4218939 100644
--- a/meta/lib/oe/package_manager/__init__.py
+++ b/meta/lib/oe/package_manager/__init__.py
@@ -32,7 +32,7 @@ def create_index(arg):
32 32
33def opkg_query(cmd_output): 33def opkg_query(cmd_output):
34 """ 34 """
35 This method parse the output from the package managerand return 35 This method parse the output from the package manager and return
36 a dictionary with the information of the packages. This is used 36 a dictionary with the information of the packages. This is used
37 when the packages are in deb or ipk format. 37 when the packages are in deb or ipk format.
38 """ 38 """
@@ -369,40 +369,48 @@ class PackageManager(object, metaclass=ABCMeta):
369 if globs: 369 if globs:
370 # we need to write the list of installed packages to a file because the 370 # we need to write the list of installed packages to a file because the
371 # oe-pkgdata-util reads it from a file 371 # oe-pkgdata-util reads it from a file
372 with tempfile.NamedTemporaryFile(mode="w+", prefix="installed-pkgs") as installed_pkgs: 372 with tempfile.NamedTemporaryFile(mode="w+", prefix="all-pkgs") as all_pkgs:
373 pkgs = self.list_installed() 373 with tempfile.NamedTemporaryFile(mode="w+", prefix="installed-pkgs") as installed_pkgs:
374 374 pkgs = self.list_installed()
375 provided_pkgs = set() 375
376 for pkg in pkgs.values(): 376 provided_pkgs = set()
377 provided_pkgs |= set(pkg.get('provs', [])) 377 for pkg in pkgs.values():
378 378 provided_pkgs |= set(pkg.get('provs', []))
379 output = oe.utils.format_pkg_list(pkgs, "arch") 379
380 installed_pkgs.write(output) 380 output = oe.utils.format_pkg_list(pkgs, "arch")
381 installed_pkgs.flush() 381 installed_pkgs.write(output)
382 382 installed_pkgs.flush()
383 cmd = ["oe-pkgdata-util", 383
384 "-p", self.d.getVar('PKGDATA_DIR'), "glob", installed_pkgs.name, 384 cmd = ["oe-pkgdata-util",
385 globs] 385 "-p", self.d.getVar('PKGDATA_DIR'), "glob",
386 exclude = self.d.getVar('PACKAGE_EXCLUDE_COMPLEMENTARY') 386 installed_pkgs.name, globs]
387 if exclude: 387
388 cmd.extend(['--exclude=' + '|'.join(exclude.split())]) 388 if hasattr(self, "list_all"):
389 try: 389 output_allpkg = self.list_all()
390 bb.note('Running %s' % cmd) 390 all_pkgs.write(output_allpkg)
391 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 391 all_pkgs.flush()
392 stdout, stderr = proc.communicate() 392 cmd.extend(["--allpkgs=%s" % all_pkgs.name])
393 if stderr: bb.note(stderr.decode("utf-8")) 393
394 complementary_pkgs = stdout.decode("utf-8") 394 exclude = self.d.getVar('PACKAGE_EXCLUDE_COMPLEMENTARY')
395 complementary_pkgs = set(complementary_pkgs.split()) 395 if exclude:
396 skip_pkgs = sorted(complementary_pkgs & provided_pkgs) 396 cmd.extend(['--exclude=' + '|'.join(exclude.split())])
397 install_pkgs = sorted(complementary_pkgs - provided_pkgs) 397 try:
398 bb.note("Installing complementary packages ... %s (skipped already provided packages %s)" % ( 398 bb.note('Running %s' % cmd)
399 ' '.join(install_pkgs), 399 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
400 ' '.join(skip_pkgs))) 400 stdout, stderr = proc.communicate()
401 self.install(install_pkgs, hard_depends_only=True) 401 if stderr: bb.note(stderr.decode("utf-8"))
402 except subprocess.CalledProcessError as e: 402 complementary_pkgs = stdout.decode("utf-8")
403 bb.fatal("Could not compute complementary packages list. Command " 403 complementary_pkgs = set(complementary_pkgs.split())
404 "'%s' returned %d:\n%s" % 404 skip_pkgs = sorted(complementary_pkgs & provided_pkgs)
405 (' '.join(cmd), e.returncode, e.output.decode("utf-8"))) 405 install_pkgs = sorted(complementary_pkgs - provided_pkgs)
406 bb.note("Installing complementary packages ... %s (skipped already provided packages %s)" % (
407 ' '.join(install_pkgs),
408 ' '.join(skip_pkgs)))
409 self.install(install_pkgs, hard_depends_only=True)
410 except subprocess.CalledProcessError as e:
411 bb.fatal("Could not compute complementary packages list. Command "
412 "'%s' returned %d:\n%s" %
413 (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
406 414
407 if self.d.getVar('IMAGE_LOCALES_ARCHIVE') == '1': 415 if self.d.getVar('IMAGE_LOCALES_ARCHIVE') == '1':
408 target_arch = self.d.getVar('TARGET_ARCH') 416 target_arch = self.d.getVar('TARGET_ARCH')
diff --git a/meta/lib/oe/package_manager/deb/__init__.py b/meta/lib/oe/package_manager/deb/__init__.py
index eb48f3f982..0f74e1322f 100644
--- a/meta/lib/oe/package_manager/deb/__init__.py
+++ b/meta/lib/oe/package_manager/deb/__init__.py
@@ -112,6 +112,29 @@ class PMPkgsList(PkgsList):
112 112
113 return opkg_query(cmd_output) 113 return opkg_query(cmd_output)
114 114
115 def list_all_pkgs(self, apt_conf_file=None):
116 if not apt_conf_file:
117 apt_conf_file = self.d.expand("${APTCONF_TARGET}/apt/apt.conf")
118 os.environ['APT_CONFIG'] = apt_conf_file
119
120 cmd = [bb.utils.which(os.getenv('PATH'), "apt"), "list"]
121
122 try:
123 cmd_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip().decode("utf-8")
124 except subprocess.CalledProcessError as e:
125 bb.fatal("Cannot get the all packages list. Command '%s' "
126 "returned %d:\n%s" % (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
127
128 all_pkgs_lines = []
129 for line in cmd_output.splitlines():
130 line_parts = line.split()
131 # the valid lines takes the format of something like "findutils-locale-ga/unknown 4.10.0-r0 amd64"
132 if len(line_parts) != 3:
133 continue
134 line_parts[0] = line_parts[0].split('/')[0]
135 new_line = ' '.join(line_parts)
136 all_pkgs_lines.append(new_line)
137 return "\n".join(all_pkgs_lines)
115 138
116class DpkgPM(OpkgDpkgPM): 139class DpkgPM(OpkgDpkgPM):
117 def __init__(self, d, target_rootfs, archs, base_archs, apt_conf_dir=None, deb_repo_workdir="oe-rootfs-repo", filterbydependencies=True): 140 def __init__(self, d, target_rootfs, archs, base_archs, apt_conf_dir=None, deb_repo_workdir="oe-rootfs-repo", filterbydependencies=True):
@@ -436,6 +459,9 @@ class DpkgPM(OpkgDpkgPM):
436 def list_installed(self): 459 def list_installed(self):
437 return PMPkgsList(self.d, self.target_rootfs).list_pkgs() 460 return PMPkgsList(self.d, self.target_rootfs).list_pkgs()
438 461
462 def list_all(self):
463 return PMPkgsList(self.d, self.target_rootfs).list_all_pkgs(apt_conf_file=self.apt_conf_file)
464
439 def package_info(self, pkg): 465 def package_info(self, pkg):
440 """ 466 """
441 Returns a dictionary with the package info. 467 Returns a dictionary with the package info.
diff --git a/meta/lib/oe/package_manager/ipk/__init__.py b/meta/lib/oe/package_manager/ipk/__init__.py
index 4794f31f88..2f330ec4f0 100644
--- a/meta/lib/oe/package_manager/ipk/__init__.py
+++ b/meta/lib/oe/package_manager/ipk/__init__.py
@@ -90,6 +90,23 @@ class PMPkgsList(PkgsList):
90 90
91 return opkg_query(cmd_output) 91 return opkg_query(cmd_output)
92 92
93 def list_all_pkgs(self, format=None):
94 cmd = "%s %s list" % (self.opkg_cmd, self.opkg_args)
95
96 # opkg returns success even when it printed some
97 # "Collected errors:" report to stderr. Mixing stderr into
98 # stdout then leads to random failures later on when
99 # parsing the output. To avoid this we need to collect both
100 # output streams separately and check for empty stderr.
101 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
102 cmd_output, cmd_stderr = p.communicate()
103 cmd_output = cmd_output.decode("utf-8")
104 cmd_stderr = cmd_stderr.decode("utf-8")
105 if p.returncode or cmd_stderr:
106 bb.fatal("Cannot get all packages list. Command '%s' "
107 "returned %d and stderr:\n%s" % (cmd, p.returncode, cmd_stderr))
108
109 return cmd_output
93 110
94class OpkgPM(OpkgDpkgPM): 111class OpkgPM(OpkgDpkgPM):
95 def __init__(self, d, target_rootfs, config_file, archs, task_name='target', ipk_repo_workdir="oe-rootfs-repo", filterbydependencies=True, prepare_index=True): 112 def __init__(self, d, target_rootfs, config_file, archs, task_name='target', ipk_repo_workdir="oe-rootfs-repo", filterbydependencies=True, prepare_index=True):
@@ -364,6 +381,9 @@ class OpkgPM(OpkgDpkgPM):
364 def list_installed(self): 381 def list_installed(self):
365 return PMPkgsList(self.d, self.target_rootfs).list_pkgs() 382 return PMPkgsList(self.d, self.target_rootfs).list_pkgs()
366 383
384 def list_all(self):
385 return PMPkgsList(self.d, self.target_rootfs).list_all_pkgs()
386
367 def dummy_install(self, pkgs): 387 def dummy_install(self, pkgs):
368 """ 388 """
369 The following function dummy installs pkgs and returns the log of output. 389 The following function dummy installs pkgs and returns the log of output.
diff --git a/meta/lib/oe/package_manager/rpm/__init__.py b/meta/lib/oe/package_manager/rpm/__init__.py
index 20e6cb8744..a51057650a 100644
--- a/meta/lib/oe/package_manager/rpm/__init__.py
+++ b/meta/lib/oe/package_manager/rpm/__init__.py
@@ -275,6 +275,14 @@ class RpmPM(PackageManager):
275 elif os.path.isfile(source_dir): 275 elif os.path.isfile(source_dir):
276 shutil.copy2(source_dir, target_dir) 276 shutil.copy2(source_dir, target_dir)
277 277
278 def list_all(self):
279 output = self._invoke_dnf(["repoquery", "--all", "--queryformat", "Packages: %{name} %{arch} %{version}"], print_output = False)
280 all_pkgs_lines = []
281 for line in output.splitlines():
282 if line.startswith("Packages: "):
283 all_pkgs_lines.append(line.replace("Packages: ", ""))
284 return "\n".join(all_pkgs_lines)
285
278 def list_installed(self): 286 def list_installed(self):
279 output = self._invoke_dnf(["repoquery", "--installed", "--queryformat", "Package: %{name} %{arch} %{version} %{name}-%{version}-%{release}.%{arch}.rpm\nDependencies:\n%{requires}\nRecommendations:\n%{recommends}\nDependenciesEndHere:\n"], 287 output = self._invoke_dnf(["repoquery", "--installed", "--queryformat", "Package: %{name} %{arch} %{version} %{name}-%{version}-%{release}.%{arch}.rpm\nDependencies:\n%{requires}\nRecommendations:\n%{recommends}\nDependenciesEndHere:\n"],
280 print_output = False) 288 print_output = False)
diff --git a/scripts/oe-pkgdata-util b/scripts/oe-pkgdata-util
index 44ae40549a..5b7cd768a4 100755
--- a/scripts/oe-pkgdata-util
+++ b/scripts/oe-pkgdata-util
@@ -51,6 +51,15 @@ def glob(args):
51 51
52 skippedpkgs = set() 52 skippedpkgs = set()
53 mappedpkgs = set() 53 mappedpkgs = set()
54 allpkgs = set()
55 if args.allpkgs:
56 with open(args.allpkgs, 'r') as f:
57 for line in f:
58 fields = line.rstrip().split()
59 if not fields:
60 continue
61 else:
62 allpkgs.add(fields[0])
54 with open(args.pkglistfile, 'r') as f: 63 with open(args.pkglistfile, 'r') as f:
55 for line in f: 64 for line in f:
56 fields = line.rstrip().split() 65 fields = line.rstrip().split()
@@ -136,6 +145,10 @@ def glob(args):
136 logger.debug("%s is not a valid package!" % (pkg)) 145 logger.debug("%s is not a valid package!" % (pkg))
137 break 146 break
138 147
148 if args.allpkgs:
149 if mappedpkg not in allpkgs:
150 continue
151
139 if mappedpkg: 152 if mappedpkg:
140 logger.debug("%s (%s) -> %s" % (pkg, g, mappedpkg)) 153 logger.debug("%s (%s) -> %s" % (pkg, g, mappedpkg))
141 mappedpkgs.add(mappedpkg) 154 mappedpkgs.add(mappedpkg)
@@ -592,6 +605,7 @@ def main():
592 parser_glob.add_argument('pkglistfile', help='File listing packages (one package name per line)') 605 parser_glob.add_argument('pkglistfile', help='File listing packages (one package name per line)')
593 parser_glob.add_argument('glob', nargs="+", help='Glob expression for package names, e.g. *-dev') 606 parser_glob.add_argument('glob', nargs="+", help='Glob expression for package names, e.g. *-dev')
594 parser_glob.add_argument('-x', '--exclude', help='Exclude packages matching specified regex from the glob operation') 607 parser_glob.add_argument('-x', '--exclude', help='Exclude packages matching specified regex from the glob operation')
608 parser_glob.add_argument('-a', '--allpkgs', help='File listing all available packages (one package name per line)')
595 parser_glob.set_defaults(func=glob) 609 parser_glob.set_defaults(func=glob)
596 610
597 611