xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/bblayers/action.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#
2*4882a593Smuzhiyun# Copyright BitBake Contributors
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
5*4882a593Smuzhiyun#
6*4882a593Smuzhiyun
7*4882a593Smuzhiyunimport fnmatch
8*4882a593Smuzhiyunimport logging
9*4882a593Smuzhiyunimport os
10*4882a593Smuzhiyunimport shutil
11*4882a593Smuzhiyunimport sys
12*4882a593Smuzhiyunimport tempfile
13*4882a593Smuzhiyun
14*4882a593Smuzhiyunimport bb.utils
15*4882a593Smuzhiyun
16*4882a593Smuzhiyunfrom bblayers.common import LayerPlugin
17*4882a593Smuzhiyun
18*4882a593Smuzhiyunlogger = logging.getLogger('bitbake-layers')
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun
21*4882a593Smuzhiyundef plugin_init(plugins):
22*4882a593Smuzhiyun    return ActionPlugin()
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun
25*4882a593Smuzhiyunclass ActionPlugin(LayerPlugin):
26*4882a593Smuzhiyun    def do_add_layer(self, args):
27*4882a593Smuzhiyun        """Add one or more layers to bblayers.conf."""
28*4882a593Smuzhiyun        layerdirs = [os.path.abspath(ldir) for ldir in args.layerdir]
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun        for layerdir in layerdirs:
31*4882a593Smuzhiyun            if not os.path.exists(layerdir):
32*4882a593Smuzhiyun                sys.stderr.write("Specified layer directory %s doesn't exist\n" % layerdir)
33*4882a593Smuzhiyun                return 1
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun            layer_conf = os.path.join(layerdir, 'conf', 'layer.conf')
36*4882a593Smuzhiyun            if not os.path.exists(layer_conf):
37*4882a593Smuzhiyun                sys.stderr.write("Specified layer directory %s doesn't contain a conf/layer.conf file\n" % layerdir)
38*4882a593Smuzhiyun                return 1
39*4882a593Smuzhiyun
40*4882a593Smuzhiyun        bblayers_conf = os.path.join('conf', 'bblayers.conf')
41*4882a593Smuzhiyun        if not os.path.exists(bblayers_conf):
42*4882a593Smuzhiyun            sys.stderr.write("Unable to find bblayers.conf\n")
43*4882a593Smuzhiyun            return 1
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun        # Back up bblayers.conf to tempdir before we add layers
46*4882a593Smuzhiyun        tempdir = tempfile.mkdtemp()
47*4882a593Smuzhiyun        backup = tempdir + "/bblayers.conf.bak"
48*4882a593Smuzhiyun        shutil.copy2(bblayers_conf, backup)
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun        try:
51*4882a593Smuzhiyun            notadded, _ = bb.utils.edit_bblayers_conf(bblayers_conf, layerdirs, None)
52*4882a593Smuzhiyun            if not (args.force or notadded):
53*4882a593Smuzhiyun                try:
54*4882a593Smuzhiyun                    self.tinfoil.run_command('parseConfiguration')
55*4882a593Smuzhiyun                except (bb.tinfoil.TinfoilUIException, bb.BBHandledException):
56*4882a593Smuzhiyun                    # Restore the back up copy of bblayers.conf
57*4882a593Smuzhiyun                    shutil.copy2(backup, bblayers_conf)
58*4882a593Smuzhiyun                    bb.fatal("Parse failure with the specified layer added, exiting.")
59*4882a593Smuzhiyun                else:
60*4882a593Smuzhiyun                    for item in notadded:
61*4882a593Smuzhiyun                        sys.stderr.write("Specified layer %s is already in BBLAYERS\n" % item)
62*4882a593Smuzhiyun        finally:
63*4882a593Smuzhiyun            # Remove the back up copy of bblayers.conf
64*4882a593Smuzhiyun            shutil.rmtree(tempdir)
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun    def do_remove_layer(self, args):
67*4882a593Smuzhiyun        """Remove one or more layers from bblayers.conf."""
68*4882a593Smuzhiyun        bblayers_conf = os.path.join('conf', 'bblayers.conf')
69*4882a593Smuzhiyun        if not os.path.exists(bblayers_conf):
70*4882a593Smuzhiyun            sys.stderr.write("Unable to find bblayers.conf\n")
71*4882a593Smuzhiyun            return 1
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun        layerdirs = []
74*4882a593Smuzhiyun        for item in args.layerdir:
75*4882a593Smuzhiyun            if item.startswith('*'):
76*4882a593Smuzhiyun                layerdir = item
77*4882a593Smuzhiyun            elif not '/' in item:
78*4882a593Smuzhiyun                layerdir = '*/%s' % item
79*4882a593Smuzhiyun            else:
80*4882a593Smuzhiyun                layerdir = os.path.abspath(item)
81*4882a593Smuzhiyun            layerdirs.append(layerdir)
82*4882a593Smuzhiyun        (_, notremoved) = bb.utils.edit_bblayers_conf(bblayers_conf, None, layerdirs)
83*4882a593Smuzhiyun        if notremoved:
84*4882a593Smuzhiyun            for item in notremoved:
85*4882a593Smuzhiyun                sys.stderr.write("No layers matching %s found in BBLAYERS\n" % item)
86*4882a593Smuzhiyun            return 1
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun    def do_flatten(self, args):
89*4882a593Smuzhiyun        """flatten layer configuration into a separate output directory.
90*4882a593Smuzhiyun
91*4882a593SmuzhiyunTakes the specified layers (or all layers in the current layer
92*4882a593Smuzhiyunconfiguration if none are specified) and builds a "flattened" directory
93*4882a593Smuzhiyuncontaining the contents of all layers, with any overlayed recipes removed
94*4882a593Smuzhiyunand bbappends appended to the corresponding recipes. Note that some manual
95*4882a593Smuzhiyuncleanup may still be necessary afterwards, in particular:
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun* where non-recipe files (such as patches) are overwritten (the flatten
98*4882a593Smuzhiyun  command will show a warning for these)
99*4882a593Smuzhiyun* where anything beyond the normal layer setup has been added to
100*4882a593Smuzhiyun  layer.conf (only the lowest priority number layer's layer.conf is used)
101*4882a593Smuzhiyun* overridden/appended items from bbappends will need to be tidied up
102*4882a593Smuzhiyun* when the flattened layers do not have the same directory structure (the
103*4882a593Smuzhiyun  flatten command should show a warning when this will cause a problem)
104*4882a593Smuzhiyun
105*4882a593SmuzhiyunWarning: if you flatten several layers where another layer is intended to
106*4882a593Smuzhiyunbe used "inbetween" them (in layer priority order) such that recipes /
107*4882a593Smuzhiyunbbappends in the layers interact, and then attempt to use the new output
108*4882a593Smuzhiyunlayer together with that other layer, you may no longer get the same
109*4882a593Smuzhiyunbuild results (as the layer priority order has effectively changed).
110*4882a593Smuzhiyun"""
111*4882a593Smuzhiyun        if len(args.layer) == 1:
112*4882a593Smuzhiyun            logger.error('If you specify layers to flatten you must specify at least two')
113*4882a593Smuzhiyun            return 1
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun        outputdir = args.outputdir
116*4882a593Smuzhiyun        if os.path.exists(outputdir) and os.listdir(outputdir):
117*4882a593Smuzhiyun            logger.error('Directory %s exists and is non-empty, please clear it out first' % outputdir)
118*4882a593Smuzhiyun            return 1
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun        layers = self.bblayers
121*4882a593Smuzhiyun        if len(args.layer) > 2:
122*4882a593Smuzhiyun            layernames = args.layer
123*4882a593Smuzhiyun            found_layernames = []
124*4882a593Smuzhiyun            found_layerdirs = []
125*4882a593Smuzhiyun            for layerdir in layers:
126*4882a593Smuzhiyun                layername = self.get_layer_name(layerdir)
127*4882a593Smuzhiyun                if layername in layernames:
128*4882a593Smuzhiyun                    found_layerdirs.append(layerdir)
129*4882a593Smuzhiyun                    found_layernames.append(layername)
130*4882a593Smuzhiyun
131*4882a593Smuzhiyun            for layername in layernames:
132*4882a593Smuzhiyun                if not layername in found_layernames:
133*4882a593Smuzhiyun                    logger.error('Unable to find layer %s in current configuration, please run "%s show-layers" to list configured layers' % (layername, os.path.basename(sys.argv[0])))
134*4882a593Smuzhiyun                    return
135*4882a593Smuzhiyun            layers = found_layerdirs
136*4882a593Smuzhiyun        else:
137*4882a593Smuzhiyun            layernames = []
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun        # Ensure a specified path matches our list of layers
140*4882a593Smuzhiyun        def layer_path_match(path):
141*4882a593Smuzhiyun            for layerdir in layers:
142*4882a593Smuzhiyun                if path.startswith(os.path.join(layerdir, '')):
143*4882a593Smuzhiyun                    return layerdir
144*4882a593Smuzhiyun            return None
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun        applied_appends = []
147*4882a593Smuzhiyun        for layer in layers:
148*4882a593Smuzhiyun            overlayed = set()
149*4882a593Smuzhiyun            for mc in self.tinfoil.cooker.multiconfigs:
150*4882a593Smuzhiyun                for f in self.tinfoil.cooker.collections[mc].overlayed.keys():
151*4882a593Smuzhiyun                    for of in self.tinfoil.cooker.collections[mc].overlayed[f]:
152*4882a593Smuzhiyun                        if of.startswith(layer):
153*4882a593Smuzhiyun                            overlayed.add(of)
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun            logger.plain('Copying files from %s...' % layer )
156*4882a593Smuzhiyun            for root, dirs, files in os.walk(layer):
157*4882a593Smuzhiyun                if '.git' in dirs:
158*4882a593Smuzhiyun                    dirs.remove('.git')
159*4882a593Smuzhiyun                if '.hg' in dirs:
160*4882a593Smuzhiyun                    dirs.remove('.hg')
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun                for f1 in files:
163*4882a593Smuzhiyun                    f1full = os.sep.join([root, f1])
164*4882a593Smuzhiyun                    if f1full in overlayed:
165*4882a593Smuzhiyun                        logger.plain('  Skipping overlayed file %s' % f1full )
166*4882a593Smuzhiyun                    else:
167*4882a593Smuzhiyun                        ext = os.path.splitext(f1)[1]
168*4882a593Smuzhiyun                        if ext != '.bbappend':
169*4882a593Smuzhiyun                            fdest = f1full[len(layer):]
170*4882a593Smuzhiyun                            fdest = os.path.normpath(os.sep.join([outputdir,fdest]))
171*4882a593Smuzhiyun                            bb.utils.mkdirhier(os.path.dirname(fdest))
172*4882a593Smuzhiyun                            if os.path.exists(fdest):
173*4882a593Smuzhiyun                                if f1 == 'layer.conf' and root.endswith('/conf'):
174*4882a593Smuzhiyun                                    logger.plain('  Skipping layer config file %s' % f1full )
175*4882a593Smuzhiyun                                    continue
176*4882a593Smuzhiyun                                else:
177*4882a593Smuzhiyun                                    logger.warning('Overwriting file %s', fdest)
178*4882a593Smuzhiyun                            bb.utils.copyfile(f1full, fdest)
179*4882a593Smuzhiyun                            if ext == '.bb':
180*4882a593Smuzhiyun                                appends = set()
181*4882a593Smuzhiyun                                for mc in self.tinfoil.cooker.multiconfigs:
182*4882a593Smuzhiyun                                    appends |= set(self.tinfoil.cooker.collections[mc].get_file_appends(f1full))
183*4882a593Smuzhiyun                                for append in appends:
184*4882a593Smuzhiyun                                    if layer_path_match(append):
185*4882a593Smuzhiyun                                        logger.plain('  Applying append %s to %s' % (append, fdest))
186*4882a593Smuzhiyun                                        self.apply_append(append, fdest)
187*4882a593Smuzhiyun                                        applied_appends.append(append)
188*4882a593Smuzhiyun
189*4882a593Smuzhiyun        # Take care of when some layers are excluded and yet we have included bbappends for those recipes
190*4882a593Smuzhiyun        bbappends = set()
191*4882a593Smuzhiyun        for mc in self.tinfoil.cooker.multiconfigs:
192*4882a593Smuzhiyun            bbappends |= set(self.tinfoil.cooker.collections[mc].bbappends)
193*4882a593Smuzhiyun
194*4882a593Smuzhiyun        for b in bbappends:
195*4882a593Smuzhiyun            (recipename, appendname) = b
196*4882a593Smuzhiyun            if appendname not in applied_appends:
197*4882a593Smuzhiyun                first_append = None
198*4882a593Smuzhiyun                layer = layer_path_match(appendname)
199*4882a593Smuzhiyun                if layer:
200*4882a593Smuzhiyun                    if first_append:
201*4882a593Smuzhiyun                        self.apply_append(appendname, first_append)
202*4882a593Smuzhiyun                    else:
203*4882a593Smuzhiyun                        fdest = appendname[len(layer):]
204*4882a593Smuzhiyun                        fdest = os.path.normpath(os.sep.join([outputdir,fdest]))
205*4882a593Smuzhiyun                        bb.utils.mkdirhier(os.path.dirname(fdest))
206*4882a593Smuzhiyun                        bb.utils.copyfile(appendname, fdest)
207*4882a593Smuzhiyun                        first_append = fdest
208*4882a593Smuzhiyun
209*4882a593Smuzhiyun        # Get the regex for the first layer in our list (which is where the conf/layer.conf file will
210*4882a593Smuzhiyun        # have come from)
211*4882a593Smuzhiyun        first_regex = None
212*4882a593Smuzhiyun        layerdir = layers[0]
213*4882a593Smuzhiyun        for layername, pattern, regex, _ in self.tinfoil.cooker.bbfile_config_priorities:
214*4882a593Smuzhiyun            if regex.match(os.path.join(layerdir, 'test')):
215*4882a593Smuzhiyun                first_regex = regex
216*4882a593Smuzhiyun                break
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun        if first_regex:
219*4882a593Smuzhiyun            # Find the BBFILES entries that match (which will have come from this conf/layer.conf file)
220*4882a593Smuzhiyun            bbfiles = str(self.tinfoil.config_data.getVar('BBFILES')).split()
221*4882a593Smuzhiyun            bbfiles_layer = []
222*4882a593Smuzhiyun            for item in bbfiles:
223*4882a593Smuzhiyun                if first_regex.match(item):
224*4882a593Smuzhiyun                    newpath = os.path.join(outputdir, item[len(layerdir)+1:])
225*4882a593Smuzhiyun                    bbfiles_layer.append(newpath)
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun            if bbfiles_layer:
228*4882a593Smuzhiyun                # Check that all important layer files match BBFILES
229*4882a593Smuzhiyun                for root, dirs, files in os.walk(outputdir):
230*4882a593Smuzhiyun                    for f1 in files:
231*4882a593Smuzhiyun                        ext = os.path.splitext(f1)[1]
232*4882a593Smuzhiyun                        if ext in ['.bb', '.bbappend']:
233*4882a593Smuzhiyun                            f1full = os.sep.join([root, f1])
234*4882a593Smuzhiyun                            entry_found = False
235*4882a593Smuzhiyun                            for item in bbfiles_layer:
236*4882a593Smuzhiyun                                if fnmatch.fnmatch(f1full, item):
237*4882a593Smuzhiyun                                    entry_found = True
238*4882a593Smuzhiyun                                    break
239*4882a593Smuzhiyun                            if not entry_found:
240*4882a593Smuzhiyun                                logger.warning("File %s does not match the flattened layer's BBFILES setting, you may need to edit conf/layer.conf or move the file elsewhere" % f1full)
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun    def get_file_layer(self, filename):
243*4882a593Smuzhiyun        layerdir = self.get_file_layerdir(filename)
244*4882a593Smuzhiyun        if layerdir:
245*4882a593Smuzhiyun            return self.get_layer_name(layerdir)
246*4882a593Smuzhiyun        else:
247*4882a593Smuzhiyun            return '?'
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun    def get_file_layerdir(self, filename):
250*4882a593Smuzhiyun        layer = bb.utils.get_file_layer(filename, self.tinfoil.config_data)
251*4882a593Smuzhiyun        return self.bbfile_collections.get(layer, None)
252*4882a593Smuzhiyun
253*4882a593Smuzhiyun    def apply_append(self, appendname, recipename):
254*4882a593Smuzhiyun        with open(appendname, 'r') as appendfile:
255*4882a593Smuzhiyun            with open(recipename, 'a') as recipefile:
256*4882a593Smuzhiyun                recipefile.write('\n')
257*4882a593Smuzhiyun                recipefile.write('##### bbappended from %s #####\n' % self.get_file_layer(appendname))
258*4882a593Smuzhiyun                recipefile.writelines(appendfile.readlines())
259*4882a593Smuzhiyun
260*4882a593Smuzhiyun    def register_commands(self, sp):
261*4882a593Smuzhiyun        parser_add_layer = self.add_command(sp, 'add-layer', self.do_add_layer, parserecipes=False)
262*4882a593Smuzhiyun        parser_add_layer.add_argument('layerdir', nargs='+', help='Layer directory/directories to add')
263*4882a593Smuzhiyun
264*4882a593Smuzhiyun        parser_remove_layer = self.add_command(sp, 'remove-layer', self.do_remove_layer, parserecipes=False)
265*4882a593Smuzhiyun        parser_remove_layer.add_argument('layerdir', nargs='+', help='Layer directory/directories to remove (wildcards allowed, enclose in quotes to avoid shell expansion)')
266*4882a593Smuzhiyun        parser_remove_layer.set_defaults(func=self.do_remove_layer)
267*4882a593Smuzhiyun
268*4882a593Smuzhiyun        parser_flatten = self.add_command(sp, 'flatten', self.do_flatten)
269*4882a593Smuzhiyun        parser_flatten.add_argument('layer', nargs='*', help='Optional layer(s) to flatten (otherwise all are flattened)')
270*4882a593Smuzhiyun        parser_flatten.add_argument('outputdir', help='Output directory')
271