diff options
-rw-r--r-- | meta/lib/oe/image.py | 282 |
1 files changed, 191 insertions, 91 deletions
diff --git a/meta/lib/oe/image.py b/meta/lib/oe/image.py index c15296f5c0..488683e42a 100644 --- a/meta/lib/oe/image.py +++ b/meta/lib/oe/image.py | |||
@@ -19,9 +19,124 @@ def generate_image(arg): | |||
19 | return None | 19 | return None |
20 | 20 | ||
21 | 21 | ||
22 | class Image(object): | 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): | ||
23 | def __init__(self, d): | 42 | def __init__(self, d): |
24 | self.d = 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]: | ||
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) | ||
25 | 140 | ||
26 | def _get_rootfs_size(self): | 141 | def _get_rootfs_size(self): |
27 | """compute the rootfs size""" | 142 | """compute the rootfs size""" |
@@ -82,66 +197,44 @@ class Image(object): | |||
82 | 197 | ||
83 | os.remove(img) | 198 | os.remove(img) |
84 | 199 | ||
200 | """ | ||
201 | This function will just filter out the compressed image types from the | ||
202 | fstype groups returning a (filtered_fstype_groups, cimages) tuple. | ||
203 | """ | ||
204 | def _filter_out_commpressed(self, fstype_groups): | ||
205 | ctypes = self.d.getVar('COMPRESSIONTYPES', True).split() | ||
206 | cimages = {} | ||
207 | |||
208 | filtered_groups = [] | ||
209 | for group in fstype_groups: | ||
210 | filtered_group = [] | ||
211 | for type in group: | ||
212 | basetype = None | ||
213 | for ctype in ctypes: | ||
214 | if type.endswith("." + ctype): | ||
215 | basetype = type[:-len("." + ctype)] | ||
216 | if basetype not in filtered_group: | ||
217 | filtered_group.append(basetype) | ||
218 | if basetype not in cimages: | ||
219 | cimages[basetype] = [] | ||
220 | if ctype not in cimages[basetype]: | ||
221 | cimages[basetype].append(ctype) | ||
222 | break | ||
223 | if not basetype and type not in filtered_group: | ||
224 | filtered_group.append(type) | ||
225 | |||
226 | filtered_groups.append(filtered_group) | ||
227 | |||
228 | return (filtered_groups, cimages) | ||
229 | |||
85 | def _get_image_types(self): | 230 | def _get_image_types(self): |
86 | """returns a (types, cimages) tuple""" | 231 | """returns a (types, cimages) tuple""" |
87 | 232 | ||
88 | alltypes = self.d.getVar('IMAGE_FSTYPES', True).split() | 233 | alltypes, fstype_groups = self.group_fstypes(self.d.getVar('IMAGE_FSTYPES', True).split()) |
89 | types = [] | ||
90 | ctypes = self.d.getVar('COMPRESSIONTYPES', True).split() | ||
91 | cimages = {} | ||
92 | 234 | ||
93 | # Image type b depends on a having been generated first | 235 | filtered_groups, cimages = self._filter_out_commpressed(fstype_groups) |
94 | def addtypedepends(a, b): | ||
95 | if a in alltypes: | ||
96 | alltypes.remove(a) | ||
97 | if b not in alltypes: | ||
98 | alltypes.append(b) | ||
99 | alltypes.append(a) | ||
100 | |||
101 | # The elf image depends on the cpio.gz image already having | ||
102 | # been created, so we add that explicit ordering here. | ||
103 | addtypedepends("elf", "cpio.gz") | ||
104 | |||
105 | # Filter out all the compressed images from alltypes | ||
106 | for type in alltypes: | ||
107 | basetype = None | ||
108 | for ctype in ctypes: | ||
109 | if type.endswith("." + ctype): | ||
110 | basetype = type[:-len("." + ctype)] | ||
111 | if basetype not in types: | ||
112 | types.append(basetype) | ||
113 | if basetype not in cimages: | ||
114 | cimages[basetype] = [] | ||
115 | if ctype not in cimages[basetype]: | ||
116 | cimages[basetype].append(ctype) | ||
117 | break | ||
118 | if not basetype and type not in types: | ||
119 | types.append(type) | ||
120 | 236 | ||
121 | # Live and VMDK images will be processed via inheriting | 237 | return (alltypes, filtered_groups, cimages) |
122 | # bbclass and does not get processed here. | ||
123 | # vmdk depend on live images also depend on ext3 so ensure its present | ||
124 | # Note: we need to ensure ext3 is in alltypes, otherwise, subimages may | ||
125 | # not contain ext3 and the .rootfs.ext3 file won't be created. | ||
126 | if "vmdk" in types: | ||
127 | if "ext3" not in types: | ||
128 | types.append("ext3") | ||
129 | if "ext3" not in alltypes: | ||
130 | alltypes.append("ext3") | ||
131 | types.remove("vmdk") | ||
132 | if "live" in types or "iso" in types or "hddimg" in types: | ||
133 | if "ext3" not in types: | ||
134 | types.append("ext3") | ||
135 | if "ext3" not in alltypes: | ||
136 | alltypes.append("ext3") | ||
137 | if "live" in types: | ||
138 | types.remove("live") | ||
139 | if "iso" in types: | ||
140 | types.remove("iso") | ||
141 | if "hddimg" in types: | ||
142 | types.remove("hddimg") | ||
143 | |||
144 | return (alltypes, types, cimages) | ||
145 | 238 | ||
146 | def _write_script(self, type, cmds): | 239 | def _write_script(self, type, cmds): |
147 | tempdir = self.d.getVar('T', True) | 240 | tempdir = self.d.getVar('T', True) |
@@ -164,36 +257,42 @@ class Image(object): | |||
164 | def _get_imagecmds(self): | 257 | def _get_imagecmds(self): |
165 | old_overrides = self.d.getVar('OVERRIDES', 0) | 258 | old_overrides = self.d.getVar('OVERRIDES', 0) |
166 | 259 | ||
167 | alltypes, types, cimages = self._get_image_types() | 260 | alltypes, fstype_groups, cimages = self._get_image_types() |
168 | 261 | ||
169 | image_cmds = [] | 262 | image_cmd_groups = [] |
170 | for type in types: | ||
171 | cmds = [] | ||
172 | subimages = [] | ||
173 | 263 | ||
174 | localdata = bb.data.createCopy(self.d) | 264 | bb.note("The image creation groups are: %s" % str(fstype_groups)) |
175 | localdata.setVar('OVERRIDES', '%s:%s' % (type, old_overrides)) | 265 | for fstype_group in fstype_groups: |
176 | bb.data.update_data(localdata) | 266 | image_cmds = [] |
177 | localdata.setVar('type', type) | 267 | for type in fstype_group: |
268 | cmds = [] | ||
269 | subimages = [] | ||
178 | 270 | ||
179 | cmds.append("\t" + localdata.getVar("IMAGE_CMD", True)) | 271 | localdata = bb.data.createCopy(self.d) |
180 | cmds.append(localdata.expand("\tcd ${DEPLOY_DIR_IMAGE}")) | 272 | localdata.setVar('OVERRIDES', '%s:%s' % (type, old_overrides)) |
273 | bb.data.update_data(localdata) | ||
274 | localdata.setVar('type', type) | ||
181 | 275 | ||
182 | if type in cimages: | 276 | cmds.append("\t" + localdata.getVar("IMAGE_CMD", True)) |
183 | for ctype in cimages[type]: | 277 | cmds.append(localdata.expand("\tcd ${DEPLOY_DIR_IMAGE}")) |
184 | cmds.append("\t" + localdata.getVar("COMPRESS_CMD_" + ctype, True)) | ||
185 | subimages.append(type + "." + ctype) | ||
186 | 278 | ||
187 | if type not in alltypes: | 279 | if type in cimages: |
188 | cmds.append(localdata.expand("\trm ${IMAGE_NAME}.rootfs.${type}")) | 280 | for ctype in cimages[type]: |
189 | else: | 281 | cmds.append("\t" + localdata.getVar("COMPRESS_CMD_" + ctype, True)) |
190 | subimages.append(type) | 282 | subimages.append(type + "." + ctype) |
283 | |||
284 | if type not in alltypes: | ||
285 | cmds.append(localdata.expand("\trm ${IMAGE_NAME}.rootfs.${type}")) | ||
286 | else: | ||
287 | subimages.append(type) | ||
288 | |||
289 | script_name = self._write_script(type, cmds) | ||
191 | 290 | ||
192 | script_name = self._write_script(type, cmds) | 291 | image_cmds.append((type, subimages, script_name)) |
193 | 292 | ||
194 | image_cmds.append((type, subimages, script_name)) | 293 | image_cmd_groups.append(image_cmds) |
195 | 294 | ||
196 | return image_cmds | 295 | return image_cmd_groups |
197 | 296 | ||
198 | def create(self): | 297 | def create(self): |
199 | bb.note("###### Generate images #######") | 298 | bb.note("###### Generate images #######") |
@@ -204,22 +303,23 @@ class Image(object): | |||
204 | 303 | ||
205 | self._remove_old_symlinks() | 304 | self._remove_old_symlinks() |
206 | 305 | ||
207 | image_cmds = self._get_imagecmds() | 306 | image_cmd_groups = self._get_imagecmds() |
208 | 307 | ||
209 | # create the images in parallel | 308 | for image_cmds in image_cmd_groups: |
210 | nproc = multiprocessing.cpu_count() | 309 | # create the images in parallel |
211 | pool = bb.utils.multiprocessingpool(nproc) | 310 | nproc = multiprocessing.cpu_count() |
212 | results = list(pool.imap(generate_image, image_cmds)) | 311 | pool = bb.utils.multiprocessingpool(nproc) |
213 | pool.close() | 312 | results = list(pool.imap(generate_image, image_cmds)) |
214 | pool.join() | 313 | pool.close() |
314 | pool.join() | ||
215 | 315 | ||
216 | for result in results: | 316 | for result in results: |
217 | if result is not None: | 317 | if result is not None: |
218 | bb.fatal(result) | 318 | bb.fatal(result) |
219 | 319 | ||
220 | for image_type, subimages, script in image_cmds: | 320 | for image_type, subimages, script in image_cmds: |
221 | bb.note("Creating symlinks for %s image ..." % image_type) | 321 | bb.note("Creating symlinks for %s image ..." % image_type) |
222 | self._create_symlinks(subimages) | 322 | self._create_symlinks(subimages) |
223 | 323 | ||
224 | execute_pre_post_process(self.d, post_process_cmds) | 324 | execute_pre_post_process(self.d, post_process_cmds) |
225 | 325 | ||