summaryrefslogtreecommitdiffstats
path: root/meta/lib/oe/image.py
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oe/image.py')
-rw-r--r--meta/lib/oe/image.py345
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 @@
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) = 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"""
23This class will help compute IMAGE_FSTYPE dependencies and group them in batches
24that can be executed in parallel.
25
26The next example is for illustration purposes, highly unlikely to happen in real life.
27It's just one of the test cases I used to test the algorithm:
28
29For:
30IMAGE_FSTYPES = "i1 i2 i3 i4 i5"
31IMAGE_TYPEDEP_i4 = "i2"
32IMAGE_TYPEDEP_i5 = "i6 i4"
33IMAGE_TYPEDEP_i6 = "i7"
34IMAGE_TYPEDEP_i7 = "i2"
35
36We get the following list of batches that can be executed in parallel, having the
37dependencies satisfied:
38
39[['i1', 'i3', 'i2'], ['i4', 'i7'], ['i6'], ['i5']]
40"""
41class 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
135class 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
336def create_image(d):
337 Image(d).create()
338
339if __name__ == "__main__":
340 """
341 Image creation can be called independent from bitbake environment.
342 """
343 """
344 TBD
345 """