Merge ~xnox/germinate:foreign-arch-ma-same into germinate:master

Proposed by Dimitri John Ledkov
Status: Needs review
Proposed branch: ~xnox/germinate:foreign-arch-ma-same
Merge into: germinate:master
Diff against target: 394 lines (+179/-37)
4 files modified
germinate/archive.py (+15/-12)
germinate/germinator.py (+91/-19)
germinate/scripts/germinate_main.py (+8/-5)
germinate/tests/test_germinator.py (+65/-1)
Reviewer Review Type Date Requested Status
Colin Watson Needs Fixing
Review via email: mp+374918@code.launchpad.net

Commit message

This branch enables to seed Multi-Arch: same package dependencies from a foreign arch (ie. :i386), making cross arch dependencies, on systems that have executed dpkg --add-architecture.

This does not allow to seed non-Multi-Arch:same packages (ie. application, e.g. wine32). However, this work can be extended to support that (care needs to be taken when expanding non-arch qualified recommends and resolving them from the default architecture).

This branch achieves generating identical list of i386 ship-live packages as hardcoded in the debian-cd branch for eoan release, when executed as

$ ./bin/germinate --arch amd64 --foreign i386 -d eoan -s ubuntu.eoan

To post a comment you must log in.
Revision history for this message
Dimitri John Ledkov (xnox) wrote :

This now passes existing testsuite. It is awkward that testsuite had many packages without an Architecture declared, and then foreign was not set, and like None == None is obviously True!

Revision history for this message
Colin Watson (cjwatson) wrote :

For the record, I'm waiting to review this until you've added the foreign arch tests you mentioned and removed the note that the branch is WIP from the description.

Revision history for this message
Dimitri John Ledkov (xnox) wrote :

Added a test that loads up both amd64&i386 Packages files, and asserts that behavior does not change with single-arch germinator; and does the right thing with foreign arch option set. Fixed a bug that said testcase surfaced.

I think this is now ready for review.

Revision history for this message
Colin Watson (cjwatson) :
review: Needs Fixing
~xnox/germinate:foreign-arch-ma-same updated
3556b42... by Dimitri John Ledkov

Merge remote-tracking branch 'germinate/master' into foreign-arch-ma-same

89bbfe9... by Dimitri John Ledkov

Start addressing review feedback

ca08d70... by Dimitri John Ledkov

Set default arch to amd64, and allow appending to it

595ccb2... by Dimitri John Ledkov

Merge remote-tracking branch 'germinate/master' into foreign-arch-ma-same

a41437a... by Dimitri John Ledkov

Update error messages

6214809... by Dimitri John Ledkov

revert defaults to be usable

Unmerged commits

6214809... by Dimitri John Ledkov

revert defaults to be usable

Failed
[FAILED] test:0 (build)
11 of 1 result
a41437a... by Dimitri John Ledkov

Update error messages

Failed
[FAILED] test:0 (build)
11 of 1 result
595ccb2... by Dimitri John Ledkov

Merge remote-tracking branch 'germinate/master' into foreign-arch-ma-same

Failed
[FAILED] test:0 (build)
11 of 1 result
ca08d70... by Dimitri John Ledkov

Set default arch to amd64, and allow appending to it

89bbfe9... by Dimitri John Ledkov

Start addressing review feedback

3556b42... by Dimitri John Ledkov

Merge remote-tracking branch 'germinate/master' into foreign-arch-ma-same

b342713... by Dimitri John Ledkov

