1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7
8__all__ = ['ImagerPlugin', 'SourcePlugin']
9
10import os
11import logging
12
13from collections import defaultdict
14from importlib.machinery import SourceFileLoader
15
16from wic import WicError
17from wic.misc import get_bitbake_var
18
19PLUGIN_TYPES = ["imager", "source"]
20
21SCRIPTS_PLUGIN_DIR = "scripts/lib/wic/plugins"
22
23logger = logging.getLogger('wic')
24
25PLUGINS = defaultdict(dict)
26
27class PluginMgr:
28    _plugin_dirs = []
29
30    @classmethod
31    def get_plugins(cls, ptype):
32        """Get dictionary of <plugin_name>:<class> pairs."""
33        if ptype not in PLUGIN_TYPES:
34            raise WicError('%s is not valid plugin type' % ptype)
35
36        # collect plugin directories
37        if not cls._plugin_dirs:
38            cls._plugin_dirs = [os.path.join(os.path.dirname(__file__), 'plugins')]
39            layers = get_bitbake_var("BBLAYERS") or ''
40            for layer_path in layers.split():
41                path = os.path.join(layer_path, SCRIPTS_PLUGIN_DIR)
42                path = os.path.abspath(os.path.expanduser(path))
43                if path not in cls._plugin_dirs and os.path.isdir(path):
44                    cls._plugin_dirs.insert(0, path)
45
46        if ptype not in PLUGINS:
47            # load all ptype plugins
48            for pdir in cls._plugin_dirs:
49                ppath = os.path.join(pdir, ptype)
50                if os.path.isdir(ppath):
51                    for fname in os.listdir(ppath):
52                        if fname.endswith('.py'):
53                            mname = fname[:-3]
54                            mpath = os.path.join(ppath, fname)
55                            logger.debug("loading plugin module %s", mpath)
56                            SourceFileLoader(mname, mpath).load_module()
57
58        return PLUGINS.get(ptype)
59
60class PluginMeta(type):
61    def __new__(cls, name, bases, attrs):
62        class_type = type.__new__(cls, name, bases, attrs)
63        if 'name' in attrs:
64            PLUGINS[class_type.wic_plugin_type][attrs['name']] = class_type
65
66        return class_type
67
68class ImagerPlugin(metaclass=PluginMeta):
69    wic_plugin_type = "imager"
70
71    def do_create(self):
72        raise WicError("Method %s.do_create is not implemented" %
73                       self.__class__.__name__)
74
75class SourcePlugin(metaclass=PluginMeta):
76    wic_plugin_type = "source"
77    """
78    The methods that can be implemented by --source plugins.
79
80    Any methods not implemented in a subclass inherit these.
81    """
82
83    @classmethod
84    def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
85                        bootimg_dir, kernel_dir, native_sysroot):
86        """
87        Called after all partitions have been prepared and assembled into a
88        disk image.  This provides a hook to allow finalization of a
89        disk image e.g. to write an MBR to it.
90        """
91        logger.debug("SourcePlugin: do_install_disk: disk: %s", disk_name)
92
93    @classmethod
94    def do_stage_partition(cls, part, source_params, creator, cr_workdir,
95                           oe_builddir, bootimg_dir, kernel_dir,
96                           native_sysroot):
97        """
98        Special content staging hook called before do_prepare_partition(),
99        normally empty.
100
101        Typically, a partition will just use the passed-in parame e.g
102        straight bootimg_dir, etc, but in some cases, things need to
103        be more tailored e.g. to use a deploy dir + /boot, etc.  This
104        hook allows those files to be staged in a customized fashion.
105        Not that get_bitbake_var() allows you to acces non-standard
106        variables that you might want to use for this.
107        """
108        logger.debug("SourcePlugin: do_stage_partition: part: %s", part)
109
110    @classmethod
111    def do_configure_partition(cls, part, source_params, creator, cr_workdir,
112                               oe_builddir, bootimg_dir, kernel_dir,
113                               native_sysroot):
114        """
115        Called before do_prepare_partition(), typically used to create
116        custom configuration files for a partition, for example
117        syslinux or grub config files.
118        """
119        logger.debug("SourcePlugin: do_configure_partition: part: %s", part)
120
121    @classmethod
122    def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
123                             oe_builddir, bootimg_dir, kernel_dir, rootfs_dir,
124                             native_sysroot):
125        """
126        Called to do the actual content population for a partition i.e. it
127        'prepares' the partition to be incorporated into the image.
128        """
129        logger.debug("SourcePlugin: do_prepare_partition: part: %s", part)
130
131    @classmethod
132    def do_post_partition(cls, part, source_params, creator, cr_workdir,
133                             oe_builddir, bootimg_dir, kernel_dir, rootfs_dir,
134                             native_sysroot):
135        """
136        Called after the partition is created. It is useful to add post
137        operations e.g. security signing the partition.
138        """
139        logger.debug("SourcePlugin: do_post_partition: part: %s", part)
140