1# 2# SPDX-License-Identifier: GPL-2.0-only 3# 4from abc import ABCMeta, abstractmethod 5from oe.utils import execute_pre_post_process 6from oe.package_manager import * 7from oe.manifest import * 8import oe.path 9import filecmp 10import shutil 11import os 12import subprocess 13import re 14 15 16class Rootfs(object, metaclass=ABCMeta): 17 """ 18 This is an abstract class. Do not instantiate this directly. 19 """ 20 21 def __init__(self, d, progress_reporter=None, logcatcher=None): 22 self.d = d 23 self.pm = None 24 self.image_rootfs = self.d.getVar('IMAGE_ROOTFS') 25 self.deploydir = self.d.getVar('IMGDEPLOYDIR') 26 self.progress_reporter = progress_reporter 27 self.logcatcher = logcatcher 28 29 self.install_order = Manifest.INSTALL_ORDER 30 31 @abstractmethod 32 def _create(self): 33 pass 34 35 @abstractmethod 36 def _get_delayed_postinsts(self): 37 pass 38 39 @abstractmethod 40 def _save_postinsts(self): 41 pass 42 43 @abstractmethod 44 def _log_check(self): 45 pass 46 47 def _log_check_common(self, type, match): 48 # Ignore any lines containing log_check to avoid recursion, and ignore 49 # lines beginning with a + since sh -x may emit code which isn't 50 # actually executed, but may contain error messages 51 excludes = [ 'log_check', r'^\+' ] 52 if hasattr(self, 'log_check_expected_regexes'): 53 excludes.extend(self.log_check_expected_regexes) 54 excludes = [re.compile(x) for x in excludes] 55 r = re.compile(match) 56 log_path = self.d.expand("${T}/log.do_rootfs") 57 messages = [] 58 with open(log_path, 'r') as log: 59 for line in log: 60 if self.logcatcher and self.logcatcher.contains(line.rstrip()): 61 continue 62 for ee in excludes: 63 m = ee.search(line) 64 if m: 65 break 66 if m: 67 continue 68 69 m = r.search(line) 70 if m: 71 messages.append('[log_check] %s' % line) 72 if messages: 73 if len(messages) == 1: 74 msg = '1 %s message' % type 75 else: 76 msg = '%d %s messages' % (len(messages), type) 77 msg = '[log_check] %s: found %s in the logfile:\n%s' % \ 78 (self.d.getVar('PN'), msg, ''.join(messages)) 79 if type == 'error': 80 bb.fatal(msg) 81 else: 82 bb.warn(msg) 83 84 def _log_check_warn(self): 85 self._log_check_common('warning', '^(warn|Warn|WARNING:)') 86 87 def _log_check_error(self): 88 self._log_check_common('error', self.log_check_regex) 89 90 def _insert_feed_uris(self): 91 if bb.utils.contains("IMAGE_FEATURES", "package-management", 92 True, False, self.d): 93 self.pm.insert_feeds_uris(self.d.getVar('PACKAGE_FEED_URIS') or "", 94 self.d.getVar('PACKAGE_FEED_BASE_PATHS') or "", 95 self.d.getVar('PACKAGE_FEED_ARCHS')) 96 97 98 """ 99 The _cleanup() method should be used to clean-up stuff that we don't really 100 want to end up on target. For example, in the case of RPM, the DB locks. 101 The method is called, once, at the end of create() method. 102 """ 103 @abstractmethod 104 def _cleanup(self): 105 pass 106 107 def _setup_dbg_rootfs(self, dirs): 108 gen_debugfs = self.d.getVar('IMAGE_GEN_DEBUGFS') or '0' 109 if gen_debugfs != '1': 110 return 111 112 bb.note(" Renaming the original rootfs...") 113 try: 114 shutil.rmtree(self.image_rootfs + '-orig') 115 except: 116 pass 117 os.rename(self.image_rootfs, self.image_rootfs + '-orig') 118 119 bb.note(" Creating debug rootfs...") 120 bb.utils.mkdirhier(self.image_rootfs) 121 122 bb.note(" Copying back package database...") 123 for dir in dirs: 124 if not os.path.isdir(self.image_rootfs + '-orig' + dir): 125 continue 126 bb.utils.mkdirhier(self.image_rootfs + os.path.dirname(dir)) 127 shutil.copytree(self.image_rootfs + '-orig' + dir, self.image_rootfs + dir, symlinks=True) 128 129 # Copy files located in /usr/lib/debug or /usr/src/debug 130 for dir in ["/usr/lib/debug", "/usr/src/debug"]: 131 src = self.image_rootfs + '-orig' + dir 132 if os.path.exists(src): 133 dst = self.image_rootfs + dir 134 bb.utils.mkdirhier(os.path.dirname(dst)) 135 shutil.copytree(src, dst) 136 137 # Copy files with suffix '.debug' or located in '.debug' dir. 138 for root, dirs, files in os.walk(self.image_rootfs + '-orig'): 139 relative_dir = root[len(self.image_rootfs + '-orig'):] 140 for f in files: 141 if f.endswith('.debug') or '/.debug' in relative_dir: 142 bb.utils.mkdirhier(self.image_rootfs + relative_dir) 143 shutil.copy(os.path.join(root, f), 144 self.image_rootfs + relative_dir) 145 146 bb.note(" Install complementary '*-dbg' packages...") 147 self.pm.install_complementary('*-dbg') 148 149 if self.d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-with-srcpkg': 150 bb.note(" Install complementary '*-src' packages...") 151 self.pm.install_complementary('*-src') 152 153 """ 154 Install additional debug packages. Possibility to install additional packages, 155 which are not automatically installed as complementary package of 156 standard one, e.g. debug package of static libraries. 157 """ 158 extra_debug_pkgs = self.d.getVar('IMAGE_INSTALL_DEBUGFS') 159 if extra_debug_pkgs: 160 bb.note(" Install extra debug packages...") 161 self.pm.install(extra_debug_pkgs.split(), True) 162 163 bb.note(" Rename debug rootfs...") 164 try: 165 shutil.rmtree(self.image_rootfs + '-dbg') 166 except: 167 pass 168 os.rename(self.image_rootfs, self.image_rootfs + '-dbg') 169 170 bb.note(" Restoreing original rootfs...") 171 os.rename(self.image_rootfs + '-orig', self.image_rootfs) 172 173 def _exec_shell_cmd(self, cmd): 174 fakerootcmd = self.d.getVar('FAKEROOT') 175 if fakerootcmd is not None: 176 exec_cmd = [fakerootcmd, cmd] 177 else: 178 exec_cmd = cmd 179 180 try: 181 subprocess.check_output(exec_cmd, stderr=subprocess.STDOUT) 182 except subprocess.CalledProcessError as e: 183 return("Command '%s' returned %d:\n%s" % (e.cmd, e.returncode, e.output)) 184 185 return None 186 187 def create(self): 188 bb.note("###### Generate rootfs #######") 189 pre_process_cmds = self.d.getVar("ROOTFS_PREPROCESS_COMMAND") 190 post_process_cmds = self.d.getVar("ROOTFS_POSTPROCESS_COMMAND") 191 rootfs_post_install_cmds = self.d.getVar('ROOTFS_POSTINSTALL_COMMAND') 192 193 bb.utils.mkdirhier(self.image_rootfs) 194 195 bb.utils.mkdirhier(self.deploydir) 196 197 execute_pre_post_process(self.d, pre_process_cmds) 198 199 if self.progress_reporter: 200 self.progress_reporter.next_stage() 201 202 # call the package manager dependent create method 203 self._create() 204 205 sysconfdir = self.image_rootfs + self.d.getVar('sysconfdir') 206 bb.utils.mkdirhier(sysconfdir) 207 with open(sysconfdir + "/version", "w+") as ver: 208 ver.write(self.d.getVar('BUILDNAME') + "\n") 209 210 execute_pre_post_process(self.d, rootfs_post_install_cmds) 211 212 self.pm.run_intercepts() 213 214 execute_pre_post_process(self.d, post_process_cmds) 215 216 if self.progress_reporter: 217 self.progress_reporter.next_stage() 218 219 if bb.utils.contains("IMAGE_FEATURES", "read-only-rootfs", 220 True, False, self.d): 221 delayed_postinsts = self._get_delayed_postinsts() 222 if delayed_postinsts is not None: 223 bb.fatal("The following packages could not be configured " 224 "offline and rootfs is read-only: %s" % 225 delayed_postinsts) 226 227 if self.d.getVar('USE_DEVFS') != "1": 228 self._create_devfs() 229 230 self._uninstall_unneeded() 231 232 if self.progress_reporter: 233 self.progress_reporter.next_stage() 234 235 self._insert_feed_uris() 236 237 self._run_ldconfig() 238 239 if self.d.getVar('USE_DEPMOD') != "0": 240 self._generate_kernel_module_deps() 241 242 self._cleanup() 243 self._log_check() 244 245 if self.progress_reporter: 246 self.progress_reporter.next_stage() 247 248 249 def _uninstall_unneeded(self): 250 # Remove unneeded init script symlinks 251 delayed_postinsts = self._get_delayed_postinsts() 252 if delayed_postinsts is None: 253 if os.path.exists(self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/init.d/run-postinsts")): 254 self._exec_shell_cmd(["update-rc.d", "-f", "-r", 255 self.d.getVar('IMAGE_ROOTFS'), 256 "run-postinsts", "remove"]) 257 258 image_rorfs = bb.utils.contains("IMAGE_FEATURES", "read-only-rootfs", 259 True, False, self.d) 260 image_rorfs_force = self.d.getVar('FORCE_RO_REMOVE') 261 262 if image_rorfs or image_rorfs_force == "1": 263 # Remove components that we don't need if it's a read-only rootfs 264 unneeded_pkgs = self.d.getVar("ROOTFS_RO_UNNEEDED").split() 265 pkgs_installed = image_list_installed_packages(self.d) 266 # Make sure update-alternatives is removed last. This is 267 # because its database has to available while uninstalling 268 # other packages, allowing alternative symlinks of packages 269 # to be uninstalled or to be managed correctly otherwise. 270 provider = self.d.getVar("VIRTUAL-RUNTIME_update-alternatives") 271 pkgs_to_remove = sorted([pkg for pkg in pkgs_installed if pkg in unneeded_pkgs], key=lambda x: x == provider) 272 273 # update-alternatives provider is removed in its own remove() 274 # call because all package managers do not guarantee the packages 275 # are removed in the order they given in the list (which is 276 # passed to the command line). The sorting done earlier is 277 # utilized to implement the 2-stage removal. 278 if len(pkgs_to_remove) > 1: 279 self.pm.remove(pkgs_to_remove[:-1], False) 280 if len(pkgs_to_remove) > 0: 281 self.pm.remove([pkgs_to_remove[-1]], False) 282 283 if delayed_postinsts: 284 self._save_postinsts() 285 if image_rorfs: 286 bb.warn("There are post install scripts " 287 "in a read-only rootfs") 288 289 post_uninstall_cmds = self.d.getVar("ROOTFS_POSTUNINSTALL_COMMAND") 290 execute_pre_post_process(self.d, post_uninstall_cmds) 291 292 runtime_pkgmanage = bb.utils.contains("IMAGE_FEATURES", "package-management", 293 True, False, self.d) 294 if not runtime_pkgmanage: 295 # Remove the package manager data files 296 self.pm.remove_packaging_data() 297 298 def _run_ldconfig(self): 299 if self.d.getVar('LDCONFIGDEPEND'): 300 bb.note("Executing: ldconfig -r " + self.image_rootfs + " -c new -v") 301 self._exec_shell_cmd(['ldconfig', '-r', self.image_rootfs, '-c', 302 'new', '-v']) 303 304 def _check_for_kernel_modules(self, modules_dir): 305 for root, dirs, files in os.walk(modules_dir, topdown=True): 306 for name in files: 307 found_ko = name.endswith(".ko") 308 if found_ko: 309 return found_ko 310 return False 311 312 def _generate_kernel_module_deps(self): 313 modules_dir = os.path.join(self.image_rootfs, 'lib', 'modules') 314 # if we don't have any modules don't bother to do the depmod 315 if not self._check_for_kernel_modules(modules_dir): 316 bb.note("No Kernel Modules found, not running depmod") 317 return 318 319 kernel_abi_ver_file = oe.path.join(self.d.getVar('PKGDATA_DIR'), "kernel-depmod", 320 'kernel-abiversion') 321 if not os.path.exists(kernel_abi_ver_file): 322 bb.fatal("No kernel-abiversion file found (%s), cannot run depmod, aborting" % kernel_abi_ver_file) 323 324 kernel_ver = open(kernel_abi_ver_file).read().strip(' \n') 325 versioned_modules_dir = os.path.join(self.image_rootfs, modules_dir, kernel_ver) 326 327 bb.utils.mkdirhier(versioned_modules_dir) 328 329 self._exec_shell_cmd(['depmodwrapper', '-a', '-b', self.image_rootfs, kernel_ver]) 330 331 """ 332 Create devfs: 333 * IMAGE_DEVICE_TABLE is the old name to an absolute path to a device table file 334 * IMAGE_DEVICE_TABLES is a new name for a file, or list of files, seached 335 for in the BBPATH 336 If neither are specified then the default name of files/device_table-minimal.txt 337 is searched for in the BBPATH (same as the old version.) 338 """ 339 def _create_devfs(self): 340 devtable_list = [] 341 devtable = self.d.getVar('IMAGE_DEVICE_TABLE') 342 if devtable is not None: 343 devtable_list.append(devtable) 344 else: 345 devtables = self.d.getVar('IMAGE_DEVICE_TABLES') 346 if devtables is None: 347 devtables = 'files/device_table-minimal.txt' 348 for devtable in devtables.split(): 349 devtable_list.append("%s" % bb.utils.which(self.d.getVar('BBPATH'), devtable)) 350 351 for devtable in devtable_list: 352 self._exec_shell_cmd(["makedevs", "-r", 353 self.image_rootfs, "-D", devtable]) 354 355 356class RpmRootfs(Rootfs): 357 def __init__(self, d, manifest_dir, progress_reporter=None, logcatcher=None): 358 super(RpmRootfs, self).__init__(d, progress_reporter, logcatcher) 359 self.log_check_regex = r'(unpacking of archive failed|Cannot find package'\ 360 r'|exit 1|ERROR: |Error: |Error |ERROR '\ 361 r'|Failed |Failed: |Failed$|Failed\(\d+\):)' 362 self.manifest = RpmManifest(d, manifest_dir) 363 364 self.pm = RpmPM(d, 365 d.getVar('IMAGE_ROOTFS'), 366 self.d.getVar('TARGET_VENDOR') 367 ) 368 369 self.inc_rpm_image_gen = self.d.getVar('INC_RPM_IMAGE_GEN') 370 if self.inc_rpm_image_gen != "1": 371 bb.utils.remove(self.image_rootfs, True) 372 else: 373 self.pm.recovery_packaging_data() 374 bb.utils.remove(self.d.getVar('MULTILIB_TEMP_ROOTFS'), True) 375 376 self.pm.create_configs() 377 378 ''' 379 While rpm incremental image generation is enabled, it will remove the 380 unneeded pkgs by comparing the new install solution manifest and the 381 old installed manifest. 382 ''' 383 def _create_incremental(self, pkgs_initial_install): 384 if self.inc_rpm_image_gen == "1": 385 386 pkgs_to_install = list() 387 for pkg_type in pkgs_initial_install: 388 pkgs_to_install += pkgs_initial_install[pkg_type] 389 390 installed_manifest = self.pm.load_old_install_solution() 391 solution_manifest = self.pm.dump_install_solution(pkgs_to_install) 392 393 pkg_to_remove = list() 394 for pkg in installed_manifest: 395 if pkg not in solution_manifest: 396 pkg_to_remove.append(pkg) 397 398 self.pm.update() 399 400 bb.note('incremental update -- upgrade packages in place ') 401 self.pm.upgrade() 402 if pkg_to_remove != []: 403 bb.note('incremental removed: %s' % ' '.join(pkg_to_remove)) 404 self.pm.remove(pkg_to_remove) 405 406 self.pm.autoremove() 407 408 def _create(self): 409 pkgs_to_install = self.manifest.parse_initial_manifest() 410 rpm_pre_process_cmds = self.d.getVar('RPM_PREPROCESS_COMMANDS') 411 rpm_post_process_cmds = self.d.getVar('RPM_POSTPROCESS_COMMANDS') 412 413 # update PM index files 414 self.pm.write_index() 415 416 execute_pre_post_process(self.d, rpm_pre_process_cmds) 417 418 if self.progress_reporter: 419 self.progress_reporter.next_stage() 420 421 if self.inc_rpm_image_gen == "1": 422 self._create_incremental(pkgs_to_install) 423 424 if self.progress_reporter: 425 self.progress_reporter.next_stage() 426 427 self.pm.update() 428 429 pkgs = [] 430 pkgs_attempt = [] 431 for pkg_type in pkgs_to_install: 432 if pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY: 433 pkgs_attempt += pkgs_to_install[pkg_type] 434 else: 435 pkgs += pkgs_to_install[pkg_type] 436 437 if self.progress_reporter: 438 self.progress_reporter.next_stage() 439 440 self.pm.install(pkgs) 441 442 if self.progress_reporter: 443 self.progress_reporter.next_stage() 444 445 self.pm.install(pkgs_attempt, True) 446 447 if self.progress_reporter: 448 self.progress_reporter.next_stage() 449 450 self.pm.install_complementary() 451 452 if self.progress_reporter: 453 self.progress_reporter.next_stage() 454 455 self._setup_dbg_rootfs(['/etc', '/var/lib/rpm', '/var/cache/dnf', '/var/lib/dnf']) 456 457 execute_pre_post_process(self.d, rpm_post_process_cmds) 458 459 if self.inc_rpm_image_gen == "1": 460 self.pm.backup_packaging_data() 461 462 if self.progress_reporter: 463 self.progress_reporter.next_stage() 464 465 466 @staticmethod 467 def _depends_list(): 468 return ['DEPLOY_DIR_RPM', 'INC_RPM_IMAGE_GEN', 'RPM_PREPROCESS_COMMANDS', 469 'RPM_POSTPROCESS_COMMANDS', 'RPM_PREFER_ELF_ARCH'] 470 471 def _get_delayed_postinsts(self): 472 postinst_dir = self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/rpm-postinsts") 473 if os.path.isdir(postinst_dir): 474 files = os.listdir(postinst_dir) 475 for f in files: 476 bb.note('Delayed package scriptlet: %s' % f) 477 return files 478 479 return None 480 481 def _save_postinsts(self): 482 # this is just a stub. For RPM, the failed postinstalls are 483 # already saved in /etc/rpm-postinsts 484 pass 485 486 def _log_check(self): 487 self._log_check_warn() 488 self._log_check_error() 489 490 def _cleanup(self): 491 if bb.utils.contains("IMAGE_FEATURES", "package-management", True, False, self.d): 492 self.pm._invoke_dnf(["clean", "all"]) 493 494 495class DpkgOpkgRootfs(Rootfs): 496 def __init__(self, d, progress_reporter=None, logcatcher=None): 497 super(DpkgOpkgRootfs, self).__init__(d, progress_reporter, logcatcher) 498 499 def _get_pkgs_postinsts(self, status_file): 500 def _get_pkg_depends_list(pkg_depends): 501 pkg_depends_list = [] 502 # filter version requirements like libc (>= 1.1) 503 for dep in pkg_depends.split(', '): 504 m_dep = re.match(r"^(.*) \(.*\)$", dep) 505 if m_dep: 506 dep = m_dep.group(1) 507 pkg_depends_list.append(dep) 508 509 return pkg_depends_list 510 511 pkgs = {} 512 pkg_name = "" 513 pkg_status_match = False 514 pkg_depends = "" 515 516 with open(status_file) as status: 517 data = status.read() 518 status.close() 519 for line in data.split('\n'): 520 m_pkg = re.match(r"^Package: (.*)", line) 521 m_status = re.match(r"^Status:.*unpacked", line) 522 m_depends = re.match(r"^Depends: (.*)", line) 523 524 #Only one of m_pkg, m_status or m_depends is not None at time 525 #If m_pkg is not None, we started a new package 526 if m_pkg is not None: 527 #Get Package name 528 pkg_name = m_pkg.group(1) 529 #Make sure we reset other variables 530 pkg_status_match = False 531 pkg_depends = "" 532 elif m_status is not None: 533 #New status matched 534 pkg_status_match = True 535 elif m_depends is not None: 536 #New depends macthed 537 pkg_depends = m_depends.group(1) 538 else: 539 pass 540 541 #Now check if we can process package depends and postinst 542 if "" != pkg_name and pkg_status_match: 543 pkgs[pkg_name] = _get_pkg_depends_list(pkg_depends) 544 else: 545 #Not enough information 546 pass 547 548 # remove package dependencies not in postinsts 549 pkg_names = list(pkgs.keys()) 550 for pkg_name in pkg_names: 551 deps = pkgs[pkg_name][:] 552 553 for d in deps: 554 if d not in pkg_names: 555 pkgs[pkg_name].remove(d) 556 557 return pkgs 558 559 def _get_delayed_postinsts_common(self, status_file): 560 def _dep_resolve(graph, node, resolved, seen): 561 seen.append(node) 562 563 for edge in graph[node]: 564 if edge not in resolved: 565 if edge in seen: 566 raise RuntimeError("Packages %s and %s have " \ 567 "a circular dependency in postinsts scripts." \ 568 % (node, edge)) 569 _dep_resolve(graph, edge, resolved, seen) 570 571 resolved.append(node) 572 573 pkg_list = [] 574 575 pkgs = None 576 if not self.d.getVar('PACKAGE_INSTALL').strip(): 577 bb.note("Building empty image") 578 else: 579 pkgs = self._get_pkgs_postinsts(status_file) 580 if pkgs: 581 root = "__packagegroup_postinst__" 582 pkgs[root] = list(pkgs.keys()) 583 _dep_resolve(pkgs, root, pkg_list, []) 584 pkg_list.remove(root) 585 586 if len(pkg_list) == 0: 587 return None 588 589 return pkg_list 590 591 def _save_postinsts_common(self, dst_postinst_dir, src_postinst_dir): 592 if bb.utils.contains("IMAGE_FEATURES", "package-management", 593 True, False, self.d): 594 return 595 num = 0 596 for p in self._get_delayed_postinsts(): 597 bb.utils.mkdirhier(dst_postinst_dir) 598 599 if os.path.exists(os.path.join(src_postinst_dir, p + ".postinst")): 600 shutil.copy(os.path.join(src_postinst_dir, p + ".postinst"), 601 os.path.join(dst_postinst_dir, "%03d-%s" % (num, p))) 602 603 num += 1 604 605class DpkgRootfs(DpkgOpkgRootfs): 606 def __init__(self, d, manifest_dir, progress_reporter=None, logcatcher=None): 607 super(DpkgRootfs, self).__init__(d, progress_reporter, logcatcher) 608 self.log_check_regex = '^E:' 609 self.log_check_expected_regexes = \ 610 [ 611 "^E: Unmet dependencies." 612 ] 613 614 bb.utils.remove(self.image_rootfs, True) 615 bb.utils.remove(self.d.getVar('MULTILIB_TEMP_ROOTFS'), True) 616 self.manifest = DpkgManifest(d, manifest_dir) 617 self.pm = DpkgPM(d, d.getVar('IMAGE_ROOTFS'), 618 d.getVar('PACKAGE_ARCHS'), 619 d.getVar('DPKG_ARCH')) 620 621 622 def _create(self): 623 pkgs_to_install = self.manifest.parse_initial_manifest() 624 deb_pre_process_cmds = self.d.getVar('DEB_PREPROCESS_COMMANDS') 625 deb_post_process_cmds = self.d.getVar('DEB_POSTPROCESS_COMMANDS') 626 627 alt_dir = self.d.expand("${IMAGE_ROOTFS}/var/lib/dpkg/alternatives") 628 bb.utils.mkdirhier(alt_dir) 629 630 # update PM index files 631 self.pm.write_index() 632 633 execute_pre_post_process(self.d, deb_pre_process_cmds) 634 635 if self.progress_reporter: 636 self.progress_reporter.next_stage() 637 # Don't support incremental, so skip that 638 self.progress_reporter.next_stage() 639 640 self.pm.update() 641 642 if self.progress_reporter: 643 self.progress_reporter.next_stage() 644 645 for pkg_type in self.install_order: 646 if pkg_type in pkgs_to_install: 647 self.pm.install(pkgs_to_install[pkg_type], 648 [False, True][pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY]) 649 self.pm.fix_broken_dependencies() 650 651 if self.progress_reporter: 652 # Don't support attemptonly, so skip that 653 self.progress_reporter.next_stage() 654 self.progress_reporter.next_stage() 655 656 self.pm.install_complementary() 657 658 if self.progress_reporter: 659 self.progress_reporter.next_stage() 660 661 self._setup_dbg_rootfs(['/var/lib/dpkg']) 662 663 self.pm.fix_broken_dependencies() 664 665 self.pm.mark_packages("installed") 666 667 self.pm.run_pre_post_installs() 668 669 execute_pre_post_process(self.d, deb_post_process_cmds) 670 671 if self.progress_reporter: 672 self.progress_reporter.next_stage() 673 674 @staticmethod 675 def _depends_list(): 676 return ['DEPLOY_DIR_DEB', 'DEB_SDK_ARCH', 'APTCONF_TARGET', 'APT_ARGS', 'DPKG_ARCH', 'DEB_PREPROCESS_COMMANDS', 'DEB_POSTPROCESS_COMMANDS'] 677 678 def _get_delayed_postinsts(self): 679 status_file = self.image_rootfs + "/var/lib/dpkg/status" 680 return self._get_delayed_postinsts_common(status_file) 681 682 def _save_postinsts(self): 683 dst_postinst_dir = self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/deb-postinsts") 684 src_postinst_dir = self.d.expand("${IMAGE_ROOTFS}/var/lib/dpkg/info") 685 return self._save_postinsts_common(dst_postinst_dir, src_postinst_dir) 686 687 def _log_check(self): 688 self._log_check_warn() 689 self._log_check_error() 690 691 def _cleanup(self): 692 pass 693 694 695class OpkgRootfs(DpkgOpkgRootfs): 696 def __init__(self, d, manifest_dir, progress_reporter=None, logcatcher=None): 697 super(OpkgRootfs, self).__init__(d, progress_reporter, logcatcher) 698 self.log_check_regex = '(exit 1|Collected errors)' 699 700 self.manifest = OpkgManifest(d, manifest_dir) 701 self.opkg_conf = self.d.getVar("IPKGCONF_TARGET") 702 self.pkg_archs = self.d.getVar("ALL_MULTILIB_PACKAGE_ARCHS") 703 704 self.inc_opkg_image_gen = self.d.getVar('INC_IPK_IMAGE_GEN') or "" 705 if self._remove_old_rootfs(): 706 bb.utils.remove(self.image_rootfs, True) 707 self.pm = OpkgPM(d, 708 self.image_rootfs, 709 self.opkg_conf, 710 self.pkg_archs) 711 else: 712 self.pm = OpkgPM(d, 713 self.image_rootfs, 714 self.opkg_conf, 715 self.pkg_archs) 716 self.pm.recover_packaging_data() 717 718 bb.utils.remove(self.d.getVar('MULTILIB_TEMP_ROOTFS'), True) 719 720 def _prelink_file(self, root_dir, filename): 721 bb.note('prelink %s in %s' % (filename, root_dir)) 722 prelink_cfg = oe.path.join(root_dir, 723 self.d.expand('${sysconfdir}/prelink.conf')) 724 if not os.path.exists(prelink_cfg): 725 shutil.copy(self.d.expand('${STAGING_DIR_NATIVE}${sysconfdir_native}/prelink.conf'), 726 prelink_cfg) 727 728 cmd_prelink = self.d.expand('${STAGING_DIR_NATIVE}${sbindir_native}/prelink') 729 self._exec_shell_cmd([cmd_prelink, 730 '--root', 731 root_dir, 732 '-amR', 733 '-N', 734 '-c', 735 self.d.expand('${sysconfdir}/prelink.conf')]) 736 737 ''' 738 Compare two files with the same key twice to see if they are equal. 739 If they are not equal, it means they are duplicated and come from 740 different packages. 741 1st: Comapre them directly; 742 2nd: While incremental image creation is enabled, one of the 743 files could be probaly prelinked in the previous image 744 creation and the file has been changed, so we need to 745 prelink the other one and compare them. 746 ''' 747 def _file_equal(self, key, f1, f2): 748 749 # Both of them are not prelinked 750 if filecmp.cmp(f1, f2): 751 return True 752 753 if bb.data.inherits_class('image-prelink', self.d): 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") 775 if allow_replace is None: 776 allow_replace = "" 777 778 allow_rep = re.compile(re.sub(r"\|$", r"", 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") 807 bb.utils.mkdirhier(ml_temp) 808 809 dirs = [self.image_rootfs] 810 811 for variant in self.d.getVar("MULTILIB_VARIANTS").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, prepare_index=False) 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') or '').strip(), 872 (self.d.getVar('NO_RECOMMENDATIONS') or '').strip(), 873 (self.d.getVar('PACKAGE_EXCLUDE') 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') 884 opkg_post_process_cmds = self.d.getVar('OPKG_POSTPROCESS_COMMANDS') 885 886 # update PM index files 887 self.pm.write_index() 888 889 execute_pre_post_process(self.d, opkg_pre_process_cmds) 890 891 if self.progress_reporter: 892 self.progress_reporter.next_stage() 893 # Steps are a bit different in order, skip next 894 self.progress_reporter.next_stage() 895 896 self.pm.update() 897 898 if self.progress_reporter: 899 self.progress_reporter.next_stage() 900 901 if self.inc_opkg_image_gen == "1": 902 self._remove_extra_packages(pkgs_to_install) 903 904 if self.progress_reporter: 905 self.progress_reporter.next_stage() 906 907 for pkg_type in self.install_order: 908 if pkg_type in pkgs_to_install: 909 # For multilib, we perform a sanity test before final install 910 # If sanity test fails, it will automatically do a bb.fatal() 911 # and the installation will stop 912 if pkg_type == Manifest.PKG_TYPE_MULTILIB: 913 self._multilib_test_install(pkgs_to_install[pkg_type]) 914 915 self.pm.install(pkgs_to_install[pkg_type], 916 [False, True][pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY]) 917 918 if self.progress_reporter: 919 self.progress_reporter.next_stage() 920 921 self.pm.install_complementary() 922 923 if self.progress_reporter: 924 self.progress_reporter.next_stage() 925 926 opkg_lib_dir = self.d.getVar('OPKGLIBDIR') 927 opkg_dir = os.path.join(opkg_lib_dir, 'opkg') 928 self._setup_dbg_rootfs([opkg_dir]) 929 930 execute_pre_post_process(self.d, opkg_post_process_cmds) 931 932 if self.inc_opkg_image_gen == "1": 933 self.pm.backup_packaging_data() 934 935 if self.progress_reporter: 936 self.progress_reporter.next_stage() 937 938 @staticmethod 939 def _depends_list(): 940 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'] 941 942 def _get_delayed_postinsts(self): 943 status_file = os.path.join(self.image_rootfs, 944 self.d.getVar('OPKGLIBDIR').strip('/'), 945 "opkg", "status") 946 return self._get_delayed_postinsts_common(status_file) 947 948 def _save_postinsts(self): 949 dst_postinst_dir = self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/ipk-postinsts") 950 src_postinst_dir = self.d.expand("${IMAGE_ROOTFS}${OPKGLIBDIR}/opkg/info") 951 return self._save_postinsts_common(dst_postinst_dir, src_postinst_dir) 952 953 def _log_check(self): 954 self._log_check_warn() 955 self._log_check_error() 956 957 def _cleanup(self): 958 self.pm.remove_lists() 959 960def get_class_for_type(imgtype): 961 return {"rpm": RpmRootfs, 962 "ipk": OpkgRootfs, 963 "deb": DpkgRootfs}[imgtype] 964 965def variable_depends(d, manifest_dir=None): 966 img_type = d.getVar('IMAGE_PKGTYPE') 967 cls = get_class_for_type(img_type) 968 return cls._depends_list() 969 970def create_rootfs(d, manifest_dir=None, progress_reporter=None, logcatcher=None): 971 env_bkp = os.environ.copy() 972 973 img_type = d.getVar('IMAGE_PKGTYPE') 974 if img_type == "rpm": 975 RpmRootfs(d, manifest_dir, progress_reporter, logcatcher).create() 976 elif img_type == "ipk": 977 OpkgRootfs(d, manifest_dir, progress_reporter, logcatcher).create() 978 elif img_type == "deb": 979 DpkgRootfs(d, manifest_dir, progress_reporter, logcatcher).create() 980 981 os.environ.clear() 982 os.environ.update(env_bkp) 983 984 985def image_list_installed_packages(d, rootfs_dir=None): 986 if not rootfs_dir: 987 rootfs_dir = d.getVar('IMAGE_ROOTFS') 988 989 img_type = d.getVar('IMAGE_PKGTYPE') 990 if img_type == "rpm": 991 return RpmPkgsList(d, rootfs_dir).list_pkgs() 992 elif img_type == "ipk": 993 return OpkgPkgsList(d, rootfs_dir, d.getVar("IPKGCONF_TARGET")).list_pkgs() 994 elif img_type == "deb": 995 return DpkgPkgsList(d, rootfs_dir).list_pkgs() 996 997if __name__ == "__main__": 998 """ 999 We should be able to run this as a standalone script, from outside bitbake 1000 environment. 1001 """ 1002 """ 1003 TBD 1004 """ 1005