blob: b9eb3de5aa954d5c0a2b3aa30e3fddb412e4f9d2 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001from oe.utils import execute_pre_post_process
2import os
3import subprocess
4import multiprocessing
5
6
7def generate_image(arg):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05008 (type, subimages, create_img_cmd, sprefix) = arg
Patrick Williamsc124f4f2015-09-15 14:41:29 -05009
10 bb.note("Running image creation script for %s: %s ..." %
11 (type, create_img_cmd))
12
13 try:
14 output = subprocess.check_output(create_img_cmd,
15 stderr=subprocess.STDOUT)
16 except subprocess.CalledProcessError as e:
17 return("Error: The image creation script '%s' returned %d:\n%s" %
18 (e.cmd, e.returncode, e.output))
19
20 bb.note("Script output:\n%s" % output)
21
22 return None
23
24
25"""
26This class will help compute IMAGE_FSTYPE dependencies and group them in batches
27that can be executed in parallel.
28
29The next example is for illustration purposes, highly unlikely to happen in real life.
30It's just one of the test cases I used to test the algorithm:
31
32For:
33IMAGE_FSTYPES = "i1 i2 i3 i4 i5"
34IMAGE_TYPEDEP_i4 = "i2"
35IMAGE_TYPEDEP_i5 = "i6 i4"
36IMAGE_TYPEDEP_i6 = "i7"
37IMAGE_TYPEDEP_i7 = "i2"
38
39We get the following list of batches that can be executed in parallel, having the
40dependencies satisfied:
41
42[['i1', 'i3', 'i2'], ['i4', 'i7'], ['i6'], ['i5']]
43"""
44class ImageDepGraph(object):
45 def __init__(self, d):
46 self.d = d
47 self.graph = dict()
48 self.deps_array = dict()
49
50 def _construct_dep_graph(self, image_fstypes):
51 graph = dict()
52
53 def add_node(node):
54 base_type = self._image_base_type(node)
55 deps = (self.d.getVar('IMAGE_TYPEDEP_' + node, True) or "")
56 base_deps = (self.d.getVar('IMAGE_TYPEDEP_' + base_type, True) or "")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057
Patrick Williamsf1e5d692016-03-30 15:21:19 -050058 graph[node] = ""
59 for dep in deps.split() + base_deps.split():
60 if not dep in graph[node]:
61 if graph[node] != "":
62 graph[node] += " "
63 graph[node] += dep
64
65 if not dep in graph:
66 add_node(dep)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050067
68 for fstype in image_fstypes:
69 add_node(fstype)
70
71 return graph
72
73 def _clean_graph(self):
74 # Live and VMDK/VDI images will be processed via inheriting
75 # bbclass and does not get processed here. Remove them from the fstypes
76 # graph. Their dependencies are already added, so no worries here.
77 remove_list = (self.d.getVar('IMAGE_TYPES_MASKED', True) or "").split()
78
79 for item in remove_list:
80 self.graph.pop(item, None)
81
82 def _image_base_type(self, type):
83 ctypes = self.d.getVar('COMPRESSIONTYPES', True).split()
84 if type in ["vmdk", "vdi", "qcow2", "live", "iso", "hddimg"]:
85 type = "ext4"
86 basetype = type
87 for ctype in ctypes:
88 if type.endswith("." + ctype):
89 basetype = type[:-len("." + ctype)]
90 break
91
92 return basetype
93
94 def _compute_dependencies(self):
95 """
96 returns dict object of nodes with [no_of_depends_on, no_of_depended_by]
97 for each node
98 """
99 deps_array = dict()
100 for node in self.graph:
101 deps_array[node] = [0, 0]
102
103 for node in self.graph:
104 deps = self.graph[node].split()
105 deps_array[node][0] += len(deps)
106 for dep in deps:
107 deps_array[dep][1] += 1
108
109 return deps_array
110
111 def _sort_graph(self):
112 sorted_list = []
113 group = []
114 for node in self.graph:
115 if node not in self.deps_array:
116 continue
117
118 depends_on = self.deps_array[node][0]
119
120 if depends_on == 0:
121 group.append(node)
122
123 if len(group) == 0 and len(self.deps_array) != 0:
124 bb.fatal("possible fstype circular dependency...")
125
126 sorted_list.append(group)
127
128 # remove added nodes from deps_array
129 for item in group:
130 for node in self.graph:
131 if item in self.graph[node].split():
132 self.deps_array[node][0] -= 1
133
134 self.deps_array.pop(item, None)
135
136 if len(self.deps_array):
137 # recursive call, to find the next group
138 sorted_list += self._sort_graph()
139
140 return sorted_list
141
142 def group_fstypes(self, image_fstypes):
143 self.graph = self._construct_dep_graph(image_fstypes)
144
145 self._clean_graph()
146
147 self.deps_array = self._compute_dependencies()
148
149 alltypes = [node for node in self.graph]
150
151 return (alltypes, self._sort_graph())
152
153
154class Image(ImageDepGraph):
155 def __init__(self, d):
156 self.d = d
157
158 super(Image, self).__init__(d)
159
160 def _get_rootfs_size(self):
161 """compute the rootfs size"""
162 rootfs_alignment = int(self.d.getVar('IMAGE_ROOTFS_ALIGNMENT', True))
163 overhead_factor = float(self.d.getVar('IMAGE_OVERHEAD_FACTOR', True))
164 rootfs_req_size = int(self.d.getVar('IMAGE_ROOTFS_SIZE', True))
165 rootfs_extra_space = eval(self.d.getVar('IMAGE_ROOTFS_EXTRA_SPACE', True))
166 rootfs_maxsize = self.d.getVar('IMAGE_ROOTFS_MAXSIZE', True)
167
168 output = subprocess.check_output(['du', '-ks',
169 self.d.getVar('IMAGE_ROOTFS', True)])
170 size_kb = int(output.split()[0])
171 base_size = size_kb * overhead_factor
172 base_size = (base_size, rootfs_req_size)[base_size < rootfs_req_size] + \
173 rootfs_extra_space
174
175 if base_size != int(base_size):
176 base_size = int(base_size + 1)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500177 else:
178 base_size = int(base_size)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500179
180 base_size += rootfs_alignment - 1
181 base_size -= base_size % rootfs_alignment
182
183 # Check the rootfs size against IMAGE_ROOTFS_MAXSIZE (if set)
184 if rootfs_maxsize:
185 rootfs_maxsize_int = int(rootfs_maxsize)
186 if base_size > rootfs_maxsize_int:
187 bb.fatal("The rootfs size %d(K) overrides the max size %d(K)" % \
188 (base_size, rootfs_maxsize_int))
189
190 return base_size
191
192 def _create_symlinks(self, subimages):
193 """create symlinks to the newly created image"""
194 deploy_dir = self.d.getVar('DEPLOY_DIR_IMAGE', True)
195 img_name = self.d.getVar('IMAGE_NAME', True)
196 link_name = self.d.getVar('IMAGE_LINK_NAME', True)
197 manifest_name = self.d.getVar('IMAGE_MANIFEST', True)
198
199 os.chdir(deploy_dir)
200
201 if link_name:
202 for type in subimages:
203 if os.path.exists(img_name + ".rootfs." + type):
204 dst = link_name + "." + type
205 src = img_name + ".rootfs." + type
206 bb.note("Creating symlink: %s -> %s" % (dst, src))
207 os.symlink(src, dst)
208
209 if manifest_name is not None and \
210 os.path.exists(manifest_name) and \
211 not os.path.exists(link_name + ".manifest"):
212 os.symlink(os.path.basename(manifest_name),
213 link_name + ".manifest")
214
215 def _remove_old_symlinks(self):
216 """remove the symlinks to old binaries"""
217
218 if self.d.getVar('IMAGE_LINK_NAME', True):
219 deploy_dir = self.d.getVar('DEPLOY_DIR_IMAGE', True)
220 for img in os.listdir(deploy_dir):
221 if img.find(self.d.getVar('IMAGE_LINK_NAME', True)) == 0:
222 img = os.path.join(deploy_dir, img)
223 if os.path.islink(img):
224 if self.d.getVar('RM_OLD_IMAGE', True) == "1" and \
225 os.path.exists(os.path.realpath(img)):
226 os.remove(os.path.realpath(img))
227
228 os.remove(img)
229
230 """
231 This function will just filter out the compressed image types from the
232 fstype groups returning a (filtered_fstype_groups, cimages) tuple.
233 """
234 def _filter_out_commpressed(self, fstype_groups):
235 ctypes = self.d.getVar('COMPRESSIONTYPES', True).split()
236 cimages = {}
237
238 filtered_groups = []
239 for group in fstype_groups:
240 filtered_group = []
241 for type in group:
242 basetype = None
243 for ctype in ctypes:
244 if type.endswith("." + ctype):
245 basetype = type[:-len("." + ctype)]
246 if basetype not in filtered_group:
247 filtered_group.append(basetype)
248 if basetype not in cimages:
249 cimages[basetype] = []
250 if ctype not in cimages[basetype]:
251 cimages[basetype].append(ctype)
252 break
253 if not basetype and type not in filtered_group:
254 filtered_group.append(type)
255
256 filtered_groups.append(filtered_group)
257
258 return (filtered_groups, cimages)
259
260 def _get_image_types(self):
261 """returns a (types, cimages) tuple"""
262
263 alltypes, fstype_groups = self.group_fstypes(self.d.getVar('IMAGE_FSTYPES', True).split())
264
265 filtered_groups, cimages = self._filter_out_commpressed(fstype_groups)
266
267 return (alltypes, filtered_groups, cimages)
268
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500269 def _write_script(self, type, cmds, sprefix=""):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500270 tempdir = self.d.getVar('T', True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500271 script_name = os.path.join(tempdir, sprefix + "create_image." + type)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500272 rootfs_size = self._get_rootfs_size()
273
274 self.d.setVar('img_creation_func', '\n'.join(cmds))
275 self.d.setVarFlag('img_creation_func', 'func', 1)
276 self.d.setVarFlag('img_creation_func', 'fakeroot', 1)
277 self.d.setVar('ROOTFS_SIZE', str(rootfs_size))
278
279 with open(script_name, "w+") as script:
280 script.write("%s" % bb.build.shell_trap_code())
281 script.write("export ROOTFS_SIZE=%d\n" % rootfs_size)
282 bb.data.emit_func('img_creation_func', script, self.d)
283 script.write("img_creation_func\n")
284
285 os.chmod(script_name, 0775)
286
287 return script_name
288
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500289 def _get_imagecmds(self, sprefix=""):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500290 old_overrides = self.d.getVar('OVERRIDES', 0)
291
292 alltypes, fstype_groups, cimages = self._get_image_types()
293
294 image_cmd_groups = []
295
296 bb.note("The image creation groups are: %s" % str(fstype_groups))
297 for fstype_group in fstype_groups:
298 image_cmds = []
299 for type in fstype_group:
300 cmds = []
301 subimages = []
302
303 localdata = bb.data.createCopy(self.d)
304 localdata.setVar('OVERRIDES', '%s:%s' % (type, old_overrides))
305 bb.data.update_data(localdata)
306 localdata.setVar('type', type)
307
308 image_cmd = localdata.getVar("IMAGE_CMD", True)
309 if image_cmd:
310 cmds.append("\t" + image_cmd)
311 else:
312 bb.fatal("No IMAGE_CMD defined for IMAGE_FSTYPES entry '%s' - possibly invalid type name or missing support class" % type)
313 cmds.append(localdata.expand("\tcd ${DEPLOY_DIR_IMAGE}"))
314
315 if type in cimages:
316 for ctype in cimages[type]:
317 cmds.append("\t" + localdata.getVar("COMPRESS_CMD_" + ctype, True))
318 subimages.append(type + "." + ctype)
319
320 if type not in alltypes:
321 cmds.append(localdata.expand("\trm ${IMAGE_NAME}.rootfs.${type}"))
322 else:
323 subimages.append(type)
324
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500325 script_name = self._write_script(type, cmds, sprefix)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500327 image_cmds.append((type, subimages, script_name, sprefix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500328
329 image_cmd_groups.append(image_cmds)
330
331 return image_cmd_groups
332
333 def _write_wic_env(self):
334 """
335 Write environment variables used by wic
336 to tmp/sysroots/<machine>/imgdata/<image>.env
337 """
338 stdir = self.d.getVar('STAGING_DIR_TARGET', True)
339 outdir = os.path.join(stdir, 'imgdata')
340 if not os.path.exists(outdir):
341 os.makedirs(outdir)
342 basename = self.d.getVar('IMAGE_BASENAME', True)
343 with open(os.path.join(outdir, basename) + '.env', 'w') as envf:
344 for var in self.d.getVar('WICVARS', True).split():
345 value = self.d.getVar(var, True)
346 if value:
347 envf.write('%s="%s"\n' % (var, value.strip()))
348
349 def create(self):
350 bb.note("###### Generate images #######")
351 pre_process_cmds = self.d.getVar("IMAGE_PREPROCESS_COMMAND", True)
352 post_process_cmds = self.d.getVar("IMAGE_POSTPROCESS_COMMAND", True)
353
354 execute_pre_post_process(self.d, pre_process_cmds)
355
356 self._remove_old_symlinks()
357
358 image_cmd_groups = self._get_imagecmds()
359
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500360 # Process the debug filesystem...
361 debugfs_d = bb.data.createCopy(self.d)
362 if self.d.getVar('IMAGE_GEN_DEBUGFS', True) == "1":
363 bb.note("Processing debugfs image(s) ...")
364 orig_d = self.d
365 self.d = debugfs_d
366
367 self.d.setVar('IMAGE_ROOTFS', orig_d.getVar('IMAGE_ROOTFS', True) + '-dbg')
368 self.d.setVar('IMAGE_NAME', orig_d.getVar('IMAGE_NAME', True) + '-dbg')
369 self.d.setVar('IMAGE_LINK_NAME', orig_d.getVar('IMAGE_LINK_NAME', True) + '-dbg')
370
371 debugfs_image_fstypes = orig_d.getVar('IMAGE_FSTYPES_DEBUGFS', True)
372 if debugfs_image_fstypes:
373 self.d.setVar('IMAGE_FSTYPES', orig_d.getVar('IMAGE_FSTYPES_DEBUGFS', True))
374
375 self._remove_old_symlinks()
376
377 image_cmd_groups += self._get_imagecmds("debugfs.")
378
379 self.d = orig_d
380
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500381 self._write_wic_env()
382
383 for image_cmds in image_cmd_groups:
384 # create the images in parallel
385 nproc = multiprocessing.cpu_count()
386 pool = bb.utils.multiprocessingpool(nproc)
387 results = list(pool.imap(generate_image, image_cmds))
388 pool.close()
389 pool.join()
390
391 for result in results:
392 if result is not None:
393 bb.fatal(result)
394
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500395 for image_type, subimages, script, sprefix in image_cmds:
396 if sprefix == 'debugfs.':
397 bb.note("Creating symlinks for %s debugfs image ..." % image_type)
398 orig_d = self.d
399 self.d = debugfs_d
400 self._create_symlinks(subimages)
401 self.d = orig_d
402 else:
403 bb.note("Creating symlinks for %s image ..." % image_type)
404 self._create_symlinks(subimages)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500405
406 execute_pre_post_process(self.d, post_process_cmds)
407
408
409def create_image(d):
410 Image(d).create()
411
412if __name__ == "__main__":
413 """
414 Image creation can be called independent from bitbake environment.
415 """
416 """
417 TBD
418 """