summaryrefslogtreecommitdiffstats
path: root/meta/lib
diff options
context:
space:
mode:
authorRichard Purdie <richard.purdie@linuxfoundation.org>2016-01-06 22:57:46 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2016-01-11 23:26:29 +0000
commit0a4e1f968ada5099e3270ed06404d2827e9729aa (patch)
treec805d6d23f593e8647b21de154d8a5fe181cb02c /meta/lib
parentfdced52387613a09368716d1f3bb7a13a6edd46d (diff)
downloadpoky-0a4e1f968ada5099e3270ed06404d2827e9729aa.tar.gz
image: Create separate tasks for rootfs construction
This patch splits the code in lib/oe/image into separate tasks, one per image type. This removes the need for the simple task graph code and defers to the bitbake task management code to handle this instead. This is a good step forward in splitting up the monolithic code and starting to make it more accessible to people. It should also make it easier for people to hook in other tasks and processes into the rootfs code. Incidentally, the reason this code was all combined originally was due to limitations of fakeroot where if you exited the session, you lost permissions data. With pseudo this constraint was removed. We did start to rework the rootfs/image code previously and got so far with untangling it however we did prioritise some performance tweaks over splitting into separate tasks and in hindsight, this was a mistake and should have been done the other way around. That work was suspended due to changes in the people working on the project but this split has always been intended, now is the time to finish it IMO. There were some side effects of doing this: * The symlink for the manifest moves to the rootfs-postcommands class and into the manifest function. * There is no seperate "symlink removal" and "symlink creation", they are merged * The date/time stamps of the manifest and the built images can now be different since the tasks can be run separately and the datetime stamp will then be different between do_rootfs and the do_image_* tasks. (From OE-Core rev: c2dab181c1cdabac3be6197f4b9ea4235cbbc140) Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib')
-rw-r--r--meta/lib/oe/image.py412
1 files changed, 0 insertions, 412 deletions
diff --git a/meta/lib/oe/image.py b/meta/lib/oe/image.py
deleted file mode 100644
index b2b002b190..0000000000
--- a/meta/lib/oe/image.py
+++ /dev/null
@@ -1,412 +0,0 @@
1from oe.utils import execute_pre_post_process
2import os
3import subprocess
4import multiprocessing
5
6
7def generate_image(arg):
8 (type, subimages, create_img_cmd, sprefix) = arg
9
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 "")
57
58 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)
67
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)
177 else:
178 base_size = int(base_size)
179
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
269 def _write_script(self, type, cmds, sprefix=""):
270 tempdir = self.d.getVar('T', True)
271 script_name = os.path.join(tempdir, sprefix + "create_image." + type)
272 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
289 def _get_imagecmds(self, sprefix=""):
290 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
325 script_name = self._write_script(type, cmds, sprefix)
326
327 image_cmds.append((type, subimages, script_name, sprefix))
328
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 wicvars = self.d.getVar('WICVARS', True)
339 if not wicvars:
340 return
341
342 stdir = self.d.getVar('STAGING_DIR_TARGET', True)
343 outdir = os.path.join(stdir, 'imgdata')
344 bb.utils.mkdirhier(outdir)
345 basename = self.d.getVar('IMAGE_BASENAME', True)
346 with open(os.path.join(outdir, basename) + '.env', 'w') as envf:
347 for var in wicvars.split():
348 value = self.d.getVar(var, True)
349 if value:
350 envf.write('%s="%s"\n' % (var, value.strip()))
351
352 def create(self):
353 bb.note("###### Generate images #######")
354
355 image_cmd_groups = self._get_imagecmds()
356
357 # Process the debug filesystem...
358 debugfs_d = bb.data.createCopy(self.d)
359 if self.d.getVar('IMAGE_GEN_DEBUGFS', True) == "1":
360 bb.note("Processing debugfs image(s) ...")
361 orig_d = self.d
362 self.d = debugfs_d
363
364 self.d.setVar('IMAGE_ROOTFS', orig_d.getVar('IMAGE_ROOTFS', True) + '-dbg')
365 self.d.setVar('IMAGE_NAME', orig_d.getVar('IMAGE_NAME', True) + '-dbg')
366 self.d.setVar('IMAGE_LINK_NAME', orig_d.getVar('IMAGE_LINK_NAME', True) + '-dbg')
367
368 debugfs_image_fstypes = orig_d.getVar('IMAGE_FSTYPES_DEBUGFS', True)
369 if debugfs_image_fstypes:
370 self.d.setVar('IMAGE_FSTYPES', orig_d.getVar('IMAGE_FSTYPES_DEBUGFS', True))
371
372 self._remove_old_symlinks()
373
374 image_cmd_groups += self._get_imagecmds("debugfs.")
375
376 self.d = orig_d
377
378 self._write_wic_env()
379
380 for image_cmds in image_cmd_groups:
381 # create the images in parallel
382 nproc = multiprocessing.cpu_count()
383 pool = bb.utils.multiprocessingpool(nproc)
384 results = list(pool.imap(generate_image, image_cmds))
385 pool.close()
386 pool.join()
387
388 for result in results:
389 if result is not None:
390 bb.fatal(result)
391
392 for image_type, subimages, script, sprefix in image_cmds:
393 if sprefix == 'debugfs.':
394 bb.note("Creating symlinks for %s debugfs image ..." % image_type)
395 orig_d = self.d
396 self.d = debugfs_d
397 self._create_symlinks(subimages)
398 self.d = orig_d
399 else:
400 bb.note("Creating symlinks for %s image ..." % image_type)
401 self._create_symlinks(subimages)
402
403def create_image(d):
404 Image(d).create()
405
406if __name__ == "__main__":
407 """
408 Image creation can be called independent from bitbake environment.
409 """
410 """
411 TBD
412 """