xref: /OK3568_Linux_fs/yocto/poky/meta/lib/oe/package_manager/ipk/rootfs.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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