diff options
Diffstat (limited to 'meta/lib/oe/image.py')
-rw-r--r-- | meta/lib/oe/image.py | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/meta/lib/oe/image.py b/meta/lib/oe/image.py new file mode 100644 index 0000000000..7e080b00dd --- /dev/null +++ b/meta/lib/oe/image.py | |||
@@ -0,0 +1,345 @@ | |||
1 | from oe.utils import execute_pre_post_process | ||
2 | import os | ||
3 | import subprocess | ||
4 | import multiprocessing | ||
5 | |||
6 | |||
7 | def generate_image(arg): | ||
8 | (type, subimages, create_img_cmd) = arg | ||
9 | |||
10 | bb.note("Running image creation script for %s: %s ..." % | ||
11 | (type, create_img_cmd)) | ||
12 | |||
13 | try: | ||
14 | subprocess.check_output(create_img_cmd, stderr=subprocess.STDOUT) | ||
15 | except subprocess.CalledProcessError as e: | ||
16 | return("Error: The image creation script '%s' returned %d:\n%s" % | ||
17 | (e.cmd, e.returncode, e.output)) | ||
18 | |||
19 | return None | ||
20 | |||
21 | |||
22 | """ | ||
23 | This class will help compute IMAGE_FSTYPE dependencies and group them in batches | ||
24 | that can be executed in parallel. | ||
25 | |||
26 | The next example is for illustration purposes, highly unlikely to happen in real life. | ||
27 | It's just one of the test cases I used to test the algorithm: | ||
28 | |||
29 | For: | ||
30 | IMAGE_FSTYPES = "i1 i2 i3 i4 i5" | ||
31 | IMAGE_TYPEDEP_i4 = "i2" | ||
32 | IMAGE_TYPEDEP_i5 = "i6 i4" | ||
33 | IMAGE_TYPEDEP_i6 = "i7" | ||
34 | IMAGE_TYPEDEP_i7 = "i2" | ||
35 | |||
36 | We get the following list of batches that can be executed in parallel, having the | ||
37 | dependencies satisfied: | ||
38 | |||
39 | [['i1', 'i3', 'i2'], ['i4', 'i7'], ['i6'], ['i5']] | ||
40 | """ | ||
41 | class ImageDepGraph(object): | ||
42 | def __init__(self, d): | ||
43 | self.d = d | ||
44 | self.graph = dict() | ||
45 | self.deps_array = dict() | ||
46 | |||
47 | def _construct_dep_graph(self, image_fstypes): | ||
48 | graph = dict() | ||
49 | |||
50 | def add_node(node): | ||
51 | deps = (self.d.getVar('IMAGE_TYPEDEP_' + node, True) or "") | ||
52 | if deps != "": | ||
53 | graph[node] = deps | ||
54 | |||
55 | for dep in deps.split(): | ||
56 | if not dep in graph: | ||
57 | add_node(dep) | ||
58 | else: | ||
59 | graph[node] = "" | ||
60 | |||
61 | for fstype in image_fstypes: | ||
62 | add_node(fstype) | ||
63 | |||
64 | return graph | ||
65 | |||
66 | def _clean_graph(self): | ||
67 | # Live and VMDK images will be processed via inheriting | ||
68 | # bbclass and does not get processed here. Remove them from the fstypes | ||
69 | # graph. Their dependencies are already added, so no worries here. | ||
70 | remove_list = (self.d.getVar('IMAGE_TYPES_MASKED', True) or "").split() | ||
71 | |||
72 | for item in remove_list: | ||
73 | self.graph.pop(item, None) | ||
74 | |||
75 | def _compute_dependencies(self): | ||
76 | """ | ||
77 | returns dict object of nodes with [no_of_depends_on, no_of_depended_by] | ||
78 | for each node | ||
79 | """ | ||
80 | deps_array = dict() | ||
81 | for node in self.graph: | ||
82 | deps_array[node] = [0, 0] | ||
83 | |||
84 | for node in self.graph: | ||
85 | deps = self.graph[node].split() | ||
86 | deps_array[node][0] += len(deps) | ||
87 | for dep in deps: | ||
88 | deps_array[dep][1] += 1 | ||
89 | |||
90 | return deps_array | ||
91 | |||
92 | def _sort_graph(self): | ||
93 | sorted_list = [] | ||
94 | group = [] | ||
95 | for node in self.graph: | ||
96 | if node not in self.deps_array: | ||
97 | continue | ||
98 | |||
99 | depends_on = self.deps_array[node][0] | ||
100 | |||
101 | if depends_on == 0: | ||
102 | group.append(node) | ||
103 | |||
104 | if len(group) == 0 and len(self.deps_array) != 0: | ||
105 | bb.fatal("possible fstype circular dependency...") | ||
106 | |||
107 | sorted_list.append(group) | ||
108 | |||
109 | # remove added nodes from deps_array | ||
110 | for item in group: | ||
111 | for node in self.graph: | ||
112 | if item in self.graph[node].split(): | ||
113 | self.deps_array[node][0] -= 1 | ||
114 | |||
115 | self.deps_array.pop(item, None) | ||
116 | |||
117 | if len(self.deps_array): | ||
118 | # recursive call, to find the next group | ||
119 | sorted_list += self._sort_graph() | ||
120 | |||
121 | return sorted_list | ||
122 | |||
123 | def group_fstypes(self, image_fstypes): | ||
124 | self.graph = self._construct_dep_graph(image_fstypes) | ||
125 | |||
126 | self._clean_graph() | ||
127 | |||
128 | self.deps_array = self._compute_dependencies() | ||
129 | |||
130 | alltypes = [node for node in self.graph] | ||
131 | |||
132 | return (alltypes, self._sort_graph()) | ||
133 | |||
134 | |||
135 | class Image(ImageDepGraph): | ||
136 | def __init__(self, d): | ||
137 | self.d = d | ||
138 | |||
139 | super(Image, self).__init__(d) | ||
140 | |||
141 | def _get_rootfs_size(self): | ||
142 | """compute the rootfs size""" | ||
143 | rootfs_alignment = int(self.d.getVar('IMAGE_ROOTFS_ALIGNMENT', True)) | ||
144 | overhead_factor = float(self.d.getVar('IMAGE_OVERHEAD_FACTOR', True)) | ||
145 | rootfs_req_size = int(self.d.getVar('IMAGE_ROOTFS_SIZE', True)) | ||
146 | rootfs_extra_space = eval(self.d.getVar('IMAGE_ROOTFS_EXTRA_SPACE', True)) | ||
147 | rootfs_maxsize = self.d.getVar('IMAGE_ROOTFS_MAXSIZE', True) | ||
148 | |||
149 | output = subprocess.check_output(['du', '-ks', | ||
150 | self.d.getVar('IMAGE_ROOTFS', True)]) | ||
151 | size_kb = int(output.split()[0]) | ||
152 | base_size = size_kb * overhead_factor | ||
153 | base_size = (base_size, rootfs_req_size)[base_size < rootfs_req_size] + \ | ||
154 | rootfs_extra_space | ||
155 | |||
156 | if base_size != int(base_size): | ||
157 | base_size = int(base_size + 1) | ||
158 | |||
159 | base_size += rootfs_alignment - 1 | ||
160 | base_size -= base_size % rootfs_alignment | ||
161 | |||
162 | # Check the rootfs size against IMAGE_ROOTFS_MAXSIZE (if set) | ||
163 | if rootfs_maxsize: | ||
164 | rootfs_maxsize_int = int(rootfs_maxsize) | ||
165 | if base_size > rootfs_maxsize_int: | ||
166 | bb.fatal("The rootfs size %d(K) overrides the max size %d(K)" % \ | ||
167 | (base_size, rootfs_maxsize_int)) | ||
168 | |||
169 | return base_size | ||
170 | |||
171 | def _create_symlinks(self, subimages): | ||
172 | """create symlinks to the newly created image""" | ||
173 | deploy_dir = self.d.getVar('DEPLOY_DIR_IMAGE', True) | ||
174 | img_name = self.d.getVar('IMAGE_NAME', True) | ||
175 | link_name = self.d.getVar('IMAGE_LINK_NAME', True) | ||
176 | manifest_name = self.d.getVar('IMAGE_MANIFEST', True) | ||
177 | |||
178 | os.chdir(deploy_dir) | ||
179 | |||
180 | if link_name is not None: | ||
181 | for type in subimages: | ||
182 | if os.path.exists(img_name + ".rootfs." + type): | ||
183 | dst = link_name + "." + type | ||
184 | src = img_name + ".rootfs." + type | ||
185 | bb.note("Creating symlink: %s -> %s" % (dst, src)) | ||
186 | os.symlink(src, dst) | ||
187 | |||
188 | if manifest_name is not None and \ | ||
189 | os.path.exists(manifest_name) and \ | ||
190 | not os.path.exists(link_name + ".manifest"): | ||
191 | os.symlink(os.path.basename(manifest_name), | ||
192 | link_name + ".manifest") | ||
193 | |||
194 | def _remove_old_symlinks(self): | ||
195 | """remove the symlinks to old binaries""" | ||
196 | |||
197 | if self.d.getVar('IMAGE_LINK_NAME', True): | ||
198 | deploy_dir = self.d.getVar('DEPLOY_DIR_IMAGE', True) | ||
199 | for img in os.listdir(deploy_dir): | ||
200 | if img.find(self.d.getVar('IMAGE_LINK_NAME', True)) == 0: | ||
201 | img = os.path.join(deploy_dir, img) | ||
202 | if os.path.islink(img): | ||
203 | if self.d.getVar('RM_OLD_IMAGE', True) == "1" and \ | ||
204 | os.path.exists(os.path.realpath(img)): | ||
205 | os.remove(os.path.realpath(img)) | ||
206 | |||
207 | os.remove(img) | ||
208 | |||
209 | """ | ||
210 | This function will just filter out the compressed image types from the | ||
211 | fstype groups returning a (filtered_fstype_groups, cimages) tuple. | ||
212 | """ | ||
213 | def _filter_out_commpressed(self, fstype_groups): | ||
214 | ctypes = self.d.getVar('COMPRESSIONTYPES', True).split() | ||
215 | cimages = {} | ||
216 | |||
217 | filtered_groups = [] | ||
218 | for group in fstype_groups: | ||
219 | filtered_group = [] | ||
220 | for type in group: | ||
221 | basetype = None | ||
222 | for ctype in ctypes: | ||
223 | if type.endswith("." + ctype): | ||
224 | basetype = type[:-len("." + ctype)] | ||
225 | if basetype not in filtered_group: | ||
226 | filtered_group.append(basetype) | ||
227 | if basetype not in cimages: | ||
228 | cimages[basetype] = [] | ||
229 | if ctype not in cimages[basetype]: | ||
230 | cimages[basetype].append(ctype) | ||
231 | break | ||
232 | if not basetype and type not in filtered_group: | ||
233 | filtered_group.append(type) | ||
234 | |||
235 | filtered_groups.append(filtered_group) | ||
236 | |||
237 | return (filtered_groups, cimages) | ||
238 | |||
239 | def _get_image_types(self): | ||
240 | """returns a (types, cimages) tuple""" | ||
241 | |||
242 | alltypes, fstype_groups = self.group_fstypes(self.d.getVar('IMAGE_FSTYPES', True).split()) | ||
243 | |||
244 | filtered_groups, cimages = self._filter_out_commpressed(fstype_groups) | ||
245 | |||
246 | return (alltypes, filtered_groups, cimages) | ||
247 | |||
248 | def _write_script(self, type, cmds): | ||
249 | tempdir = self.d.getVar('T', True) | ||
250 | script_name = os.path.join(tempdir, "create_image." + type) | ||
251 | |||
252 | self.d.setVar('img_creation_func', '\n'.join(cmds)) | ||
253 | self.d.setVarFlag('img_creation_func', 'func', 1) | ||
254 | self.d.setVarFlag('img_creation_func', 'fakeroot', 1) | ||
255 | |||
256 | with open(script_name, "w+") as script: | ||
257 | script.write("%s" % bb.build.shell_trap_code()) | ||
258 | script.write("export ROOTFS_SIZE=%d\n" % self._get_rootfs_size()) | ||
259 | bb.data.emit_func('img_creation_func', script, self.d) | ||
260 | script.write("img_creation_func\n") | ||
261 | |||
262 | os.chmod(script_name, 0775) | ||
263 | |||
264 | return script_name | ||
265 | |||
266 | def _get_imagecmds(self): | ||
267 | old_overrides = self.d.getVar('OVERRIDES', 0) | ||
268 | |||
269 | alltypes, fstype_groups, cimages = self._get_image_types() | ||
270 | |||
271 | image_cmd_groups = [] | ||
272 | |||
273 | bb.note("The image creation groups are: %s" % str(fstype_groups)) | ||
274 | for fstype_group in fstype_groups: | ||
275 | image_cmds = [] | ||
276 | for type in fstype_group: | ||
277 | cmds = [] | ||
278 | subimages = [] | ||
279 | |||
280 | localdata = bb.data.createCopy(self.d) | ||
281 | localdata.setVar('OVERRIDES', '%s:%s' % (type, old_overrides)) | ||
282 | bb.data.update_data(localdata) | ||
283 | localdata.setVar('type', type) | ||
284 | |||
285 | cmds.append("\t" + localdata.getVar("IMAGE_CMD", True)) | ||
286 | cmds.append(localdata.expand("\tcd ${DEPLOY_DIR_IMAGE}")) | ||
287 | |||
288 | if type in cimages: | ||
289 | for ctype in cimages[type]: | ||
290 | cmds.append("\t" + localdata.getVar("COMPRESS_CMD_" + ctype, True)) | ||
291 | subimages.append(type + "." + ctype) | ||
292 | |||
293 | if type not in alltypes: | ||
294 | cmds.append(localdata.expand("\trm ${IMAGE_NAME}.rootfs.${type}")) | ||
295 | else: | ||
296 | subimages.append(type) | ||
297 | |||
298 | script_name = self._write_script(type, cmds) | ||
299 | |||
300 | image_cmds.append((type, subimages, script_name)) | ||
301 | |||
302 | image_cmd_groups.append(image_cmds) | ||
303 | |||
304 | return image_cmd_groups | ||
305 | |||
306 | def create(self): | ||
307 | bb.note("###### Generate images #######") | ||
308 | pre_process_cmds = self.d.getVar("IMAGE_PREPROCESS_COMMAND", True) | ||
309 | post_process_cmds = self.d.getVar("IMAGE_POSTPROCESS_COMMAND", True) | ||
310 | |||
311 | execute_pre_post_process(self.d, pre_process_cmds) | ||
312 | |||
313 | self._remove_old_symlinks() | ||
314 | |||
315 | image_cmd_groups = self._get_imagecmds() | ||
316 | |||
317 | for image_cmds in image_cmd_groups: | ||
318 | # create the images in parallel | ||
319 | nproc = multiprocessing.cpu_count() | ||
320 | pool = bb.utils.multiprocessingpool(nproc) | ||
321 | results = list(pool.imap(generate_image, image_cmds)) | ||
322 | pool.close() | ||
323 | pool.join() | ||
324 | |||
325 | for result in results: | ||
326 | if result is not None: | ||
327 | bb.fatal(result) | ||
328 | |||
329 | for image_type, subimages, script in image_cmds: | ||
330 | bb.note("Creating symlinks for %s image ..." % image_type) | ||
331 | self._create_symlinks(subimages) | ||
332 | |||
333 | execute_pre_post_process(self.d, post_process_cmds) | ||
334 | |||
335 | |||
336 | def create_image(d): | ||
337 | Image(d).create() | ||
338 | |||
339 | if __name__ == "__main__": | ||
340 | """ | ||
341 | Image creation can be called independent from bitbake environment. | ||
342 | """ | ||
343 | """ | ||
344 | TBD | ||
345 | """ | ||