blob: 10a831994eee953e042dbe3421a0a760b8927983 [file] [log] [blame]
Andrew Geissler635e0e42020-08-21 15:58:33 -05001#
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
Andrew Geissler6ce62a22020-11-30 19:58:47 -060011from oe.package_manager.ipk.manifest import PkgManifest
Andrew Geissler635e0e42020-08-21 15:58:33 -050012from 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
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600124class PkgRootfs(DpkgOpkgRootfs):
Andrew Geissler635e0e42020-08-21 15:58:33 -0500125 def __init__(self, d, manifest_dir, progress_reporter=None, logcatcher=None):
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600126 super(PkgRootfs, self).__init__(d, progress_reporter, logcatcher)
Andrew Geissler635e0e42020-08-21 15:58:33 -0500127 self.log_check_regex = '(exit 1|Collected errors)'
128
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600129 self.manifest = PkgManifest(d, manifest_dir)
Andrew Geissler635e0e42020-08-21 15:58:33 -0500130 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)
Andrew Geissler635e0e42020-08-21 15:58:33 -0500148 '''
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.
Andrew Geissler635e0e42020-08-21 15:58:33 -0500152 '''
153 def _file_equal(self, key, f1, f2):
Andrew Geissler635e0e42020-08-21 15:58:33 -0500154 if filecmp.cmp(f1, f2):
155 return True
Andrew Geissler635e0e42020-08-21 15:58:33 -0500156 # 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()