xref: /openbmc/openbmc/poky/meta/lib/oe/manifest.py (revision 26bdd445)
1#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
5from abc import ABCMeta, abstractmethod
6import os
7import re
8import bb
9
10
11class Manifest(object, metaclass=ABCMeta):
12    """
13    This is an abstract class. Do not instantiate this directly.
14    """
15
16    PKG_TYPE_MUST_INSTALL = "mip"
17    PKG_TYPE_MULTILIB = "mlp"
18    PKG_TYPE_LANGUAGE = "lgp"
19    PKG_TYPE_ATTEMPT_ONLY = "aop"
20
21    MANIFEST_TYPE_IMAGE = "image"
22    MANIFEST_TYPE_SDK_HOST = "sdk_host"
23    MANIFEST_TYPE_SDK_TARGET = "sdk_target"
24
25    var_maps = {
26        MANIFEST_TYPE_IMAGE: {
27            "PACKAGE_INSTALL": PKG_TYPE_MUST_INSTALL,
28            "PACKAGE_INSTALL_ATTEMPTONLY": PKG_TYPE_ATTEMPT_ONLY,
29            "LINGUAS_INSTALL": PKG_TYPE_LANGUAGE
30        },
31        MANIFEST_TYPE_SDK_HOST: {
32            "TOOLCHAIN_HOST_TASK": PKG_TYPE_MUST_INSTALL,
33            "TOOLCHAIN_HOST_TASK_ATTEMPTONLY": PKG_TYPE_ATTEMPT_ONLY
34        },
35        MANIFEST_TYPE_SDK_TARGET: {
36            "TOOLCHAIN_TARGET_TASK": PKG_TYPE_MUST_INSTALL,
37            "TOOLCHAIN_TARGET_TASK_ATTEMPTONLY": PKG_TYPE_ATTEMPT_ONLY
38        }
39    }
40
41    INSTALL_ORDER = [
42        PKG_TYPE_LANGUAGE,
43        PKG_TYPE_MUST_INSTALL,
44        PKG_TYPE_ATTEMPT_ONLY,
45        PKG_TYPE_MULTILIB
46    ]
47
48    initial_manifest_file_header = \
49        "# This file was generated automatically and contains the packages\n" \
50        "# passed on to the package manager in order to create the rootfs.\n\n" \
51        "# Format:\n" \
52        "#  <package_type>,<package_name>\n" \
53        "# where:\n" \
54        "#   <package_type> can be:\n" \
55        "#      'mip' = must install package\n" \
56        "#      'aop' = attempt only package\n" \
57        "#      'mlp' = multilib package\n" \
58        "#      'lgp' = language package\n\n"
59
60    def __init__(self, d, manifest_dir=None, manifest_type=MANIFEST_TYPE_IMAGE):
61        self.d = d
62        self.manifest_type = manifest_type
63
64        if manifest_dir is None:
65            if manifest_type != self.MANIFEST_TYPE_IMAGE:
66                self.manifest_dir = self.d.getVar('SDK_DIR')
67            else:
68                self.manifest_dir = self.d.getVar('WORKDIR')
69        else:
70            self.manifest_dir = manifest_dir
71
72        bb.utils.mkdirhier(self.manifest_dir)
73
74        self.initial_manifest = os.path.join(self.manifest_dir, "%s_initial_manifest" % manifest_type)
75        self.final_manifest = os.path.join(self.manifest_dir, "%s_final_manifest" % manifest_type)
76        self.full_manifest = os.path.join(self.manifest_dir, "%s_full_manifest" % manifest_type)
77
78        # packages in the following vars will be split in 'must install' and
79        # 'multilib'
80        self.vars_to_split = ["PACKAGE_INSTALL",
81                              "TOOLCHAIN_HOST_TASK",
82                              "TOOLCHAIN_TARGET_TASK"]
83
84    """
85    This creates a standard initial manifest for core-image-(minimal|sato|sato-sdk).
86    This will be used for testing until the class is implemented properly!
87    """
88    def _create_dummy_initial(self):
89        image_rootfs = self.d.getVar('IMAGE_ROOTFS')
90        pkg_list = dict()
91        if image_rootfs.find("core-image-sato-sdk") > 0:
92            pkg_list[self.PKG_TYPE_MUST_INSTALL] = \
93                "packagegroup-core-x11-sato-games packagegroup-base-extended " \
94                "packagegroup-core-x11-sato packagegroup-core-x11-base " \
95                "packagegroup-core-sdk packagegroup-core-tools-debug " \
96                "packagegroup-core-boot packagegroup-core-tools-testapps " \
97                "packagegroup-core-eclipse-debug packagegroup-core-qt-demoapps " \
98                "apt packagegroup-core-tools-profile psplash " \
99                "packagegroup-core-standalone-sdk-target " \
100                "packagegroup-core-ssh-openssh dpkg kernel-dev"
101            pkg_list[self.PKG_TYPE_LANGUAGE] = \
102                "locale-base-en-us locale-base-en-gb"
103        elif image_rootfs.find("core-image-sato") > 0:
104            pkg_list[self.PKG_TYPE_MUST_INSTALL] = \
105                "packagegroup-core-ssh-dropbear packagegroup-core-x11-sato-games " \
106                "packagegroup-core-x11-base psplash apt dpkg packagegroup-base-extended " \
107                "packagegroup-core-x11-sato packagegroup-core-boot"
108            pkg_list['lgp'] = \
109                "locale-base-en-us locale-base-en-gb"
110        elif image_rootfs.find("core-image-minimal") > 0:
111            pkg_list[self.PKG_TYPE_MUST_INSTALL] = "packagegroup-core-boot"
112
113        with open(self.initial_manifest, "w+") as manifest:
114            manifest.write(self.initial_manifest_file_header)
115
116            for pkg_type in pkg_list:
117                for pkg in pkg_list[pkg_type].split():
118                    manifest.write("%s,%s\n" % (pkg_type, pkg))
119
120    """
121    This will create the initial manifest which will be used by Rootfs class to
122    generate the rootfs
123    """
124    @abstractmethod
125    def create_initial(self):
126        pass
127
128    """
129    This creates the manifest after everything has been installed.
130    """
131    @abstractmethod
132    def create_final(self):
133        pass
134
135    """
136    This creates the manifest after the package in initial manifest has been
137    dummy installed. It lists all *to be installed* packages. There is no real
138    installation, just a test.
139    """
140    @abstractmethod
141    def create_full(self, pm):
142        pass
143
144    """
145    The following function parses an initial manifest and returns a dictionary
146    object with the must install, attempt only, multilib and language packages.
147    """
148    def parse_initial_manifest(self):
149        pkgs = dict()
150
151        with open(self.initial_manifest) as manifest:
152            for line in manifest.read().split('\n'):
153                comment = re.match("^#.*", line)
154                pattern = "^(%s|%s|%s|%s),(.*)$" % \
155                          (self.PKG_TYPE_MUST_INSTALL,
156                           self.PKG_TYPE_ATTEMPT_ONLY,
157                           self.PKG_TYPE_MULTILIB,
158                           self.PKG_TYPE_LANGUAGE)
159                pkg = re.match(pattern, line)
160
161                if comment is not None:
162                    continue
163
164                if pkg is not None:
165                    pkg_type = pkg.group(1)
166                    pkg_name = pkg.group(2)
167
168                    if not pkg_type in pkgs:
169                        pkgs[pkg_type] = [pkg_name]
170                    else:
171                        pkgs[pkg_type].append(pkg_name)
172
173        return pkgs
174
175    '''
176    This following function parses a full manifest and return a list
177    object with packages.
178    '''
179    def parse_full_manifest(self):
180        installed_pkgs = list()
181        if not os.path.exists(self.full_manifest):
182            bb.note('full manifest not exist')
183            return installed_pkgs
184
185        with open(self.full_manifest, 'r') as manifest:
186            for pkg in manifest.read().split('\n'):
187                installed_pkgs.append(pkg.strip())
188
189        return installed_pkgs
190
191
192class RpmManifest(Manifest):
193    """
194    Returns a dictionary object with mip and mlp packages.
195    """
196    def _split_multilib(self, pkg_list):
197        pkgs = dict()
198
199        for pkg in pkg_list.split():
200            pkg_type = self.PKG_TYPE_MUST_INSTALL
201
202            ml_variants = self.d.getVar('MULTILIB_VARIANTS').split()
203
204            for ml_variant in ml_variants:
205                if pkg.startswith(ml_variant + '-'):
206                    pkg_type = self.PKG_TYPE_MULTILIB
207
208            if not pkg_type in pkgs:
209                pkgs[pkg_type] = pkg
210            else:
211                pkgs[pkg_type] += " " + pkg
212
213        return pkgs
214
215    def create_initial(self):
216        pkgs = dict()
217
218        with open(self.initial_manifest, "w+") as manifest:
219            manifest.write(self.initial_manifest_file_header)
220
221            for var in self.var_maps[self.manifest_type]:
222                if var in self.vars_to_split:
223                    split_pkgs = self._split_multilib(self.d.getVar(var))
224                    if split_pkgs is not None:
225                        pkgs = dict(list(pkgs.items()) + list(split_pkgs.items()))
226                else:
227                    pkg_list = self.d.getVar(var)
228                    if pkg_list is not None:
229                        pkgs[self.var_maps[self.manifest_type][var]] = self.d.getVar(var)
230
231            for pkg_type in pkgs:
232                for pkg in pkgs[pkg_type].split():
233                    manifest.write("%s,%s\n" % (pkg_type, pkg))
234
235    def create_final(self):
236        pass
237
238    def create_full(self, pm):
239        pass
240
241
242class OpkgManifest(Manifest):
243    """
244    Returns a dictionary object with mip and mlp packages.
245    """
246    def _split_multilib(self, pkg_list):
247        pkgs = dict()
248
249        for pkg in pkg_list.split():
250            pkg_type = self.PKG_TYPE_MUST_INSTALL
251
252            ml_variants = self.d.getVar('MULTILIB_VARIANTS').split()
253
254            for ml_variant in ml_variants:
255                if pkg.startswith(ml_variant + '-'):
256                    pkg_type = self.PKG_TYPE_MULTILIB
257
258            if not pkg_type in pkgs:
259                pkgs[pkg_type] = pkg
260            else:
261                pkgs[pkg_type] += " " + pkg
262
263        return pkgs
264
265    def create_initial(self):
266        pkgs = dict()
267
268        with open(self.initial_manifest, "w+") as manifest:
269            manifest.write(self.initial_manifest_file_header)
270
271            for var in self.var_maps[self.manifest_type]:
272                if var in self.vars_to_split:
273                    split_pkgs = self._split_multilib(self.d.getVar(var))
274                    if split_pkgs is not None:
275                        pkgs = dict(list(pkgs.items()) + list(split_pkgs.items()))
276                else:
277                    pkg_list = self.d.getVar(var)
278                    if pkg_list is not None:
279                        pkgs[self.var_maps[self.manifest_type][var]] = self.d.getVar(var)
280
281            for pkg_type in sorted(pkgs):
282                for pkg in sorted(pkgs[pkg_type].split()):
283                    manifest.write("%s,%s\n" % (pkg_type, pkg))
284
285    def create_final(self):
286        pass
287
288    def create_full(self, pm):
289        if not os.path.exists(self.initial_manifest):
290            self.create_initial()
291
292        initial_manifest = self.parse_initial_manifest()
293        pkgs_to_install = list()
294        for pkg_type in initial_manifest:
295            pkgs_to_install += initial_manifest[pkg_type]
296        if len(pkgs_to_install) == 0:
297            return
298
299        output = pm.dummy_install(pkgs_to_install)
300
301        with open(self.full_manifest, 'w+') as manifest:
302            pkg_re = re.compile('^Installing ([^ ]+) [^ ].*')
303            for line in set(output.split('\n')):
304                m = pkg_re.match(line)
305                if m:
306                    manifest.write(m.group(1) + '\n')
307
308        return
309
310
311class DpkgManifest(Manifest):
312    def create_initial(self):
313        with open(self.initial_manifest, "w+") as manifest:
314            manifest.write(self.initial_manifest_file_header)
315
316            for var in self.var_maps[self.manifest_type]:
317                pkg_list = self.d.getVar(var)
318
319                if pkg_list is None:
320                    continue
321
322                for pkg in pkg_list.split():
323                    manifest.write("%s,%s\n" %
324                                   (self.var_maps[self.manifest_type][var], pkg))
325
326    def create_final(self):
327        pass
328
329    def create_full(self, pm):
330        pass
331
332
333def create_manifest(d, final_manifest=False, manifest_dir=None,
334                    manifest_type=Manifest.MANIFEST_TYPE_IMAGE):
335    manifest_map = {'rpm': RpmManifest,
336                    'ipk': OpkgManifest,
337                    'deb': DpkgManifest}
338
339    manifest = manifest_map[d.getVar('IMAGE_PKGTYPE')](d, manifest_dir, manifest_type)
340
341    if final_manifest:
342        manifest.create_final()
343    else:
344        manifest.create_initial()
345
346
347if __name__ == "__main__":
348    pass
349