blob: 9f112ae25b386f5ae64b5d096ef73b1c9296f65d [file] [log] [blame]
Andrew Geissler635e0e42020-08-21 15:58:33 -05001#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
5import re
6import subprocess
7from oe.package_manager import *
8
9class DpkgIndexer(Indexer):
10 def _create_configs(self):
11 bb.utils.mkdirhier(self.apt_conf_dir)
12 bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "lists", "partial"))
13 bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "apt.conf.d"))
14 bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "preferences.d"))
15
16 with open(os.path.join(self.apt_conf_dir, "preferences"),
17 "w") as prefs_file:
18 pass
19 with open(os.path.join(self.apt_conf_dir, "sources.list"),
20 "w+") as sources_file:
21 pass
22
23 with open(self.apt_conf_file, "w") as apt_conf:
24 with open(os.path.join(self.d.expand("${STAGING_ETCDIR_NATIVE}"),
25 "apt", "apt.conf.sample")) as apt_conf_sample:
26 for line in apt_conf_sample.read().split("\n"):
27 line = re.sub(r"#ROOTFS#", "/dev/null", line)
28 line = re.sub(r"#APTCONF#", self.apt_conf_dir, line)
29 apt_conf.write(line + "\n")
30
31 def write_index(self):
32 self.apt_conf_dir = os.path.join(self.d.expand("${APTCONF_TARGET}"),
33 "apt-ftparchive")
34 self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf")
35 self._create_configs()
36
37 os.environ['APT_CONFIG'] = self.apt_conf_file
38
39 pkg_archs = self.d.getVar('PACKAGE_ARCHS')
40 if pkg_archs is not None:
41 arch_list = pkg_archs.split()
42 sdk_pkg_archs = self.d.getVar('SDK_PACKAGE_ARCHS')
43 if sdk_pkg_archs is not None:
44 for a in sdk_pkg_archs.split():
45 if a not in pkg_archs:
46 arch_list.append(a)
47
48 all_mlb_pkg_arch_list = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS') or "").split()
49 arch_list.extend(arch for arch in all_mlb_pkg_arch_list if arch not in arch_list)
50
51 apt_ftparchive = bb.utils.which(os.getenv('PATH'), "apt-ftparchive")
52 gzip = bb.utils.which(os.getenv('PATH'), "gzip")
53
54 index_cmds = []
55 deb_dirs_found = False
56 for arch in arch_list:
57 arch_dir = os.path.join(self.deploy_dir, arch)
58 if not os.path.isdir(arch_dir):
59 continue
60
61 cmd = "cd %s; PSEUDO_UNLOAD=1 %s packages . > Packages;" % (arch_dir, apt_ftparchive)
62
63 cmd += "%s -fcn Packages > Packages.gz;" % gzip
64
65 with open(os.path.join(arch_dir, "Release"), "w+") as release:
66 release.write("Label: %s\n" % arch)
67
68 cmd += "PSEUDO_UNLOAD=1 %s release . >> Release" % apt_ftparchive
69
70 index_cmds.append(cmd)
71
72 deb_dirs_found = True
73
74 if not deb_dirs_found:
75 bb.note("There are no packages in %s" % self.deploy_dir)
76 return
77
78 oe.utils.multiprocess_launch(create_index, index_cmds, self.d)
79 if self.d.getVar('PACKAGE_FEED_SIGN') == '1':
80 raise NotImplementedError('Package feed signing not implementd for dpkg')
81
Andrew Geissler6ce62a22020-11-30 19:58:47 -060082class PMPkgsList(PkgsList):
Andrew Geissler635e0e42020-08-21 15:58:33 -050083
84 def list_pkgs(self):
85 cmd = [bb.utils.which(os.getenv('PATH'), "dpkg-query"),
86 "--admindir=%s/var/lib/dpkg" % self.rootfs_dir,
87 "-W"]
88
89 cmd.append("-f=Package: ${Package}\nArchitecture: ${PackageArch}\nVersion: ${Version}\nFile: ${Package}_${Version}_${Architecture}.deb\nDepends: ${Depends}\nRecommends: ${Recommends}\nProvides: ${Provides}\n\n")
90
91 try:
92 cmd_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip().decode("utf-8")
93 except subprocess.CalledProcessError as e:
94 bb.fatal("Cannot get the installed packages list. Command '%s' "
95 "returned %d:\n%s" % (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
96
97 return opkg_query(cmd_output)
98
99class OpkgDpkgPM(PackageManager):
100 def __init__(self, d, target_rootfs):
101 """
102 This is an abstract class. Do not instantiate this directly.
103 """
104 super(OpkgDpkgPM, self).__init__(d, target_rootfs)
105
106 def package_info(self, pkg, cmd):
107 """
108 Returns a dictionary with the package info.
109
110 This method extracts the common parts for Opkg and Dpkg
111 """
112
113 try:
114 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8")
115 except subprocess.CalledProcessError as e:
116 bb.fatal("Unable to list available packages. Command '%s' "
117 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
118 return opkg_query(output)
119
120 def extract(self, pkg, pkg_info):
121 """
122 Returns the path to a tmpdir where resides the contents of a package.
123
124 Deleting the tmpdir is responsability of the caller.
125
126 This method extracts the common parts for Opkg and Dpkg
127 """
128
129 ar_cmd = bb.utils.which(os.getenv("PATH"), "ar")
130 tar_cmd = bb.utils.which(os.getenv("PATH"), "tar")
131 pkg_path = pkg_info[pkg]["filepath"]
132
133 if not os.path.isfile(pkg_path):
134 bb.fatal("Unable to extract package for '%s'."
135 "File %s doesn't exists" % (pkg, pkg_path))
136
137 tmp_dir = tempfile.mkdtemp()
138 current_dir = os.getcwd()
139 os.chdir(tmp_dir)
140 data_tar = 'data.tar.xz'
141
142 try:
143 cmd = [ar_cmd, 'x', pkg_path]
144 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
145 cmd = [tar_cmd, 'xf', data_tar]
146 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
147 except subprocess.CalledProcessError as e:
148 bb.utils.remove(tmp_dir, recurse=True)
149 bb.fatal("Unable to extract %s package. Command '%s' "
150 "returned %d:\n%s" % (pkg_path, ' '.join(cmd), e.returncode, e.output.decode("utf-8")))
151 except OSError as e:
152 bb.utils.remove(tmp_dir, recurse=True)
153 bb.fatal("Unable to extract %s package. Command '%s' "
154 "returned %d:\n%s at %s" % (pkg_path, ' '.join(cmd), e.errno, e.strerror, e.filename))
155
156 bb.note("Extracted %s to %s" % (pkg_path, tmp_dir))
157 bb.utils.remove(os.path.join(tmp_dir, "debian-binary"))
158 bb.utils.remove(os.path.join(tmp_dir, "control.tar.gz"))
159 os.chdir(current_dir)
160
161 return tmp_dir
162
163 def _handle_intercept_failure(self, registered_pkgs):
164 self.mark_packages("unpacked", registered_pkgs.split())
165
166class DpkgPM(OpkgDpkgPM):
167 def __init__(self, d, target_rootfs, archs, base_archs, apt_conf_dir=None, deb_repo_workdir="oe-rootfs-repo", filterbydependencies=True):
168 super(DpkgPM, self).__init__(d, target_rootfs)
169 self.deploy_dir = oe.path.join(self.d.getVar('WORKDIR'), deb_repo_workdir)
170
171 create_packages_dir(self.d, self.deploy_dir, d.getVar("DEPLOY_DIR_DEB"), "package_write_deb", filterbydependencies)
172
173 if apt_conf_dir is None:
174 self.apt_conf_dir = self.d.expand("${APTCONF_TARGET}/apt")
175 else:
176 self.apt_conf_dir = apt_conf_dir
177 self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf")
178 self.apt_get_cmd = bb.utils.which(os.getenv('PATH'), "apt-get")
179 self.apt_cache_cmd = bb.utils.which(os.getenv('PATH'), "apt-cache")
180
181 self.apt_args = d.getVar("APT_ARGS")
182
183 self.all_arch_list = archs.split()
184 all_mlb_pkg_arch_list = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS') or "").split()
185 self.all_arch_list.extend(arch for arch in all_mlb_pkg_arch_list if arch not in self.all_arch_list)
186
187 self._create_configs(archs, base_archs)
188
189 self.indexer = DpkgIndexer(self.d, self.deploy_dir)
190
191 def mark_packages(self, status_tag, packages=None):
192 """
193 This function will change a package's status in /var/lib/dpkg/status file.
194 If 'packages' is None then the new_status will be applied to all
195 packages
196 """
197 status_file = self.target_rootfs + "/var/lib/dpkg/status"
198
199 with open(status_file, "r") as sf:
200 with open(status_file + ".tmp", "w+") as tmp_sf:
201 if packages is None:
202 tmp_sf.write(re.sub(r"Package: (.*?)\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)",
203 r"Package: \1\n\2Status: \3%s" % status_tag,
204 sf.read()))
205 else:
206 if type(packages).__name__ != "list":
207 raise TypeError("'packages' should be a list object")
208
209 status = sf.read()
210 for pkg in packages:
211 status = re.sub(r"Package: %s\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)" % pkg,
212 r"Package: %s\n\1Status: \2%s" % (pkg, status_tag),
213 status)
214
215 tmp_sf.write(status)
216
Andrew Geisslerc926e172021-05-07 16:11:35 -0500217 bb.utils.rename(status_file + ".tmp", status_file)
Andrew Geissler635e0e42020-08-21 15:58:33 -0500218
219 def run_pre_post_installs(self, package_name=None):
220 """
221 Run the pre/post installs for package "package_name". If package_name is
222 None, then run all pre/post install scriptlets.
223 """
224 info_dir = self.target_rootfs + "/var/lib/dpkg/info"
225 ControlScript = collections.namedtuple("ControlScript", ["suffix", "name", "argument"])
226 control_scripts = [
227 ControlScript(".preinst", "Preinstall", "install"),
228 ControlScript(".postinst", "Postinstall", "configure")]
229 status_file = self.target_rootfs + "/var/lib/dpkg/status"
230 installed_pkgs = []
231
232 with open(status_file, "r") as status:
233 for line in status.read().split('\n'):
234 m = re.match(r"^Package: (.*)", line)
235 if m is not None:
236 installed_pkgs.append(m.group(1))
237
238 if package_name is not None and not package_name in installed_pkgs:
239 return
240
241 os.environ['D'] = self.target_rootfs
242 os.environ['OFFLINE_ROOT'] = self.target_rootfs
243 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
244 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
245 os.environ['INTERCEPT_DIR'] = self.intercepts_dir
246 os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE')
247
248 for pkg_name in installed_pkgs:
249 for control_script in control_scripts:
250 p_full = os.path.join(info_dir, pkg_name + control_script.suffix)
251 if os.path.exists(p_full):
252 try:
253 bb.note("Executing %s for package: %s ..." %
254 (control_script.name.lower(), pkg_name))
255 output = subprocess.check_output([p_full, control_script.argument],
256 stderr=subprocess.STDOUT).decode("utf-8")
257 bb.note(output)
258 except subprocess.CalledProcessError as e:
259 bb.warn("%s for package %s failed with %d:\n%s" %
260 (control_script.name, pkg_name, e.returncode,
261 e.output.decode("utf-8")))
262 failed_postinsts_abort([pkg_name], self.d.expand("${T}/log.do_${BB_CURRENTTASK}"))
263
264 def update(self):
265 os.environ['APT_CONFIG'] = self.apt_conf_file
266
267 self.deploy_dir_lock()
268
269 cmd = "%s update" % self.apt_get_cmd
270
271 try:
272 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
273 except subprocess.CalledProcessError as e:
274 bb.fatal("Unable to update the package index files. Command '%s' "
275 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
276
277 self.deploy_dir_unlock()
278
279 def install(self, pkgs, attempt_only=False):
280 if attempt_only and len(pkgs) == 0:
281 return
282
283 os.environ['APT_CONFIG'] = self.apt_conf_file
284
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600285 cmd = "%s %s install --allow-downgrades --allow-remove-essential --allow-change-held-packages --allow-unauthenticated --no-remove %s" % \
Andrew Geissler635e0e42020-08-21 15:58:33 -0500286 (self.apt_get_cmd, self.apt_args, ' '.join(pkgs))
287
288 try:
289 bb.note("Installing the following packages: %s" % ' '.join(pkgs))
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600290 output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
291 bb.note(output.decode("utf-8"))
Andrew Geissler635e0e42020-08-21 15:58:33 -0500292 except subprocess.CalledProcessError as e:
293 (bb.fatal, bb.warn)[attempt_only]("Unable to install packages. "
294 "Command '%s' returned %d:\n%s" %
295 (cmd, e.returncode, e.output.decode("utf-8")))
296
297 # rename *.dpkg-new files/dirs
298 for root, dirs, files in os.walk(self.target_rootfs):
299 for dir in dirs:
300 new_dir = re.sub(r"\.dpkg-new", "", dir)
301 if dir != new_dir:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500302 bb.utils.rename(os.path.join(root, dir),
Andrew Geissler635e0e42020-08-21 15:58:33 -0500303 os.path.join(root, new_dir))
304
305 for file in files:
306 new_file = re.sub(r"\.dpkg-new", "", file)
307 if file != new_file:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500308 bb.utils.rename(os.path.join(root, file),
Andrew Geissler635e0e42020-08-21 15:58:33 -0500309 os.path.join(root, new_file))
310
311
312 def remove(self, pkgs, with_dependencies=True):
313 if not pkgs:
314 return
315
Andrew Geissler9b4d8b02021-02-19 12:26:16 -0600316 os.environ['D'] = self.target_rootfs
317 os.environ['OFFLINE_ROOT'] = self.target_rootfs
318 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
319 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600320 os.environ['INTERCEPT_DIR'] = self.intercepts_dir
321
Andrew Geissler635e0e42020-08-21 15:58:33 -0500322 if with_dependencies:
323 os.environ['APT_CONFIG'] = self.apt_conf_file
324 cmd = "%s purge %s" % (self.apt_get_cmd, ' '.join(pkgs))
325 else:
326 cmd = "%s --admindir=%s/var/lib/dpkg --instdir=%s" \
327 " -P --force-depends %s" % \
328 (bb.utils.which(os.getenv('PATH'), "dpkg"),
329 self.target_rootfs, self.target_rootfs, ' '.join(pkgs))
330
331 try:
332 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
333 except subprocess.CalledProcessError as e:
334 bb.fatal("Unable to remove packages. Command '%s' "
335 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
336
337 def write_index(self):
338 self.deploy_dir_lock()
339
340 result = self.indexer.write_index()
341
342 self.deploy_dir_unlock()
343
344 if result is not None:
345 bb.fatal(result)
346
347 def insert_feeds_uris(self, feed_uris, feed_base_paths, feed_archs):
348 if feed_uris == "":
349 return
350
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600351
Andrew Geissler635e0e42020-08-21 15:58:33 -0500352 sources_conf = os.path.join("%s/etc/apt/sources.list"
353 % self.target_rootfs)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600354 if not os.path.exists(os.path.dirname(sources_conf)):
355 return
356
Andrew Geissler635e0e42020-08-21 15:58:33 -0500357 arch_list = []
358
359 if feed_archs is None:
360 for arch in self.all_arch_list:
361 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
362 continue
363 arch_list.append(arch)
364 else:
365 arch_list = feed_archs.split()
366
367 feed_uris = self.construct_uris(feed_uris.split(), feed_base_paths.split())
368
369 with open(sources_conf, "w+") as sources_file:
370 for uri in feed_uris:
371 if arch_list:
372 for arch in arch_list:
373 bb.note('Adding dpkg channel at (%s)' % uri)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600374 sources_file.write("deb [trusted=yes] %s/%s ./\n" %
Andrew Geissler635e0e42020-08-21 15:58:33 -0500375 (uri, arch))
376 else:
377 bb.note('Adding dpkg channel at (%s)' % uri)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600378 sources_file.write("deb [trusted=yes] %s ./\n" % uri)
Andrew Geissler635e0e42020-08-21 15:58:33 -0500379
380 def _create_configs(self, archs, base_archs):
381 base_archs = re.sub(r"_", r"-", base_archs)
382
383 if os.path.exists(self.apt_conf_dir):
384 bb.utils.remove(self.apt_conf_dir, True)
385
386 bb.utils.mkdirhier(self.apt_conf_dir)
387 bb.utils.mkdirhier(self.apt_conf_dir + "/lists/partial/")
388 bb.utils.mkdirhier(self.apt_conf_dir + "/apt.conf.d/")
389 bb.utils.mkdirhier(self.apt_conf_dir + "/preferences.d/")
390
391 arch_list = []
392 for arch in self.all_arch_list:
393 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
394 continue
395 arch_list.append(arch)
396
397 with open(os.path.join(self.apt_conf_dir, "preferences"), "w+") as prefs_file:
398 priority = 801
399 for arch in arch_list:
400 prefs_file.write(
401 "Package: *\n"
402 "Pin: release l=%s\n"
403 "Pin-Priority: %d\n\n" % (arch, priority))
404
405 priority += 5
406
407 pkg_exclude = self.d.getVar('PACKAGE_EXCLUDE') or ""
408 for pkg in pkg_exclude.split():
409 prefs_file.write(
410 "Package: %s\n"
411 "Pin: release *\n"
412 "Pin-Priority: -1\n\n" % pkg)
413
414 arch_list.reverse()
415
416 with open(os.path.join(self.apt_conf_dir, "sources.list"), "w+") as sources_file:
417 for arch in arch_list:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600418 sources_file.write("deb [trusted=yes] file:%s/ ./\n" %
Andrew Geissler635e0e42020-08-21 15:58:33 -0500419 os.path.join(self.deploy_dir, arch))
420
421 base_arch_list = base_archs.split()
422 multilib_variants = self.d.getVar("MULTILIB_VARIANTS");
423 for variant in multilib_variants.split():
424 localdata = bb.data.createCopy(self.d)
Patrick Williams213cb262021-08-07 19:21:33 -0500425 variant_tune = localdata.getVar("DEFAULTTUNE:virtclass-multilib-" + variant, False)
Andrew Geissler635e0e42020-08-21 15:58:33 -0500426 orig_arch = localdata.getVar("DPKG_ARCH")
427 localdata.setVar("DEFAULTTUNE", variant_tune)
428 variant_arch = localdata.getVar("DPKG_ARCH")
429 if variant_arch not in base_arch_list:
430 base_arch_list.append(variant_arch)
431
432 with open(self.apt_conf_file, "w+") as apt_conf:
433 with open(self.d.expand("${STAGING_ETCDIR_NATIVE}/apt/apt.conf.sample")) as apt_conf_sample:
434 for line in apt_conf_sample.read().split("\n"):
435 match_arch = re.match(r" Architecture \".*\";$", line)
436 architectures = ""
437 if match_arch:
438 for base_arch in base_arch_list:
439 architectures += "\"%s\";" % base_arch
440 apt_conf.write(" Architectures {%s};\n" % architectures);
441 apt_conf.write(" Architecture \"%s\";\n" % base_archs)
442 else:
443 line = re.sub(r"#ROOTFS#", self.target_rootfs, line)
444 line = re.sub(r"#APTCONF#", self.apt_conf_dir, line)
445 apt_conf.write(line + "\n")
446
447 target_dpkg_dir = "%s/var/lib/dpkg" % self.target_rootfs
448 bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "info"))
449
450 bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "updates"))
451
452 if not os.path.exists(os.path.join(target_dpkg_dir, "status")):
453 open(os.path.join(target_dpkg_dir, "status"), "w+").close()
454 if not os.path.exists(os.path.join(target_dpkg_dir, "available")):
455 open(os.path.join(target_dpkg_dir, "available"), "w+").close()
456
457 def remove_packaging_data(self):
458 bb.utils.remove(self.target_rootfs + self.d.getVar('opkglibdir'), True)
459 bb.utils.remove(self.target_rootfs + "/var/lib/dpkg/", True)
460
461 def fix_broken_dependencies(self):
462 os.environ['APT_CONFIG'] = self.apt_conf_file
463
464 cmd = "%s %s --allow-unauthenticated -f install" % (self.apt_get_cmd, self.apt_args)
465
466 try:
467 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
468 except subprocess.CalledProcessError as e:
469 bb.fatal("Cannot fix broken dependencies. Command '%s' "
470 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
471
472 def list_installed(self):
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600473 return PMPkgsList(self.d, self.target_rootfs).list_pkgs()
Andrew Geissler635e0e42020-08-21 15:58:33 -0500474
475 def package_info(self, pkg):
476 """
477 Returns a dictionary with the package info.
478 """
479 cmd = "%s show %s" % (self.apt_cache_cmd, pkg)
480 pkg_info = super(DpkgPM, self).package_info(pkg, cmd)
481
482 pkg_arch = pkg_info[pkg]["pkgarch"]
483 pkg_filename = pkg_info[pkg]["filename"]
484 pkg_info[pkg]["filepath"] = \
485 os.path.join(self.deploy_dir, pkg_arch, pkg_filename)
486
487 return pkg_info
488
489 def extract(self, pkg):
490 """
491 Returns the path to a tmpdir where resides the contents of a package.
492
493 Deleting the tmpdir is responsability of the caller.
494 """
495 pkg_info = self.package_info(pkg)
496 if not pkg_info:
497 bb.fatal("Unable to get information for package '%s' while "
498 "trying to extract the package." % pkg)
499
500 tmp_dir = super(DpkgPM, self).extract(pkg, pkg_info)
501 bb.utils.remove(os.path.join(tmp_dir, "data.tar.xz"))
502
503 return tmp_dir