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