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