blob: ed8fec850999d3e41c991274a5c27f8b19b6877b [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001from abc import ABCMeta, abstractmethod
2import os
3import glob
4import subprocess
5import shutil
6import multiprocessing
7import re
Patrick Williamsc0f7c042017-02-23 20:41:17 -06008import collections
Patrick Williamsc124f4f2015-09-15 14:41:29 -05009import bb
10import tempfile
11import oe.utils
Patrick Williamsc0f7c042017-02-23 20:41:17 -060012import oe.path
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050013import string
14from oe.gpg_sign import get_signer
Patrick Williamsc124f4f2015-09-15 14:41:29 -050015
16# this can be used by all PM backends to create the index files in parallel
17def create_index(arg):
18 index_cmd = arg
19
Brad Bishopd7bf8c12018-02-25 22:55:05 -050020 bb.note("Executing '%s' ..." % index_cmd)
21 result = subprocess.check_output(index_cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050022 if result:
23 bb.note(result)
24
Patrick Williamsc0f7c042017-02-23 20:41:17 -060025"""
26This method parse the output from the package managerand return
27a dictionary with the information of the packages. This is used
28when the packages are in deb or ipk format.
29"""
30def opkg_query(cmd_output):
31 verregex = re.compile(' \([=<>]* [^ )]*\)')
32 output = dict()
33 pkg = ""
34 arch = ""
35 ver = ""
36 filename = ""
37 dep = []
38 pkgarch = ""
39 for line in cmd_output.splitlines():
40 line = line.rstrip()
41 if ':' in line:
42 if line.startswith("Package: "):
43 pkg = line.split(": ")[1]
44 elif line.startswith("Architecture: "):
45 arch = line.split(": ")[1]
46 elif line.startswith("Version: "):
47 ver = line.split(": ")[1]
48 elif line.startswith("File: ") or line.startswith("Filename:"):
49 filename = line.split(": ")[1]
50 if "/" in filename:
51 filename = os.path.basename(filename)
52 elif line.startswith("Depends: "):
53 depends = verregex.sub('', line.split(": ")[1])
54 for depend in depends.split(", "):
55 dep.append(depend)
56 elif line.startswith("Recommends: "):
57 recommends = verregex.sub('', line.split(": ")[1])
58 for recommend in recommends.split(", "):
59 dep.append("%s [REC]" % recommend)
60 elif line.startswith("PackageArch: "):
61 pkgarch = line.split(": ")[1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -050062
Patrick Williamsc0f7c042017-02-23 20:41:17 -060063 # When there is a blank line save the package information
64 elif not line:
65 # IPK doesn't include the filename
66 if not filename:
67 filename = "%s_%s_%s.ipk" % (pkg, ver, arch)
68 if pkg:
69 output[pkg] = {"arch":arch, "ver":ver,
70 "filename":filename, "deps": dep, "pkgarch":pkgarch }
71 pkg = ""
72 arch = ""
73 ver = ""
74 filename = ""
75 dep = []
76 pkgarch = ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -050077
Patrick Williamsc0f7c042017-02-23 20:41:17 -060078 if pkg:
79 if not filename:
80 filename = "%s_%s_%s.ipk" % (pkg, ver, arch)
81 output[pkg] = {"arch":arch, "ver":ver,
82 "filename":filename, "deps": dep }
83
84 return output
85
86
87class Indexer(object, metaclass=ABCMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050088 def __init__(self, d, deploy_dir):
89 self.d = d
90 self.deploy_dir = deploy_dir
91
92 @abstractmethod
93 def write_index(self):
94 pass
95
96
97class RpmIndexer(Indexer):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098 def write_index(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -050099 if self.d.getVar('PACKAGE_FEED_SIGN') == '1':
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500100 signer = get_signer(self.d, self.d.getVar('PACKAGE_FEED_GPG_BACKEND'))
101 else:
102 signer = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500103
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500104 createrepo_c = bb.utils.which(os.environ['PATH'], "createrepo_c")
105 result = create_index("%s --update -q %s" % (createrepo_c, self.deploy_dir))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500106 if result:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500107 bb.fatal(result)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500108
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500109 # Sign repomd
110 if signer:
111 sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE')
112 is_ascii_sig = (sig_type.upper() != "BIN")
113 signer.detach_sign(os.path.join(self.deploy_dir, 'repodata', 'repomd.xml'),
114 self.d.getVar('PACKAGE_FEED_GPG_NAME'),
115 self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE'),
116 armor=is_ascii_sig)
117
118
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119class OpkgIndexer(Indexer):
120 def write_index(self):
121 arch_vars = ["ALL_MULTILIB_PACKAGE_ARCHS",
122 "SDK_PACKAGE_ARCHS",
123 "MULTILIB_ARCHS"]
124
125 opkg_index_cmd = bb.utils.which(os.getenv('PATH'), "opkg-make-index")
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500126 if self.d.getVar('PACKAGE_FEED_SIGN') == '1':
127 signer = get_signer(self.d, self.d.getVar('PACKAGE_FEED_GPG_BACKEND'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500128 else:
129 signer = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500130
131 if not os.path.exists(os.path.join(self.deploy_dir, "Packages")):
132 open(os.path.join(self.deploy_dir, "Packages"), "w").close()
133
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500134 index_cmds = set()
135 index_sign_files = set()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500136 for arch_var in arch_vars:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500137 archs = self.d.getVar(arch_var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500138 if archs is None:
139 continue
140
141 for arch in archs.split():
142 pkgs_dir = os.path.join(self.deploy_dir, arch)
143 pkgs_file = os.path.join(pkgs_dir, "Packages")
144
145 if not os.path.isdir(pkgs_dir):
146 continue
147
148 if not os.path.exists(pkgs_file):
149 open(pkgs_file, "w").close()
150
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500151 index_cmds.add('%s -r %s -p %s -m %s' %
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500152 (opkg_index_cmd, pkgs_file, pkgs_file, pkgs_dir))
153
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500154 index_sign_files.add(pkgs_file)
155
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500156 if len(index_cmds) == 0:
157 bb.note("There are no packages in %s!" % self.deploy_dir)
158 return
159
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500160 oe.utils.multiprocess_exec(index_cmds, create_index)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500161
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500162 if signer:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500163 feed_sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500164 is_ascii_sig = (feed_sig_type.upper() != "BIN")
165 for f in index_sign_files:
166 signer.detach_sign(f,
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500167 self.d.getVar('PACKAGE_FEED_GPG_NAME'),
168 self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE'),
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500169 armor=is_ascii_sig)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170
171
172class DpkgIndexer(Indexer):
173 def _create_configs(self):
174 bb.utils.mkdirhier(self.apt_conf_dir)
175 bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "lists", "partial"))
176 bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "apt.conf.d"))
177 bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "preferences.d"))
178
179 with open(os.path.join(self.apt_conf_dir, "preferences"),
180 "w") as prefs_file:
181 pass
182 with open(os.path.join(self.apt_conf_dir, "sources.list"),
183 "w+") as sources_file:
184 pass
185
186 with open(self.apt_conf_file, "w") as apt_conf:
187 with open(os.path.join(self.d.expand("${STAGING_ETCDIR_NATIVE}"),
188 "apt", "apt.conf.sample")) as apt_conf_sample:
189 for line in apt_conf_sample.read().split("\n"):
190 line = re.sub("#ROOTFS#", "/dev/null", line)
191 line = re.sub("#APTCONF#", self.apt_conf_dir, line)
192 apt_conf.write(line + "\n")
193
194 def write_index(self):
195 self.apt_conf_dir = os.path.join(self.d.expand("${APTCONF_TARGET}"),
196 "apt-ftparchive")
197 self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf")
198 self._create_configs()
199
200 os.environ['APT_CONFIG'] = self.apt_conf_file
201
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500202 pkg_archs = self.d.getVar('PACKAGE_ARCHS')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500203 if pkg_archs is not None:
204 arch_list = pkg_archs.split()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500205 sdk_pkg_archs = self.d.getVar('SDK_PACKAGE_ARCHS')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500206 if sdk_pkg_archs is not None:
207 for a in sdk_pkg_archs.split():
208 if a not in pkg_archs:
209 arch_list.append(a)
210
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500211 all_mlb_pkg_arch_list = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS') or "").split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500212 arch_list.extend(arch for arch in all_mlb_pkg_arch_list if arch not in arch_list)
213
214 apt_ftparchive = bb.utils.which(os.getenv('PATH'), "apt-ftparchive")
215 gzip = bb.utils.which(os.getenv('PATH'), "gzip")
216
217 index_cmds = []
218 deb_dirs_found = False
219 for arch in arch_list:
220 arch_dir = os.path.join(self.deploy_dir, arch)
221 if not os.path.isdir(arch_dir):
222 continue
223
224 cmd = "cd %s; PSEUDO_UNLOAD=1 %s packages . > Packages;" % (arch_dir, apt_ftparchive)
225
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500226 cmd += "%s -fcn Packages > Packages.gz;" % gzip
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500227
228 with open(os.path.join(arch_dir, "Release"), "w+") as release:
229 release.write("Label: %s\n" % arch)
230
231 cmd += "PSEUDO_UNLOAD=1 %s release . >> Release" % apt_ftparchive
232
233 index_cmds.append(cmd)
234
235 deb_dirs_found = True
236
237 if not deb_dirs_found:
238 bb.note("There are no packages in %s" % self.deploy_dir)
239 return
240
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500241 oe.utils.multiprocess_exec(index_cmds, create_index)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500242 if self.d.getVar('PACKAGE_FEED_SIGN') == '1':
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500243 raise NotImplementedError('Package feed signing not implementd for dpkg')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500244
245
246
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600247class PkgsList(object, metaclass=ABCMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500248 def __init__(self, d, rootfs_dir):
249 self.d = d
250 self.rootfs_dir = rootfs_dir
251
252 @abstractmethod
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500253 def list_pkgs(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254 pass
255
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500256class RpmPkgsList(PkgsList):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500257 def list_pkgs(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500258 return RpmPM(self.d, self.rootfs_dir, self.d.getVar('TARGET_VENDOR')).list_installed()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500259
260class OpkgPkgsList(PkgsList):
261 def __init__(self, d, rootfs_dir, config_file):
262 super(OpkgPkgsList, self).__init__(d, rootfs_dir)
263
264 self.opkg_cmd = bb.utils.which(os.getenv('PATH'), "opkg")
265 self.opkg_args = "-f %s -o %s " % (config_file, rootfs_dir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500266 self.opkg_args += self.d.getVar("OPKG_ARGS")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500267
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500268 def list_pkgs(self, format=None):
269 cmd = "%s %s status" % (self.opkg_cmd, self.opkg_args)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500270
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500271 # opkg returns success even when it printed some
272 # "Collected errors:" report to stderr. Mixing stderr into
273 # stdout then leads to random failures later on when
274 # parsing the output. To avoid this we need to collect both
275 # output streams separately and check for empty stderr.
276 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
277 cmd_output, cmd_stderr = p.communicate()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600278 cmd_output = cmd_output.decode("utf-8")
279 cmd_stderr = cmd_stderr.decode("utf-8")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500280 if p.returncode or cmd_stderr:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500281 bb.fatal("Cannot get the installed packages list. Command '%s' "
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500282 "returned %d and stderr:\n%s" % (cmd, p.returncode, cmd_stderr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500283
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600284 return opkg_query(cmd_output)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500285
286
287class DpkgPkgsList(PkgsList):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500288
289 def list_pkgs(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500290 cmd = [bb.utils.which(os.getenv('PATH'), "dpkg-query"),
291 "--admindir=%s/var/lib/dpkg" % self.rootfs_dir,
292 "-W"]
293
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500294 cmd.append("-f=Package: ${Package}\nArchitecture: ${PackageArch}\nVersion: ${Version}\nFile: ${Package}_${Version}_${Architecture}.deb\nDepends: ${Depends}\nRecommends: ${Recommends}\n\n")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500295
296 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600297 cmd_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip().decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500298 except subprocess.CalledProcessError as e:
299 bb.fatal("Cannot get the installed packages list. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600300 "returned %d:\n%s" % (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500301
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600302 return opkg_query(cmd_output)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500303
304
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600305class PackageManager(object, metaclass=ABCMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500306 """
307 This is an abstract class. Do not instantiate this directly.
308 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500309
310 def __init__(self, d):
311 self.d = d
312 self.deploy_dir = None
313 self.deploy_lock = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500314
315 """
316 Update the package manager package database.
317 """
318 @abstractmethod
319 def update(self):
320 pass
321
322 """
323 Install a list of packages. 'pkgs' is a list object. If 'attempt_only' is
324 True, installation failures are ignored.
325 """
326 @abstractmethod
327 def install(self, pkgs, attempt_only=False):
328 pass
329
330 """
331 Remove a list of packages. 'pkgs' is a list object. If 'with_dependencies'
332 is False, the any dependencies are left in place.
333 """
334 @abstractmethod
335 def remove(self, pkgs, with_dependencies=True):
336 pass
337
338 """
339 This function creates the index files
340 """
341 @abstractmethod
342 def write_index(self):
343 pass
344
345 @abstractmethod
346 def remove_packaging_data(self):
347 pass
348
349 @abstractmethod
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500350 def list_installed(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500351 pass
352
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500353 """
354 Returns the path to a tmpdir where resides the contents of a package.
355
356 Deleting the tmpdir is responsability of the caller.
357
358 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500359 @abstractmethod
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500360 def extract(self, pkg):
361 pass
362
363 """
364 Add remote package feeds into repository manager configuration. The parameters
365 for the feeds are set by feed_uris, feed_base_paths and feed_archs.
366 See http://www.yoctoproject.org/docs/current/ref-manual/ref-manual.html#var-PACKAGE_FEED_URIS
367 for their description.
368 """
369 @abstractmethod
370 def insert_feeds_uris(self, feed_uris, feed_base_paths, feed_archs):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500371 pass
372
373 """
Brad Bishop00111322018-04-01 22:23:53 -0400374 Install all packages that match a glob.
375 """
376 def install_glob(self, globs, sdk=False):
377 # TODO don't have sdk here but have a property on the superclass
378 # (and respect in install_complementary)
379 if sdk:
380 pkgdatadir = self.d.expand("${TMPDIR}/pkgdata/${SDK_SYS}")
381 else:
382 pkgdatadir = self.d.getVar("PKGDATA_DIR")
383
384 try:
385 bb.note("Installing globbed packages...")
386 cmd = ["oe-pkgdata-util", "-p", pkgdatadir, "list-pkgs", globs]
387 pkgs = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode("utf-8")
388 self.install(pkgs.split(), attempt_only=True)
389 except subprocess.CalledProcessError as e:
390 # Return code 1 means no packages matched
391 if e.returncode != 1:
392 bb.fatal("Could not compute globbed packages list. Command "
393 "'%s' returned %d:\n%s" %
394 (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
395
396 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500397 Install complementary packages based upon the list of currently installed
398 packages e.g. locales, *-dev, *-dbg, etc. This will only attempt to install
399 these packages, if they don't exist then no error will occur. Note: every
400 backend needs to call this function explicitly after the normal package
401 installation
402 """
403 def install_complementary(self, globs=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500404 if globs is None:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500405 globs = self.d.getVar('IMAGE_INSTALL_COMPLEMENTARY')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500406 split_linguas = set()
407
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500408 for translation in self.d.getVar('IMAGE_LINGUAS').split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500409 split_linguas.add(translation)
410 split_linguas.add(translation.split('-')[0])
411
412 split_linguas = sorted(split_linguas)
413
414 for lang in split_linguas:
415 globs += " *-locale-%s" % lang
416
417 if globs is None:
418 return
419
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500420 # we need to write the list of installed packages to a file because the
421 # oe-pkgdata-util reads it from a file
422 with tempfile.NamedTemporaryFile(mode="w+", prefix="installed-pkgs") as installed_pkgs:
423 pkgs = self.list_installed()
424 output = oe.utils.format_pkg_list(pkgs, "arch")
425 installed_pkgs.write(output)
426 installed_pkgs.flush()
427
Brad Bishop00111322018-04-01 22:23:53 -0400428 cmd = ["oe-pkgdata-util",
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500429 "-p", self.d.getVar('PKGDATA_DIR'), "glob", installed_pkgs.name,
430 globs]
431 exclude = self.d.getVar('PACKAGE_EXCLUDE_COMPLEMENTARY')
432 if exclude:
433 cmd.extend(['--exclude=' + '|'.join(exclude.split())])
434 try:
435 bb.note("Installing complementary packages ...")
436 bb.note('Running %s' % cmd)
437 complementary_pkgs = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode("utf-8")
Brad Bishop00111322018-04-01 22:23:53 -0400438 self.install(complementary_pkgs.split(), attempt_only=True)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500439 except subprocess.CalledProcessError as e:
440 bb.fatal("Could not compute complementary packages list. Command "
441 "'%s' returned %d:\n%s" %
442 (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500443
444 def deploy_dir_lock(self):
445 if self.deploy_dir is None:
446 raise RuntimeError("deploy_dir is not set!")
447
448 lock_file_name = os.path.join(self.deploy_dir, "deploy.lock")
449
450 self.deploy_lock = bb.utils.lockfile(lock_file_name)
451
452 def deploy_dir_unlock(self):
453 if self.deploy_lock is None:
454 return
455
456 bb.utils.unlockfile(self.deploy_lock)
457
458 self.deploy_lock = None
459
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500460 """
461 Construct URIs based on the following pattern: uri/base_path where 'uri'
462 and 'base_path' correspond to each element of the corresponding array
463 argument leading to len(uris) x len(base_paths) elements on the returned
464 array
465 """
466 def construct_uris(self, uris, base_paths):
467 def _append(arr1, arr2, sep='/'):
468 res = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600469 narr1 = [a.rstrip(sep) for a in arr1]
470 narr2 = [a.rstrip(sep).lstrip(sep) for a in arr2]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500471 for a1 in narr1:
472 if arr2:
473 for a2 in narr2:
474 res.append("%s%s%s" % (a1, sep, a2))
475 else:
476 res.append(a1)
477 return res
478 return _append(uris, base_paths)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500479
480class RpmPM(PackageManager):
481 def __init__(self,
482 d,
483 target_rootfs,
484 target_vendor,
485 task_name='target',
486 providename=None,
487 arch_var=None,
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500488 os_var=None,
489 rpm_repo_workdir="oe-rootfs-repo"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500490 super(RpmPM, self).__init__(d)
491 self.target_rootfs = target_rootfs
492 self.target_vendor = target_vendor
493 self.task_name = task_name
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500494 if arch_var == None:
495 self.archs = self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS').replace("-","_")
496 else:
497 self.archs = self.d.getVar(arch_var).replace("-","_")
498 if task_name == "host":
499 self.primary_arch = self.d.getVar('SDK_ARCH')
500 else:
501 self.primary_arch = self.d.getVar('MACHINE_ARCH')
502
503 self.rpm_repo_dir = oe.path.join(self.d.getVar('WORKDIR'), rpm_repo_workdir)
504 bb.utils.mkdirhier(self.rpm_repo_dir)
505 oe.path.symlink(self.d.getVar('DEPLOY_DIR_RPM'), oe.path.join(self.rpm_repo_dir, "rpm"), True)
506
507 self.saved_packaging_data = self.d.expand('${T}/saved_packaging_data/%s' % self.task_name)
508 if not os.path.exists(self.d.expand('${T}/saved_packaging_data')):
509 bb.utils.mkdirhier(self.d.expand('${T}/saved_packaging_data'))
510 self.packaging_data_dirs = ['var/lib/rpm', 'var/lib/dnf', 'var/cache/dnf']
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500511 self.solution_manifest = self.d.expand('${T}/saved/%s_solution' %
512 self.task_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500513 if not os.path.exists(self.d.expand('${T}/saved')):
514 bb.utils.mkdirhier(self.d.expand('${T}/saved'))
515
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500516 def _configure_dnf(self):
517 # libsolv handles 'noarch' internally, we don't need to specify it explicitly
518 archs = [i for i in reversed(self.archs.split()) if i not in ["any", "all", "noarch"]]
519 # This prevents accidental matching against libsolv's built-in policies
520 if len(archs) <= 1:
521 archs = archs + ["bogusarch"]
522 confdir = "%s/%s" %(self.target_rootfs, "etc/dnf/vars/")
523 bb.utils.mkdirhier(confdir)
524 open(confdir + "arch", 'w').write(":".join(archs))
525 distro_codename = self.d.getVar('DISTRO_CODENAME')
526 open(confdir + "releasever", 'w').write(distro_codename if distro_codename is not None else '')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500527
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500528 open(oe.path.join(self.target_rootfs, "etc/dnf/dnf.conf"), 'w').write("")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500529
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500530
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500531 def _configure_rpm(self):
532 # We need to configure rpm to use our primary package architecture as the installation architecture,
533 # and to make it compatible with other package architectures that we use.
534 # Otherwise it will refuse to proceed with packages installation.
535 platformconfdir = "%s/%s" %(self.target_rootfs, "etc/rpm/")
536 rpmrcconfdir = "%s/%s" %(self.target_rootfs, "etc/")
537 bb.utils.mkdirhier(platformconfdir)
538 open(platformconfdir + "platform", 'w').write("%s-pc-linux" % self.primary_arch)
539 open(rpmrcconfdir + "rpmrc", 'w').write("arch_compat: %s: %s\n" % (self.primary_arch, self.archs if len(self.archs) > 0 else self.primary_arch))
540
541 open(platformconfdir + "macros", 'w').write("%_transaction_color 7\n")
542 if self.d.getVar('RPM_PREFER_ELF_ARCH'):
543 open(platformconfdir + "macros", 'a').write("%%_prefer_color %s" % (self.d.getVar('RPM_PREFER_ELF_ARCH')))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500544 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500545 open(platformconfdir + "macros", 'a').write("%_prefer_color 7")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500546
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500547 if self.d.getVar('RPM_SIGN_PACKAGES') == '1':
548 signer = get_signer(self.d, self.d.getVar('RPM_GPG_BACKEND'))
549 pubkey_path = oe.path.join(self.d.getVar('B'), 'rpm-key')
550 signer.export_pubkey(pubkey_path, self.d.getVar('RPM_GPG_NAME'))
551 rpm_bin = bb.utils.which(os.getenv('PATH'), "rpmkeys")
552 cmd = [rpm_bin, '--root=%s' % self.target_rootfs, '--import', pubkey_path]
553 try:
554 subprocess.check_output(cmd, stderr=subprocess.STDOUT)
555 except subprocess.CalledProcessError as e:
556 bb.fatal("Importing GPG key failed. Command '%s' "
557 "returned %d:\n%s" % (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500558
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500559 def create_configs(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500560 self._configure_dnf()
561 self._configure_rpm()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500562
563 def write_index(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500564 lockfilename = self.d.getVar('DEPLOY_DIR_RPM') + "/rpm.lock"
565 lf = bb.utils.lockfile(lockfilename, False)
566 RpmIndexer(self.d, self.rpm_repo_dir).write_index()
567 bb.utils.unlockfile(lf)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500568
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500569 def insert_feeds_uris(self, feed_uris, feed_base_paths, feed_archs):
570 from urllib.parse import urlparse
571
572 if feed_uris == "":
573 return
574
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500575 gpg_opts = ''
576 if self.d.getVar('PACKAGE_FEED_SIGN') == '1':
577 gpg_opts += 'repo_gpgcheck=1\n'
578 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'))
579
580 if self.d.getVar('RPM_SIGN_PACKAGES') == '0':
581 gpg_opts += 'gpgcheck=0\n'
582
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500583 bb.utils.mkdirhier(oe.path.join(self.target_rootfs, "etc", "yum.repos.d"))
584 remote_uris = self.construct_uris(feed_uris.split(), feed_base_paths.split())
585 for uri in remote_uris:
586 repo_base = "oe-remote-repo" + "-".join(urlparse(uri).path.split("/"))
587 if feed_archs is not None:
588 for arch in feed_archs.split():
589 repo_uri = uri + "/" + arch
590 repo_id = "oe-remote-repo" + "-".join(urlparse(repo_uri).path.split("/"))
591 repo_name = "OE Remote Repo:" + " ".join(urlparse(repo_uri).path.split("/"))
592 open(oe.path.join(self.target_rootfs, "etc", "yum.repos.d", repo_base + ".repo"), 'a').write(
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500593 "[%s]\nname=%s\nbaseurl=%s\n%s\n" % (repo_id, repo_name, repo_uri, gpg_opts))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500594 else:
595 repo_name = "OE Remote Repo:" + " ".join(urlparse(uri).path.split("/"))
596 repo_uri = uri
597 open(oe.path.join(self.target_rootfs, "etc", "yum.repos.d", repo_base + ".repo"), 'w').write(
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500598 "[%s]\nname=%s\nbaseurl=%s\n%s" % (repo_base, repo_name, repo_uri, gpg_opts))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500599
600 def _prepare_pkg_transaction(self):
601 os.environ['D'] = self.target_rootfs
602 os.environ['OFFLINE_ROOT'] = self.target_rootfs
603 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
604 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
605 os.environ['INTERCEPT_DIR'] = oe.path.join(self.d.getVar('WORKDIR'),
606 "intercept_scripts")
607 os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE')
608
609
610 def install(self, pkgs, attempt_only = False):
611 if len(pkgs) == 0:
612 return
613 self._prepare_pkg_transaction()
614
615 bad_recommendations = self.d.getVar('BAD_RECOMMENDATIONS')
616 package_exclude = self.d.getVar('PACKAGE_EXCLUDE')
617 exclude_pkgs = (bad_recommendations.split() if bad_recommendations else []) + (package_exclude.split() if package_exclude else [])
618
619 output = self._invoke_dnf((["--skip-broken"] if attempt_only else []) +
620 (["-x", ",".join(exclude_pkgs)] if len(exclude_pkgs) > 0 else []) +
621 (["--setopt=install_weak_deps=False"] if self.d.getVar('NO_RECOMMENDATIONS') == "1" else []) +
622 (["--nogpgcheck"] if self.d.getVar('RPM_SIGN_PACKAGES') != '1' else ["--setopt=gpgcheck=True"]) +
623 ["install"] +
624 pkgs)
625
626 failed_scriptlets_pkgnames = collections.OrderedDict()
627 for line in output.splitlines():
628 if line.startswith("Non-fatal POSTIN scriptlet failure in rpm package"):
629 failed_scriptlets_pkgnames[line.split()[-1]] = True
630
631 for pkg in failed_scriptlets_pkgnames.keys():
632 self.save_rpmpostinst(pkg)
633
634 def remove(self, pkgs, with_dependencies = True):
635 if len(pkgs) == 0:
636 return
637 self._prepare_pkg_transaction()
638
639 if with_dependencies:
640 self._invoke_dnf(["remove"] + pkgs)
641 else:
642 cmd = bb.utils.which(os.getenv('PATH'), "rpm")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500643 args = ["-e", "-v", "--nodeps", "--root=%s" %self.target_rootfs]
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500644
645 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500646 bb.note("Running %s" % ' '.join([cmd] + args + pkgs))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500647 output = subprocess.check_output([cmd] + args + pkgs, stderr=subprocess.STDOUT).decode("utf-8")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500648 bb.note(output)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500649 except subprocess.CalledProcessError as e:
650 bb.fatal("Could not invoke rpm. Command "
651 "'%s' returned %d:\n%s" % (' '.join([cmd] + args + pkgs), e.returncode, e.output.decode("utf-8")))
652
653 def upgrade(self):
654 self._prepare_pkg_transaction()
655 self._invoke_dnf(["upgrade"])
656
657 def autoremove(self):
658 self._prepare_pkg_transaction()
659 self._invoke_dnf(["autoremove"])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500660
661 def remove_packaging_data(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500662 self._invoke_dnf(["clean", "all"])
663 for dir in self.packaging_data_dirs:
664 bb.utils.remove(oe.path.join(self.target_rootfs, dir), True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500665
666 def backup_packaging_data(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500667 # Save the packaging dirs for increment rpm image generation
668 if os.path.exists(self.saved_packaging_data):
669 bb.utils.remove(self.saved_packaging_data, True)
670 for i in self.packaging_data_dirs:
671 source_dir = oe.path.join(self.target_rootfs, i)
672 target_dir = oe.path.join(self.saved_packaging_data, i)
673 shutil.copytree(source_dir, target_dir, symlinks=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500674
675 def recovery_packaging_data(self):
676 # Move the rpmlib back
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500677 if os.path.exists(self.saved_packaging_data):
678 for i in self.packaging_data_dirs:
679 target_dir = oe.path.join(self.target_rootfs, i)
680 if os.path.exists(target_dir):
681 bb.utils.remove(target_dir, True)
682 source_dir = oe.path.join(self.saved_packaging_data, i)
683 shutil.copytree(source_dir,
684 target_dir,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500685 symlinks=True)
686
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500687 def list_installed(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500688 output = self._invoke_dnf(["repoquery", "--installed", "--queryformat", "Package: %{name} %{arch} %{version} %{name}-%{version}-%{release}.%{arch}.rpm\nDependencies:\n%{requires}\nRecommendations:\n%{recommends}\nDependenciesEndHere:\n"],
689 print_output = False)
690 packages = {}
691 current_package = None
692 current_deps = None
693 current_state = "initial"
694 for line in output.splitlines():
695 if line.startswith("Package:"):
696 package_info = line.split(" ")[1:]
697 current_package = package_info[0]
698 package_arch = package_info[1]
699 package_version = package_info[2]
700 package_rpm = package_info[3]
701 packages[current_package] = {"arch":package_arch, "ver":package_version, "filename":package_rpm}
702 current_deps = []
703 elif line.startswith("Dependencies:"):
704 current_state = "dependencies"
705 elif line.startswith("Recommendations"):
706 current_state = "recommendations"
707 elif line.startswith("DependenciesEndHere:"):
708 current_state = "initial"
709 packages[current_package]["deps"] = current_deps
710 elif len(line) > 0:
711 if current_state == "dependencies":
712 current_deps.append(line)
713 elif current_state == "recommendations":
714 current_deps.append("%s [REC]" % line)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500715
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500716 return packages
717
718 def update(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500719 self._invoke_dnf(["makecache", "--refresh"])
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500720
721 def _invoke_dnf(self, dnf_args, fatal = True, print_output = True ):
722 os.environ['RPM_ETCCONFIGDIR'] = self.target_rootfs
723
724 dnf_cmd = bb.utils.which(os.getenv('PATH'), "dnf")
725 standard_dnf_args = (["-v", "--rpmverbosity=debug"] if self.d.getVar('ROOTFS_RPM_DEBUG') else []) + ["-y",
726 "-c", oe.path.join(self.target_rootfs, "etc/dnf/dnf.conf"),
727 "--setopt=reposdir=%s" %(oe.path.join(self.target_rootfs, "etc/yum.repos.d")),
728 "--repofrompath=oe-repo,%s" % (self.rpm_repo_dir),
729 "--installroot=%s" % (self.target_rootfs),
730 "--setopt=logdir=%s" % (self.d.getVar('T'))
731 ]
732 cmd = [dnf_cmd] + standard_dnf_args + dnf_args
733 try:
734 output = subprocess.check_output(cmd,stderr=subprocess.STDOUT).decode("utf-8")
735 if print_output:
736 bb.note(output)
737 return output
738 except subprocess.CalledProcessError as e:
739 if print_output:
740 (bb.note, bb.fatal)[fatal]("Could not invoke dnf. Command "
741 "'%s' returned %d:\n%s" % (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
742 else:
743 (bb.note, bb.fatal)[fatal]("Could not invoke dnf. Command "
744 "'%s' returned %d:" % (' '.join(cmd), e.returncode))
745 return e.output.decode("utf-8")
746
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500747 def dump_install_solution(self, pkgs):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500748 open(self.solution_manifest, 'w').write(" ".join(pkgs))
749 return pkgs
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500750
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751 def load_old_install_solution(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500752 if not os.path.exists(self.solution_manifest):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500753 return []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500754
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500755 return open(self.solution_manifest, 'r').read().split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500756
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500757 def _script_num_prefix(self, path):
758 files = os.listdir(path)
759 numbers = set()
760 numbers.add(99)
761 for f in files:
762 numbers.add(int(f.split("-")[0]))
763 return max(numbers) + 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500764
765 def save_rpmpostinst(self, pkg):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500766 bb.note("Saving postinstall script of %s" % (pkg))
767 cmd = bb.utils.which(os.getenv('PATH'), "rpm")
768 args = ["-q", "--root=%s" % self.target_rootfs, "--queryformat", "%{postin}", pkg]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500769
770 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500771 output = subprocess.check_output([cmd] + args,stderr=subprocess.STDOUT).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500772 except subprocess.CalledProcessError as e:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500773 bb.fatal("Could not invoke rpm. Command "
774 "'%s' returned %d:\n%s" % (' '.join([cmd] + args), e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500775
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500776 # may need to prepend #!/bin/sh to output
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500777
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500778 target_path = oe.path.join(self.target_rootfs, self.d.expand('${sysconfdir}/rpm-postinsts/'))
779 bb.utils.mkdirhier(target_path)
780 num = self._script_num_prefix(target_path)
781 saved_script_name = oe.path.join(target_path, "%d-%s" % (num, pkg))
782 open(saved_script_name, 'w').write(output)
783 os.chmod(saved_script_name, 0o755)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500784
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600785 def extract(self, pkg):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500786 output = self._invoke_dnf(["repoquery", "--queryformat", "%{location}", pkg])
787 pkg_name = output.splitlines()[-1]
788 if not pkg_name.endswith(".rpm"):
789 bb.fatal("dnf could not find package %s in repository: %s" %(pkg, output))
790 pkg_path = oe.path.join(self.rpm_repo_dir, pkg_name)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600791
792 cpio_cmd = bb.utils.which(os.getenv("PATH"), "cpio")
793 rpm2cpio_cmd = bb.utils.which(os.getenv("PATH"), "rpm2cpio")
794
795 if not os.path.isfile(pkg_path):
796 bb.fatal("Unable to extract package for '%s'."
797 "File %s doesn't exists" % (pkg, pkg_path))
798
799 tmp_dir = tempfile.mkdtemp()
800 current_dir = os.getcwd()
801 os.chdir(tmp_dir)
802
803 try:
804 cmd = "%s %s | %s -idmv" % (rpm2cpio_cmd, pkg_path, cpio_cmd)
805 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
806 except subprocess.CalledProcessError as e:
807 bb.utils.remove(tmp_dir, recurse=True)
808 bb.fatal("Unable to extract %s package. Command '%s' "
809 "returned %d:\n%s" % (pkg_path, cmd, e.returncode, e.output.decode("utf-8")))
810 except OSError as e:
811 bb.utils.remove(tmp_dir, recurse=True)
812 bb.fatal("Unable to extract %s package. Command '%s' "
813 "returned %d:\n%s at %s" % (pkg_path, cmd, e.errno, e.strerror, e.filename))
814
815 bb.note("Extracted %s to %s" % (pkg_path, tmp_dir))
816 os.chdir(current_dir)
817
818 return tmp_dir
819
820
821class OpkgDpkgPM(PackageManager):
822 """
823 This is an abstract class. Do not instantiate this directly.
824 """
825 def __init__(self, d):
826 super(OpkgDpkgPM, self).__init__(d)
827
828 """
829 Returns a dictionary with the package info.
830
831 This method extracts the common parts for Opkg and Dpkg
832 """
833 def package_info(self, pkg, cmd):
834
835 try:
836 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8")
837 except subprocess.CalledProcessError as e:
838 bb.fatal("Unable to list available packages. Command '%s' "
839 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
840 return opkg_query(output)
841
842 """
843 Returns the path to a tmpdir where resides the contents of a package.
844
845 Deleting the tmpdir is responsability of the caller.
846
847 This method extracts the common parts for Opkg and Dpkg
848 """
849 def extract(self, pkg, pkg_info):
850
851 ar_cmd = bb.utils.which(os.getenv("PATH"), "ar")
852 tar_cmd = bb.utils.which(os.getenv("PATH"), "tar")
853 pkg_path = pkg_info[pkg]["filepath"]
854
855 if not os.path.isfile(pkg_path):
856 bb.fatal("Unable to extract package for '%s'."
857 "File %s doesn't exists" % (pkg, pkg_path))
858
859 tmp_dir = tempfile.mkdtemp()
860 current_dir = os.getcwd()
861 os.chdir(tmp_dir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500862 if self.d.getVar('IMAGE_PKGTYPE') == 'deb':
863 data_tar = 'data.tar.xz'
864 else:
865 data_tar = 'data.tar.gz'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600866
867 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500868 cmd = [ar_cmd, 'x', pkg_path]
869 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
870 cmd = [tar_cmd, 'xf', data_tar]
871 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600872 except subprocess.CalledProcessError as e:
873 bb.utils.remove(tmp_dir, recurse=True)
874 bb.fatal("Unable to extract %s package. Command '%s' "
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500875 "returned %d:\n%s" % (pkg_path, ' '.join(cmd), e.returncode, e.output.decode("utf-8")))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600876 except OSError as e:
877 bb.utils.remove(tmp_dir, recurse=True)
878 bb.fatal("Unable to extract %s package. Command '%s' "
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500879 "returned %d:\n%s at %s" % (pkg_path, ' '.join(cmd), e.errno, e.strerror, e.filename))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600880
881 bb.note("Extracted %s to %s" % (pkg_path, tmp_dir))
882 bb.utils.remove(os.path.join(tmp_dir, "debian-binary"))
883 bb.utils.remove(os.path.join(tmp_dir, "control.tar.gz"))
884 os.chdir(current_dir)
885
886 return tmp_dir
887
888
889class OpkgPM(OpkgDpkgPM):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500890 def __init__(self, d, target_rootfs, config_file, archs, task_name='target'):
891 super(OpkgPM, self).__init__(d)
892
893 self.target_rootfs = target_rootfs
894 self.config_file = config_file
895 self.pkg_archs = archs
896 self.task_name = task_name
897
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500898 self.deploy_dir = self.d.getVar("DEPLOY_DIR_IPK")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500899 self.deploy_lock_file = os.path.join(self.deploy_dir, "deploy.lock")
900 self.opkg_cmd = bb.utils.which(os.getenv('PATH'), "opkg")
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600901 self.opkg_args = "--volatile-cache -f %s -t %s -o %s " % (self.config_file, self.d.expand('${T}/ipktemp/') ,target_rootfs)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500902 self.opkg_args += self.d.getVar("OPKG_ARGS")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500903
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500904 opkg_lib_dir = self.d.getVar('OPKGLIBDIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500905 if opkg_lib_dir[0] == "/":
906 opkg_lib_dir = opkg_lib_dir[1:]
907
908 self.opkg_dir = os.path.join(target_rootfs, opkg_lib_dir, "opkg")
909
910 bb.utils.mkdirhier(self.opkg_dir)
911
912 self.saved_opkg_dir = self.d.expand('${T}/saved/%s' % self.task_name)
913 if not os.path.exists(self.d.expand('${T}/saved')):
914 bb.utils.mkdirhier(self.d.expand('${T}/saved'))
915
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500916 self.from_feeds = (self.d.getVar('BUILD_IMAGES_FROM_FEEDS') or "") == "1"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500917 if self.from_feeds:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500918 self._create_custom_config()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500919 else:
920 self._create_config()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500921
922 self.indexer = OpkgIndexer(self.d, self.deploy_dir)
923
924 """
925 This function will change a package's status in /var/lib/opkg/status file.
926 If 'packages' is None then the new_status will be applied to all
927 packages
928 """
929 def mark_packages(self, status_tag, packages=None):
930 status_file = os.path.join(self.opkg_dir, "status")
931
932 with open(status_file, "r") as sf:
933 with open(status_file + ".tmp", "w+") as tmp_sf:
934 if packages is None:
935 tmp_sf.write(re.sub(r"Package: (.*?)\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)",
936 r"Package: \1\n\2Status: \3%s" % status_tag,
937 sf.read()))
938 else:
939 if type(packages).__name__ != "list":
940 raise TypeError("'packages' should be a list object")
941
942 status = sf.read()
943 for pkg in packages:
944 status = re.sub(r"Package: %s\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)" % pkg,
945 r"Package: %s\n\1Status: \2%s" % (pkg, status_tag),
946 status)
947
948 tmp_sf.write(status)
949
950 os.rename(status_file + ".tmp", status_file)
951
952 def _create_custom_config(self):
953 bb.note("Building from feeds activated!")
954
955 with open(self.config_file, "w+") as config_file:
956 priority = 1
957 for arch in self.pkg_archs.split():
958 config_file.write("arch %s %d\n" % (arch, priority))
959 priority += 5
960
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500961 for line in (self.d.getVar('IPK_FEED_URIS') or "").split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500962 feed_match = re.match("^[ \t]*(.*)##([^ \t]*)[ \t]*$", line)
963
964 if feed_match is not None:
965 feed_name = feed_match.group(1)
966 feed_uri = feed_match.group(2)
967
968 bb.note("Add %s feed with URL %s" % (feed_name, feed_uri))
969
970 config_file.write("src/gz %s %s\n" % (feed_name, feed_uri))
971
972 """
973 Allow to use package deploy directory contents as quick devel-testing
974 feed. This creates individual feed configs for each arch subdir of those
975 specified as compatible for the current machine.
976 NOTE: Development-helper feature, NOT a full-fledged feed.
977 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500978 if (self.d.getVar('FEED_DEPLOYDIR_BASE_URI') or "") != "":
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500979 for arch in self.pkg_archs.split():
980 cfg_file_name = os.path.join(self.target_rootfs,
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500981 self.d.getVar("sysconfdir"),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500982 "opkg",
983 "local-%s-feed.conf" % arch)
984
985 with open(cfg_file_name, "w+") as cfg_file:
986 cfg_file.write("src/gz local-%s %s/%s" %
987 (arch,
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500988 self.d.getVar('FEED_DEPLOYDIR_BASE_URI'),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500989 arch))
990
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500991 if self.d.getVar('OPKGLIBDIR') != '/var/lib':
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500992 # There is no command line option for this anymore, we need to add
993 # info_dir and status_file to config file, if OPKGLIBDIR doesn't have
994 # the default value of "/var/lib" as defined in opkg:
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500995 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_LISTS_DIR VARDIR "/lib/opkg/lists"
996 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_INFO_DIR VARDIR "/lib/opkg/info"
997 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_STATUS_FILE VARDIR "/lib/opkg/status"
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500998 cfg_file.write("option info_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'info'))
999 cfg_file.write("option lists_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'lists'))
1000 cfg_file.write("option status_file %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'status'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001001
1002
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001003 def _create_config(self):
1004 with open(self.config_file, "w+") as config_file:
1005 priority = 1
1006 for arch in self.pkg_archs.split():
1007 config_file.write("arch %s %d\n" % (arch, priority))
1008 priority += 5
1009
1010 config_file.write("src oe file:%s\n" % self.deploy_dir)
1011
1012 for arch in self.pkg_archs.split():
1013 pkgs_dir = os.path.join(self.deploy_dir, arch)
1014 if os.path.isdir(pkgs_dir):
1015 config_file.write("src oe-%s file:%s\n" %
1016 (arch, pkgs_dir))
1017
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001018 if self.d.getVar('OPKGLIBDIR') != '/var/lib':
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001019 # There is no command line option for this anymore, we need to add
1020 # info_dir and status_file to config file, if OPKGLIBDIR doesn't have
1021 # the default value of "/var/lib" as defined in opkg:
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001022 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_LISTS_DIR VARDIR "/lib/opkg/lists"
1023 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_INFO_DIR VARDIR "/lib/opkg/info"
1024 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_STATUS_FILE VARDIR "/lib/opkg/status"
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001025 config_file.write("option info_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'info'))
1026 config_file.write("option lists_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'lists'))
1027 config_file.write("option status_file %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'status'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001028
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001029 def insert_feeds_uris(self, feed_uris, feed_base_paths, feed_archs):
1030 if feed_uris == "":
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001031 return
1032
1033 rootfs_config = os.path.join('%s/etc/opkg/base-feeds.conf'
1034 % self.target_rootfs)
1035
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001036 feed_uris = self.construct_uris(feed_uris.split(), feed_base_paths.split())
1037 archs = self.pkg_archs.split() if feed_archs is None else feed_archs.split()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001038
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001039 with open(rootfs_config, "w+") as config_file:
1040 uri_iterator = 0
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001041 for uri in feed_uris:
1042 if archs:
1043 for arch in archs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001044 if (feed_archs is None) and (not os.path.exists(oe.path.join(self.deploy_dir, arch))):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001045 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001046 bb.note('Adding opkg feed url-%s-%d (%s)' %
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001047 (arch, uri_iterator, uri))
1048 config_file.write("src/gz uri-%s-%d %s/%s\n" %
1049 (arch, uri_iterator, uri, arch))
1050 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001051 bb.note('Adding opkg feed url-%d (%s)' %
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001052 (uri_iterator, uri))
1053 config_file.write("src/gz uri-%d %s\n" %
1054 (uri_iterator, uri))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001055
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001056 uri_iterator += 1
1057
1058 def update(self):
1059 self.deploy_dir_lock()
1060
1061 cmd = "%s %s update" % (self.opkg_cmd, self.opkg_args)
1062
1063 try:
1064 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
1065 except subprocess.CalledProcessError as e:
1066 self.deploy_dir_unlock()
1067 bb.fatal("Unable to update the package index files. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001068 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001069
1070 self.deploy_dir_unlock()
1071
1072 def install(self, pkgs, attempt_only=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001073 if not pkgs:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001074 return
1075
1076 cmd = "%s %s install %s" % (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
1077
1078 os.environ['D'] = self.target_rootfs
1079 os.environ['OFFLINE_ROOT'] = self.target_rootfs
1080 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
1081 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001082 os.environ['INTERCEPT_DIR'] = os.path.join(self.d.getVar('WORKDIR'),
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001083 "intercept_scripts")
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001084 os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001085
1086 try:
1087 bb.note("Installing the following packages: %s" % ' '.join(pkgs))
1088 bb.note(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001089 output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001090 bb.note(output)
1091 except subprocess.CalledProcessError as e:
Brad Bishop00111322018-04-01 22:23:53 -04001092 (bb.fatal, bb.warn)[attempt_only]("Unable to install packages. "
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001093 "Command '%s' returned %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001094 (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001095
1096 def remove(self, pkgs, with_dependencies=True):
1097 if with_dependencies:
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001098 cmd = "%s %s --force-remove --force-removal-of-dependent-packages remove %s" % \
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001099 (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
1100 else:
1101 cmd = "%s %s --force-depends remove %s" % \
1102 (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
1103
1104 try:
1105 bb.note(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001106 output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001107 bb.note(output)
1108 except subprocess.CalledProcessError as e:
1109 bb.fatal("Unable to remove packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001110 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001111
1112 def write_index(self):
1113 self.deploy_dir_lock()
1114
1115 result = self.indexer.write_index()
1116
1117 self.deploy_dir_unlock()
1118
1119 if result is not None:
1120 bb.fatal(result)
1121
1122 def remove_packaging_data(self):
1123 bb.utils.remove(self.opkg_dir, True)
1124 # create the directory back, it's needed by PM lock
1125 bb.utils.mkdirhier(self.opkg_dir)
1126
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001127 def remove_lists(self):
1128 if not self.from_feeds:
1129 bb.utils.remove(os.path.join(self.opkg_dir, "lists"), True)
1130
1131 def list_installed(self):
1132 return OpkgPkgsList(self.d, self.target_rootfs, self.config_file).list_pkgs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001133
1134 def handle_bad_recommendations(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001135 bad_recommendations = self.d.getVar("BAD_RECOMMENDATIONS") or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001136 if bad_recommendations.strip() == "":
1137 return
1138
1139 status_file = os.path.join(self.opkg_dir, "status")
1140
1141 # If status file existed, it means the bad recommendations has already
1142 # been handled
1143 if os.path.exists(status_file):
1144 return
1145
1146 cmd = "%s %s info " % (self.opkg_cmd, self.opkg_args)
1147
1148 with open(status_file, "w+") as status:
1149 for pkg in bad_recommendations.split():
1150 pkg_info = cmd + pkg
1151
1152 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001153 output = subprocess.check_output(pkg_info.split(), stderr=subprocess.STDOUT).strip().decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001154 except subprocess.CalledProcessError as e:
1155 bb.fatal("Cannot get package info. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001156 "returned %d:\n%s" % (pkg_info, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001157
1158 if output == "":
1159 bb.note("Ignored bad recommendation: '%s' is "
1160 "not a package" % pkg)
1161 continue
1162
1163 for line in output.split('\n'):
1164 if line.startswith("Status:"):
1165 status.write("Status: deinstall hold not-installed\n")
1166 else:
1167 status.write(line + "\n")
1168
1169 # Append a blank line after each package entry to ensure that it
1170 # is separated from the following entry
1171 status.write("\n")
1172
1173 '''
1174 The following function dummy installs pkgs and returns the log of output.
1175 '''
1176 def dummy_install(self, pkgs):
1177 if len(pkgs) == 0:
1178 return
1179
1180 # Create an temp dir as opkg root for dummy installation
1181 temp_rootfs = self.d.expand('${T}/opkg')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001182 opkg_lib_dir = self.d.getVar('OPKGLIBDIR')
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001183 if opkg_lib_dir[0] == "/":
1184 opkg_lib_dir = opkg_lib_dir[1:]
1185 temp_opkg_dir = os.path.join(temp_rootfs, opkg_lib_dir, 'opkg')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001186 bb.utils.mkdirhier(temp_opkg_dir)
1187
1188 opkg_args = "-f %s -o %s " % (self.config_file, temp_rootfs)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001189 opkg_args += self.d.getVar("OPKG_ARGS")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001190
1191 cmd = "%s %s update" % (self.opkg_cmd, opkg_args)
1192 try:
1193 subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1194 except subprocess.CalledProcessError as e:
1195 bb.fatal("Unable to update. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001196 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001197
1198 # Dummy installation
1199 cmd = "%s %s --noaction install %s " % (self.opkg_cmd,
1200 opkg_args,
1201 ' '.join(pkgs))
1202 try:
1203 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1204 except subprocess.CalledProcessError as e:
1205 bb.fatal("Unable to dummy install packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001206 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001207
1208 bb.utils.remove(temp_rootfs, True)
1209
1210 return output
1211
1212 def backup_packaging_data(self):
1213 # Save the opkglib for increment ipk image generation
1214 if os.path.exists(self.saved_opkg_dir):
1215 bb.utils.remove(self.saved_opkg_dir, True)
1216 shutil.copytree(self.opkg_dir,
1217 self.saved_opkg_dir,
1218 symlinks=True)
1219
1220 def recover_packaging_data(self):
1221 # Move the opkglib back
1222 if os.path.exists(self.saved_opkg_dir):
1223 if os.path.exists(self.opkg_dir):
1224 bb.utils.remove(self.opkg_dir, True)
1225
1226 bb.note('Recover packaging data')
1227 shutil.copytree(self.saved_opkg_dir,
1228 self.opkg_dir,
1229 symlinks=True)
1230
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001231 """
1232 Returns a dictionary with the package info.
1233 """
1234 def package_info(self, pkg):
1235 cmd = "%s %s info %s" % (self.opkg_cmd, self.opkg_args, pkg)
1236 pkg_info = super(OpkgPM, self).package_info(pkg, cmd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001237
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001238 pkg_arch = pkg_info[pkg]["arch"]
1239 pkg_filename = pkg_info[pkg]["filename"]
1240 pkg_info[pkg]["filepath"] = \
1241 os.path.join(self.deploy_dir, pkg_arch, pkg_filename)
1242
1243 return pkg_info
1244
1245 """
1246 Returns the path to a tmpdir where resides the contents of a package.
1247
1248 Deleting the tmpdir is responsability of the caller.
1249 """
1250 def extract(self, pkg):
1251 pkg_info = self.package_info(pkg)
1252 if not pkg_info:
1253 bb.fatal("Unable to get information for package '%s' while "
1254 "trying to extract the package." % pkg)
1255
1256 tmp_dir = super(OpkgPM, self).extract(pkg, pkg_info)
1257 bb.utils.remove(os.path.join(tmp_dir, "data.tar.gz"))
1258
1259 return tmp_dir
1260
1261class DpkgPM(OpkgDpkgPM):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001262 def __init__(self, d, target_rootfs, archs, base_archs, apt_conf_dir=None):
1263 super(DpkgPM, self).__init__(d)
1264 self.target_rootfs = target_rootfs
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001265 self.deploy_dir = self.d.getVar('DEPLOY_DIR_DEB')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001266 if apt_conf_dir is None:
1267 self.apt_conf_dir = self.d.expand("${APTCONF_TARGET}/apt")
1268 else:
1269 self.apt_conf_dir = apt_conf_dir
1270 self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf")
1271 self.apt_get_cmd = bb.utils.which(os.getenv('PATH'), "apt-get")
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001272 self.apt_cache_cmd = bb.utils.which(os.getenv('PATH'), "apt-cache")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001273
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001274 self.apt_args = d.getVar("APT_ARGS")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001275
1276 self.all_arch_list = archs.split()
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001277 all_mlb_pkg_arch_list = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS') or "").split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001278 self.all_arch_list.extend(arch for arch in all_mlb_pkg_arch_list if arch not in self.all_arch_list)
1279
1280 self._create_configs(archs, base_archs)
1281
1282 self.indexer = DpkgIndexer(self.d, self.deploy_dir)
1283
1284 """
1285 This function will change a package's status in /var/lib/dpkg/status file.
1286 If 'packages' is None then the new_status will be applied to all
1287 packages
1288 """
1289 def mark_packages(self, status_tag, packages=None):
1290 status_file = self.target_rootfs + "/var/lib/dpkg/status"
1291
1292 with open(status_file, "r") as sf:
1293 with open(status_file + ".tmp", "w+") as tmp_sf:
1294 if packages is None:
1295 tmp_sf.write(re.sub(r"Package: (.*?)\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)",
1296 r"Package: \1\n\2Status: \3%s" % status_tag,
1297 sf.read()))
1298 else:
1299 if type(packages).__name__ != "list":
1300 raise TypeError("'packages' should be a list object")
1301
1302 status = sf.read()
1303 for pkg in packages:
1304 status = re.sub(r"Package: %s\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)" % pkg,
1305 r"Package: %s\n\1Status: \2%s" % (pkg, status_tag),
1306 status)
1307
1308 tmp_sf.write(status)
1309
1310 os.rename(status_file + ".tmp", status_file)
1311
1312 """
1313 Run the pre/post installs for package "package_name". If package_name is
1314 None, then run all pre/post install scriptlets.
1315 """
1316 def run_pre_post_installs(self, package_name=None):
1317 info_dir = self.target_rootfs + "/var/lib/dpkg/info"
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001318 ControlScript = collections.namedtuple("ControlScript", ["suffix", "name", "argument"])
1319 control_scripts = [
1320 ControlScript(".preinst", "Preinstall", "install"),
1321 ControlScript(".postinst", "Postinstall", "configure")]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001322 status_file = self.target_rootfs + "/var/lib/dpkg/status"
1323 installed_pkgs = []
1324
1325 with open(status_file, "r") as status:
1326 for line in status.read().split('\n'):
1327 m = re.match("^Package: (.*)", line)
1328 if m is not None:
1329 installed_pkgs.append(m.group(1))
1330
1331 if package_name is not None and not package_name in installed_pkgs:
1332 return
1333
1334 os.environ['D'] = self.target_rootfs
1335 os.environ['OFFLINE_ROOT'] = self.target_rootfs
1336 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
1337 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001338 os.environ['INTERCEPT_DIR'] = os.path.join(self.d.getVar('WORKDIR'),
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001339 "intercept_scripts")
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001340 os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001341
1342 failed_pkgs = []
1343 for pkg_name in installed_pkgs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001344 for control_script in control_scripts:
1345 p_full = os.path.join(info_dir, pkg_name + control_script.suffix)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001346 if os.path.exists(p_full):
1347 try:
1348 bb.note("Executing %s for package: %s ..." %
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001349 (control_script.name.lower(), pkg_name))
1350 output = subprocess.check_output([p_full, control_script.argument],
1351 stderr=subprocess.STDOUT).decode("utf-8")
1352 bb.note(output)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001353 except subprocess.CalledProcessError as e:
1354 bb.note("%s for package %s failed with %d:\n%s" %
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001355 (control_script.name, pkg_name, e.returncode,
1356 e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001357 failed_pkgs.append(pkg_name)
1358 break
1359
1360 if len(failed_pkgs):
1361 self.mark_packages("unpacked", failed_pkgs)
1362
1363 def update(self):
1364 os.environ['APT_CONFIG'] = self.apt_conf_file
1365
1366 self.deploy_dir_lock()
1367
1368 cmd = "%s update" % self.apt_get_cmd
1369
1370 try:
1371 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
1372 except subprocess.CalledProcessError as e:
1373 bb.fatal("Unable to update the package index files. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001374 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001375
1376 self.deploy_dir_unlock()
1377
1378 def install(self, pkgs, attempt_only=False):
1379 if attempt_only and len(pkgs) == 0:
1380 return
1381
1382 os.environ['APT_CONFIG'] = self.apt_conf_file
1383
1384 cmd = "%s %s install --force-yes --allow-unauthenticated %s" % \
1385 (self.apt_get_cmd, self.apt_args, ' '.join(pkgs))
1386
1387 try:
1388 bb.note("Installing the following packages: %s" % ' '.join(pkgs))
1389 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
1390 except subprocess.CalledProcessError as e:
Brad Bishop00111322018-04-01 22:23:53 -04001391 (bb.fatal, bb.warn)[attempt_only]("Unable to install packages. "
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001392 "Command '%s' returned %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001393 (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001394
1395 # rename *.dpkg-new files/dirs
1396 for root, dirs, files in os.walk(self.target_rootfs):
1397 for dir in dirs:
1398 new_dir = re.sub("\.dpkg-new", "", dir)
1399 if dir != new_dir:
1400 os.rename(os.path.join(root, dir),
1401 os.path.join(root, new_dir))
1402
1403 for file in files:
1404 new_file = re.sub("\.dpkg-new", "", file)
1405 if file != new_file:
1406 os.rename(os.path.join(root, file),
1407 os.path.join(root, new_file))
1408
1409
1410 def remove(self, pkgs, with_dependencies=True):
1411 if with_dependencies:
1412 os.environ['APT_CONFIG'] = self.apt_conf_file
1413 cmd = "%s purge %s" % (self.apt_get_cmd, ' '.join(pkgs))
1414 else:
1415 cmd = "%s --admindir=%s/var/lib/dpkg --instdir=%s" \
1416 " -P --force-depends %s" % \
1417 (bb.utils.which(os.getenv('PATH'), "dpkg"),
1418 self.target_rootfs, self.target_rootfs, ' '.join(pkgs))
1419
1420 try:
1421 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
1422 except subprocess.CalledProcessError as e:
1423 bb.fatal("Unable to remove packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001424 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001425
1426 def write_index(self):
1427 self.deploy_dir_lock()
1428
1429 result = self.indexer.write_index()
1430
1431 self.deploy_dir_unlock()
1432
1433 if result is not None:
1434 bb.fatal(result)
1435
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001436 def insert_feeds_uris(self, feed_uris, feed_base_paths, feed_archs):
1437 if feed_uris == "":
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001438 return
1439
1440 sources_conf = os.path.join("%s/etc/apt/sources.list"
1441 % self.target_rootfs)
1442 arch_list = []
1443
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001444 if feed_archs is None:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001445 for arch in self.all_arch_list:
1446 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
1447 continue
1448 arch_list.append(arch)
1449 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001450 arch_list = feed_archs.split()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001451
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001452 feed_uris = self.construct_uris(feed_uris.split(), feed_base_paths.split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001453
1454 with open(sources_conf, "w+") as sources_file:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001455 for uri in feed_uris:
1456 if arch_list:
1457 for arch in arch_list:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001458 bb.note('Adding dpkg channel at (%s)' % uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001459 sources_file.write("deb %s/%s ./\n" %
1460 (uri, arch))
1461 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001462 bb.note('Adding dpkg channel at (%s)' % uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001463 sources_file.write("deb %s ./\n" % uri)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001464
1465 def _create_configs(self, archs, base_archs):
1466 base_archs = re.sub("_", "-", base_archs)
1467
1468 if os.path.exists(self.apt_conf_dir):
1469 bb.utils.remove(self.apt_conf_dir, True)
1470
1471 bb.utils.mkdirhier(self.apt_conf_dir)
1472 bb.utils.mkdirhier(self.apt_conf_dir + "/lists/partial/")
1473 bb.utils.mkdirhier(self.apt_conf_dir + "/apt.conf.d/")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001474 bb.utils.mkdirhier(self.apt_conf_dir + "/preferences.d/")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001475
1476 arch_list = []
1477 for arch in self.all_arch_list:
1478 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
1479 continue
1480 arch_list.append(arch)
1481
1482 with open(os.path.join(self.apt_conf_dir, "preferences"), "w+") as prefs_file:
1483 priority = 801
1484 for arch in arch_list:
1485 prefs_file.write(
1486 "Package: *\n"
1487 "Pin: release l=%s\n"
1488 "Pin-Priority: %d\n\n" % (arch, priority))
1489
1490 priority += 5
1491
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001492 pkg_exclude = self.d.getVar('PACKAGE_EXCLUDE') or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001493 for pkg in pkg_exclude.split():
1494 prefs_file.write(
1495 "Package: %s\n"
1496 "Pin: release *\n"
1497 "Pin-Priority: -1\n\n" % pkg)
1498
1499 arch_list.reverse()
1500
1501 with open(os.path.join(self.apt_conf_dir, "sources.list"), "w+") as sources_file:
1502 for arch in arch_list:
1503 sources_file.write("deb file:%s/ ./\n" %
1504 os.path.join(self.deploy_dir, arch))
1505
1506 base_arch_list = base_archs.split()
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001507 multilib_variants = self.d.getVar("MULTILIB_VARIANTS");
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001508 for variant in multilib_variants.split():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001509 localdata = bb.data.createCopy(self.d)
1510 variant_tune = localdata.getVar("DEFAULTTUNE_virtclass-multilib-" + variant, False)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001511 orig_arch = localdata.getVar("DPKG_ARCH")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001512 localdata.setVar("DEFAULTTUNE", variant_tune)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001513 variant_arch = localdata.getVar("DPKG_ARCH")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001514 if variant_arch not in base_arch_list:
1515 base_arch_list.append(variant_arch)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001516
1517 with open(self.apt_conf_file, "w+") as apt_conf:
1518 with open(self.d.expand("${STAGING_ETCDIR_NATIVE}/apt/apt.conf.sample")) as apt_conf_sample:
1519 for line in apt_conf_sample.read().split("\n"):
1520 match_arch = re.match(" Architecture \".*\";$", line)
1521 architectures = ""
1522 if match_arch:
1523 for base_arch in base_arch_list:
1524 architectures += "\"%s\";" % base_arch
1525 apt_conf.write(" Architectures {%s};\n" % architectures);
1526 apt_conf.write(" Architecture \"%s\";\n" % base_archs)
1527 else:
1528 line = re.sub("#ROOTFS#", self.target_rootfs, line)
1529 line = re.sub("#APTCONF#", self.apt_conf_dir, line)
1530 apt_conf.write(line + "\n")
1531
1532 target_dpkg_dir = "%s/var/lib/dpkg" % self.target_rootfs
1533 bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "info"))
1534
1535 bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "updates"))
1536
1537 if not os.path.exists(os.path.join(target_dpkg_dir, "status")):
1538 open(os.path.join(target_dpkg_dir, "status"), "w+").close()
1539 if not os.path.exists(os.path.join(target_dpkg_dir, "available")):
1540 open(os.path.join(target_dpkg_dir, "available"), "w+").close()
1541
1542 def remove_packaging_data(self):
1543 bb.utils.remove(os.path.join(self.target_rootfs,
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001544 self.d.getVar('opkglibdir')), True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001545 bb.utils.remove(self.target_rootfs + "/var/lib/dpkg/", True)
1546
1547 def fix_broken_dependencies(self):
1548 os.environ['APT_CONFIG'] = self.apt_conf_file
1549
1550 cmd = "%s %s -f install" % (self.apt_get_cmd, self.apt_args)
1551
1552 try:
1553 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
1554 except subprocess.CalledProcessError as e:
1555 bb.fatal("Cannot fix broken dependencies. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001556 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001557
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001558 def list_installed(self):
1559 return DpkgPkgsList(self.d, self.target_rootfs).list_pkgs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001560
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001561 """
1562 Returns a dictionary with the package info.
1563 """
1564 def package_info(self, pkg):
1565 cmd = "%s show %s" % (self.apt_cache_cmd, pkg)
1566 pkg_info = super(DpkgPM, self).package_info(pkg, cmd)
1567
1568 pkg_arch = pkg_info[pkg]["pkgarch"]
1569 pkg_filename = pkg_info[pkg]["filename"]
1570 pkg_info[pkg]["filepath"] = \
1571 os.path.join(self.deploy_dir, pkg_arch, pkg_filename)
1572
1573 return pkg_info
1574
1575 """
1576 Returns the path to a tmpdir where resides the contents of a package.
1577
1578 Deleting the tmpdir is responsability of the caller.
1579 """
1580 def extract(self, pkg):
1581 pkg_info = self.package_info(pkg)
1582 if not pkg_info:
1583 bb.fatal("Unable to get information for package '%s' while "
1584 "trying to extract the package." % pkg)
1585
1586 tmp_dir = super(DpkgPM, self).extract(pkg, pkg_info)
1587 bb.utils.remove(os.path.join(tmp_dir, "data.tar.xz"))
1588
1589 return tmp_dir
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001590
1591def generate_index_files(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001592 classes = d.getVar('PACKAGE_CLASSES').replace("package_", "").split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001593
1594 indexer_map = {
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001595 "rpm": (RpmIndexer, d.getVar('DEPLOY_DIR_RPM')),
1596 "ipk": (OpkgIndexer, d.getVar('DEPLOY_DIR_IPK')),
1597 "deb": (DpkgIndexer, d.getVar('DEPLOY_DIR_DEB'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001598 }
1599
1600 result = None
1601
1602 for pkg_class in classes:
1603 if not pkg_class in indexer_map:
1604 continue
1605
1606 if os.path.exists(indexer_map[pkg_class][1]):
1607 result = indexer_map[pkg_class][0](d, indexer_map[pkg_class][1]).write_index()
1608
1609 if result is not None:
1610 bb.fatal(result)
1611
1612if __name__ == "__main__":
1613 """
1614 We should be able to run this as a standalone script, from outside bitbake
1615 environment.
1616 """
1617 """
1618 TBD
1619 """