Add foreign arch parsing, to support seeding M-A:same deps

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/germinate/archive.py b/germinate/archive.py
2index efb5e72..f48bbd2 100644
3--- a/germinate/archive.py
4+++ b/germinate/archive.py
5@@ -85,13 +85,15 @@ class Archive:
6 class TagFile(Archive):
7 """Fetch package lists from a Debian-format archive as apt tag files."""
8
9- def __init__(self, dists, components, arch, mirrors, source_mirrors=None,
10+ def __init__(self, dists, components, arches, mirrors, source_mirrors=None,
11 installer_packages=True, cleanup=False):
12 """Create a representation of a Debian-format apt archive."""
13 if isinstance(dists, six.string_types):
14 dists = [dists]
15 if isinstance(components, six.string_types):
16 components = [components]
17+ if isinstance(arches, six.string_types):
18+ arches = [arches]
19 if isinstance(mirrors, six.string_types):
20 mirrors = [mirrors]
21 if isinstance(source_mirrors, six.string_types):
22@@ -99,7 +101,7 @@ class TagFile(Archive):
23
24 self._dists = dists
25 self._components = components
26- self._arch = arch
27+ self._arches = arches
28 self._mirrors = mirrors
29 self._installer_packages = installer_packages
30 if source_mirrors:
31@@ -221,15 +223,16 @@ class TagFile(Archive):
32 try:
33 for dist in self._dists:
34 for component in self._components:
35- packages = self._open_tag_files(
36- self._mirrors, dirname, "Packages", dist, component,
37- "binary-" + self._arch + "/Packages")
38- for tag_file in packages:
39- try:
40- for section in apt_pkg.TagFile(tag_file):
41- yield (IndexType.PACKAGES, section)
42- finally:
43- tag_file.close()
44+ for arch in self._arches:
45+ packages = self._open_tag_files(
46+ self._mirrors, dirname, arch + "_Packages", dist,
47+ component, "binary-" + arch + "/Packages")
48+ for tag_file in packages:
49+ try:
50+ for section in apt_pkg.TagFile(tag_file):
51+ yield (IndexType.PACKAGES, section)
52+ finally:
53+ tag_file.close()
54
55 sources = self._open_tag_files(
56 self._source_mirrors, dirname, "Sources", dist,
57@@ -247,7 +250,7 @@ class TagFile(Archive):
58 instpackages = self._open_tag_files(
59 self._mirrors, dirname, "InstallerPackages",
60 dist, component,
61- "debian-installer/binary-" + self._arch +
62+ "debian-installer/binary-" + self._arches[0] +
63 "/Packages")
64 except IOError:
65 # can live without these
66diff --git a/germinate/germinator.py b/germinate/germinator.py
67index 8817990..379f569 100644
68--- a/germinate/germinator.py
69+++ b/germinate/germinator.py
70@@ -30,6 +30,7 @@ import re
71 import sys
72
73 import apt_pkg
74+import six
75 from six.moves.collections_abc import MutableMapping
76
77 from germinate.archive import IndexType
78@@ -382,15 +383,26 @@ class Germinator(object):
79 # Initialisation.
80 # ---------------
81
82- def __init__(self, arch):
83+ def __init__(self, arches):
84 """Create a dependency expander.
85
86- Each instance of this class can only process a single architecture,
87- but can process multiple seed structures against it.
88+ Each instance of this class can only process a single main
89+ architecture, and an optional single foreign architecture (for
90+ Multi-Arch:same only packages), but can process multiple seed
91+ structures against it.
92+
93+ arches can be a string, for main arch only. Or a list, in that
94+ case the first element is the main architecture, and the
95+ second element is the foreign arch.
96
97 """
98- self._arch = arch
99- apt_pkg.config.set("APT::Architecture", self._arch)
100+ if isinstance(arches, six.string_types):
101+ arches = [arches]
102+ self._arches = arches
103+
104+ apt_pkg.config.set("APT::Architecture", self._arches[0])
105+ for arch in self._arches:
106+ apt_pkg.config.set("APT::Architectures::", arch)
107
108 # Global hints file.
109 self._hints = {}
110@@ -445,6 +457,17 @@ class Germinator(object):
111 pkg = section["Package"]
112 ver = section["Version"]
113
114+ arch = section.get("Architecture", self._arches[0])
115+ if arch not in self._arches + ["all"]:
116+ # Skip unknown arches
117+ return
118+ if arch != self._arches[0]:
119+ if section.get("Multi-Arch") == "same":
120+ pkg += ":" + arch
121+ else:
122+ # Skip non-M-A-same foreign packages
123+ return
124+
125 # If we have already seen an equal or newer version of this package,
126 # then skip this section.
127 if pkg in self._packages:
128@@ -465,8 +488,7 @@ class Germinator(object):
129
130 self._packages[pkg]["Essential"] = section.get("Essential", "")
131
132- self._packages[pkg]["Architecture"] = \
133- section.get("Architecture", self._arch)
134+ self._packages[pkg]["Architecture"] = arch
135
136 for field in "Pre-Depends", "Depends", "Recommends", "Built-Using":
137 value = section.get(field, "")
138@@ -483,7 +505,7 @@ class Germinator(object):
139 value = section.get(field, "0")
140 self._packages[pkg][field] = int(value)
141
142- src = section.get("Source", pkg)
143+ src = section.get("Source", section["Package"])
144 idx = src.find("(")
145 if idx != -1:
146 src = src[:idx].strip()
147@@ -494,6 +516,38 @@ class Germinator(object):
148
149 self._packages[pkg]["Multi-Arch"] = section.get("Multi-Arch", "none")
150
151+ # if Multi-arch allowed/foreign, and main Architecture add
152+ # :foreign provides
153+ if arch in [self._arches[0], "all"]:
154+ if self._packages[pkg]["Multi-Arch"] in ["allowed", "foreign"]:
155+ for a in self._arches[1:]:
156+ self._packages[pkg]["Provides"].append(
157+ [("%s:%s" % (pkg, a), '', '')])
158+
159+ # if Package is from supplementary-arches qualify dependencies
160+ # as follows:
161+ # no tag, :native are changed to :arch
162+ # :any is dropped, meaning use host arch
163+ if arch in self._arches[1:]:
164+ for field in "Pre-Depends", "Depends", "Recommends", "Provides":
165+ new_deps = []
166+ for dep in self._packages[pkg][field]:
167+ new_dep = []
168+ for alt in dep:
169+ base = alt[0].split(':', 1)
170+ if len(base) > 1:
171+ tag = base[1]
172+ else:
173+ tag = arch
174+ if tag == "native":
175+ tag = arch
176+ if tag == "any":
177+ tag = ''
178+ new_dep.append(
179+ ('%s:%s' % (alt[0], tag), alt[1], alt[2]))
180+ new_deps.append(new_dep)
181+ self._packages[pkg][field] = new_deps
182+
183 self._packages[pkg]["Kernel-Version"] = section.get(
184 "Kernel-Version", "")
185
186@@ -843,11 +897,21 @@ class Germinator(object):
187 if startarchspec != -1:
188 archspec = pkg[startarchspec + 1:-1].split()
189 pkg = pkg[:startarchspec - 1]
190- posarch = [x for x in archspec if not x.startswith('!')]
191- negarch = [x[1:] for x in archspec if x.startswith('!')]
192- if self._arch in negarch:
193- continue
194- if posarch and self._arch not in posarch:
195+ # include only these arches
196+ posarch = set(
197+ [x for x in archspec if not x.startswith('!')])
198+ # exclude these arches
199+ negarch = set(
200+ [x[1:] for x in archspec if x.startswith('!')])
201+ # all available arches for this germinator
202+ archset = set(self._arches)
203+ # remove blacklisted
204+ archset = archset - negarch
205+ # if there is a whitelist, find common only
206+ if posarch:
207+ archset = archset & posarch
208+ # if we left without any usable arches, skip this
209+ if not archset:
210 continue
211
212 pkg = pkg.split()[0]
213@@ -962,8 +1026,8 @@ class Germinator(object):
214
215 else:
216 # No idea
217- _logger.error("Unknown package %s for seed %s on arch %s",
218- pkg, seed, self._arch)
219+ _logger.error("Unknown package %s for seed %s on arches %s",
220+ pkg, seed, self._arches)
221
222 for pkg in seedsnaps:
223 seed._snaps.add(pkg)
224@@ -1143,7 +1207,7 @@ class Germinator(object):
225 within any seed.
226
227 """
228- if ":" in depend:
229+ if depend.endswith(":any") or depend.endswith(":native"):
230 depname, depqual = depend.split(":", 1)
231 else:
232 depname = depend
233@@ -1184,7 +1248,11 @@ class Germinator(object):
234 seed, build_depend,
235 always_include_virtual=False):
236 """Get the possible candidates for satisfying a dependency."""
237- plain_depname = depname.split(":", 1)[0]
238+ depname_split = depname.split(":", 1)
239+ plain_depname = depname_split[0]
240+ if depname_split[-1] in self._arches[1:]:
241+ plain_depname = depname
242+
243 candidates = []
244 if plain_depname in self._packages:
245 candidates.append(
246@@ -1193,7 +1261,11 @@ class Germinator(object):
247 candidates.extend(self._provides[plain_depname].items())
248
249 for candpkg, candver in candidates:
250+ candpkg_split = candpkg.split(":", 1)
251 plain_candpkg = candpkg.split(":", 1)[0]
252+ if candpkg_split[-1] in self._arches[1:]:
253+ plain_candpkg = candpkg
254+
255 if plain_candpkg not in self._packages:
256 continue
257 allowed = False
258@@ -1453,9 +1525,9 @@ class Germinator(object):
259 desc = "recommendation"
260 else:
261 desc = "dependency"
262- _logger.error("Unknown %s %s by %s on arch %s", desc,
263+ _logger.error("Unknown %s %s by %s on arches %s", desc,
264 self._unparse_dependency(depname, depver, deptype),
265- pkg, self._arch)
266+ pkg, self._arches)
267 return False
268
269 if dependlist:
270diff --git a/germinate/scripts/germinate_main.py b/germinate/scripts/germinate_main.py
271index 329c1e6..3b37b26 100644
272--- a/germinate/scripts/germinate_main.py
273+++ b/germinate/scripts/germinate_main.py
274@@ -82,9 +82,9 @@ def parse_options(argv):
275 parser.add_option('-d', '--dist', dest='dist',
276 default=germinate.defaults.dist,
277 help='operate on distribution DIST (default: %default)')
278- parser.add_option('-a', '--arch', dest='arch',
279- default=germinate.defaults.arch,
280- help='operate on architecture ARCH (default: %default)')
281+ parser.add_option('-a', '--arch', dest='arches',
282+ action='append',
283+ help='operate on architecture ARCH (default: i386)')
284 parser.add_option('-c', '--components', dest='components',
285 default='main,restricted', metavar='COMPS',
286 help='operate on components COMPS (default: %default)')
287@@ -134,6 +134,9 @@ def parse_options(argv):
288 else:
289 options.seed_packages = options.seed_packages.split(',')
290
291+ if not options.arches:
292+ options.arches = [germinate.defaults.arch, ]
293+
294 return options
295
296
297@@ -145,11 +148,11 @@ def main(argv):
298 else:
299 germinate_logging(logging.INFO)
300
301- g = Germinator(options.arch)
302+ g = Germinator(options.arches)
303 g._always_follow_build_depends = options.always_follow_build_depends
304
305 archive = germinate.archive.TagFile(
306- options.dist, options.components, options.arch,
307+ options.dist, options.components, options.arches,
308 options.mirrors, source_mirrors=options.source_mirrors,
309 installer_packages=options.installer, cleanup=options.cleanup)
310 g.parse_archive(archive)
311diff --git a/germinate/tests/test_germinator.py b/germinate/tests/test_germinator.py
312index aaab106..a848cae 100644
313--- a/germinate/tests/test_germinator.py
314+++ b/germinate/tests/test_germinator.py
315@@ -323,6 +323,71 @@ class TestGerminator(TestCase):
316 shutil.rmtree(self.archive_dir)
317 shutil.rmtree(self.seeds_dir)
318
319+ def test_depends_foreign_multiarch(self):
320+ """Test that amd64 packages can recommend multi-arched :i386
321+ packages, although it's unspecified in the MultiarchSpec, it
322+ is possible with apt and is in use for Nvidia drivers
323+
324+ """
325+ for arches in (["amd64"], ["amd64", "i386"]):
326+ self.addSource("focal", "main", "hello", "1.0-1", ["hello"])
327+ self.addSource("focal", "main", "glibc", "2.36", ["libc6"])
328+ self.addSource("focal", "restricted", "nvidia-graphics", "1.0-1",
329+ ["libnvidia-compute"])
330+ self.addPackage("focal", "main", "amd64", "hello", "1.0-1",
331+ fields={
332+ "Architecture": "amd64",
333+ "Depends": "libnvidia-compute",
334+ "Recommends": "libnvidia-compute:i386",
335+ })
336+ self.addPackage("focal", "main", "i386", "hello-32bit", "1.0-1",
337+ fields={
338+ "Architecture": "i386",
339+ "Depends": "libc6",
340+ })
341+ for arch in "amd64", "i386":
342+ self.addPackage("focal", "main", arch, "libc6", "2.36",
343+ fields={
344+ "Architecture": arch,
345+ "Multi-Arch": "same"
346+ })
347+ self.addPackage("focal", "restricted", arch,
348+ "libnvidia-compute",
349+ "1.0-1", fields={
350+ "Architecture": arch,
351+ "Depends": "libc6",
352+ "Multi-Arch": "same",
353+ })
354+
355+ branch = "collection.focal"
356+ self.addStructureLine(branch, "feature follow-recommends")
357+ self.addSeed(branch, "base")
358+ self.addSeedPackage(branch, "base", "hello")
359+ # This package is seeded, but should be excluded
360+ # only M-A:same foreign arch packages are supported atm
361+ self.addSeedPackage(branch, "base", "hello-32bit")
362+ germinator = Germinator(arches)
363+ for arch in "i386", "amd64":
364+ for component in "main", "restricted":
365+ archive = TagFile(
366+ "focal", component, arch,
367+ "file://%s" % self.archive_dir)
368+ germinator.parse_archive(archive)
369+ structure = self.openSeedStructure(branch)
370+ germinator.plant_seeds(structure)
371+ germinator.grow(structure)
372+ expected = set(["hello",
373+ "libc6",
374+ "libnvidia-compute"])
375+ if 'i386' in arches:
376+ expected.add("libc6:i386")
377+ expected.add("libnvidia-compute:i386")
378+ self.assertEqual(
379+ expected, set(germinator.get_all(structure)))
380+
381+ shutil.rmtree(self.archive_dir)
382+ shutil.rmtree(self.seeds_dir)
383+
384 def test_build_depends_all(self):
385 """Confirm build-depends of arch: all packages are recursed when
386 follow-build-depends-all is set, and not when
387@@ -349,7 +414,6 @@ class TestGerminator(TestCase):
388 structure = self.openSeedStructure(branch)
389 germinator.plant_seeds(structure)
390 germinator.grow(structure)
391-
392 expected = set()
393 if sense == "":
394 expected.add("gettext")

Subscribers

People subscribed via source and target branches