blob: 3cee9730a8d17b897241918232f4575815759bee [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
20 try:
21 bb.note("Executing '%s' ..." % index_cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060022 result = subprocess.check_output(index_cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050023 except subprocess.CalledProcessError as e:
24 return("Index creation command '%s' failed with return code %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -060025 (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050026
27 if result:
28 bb.note(result)
29
30 return None
31
Patrick Williamsc0f7c042017-02-23 20:41:17 -060032"""
33This method parse the output from the package managerand return
34a dictionary with the information of the packages. This is used
35when the packages are in deb or ipk format.
36"""
37def opkg_query(cmd_output):
38 verregex = re.compile(' \([=<>]* [^ )]*\)')
39 output = dict()
40 pkg = ""
41 arch = ""
42 ver = ""
43 filename = ""
44 dep = []
45 pkgarch = ""
46 for line in cmd_output.splitlines():
47 line = line.rstrip()
48 if ':' in line:
49 if line.startswith("Package: "):
50 pkg = line.split(": ")[1]
51 elif line.startswith("Architecture: "):
52 arch = line.split(": ")[1]
53 elif line.startswith("Version: "):
54 ver = line.split(": ")[1]
55 elif line.startswith("File: ") or line.startswith("Filename:"):
56 filename = line.split(": ")[1]
57 if "/" in filename:
58 filename = os.path.basename(filename)
59 elif line.startswith("Depends: "):
60 depends = verregex.sub('', line.split(": ")[1])
61 for depend in depends.split(", "):
62 dep.append(depend)
63 elif line.startswith("Recommends: "):
64 recommends = verregex.sub('', line.split(": ")[1])
65 for recommend in recommends.split(", "):
66 dep.append("%s [REC]" % recommend)
67 elif line.startswith("PackageArch: "):
68 pkgarch = line.split(": ")[1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -050069
Patrick Williamsc0f7c042017-02-23 20:41:17 -060070 # When there is a blank line save the package information
71 elif not line:
72 # IPK doesn't include the filename
73 if not filename:
74 filename = "%s_%s_%s.ipk" % (pkg, ver, arch)
75 if pkg:
76 output[pkg] = {"arch":arch, "ver":ver,
77 "filename":filename, "deps": dep, "pkgarch":pkgarch }
78 pkg = ""
79 arch = ""
80 ver = ""
81 filename = ""
82 dep = []
83 pkgarch = ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -050084
Patrick Williamsc0f7c042017-02-23 20:41:17 -060085 if pkg:
86 if not filename:
87 filename = "%s_%s_%s.ipk" % (pkg, ver, arch)
88 output[pkg] = {"arch":arch, "ver":ver,
89 "filename":filename, "deps": dep }
90
91 return output
92
93
94class Indexer(object, metaclass=ABCMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050095 def __init__(self, d, deploy_dir):
96 self.d = d
97 self.deploy_dir = deploy_dir
98
99 @abstractmethod
100 def write_index(self):
101 pass
102
103
104class RpmIndexer(Indexer):
105 def get_ml_prefix_and_os_list(self, arch_var=None, os_var=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600106 package_archs = collections.OrderedDict()
107 target_os = collections.OrderedDict()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500108
109 if arch_var is not None and os_var is not None:
110 package_archs['default'] = self.d.getVar(arch_var, True).split()
111 package_archs['default'].reverse()
112 target_os['default'] = self.d.getVar(os_var, True).strip()
113 else:
114 package_archs['default'] = self.d.getVar("PACKAGE_ARCHS", True).split()
115 # arch order is reversed. This ensures the -best- match is
116 # listed first!
117 package_archs['default'].reverse()
118 target_os['default'] = self.d.getVar("TARGET_OS", True).strip()
119 multilibs = self.d.getVar('MULTILIBS', True) or ""
120 for ext in multilibs.split():
121 eext = ext.split(':')
122 if len(eext) > 1 and eext[0] == 'multilib':
123 localdata = bb.data.createCopy(self.d)
124 default_tune_key = "DEFAULTTUNE_virtclass-multilib-" + eext[1]
125 default_tune = localdata.getVar(default_tune_key, False)
126 if default_tune is None:
127 default_tune_key = "DEFAULTTUNE_ML_" + eext[1]
128 default_tune = localdata.getVar(default_tune_key, False)
129 if default_tune:
130 localdata.setVar("DEFAULTTUNE", default_tune)
131 bb.data.update_data(localdata)
132 package_archs[eext[1]] = localdata.getVar('PACKAGE_ARCHS',
133 True).split()
134 package_archs[eext[1]].reverse()
135 target_os[eext[1]] = localdata.getVar("TARGET_OS",
136 True).strip()
137
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600138 ml_prefix_list = collections.OrderedDict()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500139 for mlib in package_archs:
140 if mlib == 'default':
141 ml_prefix_list[mlib] = package_archs[mlib]
142 else:
143 ml_prefix_list[mlib] = list()
144 for arch in package_archs[mlib]:
145 if arch in ['all', 'noarch', 'any']:
146 ml_prefix_list[mlib].append(arch)
147 else:
148 ml_prefix_list[mlib].append(mlib + "_" + arch)
149
150 return (ml_prefix_list, target_os)
151
152 def write_index(self):
153 sdk_pkg_archs = (self.d.getVar('SDK_PACKAGE_ARCHS', True) or "").replace('-', '_').split()
154 all_mlb_pkg_archs = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS', True) or "").replace('-', '_').split()
155
156 mlb_prefix_list = self.get_ml_prefix_and_os_list()[0]
157
158 archs = set()
159 for item in mlb_prefix_list:
160 archs = archs.union(set(i.replace('-', '_') for i in mlb_prefix_list[item]))
161
162 if len(archs) == 0:
163 archs = archs.union(set(all_mlb_pkg_archs))
164
165 archs = archs.union(set(sdk_pkg_archs))
166
167 rpm_createrepo = bb.utils.which(os.getenv('PATH'), "createrepo")
168 if self.d.getVar('PACKAGE_FEED_SIGN', True) == '1':
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500169 signer = get_signer(self.d, self.d.getVar('PACKAGE_FEED_GPG_BACKEND', True))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500171 signer = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500172 index_cmds = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500173 repomd_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500174 rpm_dirs_found = False
175 for arch in archs:
176 dbpath = os.path.join(self.d.getVar('WORKDIR', True), 'rpmdb', arch)
177 if os.path.exists(dbpath):
178 bb.utils.remove(dbpath, True)
179 arch_dir = os.path.join(self.deploy_dir, arch)
180 if not os.path.isdir(arch_dir):
181 continue
182
183 index_cmds.append("%s --dbpath %s --update -q %s" % \
184 (rpm_createrepo, dbpath, arch_dir))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500185 repomd_files.append(os.path.join(arch_dir, 'repodata', 'repomd.xml'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500186
187 rpm_dirs_found = True
188
189 if not rpm_dirs_found:
190 bb.note("There are no packages in %s" % self.deploy_dir)
191 return
192
193 # Create repodata
194 result = oe.utils.multiprocess_exec(index_cmds, create_index)
195 if result:
196 bb.fatal('%s' % ('\n'.join(result)))
197 # Sign repomd
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500198 if signer:
199 for repomd in repomd_files:
200 feed_sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE', True)
201 is_ascii_sig = (feed_sig_type.upper() != "BIN")
202 signer.detach_sign(repomd,
203 self.d.getVar('PACKAGE_FEED_GPG_NAME', True),
204 self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True),
205 armor=is_ascii_sig)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500206
207
208class OpkgIndexer(Indexer):
209 def write_index(self):
210 arch_vars = ["ALL_MULTILIB_PACKAGE_ARCHS",
211 "SDK_PACKAGE_ARCHS",
212 "MULTILIB_ARCHS"]
213
214 opkg_index_cmd = bb.utils.which(os.getenv('PATH'), "opkg-make-index")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500215 if self.d.getVar('PACKAGE_FEED_SIGN', True) == '1':
216 signer = get_signer(self.d, self.d.getVar('PACKAGE_FEED_GPG_BACKEND', True))
217 else:
218 signer = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500219
220 if not os.path.exists(os.path.join(self.deploy_dir, "Packages")):
221 open(os.path.join(self.deploy_dir, "Packages"), "w").close()
222
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500223 index_cmds = set()
224 index_sign_files = set()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225 for arch_var in arch_vars:
226 archs = self.d.getVar(arch_var, True)
227 if archs is None:
228 continue
229
230 for arch in archs.split():
231 pkgs_dir = os.path.join(self.deploy_dir, arch)
232 pkgs_file = os.path.join(pkgs_dir, "Packages")
233
234 if not os.path.isdir(pkgs_dir):
235 continue
236
237 if not os.path.exists(pkgs_file):
238 open(pkgs_file, "w").close()
239
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500240 index_cmds.add('%s -r %s -p %s -m %s' %
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500241 (opkg_index_cmd, pkgs_file, pkgs_file, pkgs_dir))
242
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500243 index_sign_files.add(pkgs_file)
244
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500245 if len(index_cmds) == 0:
246 bb.note("There are no packages in %s!" % self.deploy_dir)
247 return
248
249 result = oe.utils.multiprocess_exec(index_cmds, create_index)
250 if result:
251 bb.fatal('%s' % ('\n'.join(result)))
252
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500253 if signer:
254 feed_sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE', True)
255 is_ascii_sig = (feed_sig_type.upper() != "BIN")
256 for f in index_sign_files:
257 signer.detach_sign(f,
258 self.d.getVar('PACKAGE_FEED_GPG_NAME', True),
259 self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True),
260 armor=is_ascii_sig)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500261
262
263class DpkgIndexer(Indexer):
264 def _create_configs(self):
265 bb.utils.mkdirhier(self.apt_conf_dir)
266 bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "lists", "partial"))
267 bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "apt.conf.d"))
268 bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "preferences.d"))
269
270 with open(os.path.join(self.apt_conf_dir, "preferences"),
271 "w") as prefs_file:
272 pass
273 with open(os.path.join(self.apt_conf_dir, "sources.list"),
274 "w+") as sources_file:
275 pass
276
277 with open(self.apt_conf_file, "w") as apt_conf:
278 with open(os.path.join(self.d.expand("${STAGING_ETCDIR_NATIVE}"),
279 "apt", "apt.conf.sample")) as apt_conf_sample:
280 for line in apt_conf_sample.read().split("\n"):
281 line = re.sub("#ROOTFS#", "/dev/null", line)
282 line = re.sub("#APTCONF#", self.apt_conf_dir, line)
283 apt_conf.write(line + "\n")
284
285 def write_index(self):
286 self.apt_conf_dir = os.path.join(self.d.expand("${APTCONF_TARGET}"),
287 "apt-ftparchive")
288 self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf")
289 self._create_configs()
290
291 os.environ['APT_CONFIG'] = self.apt_conf_file
292
293 pkg_archs = self.d.getVar('PACKAGE_ARCHS', True)
294 if pkg_archs is not None:
295 arch_list = pkg_archs.split()
296 sdk_pkg_archs = self.d.getVar('SDK_PACKAGE_ARCHS', True)
297 if sdk_pkg_archs is not None:
298 for a in sdk_pkg_archs.split():
299 if a not in pkg_archs:
300 arch_list.append(a)
301
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500302 all_mlb_pkg_arch_list = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS', True) or "").split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500303 arch_list.extend(arch for arch in all_mlb_pkg_arch_list if arch not in arch_list)
304
305 apt_ftparchive = bb.utils.which(os.getenv('PATH'), "apt-ftparchive")
306 gzip = bb.utils.which(os.getenv('PATH'), "gzip")
307
308 index_cmds = []
309 deb_dirs_found = False
310 for arch in arch_list:
311 arch_dir = os.path.join(self.deploy_dir, arch)
312 if not os.path.isdir(arch_dir):
313 continue
314
315 cmd = "cd %s; PSEUDO_UNLOAD=1 %s packages . > Packages;" % (arch_dir, apt_ftparchive)
316
317 cmd += "%s -fc Packages > Packages.gz;" % gzip
318
319 with open(os.path.join(arch_dir, "Release"), "w+") as release:
320 release.write("Label: %s\n" % arch)
321
322 cmd += "PSEUDO_UNLOAD=1 %s release . >> Release" % apt_ftparchive
323
324 index_cmds.append(cmd)
325
326 deb_dirs_found = True
327
328 if not deb_dirs_found:
329 bb.note("There are no packages in %s" % self.deploy_dir)
330 return
331
332 result = oe.utils.multiprocess_exec(index_cmds, create_index)
333 if result:
334 bb.fatal('%s' % ('\n'.join(result)))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500335 if self.d.getVar('PACKAGE_FEED_SIGN', True) == '1':
336 raise NotImplementedError('Package feed signing not implementd for dpkg')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500337
338
339
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600340class PkgsList(object, metaclass=ABCMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341 def __init__(self, d, rootfs_dir):
342 self.d = d
343 self.rootfs_dir = rootfs_dir
344
345 @abstractmethod
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500346 def list_pkgs(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500347 pass
348
349
350class RpmPkgsList(PkgsList):
351 def __init__(self, d, rootfs_dir, arch_var=None, os_var=None):
352 super(RpmPkgsList, self).__init__(d, rootfs_dir)
353
354 self.rpm_cmd = bb.utils.which(os.getenv('PATH'), "rpm")
355 self.image_rpmlib = os.path.join(self.rootfs_dir, 'var/lib/rpm')
356
357 self.ml_prefix_list, self.ml_os_list = \
358 RpmIndexer(d, rootfs_dir).get_ml_prefix_and_os_list(arch_var, os_var)
359
360 # Determine rpm version
361 cmd = "%s --version" % self.rpm_cmd
362 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600363 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500364 except subprocess.CalledProcessError as e:
365 bb.fatal("Getting rpm version failed. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600366 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500367
368 '''
369 Translate the RPM/Smart format names to the OE multilib format names
370 '''
371 def _pkg_translate_smart_to_oe(self, pkg, arch):
372 new_pkg = pkg
373 new_arch = arch
374 fixed_arch = arch.replace('_', '-')
375 found = 0
376 for mlib in self.ml_prefix_list:
377 for cmp_arch in self.ml_prefix_list[mlib]:
378 fixed_cmp_arch = cmp_arch.replace('_', '-')
379 if fixed_arch == fixed_cmp_arch:
380 if mlib == 'default':
381 new_pkg = pkg
382 new_arch = cmp_arch
383 else:
384 new_pkg = mlib + '-' + pkg
385 # We need to strip off the ${mlib}_ prefix on the arch
386 new_arch = cmp_arch.replace(mlib + '_', '')
387
388 # Workaround for bug 3565. Simply look to see if we
389 # know of a package with that name, if not try again!
390 filename = os.path.join(self.d.getVar('PKGDATA_DIR', True),
391 'runtime-reverse',
392 new_pkg)
393 if os.path.exists(filename):
394 found = 1
395 break
396
397 if found == 1 and fixed_arch == fixed_cmp_arch:
398 break
399 #bb.note('%s, %s -> %s, %s' % (pkg, arch, new_pkg, new_arch))
400 return new_pkg, new_arch
401
402 def _list_pkg_deps(self):
403 cmd = [bb.utils.which(os.getenv('PATH'), "rpmresolve"),
404 "-t", self.image_rpmlib]
405
406 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600407 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip().decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500408 except subprocess.CalledProcessError as e:
409 bb.fatal("Cannot get the package dependencies. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600410 "returned %d:\n%s" % (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500411
412 return output
413
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500414 def list_pkgs(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500415 cmd = self.rpm_cmd + ' --root ' + self.rootfs_dir
416 cmd += ' -D "_dbpath /var/lib/rpm" -qa'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500417 cmd += " --qf '[%{NAME} %{ARCH} %{VERSION} %{PACKAGEORIGIN}\n]'"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500418
419 try:
420 # bb.note(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600421 tmp_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).strip().decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500422 except subprocess.CalledProcessError as e:
423 bb.fatal("Cannot get the installed packages list. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600424 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500426 output = dict()
427 deps = dict()
428 dependencies = self._list_pkg_deps()
429
430 # Populate deps dictionary for better manipulation
431 for line in dependencies.splitlines():
432 try:
433 pkg, dep = line.split("|")
434 if not pkg in deps:
435 deps[pkg] = list()
436 if not dep in deps[pkg]:
437 deps[pkg].append(dep)
438 except:
439 # Ignore any other lines they're debug or errors
440 pass
441
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500442 for line in tmp_output.split('\n'):
443 if len(line.strip()) == 0:
444 continue
445 pkg = line.split()[0]
446 arch = line.split()[1]
447 ver = line.split()[2]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500448 dep = deps.get(pkg, [])
449
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500450 # Skip GPG keys
451 if pkg == 'gpg-pubkey':
452 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500453
454 pkgorigin = line.split()[3]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500455 new_pkg, new_arch = self._pkg_translate_smart_to_oe(pkg, arch)
456
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500457 output[new_pkg] = {"arch":new_arch, "ver":ver,
458 "filename":pkgorigin, "deps":dep}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500459
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500460 return output
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500461
462
463class OpkgPkgsList(PkgsList):
464 def __init__(self, d, rootfs_dir, config_file):
465 super(OpkgPkgsList, self).__init__(d, rootfs_dir)
466
467 self.opkg_cmd = bb.utils.which(os.getenv('PATH'), "opkg")
468 self.opkg_args = "-f %s -o %s " % (config_file, rootfs_dir)
469 self.opkg_args += self.d.getVar("OPKG_ARGS", True)
470
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500471 def list_pkgs(self, format=None):
472 cmd = "%s %s status" % (self.opkg_cmd, self.opkg_args)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500473
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500474 # opkg returns success even when it printed some
475 # "Collected errors:" report to stderr. Mixing stderr into
476 # stdout then leads to random failures later on when
477 # parsing the output. To avoid this we need to collect both
478 # output streams separately and check for empty stderr.
479 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
480 cmd_output, cmd_stderr = p.communicate()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600481 cmd_output = cmd_output.decode("utf-8")
482 cmd_stderr = cmd_stderr.decode("utf-8")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500483 if p.returncode or cmd_stderr:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500484 bb.fatal("Cannot get the installed packages list. Command '%s' "
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500485 "returned %d and stderr:\n%s" % (cmd, p.returncode, cmd_stderr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500486
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600487 return opkg_query(cmd_output)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500488
489
490class DpkgPkgsList(PkgsList):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500491
492 def list_pkgs(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500493 cmd = [bb.utils.which(os.getenv('PATH'), "dpkg-query"),
494 "--admindir=%s/var/lib/dpkg" % self.rootfs_dir,
495 "-W"]
496
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500497 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 -0500498
499 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600500 cmd_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip().decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500501 except subprocess.CalledProcessError as e:
502 bb.fatal("Cannot get the installed packages list. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600503 "returned %d:\n%s" % (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500504
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600505 return opkg_query(cmd_output)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500506
507
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600508class PackageManager(object, metaclass=ABCMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500509 """
510 This is an abstract class. Do not instantiate this directly.
511 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500512
513 def __init__(self, d):
514 self.d = d
515 self.deploy_dir = None
516 self.deploy_lock = None
517 self.feed_uris = self.d.getVar('PACKAGE_FEED_URIS', True) or ""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500518 self.feed_base_paths = self.d.getVar('PACKAGE_FEED_BASE_PATHS', True) or ""
519 self.feed_archs = self.d.getVar('PACKAGE_FEED_ARCHS', True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520
521 """
522 Update the package manager package database.
523 """
524 @abstractmethod
525 def update(self):
526 pass
527
528 """
529 Install a list of packages. 'pkgs' is a list object. If 'attempt_only' is
530 True, installation failures are ignored.
531 """
532 @abstractmethod
533 def install(self, pkgs, attempt_only=False):
534 pass
535
536 """
537 Remove a list of packages. 'pkgs' is a list object. If 'with_dependencies'
538 is False, the any dependencies are left in place.
539 """
540 @abstractmethod
541 def remove(self, pkgs, with_dependencies=True):
542 pass
543
544 """
545 This function creates the index files
546 """
547 @abstractmethod
548 def write_index(self):
549 pass
550
551 @abstractmethod
552 def remove_packaging_data(self):
553 pass
554
555 @abstractmethod
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500556 def list_installed(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500557 pass
558
559 @abstractmethod
560 def insert_feeds_uris(self):
561 pass
562
563 """
564 Install complementary packages based upon the list of currently installed
565 packages e.g. locales, *-dev, *-dbg, etc. This will only attempt to install
566 these packages, if they don't exist then no error will occur. Note: every
567 backend needs to call this function explicitly after the normal package
568 installation
569 """
570 def install_complementary(self, globs=None):
571 # we need to write the list of installed packages to a file because the
572 # oe-pkgdata-util reads it from a file
573 installed_pkgs_file = os.path.join(self.d.getVar('WORKDIR', True),
574 "installed_pkgs.txt")
575 with open(installed_pkgs_file, "w+") as installed_pkgs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500576 pkgs = self.list_installed()
577 output = oe.utils.format_pkg_list(pkgs, "arch")
578 installed_pkgs.write(output)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500579
580 if globs is None:
581 globs = self.d.getVar('IMAGE_INSTALL_COMPLEMENTARY', True)
582 split_linguas = set()
583
584 for translation in self.d.getVar('IMAGE_LINGUAS', True).split():
585 split_linguas.add(translation)
586 split_linguas.add(translation.split('-')[0])
587
588 split_linguas = sorted(split_linguas)
589
590 for lang in split_linguas:
591 globs += " *-locale-%s" % lang
592
593 if globs is None:
594 return
595
596 cmd = [bb.utils.which(os.getenv('PATH'), "oe-pkgdata-util"),
597 "-p", self.d.getVar('PKGDATA_DIR', True), "glob", installed_pkgs_file,
598 globs]
599 exclude = self.d.getVar('PACKAGE_EXCLUDE_COMPLEMENTARY', True)
600 if exclude:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600601 cmd.extend(['--exclude=' + '|'.join(exclude.split())])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500602 try:
603 bb.note("Installing complementary packages ...")
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500604 bb.note('Running %s' % cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600605 complementary_pkgs = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500606 except subprocess.CalledProcessError as e:
607 bb.fatal("Could not compute complementary packages list. Command "
608 "'%s' returned %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600609 (' '.join(cmd), e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500610 self.install(complementary_pkgs.split(), attempt_only=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500611 os.remove(installed_pkgs_file)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500612
613 def deploy_dir_lock(self):
614 if self.deploy_dir is None:
615 raise RuntimeError("deploy_dir is not set!")
616
617 lock_file_name = os.path.join(self.deploy_dir, "deploy.lock")
618
619 self.deploy_lock = bb.utils.lockfile(lock_file_name)
620
621 def deploy_dir_unlock(self):
622 if self.deploy_lock is None:
623 return
624
625 bb.utils.unlockfile(self.deploy_lock)
626
627 self.deploy_lock = None
628
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500629 """
630 Construct URIs based on the following pattern: uri/base_path where 'uri'
631 and 'base_path' correspond to each element of the corresponding array
632 argument leading to len(uris) x len(base_paths) elements on the returned
633 array
634 """
635 def construct_uris(self, uris, base_paths):
636 def _append(arr1, arr2, sep='/'):
637 res = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600638 narr1 = [a.rstrip(sep) for a in arr1]
639 narr2 = [a.rstrip(sep).lstrip(sep) for a in arr2]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500640 for a1 in narr1:
641 if arr2:
642 for a2 in narr2:
643 res.append("%s%s%s" % (a1, sep, a2))
644 else:
645 res.append(a1)
646 return res
647 return _append(uris, base_paths)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500648
649class RpmPM(PackageManager):
650 def __init__(self,
651 d,
652 target_rootfs,
653 target_vendor,
654 task_name='target',
655 providename=None,
656 arch_var=None,
657 os_var=None):
658 super(RpmPM, self).__init__(d)
659 self.target_rootfs = target_rootfs
660 self.target_vendor = target_vendor
661 self.task_name = task_name
662 self.providename = providename
663 self.fullpkglist = list()
664 self.deploy_dir = self.d.getVar('DEPLOY_DIR_RPM', True)
665 self.etcrpm_dir = os.path.join(self.target_rootfs, "etc/rpm")
666 self.install_dir_name = "oe_install"
667 self.install_dir_path = os.path.join(self.target_rootfs, self.install_dir_name)
668 self.rpm_cmd = bb.utils.which(os.getenv('PATH'), "rpm")
669 self.smart_cmd = bb.utils.which(os.getenv('PATH'), "smart")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500670 # 0 = default, only warnings
671 # 1 = --log-level=info (includes information about executing scriptlets and their output)
672 # 2 = --log-level=debug
673 # 3 = --log-level=debug plus dumps of scriplet content and command invocation
674 self.debug_level = int(d.getVar('ROOTFS_RPM_DEBUG', True) or "0")
675 self.smart_opt = "--log-level=%s --data-dir=%s" % \
676 ("warning" if self.debug_level == 0 else
677 "info" if self.debug_level == 1 else
678 "debug",
679 os.path.join(target_rootfs, 'var/lib/smart'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500680 self.scriptlet_wrapper = self.d.expand('${WORKDIR}/scriptlet_wrapper')
681 self.solution_manifest = self.d.expand('${T}/saved/%s_solution' %
682 self.task_name)
683 self.saved_rpmlib = self.d.expand('${T}/saved/%s' % self.task_name)
684 self.image_rpmlib = os.path.join(self.target_rootfs, 'var/lib/rpm')
685
686 if not os.path.exists(self.d.expand('${T}/saved')):
687 bb.utils.mkdirhier(self.d.expand('${T}/saved'))
688
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600689 packageindex_dir = os.path.join(self.d.getVar('WORKDIR', True), 'rpms')
690 self.indexer = RpmIndexer(self.d, packageindex_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500691 self.pkgs_list = RpmPkgsList(self.d, self.target_rootfs, arch_var, os_var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500692
693 self.ml_prefix_list, self.ml_os_list = self.indexer.get_ml_prefix_and_os_list(arch_var, os_var)
694
695 def insert_feeds_uris(self):
696 if self.feed_uris == "":
697 return
698
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500699 arch_list = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500700 if self.feed_archs is not None:
701 # User define feed architectures
702 arch_list = self.feed_archs.split()
703 else:
704 # List must be prefered to least preferred order
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600705 default_platform_extra = list()
706 platform_extra = list()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500707 bbextendvariant = self.d.getVar('BBEXTENDVARIANT', True) or ""
708 for mlib in self.ml_os_list:
709 for arch in self.ml_prefix_list[mlib]:
710 plt = arch.replace('-', '_') + '-.*-' + self.ml_os_list[mlib]
711 if mlib == bbextendvariant:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600712 if plt not in default_platform_extra:
713 default_platform_extra.append(plt)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500714 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600715 if plt not in platform_extra:
716 platform_extra.append(plt)
717 platform_extra = default_platform_extra + platform_extra
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500718
719 for canonical_arch in platform_extra:
720 arch = canonical_arch.split('-')[0]
721 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
722 continue
723 arch_list.append(arch)
724
725 feed_uris = self.construct_uris(self.feed_uris.split(), self.feed_base_paths.split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500726
727 uri_iterator = 0
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500728 channel_priority = 10 + 5 * len(feed_uris) * (len(arch_list) if arch_list else 1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500729
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500730 for uri in feed_uris:
731 if arch_list:
732 for arch in arch_list:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600733 bb.note('Adding Smart channel url%d%s (%s)' %
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500734 (uri_iterator, arch, channel_priority))
735 self._invoke_smart('channel --add url%d-%s type=rpm-md baseurl=%s/%s -y'
736 % (uri_iterator, arch, uri, arch))
737 self._invoke_smart('channel --set url%d-%s priority=%d' %
738 (uri_iterator, arch, channel_priority))
739 channel_priority -= 5
740 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600741 bb.note('Adding Smart channel url%d (%s)' %
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500742 (uri_iterator, channel_priority))
743 self._invoke_smart('channel --add url%d type=rpm-md baseurl=%s -y'
744 % (uri_iterator, uri))
745 self._invoke_smart('channel --set url%d priority=%d' %
746 (uri_iterator, channel_priority))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500747 channel_priority -= 5
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500748
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500749 uri_iterator += 1
750
751 '''
752 Create configs for rpm and smart, and multilib is supported
753 '''
754 def create_configs(self):
755 target_arch = self.d.getVar('TARGET_ARCH', True)
756 platform = '%s%s-%s' % (target_arch.replace('-', '_'),
757 self.target_vendor,
758 self.ml_os_list['default'])
759
760 # List must be prefered to least preferred order
761 default_platform_extra = list()
762 platform_extra = list()
763 bbextendvariant = self.d.getVar('BBEXTENDVARIANT', True) or ""
764 for mlib in self.ml_os_list:
765 for arch in self.ml_prefix_list[mlib]:
766 plt = arch.replace('-', '_') + '-.*-' + self.ml_os_list[mlib]
767 if mlib == bbextendvariant:
768 if plt not in default_platform_extra:
769 default_platform_extra.append(plt)
770 else:
771 if plt not in platform_extra:
772 platform_extra.append(plt)
773 platform_extra = default_platform_extra + platform_extra
774
775 self._create_configs(platform, platform_extra)
776
777 def _invoke_smart(self, args):
778 cmd = "%s %s %s" % (self.smart_cmd, self.smart_opt, args)
779 # bb.note(cmd)
780 try:
781 complementary_pkgs = subprocess.check_output(cmd,
782 stderr=subprocess.STDOUT,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600783 shell=True).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500784 # bb.note(complementary_pkgs)
785 return complementary_pkgs
786 except subprocess.CalledProcessError as e:
787 bb.fatal("Could not invoke smart. Command "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600788 "'%s' returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500789
790 def _search_pkg_name_in_feeds(self, pkg, feed_archs):
791 for arch in feed_archs:
792 arch = arch.replace('-', '_')
793 regex_match = re.compile(r"^%s-[^-]*-[^-]*@%s$" % \
794 (re.escape(pkg), re.escape(arch)))
795 for p in self.fullpkglist:
796 if regex_match.match(p) is not None:
797 # First found is best match
798 # bb.note('%s -> %s' % (pkg, pkg + '@' + arch))
799 return pkg + '@' + arch
800
801 # Search provides if not found by pkgname.
802 bb.note('Not found %s by name, searching provides ...' % pkg)
803 cmd = "%s %s query --provides %s --show-format='$name-$version'" % \
804 (self.smart_cmd, self.smart_opt, pkg)
805 cmd += " | sed -ne 's/ *Provides://p'"
806 bb.note('cmd: %s' % cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600807 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500808 # Found a provider
809 if output:
810 bb.note('Found providers for %s: %s' % (pkg, output))
811 for p in output.split():
812 for arch in feed_archs:
813 arch = arch.replace('-', '_')
814 if p.rstrip().endswith('@' + arch):
815 return p
816
817 return ""
818
819 '''
820 Translate the OE multilib format names to the RPM/Smart format names
821 It searched the RPM/Smart format names in probable multilib feeds first,
822 and then searched the default base feed.
823 '''
824 def _pkg_translate_oe_to_smart(self, pkgs, attempt_only=False):
825 new_pkgs = list()
826
827 for pkg in pkgs:
828 new_pkg = pkg
829 # Search new_pkg in probable multilibs first
830 for mlib in self.ml_prefix_list:
831 # Jump the default archs
832 if mlib == 'default':
833 continue
834
835 subst = pkg.replace(mlib + '-', '')
836 # if the pkg in this multilib feed
837 if subst != pkg:
838 feed_archs = self.ml_prefix_list[mlib]
839 new_pkg = self._search_pkg_name_in_feeds(subst, feed_archs)
840 if not new_pkg:
841 # Failed to translate, package not found!
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600842 err_msg = '%s not found in the %s feeds (%s) in %s.' % \
843 (pkg, mlib, " ".join(feed_archs), self.d.getVar('DEPLOY_DIR_RPM', True))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500844 if not attempt_only:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600845 bb.error(err_msg)
846 bb.fatal("This is often caused by an empty package declared " \
847 "in a recipe's PACKAGES variable. (Empty packages are " \
848 "not constructed unless ALLOW_EMPTY_<pkg> = '1' is used.)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500849 bb.warn(err_msg)
850 else:
851 new_pkgs.append(new_pkg)
852
853 break
854
855 # Apparently not a multilib package...
856 if pkg == new_pkg:
857 # Search new_pkg in default archs
858 default_archs = self.ml_prefix_list['default']
859 new_pkg = self._search_pkg_name_in_feeds(pkg, default_archs)
860 if not new_pkg:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600861 err_msg = '%s not found in the feeds (%s) in %s.' % \
862 (pkg, " ".join(default_archs), self.d.getVar('DEPLOY_DIR_RPM', True))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500863 if not attempt_only:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600864 bb.error(err_msg)
865 bb.fatal("This is often caused by an empty package declared " \
866 "in a recipe's PACKAGES variable. (Empty packages are " \
867 "not constructed unless ALLOW_EMPTY_<pkg> = '1' is used.)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500868 bb.warn(err_msg)
869 else:
870 new_pkgs.append(new_pkg)
871
872 return new_pkgs
873
874 def _create_configs(self, platform, platform_extra):
875 # Setup base system configuration
876 bb.note("configuring RPM platform settings")
877
878 # Configure internal RPM environment when using Smart
879 os.environ['RPM_ETCRPM'] = self.etcrpm_dir
880 bb.utils.mkdirhier(self.etcrpm_dir)
881
882 # Setup temporary directory -- install...
883 if os.path.exists(self.install_dir_path):
884 bb.utils.remove(self.install_dir_path, True)
885 bb.utils.mkdirhier(os.path.join(self.install_dir_path, 'tmp'))
886
887 channel_priority = 5
888 platform_dir = os.path.join(self.etcrpm_dir, "platform")
889 sdkos = self.d.getVar("SDK_OS", True)
890 with open(platform_dir, "w+") as platform_fd:
891 platform_fd.write(platform + '\n')
892 for pt in platform_extra:
893 channel_priority += 5
894 if sdkos:
895 tmp = re.sub("-%s$" % sdkos, "-%s\n" % sdkos, pt)
896 tmp = re.sub("-linux.*$", "-linux.*\n", tmp)
897 platform_fd.write(tmp)
898
899 # Tell RPM that the "/" directory exist and is available
900 bb.note("configuring RPM system provides")
901 sysinfo_dir = os.path.join(self.etcrpm_dir, "sysinfo")
902 bb.utils.mkdirhier(sysinfo_dir)
903 with open(os.path.join(sysinfo_dir, "Dirnames"), "w+") as dirnames:
904 dirnames.write("/\n")
905
906 if self.providename:
907 providename_dir = os.path.join(sysinfo_dir, "Providename")
908 if not os.path.exists(providename_dir):
909 providename_content = '\n'.join(self.providename)
910 providename_content += '\n'
911 open(providename_dir, "w+").write(providename_content)
912
913 # Configure RPM... we enforce these settings!
914 bb.note("configuring RPM DB settings")
915 # After change the __db.* cache size, log file will not be
916 # generated automatically, that will raise some warnings,
917 # so touch a bare log for rpm write into it.
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500918 rpmlib_log = os.path.join(self.image_rpmlib, 'log', 'log.0000000001')
919 if not os.path.exists(rpmlib_log):
920 bb.utils.mkdirhier(os.path.join(self.image_rpmlib, 'log'))
921 open(rpmlib_log, 'w+').close()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500922
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500923 DB_CONFIG_CONTENT = "# ================ Environment\n" \
924 "set_data_dir .\n" \
925 "set_create_dir .\n" \
926 "set_lg_dir ./log\n" \
927 "set_tmp_dir ./tmp\n" \
928 "set_flags db_log_autoremove on\n" \
929 "\n" \
930 "# -- thread_count must be >= 8\n" \
931 "set_thread_count 64\n" \
932 "\n" \
933 "# ================ Logging\n" \
934 "\n" \
935 "# ================ Memory Pool\n" \
936 "set_cachesize 0 1048576 0\n" \
937 "set_mp_mmapsize 268435456\n" \
938 "\n" \
939 "# ================ Locking\n" \
940 "set_lk_max_locks 16384\n" \
941 "set_lk_max_lockers 16384\n" \
942 "set_lk_max_objects 16384\n" \
943 "mutex_set_max 163840\n" \
944 "\n" \
945 "# ================ Replication\n"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500946
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500947 db_config_dir = os.path.join(self.image_rpmlib, 'DB_CONFIG')
948 if not os.path.exists(db_config_dir):
949 open(db_config_dir, 'w+').write(DB_CONFIG_CONTENT)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500950
951 # Create database so that smart doesn't complain (lazy init)
952 opt = "-qa"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500953 cmd = "%s --root %s --dbpath /var/lib/rpm %s > /dev/null" % (
954 self.rpm_cmd, self.target_rootfs, opt)
955 try:
956 subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
957 except subprocess.CalledProcessError as e:
958 bb.fatal("Create rpm database failed. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600959 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500960 # Import GPG key to RPM database of the target system
961 if self.d.getVar('RPM_SIGN_PACKAGES', True) == '1':
962 pubkey_path = self.d.getVar('RPM_GPG_PUBKEY', True)
963 cmd = "%s --root %s --dbpath /var/lib/rpm --import %s > /dev/null" % (
964 self.rpm_cmd, self.target_rootfs, pubkey_path)
965 subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
966
967 # Configure smart
968 bb.note("configuring Smart settings")
969 bb.utils.remove(os.path.join(self.target_rootfs, 'var/lib/smart'),
970 True)
971 self._invoke_smart('config --set rpm-root=%s' % self.target_rootfs)
972 self._invoke_smart('config --set rpm-dbpath=/var/lib/rpm')
973 self._invoke_smart('config --set rpm-extra-macros._var=%s' %
974 self.d.getVar('localstatedir', True))
975 cmd = "config --set rpm-extra-macros._tmppath=/%s/tmp" % (self.install_dir_name)
976
977 prefer_color = self.d.getVar('RPM_PREFER_ELF_ARCH', True)
978 if prefer_color:
979 if prefer_color not in ['0', '1', '2', '4']:
980 bb.fatal("Invalid RPM_PREFER_ELF_ARCH: %s, it should be one of:\n"
981 "\t1: ELF32 wins\n"
982 "\t2: ELF64 wins\n"
983 "\t4: ELF64 N32 wins (mips64 or mips64el only)" %
984 prefer_color)
985 if prefer_color == "4" and self.d.getVar("TUNE_ARCH", True) not in \
986 ['mips64', 'mips64el']:
987 bb.fatal("RPM_PREFER_ELF_ARCH = \"4\" is for mips64 or mips64el "
988 "only.")
989 self._invoke_smart('config --set rpm-extra-macros._prefer_color=%s'
990 % prefer_color)
991
992 self._invoke_smart(cmd)
993 self._invoke_smart('config --set rpm-ignoresize=1')
994
995 # Write common configuration for host and target usage
996 self._invoke_smart('config --set rpm-nolinktos=1')
997 self._invoke_smart('config --set rpm-noparentdirs=1')
998 check_signature = self.d.getVar('RPM_CHECK_SIGNATURES', True)
999 if check_signature and check_signature.strip() == "0":
1000 self._invoke_smart('config --set rpm-check-signatures=false')
1001 for i in self.d.getVar('BAD_RECOMMENDATIONS', True).split():
1002 self._invoke_smart('flag --set ignore-recommends %s' % i)
1003
1004 # Do the following configurations here, to avoid them being
1005 # saved for field upgrade
1006 if self.d.getVar('NO_RECOMMENDATIONS', True).strip() == "1":
1007 self._invoke_smart('config --set ignore-all-recommends=1')
1008 pkg_exclude = self.d.getVar('PACKAGE_EXCLUDE', True) or ""
1009 for i in pkg_exclude.split():
1010 self._invoke_smart('flag --set exclude-packages %s' % i)
1011
1012 # Optional debugging
1013 # self._invoke_smart('config --set rpm-log-level=debug')
1014 # cmd = 'config --set rpm-log-file=/tmp/smart-debug-logfile'
1015 # self._invoke_smart(cmd)
1016 ch_already_added = []
1017 for canonical_arch in platform_extra:
1018 arch = canonical_arch.split('-')[0]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001019 arch_channel = os.path.join(self.d.getVar('WORKDIR', True), 'rpms', arch)
1020 oe.path.remove(arch_channel)
1021 deploy_arch_dir = os.path.join(self.deploy_dir, arch)
1022 if not os.path.exists(deploy_arch_dir):
1023 continue
1024
1025 lockfilename = self.d.getVar('DEPLOY_DIR_RPM', True) + "/rpm.lock"
1026 lf = bb.utils.lockfile(lockfilename, False)
1027 oe.path.copyhardlinktree(deploy_arch_dir, arch_channel)
1028 bb.utils.unlockfile(lf)
1029
1030 if not arch in ch_already_added:
1031 bb.note('Adding Smart channel %s (%s)' %
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001032 (arch, channel_priority))
1033 self._invoke_smart('channel --add %s type=rpm-md baseurl=%s -y'
1034 % (arch, arch_channel))
1035 self._invoke_smart('channel --set %s priority=%d' %
1036 (arch, channel_priority))
1037 channel_priority -= 5
1038
1039 ch_already_added.append(arch)
1040
1041 bb.note('adding Smart RPM DB channel')
1042 self._invoke_smart('channel --add rpmsys type=rpm-sys -y')
1043
1044 # Construct install scriptlet wrapper.
1045 # Scripts need to be ordered when executed, this ensures numeric order.
1046 # If we ever run into needing more the 899 scripts, we'll have to.
1047 # change num to start with 1000.
1048 #
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001049 scriptletcmd = "$2 $1/$3 $4\n"
1050 scriptpath = "$1/$3"
1051
1052 # When self.debug_level >= 3, also dump the content of the
1053 # executed scriptlets and how they get invoked. We have to
1054 # replace "exit 1" and "ERR" because printing those as-is
1055 # would trigger a log analysis failure.
1056 if self.debug_level >= 3:
1057 dump_invocation = 'echo "Executing ${name} ${kind} with: ' + scriptletcmd + '"\n'
1058 dump_script = 'cat ' + scriptpath + '| sed -e "s/exit 1/exxxit 1/g" -e "s/ERR/IRR/g"; echo\n'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001059 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001060 dump_invocation = 'echo "Executing ${name} ${kind}"\n'
1061 dump_script = ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001062
1063 SCRIPTLET_FORMAT = "#!/bin/bash\n" \
1064 "\n" \
1065 "export PATH=%s\n" \
1066 "export D=%s\n" \
1067 'export OFFLINE_ROOT="$D"\n' \
1068 'export IPKG_OFFLINE_ROOT="$D"\n' \
1069 'export OPKG_OFFLINE_ROOT="$D"\n' \
1070 "export INTERCEPT_DIR=%s\n" \
1071 "export NATIVE_ROOT=%s\n" \
1072 "\n" \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001073 "name=`head -1 " + scriptpath + " | cut -d\' \' -f 2`\n" \
1074 "kind=`head -1 " + scriptpath + " | cut -d\' \' -f 4`\n" \
1075 + dump_invocation \
1076 + dump_script \
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001077 + scriptletcmd + \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001078 "ret=$?\n" \
1079 "echo Result of ${name} ${kind}: ${ret}\n" \
1080 "if [ ${ret} -ne 0 ]; then\n" \
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001081 " if [ $4 -eq 1 ]; then\n" \
1082 " mkdir -p $1/etc/rpm-postinsts\n" \
1083 " num=100\n" \
1084 " while [ -e $1/etc/rpm-postinsts/${num}-* ]; do num=$((num + 1)); done\n" \
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001085 ' echo "#!$2" > $1/etc/rpm-postinsts/${num}-${name}\n' \
1086 ' echo "# Arg: $4" >> $1/etc/rpm-postinsts/${num}-${name}\n' \
1087 " cat " + scriptpath + " >> $1/etc/rpm-postinsts/${num}-${name}\n" \
1088 " chmod +x $1/etc/rpm-postinsts/${num}-${name}\n" \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001089 ' echo "Info: deferring ${name} ${kind} install scriptlet to first boot"\n' \
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001090 " else\n" \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001091 ' echo "Error: ${name} ${kind} remove scriptlet failed"\n' \
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001092 " fi\n" \
1093 "fi\n"
1094
1095 intercept_dir = self.d.expand('${WORKDIR}/intercept_scripts')
1096 native_root = self.d.getVar('STAGING_DIR_NATIVE', True)
1097 scriptlet_content = SCRIPTLET_FORMAT % (os.environ['PATH'],
1098 self.target_rootfs,
1099 intercept_dir,
1100 native_root)
1101 open(self.scriptlet_wrapper, 'w+').write(scriptlet_content)
1102
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001103 bb.note("configuring RPM cross-install scriptlet_wrapper")
1104 os.chmod(self.scriptlet_wrapper, 0o755)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001105 cmd = 'config --set rpm-extra-macros._cross_scriptlet_wrapper=%s' % \
1106 self.scriptlet_wrapper
1107 self._invoke_smart(cmd)
1108
1109 # Debug to show smart config info
1110 # bb.note(self._invoke_smart('config --show'))
1111
1112 def update(self):
1113 self._invoke_smart('update rpmsys')
1114
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001115 def get_rdepends_recursively(self, pkgs):
1116 # pkgs will be changed during the loop, so use [:] to make a copy.
1117 for pkg in pkgs[:]:
1118 sub_data = oe.packagedata.read_subpkgdata(pkg, self.d)
1119 sub_rdep = sub_data.get("RDEPENDS_" + pkg)
1120 if not sub_rdep:
1121 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001122 done = list(bb.utils.explode_dep_versions2(sub_rdep).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001123 next = done
1124 # Find all the rdepends on dependency chain
1125 while next:
1126 new = []
1127 for sub_pkg in next:
1128 sub_data = oe.packagedata.read_subpkgdata(sub_pkg, self.d)
1129 sub_pkg_rdep = sub_data.get("RDEPENDS_" + sub_pkg)
1130 if not sub_pkg_rdep:
1131 continue
1132 for p in bb.utils.explode_dep_versions2(sub_pkg_rdep):
1133 # Already handled, skip it.
1134 if p in done or p in pkgs:
1135 continue
1136 # It's a new dep
1137 if oe.packagedata.has_subpkgdata(p, self.d):
1138 done.append(p)
1139 new.append(p)
1140 next = new
1141 pkgs.extend(done)
1142 return pkgs
1143
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001144 '''
1145 Install pkgs with smart, the pkg name is oe format
1146 '''
1147 def install(self, pkgs, attempt_only=False):
1148
1149 if not pkgs:
1150 bb.note("There are no packages to install")
1151 return
1152 bb.note("Installing the following packages: %s" % ' '.join(pkgs))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001153 if not attempt_only:
1154 # Pull in multilib requires since rpm may not pull in them
1155 # correctly, for example,
1156 # lib32-packagegroup-core-standalone-sdk-target requires
1157 # lib32-libc6, but rpm may pull in libc6 rather than lib32-libc6
1158 # since it doesn't know mlprefix (lib32-), bitbake knows it and
1159 # can handle it well, find out the RDEPENDS on the chain will
1160 # fix the problem. Both do_rootfs and do_populate_sdk have this
1161 # issue.
1162 # The attempt_only packages don't need this since they are
1163 # based on the installed ones.
1164 #
1165 # Separate pkgs into two lists, one is multilib, the other one
1166 # is non-multilib.
1167 ml_pkgs = []
1168 non_ml_pkgs = pkgs[:]
1169 for pkg in pkgs:
1170 for mlib in (self.d.getVar("MULTILIB_VARIANTS", True) or "").split():
1171 if pkg.startswith(mlib + '-'):
1172 ml_pkgs.append(pkg)
1173 non_ml_pkgs.remove(pkg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001174
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001175 if len(ml_pkgs) > 0 and len(non_ml_pkgs) > 0:
1176 # Found both foo and lib-foo
1177 ml_pkgs = self.get_rdepends_recursively(ml_pkgs)
1178 non_ml_pkgs = self.get_rdepends_recursively(non_ml_pkgs)
1179 # Longer list makes smart slower, so only keep the pkgs
1180 # which have the same BPN, and smart can handle others
1181 # correctly.
1182 pkgs_new = []
1183 for pkg in non_ml_pkgs:
1184 for mlib in (self.d.getVar("MULTILIB_VARIANTS", True) or "").split():
1185 mlib_pkg = mlib + "-" + pkg
1186 if mlib_pkg in ml_pkgs:
1187 pkgs_new.append(pkg)
1188 pkgs_new.append(mlib_pkg)
1189 for pkg in pkgs:
1190 if pkg not in pkgs_new:
1191 pkgs_new.append(pkg)
1192 pkgs = pkgs_new
1193 new_depends = {}
1194 deps = bb.utils.explode_dep_versions2(" ".join(pkgs))
1195 for depend in deps:
1196 data = oe.packagedata.read_subpkgdata(depend, self.d)
1197 key = "PKG_%s" % depend
1198 if key in data:
1199 new_depend = data[key]
1200 else:
1201 new_depend = depend
1202 new_depends[new_depend] = deps[depend]
1203 pkgs = bb.utils.join_deps(new_depends, commasep=True).split(', ')
1204 pkgs = self._pkg_translate_oe_to_smart(pkgs, attempt_only)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001205 if not pkgs:
1206 bb.note("There are no packages to install")
1207 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001208 if not attempt_only:
1209 bb.note('to be installed: %s' % ' '.join(pkgs))
1210 cmd = "%s %s install -y %s" % \
1211 (self.smart_cmd, self.smart_opt, ' '.join(pkgs))
1212 bb.note(cmd)
1213 else:
1214 bb.note('installing attempt only packages...')
1215 bb.note('Attempting %s' % ' '.join(pkgs))
1216 cmd = "%s %s install --attempt -y %s" % \
1217 (self.smart_cmd, self.smart_opt, ' '.join(pkgs))
1218 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001219 output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001220 bb.note(output)
1221 except subprocess.CalledProcessError as e:
1222 bb.fatal("Unable to install packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001223 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001224
1225 '''
1226 Remove pkgs with smart, the pkg name is smart/rpm format
1227 '''
1228 def remove(self, pkgs, with_dependencies=True):
1229 bb.note('to be removed: ' + ' '.join(pkgs))
1230
1231 if not with_dependencies:
1232 cmd = "%s -e --nodeps " % self.rpm_cmd
1233 cmd += "--root=%s " % self.target_rootfs
1234 cmd += "--dbpath=/var/lib/rpm "
1235 cmd += "--define='_cross_scriptlet_wrapper %s' " % \
1236 self.scriptlet_wrapper
1237 cmd += "--define='_tmppath /%s/tmp' %s" % (self.install_dir_name, ' '.join(pkgs))
1238 else:
1239 # for pkg in pkgs:
1240 # bb.note('Debug: What required: %s' % pkg)
1241 # bb.note(self._invoke_smart('query %s --show-requiredby' % pkg))
1242
1243 cmd = "%s %s remove -y %s" % (self.smart_cmd,
1244 self.smart_opt,
1245 ' '.join(pkgs))
1246
1247 try:
1248 bb.note(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001249 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001250 bb.note(output)
1251 except subprocess.CalledProcessError as e:
1252 bb.note("Unable to remove packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001253 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001254
1255 def upgrade(self):
1256 bb.note('smart upgrade')
1257 self._invoke_smart('upgrade')
1258
1259 def write_index(self):
1260 result = self.indexer.write_index()
1261
1262 if result is not None:
1263 bb.fatal(result)
1264
1265 def remove_packaging_data(self):
1266 bb.utils.remove(self.image_rpmlib, True)
1267 bb.utils.remove(os.path.join(self.target_rootfs, 'var/lib/smart'),
1268 True)
1269 bb.utils.remove(os.path.join(self.target_rootfs, 'var/lib/opkg'), True)
1270
1271 # remove temp directory
1272 bb.utils.remove(self.install_dir_path, True)
1273
1274 def backup_packaging_data(self):
1275 # Save the rpmlib for increment rpm image generation
1276 if os.path.exists(self.saved_rpmlib):
1277 bb.utils.remove(self.saved_rpmlib, True)
1278 shutil.copytree(self.image_rpmlib,
1279 self.saved_rpmlib,
1280 symlinks=True)
1281
1282 def recovery_packaging_data(self):
1283 # Move the rpmlib back
1284 if os.path.exists(self.saved_rpmlib):
1285 if os.path.exists(self.image_rpmlib):
1286 bb.utils.remove(self.image_rpmlib, True)
1287
1288 bb.note('Recovery packaging data')
1289 shutil.copytree(self.saved_rpmlib,
1290 self.image_rpmlib,
1291 symlinks=True)
1292
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001293 def list_installed(self):
1294 return self.pkgs_list.list_pkgs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001295
1296 '''
1297 If incremental install, we need to determine what we've got,
1298 what we need to add, and what to remove...
1299 The dump_install_solution will dump and save the new install
1300 solution.
1301 '''
1302 def dump_install_solution(self, pkgs):
1303 bb.note('creating new install solution for incremental install')
1304 if len(pkgs) == 0:
1305 return
1306
1307 pkgs = self._pkg_translate_oe_to_smart(pkgs, False)
1308 install_pkgs = list()
1309
1310 cmd = "%s %s install -y --dump %s 2>%s" % \
1311 (self.smart_cmd,
1312 self.smart_opt,
1313 ' '.join(pkgs),
1314 self.solution_manifest)
1315 try:
1316 # Disable rpmsys channel for the fake install
1317 self._invoke_smart('channel --disable rpmsys')
1318
1319 subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1320 with open(self.solution_manifest, 'r') as manifest:
1321 for pkg in manifest.read().split('\n'):
1322 if '@' in pkg:
1323 install_pkgs.append(pkg)
1324 except subprocess.CalledProcessError as e:
1325 bb.note("Unable to dump install packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001326 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001327 # Recovery rpmsys channel
1328 self._invoke_smart('channel --enable rpmsys')
1329 return install_pkgs
1330
1331 '''
1332 If incremental install, we need to determine what we've got,
1333 what we need to add, and what to remove...
1334 The load_old_install_solution will load the previous install
1335 solution
1336 '''
1337 def load_old_install_solution(self):
1338 bb.note('load old install solution for incremental install')
1339 installed_pkgs = list()
1340 if not os.path.exists(self.solution_manifest):
1341 bb.note('old install solution not exist')
1342 return installed_pkgs
1343
1344 with open(self.solution_manifest, 'r') as manifest:
1345 for pkg in manifest.read().split('\n'):
1346 if '@' in pkg:
1347 installed_pkgs.append(pkg.strip())
1348
1349 return installed_pkgs
1350
1351 '''
1352 Dump all available packages in feeds, it should be invoked after the
1353 newest rpm index was created
1354 '''
1355 def dump_all_available_pkgs(self):
1356 available_manifest = self.d.expand('${T}/saved/available_pkgs.txt')
1357 available_pkgs = list()
1358 cmd = "%s %s query --output %s" % \
1359 (self.smart_cmd, self.smart_opt, available_manifest)
1360 try:
1361 subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1362 with open(available_manifest, 'r') as manifest:
1363 for pkg in manifest.read().split('\n'):
1364 if '@' in pkg:
1365 available_pkgs.append(pkg.strip())
1366 except subprocess.CalledProcessError as e:
1367 bb.note("Unable to list all available packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001368 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001369
1370 self.fullpkglist = available_pkgs
1371
1372 return
1373
1374 def save_rpmpostinst(self, pkg):
1375 mlibs = (self.d.getVar('MULTILIB_GLOBAL_VARIANTS', False) or "").split()
1376
1377 new_pkg = pkg
1378 # Remove any multilib prefix from the package name
1379 for mlib in mlibs:
1380 if mlib in pkg:
1381 new_pkg = pkg.replace(mlib + '-', '')
1382 break
1383
1384 bb.note(' * postponing %s' % new_pkg)
1385 saved_dir = self.target_rootfs + self.d.expand('${sysconfdir}/rpm-postinsts/') + new_pkg
1386
1387 cmd = self.rpm_cmd + ' -q --scripts --root ' + self.target_rootfs
1388 cmd += ' --dbpath=/var/lib/rpm ' + new_pkg
1389 cmd += ' | sed -n -e "/^postinstall scriptlet (using .*):$/,/^.* scriptlet (using .*):$/ {/.*/p}"'
1390 cmd += ' | sed -e "/postinstall scriptlet (using \(.*\)):$/d"'
1391 cmd += ' -e "/^.* scriptlet (using .*):$/d" > %s' % saved_dir
1392
1393 try:
1394 bb.note(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001395 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).strip().decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001396 bb.note(output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001397 os.chmod(saved_dir, 0o755)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001398 except subprocess.CalledProcessError as e:
1399 bb.fatal("Invoke save_rpmpostinst failed. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001400 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001401
1402 '''Write common configuration for target usage'''
1403 def rpm_setup_smart_target_config(self):
1404 bb.utils.remove(os.path.join(self.target_rootfs, 'var/lib/smart'),
1405 True)
1406
1407 self._invoke_smart('config --set rpm-nolinktos=1')
1408 self._invoke_smart('config --set rpm-noparentdirs=1')
1409 for i in self.d.getVar('BAD_RECOMMENDATIONS', True).split():
1410 self._invoke_smart('flag --set ignore-recommends %s' % i)
1411 self._invoke_smart('channel --add rpmsys type=rpm-sys -y')
1412
1413 '''
1414 The rpm db lock files were produced after invoking rpm to query on
1415 build system, and they caused the rpm on target didn't work, so we
1416 need to unlock the rpm db by removing the lock files.
1417 '''
1418 def unlock_rpm_db(self):
1419 # Remove rpm db lock files
1420 rpm_db_locks = glob.glob('%s/var/lib/rpm/__db.*' % self.target_rootfs)
1421 for f in rpm_db_locks:
1422 bb.utils.remove(f, True)
1423
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001424 """
1425 Returns a dictionary with the package info.
1426 """
1427 def package_info(self, pkg):
1428 cmd = "%s %s info --urls %s" % (self.smart_cmd, self.smart_opt, pkg)
1429 try:
1430 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8")
1431 except subprocess.CalledProcessError as e:
1432 bb.fatal("Unable to list available packages. Command '%s' "
1433 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001434
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001435 # Set default values to avoid UnboundLocalError
1436 arch = ""
1437 ver = ""
1438 filename = ""
1439
1440 #Parse output
1441 for line in output.splitlines():
1442 line = line.rstrip()
1443 if line.startswith("Name:"):
1444 pkg = line.split(": ")[1]
1445 elif line.startswith("Version:"):
1446 tmp_str = line.split(": ")[1]
1447 ver, arch = tmp_str.split("@")
1448 break
1449
1450 # Get filename
1451 index = re.search("^URLs", output, re.MULTILINE)
1452 tmp_str = output[index.end():]
1453 for line in tmp_str.splitlines():
1454 if "/" in line:
1455 line = line.lstrip()
1456 filename = line.split(" ")[0]
1457 break
1458
1459 # To have the same data type than other package_info methods
1460 filepath = os.path.join(self.deploy_dir, arch, filename)
1461 pkg_dict = {}
1462 pkg_dict[pkg] = {"arch":arch, "ver":ver, "filename":filename,
1463 "filepath": filepath}
1464
1465 return pkg_dict
1466
1467 """
1468 Returns the path to a tmpdir where resides the contents of a package.
1469
1470 Deleting the tmpdir is responsability of the caller.
1471
1472 """
1473 def extract(self, pkg):
1474 pkg_info = self.package_info(pkg)
1475 if not pkg_info:
1476 bb.fatal("Unable to get information for package '%s' while "
1477 "trying to extract the package." % pkg)
1478
1479 pkg_path = pkg_info[pkg]["filepath"]
1480
1481 cpio_cmd = bb.utils.which(os.getenv("PATH"), "cpio")
1482 rpm2cpio_cmd = bb.utils.which(os.getenv("PATH"), "rpm2cpio")
1483
1484 if not os.path.isfile(pkg_path):
1485 bb.fatal("Unable to extract package for '%s'."
1486 "File %s doesn't exists" % (pkg, pkg_path))
1487
1488 tmp_dir = tempfile.mkdtemp()
1489 current_dir = os.getcwd()
1490 os.chdir(tmp_dir)
1491
1492 try:
1493 cmd = "%s %s | %s -idmv" % (rpm2cpio_cmd, pkg_path, cpio_cmd)
1494 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1495 except subprocess.CalledProcessError as e:
1496 bb.utils.remove(tmp_dir, recurse=True)
1497 bb.fatal("Unable to extract %s package. Command '%s' "
1498 "returned %d:\n%s" % (pkg_path, cmd, e.returncode, e.output.decode("utf-8")))
1499 except OSError as e:
1500 bb.utils.remove(tmp_dir, recurse=True)
1501 bb.fatal("Unable to extract %s package. Command '%s' "
1502 "returned %d:\n%s at %s" % (pkg_path, cmd, e.errno, e.strerror, e.filename))
1503
1504 bb.note("Extracted %s to %s" % (pkg_path, tmp_dir))
1505 os.chdir(current_dir)
1506
1507 return tmp_dir
1508
1509
1510class OpkgDpkgPM(PackageManager):
1511 """
1512 This is an abstract class. Do not instantiate this directly.
1513 """
1514 def __init__(self, d):
1515 super(OpkgDpkgPM, self).__init__(d)
1516
1517 """
1518 Returns a dictionary with the package info.
1519
1520 This method extracts the common parts for Opkg and Dpkg
1521 """
1522 def package_info(self, pkg, cmd):
1523
1524 try:
1525 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8")
1526 except subprocess.CalledProcessError as e:
1527 bb.fatal("Unable to list available packages. Command '%s' "
1528 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
1529 return opkg_query(output)
1530
1531 """
1532 Returns the path to a tmpdir where resides the contents of a package.
1533
1534 Deleting the tmpdir is responsability of the caller.
1535
1536 This method extracts the common parts for Opkg and Dpkg
1537 """
1538 def extract(self, pkg, pkg_info):
1539
1540 ar_cmd = bb.utils.which(os.getenv("PATH"), "ar")
1541 tar_cmd = bb.utils.which(os.getenv("PATH"), "tar")
1542 pkg_path = pkg_info[pkg]["filepath"]
1543
1544 if not os.path.isfile(pkg_path):
1545 bb.fatal("Unable to extract package for '%s'."
1546 "File %s doesn't exists" % (pkg, pkg_path))
1547
1548 tmp_dir = tempfile.mkdtemp()
1549 current_dir = os.getcwd()
1550 os.chdir(tmp_dir)
1551
1552 try:
1553 cmd = "%s x %s" % (ar_cmd, pkg_path)
1554 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1555 cmd = "%s xf data.tar.*" % tar_cmd
1556 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1557 except subprocess.CalledProcessError as e:
1558 bb.utils.remove(tmp_dir, recurse=True)
1559 bb.fatal("Unable to extract %s package. Command '%s' "
1560 "returned %d:\n%s" % (pkg_path, cmd, e.returncode, e.output.decode("utf-8")))
1561 except OSError as e:
1562 bb.utils.remove(tmp_dir, recurse=True)
1563 bb.fatal("Unable to extract %s package. Command '%s' "
1564 "returned %d:\n%s at %s" % (pkg_path, cmd, e.errno, e.strerror, e.filename))
1565
1566 bb.note("Extracted %s to %s" % (pkg_path, tmp_dir))
1567 bb.utils.remove(os.path.join(tmp_dir, "debian-binary"))
1568 bb.utils.remove(os.path.join(tmp_dir, "control.tar.gz"))
1569 os.chdir(current_dir)
1570
1571 return tmp_dir
1572
1573
1574class OpkgPM(OpkgDpkgPM):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001575 def __init__(self, d, target_rootfs, config_file, archs, task_name='target'):
1576 super(OpkgPM, self).__init__(d)
1577
1578 self.target_rootfs = target_rootfs
1579 self.config_file = config_file
1580 self.pkg_archs = archs
1581 self.task_name = task_name
1582
1583 self.deploy_dir = self.d.getVar("DEPLOY_DIR_IPK", True)
1584 self.deploy_lock_file = os.path.join(self.deploy_dir, "deploy.lock")
1585 self.opkg_cmd = bb.utils.which(os.getenv('PATH'), "opkg")
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001586 self.opkg_args = "--volatile-cache -f %s -t %s -o %s " % (self.config_file, self.d.expand('${T}/ipktemp/') ,target_rootfs)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001587 self.opkg_args += self.d.getVar("OPKG_ARGS", True)
1588
1589 opkg_lib_dir = self.d.getVar('OPKGLIBDIR', True)
1590 if opkg_lib_dir[0] == "/":
1591 opkg_lib_dir = opkg_lib_dir[1:]
1592
1593 self.opkg_dir = os.path.join(target_rootfs, opkg_lib_dir, "opkg")
1594
1595 bb.utils.mkdirhier(self.opkg_dir)
1596
1597 self.saved_opkg_dir = self.d.expand('${T}/saved/%s' % self.task_name)
1598 if not os.path.exists(self.d.expand('${T}/saved')):
1599 bb.utils.mkdirhier(self.d.expand('${T}/saved'))
1600
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001601 self.from_feeds = (self.d.getVar('BUILD_IMAGES_FROM_FEEDS', True) or "") == "1"
1602 if self.from_feeds:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001603 self._create_custom_config()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001604 else:
1605 self._create_config()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001606
1607 self.indexer = OpkgIndexer(self.d, self.deploy_dir)
1608
1609 """
1610 This function will change a package's status in /var/lib/opkg/status file.
1611 If 'packages' is None then the new_status will be applied to all
1612 packages
1613 """
1614 def mark_packages(self, status_tag, packages=None):
1615 status_file = os.path.join(self.opkg_dir, "status")
1616
1617 with open(status_file, "r") as sf:
1618 with open(status_file + ".tmp", "w+") as tmp_sf:
1619 if packages is None:
1620 tmp_sf.write(re.sub(r"Package: (.*?)\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)",
1621 r"Package: \1\n\2Status: \3%s" % status_tag,
1622 sf.read()))
1623 else:
1624 if type(packages).__name__ != "list":
1625 raise TypeError("'packages' should be a list object")
1626
1627 status = sf.read()
1628 for pkg in packages:
1629 status = re.sub(r"Package: %s\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)" % pkg,
1630 r"Package: %s\n\1Status: \2%s" % (pkg, status_tag),
1631 status)
1632
1633 tmp_sf.write(status)
1634
1635 os.rename(status_file + ".tmp", status_file)
1636
1637 def _create_custom_config(self):
1638 bb.note("Building from feeds activated!")
1639
1640 with open(self.config_file, "w+") as config_file:
1641 priority = 1
1642 for arch in self.pkg_archs.split():
1643 config_file.write("arch %s %d\n" % (arch, priority))
1644 priority += 5
1645
1646 for line in (self.d.getVar('IPK_FEED_URIS', True) or "").split():
1647 feed_match = re.match("^[ \t]*(.*)##([^ \t]*)[ \t]*$", line)
1648
1649 if feed_match is not None:
1650 feed_name = feed_match.group(1)
1651 feed_uri = feed_match.group(2)
1652
1653 bb.note("Add %s feed with URL %s" % (feed_name, feed_uri))
1654
1655 config_file.write("src/gz %s %s\n" % (feed_name, feed_uri))
1656
1657 """
1658 Allow to use package deploy directory contents as quick devel-testing
1659 feed. This creates individual feed configs for each arch subdir of those
1660 specified as compatible for the current machine.
1661 NOTE: Development-helper feature, NOT a full-fledged feed.
1662 """
1663 if (self.d.getVar('FEED_DEPLOYDIR_BASE_URI', True) or "") != "":
1664 for arch in self.pkg_archs.split():
1665 cfg_file_name = os.path.join(self.target_rootfs,
1666 self.d.getVar("sysconfdir", True),
1667 "opkg",
1668 "local-%s-feed.conf" % arch)
1669
1670 with open(cfg_file_name, "w+") as cfg_file:
1671 cfg_file.write("src/gz local-%s %s/%s" %
1672 (arch,
1673 self.d.getVar('FEED_DEPLOYDIR_BASE_URI', True),
1674 arch))
1675
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001676 if self.opkg_dir != '/var/lib/opkg':
1677 # There is no command line option for this anymore, we need to add
1678 # info_dir and status_file to config file, if OPKGLIBDIR doesn't have
1679 # the default value of "/var/lib" as defined in opkg:
1680 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_INFO_DIR "/var/lib/opkg/info"
1681 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_STATUS_FILE "/var/lib/opkg/status"
1682 cfg_file.write("option info_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR', True), 'opkg', 'info'))
1683 cfg_file.write("option status_file %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR', True), 'opkg', 'status'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001684
1685
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001686 def _create_config(self):
1687 with open(self.config_file, "w+") as config_file:
1688 priority = 1
1689 for arch in self.pkg_archs.split():
1690 config_file.write("arch %s %d\n" % (arch, priority))
1691 priority += 5
1692
1693 config_file.write("src oe file:%s\n" % self.deploy_dir)
1694
1695 for arch in self.pkg_archs.split():
1696 pkgs_dir = os.path.join(self.deploy_dir, arch)
1697 if os.path.isdir(pkgs_dir):
1698 config_file.write("src oe-%s file:%s\n" %
1699 (arch, pkgs_dir))
1700
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001701 if self.opkg_dir != '/var/lib/opkg':
1702 # There is no command line option for this anymore, we need to add
1703 # info_dir and status_file to config file, if OPKGLIBDIR doesn't have
1704 # the default value of "/var/lib" as defined in opkg:
1705 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_INFO_DIR "/var/lib/opkg/info"
1706 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_STATUS_FILE "/var/lib/opkg/status"
1707 config_file.write("option info_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR', True), 'opkg', 'info'))
1708 config_file.write("option status_file %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR', True), 'opkg', 'status'))
1709
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001710 def insert_feeds_uris(self):
1711 if self.feed_uris == "":
1712 return
1713
1714 rootfs_config = os.path.join('%s/etc/opkg/base-feeds.conf'
1715 % self.target_rootfs)
1716
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001717 feed_uris = self.construct_uris(self.feed_uris.split(), self.feed_base_paths.split())
1718 archs = self.pkg_archs.split() if self.feed_archs is None else self.feed_archs.split()
1719
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001720 with open(rootfs_config, "w+") as config_file:
1721 uri_iterator = 0
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001722 for uri in feed_uris:
1723 if archs:
1724 for arch in archs:
1725 if (self.feed_archs is None) and (not os.path.exists(os.path.join(self.deploy_dir, arch))):
1726 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001727 bb.note('Adding opkg feed url-%s-%d (%s)' %
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001728 (arch, uri_iterator, uri))
1729 config_file.write("src/gz uri-%s-%d %s/%s\n" %
1730 (arch, uri_iterator, uri, arch))
1731 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001732 bb.note('Adding opkg feed url-%d (%s)' %
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001733 (uri_iterator, uri))
1734 config_file.write("src/gz uri-%d %s\n" %
1735 (uri_iterator, uri))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001736
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001737 uri_iterator += 1
1738
1739 def update(self):
1740 self.deploy_dir_lock()
1741
1742 cmd = "%s %s update" % (self.opkg_cmd, self.opkg_args)
1743
1744 try:
1745 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
1746 except subprocess.CalledProcessError as e:
1747 self.deploy_dir_unlock()
1748 bb.fatal("Unable to update the package index files. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001749 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001750
1751 self.deploy_dir_unlock()
1752
1753 def install(self, pkgs, attempt_only=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001754 if not pkgs:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001755 return
1756
1757 cmd = "%s %s install %s" % (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
1758
1759 os.environ['D'] = self.target_rootfs
1760 os.environ['OFFLINE_ROOT'] = self.target_rootfs
1761 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
1762 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
1763 os.environ['INTERCEPT_DIR'] = os.path.join(self.d.getVar('WORKDIR', True),
1764 "intercept_scripts")
1765 os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE', True)
1766
1767 try:
1768 bb.note("Installing the following packages: %s" % ' '.join(pkgs))
1769 bb.note(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001770 output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001771 bb.note(output)
1772 except subprocess.CalledProcessError as e:
1773 (bb.fatal, bb.note)[attempt_only]("Unable to install packages. "
1774 "Command '%s' returned %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001775 (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001776
1777 def remove(self, pkgs, with_dependencies=True):
1778 if with_dependencies:
1779 cmd = "%s %s --force-depends --force-remove --force-removal-of-dependent-packages remove %s" % \
1780 (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
1781 else:
1782 cmd = "%s %s --force-depends remove %s" % \
1783 (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
1784
1785 try:
1786 bb.note(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001787 output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001788 bb.note(output)
1789 except subprocess.CalledProcessError as e:
1790 bb.fatal("Unable to remove packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001791 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001792
1793 def write_index(self):
1794 self.deploy_dir_lock()
1795
1796 result = self.indexer.write_index()
1797
1798 self.deploy_dir_unlock()
1799
1800 if result is not None:
1801 bb.fatal(result)
1802
1803 def remove_packaging_data(self):
1804 bb.utils.remove(self.opkg_dir, True)
1805 # create the directory back, it's needed by PM lock
1806 bb.utils.mkdirhier(self.opkg_dir)
1807
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001808 def remove_lists(self):
1809 if not self.from_feeds:
1810 bb.utils.remove(os.path.join(self.opkg_dir, "lists"), True)
1811
1812 def list_installed(self):
1813 return OpkgPkgsList(self.d, self.target_rootfs, self.config_file).list_pkgs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001814
1815 def handle_bad_recommendations(self):
1816 bad_recommendations = self.d.getVar("BAD_RECOMMENDATIONS", True) or ""
1817 if bad_recommendations.strip() == "":
1818 return
1819
1820 status_file = os.path.join(self.opkg_dir, "status")
1821
1822 # If status file existed, it means the bad recommendations has already
1823 # been handled
1824 if os.path.exists(status_file):
1825 return
1826
1827 cmd = "%s %s info " % (self.opkg_cmd, self.opkg_args)
1828
1829 with open(status_file, "w+") as status:
1830 for pkg in bad_recommendations.split():
1831 pkg_info = cmd + pkg
1832
1833 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001834 output = subprocess.check_output(pkg_info.split(), stderr=subprocess.STDOUT).strip().decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001835 except subprocess.CalledProcessError as e:
1836 bb.fatal("Cannot get package info. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001837 "returned %d:\n%s" % (pkg_info, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001838
1839 if output == "":
1840 bb.note("Ignored bad recommendation: '%s' is "
1841 "not a package" % pkg)
1842 continue
1843
1844 for line in output.split('\n'):
1845 if line.startswith("Status:"):
1846 status.write("Status: deinstall hold not-installed\n")
1847 else:
1848 status.write(line + "\n")
1849
1850 # Append a blank line after each package entry to ensure that it
1851 # is separated from the following entry
1852 status.write("\n")
1853
1854 '''
1855 The following function dummy installs pkgs and returns the log of output.
1856 '''
1857 def dummy_install(self, pkgs):
1858 if len(pkgs) == 0:
1859 return
1860
1861 # Create an temp dir as opkg root for dummy installation
1862 temp_rootfs = self.d.expand('${T}/opkg')
1863 temp_opkg_dir = os.path.join(temp_rootfs, 'var/lib/opkg')
1864 bb.utils.mkdirhier(temp_opkg_dir)
1865
1866 opkg_args = "-f %s -o %s " % (self.config_file, temp_rootfs)
1867 opkg_args += self.d.getVar("OPKG_ARGS", True)
1868
1869 cmd = "%s %s update" % (self.opkg_cmd, opkg_args)
1870 try:
1871 subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1872 except subprocess.CalledProcessError as e:
1873 bb.fatal("Unable to update. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001874 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001875
1876 # Dummy installation
1877 cmd = "%s %s --noaction install %s " % (self.opkg_cmd,
1878 opkg_args,
1879 ' '.join(pkgs))
1880 try:
1881 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1882 except subprocess.CalledProcessError as e:
1883 bb.fatal("Unable to dummy install packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001884 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001885
1886 bb.utils.remove(temp_rootfs, True)
1887
1888 return output
1889
1890 def backup_packaging_data(self):
1891 # Save the opkglib for increment ipk image generation
1892 if os.path.exists(self.saved_opkg_dir):
1893 bb.utils.remove(self.saved_opkg_dir, True)
1894 shutil.copytree(self.opkg_dir,
1895 self.saved_opkg_dir,
1896 symlinks=True)
1897
1898 def recover_packaging_data(self):
1899 # Move the opkglib back
1900 if os.path.exists(self.saved_opkg_dir):
1901 if os.path.exists(self.opkg_dir):
1902 bb.utils.remove(self.opkg_dir, True)
1903
1904 bb.note('Recover packaging data')
1905 shutil.copytree(self.saved_opkg_dir,
1906 self.opkg_dir,
1907 symlinks=True)
1908
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001909 """
1910 Returns a dictionary with the package info.
1911 """
1912 def package_info(self, pkg):
1913 cmd = "%s %s info %s" % (self.opkg_cmd, self.opkg_args, pkg)
1914 pkg_info = super(OpkgPM, self).package_info(pkg, cmd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001915
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001916 pkg_arch = pkg_info[pkg]["arch"]
1917 pkg_filename = pkg_info[pkg]["filename"]
1918 pkg_info[pkg]["filepath"] = \
1919 os.path.join(self.deploy_dir, pkg_arch, pkg_filename)
1920
1921 return pkg_info
1922
1923 """
1924 Returns the path to a tmpdir where resides the contents of a package.
1925
1926 Deleting the tmpdir is responsability of the caller.
1927 """
1928 def extract(self, pkg):
1929 pkg_info = self.package_info(pkg)
1930 if not pkg_info:
1931 bb.fatal("Unable to get information for package '%s' while "
1932 "trying to extract the package." % pkg)
1933
1934 tmp_dir = super(OpkgPM, self).extract(pkg, pkg_info)
1935 bb.utils.remove(os.path.join(tmp_dir, "data.tar.gz"))
1936
1937 return tmp_dir
1938
1939class DpkgPM(OpkgDpkgPM):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001940 def __init__(self, d, target_rootfs, archs, base_archs, apt_conf_dir=None):
1941 super(DpkgPM, self).__init__(d)
1942 self.target_rootfs = target_rootfs
1943 self.deploy_dir = self.d.getVar('DEPLOY_DIR_DEB', True)
1944 if apt_conf_dir is None:
1945 self.apt_conf_dir = self.d.expand("${APTCONF_TARGET}/apt")
1946 else:
1947 self.apt_conf_dir = apt_conf_dir
1948 self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf")
1949 self.apt_get_cmd = bb.utils.which(os.getenv('PATH'), "apt-get")
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001950 self.apt_cache_cmd = bb.utils.which(os.getenv('PATH'), "apt-cache")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001951
1952 self.apt_args = d.getVar("APT_ARGS", True)
1953
1954 self.all_arch_list = archs.split()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001955 all_mlb_pkg_arch_list = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS', True) or "").split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001956 self.all_arch_list.extend(arch for arch in all_mlb_pkg_arch_list if arch not in self.all_arch_list)
1957
1958 self._create_configs(archs, base_archs)
1959
1960 self.indexer = DpkgIndexer(self.d, self.deploy_dir)
1961
1962 """
1963 This function will change a package's status in /var/lib/dpkg/status file.
1964 If 'packages' is None then the new_status will be applied to all
1965 packages
1966 """
1967 def mark_packages(self, status_tag, packages=None):
1968 status_file = self.target_rootfs + "/var/lib/dpkg/status"
1969
1970 with open(status_file, "r") as sf:
1971 with open(status_file + ".tmp", "w+") as tmp_sf:
1972 if packages is None:
1973 tmp_sf.write(re.sub(r"Package: (.*?)\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)",
1974 r"Package: \1\n\2Status: \3%s" % status_tag,
1975 sf.read()))
1976 else:
1977 if type(packages).__name__ != "list":
1978 raise TypeError("'packages' should be a list object")
1979
1980 status = sf.read()
1981 for pkg in packages:
1982 status = re.sub(r"Package: %s\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)" % pkg,
1983 r"Package: %s\n\1Status: \2%s" % (pkg, status_tag),
1984 status)
1985
1986 tmp_sf.write(status)
1987
1988 os.rename(status_file + ".tmp", status_file)
1989
1990 """
1991 Run the pre/post installs for package "package_name". If package_name is
1992 None, then run all pre/post install scriptlets.
1993 """
1994 def run_pre_post_installs(self, package_name=None):
1995 info_dir = self.target_rootfs + "/var/lib/dpkg/info"
1996 suffixes = [(".preinst", "Preinstall"), (".postinst", "Postinstall")]
1997 status_file = self.target_rootfs + "/var/lib/dpkg/status"
1998 installed_pkgs = []
1999
2000 with open(status_file, "r") as status:
2001 for line in status.read().split('\n'):
2002 m = re.match("^Package: (.*)", line)
2003 if m is not None:
2004 installed_pkgs.append(m.group(1))
2005
2006 if package_name is not None and not package_name in installed_pkgs:
2007 return
2008
2009 os.environ['D'] = self.target_rootfs
2010 os.environ['OFFLINE_ROOT'] = self.target_rootfs
2011 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
2012 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
2013 os.environ['INTERCEPT_DIR'] = os.path.join(self.d.getVar('WORKDIR', True),
2014 "intercept_scripts")
2015 os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE', True)
2016
2017 failed_pkgs = []
2018 for pkg_name in installed_pkgs:
2019 for suffix in suffixes:
2020 p_full = os.path.join(info_dir, pkg_name + suffix[0])
2021 if os.path.exists(p_full):
2022 try:
2023 bb.note("Executing %s for package: %s ..." %
2024 (suffix[1].lower(), pkg_name))
2025 subprocess.check_output(p_full, stderr=subprocess.STDOUT)
2026 except subprocess.CalledProcessError as e:
2027 bb.note("%s for package %s failed with %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002028 (suffix[1], pkg_name, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002029 failed_pkgs.append(pkg_name)
2030 break
2031
2032 if len(failed_pkgs):
2033 self.mark_packages("unpacked", failed_pkgs)
2034
2035 def update(self):
2036 os.environ['APT_CONFIG'] = self.apt_conf_file
2037
2038 self.deploy_dir_lock()
2039
2040 cmd = "%s update" % self.apt_get_cmd
2041
2042 try:
2043 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
2044 except subprocess.CalledProcessError as e:
2045 bb.fatal("Unable to update the package index files. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002046 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002047
2048 self.deploy_dir_unlock()
2049
2050 def install(self, pkgs, attempt_only=False):
2051 if attempt_only and len(pkgs) == 0:
2052 return
2053
2054 os.environ['APT_CONFIG'] = self.apt_conf_file
2055
2056 cmd = "%s %s install --force-yes --allow-unauthenticated %s" % \
2057 (self.apt_get_cmd, self.apt_args, ' '.join(pkgs))
2058
2059 try:
2060 bb.note("Installing the following packages: %s" % ' '.join(pkgs))
2061 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
2062 except subprocess.CalledProcessError as e:
2063 (bb.fatal, bb.note)[attempt_only]("Unable to install packages. "
2064 "Command '%s' returned %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002065 (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002066
2067 # rename *.dpkg-new files/dirs
2068 for root, dirs, files in os.walk(self.target_rootfs):
2069 for dir in dirs:
2070 new_dir = re.sub("\.dpkg-new", "", dir)
2071 if dir != new_dir:
2072 os.rename(os.path.join(root, dir),
2073 os.path.join(root, new_dir))
2074
2075 for file in files:
2076 new_file = re.sub("\.dpkg-new", "", file)
2077 if file != new_file:
2078 os.rename(os.path.join(root, file),
2079 os.path.join(root, new_file))
2080
2081
2082 def remove(self, pkgs, with_dependencies=True):
2083 if with_dependencies:
2084 os.environ['APT_CONFIG'] = self.apt_conf_file
2085 cmd = "%s purge %s" % (self.apt_get_cmd, ' '.join(pkgs))
2086 else:
2087 cmd = "%s --admindir=%s/var/lib/dpkg --instdir=%s" \
2088 " -P --force-depends %s" % \
2089 (bb.utils.which(os.getenv('PATH'), "dpkg"),
2090 self.target_rootfs, self.target_rootfs, ' '.join(pkgs))
2091
2092 try:
2093 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
2094 except subprocess.CalledProcessError as e:
2095 bb.fatal("Unable to remove packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002096 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002097
2098 def write_index(self):
2099 self.deploy_dir_lock()
2100
2101 result = self.indexer.write_index()
2102
2103 self.deploy_dir_unlock()
2104
2105 if result is not None:
2106 bb.fatal(result)
2107
2108 def insert_feeds_uris(self):
2109 if self.feed_uris == "":
2110 return
2111
2112 sources_conf = os.path.join("%s/etc/apt/sources.list"
2113 % self.target_rootfs)
2114 arch_list = []
2115
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002116 if self.feed_archs is None:
2117 for arch in self.all_arch_list:
2118 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
2119 continue
2120 arch_list.append(arch)
2121 else:
2122 arch_list = self.feed_archs.split()
2123
2124 feed_uris = self.construct_uris(self.feed_uris.split(), self.feed_base_paths.split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002125
2126 with open(sources_conf, "w+") as sources_file:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002127 for uri in feed_uris:
2128 if arch_list:
2129 for arch in arch_list:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002130 bb.note('Adding dpkg channel at (%s)' % uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002131 sources_file.write("deb %s/%s ./\n" %
2132 (uri, arch))
2133 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002134 bb.note('Adding dpkg channel at (%s)' % uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002135 sources_file.write("deb %s ./\n" % uri)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002136
2137 def _create_configs(self, archs, base_archs):
2138 base_archs = re.sub("_", "-", base_archs)
2139
2140 if os.path.exists(self.apt_conf_dir):
2141 bb.utils.remove(self.apt_conf_dir, True)
2142
2143 bb.utils.mkdirhier(self.apt_conf_dir)
2144 bb.utils.mkdirhier(self.apt_conf_dir + "/lists/partial/")
2145 bb.utils.mkdirhier(self.apt_conf_dir + "/apt.conf.d/")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002146 bb.utils.mkdirhier(self.apt_conf_dir + "/preferences.d/")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002147
2148 arch_list = []
2149 for arch in self.all_arch_list:
2150 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
2151 continue
2152 arch_list.append(arch)
2153
2154 with open(os.path.join(self.apt_conf_dir, "preferences"), "w+") as prefs_file:
2155 priority = 801
2156 for arch in arch_list:
2157 prefs_file.write(
2158 "Package: *\n"
2159 "Pin: release l=%s\n"
2160 "Pin-Priority: %d\n\n" % (arch, priority))
2161
2162 priority += 5
2163
2164 pkg_exclude = self.d.getVar('PACKAGE_EXCLUDE', True) or ""
2165 for pkg in pkg_exclude.split():
2166 prefs_file.write(
2167 "Package: %s\n"
2168 "Pin: release *\n"
2169 "Pin-Priority: -1\n\n" % pkg)
2170
2171 arch_list.reverse()
2172
2173 with open(os.path.join(self.apt_conf_dir, "sources.list"), "w+") as sources_file:
2174 for arch in arch_list:
2175 sources_file.write("deb file:%s/ ./\n" %
2176 os.path.join(self.deploy_dir, arch))
2177
2178 base_arch_list = base_archs.split()
2179 multilib_variants = self.d.getVar("MULTILIB_VARIANTS", True);
2180 for variant in multilib_variants.split():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002181 localdata = bb.data.createCopy(self.d)
2182 variant_tune = localdata.getVar("DEFAULTTUNE_virtclass-multilib-" + variant, False)
2183 orig_arch = localdata.getVar("DPKG_ARCH", True)
2184 localdata.setVar("DEFAULTTUNE", variant_tune)
2185 bb.data.update_data(localdata)
2186 variant_arch = localdata.getVar("DPKG_ARCH", True)
2187 if variant_arch not in base_arch_list:
2188 base_arch_list.append(variant_arch)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002189
2190 with open(self.apt_conf_file, "w+") as apt_conf:
2191 with open(self.d.expand("${STAGING_ETCDIR_NATIVE}/apt/apt.conf.sample")) as apt_conf_sample:
2192 for line in apt_conf_sample.read().split("\n"):
2193 match_arch = re.match(" Architecture \".*\";$", line)
2194 architectures = ""
2195 if match_arch:
2196 for base_arch in base_arch_list:
2197 architectures += "\"%s\";" % base_arch
2198 apt_conf.write(" Architectures {%s};\n" % architectures);
2199 apt_conf.write(" Architecture \"%s\";\n" % base_archs)
2200 else:
2201 line = re.sub("#ROOTFS#", self.target_rootfs, line)
2202 line = re.sub("#APTCONF#", self.apt_conf_dir, line)
2203 apt_conf.write(line + "\n")
2204
2205 target_dpkg_dir = "%s/var/lib/dpkg" % self.target_rootfs
2206 bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "info"))
2207
2208 bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "updates"))
2209
2210 if not os.path.exists(os.path.join(target_dpkg_dir, "status")):
2211 open(os.path.join(target_dpkg_dir, "status"), "w+").close()
2212 if not os.path.exists(os.path.join(target_dpkg_dir, "available")):
2213 open(os.path.join(target_dpkg_dir, "available"), "w+").close()
2214
2215 def remove_packaging_data(self):
2216 bb.utils.remove(os.path.join(self.target_rootfs,
2217 self.d.getVar('opkglibdir', True)), True)
2218 bb.utils.remove(self.target_rootfs + "/var/lib/dpkg/", True)
2219
2220 def fix_broken_dependencies(self):
2221 os.environ['APT_CONFIG'] = self.apt_conf_file
2222
2223 cmd = "%s %s -f install" % (self.apt_get_cmd, self.apt_args)
2224
2225 try:
2226 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
2227 except subprocess.CalledProcessError as e:
2228 bb.fatal("Cannot fix broken dependencies. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002229 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002230
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002231 def list_installed(self):
2232 return DpkgPkgsList(self.d, self.target_rootfs).list_pkgs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002233
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002234 """
2235 Returns a dictionary with the package info.
2236 """
2237 def package_info(self, pkg):
2238 cmd = "%s show %s" % (self.apt_cache_cmd, pkg)
2239 pkg_info = super(DpkgPM, self).package_info(pkg, cmd)
2240
2241 pkg_arch = pkg_info[pkg]["pkgarch"]
2242 pkg_filename = pkg_info[pkg]["filename"]
2243 pkg_info[pkg]["filepath"] = \
2244 os.path.join(self.deploy_dir, pkg_arch, pkg_filename)
2245
2246 return pkg_info
2247
2248 """
2249 Returns the path to a tmpdir where resides the contents of a package.
2250
2251 Deleting the tmpdir is responsability of the caller.
2252 """
2253 def extract(self, pkg):
2254 pkg_info = self.package_info(pkg)
2255 if not pkg_info:
2256 bb.fatal("Unable to get information for package '%s' while "
2257 "trying to extract the package." % pkg)
2258
2259 tmp_dir = super(DpkgPM, self).extract(pkg, pkg_info)
2260 bb.utils.remove(os.path.join(tmp_dir, "data.tar.xz"))
2261
2262 return tmp_dir
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002263
2264def generate_index_files(d):
2265 classes = d.getVar('PACKAGE_CLASSES', True).replace("package_", "").split()
2266
2267 indexer_map = {
2268 "rpm": (RpmIndexer, d.getVar('DEPLOY_DIR_RPM', True)),
2269 "ipk": (OpkgIndexer, d.getVar('DEPLOY_DIR_IPK', True)),
2270 "deb": (DpkgIndexer, d.getVar('DEPLOY_DIR_DEB', True))
2271 }
2272
2273 result = None
2274
2275 for pkg_class in classes:
2276 if not pkg_class in indexer_map:
2277 continue
2278
2279 if os.path.exists(indexer_map[pkg_class][1]):
2280 result = indexer_map[pkg_class][0](d, indexer_map[pkg_class][1]).write_index()
2281
2282 if result is not None:
2283 bb.fatal(result)
2284
2285if __name__ == "__main__":
2286 """
2287 We should be able to run this as a standalone script, from outside bitbake
2288 environment.
2289 """
2290 """
2291 TBD
2292 """