blob: 13577b18bd47ade498c5a9b808f2e0e3a4886c3a [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
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001676 if self.d.getVar('OPKGLIBDIR', True) != '/var/lib':
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001677 # 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:
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001680 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_LISTS_DIR VARDIR "/lib/opkg/lists"
1681 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_INFO_DIR VARDIR "/lib/opkg/info"
1682 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_STATUS_FILE VARDIR "/lib/opkg/status"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001683 cfg_file.write("option info_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR', True), 'opkg', 'info'))
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001684 cfg_file.write("option lists_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR', True), 'opkg', 'lists'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001685 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 -05001686
1687
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001688 def _create_config(self):
1689 with open(self.config_file, "w+") as config_file:
1690 priority = 1
1691 for arch in self.pkg_archs.split():
1692 config_file.write("arch %s %d\n" % (arch, priority))
1693 priority += 5
1694
1695 config_file.write("src oe file:%s\n" % self.deploy_dir)
1696
1697 for arch in self.pkg_archs.split():
1698 pkgs_dir = os.path.join(self.deploy_dir, arch)
1699 if os.path.isdir(pkgs_dir):
1700 config_file.write("src oe-%s file:%s\n" %
1701 (arch, pkgs_dir))
1702
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001703 if self.d.getVar('OPKGLIBDIR', True) != '/var/lib':
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001704 # There is no command line option for this anymore, we need to add
1705 # info_dir and status_file to config file, if OPKGLIBDIR doesn't have
1706 # the default value of "/var/lib" as defined in opkg:
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001707 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_LISTS_DIR VARDIR "/lib/opkg/lists"
1708 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_INFO_DIR VARDIR "/lib/opkg/info"
1709 # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_STATUS_FILE VARDIR "/lib/opkg/status"
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001710 config_file.write("option info_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR', True), 'opkg', 'info'))
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001711 config_file.write("option lists_dir %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR', True), 'opkg', 'lists'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001712 config_file.write("option status_file %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR', True), 'opkg', 'status'))
1713
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001714 def insert_feeds_uris(self):
1715 if self.feed_uris == "":
1716 return
1717
1718 rootfs_config = os.path.join('%s/etc/opkg/base-feeds.conf'
1719 % self.target_rootfs)
1720
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001721 feed_uris = self.construct_uris(self.feed_uris.split(), self.feed_base_paths.split())
1722 archs = self.pkg_archs.split() if self.feed_archs is None else self.feed_archs.split()
1723
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001724 with open(rootfs_config, "w+") as config_file:
1725 uri_iterator = 0
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001726 for uri in feed_uris:
1727 if archs:
1728 for arch in archs:
1729 if (self.feed_archs is None) and (not os.path.exists(os.path.join(self.deploy_dir, arch))):
1730 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001731 bb.note('Adding opkg feed url-%s-%d (%s)' %
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001732 (arch, uri_iterator, uri))
1733 config_file.write("src/gz uri-%s-%d %s/%s\n" %
1734 (arch, uri_iterator, uri, arch))
1735 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001736 bb.note('Adding opkg feed url-%d (%s)' %
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001737 (uri_iterator, uri))
1738 config_file.write("src/gz uri-%d %s\n" %
1739 (uri_iterator, uri))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001740
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001741 uri_iterator += 1
1742
1743 def update(self):
1744 self.deploy_dir_lock()
1745
1746 cmd = "%s %s update" % (self.opkg_cmd, self.opkg_args)
1747
1748 try:
1749 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
1750 except subprocess.CalledProcessError as e:
1751 self.deploy_dir_unlock()
1752 bb.fatal("Unable to update the package index files. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001753 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001754
1755 self.deploy_dir_unlock()
1756
1757 def install(self, pkgs, attempt_only=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001758 if not pkgs:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001759 return
1760
1761 cmd = "%s %s install %s" % (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
1762
1763 os.environ['D'] = self.target_rootfs
1764 os.environ['OFFLINE_ROOT'] = self.target_rootfs
1765 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
1766 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
1767 os.environ['INTERCEPT_DIR'] = os.path.join(self.d.getVar('WORKDIR', True),
1768 "intercept_scripts")
1769 os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE', True)
1770
1771 try:
1772 bb.note("Installing the following packages: %s" % ' '.join(pkgs))
1773 bb.note(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001774 output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001775 bb.note(output)
1776 except subprocess.CalledProcessError as e:
1777 (bb.fatal, bb.note)[attempt_only]("Unable to install packages. "
1778 "Command '%s' returned %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001779 (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001780
1781 def remove(self, pkgs, with_dependencies=True):
1782 if with_dependencies:
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001783 cmd = "%s %s --force-remove --force-removal-of-dependent-packages remove %s" % \
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001784 (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
1785 else:
1786 cmd = "%s %s --force-depends remove %s" % \
1787 (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
1788
1789 try:
1790 bb.note(cmd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001791 output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001792 bb.note(output)
1793 except subprocess.CalledProcessError as e:
1794 bb.fatal("Unable to remove packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001795 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001796
1797 def write_index(self):
1798 self.deploy_dir_lock()
1799
1800 result = self.indexer.write_index()
1801
1802 self.deploy_dir_unlock()
1803
1804 if result is not None:
1805 bb.fatal(result)
1806
1807 def remove_packaging_data(self):
1808 bb.utils.remove(self.opkg_dir, True)
1809 # create the directory back, it's needed by PM lock
1810 bb.utils.mkdirhier(self.opkg_dir)
1811
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001812 def remove_lists(self):
1813 if not self.from_feeds:
1814 bb.utils.remove(os.path.join(self.opkg_dir, "lists"), True)
1815
1816 def list_installed(self):
1817 return OpkgPkgsList(self.d, self.target_rootfs, self.config_file).list_pkgs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001818
1819 def handle_bad_recommendations(self):
1820 bad_recommendations = self.d.getVar("BAD_RECOMMENDATIONS", True) or ""
1821 if bad_recommendations.strip() == "":
1822 return
1823
1824 status_file = os.path.join(self.opkg_dir, "status")
1825
1826 # If status file existed, it means the bad recommendations has already
1827 # been handled
1828 if os.path.exists(status_file):
1829 return
1830
1831 cmd = "%s %s info " % (self.opkg_cmd, self.opkg_args)
1832
1833 with open(status_file, "w+") as status:
1834 for pkg in bad_recommendations.split():
1835 pkg_info = cmd + pkg
1836
1837 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001838 output = subprocess.check_output(pkg_info.split(), stderr=subprocess.STDOUT).strip().decode("utf-8")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001839 except subprocess.CalledProcessError as e:
1840 bb.fatal("Cannot get package info. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001841 "returned %d:\n%s" % (pkg_info, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001842
1843 if output == "":
1844 bb.note("Ignored bad recommendation: '%s' is "
1845 "not a package" % pkg)
1846 continue
1847
1848 for line in output.split('\n'):
1849 if line.startswith("Status:"):
1850 status.write("Status: deinstall hold not-installed\n")
1851 else:
1852 status.write(line + "\n")
1853
1854 # Append a blank line after each package entry to ensure that it
1855 # is separated from the following entry
1856 status.write("\n")
1857
1858 '''
1859 The following function dummy installs pkgs and returns the log of output.
1860 '''
1861 def dummy_install(self, pkgs):
1862 if len(pkgs) == 0:
1863 return
1864
1865 # Create an temp dir as opkg root for dummy installation
1866 temp_rootfs = self.d.expand('${T}/opkg')
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001867 opkg_lib_dir = self.d.getVar('OPKGLIBDIR', True)
1868 if opkg_lib_dir[0] == "/":
1869 opkg_lib_dir = opkg_lib_dir[1:]
1870 temp_opkg_dir = os.path.join(temp_rootfs, opkg_lib_dir, 'opkg')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001871 bb.utils.mkdirhier(temp_opkg_dir)
1872
1873 opkg_args = "-f %s -o %s " % (self.config_file, temp_rootfs)
1874 opkg_args += self.d.getVar("OPKG_ARGS", True)
1875
1876 cmd = "%s %s update" % (self.opkg_cmd, opkg_args)
1877 try:
1878 subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1879 except subprocess.CalledProcessError as e:
1880 bb.fatal("Unable to update. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001881 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001882
1883 # Dummy installation
1884 cmd = "%s %s --noaction install %s " % (self.opkg_cmd,
1885 opkg_args,
1886 ' '.join(pkgs))
1887 try:
1888 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
1889 except subprocess.CalledProcessError as e:
1890 bb.fatal("Unable to dummy install packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001891 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001892
1893 bb.utils.remove(temp_rootfs, True)
1894
1895 return output
1896
1897 def backup_packaging_data(self):
1898 # Save the opkglib for increment ipk image generation
1899 if os.path.exists(self.saved_opkg_dir):
1900 bb.utils.remove(self.saved_opkg_dir, True)
1901 shutil.copytree(self.opkg_dir,
1902 self.saved_opkg_dir,
1903 symlinks=True)
1904
1905 def recover_packaging_data(self):
1906 # Move the opkglib back
1907 if os.path.exists(self.saved_opkg_dir):
1908 if os.path.exists(self.opkg_dir):
1909 bb.utils.remove(self.opkg_dir, True)
1910
1911 bb.note('Recover packaging data')
1912 shutil.copytree(self.saved_opkg_dir,
1913 self.opkg_dir,
1914 symlinks=True)
1915
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001916 """
1917 Returns a dictionary with the package info.
1918 """
1919 def package_info(self, pkg):
1920 cmd = "%s %s info %s" % (self.opkg_cmd, self.opkg_args, pkg)
1921 pkg_info = super(OpkgPM, self).package_info(pkg, cmd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001922
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001923 pkg_arch = pkg_info[pkg]["arch"]
1924 pkg_filename = pkg_info[pkg]["filename"]
1925 pkg_info[pkg]["filepath"] = \
1926 os.path.join(self.deploy_dir, pkg_arch, pkg_filename)
1927
1928 return pkg_info
1929
1930 """
1931 Returns the path to a tmpdir where resides the contents of a package.
1932
1933 Deleting the tmpdir is responsability of the caller.
1934 """
1935 def extract(self, pkg):
1936 pkg_info = self.package_info(pkg)
1937 if not pkg_info:
1938 bb.fatal("Unable to get information for package '%s' while "
1939 "trying to extract the package." % pkg)
1940
1941 tmp_dir = super(OpkgPM, self).extract(pkg, pkg_info)
1942 bb.utils.remove(os.path.join(tmp_dir, "data.tar.gz"))
1943
1944 return tmp_dir
1945
1946class DpkgPM(OpkgDpkgPM):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001947 def __init__(self, d, target_rootfs, archs, base_archs, apt_conf_dir=None):
1948 super(DpkgPM, self).__init__(d)
1949 self.target_rootfs = target_rootfs
1950 self.deploy_dir = self.d.getVar('DEPLOY_DIR_DEB', True)
1951 if apt_conf_dir is None:
1952 self.apt_conf_dir = self.d.expand("${APTCONF_TARGET}/apt")
1953 else:
1954 self.apt_conf_dir = apt_conf_dir
1955 self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf")
1956 self.apt_get_cmd = bb.utils.which(os.getenv('PATH'), "apt-get")
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001957 self.apt_cache_cmd = bb.utils.which(os.getenv('PATH'), "apt-cache")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001958
1959 self.apt_args = d.getVar("APT_ARGS", True)
1960
1961 self.all_arch_list = archs.split()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001962 all_mlb_pkg_arch_list = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS', True) or "").split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001963 self.all_arch_list.extend(arch for arch in all_mlb_pkg_arch_list if arch not in self.all_arch_list)
1964
1965 self._create_configs(archs, base_archs)
1966
1967 self.indexer = DpkgIndexer(self.d, self.deploy_dir)
1968
1969 """
1970 This function will change a package's status in /var/lib/dpkg/status file.
1971 If 'packages' is None then the new_status will be applied to all
1972 packages
1973 """
1974 def mark_packages(self, status_tag, packages=None):
1975 status_file = self.target_rootfs + "/var/lib/dpkg/status"
1976
1977 with open(status_file, "r") as sf:
1978 with open(status_file + ".tmp", "w+") as tmp_sf:
1979 if packages is None:
1980 tmp_sf.write(re.sub(r"Package: (.*?)\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)",
1981 r"Package: \1\n\2Status: \3%s" % status_tag,
1982 sf.read()))
1983 else:
1984 if type(packages).__name__ != "list":
1985 raise TypeError("'packages' should be a list object")
1986
1987 status = sf.read()
1988 for pkg in packages:
1989 status = re.sub(r"Package: %s\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)" % pkg,
1990 r"Package: %s\n\1Status: \2%s" % (pkg, status_tag),
1991 status)
1992
1993 tmp_sf.write(status)
1994
1995 os.rename(status_file + ".tmp", status_file)
1996
1997 """
1998 Run the pre/post installs for package "package_name". If package_name is
1999 None, then run all pre/post install scriptlets.
2000 """
2001 def run_pre_post_installs(self, package_name=None):
2002 info_dir = self.target_rootfs + "/var/lib/dpkg/info"
2003 suffixes = [(".preinst", "Preinstall"), (".postinst", "Postinstall")]
2004 status_file = self.target_rootfs + "/var/lib/dpkg/status"
2005 installed_pkgs = []
2006
2007 with open(status_file, "r") as status:
2008 for line in status.read().split('\n'):
2009 m = re.match("^Package: (.*)", line)
2010 if m is not None:
2011 installed_pkgs.append(m.group(1))
2012
2013 if package_name is not None and not package_name in installed_pkgs:
2014 return
2015
2016 os.environ['D'] = self.target_rootfs
2017 os.environ['OFFLINE_ROOT'] = self.target_rootfs
2018 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
2019 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
2020 os.environ['INTERCEPT_DIR'] = os.path.join(self.d.getVar('WORKDIR', True),
2021 "intercept_scripts")
2022 os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE', True)
2023
2024 failed_pkgs = []
2025 for pkg_name in installed_pkgs:
2026 for suffix in suffixes:
2027 p_full = os.path.join(info_dir, pkg_name + suffix[0])
2028 if os.path.exists(p_full):
2029 try:
2030 bb.note("Executing %s for package: %s ..." %
2031 (suffix[1].lower(), pkg_name))
2032 subprocess.check_output(p_full, stderr=subprocess.STDOUT)
2033 except subprocess.CalledProcessError as e:
2034 bb.note("%s for package %s failed with %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002035 (suffix[1], pkg_name, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002036 failed_pkgs.append(pkg_name)
2037 break
2038
2039 if len(failed_pkgs):
2040 self.mark_packages("unpacked", failed_pkgs)
2041
2042 def update(self):
2043 os.environ['APT_CONFIG'] = self.apt_conf_file
2044
2045 self.deploy_dir_lock()
2046
2047 cmd = "%s update" % self.apt_get_cmd
2048
2049 try:
2050 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
2051 except subprocess.CalledProcessError as e:
2052 bb.fatal("Unable to update the package index files. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002053 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002054
2055 self.deploy_dir_unlock()
2056
2057 def install(self, pkgs, attempt_only=False):
2058 if attempt_only and len(pkgs) == 0:
2059 return
2060
2061 os.environ['APT_CONFIG'] = self.apt_conf_file
2062
2063 cmd = "%s %s install --force-yes --allow-unauthenticated %s" % \
2064 (self.apt_get_cmd, self.apt_args, ' '.join(pkgs))
2065
2066 try:
2067 bb.note("Installing the following packages: %s" % ' '.join(pkgs))
2068 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
2069 except subprocess.CalledProcessError as e:
2070 (bb.fatal, bb.note)[attempt_only]("Unable to install packages. "
2071 "Command '%s' returned %d:\n%s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002072 (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002073
2074 # rename *.dpkg-new files/dirs
2075 for root, dirs, files in os.walk(self.target_rootfs):
2076 for dir in dirs:
2077 new_dir = re.sub("\.dpkg-new", "", dir)
2078 if dir != new_dir:
2079 os.rename(os.path.join(root, dir),
2080 os.path.join(root, new_dir))
2081
2082 for file in files:
2083 new_file = re.sub("\.dpkg-new", "", file)
2084 if file != new_file:
2085 os.rename(os.path.join(root, file),
2086 os.path.join(root, new_file))
2087
2088
2089 def remove(self, pkgs, with_dependencies=True):
2090 if with_dependencies:
2091 os.environ['APT_CONFIG'] = self.apt_conf_file
2092 cmd = "%s purge %s" % (self.apt_get_cmd, ' '.join(pkgs))
2093 else:
2094 cmd = "%s --admindir=%s/var/lib/dpkg --instdir=%s" \
2095 " -P --force-depends %s" % \
2096 (bb.utils.which(os.getenv('PATH'), "dpkg"),
2097 self.target_rootfs, self.target_rootfs, ' '.join(pkgs))
2098
2099 try:
2100 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
2101 except subprocess.CalledProcessError as e:
2102 bb.fatal("Unable to remove packages. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002103 "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002104
2105 def write_index(self):
2106 self.deploy_dir_lock()
2107
2108 result = self.indexer.write_index()
2109
2110 self.deploy_dir_unlock()
2111
2112 if result is not None:
2113 bb.fatal(result)
2114
2115 def insert_feeds_uris(self):
2116 if self.feed_uris == "":
2117 return
2118
2119 sources_conf = os.path.join("%s/etc/apt/sources.list"
2120 % self.target_rootfs)
2121 arch_list = []
2122
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002123 if self.feed_archs is None:
2124 for arch in self.all_arch_list:
2125 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
2126 continue
2127 arch_list.append(arch)
2128 else:
2129 arch_list = self.feed_archs.split()
2130
2131 feed_uris = self.construct_uris(self.feed_uris.split(), self.feed_base_paths.split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002132
2133 with open(sources_conf, "w+") as sources_file:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002134 for uri in feed_uris:
2135 if arch_list:
2136 for arch in arch_list:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002137 bb.note('Adding dpkg channel at (%s)' % uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002138 sources_file.write("deb %s/%s ./\n" %
2139 (uri, arch))
2140 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002141 bb.note('Adding dpkg channel at (%s)' % uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002142 sources_file.write("deb %s ./\n" % uri)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002143
2144 def _create_configs(self, archs, base_archs):
2145 base_archs = re.sub("_", "-", base_archs)
2146
2147 if os.path.exists(self.apt_conf_dir):
2148 bb.utils.remove(self.apt_conf_dir, True)
2149
2150 bb.utils.mkdirhier(self.apt_conf_dir)
2151 bb.utils.mkdirhier(self.apt_conf_dir + "/lists/partial/")
2152 bb.utils.mkdirhier(self.apt_conf_dir + "/apt.conf.d/")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002153 bb.utils.mkdirhier(self.apt_conf_dir + "/preferences.d/")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002154
2155 arch_list = []
2156 for arch in self.all_arch_list:
2157 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
2158 continue
2159 arch_list.append(arch)
2160
2161 with open(os.path.join(self.apt_conf_dir, "preferences"), "w+") as prefs_file:
2162 priority = 801
2163 for arch in arch_list:
2164 prefs_file.write(
2165 "Package: *\n"
2166 "Pin: release l=%s\n"
2167 "Pin-Priority: %d\n\n" % (arch, priority))
2168
2169 priority += 5
2170
2171 pkg_exclude = self.d.getVar('PACKAGE_EXCLUDE', True) or ""
2172 for pkg in pkg_exclude.split():
2173 prefs_file.write(
2174 "Package: %s\n"
2175 "Pin: release *\n"
2176 "Pin-Priority: -1\n\n" % pkg)
2177
2178 arch_list.reverse()
2179
2180 with open(os.path.join(self.apt_conf_dir, "sources.list"), "w+") as sources_file:
2181 for arch in arch_list:
2182 sources_file.write("deb file:%s/ ./\n" %
2183 os.path.join(self.deploy_dir, arch))
2184
2185 base_arch_list = base_archs.split()
2186 multilib_variants = self.d.getVar("MULTILIB_VARIANTS", True);
2187 for variant in multilib_variants.split():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002188 localdata = bb.data.createCopy(self.d)
2189 variant_tune = localdata.getVar("DEFAULTTUNE_virtclass-multilib-" + variant, False)
2190 orig_arch = localdata.getVar("DPKG_ARCH", True)
2191 localdata.setVar("DEFAULTTUNE", variant_tune)
2192 bb.data.update_data(localdata)
2193 variant_arch = localdata.getVar("DPKG_ARCH", True)
2194 if variant_arch not in base_arch_list:
2195 base_arch_list.append(variant_arch)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002196
2197 with open(self.apt_conf_file, "w+") as apt_conf:
2198 with open(self.d.expand("${STAGING_ETCDIR_NATIVE}/apt/apt.conf.sample")) as apt_conf_sample:
2199 for line in apt_conf_sample.read().split("\n"):
2200 match_arch = re.match(" Architecture \".*\";$", line)
2201 architectures = ""
2202 if match_arch:
2203 for base_arch in base_arch_list:
2204 architectures += "\"%s\";" % base_arch
2205 apt_conf.write(" Architectures {%s};\n" % architectures);
2206 apt_conf.write(" Architecture \"%s\";\n" % base_archs)
2207 else:
2208 line = re.sub("#ROOTFS#", self.target_rootfs, line)
2209 line = re.sub("#APTCONF#", self.apt_conf_dir, line)
2210 apt_conf.write(line + "\n")
2211
2212 target_dpkg_dir = "%s/var/lib/dpkg" % self.target_rootfs
2213 bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "info"))
2214
2215 bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "updates"))
2216
2217 if not os.path.exists(os.path.join(target_dpkg_dir, "status")):
2218 open(os.path.join(target_dpkg_dir, "status"), "w+").close()
2219 if not os.path.exists(os.path.join(target_dpkg_dir, "available")):
2220 open(os.path.join(target_dpkg_dir, "available"), "w+").close()
2221
2222 def remove_packaging_data(self):
2223 bb.utils.remove(os.path.join(self.target_rootfs,
2224 self.d.getVar('opkglibdir', True)), True)
2225 bb.utils.remove(self.target_rootfs + "/var/lib/dpkg/", True)
2226
2227 def fix_broken_dependencies(self):
2228 os.environ['APT_CONFIG'] = self.apt_conf_file
2229
2230 cmd = "%s %s -f install" % (self.apt_get_cmd, self.apt_args)
2231
2232 try:
2233 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
2234 except subprocess.CalledProcessError as e:
2235 bb.fatal("Cannot fix broken dependencies. Command '%s' "
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002236 "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002237
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002238 def list_installed(self):
2239 return DpkgPkgsList(self.d, self.target_rootfs).list_pkgs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002240
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002241 """
2242 Returns a dictionary with the package info.
2243 """
2244 def package_info(self, pkg):
2245 cmd = "%s show %s" % (self.apt_cache_cmd, pkg)
2246 pkg_info = super(DpkgPM, self).package_info(pkg, cmd)
2247
2248 pkg_arch = pkg_info[pkg]["pkgarch"]
2249 pkg_filename = pkg_info[pkg]["filename"]
2250 pkg_info[pkg]["filepath"] = \
2251 os.path.join(self.deploy_dir, pkg_arch, pkg_filename)
2252
2253 return pkg_info
2254
2255 """
2256 Returns the path to a tmpdir where resides the contents of a package.
2257
2258 Deleting the tmpdir is responsability of the caller.
2259 """
2260 def extract(self, pkg):
2261 pkg_info = self.package_info(pkg)
2262 if not pkg_info:
2263 bb.fatal("Unable to get information for package '%s' while "
2264 "trying to extract the package." % pkg)
2265
2266 tmp_dir = super(DpkgPM, self).extract(pkg, pkg_info)
2267 bb.utils.remove(os.path.join(tmp_dir, "data.tar.xz"))
2268
2269 return tmp_dir
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002270
2271def generate_index_files(d):
2272 classes = d.getVar('PACKAGE_CLASSES', True).replace("package_", "").split()
2273
2274 indexer_map = {
2275 "rpm": (RpmIndexer, d.getVar('DEPLOY_DIR_RPM', True)),
2276 "ipk": (OpkgIndexer, d.getVar('DEPLOY_DIR_IPK', True)),
2277 "deb": (DpkgIndexer, d.getVar('DEPLOY_DIR_DEB', True))
2278 }
2279
2280 result = None
2281
2282 for pkg_class in classes:
2283 if not pkg_class in indexer_map:
2284 continue
2285
2286 if os.path.exists(indexer_map[pkg_class][1]):
2287 result = indexer_map[pkg_class][0](d, indexer_map[pkg_class][1]).write_index()
2288
2289 if result is not None:
2290 bb.fatal(result)
2291
2292if __name__ == "__main__":
2293 """
2294 We should be able to run this as a standalone script, from outside bitbake
2295 environment.
2296 """
2297 """
2298 TBD
2299 """