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