1# 2# SPDX-License-Identifier: GPL-2.0-only 3# 4 5import re 6import filecmp 7import shutil 8from oe.rootfs import Rootfs 9from oe.manifest import Manifest 10from oe.utils import execute_pre_post_process 11from oe.package_manager.ipk.manifest import PkgManifest 12from oe.package_manager.ipk import OpkgPM 13 14class DpkgOpkgRootfs(Rootfs): 15 def __init__(self, d, progress_reporter=None, logcatcher=None): 16 super(DpkgOpkgRootfs, self).__init__(d, progress_reporter, logcatcher) 17 18 def _get_pkgs_postinsts(self, status_file): 19 def _get_pkg_depends_list(pkg_depends): 20 pkg_depends_list = [] 21 # filter version requirements like libc (>= 1.1) 22 for dep in pkg_depends.split(', '): 23 m_dep = re.match(r"^(.*) \(.*\)$", dep) 24 if m_dep: 25 dep = m_dep.group(1) 26 pkg_depends_list.append(dep) 27 28 return pkg_depends_list 29 30 pkgs = {} 31 pkg_name = "" 32 pkg_status_match = False 33 pkg_depends = "" 34 35 with open(status_file) as status: 36 data = status.read() 37 status.close() 38 for line in data.split('\n'): 39 m_pkg = re.match(r"^Package: (.*)", line) 40 m_status = re.match(r"^Status:.*unpacked", line) 41 m_depends = re.match(r"^Depends: (.*)", line) 42 43 #Only one of m_pkg, m_status or m_depends is not None at time 44 #If m_pkg is not None, we started a new package 45 if m_pkg is not None: 46 #Get Package name 47 pkg_name = m_pkg.group(1) 48 #Make sure we reset other variables 49 pkg_status_match = False 50 pkg_depends = "" 51 elif m_status is not None: 52 #New status matched 53 pkg_status_match = True 54 elif m_depends is not None: 55 #New depends macthed 56 pkg_depends = m_depends.group(1) 57 else: 58 pass 59 60 #Now check if we can process package depends and postinst 61 if "" != pkg_name and pkg_status_match: 62 pkgs[pkg_name] = _get_pkg_depends_list(pkg_depends) 63 else: 64 #Not enough information 65 pass 66 67 # remove package dependencies not in postinsts 68 pkg_names = list(pkgs.keys()) 69 for pkg_name in pkg_names: 70 deps = pkgs[pkg_name][:] 71 72 for d in deps: 73 if d not in pkg_names: 74 pkgs[pkg_name].remove(d) 75 76 return pkgs 77 78 def _get_delayed_postinsts_common(self, status_file): 79 def _dep_resolve(graph, node, resolved, seen): 80 seen.append(node) 81 82 for edge in graph[node]: 83 if edge not in resolved: 84 if edge in seen: 85 raise RuntimeError("Packages %s and %s have " \ 86 "a circular dependency in postinsts scripts." \ 87 % (node, edge)) 88 _dep_resolve(graph, edge, resolved, seen) 89 90 resolved.append(node) 91 92 pkg_list = [] 93 94 pkgs = None 95 if not self.d.getVar('PACKAGE_INSTALL').strip(): 96 bb.note("Building empty image") 97 else: 98 pkgs = self._get_pkgs_postinsts(status_file) 99 if pkgs: 100 root = "__packagegroup_postinst__" 101 pkgs[root] = list(pkgs.keys()) 102 _dep_resolve(pkgs, root, pkg_list, []) 103 pkg_list.remove(root) 104 105 if len(pkg_list) == 0: 106 return None 107 108 return pkg_list 109 110 def _save_postinsts_common(self, dst_postinst_dir, src_postinst_dir): 111 if bb.utils.contains("IMAGE_FEATURES", "package-management", 112 True, False, self.d): 113 return 114 num = 0 115 for p in self._get_delayed_postinsts(): 116 bb.utils.mkdirhier(dst_postinst_dir) 117 118 if os.path.exists(os.path.join(src_postinst_dir, p + ".postinst")): 119 shutil.copy(os.path.join(src_postinst_dir, p + ".postinst"), 120 os.path.join(dst_postinst_dir, "%03d-%s" % (num, p))) 121 122 num += 1 123 124class PkgRootfs(DpkgOpkgRootfs): 125 def __init__(self, d, manifest_dir, progress_reporter=None, logcatcher=None): 126 super(PkgRootfs, self).__init__(d, progress_reporter, logcatcher) 127 self.log_check_regex = '(exit 1|Collected errors)' 128 129 self.manifest = PkgManifest(d, manifest_dir) 130 self.opkg_conf = self.d.getVar("IPKGCONF_TARGET") 131 self.pkg_archs = self.d.getVar("ALL_MULTILIB_PACKAGE_ARCHS") 132 133 self.inc_opkg_image_gen = self.d.getVar('INC_IPK_IMAGE_GEN') or "" 134 if self._remove_old_rootfs(): 135 bb.utils.remove(self.image_rootfs, True) 136 self.pm = OpkgPM(d, 137 self.image_rootfs, 138 self.opkg_conf, 139 self.pkg_archs) 140 else: 141 self.pm = OpkgPM(d, 142 self.image_rootfs, 143 self.opkg_conf, 144 self.pkg_archs) 145 self.pm.recover_packaging_data() 146 147 bb.utils.remove(self.d.getVar('MULTILIB_TEMP_ROOTFS'), True) 148 ''' 149 Compare two files with the same key twice to see if they are equal. 150 If they are not equal, it means they are duplicated and come from 151 different packages. 152 ''' 153 def _file_equal(self, key, f1, f2): 154 if filecmp.cmp(f1, f2): 155 return True 156 # Not equal 157 return False 158 159 """ 160 This function was reused from the old implementation. 161 See commit: "image.bbclass: Added variables for multilib support." by 162 Lianhao Lu. 163 """ 164 def _multilib_sanity_test(self, dirs): 165 166 allow_replace = self.d.getVar("MULTILIBRE_ALLOW_REP") 167 if allow_replace is None: 168 allow_replace = "" 169 170 allow_rep = re.compile(re.sub(r"\|$", r"", allow_replace)) 171 error_prompt = "Multilib check error:" 172 173 files = {} 174 for dir in dirs: 175 for root, subfolders, subfiles in os.walk(dir): 176 for file in subfiles: 177 item = os.path.join(root, file) 178 key = str(os.path.join("/", os.path.relpath(item, dir))) 179 180 valid = True 181 if key in files: 182 #check whether the file is allow to replace 183 if allow_rep.match(key): 184 valid = True 185 else: 186 if os.path.exists(files[key]) and \ 187 os.path.exists(item) and \ 188 not self._file_equal(key, files[key], item): 189 valid = False 190 bb.fatal("%s duplicate files %s %s is not the same\n" % 191 (error_prompt, item, files[key])) 192 193 #pass the check, add to list 194 if valid: 195 files[key] = item 196 197 def _multilib_test_install(self, pkgs): 198 ml_temp = self.d.getVar("MULTILIB_TEMP_ROOTFS") 199 bb.utils.mkdirhier(ml_temp) 200 201 dirs = [self.image_rootfs] 202 203 for variant in self.d.getVar("MULTILIB_VARIANTS").split(): 204 ml_target_rootfs = os.path.join(ml_temp, variant) 205 206 bb.utils.remove(ml_target_rootfs, True) 207 208 ml_opkg_conf = os.path.join(ml_temp, 209 variant + "-" + os.path.basename(self.opkg_conf)) 210 211 ml_pm = OpkgPM(self.d, ml_target_rootfs, ml_opkg_conf, self.pkg_archs, prepare_index=False) 212 213 ml_pm.update() 214 ml_pm.install(pkgs) 215 216 dirs.append(ml_target_rootfs) 217 218 self._multilib_sanity_test(dirs) 219 220 ''' 221 While ipk incremental image generation is enabled, it will remove the 222 unneeded pkgs by comparing the old full manifest in previous existing 223 image and the new full manifest in the current image. 224 ''' 225 def _remove_extra_packages(self, pkgs_initial_install): 226 if self.inc_opkg_image_gen == "1": 227 # Parse full manifest in previous existing image creation session 228 old_full_manifest = self.manifest.parse_full_manifest() 229 230 # Create full manifest for the current image session, the old one 231 # will be replaced by the new one. 232 self.manifest.create_full(self.pm) 233 234 # Parse full manifest in current image creation session 235 new_full_manifest = self.manifest.parse_full_manifest() 236 237 pkg_to_remove = list() 238 for pkg in old_full_manifest: 239 if pkg not in new_full_manifest: 240 pkg_to_remove.append(pkg) 241 242 if pkg_to_remove != []: 243 bb.note('decremental removed: %s' % ' '.join(pkg_to_remove)) 244 self.pm.remove(pkg_to_remove) 245 246 ''' 247 Compare with previous existing image creation, if some conditions 248 triggered, the previous old image should be removed. 249 The conditions include any of 'PACKAGE_EXCLUDE, NO_RECOMMENDATIONS 250 and BAD_RECOMMENDATIONS' has been changed. 251 ''' 252 def _remove_old_rootfs(self): 253 if self.inc_opkg_image_gen != "1": 254 return True 255 256 vars_list_file = self.d.expand('${T}/vars_list') 257 258 old_vars_list = "" 259 if os.path.exists(vars_list_file): 260 old_vars_list = open(vars_list_file, 'r+').read() 261 262 new_vars_list = '%s:%s:%s\n' % \ 263 ((self.d.getVar('BAD_RECOMMENDATIONS') or '').strip(), 264 (self.d.getVar('NO_RECOMMENDATIONS') or '').strip(), 265 (self.d.getVar('PACKAGE_EXCLUDE') or '').strip()) 266 open(vars_list_file, 'w+').write(new_vars_list) 267 268 if old_vars_list != new_vars_list: 269 return True 270 271 return False 272 273 def _create(self): 274 pkgs_to_install = self.manifest.parse_initial_manifest() 275 opkg_pre_process_cmds = self.d.getVar('OPKG_PREPROCESS_COMMANDS') 276 opkg_post_process_cmds = self.d.getVar('OPKG_POSTPROCESS_COMMANDS') 277 278 # update PM index files 279 self.pm.write_index() 280 281 execute_pre_post_process(self.d, opkg_pre_process_cmds) 282 283 if self.progress_reporter: 284 self.progress_reporter.next_stage() 285 # Steps are a bit different in order, skip next 286 self.progress_reporter.next_stage() 287 288 self.pm.update() 289 290 if self.progress_reporter: 291 self.progress_reporter.next_stage() 292 293 if self.inc_opkg_image_gen == "1": 294 self._remove_extra_packages(pkgs_to_install) 295 296 if self.progress_reporter: 297 self.progress_reporter.next_stage() 298 299 for pkg_type in self.install_order: 300 if pkg_type in pkgs_to_install: 301 # For multilib, we perform a sanity test before final install 302 # If sanity test fails, it will automatically do a bb.fatal() 303 # and the installation will stop 304 if pkg_type == Manifest.PKG_TYPE_MULTILIB: 305 self._multilib_test_install(pkgs_to_install[pkg_type]) 306 307 self.pm.install(pkgs_to_install[pkg_type], 308 [False, True][pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY]) 309 310 if self.progress_reporter: 311 self.progress_reporter.next_stage() 312 313 self.pm.install_complementary() 314 315 if self.progress_reporter: 316 self.progress_reporter.next_stage() 317 318 opkg_lib_dir = self.d.getVar('OPKGLIBDIR') 319 opkg_dir = os.path.join(opkg_lib_dir, 'opkg') 320 self._setup_dbg_rootfs([opkg_dir]) 321 322 execute_pre_post_process(self.d, opkg_post_process_cmds) 323 324 if self.inc_opkg_image_gen == "1": 325 self.pm.backup_packaging_data() 326 327 if self.progress_reporter: 328 self.progress_reporter.next_stage() 329 330 @staticmethod 331 def _depends_list(): 332 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'] 333 334 def _get_delayed_postinsts(self): 335 status_file = os.path.join(self.image_rootfs, 336 self.d.getVar('OPKGLIBDIR').strip('/'), 337 "opkg", "status") 338 return self._get_delayed_postinsts_common(status_file) 339 340 def _save_postinsts(self): 341 dst_postinst_dir = self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/ipk-postinsts") 342 src_postinst_dir = self.d.expand("${IMAGE_ROOTFS}${OPKGLIBDIR}/opkg/info") 343 return self._save_postinsts_common(dst_postinst_dir, src_postinst_dir) 344 345 def _log_check(self): 346 self._log_check_warn() 347 self._log_check_error() 348 349 def _cleanup(self): 350 self.pm.remove_lists() 351