blob: 3b53fce4aeee06388c2b13246f6f72f8cfb3ca62 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001from abc import ABCMeta, abstractmethod
2from oe.utils import execute_pre_post_process
3from oe.package_manager import *
4from oe.manifest import *
5import oe.path
6import filecmp
7import shutil
8import os
9import subprocess
10import re
11
12
13class Rootfs(object):
14 """
15 This is an abstract class. Do not instantiate this directly.
16 """
17 __metaclass__ = ABCMeta
18
19 def __init__(self, d):
20 self.d = d
21 self.pm = None
22 self.image_rootfs = self.d.getVar('IMAGE_ROOTFS', True)
23 self.deploy_dir_image = self.d.getVar('DEPLOY_DIR_IMAGE', True)
24
25 self.install_order = Manifest.INSTALL_ORDER
26
27 @abstractmethod
28 def _create(self):
29 pass
30
31 @abstractmethod
32 def _get_delayed_postinsts(self):
33 pass
34
35 @abstractmethod
36 def _save_postinsts(self):
37 pass
38
39 @abstractmethod
40 def _log_check(self):
41 pass
42
43 def _log_check_warn(self):
44 r = re.compile('^(warn|Warn|NOTE: warn|NOTE: Warn|WARNING:)')
45 log_path = self.d.expand("${T}/log.do_rootfs")
46 with open(log_path, 'r') as log:
47 for line in log:
48 if 'log_check' in line or 'NOTE:' in line:
49 continue
50
51 m = r.search(line)
52 if m:
53 bb.warn('[log_check] %s: found a warning message in the logfile (keyword \'%s\'):\n[log_check] %s'
54 % (self.d.getVar('PN', True), m.group(), line))
55
56 def _log_check_error(self):
57 r = re.compile(self.log_check_regex)
58 log_path = self.d.expand("${T}/log.do_rootfs")
59 with open(log_path, 'r') as log:
60 found_error = 0
61 message = "\n"
62 for line in log:
63 if 'log_check' in line:
64 continue
65
66 m = r.search(line)
67 if m:
68 found_error = 1
69 bb.warn('[log_check] %s: found an error message in the logfile (keyword \'%s\'):\n[log_check] %s'
70 % (self.d.getVar('PN', True), m.group(), line))
71
72 if found_error >= 1 and found_error <= 5:
73 message += line + '\n'
74 found_error += 1
75
76 if found_error == 6:
77 bb.fatal(message)
78
79 def _insert_feed_uris(self):
80 if bb.utils.contains("IMAGE_FEATURES", "package-management",
81 True, False, self.d):
82 self.pm.insert_feeds_uris()
83
84 @abstractmethod
85 def _handle_intercept_failure(self, failed_script):
86 pass
87
88 """
89 The _cleanup() method should be used to clean-up stuff that we don't really
90 want to end up on target. For example, in the case of RPM, the DB locks.
91 The method is called, once, at the end of create() method.
92 """
93 @abstractmethod
94 def _cleanup(self):
95 pass
96
97 def _setup_dbg_rootfs(self, dirs):
98 gen_debugfs = self.d.getVar('IMAGE_GEN_DEBUGFS', True) or '0'
99 if gen_debugfs != '1':
100 return
101
102 bb.note(" Renaming the original rootfs...")
103 try:
104 shutil.rmtree(self.image_rootfs + '-orig')
105 except:
106 pass
107 os.rename(self.image_rootfs, self.image_rootfs + '-orig')
108
109 bb.note(" Creating debug rootfs...")
110 bb.utils.mkdirhier(self.image_rootfs)
111
112 bb.note(" Copying back package database...")
113 for dir in dirs:
114 bb.utils.mkdirhier(self.image_rootfs + os.path.dirname(dir))
115 shutil.copytree(self.image_rootfs + '-orig' + dir, self.image_rootfs + dir)
116
117 cpath = oe.cachedpath.CachedPath()
118 # Copy files located in /usr/lib/debug or /usr/src/debug
119 for dir in ["/usr/lib/debug", "/usr/src/debug"]:
120 src = self.image_rootfs + '-orig' + dir
121 if cpath.exists(src):
122 dst = self.image_rootfs + dir
123 bb.utils.mkdirhier(os.path.dirname(dst))
124 shutil.copytree(src, dst)
125
126 # Copy files with suffix '.debug' or located in '.debug' dir.
127 for root, dirs, files in cpath.walk(self.image_rootfs + '-orig'):
128 relative_dir = root[len(self.image_rootfs + '-orig'):]
129 for f in files:
130 if f.endswith('.debug') or '/.debug' in relative_dir:
131 bb.utils.mkdirhier(self.image_rootfs + relative_dir)
132 shutil.copy(os.path.join(root, f),
133 self.image_rootfs + relative_dir)
134
135 bb.note(" Install complementary '*-dbg' packages...")
136 self.pm.install_complementary('*-dbg')
137
138 bb.note(" Rename debug rootfs...")
139 try:
140 shutil.rmtree(self.image_rootfs + '-dbg')
141 except:
142 pass
143 os.rename(self.image_rootfs, self.image_rootfs + '-dbg')
144
145 bb.note(" Restoreing original rootfs...")
146 os.rename(self.image_rootfs + '-orig', self.image_rootfs)
147
148 def _exec_shell_cmd(self, cmd):
149 fakerootcmd = self.d.getVar('FAKEROOT', True)
150 if fakerootcmd is not None:
151 exec_cmd = [fakerootcmd, cmd]
152 else:
153 exec_cmd = cmd
154
155 try:
156 subprocess.check_output(exec_cmd, stderr=subprocess.STDOUT)
157 except subprocess.CalledProcessError as e:
158 return("Command '%s' returned %d:\n%s" % (e.cmd, e.returncode, e.output))
159
160 return None
161
162 def create(self):
163 bb.note("###### Generate rootfs #######")
164 pre_process_cmds = self.d.getVar("ROOTFS_PREPROCESS_COMMAND", True)
165 post_process_cmds = self.d.getVar("ROOTFS_POSTPROCESS_COMMAND", True)
166
167 postinst_intercepts_dir = self.d.getVar("POSTINST_INTERCEPTS_DIR", True)
168 if not postinst_intercepts_dir:
169 postinst_intercepts_dir = self.d.expand("${COREBASE}/scripts/postinst-intercepts")
170 intercepts_dir = os.path.join(self.d.getVar('WORKDIR', True),
171 "intercept_scripts")
172
173 bb.utils.remove(intercepts_dir, True)
174
175 bb.utils.mkdirhier(self.image_rootfs)
176
177 bb.utils.mkdirhier(self.deploy_dir_image)
178
179 shutil.copytree(postinst_intercepts_dir, intercepts_dir)
180
181 shutil.copy(self.d.expand("${COREBASE}/meta/files/deploydir_readme.txt"),
182 self.deploy_dir_image +
183 "/README_-_DO_NOT_DELETE_FILES_IN_THIS_DIRECTORY.txt")
184
185 execute_pre_post_process(self.d, pre_process_cmds)
186
187 # call the package manager dependent create method
188 self._create()
189
190 sysconfdir = self.image_rootfs + self.d.getVar('sysconfdir', True)
191 bb.utils.mkdirhier(sysconfdir)
192 with open(sysconfdir + "/version", "w+") as ver:
193 ver.write(self.d.getVar('BUILDNAME', True) + "\n")
194
195 self._run_intercepts()
196
197 execute_pre_post_process(self.d, post_process_cmds)
198
199 if bb.utils.contains("IMAGE_FEATURES", "read-only-rootfs",
200 True, False, self.d):
201 delayed_postinsts = self._get_delayed_postinsts()
202 if delayed_postinsts is not None:
203 bb.fatal("The following packages could not be configured "
204 "offline and rootfs is read-only: %s" %
205 delayed_postinsts)
206
207 if self.d.getVar('USE_DEVFS', True) != "1":
208 self._create_devfs()
209
210 self._uninstall_unneeded()
211
212 self._insert_feed_uris()
213
214 self._run_ldconfig()
215
216 if self.d.getVar('USE_DEPMOD', True) != "0":
217 self._generate_kernel_module_deps()
218
219 self._cleanup()
220 self._log_check()
221
222 def _uninstall_unneeded(self):
223 # Remove unneeded init script symlinks
224 delayed_postinsts = self._get_delayed_postinsts()
225 if delayed_postinsts is None:
226 if os.path.exists(self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/init.d/run-postinsts")):
227 self._exec_shell_cmd(["update-rc.d", "-f", "-r",
228 self.d.getVar('IMAGE_ROOTFS', True),
229 "run-postinsts", "remove"])
230
231 runtime_pkgmanage = bb.utils.contains("IMAGE_FEATURES", "package-management",
232 True, False, self.d)
233 sysvcompat_in_distro = bb.utils.contains("DISTRO_FEATURES", [ "systemd", "sysvinit" ],
234 True, False, self.d)
235 image_rorfs = bb.utils.contains("IMAGE_FEATURES", "read-only-rootfs",
236 True, False, self.d)
237 if sysvcompat_in_distro and not image_rorfs:
238 pkg_to_remove = ""
239 else:
240 pkg_to_remove = "update-rc.d"
241 if not runtime_pkgmanage:
242 # Remove components that we don't need if we're not going to install
243 # additional packages at runtime
244 if delayed_postinsts is None:
245 installed_pkgs_dir = self.d.expand('${WORKDIR}/installed_pkgs.txt')
246 pkgs_to_remove = list()
247 with open(installed_pkgs_dir, "r+") as installed_pkgs:
248 pkgs_installed = installed_pkgs.read().splitlines()
249 for pkg_installed in pkgs_installed[:]:
250 pkg = pkg_installed.split()[0]
251 if pkg in ["update-rc.d",
252 "base-passwd",
253 "shadow",
254 "update-alternatives", pkg_to_remove,
255 self.d.getVar("ROOTFS_BOOTSTRAP_INSTALL", True)
256 ]:
257 pkgs_to_remove.append(pkg)
258 pkgs_installed.remove(pkg_installed)
259
260 if len(pkgs_to_remove) > 0:
261 self.pm.remove(pkgs_to_remove, False)
262 # Update installed_pkgs.txt
263 open(installed_pkgs_dir, "w+").write('\n'.join(pkgs_installed))
264
265 else:
266 self._save_postinsts()
267
268 post_uninstall_cmds = self.d.getVar("ROOTFS_POSTUNINSTALL_COMMAND", True)
269 execute_pre_post_process(self.d, post_uninstall_cmds)
270
271 if not runtime_pkgmanage:
272 # Remove the package manager data files
273 self.pm.remove_packaging_data()
274
275 def _run_intercepts(self):
276 intercepts_dir = os.path.join(self.d.getVar('WORKDIR', True),
277 "intercept_scripts")
278
279 bb.note("Running intercept scripts:")
280 os.environ['D'] = self.image_rootfs
281 for script in os.listdir(intercepts_dir):
282 script_full = os.path.join(intercepts_dir, script)
283
284 if script == "postinst_intercept" or not os.access(script_full, os.X_OK):
285 continue
286
287 bb.note("> Executing %s intercept ..." % script)
288
289 try:
290 subprocess.check_call(script_full)
291 except subprocess.CalledProcessError as e:
292 bb.warn("The postinstall intercept hook '%s' failed (exit code: %d)! See log for details!" %
293 (script, e.returncode))
294
295 with open(script_full) as intercept:
296 registered_pkgs = None
297 for line in intercept.read().split("\n"):
298 m = re.match("^##PKGS:(.*)", line)
299 if m is not None:
300 registered_pkgs = m.group(1).strip()
301 break
302
303 if registered_pkgs is not None:
304 bb.warn("The postinstalls for the following packages "
305 "will be postponed for first boot: %s" %
306 registered_pkgs)
307
308 # call the backend dependent handler
309 self._handle_intercept_failure(registered_pkgs)
310
311 def _run_ldconfig(self):
312 if self.d.getVar('LDCONFIGDEPEND', True):
313 bb.note("Executing: ldconfig -r" + self.image_rootfs + "-c new -v")
314 self._exec_shell_cmd(['ldconfig', '-r', self.image_rootfs, '-c',
315 'new', '-v'])
316
317 def _check_for_kernel_modules(self, modules_dir):
318 for root, dirs, files in os.walk(modules_dir, topdown=True):
319 for name in files:
320 found_ko = name.endswith(".ko")
321 if found_ko:
322 return found_ko
323 return False
324
325 def _generate_kernel_module_deps(self):
326 modules_dir = os.path.join(self.image_rootfs, 'lib', 'modules')
327 # if we don't have any modules don't bother to do the depmod
328 if not self._check_for_kernel_modules(modules_dir):
329 bb.note("No Kernel Modules found, not running depmod")
330 return
331
332 kernel_abi_ver_file = oe.path.join(self.d.getVar('PKGDATA_DIR', True), "kernel-depmod",
333 'kernel-abiversion')
334 if not os.path.exists(kernel_abi_ver_file):
335 bb.fatal("No kernel-abiversion file found (%s), cannot run depmod, aborting" % kernel_abi_ver_file)
336
337 kernel_ver = open(kernel_abi_ver_file).read().strip(' \n')
338 versioned_modules_dir = os.path.join(self.image_rootfs, modules_dir, kernel_ver)
339
340 bb.utils.mkdirhier(versioned_modules_dir)
341
342 self._exec_shell_cmd(['depmodwrapper', '-a', '-b', self.image_rootfs, kernel_ver])
343
344 """
345 Create devfs:
346 * IMAGE_DEVICE_TABLE is the old name to an absolute path to a device table file
347 * IMAGE_DEVICE_TABLES is a new name for a file, or list of files, seached
348 for in the BBPATH
349 If neither are specified then the default name of files/device_table-minimal.txt
350 is searched for in the BBPATH (same as the old version.)
351 """
352 def _create_devfs(self):
353 devtable_list = []
354 devtable = self.d.getVar('IMAGE_DEVICE_TABLE', True)
355 if devtable is not None:
356 devtable_list.append(devtable)
357 else:
358 devtables = self.d.getVar('IMAGE_DEVICE_TABLES', True)
359 if devtables is None:
360 devtables = 'files/device_table-minimal.txt'
361 for devtable in devtables.split():
362 devtable_list.append("%s" % bb.utils.which(self.d.getVar('BBPATH', True), devtable))
363
364 for devtable in devtable_list:
365 self._exec_shell_cmd(["makedevs", "-r",
366 self.image_rootfs, "-D", devtable])
367
368
369class RpmRootfs(Rootfs):
370 def __init__(self, d, manifest_dir):
371 super(RpmRootfs, self).__init__(d)
372 self.log_check_regex = '(unpacking of archive failed|Cannot find package'\
373 '|exit 1|ERROR: |Error: |Error |ERROR '\
374 '|Failed |Failed: |Failed$|Failed\(\d+\):)'
375 self.manifest = RpmManifest(d, manifest_dir)
376
377 self.pm = RpmPM(d,
378 d.getVar('IMAGE_ROOTFS', True),
379 self.d.getVar('TARGET_VENDOR', True)
380 )
381
382 self.inc_rpm_image_gen = self.d.getVar('INC_RPM_IMAGE_GEN', True)
383 if self.inc_rpm_image_gen != "1":
384 bb.utils.remove(self.image_rootfs, True)
385 else:
386 self.pm.recovery_packaging_data()
387 bb.utils.remove(self.d.getVar('MULTILIB_TEMP_ROOTFS', True), True)
388
389 self.pm.create_configs()
390
391 '''
392 While rpm incremental image generation is enabled, it will remove the
393 unneeded pkgs by comparing the new install solution manifest and the
394 old installed manifest.
395 '''
396 def _create_incremental(self, pkgs_initial_install):
397 if self.inc_rpm_image_gen == "1":
398
399 pkgs_to_install = list()
400 for pkg_type in pkgs_initial_install:
401 pkgs_to_install += pkgs_initial_install[pkg_type]
402
403 installed_manifest = self.pm.load_old_install_solution()
404 solution_manifest = self.pm.dump_install_solution(pkgs_to_install)
405
406 pkg_to_remove = list()
407 for pkg in installed_manifest:
408 if pkg not in solution_manifest:
409 pkg_to_remove.append(pkg)
410
411 self.pm.update()
412
413 bb.note('incremental update -- upgrade packages in place ')
414 self.pm.upgrade()
415 if pkg_to_remove != []:
416 bb.note('incremental removed: %s' % ' '.join(pkg_to_remove))
417 self.pm.remove(pkg_to_remove)
418
419 def _create(self):
420 pkgs_to_install = self.manifest.parse_initial_manifest()
421 rpm_pre_process_cmds = self.d.getVar('RPM_PREPROCESS_COMMANDS', True)
422 rpm_post_process_cmds = self.d.getVar('RPM_POSTPROCESS_COMMANDS', True)
423
424 # update PM index files
425 self.pm.write_index()
426
427 execute_pre_post_process(self.d, rpm_pre_process_cmds)
428
429 self.pm.dump_all_available_pkgs()
430
431 if self.inc_rpm_image_gen == "1":
432 self._create_incremental(pkgs_to_install)
433
434 self.pm.update()
435
436 pkgs = []
437 pkgs_attempt = []
438 for pkg_type in pkgs_to_install:
439 if pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY:
440 pkgs_attempt += pkgs_to_install[pkg_type]
441 else:
442 pkgs += pkgs_to_install[pkg_type]
443
444 self.pm.install(pkgs)
445
446 self.pm.install(pkgs_attempt, True)
447
448 self.pm.install_complementary()
449
450 self._setup_dbg_rootfs(['/etc/rpm', '/var/lib/rpm', '/var/lib/smart'])
451
452 execute_pre_post_process(self.d, rpm_post_process_cmds)
453
454 self._log_check()
455
456 if self.inc_rpm_image_gen == "1":
457 self.pm.backup_packaging_data()
458
459 self.pm.rpm_setup_smart_target_config()
460
461 @staticmethod
462 def _depends_list():
463 return ['DEPLOY_DIR_RPM', 'INC_RPM_IMAGE_GEN', 'RPM_PREPROCESS_COMMANDS',
464 'RPM_POSTPROCESS_COMMANDS', 'RPM_PREFER_ELF_ARCH']
465
466 def _get_delayed_postinsts(self):
467 postinst_dir = self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/rpm-postinsts")
468 if os.path.isdir(postinst_dir):
469 files = os.listdir(postinst_dir)
470 for f in files:
471 bb.note('Delayed package scriptlet: %s' % f)
472 return files
473
474 return None
475
476 def _save_postinsts(self):
477 # this is just a stub. For RPM, the failed postinstalls are
478 # already saved in /etc/rpm-postinsts
479 pass
480
481 def _log_check_error(self):
482 r = re.compile('(unpacking of archive failed|Cannot find package|exit 1|ERR|Fail)')
483 log_path = self.d.expand("${T}/log.do_rootfs")
484 with open(log_path, 'r') as log:
485 found_error = 0
486 message = "\n"
487 for line in log.read().split('\n'):
488 if 'log_check' in line:
489 continue
490 # sh -x may emit code which isn't actually executed
491 if line.startswith('+'):
492 continue
493
494 m = r.search(line)
495 if m:
496 found_error = 1
497 bb.warn('log_check: There were error messages in the logfile')
498 bb.warn('log_check: Matched keyword: [%s]\n\n' % m.group())
499
500 if found_error >= 1 and found_error <= 5:
501 message += line + '\n'
502 found_error += 1
503
504 if found_error == 6:
505 bb.fatal(message)
506
507 def _log_check(self):
508 self._log_check_warn()
509 self._log_check_error()
510
511 def _handle_intercept_failure(self, registered_pkgs):
512 rpm_postinsts_dir = self.image_rootfs + self.d.expand('${sysconfdir}/rpm-postinsts/')
513 bb.utils.mkdirhier(rpm_postinsts_dir)
514
515 # Save the package postinstalls in /etc/rpm-postinsts
516 for pkg in registered_pkgs.split():
517 self.pm.save_rpmpostinst(pkg)
518
519 def _cleanup(self):
520 # during the execution of postprocess commands, rpm is called several
521 # times to get the files installed, dependencies, etc. This creates the
522 # __db.00* (Berkeley DB files that hold locks, rpm specific environment
523 # settings, etc.), that should not get into the final rootfs
524 self.pm.unlock_rpm_db()
525 if os.path.isdir(self.pm.install_dir_path + "/tmp") and not os.listdir(self.pm.install_dir_path + "/tmp"):
526 bb.utils.remove(self.pm.install_dir_path + "/tmp", True)
527 if os.path.isdir(self.pm.install_dir_path) and not os.listdir(self.pm.install_dir_path):
528 bb.utils.remove(self.pm.install_dir_path, True)
529
530class DpkgOpkgRootfs(Rootfs):
531 def __init__(self, d):
532 super(DpkgOpkgRootfs, self).__init__(d)
533
534 def _get_pkgs_postinsts(self, status_file):
535 def _get_pkg_depends_list(pkg_depends):
536 pkg_depends_list = []
537 # filter version requirements like libc (>= 1.1)
538 for dep in pkg_depends.split(', '):
539 m_dep = re.match("^(.*) \(.*\)$", dep)
540 if m_dep:
541 dep = m_dep.group(1)
542 pkg_depends_list.append(dep)
543
544 return pkg_depends_list
545
546 pkgs = {}
547 pkg_name = ""
548 pkg_status_match = False
549 pkg_depends = ""
550
551 with open(status_file) as status:
552 data = status.read()
553 status.close()
554 for line in data.split('\n'):
555 m_pkg = re.match("^Package: (.*)", line)
556 m_status = re.match("^Status:.*unpacked", line)
557 m_depends = re.match("^Depends: (.*)", line)
558
559 if m_pkg is not None:
560 if pkg_name and pkg_status_match:
561 pkgs[pkg_name] = _get_pkg_depends_list(pkg_depends)
562
563 pkg_name = m_pkg.group(1)
564 pkg_status_match = False
565 pkg_depends = ""
566 elif m_status is not None:
567 pkg_status_match = True
568 elif m_depends is not None:
569 pkg_depends = m_depends.group(1)
570
571 # remove package dependencies not in postinsts
572 pkg_names = pkgs.keys()
573 for pkg_name in pkg_names:
574 deps = pkgs[pkg_name][:]
575
576 for d in deps:
577 if d not in pkg_names:
578 pkgs[pkg_name].remove(d)
579
580 return pkgs
581
582 def _get_delayed_postinsts_common(self, status_file):
583 def _dep_resolve(graph, node, resolved, seen):
584 seen.append(node)
585
586 for edge in graph[node]:
587 if edge not in resolved:
588 if edge in seen:
589 raise RuntimeError("Packages %s and %s have " \
590 "a circular dependency in postinsts scripts." \
591 % (node, edge))
592 _dep_resolve(graph, edge, resolved, seen)
593
594 resolved.append(node)
595
596 pkg_list = []
597
598 pkgs = self._get_pkgs_postinsts(status_file)
599 if pkgs:
600 root = "__packagegroup_postinst__"
601 pkgs[root] = pkgs.keys()
602 _dep_resolve(pkgs, root, pkg_list, [])
603 pkg_list.remove(root)
604
605 if len(pkg_list) == 0:
606 return None
607
608 return pkg_list
609
610 def _save_postinsts_common(self, dst_postinst_dir, src_postinst_dir):
611 num = 0
612 for p in self._get_delayed_postinsts():
613 bb.utils.mkdirhier(dst_postinst_dir)
614
615 if os.path.exists(os.path.join(src_postinst_dir, p + ".postinst")):
616 shutil.copy(os.path.join(src_postinst_dir, p + ".postinst"),
617 os.path.join(dst_postinst_dir, "%03d-%s" % (num, p)))
618
619 num += 1
620
621class DpkgRootfs(DpkgOpkgRootfs):
622 def __init__(self, d, manifest_dir):
623 super(DpkgRootfs, self).__init__(d)
624 self.log_check_regex = '^E:'
625
626 bb.utils.remove(self.image_rootfs, True)
627 bb.utils.remove(self.d.getVar('MULTILIB_TEMP_ROOTFS', True), True)
628 self.manifest = DpkgManifest(d, manifest_dir)
629 self.pm = DpkgPM(d, d.getVar('IMAGE_ROOTFS', True),
630 d.getVar('PACKAGE_ARCHS', True),
631 d.getVar('DPKG_ARCH', True))
632
633
634 def _create(self):
635 pkgs_to_install = self.manifest.parse_initial_manifest()
636 deb_pre_process_cmds = self.d.getVar('DEB_PREPROCESS_COMMANDS', True)
637 deb_post_process_cmds = self.d.getVar('DEB_POSTPROCESS_COMMANDS', True)
638
639 alt_dir = self.d.expand("${IMAGE_ROOTFS}/var/lib/dpkg/alternatives")
640 bb.utils.mkdirhier(alt_dir)
641
642 # update PM index files
643 self.pm.write_index()
644
645 execute_pre_post_process(self.d, deb_pre_process_cmds)
646
647 self.pm.update()
648
649 for pkg_type in self.install_order:
650 if pkg_type in pkgs_to_install:
651 self.pm.install(pkgs_to_install[pkg_type],
652 [False, True][pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY])
653
654 self.pm.install_complementary()
655
656 self._setup_dbg_rootfs(['/var/lib/dpkg'])
657
658 self.pm.fix_broken_dependencies()
659
660 self.pm.mark_packages("installed")
661
662 self.pm.run_pre_post_installs()
663
664 execute_pre_post_process(self.d, deb_post_process_cmds)
665
666 @staticmethod
667 def _depends_list():
668 return ['DEPLOY_DIR_DEB', 'DEB_SDK_ARCH', 'APTCONF_TARGET', 'APT_ARGS', 'DPKG_ARCH', 'DEB_PREPROCESS_COMMANDS', 'DEB_POSTPROCESS_COMMANDS']
669
670 def _get_delayed_postinsts(self):
671 status_file = self.image_rootfs + "/var/lib/dpkg/status"
672 return self._get_delayed_postinsts_common(status_file)
673
674 def _save_postinsts(self):
675 dst_postinst_dir = self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/deb-postinsts")
676 src_postinst_dir = self.d.expand("${IMAGE_ROOTFS}/var/lib/dpkg/info")
677 return self._save_postinsts_common(dst_postinst_dir, src_postinst_dir)
678
679 def _handle_intercept_failure(self, registered_pkgs):
680 self.pm.mark_packages("unpacked", registered_pkgs.split())
681
682 def _log_check(self):
683 self._log_check_warn()
684 self._log_check_error()
685
686 def _cleanup(self):
687 pass
688
689
690class OpkgRootfs(DpkgOpkgRootfs):
691 def __init__(self, d, manifest_dir):
692 super(OpkgRootfs, self).__init__(d)
693 self.log_check_regex = '(exit 1|Collected errors)'
694
695 self.manifest = OpkgManifest(d, manifest_dir)
696 self.opkg_conf = self.d.getVar("IPKGCONF_TARGET", True)
697 self.pkg_archs = self.d.getVar("ALL_MULTILIB_PACKAGE_ARCHS", True)
698
699 self.inc_opkg_image_gen = self.d.getVar('INC_IPK_IMAGE_GEN', True) or ""
700 if self._remove_old_rootfs():
701 bb.utils.remove(self.image_rootfs, True)
702 self.pm = OpkgPM(d,
703 self.image_rootfs,
704 self.opkg_conf,
705 self.pkg_archs)
706 else:
707 self.pm = OpkgPM(d,
708 self.image_rootfs,
709 self.opkg_conf,
710 self.pkg_archs)
711 self.pm.recover_packaging_data()
712
713 bb.utils.remove(self.d.getVar('MULTILIB_TEMP_ROOTFS', True), True)
714
715 def _prelink_file(self, root_dir, filename):
716 bb.note('prelink %s in %s' % (filename, root_dir))
717 prelink_cfg = oe.path.join(root_dir,
718 self.d.expand('${sysconfdir}/prelink.conf'))
719 if not os.path.exists(prelink_cfg):
720 shutil.copy(self.d.expand('${STAGING_DIR_NATIVE}${sysconfdir_native}/prelink.conf'),
721 prelink_cfg)
722
723 cmd_prelink = self.d.expand('${STAGING_DIR_NATIVE}${sbindir_native}/prelink')
724 self._exec_shell_cmd([cmd_prelink,
725 '--root',
726 root_dir,
727 '-amR',
728 '-N',
729 '-c',
730 self.d.expand('${sysconfdir}/prelink.conf')])
731
732 '''
733 Compare two files with the same key twice to see if they are equal.
734 If they are not equal, it means they are duplicated and come from
735 different packages.
736 1st: Comapre them directly;
737 2nd: While incremental image creation is enabled, one of the
738 files could be probaly prelinked in the previous image
739 creation and the file has been changed, so we need to
740 prelink the other one and compare them.
741 '''
742 def _file_equal(self, key, f1, f2):
743
744 # Both of them are not prelinked
745 if filecmp.cmp(f1, f2):
746 return True
747
748 if self.image_rootfs not in f1:
749 self._prelink_file(f1.replace(key, ''), f1)
750
751 if self.image_rootfs not in f2:
752 self._prelink_file(f2.replace(key, ''), f2)
753
754 # Both of them are prelinked
755 if filecmp.cmp(f1, f2):
756 return True
757
758 # Not equal
759 return False
760
761 """
762 This function was reused from the old implementation.
763 See commit: "image.bbclass: Added variables for multilib support." by
764 Lianhao Lu.
765 """
766 def _multilib_sanity_test(self, dirs):
767
768 allow_replace = self.d.getVar("MULTILIBRE_ALLOW_REP", True)
769 if allow_replace is None:
770 allow_replace = ""
771
772 allow_rep = re.compile(re.sub("\|$", "", allow_replace))
773 error_prompt = "Multilib check error:"
774
775 files = {}
776 for dir in dirs:
777 for root, subfolders, subfiles in os.walk(dir):
778 for file in subfiles:
779 item = os.path.join(root, file)
780 key = str(os.path.join("/", os.path.relpath(item, dir)))
781
782 valid = True
783 if key in files:
784 #check whether the file is allow to replace
785 if allow_rep.match(key):
786 valid = True
787 else:
788 if os.path.exists(files[key]) and \
789 os.path.exists(item) and \
790 not self._file_equal(key, files[key], item):
791 valid = False
792 bb.fatal("%s duplicate files %s %s is not the same\n" %
793 (error_prompt, item, files[key]))
794
795 #pass the check, add to list
796 if valid:
797 files[key] = item
798
799 def _multilib_test_install(self, pkgs):
800 ml_temp = self.d.getVar("MULTILIB_TEMP_ROOTFS", True)
801 bb.utils.mkdirhier(ml_temp)
802
803 dirs = [self.image_rootfs]
804
805 for variant in self.d.getVar("MULTILIB_VARIANTS", True).split():
806 ml_target_rootfs = os.path.join(ml_temp, variant)
807
808 bb.utils.remove(ml_target_rootfs, True)
809
810 ml_opkg_conf = os.path.join(ml_temp,
811 variant + "-" + os.path.basename(self.opkg_conf))
812
813 ml_pm = OpkgPM(self.d, ml_target_rootfs, ml_opkg_conf, self.pkg_archs)
814
815 ml_pm.update()
816 ml_pm.install(pkgs)
817
818 dirs.append(ml_target_rootfs)
819
820 self._multilib_sanity_test(dirs)
821
822 '''
823 While ipk incremental image generation is enabled, it will remove the
824 unneeded pkgs by comparing the old full manifest in previous existing
825 image and the new full manifest in the current image.
826 '''
827 def _remove_extra_packages(self, pkgs_initial_install):
828 if self.inc_opkg_image_gen == "1":
829 # Parse full manifest in previous existing image creation session
830 old_full_manifest = self.manifest.parse_full_manifest()
831
832 # Create full manifest for the current image session, the old one
833 # will be replaced by the new one.
834 self.manifest.create_full(self.pm)
835
836 # Parse full manifest in current image creation session
837 new_full_manifest = self.manifest.parse_full_manifest()
838
839 pkg_to_remove = list()
840 for pkg in old_full_manifest:
841 if pkg not in new_full_manifest:
842 pkg_to_remove.append(pkg)
843
844 if pkg_to_remove != []:
845 bb.note('decremental removed: %s' % ' '.join(pkg_to_remove))
846 self.pm.remove(pkg_to_remove)
847
848 '''
849 Compare with previous existing image creation, if some conditions
850 triggered, the previous old image should be removed.
851 The conditions include any of 'PACKAGE_EXCLUDE, NO_RECOMMENDATIONS
852 and BAD_RECOMMENDATIONS' has been changed.
853 '''
854 def _remove_old_rootfs(self):
855 if self.inc_opkg_image_gen != "1":
856 return True
857
858 vars_list_file = self.d.expand('${T}/vars_list')
859
860 old_vars_list = ""
861 if os.path.exists(vars_list_file):
862 old_vars_list = open(vars_list_file, 'r+').read()
863
864 new_vars_list = '%s:%s:%s\n' % \
865 ((self.d.getVar('BAD_RECOMMENDATIONS', True) or '').strip(),
866 (self.d.getVar('NO_RECOMMENDATIONS', True) or '').strip(),
867 (self.d.getVar('PACKAGE_EXCLUDE', True) or '').strip())
868 open(vars_list_file, 'w+').write(new_vars_list)
869
870 if old_vars_list != new_vars_list:
871 return True
872
873 return False
874
875 def _create(self):
876 pkgs_to_install = self.manifest.parse_initial_manifest()
877 opkg_pre_process_cmds = self.d.getVar('OPKG_PREPROCESS_COMMANDS', True)
878 opkg_post_process_cmds = self.d.getVar('OPKG_POSTPROCESS_COMMANDS', True)
879 rootfs_post_install_cmds = self.d.getVar('ROOTFS_POSTINSTALL_COMMAND', True)
880
881 # update PM index files, unless users provide their own feeds
882 if (self.d.getVar('BUILD_IMAGES_FROM_FEEDS', True) or "") != "1":
883 self.pm.write_index()
884
885 execute_pre_post_process(self.d, opkg_pre_process_cmds)
886
887 self.pm.update()
888
889 self.pm.handle_bad_recommendations()
890
891 if self.inc_opkg_image_gen == "1":
892 self._remove_extra_packages(pkgs_to_install)
893
894 for pkg_type in self.install_order:
895 if pkg_type in pkgs_to_install:
896 # For multilib, we perform a sanity test before final install
897 # If sanity test fails, it will automatically do a bb.fatal()
898 # and the installation will stop
899 if pkg_type == Manifest.PKG_TYPE_MULTILIB:
900 self._multilib_test_install(pkgs_to_install[pkg_type])
901
902 self.pm.install(pkgs_to_install[pkg_type],
903 [False, True][pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY])
904
905 self.pm.install_complementary()
906
907 self._setup_dbg_rootfs(['/var/lib/opkg'])
908
909 execute_pre_post_process(self.d, opkg_post_process_cmds)
910 execute_pre_post_process(self.d, rootfs_post_install_cmds)
911
912 if self.inc_opkg_image_gen == "1":
913 self.pm.backup_packaging_data()
914
915 @staticmethod
916 def _depends_list():
917 return ['IPKGCONF_SDK', 'IPK_FEED_URIS', 'DEPLOY_DIR_IPK', 'IPKGCONF_TARGET', 'INC_IPK_IMAGE_GEN', 'OPKG_ARGS', 'OPKGLIBDIR', 'OPKG_PREPROCESS_COMMANDS', 'OPKG_POSTPROCESS_COMMANDS', 'OPKGLIBDIR']
918
919 def _get_delayed_postinsts(self):
920 status_file = os.path.join(self.image_rootfs,
921 self.d.getVar('OPKGLIBDIR', True).strip('/'),
922 "opkg", "status")
923 return self._get_delayed_postinsts_common(status_file)
924
925 def _save_postinsts(self):
926 dst_postinst_dir = self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/ipk-postinsts")
927 src_postinst_dir = self.d.expand("${IMAGE_ROOTFS}${OPKGLIBDIR}/opkg/info")
928 return self._save_postinsts_common(dst_postinst_dir, src_postinst_dir)
929
930 def _handle_intercept_failure(self, registered_pkgs):
931 self.pm.mark_packages("unpacked", registered_pkgs.split())
932
933 def _log_check(self):
934 self._log_check_warn()
935 self._log_check_error()
936
937 def _cleanup(self):
938 pass
939
940def get_class_for_type(imgtype):
941 return {"rpm": RpmRootfs,
942 "ipk": OpkgRootfs,
943 "deb": DpkgRootfs}[imgtype]
944
945def variable_depends(d, manifest_dir=None):
946 img_type = d.getVar('IMAGE_PKGTYPE', True)
947 cls = get_class_for_type(img_type)
948 return cls._depends_list()
949
950def create_rootfs(d, manifest_dir=None):
951 env_bkp = os.environ.copy()
952
953 img_type = d.getVar('IMAGE_PKGTYPE', True)
954 if img_type == "rpm":
955 RpmRootfs(d, manifest_dir).create()
956 elif img_type == "ipk":
957 OpkgRootfs(d, manifest_dir).create()
958 elif img_type == "deb":
959 DpkgRootfs(d, manifest_dir).create()
960
961 os.environ.clear()
962 os.environ.update(env_bkp)
963
964
965def image_list_installed_packages(d, format=None, rootfs_dir=None):
966 if not rootfs_dir:
967 rootfs_dir = d.getVar('IMAGE_ROOTFS', True)
968
969 img_type = d.getVar('IMAGE_PKGTYPE', True)
970 if img_type == "rpm":
971 return RpmPkgsList(d, rootfs_dir).list(format)
972 elif img_type == "ipk":
973 return OpkgPkgsList(d, rootfs_dir, d.getVar("IPKGCONF_TARGET", True)).list(format)
974 elif img_type == "deb":
975 return DpkgPkgsList(d, rootfs_dir).list(format)
976
977if __name__ == "__main__":
978 """
979 We should be able to run this as a standalone script, from outside bitbake
980 environment.
981 """
982 """
983 TBD
984 """