Merge lp:~dooferlad/linaro-image-tools/copy-files-new-syntax into lp:linaro-image-tools/11.11
- copy-files-new-syntax
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | James Tunnicliffe |
Approved revision: | 564 |
Merged at revision: | 560 |
Proposed branch: | lp:~dooferlad/linaro-image-tools/copy-files-new-syntax |
Merge into: | lp:linaro-image-tools/11.11 |
Diff against target: |
913 lines (+484/-139) 5 files modified
linaro_image_tools/hwpack/builder.py (+70/-74) linaro_image_tools/hwpack/config.py (+67/-3) linaro_image_tools/hwpack/tests/test_builder.py (+22/-16) linaro_image_tools/media_create/boards.py (+171/-11) linaro_image_tools/media_create/tests/test_media_create.py (+154/-35) |
To merge this branch: | bzr merge lp:~dooferlad/linaro-image-tools/copy-files-new-syntax |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Sokolovsky | Approve | ||
Review via email: mp+123761@code.launchpad.net |
This proposal supersedes a proposal from 2012-09-10.
Commit message
Description of the change
Have addressed Paul's review.
Tests run:
testr run
Built image using 12.06 release Nano image (V2 hwpack). Booted on Pandaboard.
Built V3 hwpack with copy_files entry and two bootloaders defined. Combined with 12.06 Nano image. Booted on Pandaboard.
----
Adds support for copy_files as specified in https:/
Unfortunately had to roll back some previous changes to make this work in a nice way.
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal | # |
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal | # |
It seems that EXTRACT_FILES dict is no longer used by this code, then it should be removed, possibly propagating that recursively (i.e. if it used symbol defined elsewhere, and those symbols are not used either, they should be removed too).
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal | # |
It's not really clear what .get_last_
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal | # |
call_for_
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal | # |
[package for package in copy_files] - that looks a bit strange. Was "copy_files[:]" meant instead (make a copy of list)?
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal | # |
> [package for package in copy_files] - that looks a bit strange. Was "copy_files[:]" meant instead (make a copy of list)?
Ah, I see, that's unobvious way to do copy_files.keys(). Btw, I missed moment when they allowed iteration on dict directly, and that started to mean iteration over its keys. I still think that doing it explicitly ("for i in a.iterkeys()") is more clear.
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal | # |
> is more clear
And here's example why:
http://
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal | # |
> if not dest_path.
Great, thanks for implementing this handling.
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal | # |
342 - for expected_file, contents in expected_files:
343 - self.assertThat(
344 - tf, TarfileHasFile(
345 + stored_
346 + for package_name in package_names:
347 + self.assertIn(
I'm again not sure about this change - old version did check that file comes from the expected package and not some else, I don't the new does.
James Tunnicliffe (dooferlad) wrote : Posted in a previous version of this proposal | # |
On 11 September 2012 10:37, Paul Sokolovsky <email address hidden> wrote:
012345678901234
> Just for the record, I don't think such extreme refactoring was required,
> and it being started, is not complete - instead of moving entire file
> processing to be package based and happen on the level of
> linaro-
> as it was, just flips copy_files handling - instead of being done consistent
> with how bootloader.
> but that means we have file-extraction code spread around both
> linaro-
>
> But for the purpose of having it done, it's ok.
I agree that we could move all file extraction to the hardware pack
install stage, and it is something I considered. I think it is worth
doing as part of a separate update because it would change existing
functionality (well, change it more than has already been done as part
of supporting multiple boards and bootloaders). I don't know if anyone
is replacing bootloader binaries in a hardware pack after they have
created it with linaro-
changes to the hardware pack format with them.
I didn't make the decision to start refactoring lightly. I only did
when it became clear that I was going to have to write some rather
ugly code to support the new copy_files format as well as
re-implementing storing files that exist in a package - it seemed that
just using packages would be a lot cleaner!
I expect that the reason for U-Boot and the second stage bootloader
binaries being in a separate folder in the hardware pack it is
historical - I could not see any mention of the reasons in the code!
It adds unnecessary complication to the hardware pack create and
install stages and now that we support multiple bootloaders and boards
in a single hardware pack, we have already missed some bugs around it,
which you fixed (thanks!)
Thanks for the other comments. I will address them now and get an
updated branch ready.
--
James Tunnicliffe
Paul Sokolovsky (pfalcon) wrote : | # |
Great work, thanks for updating and testing!
Fathi Boudra (fboudra) wrote : | # |
See https:/
It breaks precise-
https:/
Preview Diff
1 | === modified file 'linaro_image_tools/hwpack/builder.py' |
2 | --- linaro_image_tools/hwpack/builder.py 2012-09-03 17:12:23 +0000 |
3 | +++ linaro_image_tools/hwpack/builder.py 2012-09-11 14:33:43 +0000 |
4 | @@ -37,29 +37,13 @@ |
5 | ) |
6 | |
7 | from linaro_image_tools.hwpack.hwpack_fields import ( |
8 | - FILE_FIELD, |
9 | PACKAGE_FIELD, |
10 | - SPL_FILE_FIELD, |
11 | SPL_PACKAGE_FIELD, |
12 | - COPY_FILES_FIELD, |
13 | ) |
14 | |
15 | # The fields that hold packages to be installed. |
16 | PACKAGE_FIELDS = [PACKAGE_FIELD, SPL_PACKAGE_FIELD] |
17 | -# Specification of files (boot related) to extract: |
18 | -# <field_containing_filepaths>: (<take_files_from_package>, |
19 | -# <put_into_this_hwpack_subdir>) |
20 | -# if <put_into_this_hwpack_subdir> is None, it will be <bootloader_name> for |
21 | -# global bootloader, or <board>-<bootloader_name> for board-specific |
22 | -# bootloader |
23 | -EXTRACT_FILES = {FILE_FIELD: (PACKAGE_FIELD, None), |
24 | - SPL_FILE_FIELD: (SPL_PACKAGE_FIELD, None), |
25 | - COPY_FILES_FIELD: (PACKAGE_FIELD, None)} |
26 | - |
27 | - |
28 | logger = logging.getLogger(__name__) |
29 | - |
30 | - |
31 | LOCAL_ARCHIVE_LABEL = 'hwpack-local' |
32 | |
33 | |
34 | @@ -166,57 +150,72 @@ |
35 | # Eliminate duplicates. |
36 | return list(set(boot_packages)) |
37 | |
38 | - def extract_bootloader_files(self, board, bootloader_name, |
39 | - bootloader_conf): |
40 | - for key, value in bootloader_conf.iteritems(): |
41 | - if key in EXTRACT_FILES: |
42 | - package_field, dest_path = EXTRACT_FILES[key] |
43 | - if not dest_path: |
44 | - dest_path = bootloader_name |
45 | - if board: |
46 | - dest_path += "-" + board |
47 | - # Dereference package field to get actual package name |
48 | - package = bootloader_conf.get(package_field) |
49 | - src_files = value |
50 | - |
51 | - # Process scalar and list fields consistently below |
52 | - field_value_scalar = False |
53 | - if type(src_files) != type([]): |
54 | - src_files = [src_files] |
55 | - field_value_scalar = True |
56 | - |
57 | - package_ref = self.find_fetched_package( |
58 | - self.packages, package) |
59 | - added_files = [] |
60 | - for f in src_files: |
61 | - added_files.append(self.add_file_to_hwpack( |
62 | - package_ref, f, dest_path)) |
63 | - # Store within-hwpack file paths with the same |
64 | - # scalar/list type as original field. |
65 | - if field_value_scalar: |
66 | - assert len(added_files) == 1 |
67 | - added_files = added_files[0] |
68 | - bootloader_conf[key] = added_files |
69 | - |
70 | - def extract_files(self, config_dictionary, is_bootloader_config, |
71 | - board=None): |
72 | - """Extract (boot) files based on EXTRACT_FILES spec and put |
73 | - them into hwpack.""" |
74 | - self.remove_packages = [] |
75 | - if is_bootloader_config: |
76 | - for bootl_name, bootl_conf in config_dictionary.iteritems(): |
77 | - self.extract_bootloader_files(board, bootl_name, bootl_conf) |
78 | - else: |
79 | - # This is board config |
80 | - for board, board_conf in config_dictionary.iteritems(): |
81 | - bootloaders = board_conf['bootloaders'] |
82 | - self.extract_files(bootloaders, True, board) |
83 | - |
84 | - # Clean up no longer needed packages. |
85 | - for package in self.remove_packages: |
86 | - if package in self.packages: |
87 | - self.packages.remove(package) |
88 | - self.remove_packages = [] |
89 | + def do_extract_file(self, package, source_path, dest_path): |
90 | + """Extract specified file from package to dest_path.""" |
91 | + package_ref = self.find_fetched_package(self.packages, package) |
92 | + return self.add_file_to_hwpack(package_ref, source_path, dest_path) |
93 | + |
94 | + def do_extract_files(self): |
95 | + """Go through a bootloader config, search for files to extract.""" |
96 | + base_dest_path = "" |
97 | + if self.config.board: |
98 | + base_dest_path = self.config.board |
99 | + base_dest_path = os.path.join(base_dest_path, self.config.bootloader) |
100 | + # Extract bootloader file |
101 | + if self.config.bootloader_package and self.config.bootloader_file: |
102 | + dest_path = os.path.join(base_dest_path, |
103 | + os.path.dirname(self.config.bootloader_file)) |
104 | + self.do_extract_file(self.config.bootloader_package, |
105 | + self.config.bootloader_file, |
106 | + dest_path) |
107 | + |
108 | + # Extract SPL file |
109 | + if self.config.spl_package and self.config.spl_file: |
110 | + dest_path = os.path.join(base_dest_path, |
111 | + os.path.dirname(self.config.spl_file)) |
112 | + self.do_extract_file(self.config.spl_package, |
113 | + self.config.spl_file, |
114 | + dest_path) |
115 | + |
116 | + def foreach_boards_and_bootloaders(self, function): |
117 | + """Call function for each board + bootloader combination in metadata""" |
118 | + if self.config.bootloaders is not None: |
119 | + for bootloader in self.config.bootloaders: |
120 | + self.config.set_board(None) |
121 | + self.config.set_bootloader(bootloader) |
122 | + function() |
123 | + |
124 | + if self.config.boards is not None: |
125 | + for board in self.config.boards: |
126 | + if self.config.bootloaders is not None: |
127 | + for bootloader in self.config.bootloaders: |
128 | + self.config.set_board(board) |
129 | + self.config.set_bootloader(bootloader) |
130 | + function() |
131 | + |
132 | + def extract_files(self): |
133 | + """Find bootloaders in config that may contain files to extract.""" |
134 | + if float(self.config.format.format_as_string) < 3.0: |
135 | + # extract files was introduced in version 3 configurations and is |
136 | + # a null operation for earlier configuration files |
137 | + return |
138 | + |
139 | + self.foreach_boards_and_bootloaders(self.do_extract_files) |
140 | + |
141 | + def do_find_copy_files_packages(self): |
142 | + """Find packages referenced by copy_files (single board, bootloader)""" |
143 | + copy_files = self.config.bootloader_copy_files |
144 | + if copy_files: |
145 | + self.copy_files_packages.extend(copy_files.keys()) |
146 | + |
147 | + def find_copy_files_packages(self): |
148 | + """Find all packages referenced by copy_files sections in metadata.""" |
149 | + self.copy_files_packages = [] |
150 | + self.foreach_boards_and_bootloaders( |
151 | + self.do_find_copy_files_packages) |
152 | + packages = self.copy_files_packages |
153 | + del(self.copy_files_packages) |
154 | + return packages |
155 | |
156 | def build(self): |
157 | for architecture in self.config.architectures: |
158 | @@ -242,6 +241,8 @@ |
159 | if self.config.boards is not None: |
160 | self.packages.extend(self.find_bootloader_packages( |
161 | self.config.boards)) |
162 | + |
163 | + self.packages.extend(self.find_copy_files_packages()) |
164 | else: |
165 | if self.config.bootloader_package is not None: |
166 | self.packages.append(self.config.bootloader_package) |
167 | @@ -268,14 +269,9 @@ |
168 | # On a v3 hwpack, all the values we need to check are |
169 | # in the bootloaders and boards section, so we loop |
170 | # through both of them changing what is necessary. |
171 | + |
172 | if self.config.format.format_as_string == '3.0': |
173 | - if self.config.bootloaders is not None: |
174 | - self.extract_files(self.config.bootloaders, |
175 | - True) |
176 | - metadata.bootloaders = self.config.bootloaders |
177 | - if self.config.boards is not None: |
178 | - self.extract_files(self.config.boards, False) |
179 | - metadata.boards = self.config.boards |
180 | + self.extract_files() |
181 | else: |
182 | bootloader_package = None |
183 | if self.config.bootloader_file is not None: |
184 | |
185 | === modified file 'linaro_image_tools/hwpack/config.py' |
186 | --- linaro_image_tools/hwpack/config.py 2012-08-30 10:39:02 +0000 |
187 | +++ linaro_image_tools/hwpack/config.py 2012-09-11 14:33:43 +0000 |
188 | @@ -21,6 +21,7 @@ |
189 | |
190 | import ConfigParser |
191 | from operator import attrgetter |
192 | +import os |
193 | import re |
194 | import string |
195 | import yaml |
196 | @@ -116,6 +117,8 @@ |
197 | translate_v2_to_v3[BOOTLOADER_IN_BOOT_PART_KEY] = IN_BOOT_PART_FIELD |
198 | BOOTLOADER_DD_KEY = 'u_boot_dd' |
199 | translate_v2_to_v3[BOOTLOADER_DD_KEY] = DD_FIELD |
200 | + last_used_keys = [] |
201 | + board = None |
202 | |
203 | def __init__(self, fp, bootloader=None, board=None, |
204 | allow_unset_bootloader=False): |
205 | @@ -354,9 +357,56 @@ |
206 | return self._get_bootloader_option(SPL_IN_BOOT_PART_FIELD) |
207 | |
208 | @property |
209 | - def boot_copy_files(self): |
210 | - """Extra files to copy to boot partition.""" |
211 | - return self._get_bootloader_option(COPY_FILES_FIELD) |
212 | + def bootloader_copy_files(self): |
213 | + """Extra files to copy to boot partition. |
214 | + |
215 | + This can be stored in several formats. We always present in a common |
216 | + one: {source_package: [{source_file_path: dest_file_path}]. |
217 | + dest_file_path (in the above example) is always absolute. |
218 | + """ |
219 | + #copy_files: |
220 | + # source_package: |
221 | + # - source_file_path : dest_file_path |
222 | + # - source_file_without_explicit_destination |
223 | + #copy_files: |
224 | + # - file1 |
225 | + # - file2: dest_path |
226 | + # |
227 | + # Note that the list of files is always that - a list. |
228 | + |
229 | + copy_files = self._get_bootloader_option(COPY_FILES_FIELD) |
230 | + |
231 | + if copy_files is None: |
232 | + return None |
233 | + |
234 | + if not isinstance(copy_files, dict): |
235 | + copy_files = {self.bootloader_package: copy_files} |
236 | + |
237 | + for package in copy_files: |
238 | + new_list = [] |
239 | + for value in copy_files[package]: |
240 | + if not isinstance(value, dict): |
241 | + dest_path = "/boot" |
242 | + source_path = value |
243 | + else: |
244 | + if len(value.keys()) > 1: |
245 | + raise HwpackConfigError("copy_files entry found with" |
246 | + "more than one destination") |
247 | + source_path = value.keys()[0] |
248 | + dest_path = value[source_path] |
249 | + |
250 | + if not dest_path.startswith("/boot"): |
251 | + # Target path should be relative, or start with /boot - we |
252 | + # don't support to copying to anywhere other than /boot. |
253 | + if dest_path[0] == "/": |
254 | + raise HwpackConfigError("copy_files destinations must" |
255 | + "be relative to /boot or start with /boot.") |
256 | + dest_path = os.path.join("/boot", dest_path) |
257 | + |
258 | + new_list.append({source_path: dest_path}) |
259 | + copy_files[package] = new_list |
260 | + |
261 | + return copy_files |
262 | |
263 | @property |
264 | def spl_dd(self): |
265 | @@ -426,8 +476,22 @@ |
266 | key = self._v2_key_to_v3(key) |
267 | if result is not None: |
268 | result = result.get(key, None) |
269 | + self.last_used_keys = keys |
270 | return result |
271 | |
272 | + def get_last_used_keys(self): |
273 | + """Used so you can work out which boards + boot loader was used. |
274 | + |
275 | + Configuration data is stored in a dictionary. This returns a list of |
276 | + keys used to traverse into the dictionary the last time an item was |
277 | + looked up. |
278 | + |
279 | + This can be used to see where a bit of information came from - we |
280 | + store data that may be indexed differently depending on which board |
281 | + and bootloader are set. |
282 | + """ |
283 | + return self.last_used_keys |
284 | + |
285 | def get_option(self, name): |
286 | """Return the value of an attribute by name. |
287 | |
288 | |
289 | === modified file 'linaro_image_tools/hwpack/tests/test_builder.py' |
290 | --- linaro_image_tools/hwpack/tests/test_builder.py 2012-09-04 10:42:27 +0000 |
291 | +++ linaro_image_tools/hwpack/tests/test_builder.py 2012-09-11 14:33:43 +0000 |
292 | @@ -31,7 +31,7 @@ |
293 | HardwarePackBuilder, |
294 | logger as builder_logger, |
295 | ) |
296 | -from linaro_image_tools.hwpack.config import HwpackConfigError, Config |
297 | +from linaro_image_tools.hwpack.config import HwpackConfigError |
298 | from linaro_image_tools.hwpack.hardwarepack import Metadata |
299 | from linaro_image_tools.hwpack.packages import ( |
300 | FetchedPackage, |
301 | @@ -55,7 +55,6 @@ |
302 | MockSomethingFixture, |
303 | MockCmdRunnerPopenFixture, |
304 | ) |
305 | -from StringIO import StringIO |
306 | |
307 | |
308 | class ConfigFileMissingTests(TestCase): |
309 | @@ -431,11 +430,17 @@ |
310 | Equals("Local package 'bar' not included")) |
311 | |
312 | def test_global_and_board_bootloader(self): |
313 | - package_names = ['package0', 'package1'] |
314 | - files = {package_names[0]: |
315 | - ["usr/lib/u-boot/omap4_panda/u-boot.img", |
316 | - "usr/share/doc/u-boot-linaro-omap4-panda/copyright"], |
317 | - package_names[1]: ["usr/lib/u-boot/omap4_panda/u-boot.img"]} |
318 | + package_names = ['package0', 'package1', 'package2', 'package3'] |
319 | + files = { |
320 | + package_names[0]: |
321 | + ["usr/lib/u-boot/omap4_panda/u-boot.img", |
322 | + "usr/share/doc/u-boot-linaro-omap4-panda/copyright"], |
323 | + package_names[1]: |
324 | + ["usr/lib/u-boot/omap4_panda/u-boot.img", |
325 | + "some/path/file"], |
326 | + package_names[2]: [], |
327 | + package_names[3]: [], |
328 | + } |
329 | |
330 | config_v3 = self.config_v3 + "\n".join([ |
331 | "bootloaders:", |
332 | @@ -443,13 +448,17 @@ |
333 | self.bootloader_config, |
334 | " file: " + files[package_names[0]][0], |
335 | " copy_files:", |
336 | - " - " + files[package_names[0]][1], |
337 | + " " + package_names[2] + ":", |
338 | + " - some_file", |
339 | "boards:", |
340 | " board1:", |
341 | " bootloaders:", |
342 | " u_boot:", |
343 | " package: %s", |
344 | " file: " + files[package_names[1]][0], |
345 | + " copy_files:", |
346 | + " " + package_names[3] + ":", |
347 | + " - some_file", |
348 | " in_boot_part: true", |
349 | "sources:", |
350 | " ubuntu: %s"]) |
351 | @@ -480,10 +489,6 @@ |
352 | |
353 | config_file_fixture = self.useFixture(ConfigFileFixture(config_v3)) |
354 | |
355 | - # Parse the config |
356 | - config = Config(StringIO(config_v3)) |
357 | - config.set_bootloader("u_boot") |
358 | - |
359 | # Build a hardware pack |
360 | builder = HardwarePackBuilder( |
361 | config_file_fixture.filename, "1.0", |
362 | @@ -491,6 +496,9 @@ |
363 | for package in available_packages]) |
364 | |
365 | builder.build() |
366 | + stored_package_names = [p.name for p in builder.packages] |
367 | + for package_name in package_names: |
368 | + self.assertIn(package_name, stored_package_names) |
369 | |
370 | # Read the contents of the hardware pack, making sure it is as expected |
371 | tf = tarfile.open("hwpack_ahwpack_1.0_armel.tar.gz", mode="r:gz") |
372 | @@ -499,11 +507,9 @@ |
373 | # files this is "<package_name> <originating path>" so they can be |
374 | # uniquely identified. |
375 | expected_files = [ |
376 | - ("u_boot/u-boot.img", |
377 | + ("u_boot/" + files[package_names[0]][0], |
378 | package_names[0] + " " + files[package_names[0]][0]), |
379 | - ("u_boot/copyright", |
380 | - package_names[0] + " " + files[package_names[0]][1]), |
381 | - ("u_boot-board1/u-boot.img", |
382 | + ("board1/u_boot/" + files[package_names[1]][0], |
383 | package_names[1] + " " + files[package_names[1]][0])] |
384 | |
385 | for expected_file, contents in expected_files: |
386 | |
387 | === modified file 'linaro_image_tools/media_create/boards.py' |
388 | --- linaro_image_tools/media_create/boards.py 2012-09-05 09:29:14 +0000 |
389 | +++ linaro_image_tools/media_create/boards.py 2012-09-11 14:33:43 +0000 |
390 | @@ -37,6 +37,7 @@ |
391 | import string |
392 | import logging |
393 | from linaro_image_tools.hwpack.config import Config |
394 | +from linaro_image_tools.hwpack.builder import PackageUnpacker |
395 | |
396 | from parted import Device |
397 | |
398 | @@ -128,6 +129,7 @@ |
399 | self.hwpack_tarfiles = [] |
400 | self.bootloader = bootloader |
401 | self.board = board |
402 | + self.tempdirs = {} |
403 | |
404 | class FakeSecHead(object): |
405 | """ Add a fake section header to the metadata file. |
406 | @@ -162,9 +164,15 @@ |
407 | if self.tempdir is not None and os.path.exists(self.tempdir): |
408 | shutil.rmtree(self.tempdir) |
409 | |
410 | - def get_field(self, field): |
411 | + for name in self.tempdirs: |
412 | + tempdir = self.tempdirs[name] |
413 | + if tempdir is not None and os.path.exists(tempdir): |
414 | + shutil.rmtree(tempdir) |
415 | + |
416 | + def get_field(self, field, return_keys=False): |
417 | data = None |
418 | hwpack_with_data = None |
419 | + keys = None |
420 | for hwpack_tarfile in self.hwpack_tarfiles: |
421 | metadata = hwpack_tarfile.extractfile(self.metadata_filename) |
422 | lines = metadata.readlines() |
423 | @@ -181,9 +189,13 @@ |
424 | new_data) |
425 | data = new_data |
426 | hwpack_with_data = hwpack_tarfile |
427 | + if return_keys: |
428 | + keys = parser.get_last_used_keys() |
429 | except ConfigParser.NoOptionError: |
430 | continue |
431 | |
432 | + if return_keys: |
433 | + return data, hwpack_with_data, keys |
434 | return data, hwpack_with_data |
435 | |
436 | def get_format(self): |
437 | @@ -207,7 +219,8 @@ |
438 | file reference(s) |
439 | :return: path to a file or list of paths to files |
440 | """ |
441 | - file_names, hwpack_tarfile = self.get_field(file_alias) |
442 | + file_names, hwpack_tarfile, keys = self.get_field(file_alias, |
443 | + return_keys=True) |
444 | if not file_names: |
445 | return file_names |
446 | single = False |
447 | @@ -215,7 +228,24 @@ |
448 | single = True |
449 | file_names = [file_names] |
450 | out_files = [] |
451 | + |
452 | + # Depending on if board and/or bootloader were used to look up the |
453 | + # file we are getting, we need to prepend those names to the path |
454 | + # to get the correct extracted file from the hardware pack. |
455 | + config_names = [("board", "boards"), ("bootloader", "bootloaders")] |
456 | + base_path = "" |
457 | + if keys: |
458 | + # If keys is non-empty, we have a V3 config option that was |
459 | + # modified by the bootloader and/or boot option... |
460 | + for name, key in config_names: |
461 | + if self.get_field(name): |
462 | + value = self.get_field(name)[0] |
463 | + if keys[0] == key: |
464 | + base_path = os.path.join(base_path, value) |
465 | + keys = keys[1:] |
466 | + |
467 | for f in file_names: |
468 | + f = os.path.join(base_path, f) |
469 | hwpack_tarfile.extract(f, self.tempdir) |
470 | f = os.path.join(self.tempdir, f) |
471 | out_files.append(f) |
472 | @@ -223,6 +253,104 @@ |
473 | return out_files[0] |
474 | return out_files |
475 | |
476 | + def list_packages(self): |
477 | + """Return list of (package names, TarFile object containing them)""" |
478 | + packages = [] |
479 | + for tf in self.hwpack_tarfiles: |
480 | + for name in tf.getnames(): |
481 | + if name.startswith("pkgs/") and name.endswith(".deb"): |
482 | + packages.append((tf, name)) |
483 | + return packages |
484 | + |
485 | + def find_package_for(self, name, version=None, revision=None, |
486 | + architecture=None): |
487 | + """Find a package that matches the name, version, rev and arch given. |
488 | + |
489 | + Packages are named according to the debian specification: |
490 | + http://www.debian.org/doc/manuals/debian-faq/ch-pkg_basics.en.html |
491 | + <name>_<Version>-<DebianRevisionNumber>_<DebianArchitecture>.deb |
492 | + DebianRevisionNumber seems to be optional. |
493 | + Use this spec to return a package matching the requirements given. |
494 | + """ |
495 | + for tar_file, package in self.list_packages(): |
496 | + file_name = os.path.basename(package) |
497 | + dpkg_chunks = re.search("^(.+)_(.+)_(.+)\.deb$", |
498 | + file_name) |
499 | + assert dpkg_chunks, "Could not split package file name into"\ |
500 | + "<name>_<Version>_<DebianArchitecture>.deb" |
501 | + |
502 | + pkg_name = dpkg_chunks.group(1) |
503 | + pkg_version = dpkg_chunks.group(2) |
504 | + pkg_architecture = dpkg_chunks.group(3) |
505 | + |
506 | + ver_chunks = re.search("^(.+)-(.+)$", pkg_version) |
507 | + if ver_chunks: |
508 | + pkg_version = ver_chunks.group(1) |
509 | + pkg_revision = ver_chunks.group(2) |
510 | + else: |
511 | + pkg_revision = None |
512 | + |
513 | + if name != pkg_name: |
514 | + continue |
515 | + if version != None and str(version) != pkg_version: |
516 | + continue |
517 | + if revision != None and str(revision) != pkg_revision: |
518 | + continue |
519 | + if (architecture != None and |
520 | + str(architecture) != pkg_architecture): |
521 | + continue |
522 | + |
523 | + # Got a matching package - return its path inside the tarball |
524 | + return tar_file, package |
525 | + |
526 | + # Failed to find a matching package - return None |
527 | + return None |
528 | + |
529 | + def get_file_from_package(self, file_path, package_name, |
530 | + package_version=None, package_revision=None, |
531 | + package_architecture=None): |
532 | + """Extract named file from package specified by name, ver, rev, arch. |
533 | + |
534 | + File is extracted from the package matching the given specification |
535 | + to a temporary directory. The absolute path to the extracted file is |
536 | + returned. |
537 | + """ |
538 | + |
539 | + package_info = self.find_package_for(package_name, |
540 | + package_version, |
541 | + package_revision, |
542 | + package_architecture) |
543 | + if package_info is None: |
544 | + return None |
545 | + tar_file, package = package_info |
546 | + |
547 | + # Avoid unpacking hardware pack more than once by assigning each one |
548 | + # its own tempdir to unpack into. |
549 | + # TODO: update logic that uses self.tempdir so we can get rid of this |
550 | + # by sharing nicely. |
551 | + if not package in self.tempdirs: |
552 | + self.tempdirs[package] = tempfile.mkdtemp() |
553 | + tempdir = self.tempdirs[package] |
554 | + |
555 | + # We extract everything in the hardware pack so we don't have to worry |
556 | + # about chasing links (extract a link, find where it points to, extract |
557 | + # that...). This is slower, but more reliable. |
558 | + tar_file.extractall(tempdir) |
559 | + package_path = os.path.join(tempdir, package) |
560 | + |
561 | + with PackageUnpacker() as self.package_unpacker: |
562 | + extracted_file = self.package_unpacker.get_file(package_path, |
563 | + file_path) |
564 | + after_tmp = re.sub(self.package_unpacker.tempdir, "", |
565 | + extracted_file).lstrip("/\\") |
566 | + extract_dir = os.path.join(tempdir, "extracted", |
567 | + os.path.dirname(after_tmp)) |
568 | + os.makedirs(extract_dir) |
569 | + shutil.move(extracted_file, extract_dir) |
570 | + extracted_file = os.path.join(extract_dir, |
571 | + os.path.basename(extracted_file)) |
572 | + return extracted_file |
573 | + |
574 | |
575 | class BoardConfig(object): |
576 | board = None |
577 | @@ -465,6 +593,13 @@ |
578 | cls.SAMSUNG_V310_BL2_START = (cls.SAMSUNG_V310_ENV_START + |
579 | cls.SAMSUNG_V310_ENV_LEN) |
580 | |
581 | + cls.bootloader_copy_files = cls.hardwarepack_handler.get_field( |
582 | + "bootloader_copy_files")[0] |
583 | + |
584 | + cls.bootloader = cls.hardwarepack_handler.get_field( |
585 | + "bootloader") |
586 | + cls.board = board |
587 | + |
588 | @classmethod |
589 | def get_file(cls, file_alias, default=None): |
590 | # XXX remove the 'default' parameter when V1 support is removed! |
591 | @@ -793,7 +928,6 @@ |
592 | bootloader_parts_dir = os.path.join(chroot_dir, parts_dir) |
593 | cmd_runner.run(['mkdir', '-p', boot_disk]).wait() |
594 | with partition_mounted(boot_partition, boot_disk): |
595 | - boot_files = [] |
596 | with cls.hardwarepack_handler: |
597 | if cls.bootloader_file_in_boot_part: |
598 | # <legacy v1 support> |
599 | @@ -813,22 +947,48 @@ |
600 | assert bootloader_bin is not None, ( |
601 | "bootloader binary could not be found") |
602 | |
603 | - boot_files.append(bootloader_bin) |
604 | - |
605 | - copy_files = cls.get_file('boot_copy_files') |
606 | - if copy_files: |
607 | - boot_files.extend(copy_files) |
608 | - |
609 | - for f in boot_files: |
610 | proc = cmd_runner.run( |
611 | - ['cp', '-v', f, boot_disk], as_root=True) |
612 | + ['cp', '-v', bootloader_bin, boot_disk], as_root=True) |
613 | proc.wait() |
614 | |
615 | + # Handle copy_files field. |
616 | + cls.copy_files(boot_disk) |
617 | + |
618 | cls.make_boot_files( |
619 | bootloader_parts_dir, is_live, is_lowmem, consoles, chroot_dir, |
620 | rootfs_id, boot_disk, boot_device_or_file) |
621 | |
622 | @classmethod |
623 | + def copy_files(cls, boot_disk): |
624 | + """Handle the copy_files metadata field.""" |
625 | + |
626 | + # Extract anything specified by copy_files sections |
627 | + # self.bootloader_copy_files is always of the form: |
628 | + # {'source_package': |
629 | + # [ |
630 | + # {'source_path': 'dest_path'} |
631 | + # ] |
632 | + # } |
633 | + if cls.bootloader_copy_files is None: |
634 | + return |
635 | + |
636 | + for source_package, file_list in cls.bootloader_copy_files.iteritems(): |
637 | + for file_info in file_list: |
638 | + for source_path, dest_path in file_info.iteritems(): |
639 | + source = cls.hardwarepack_handler.get_file_from_package( |
640 | + source_path, source_package) |
641 | + dest_path = dest_path.lstrip("/\\") |
642 | + dirname = os.path.dirname(dest_path) |
643 | + dirname = os.path.join(boot_disk, dirname) |
644 | + if not os.path.exists(dirname): |
645 | + cmd_runner.run(["mkdir", "-p", dirname], |
646 | + as_root=True).wait() |
647 | + proc = cmd_runner.run( |
648 | + ['cp', '-v', source, |
649 | + os.path.join(boot_disk, dest_path)], as_root=True) |
650 | + proc.wait() |
651 | + |
652 | + @classmethod |
653 | def _get_kflavor_files(cls, path): |
654 | """Search for kernel, initrd and optional dtb in path.""" |
655 | if cls.kernel_flavors is None: |
656 | |
657 | === modified file 'linaro_image_tools/media_create/tests/test_media_create.py' |
658 | --- linaro_image_tools/media_create/tests/test_media_create.py 2012-09-04 13:53:43 +0000 |
659 | +++ linaro_image_tools/media_create/tests/test_media_create.py 2012-09-11 14:33:43 +0000 |
660 | @@ -36,6 +36,7 @@ |
661 | from testtools import TestCase |
662 | |
663 | from linaro_image_tools import cmd_runner |
664 | +from linaro_image_tools.hwpack.packages import PackageMaker |
665 | import linaro_image_tools.media_create |
666 | from linaro_image_tools.media_create import ( |
667 | android_boards, |
668 | @@ -124,6 +125,7 @@ |
669 | ) |
670 | from linaro_image_tools.utils import find_command, preferred_tools_dir |
671 | |
672 | +from linaro_image_tools.hwpack.testing import ContextManagerFixture |
673 | |
674 | chroot_args = " ".join(cmd_runner.CHROOT_ARGS) |
675 | sudo_args = " ".join(cmd_runner.SUDO_ARGS) |
676 | @@ -309,36 +311,95 @@ |
677 | test_file = hp.get_file('bootloader_file') |
678 | self.assertEquals(data, open(test_file, 'r').read()) |
679 | |
680 | - def test_get_file_v3(self): |
681 | - # Test that get_file() works as expected with hwpackv3 and |
682 | - # supports its new file fields. |
683 | - metadata = textwrap.dedent("""\ |
684 | - format: 3.0 |
685 | - name: ahwpack |
686 | - version: 4 |
687 | - architecture: armel |
688 | - origin: linaro |
689 | - bootloaders: |
690 | - u_boot: |
691 | - file: a_file |
692 | - copy_files: |
693 | - - file1 |
694 | - - file2 |
695 | - uefi: |
696 | - file: b_file |
697 | - """) |
698 | - files = {'FORMAT': '3.0\n', 'metadata': metadata, |
699 | - 'a_file': 'a_file content', 'file1': 'file1 content', |
700 | - 'file2': 'file2 content'} |
701 | - tarball = self.add_to_tarball(files.items()) |
702 | - hp = HardwarepackHandler([tarball], bootloader='u_boot') |
703 | - with hp: |
704 | - test_file = hp.get_file('bootloader_file') |
705 | - self.assertEquals(files['a_file'], open(test_file, 'r').read()) |
706 | - test_files = hp.get_file('boot_copy_files') |
707 | - self.assertEquals(len(test_files), 2) |
708 | - self.assertEquals(files['file1'], open(test_files[0], 'r').read()) |
709 | - self.assertEquals(files['file2'], open(test_files[1], 'r').read()) |
710 | + def test_list_packages(self): |
711 | + metadata = ("format: 3.0\nname: ahwpack\nversion: 4\narchitecture: " |
712 | + "armel\norigin: linaro\n") |
713 | + format = "3.0\n" |
714 | + tarball = self.add_to_tarball([ |
715 | + ("FORMAT", format), |
716 | + ("metadata", metadata), |
717 | + ("pkgs/foo_1-1_all.deb", ''), |
718 | + ("pkgs/bar_1-1_all.deb", ''), |
719 | + ]) |
720 | + |
721 | + hp = HardwarepackHandler([tarball], board='panda', bootloader='uefi') |
722 | + with hp: |
723 | + packages = hp.list_packages() |
724 | + names = [p[1] for p in packages] |
725 | + self.assertIn('pkgs/foo_1-1_all.deb', names) |
726 | + self.assertIn('pkgs/bar_1-1_all.deb', names) |
727 | + self.assertEqual(len(packages), 2) |
728 | + |
729 | + def test_find_package_for(self): |
730 | + metadata = ("format: 3.0\nname: ahwpack\nversion: 4\narchitecture: " |
731 | + "armel\norigin: linaro\n") |
732 | + format = "3.0\n" |
733 | + tarball = self.add_to_tarball([ |
734 | + ("FORMAT", format), |
735 | + ("metadata", metadata), |
736 | + ("pkgs/foo_1-3_all.deb", ''), |
737 | + ("pkgs/foo_2-5_arm.deb", ''), |
738 | + ("pkgs/bar_1-3_arm.deb", ''), |
739 | + ]) |
740 | + |
741 | + hp = HardwarepackHandler([tarball], board='panda', bootloader='uefi') |
742 | + with hp: |
743 | + self.assertEqual(hp.find_package_for("foo")[1], |
744 | + "pkgs/foo_1-3_all.deb") |
745 | + self.assertEqual(hp.find_package_for("bar")[1], |
746 | + "pkgs/bar_1-3_arm.deb") |
747 | + self.assertEqual(hp.find_package_for("foo", version=2)[1], |
748 | + "pkgs/foo_2-5_arm.deb") |
749 | + self.assertEqual(hp.find_package_for("foo", version=2, |
750 | + revision=5)[1], |
751 | + "pkgs/foo_2-5_arm.deb") |
752 | + self.assertEqual(hp.find_package_for("foo", version=2, revision=5, |
753 | + architecture="arm")[1], |
754 | + "pkgs/foo_2-5_arm.deb") |
755 | + self.assertEqual(hp.find_package_for("foo", architecture="arm")[1], |
756 | + "pkgs/foo_2-5_arm.deb") |
757 | + self.assertEqual(hp.find_package_for("foo", architecture="all")[1], |
758 | + "pkgs/foo_1-3_all.deb") |
759 | + |
760 | + def test_get_file_from_package(self): |
761 | + metadata = ("format: 3.0\nname: ahwpack\nversion: 4\narchitecture: " |
762 | + "armel\norigin: linaro\n") |
763 | + format = "3.0\n" |
764 | + |
765 | + names = ['package0', 'package1', 'package2'] |
766 | + files = {names[0]: |
767 | + ["usr/lib/u-boot/omap4_panda/u-boot.img", |
768 | + "usr/share/doc/u-boot-linaro-omap4-panda/copyright"], |
769 | + names[1]: ["usr/lib/u-boot/omap4_panda/u-boot2.img", |
770 | + "foo/bar", |
771 | + "flim/flam"], |
772 | + names[2]: ["some/path/config"]} |
773 | + |
774 | + # Generate some test packages |
775 | + maker = PackageMaker() |
776 | + self.useFixture(ContextManagerFixture(maker)) |
777 | + |
778 | + tarball_content = [("FORMAT", format), ("metadata", metadata)] |
779 | + |
780 | + package_names = [] |
781 | + for package_name in names: |
782 | + # The files parameter to make_package is a list of files to create. |
783 | + # These files are text files containing package_name and their |
784 | + # path. Since package_name is different for each package, this |
785 | + # gives each file a unique content. |
786 | + deb_file_path = maker.make_package(package_name, '1.0', {}, |
787 | + files=files[package_name]) |
788 | + name = os.path.basename(deb_file_path) |
789 | + tarball_content.append((os.path.join("pkgs", name), |
790 | + open(deb_file_path).read())) |
791 | + package_names.append(name) |
792 | + |
793 | + tarball = self.add_to_tarball(tarball_content) |
794 | + |
795 | + hp = HardwarepackHandler([tarball], board='panda', bootloader='uefi') |
796 | + with hp: |
797 | + path = hp.get_file_from_package("some/path/config", "package2") |
798 | + self.assertTrue(path.endswith("some/path/config")) |
799 | |
800 | |
801 | class TestSetMetadata(TestCaseWithFixtures): |
802 | @@ -574,6 +635,23 @@ |
803 | self.assertRaises( |
804 | AssertionError, config.set_metadata, 'ahwpack.tar.gz') |
805 | |
806 | + def test_sets_copy_files(self): |
807 | + self.useFixture(MockSomethingFixture( |
808 | + linaro_image_tools.media_create.boards, 'HardwarepackHandler', |
809 | + self.MockHardwarepackHandler)) |
810 | + field_to_test = 'bootloader_copy_files' |
811 | + data_to_set = {'package': |
812 | + [{"source1": "dest1"}, |
813 | + {"source2": "dest2"}]} |
814 | + self.MockHardwarepackHandler.metadata_dict = { |
815 | + field_to_test: data_to_set, |
816 | + } |
817 | + |
818 | + class config(BoardConfig): |
819 | + pass |
820 | + config.set_metadata('ahwpack.tar.gz') |
821 | + self.assertEquals(data_to_set, config.bootloader_copy_files) |
822 | + |
823 | |
824 | class TestGetMLOFile(TestCaseWithFixtures): |
825 | |
826 | @@ -2937,6 +3015,16 @@ |
827 | self.popen_fixture = self.useFixture(MockCmdRunnerPopenFixture()) |
828 | self.useFixture(MockSomethingFixture( |
829 | self.config, 'make_boot_files', self.save_args)) |
830 | + self.config.hardwarepack_handler.get_file_from_package = \ |
831 | + self.get_file_from_package |
832 | + self.config.bootloader_copy_files = None |
833 | + |
834 | + def get_file_from_package(self, source_path, source_package): |
835 | + if source_package in self.config.bootloader_copy_files: |
836 | + for file_info in self.config.bootloader_copy_files[source_package]: |
837 | + if source_path in file_info: |
838 | + return source_path |
839 | + return None |
840 | |
841 | def prepare_config_v3(self, config): |
842 | class c(config): |
843 | @@ -2949,6 +3037,13 @@ |
844 | self.config.hardwarepack_handler.get_format = lambda: '3.0' |
845 | self.config.hardwarepack_handler.get_file = \ |
846 | lambda file_alias: ['file1', 'file2'] |
847 | + self.config.hardwarepack_handler.get_file_from_package = \ |
848 | + self.get_file_from_package |
849 | + self.config.bootloader_copy_files = { |
850 | + "package1": |
851 | + [{"file1": "/boot/"}, |
852 | + {"file2": "/boot/grub/renamed"}]} |
853 | + |
854 | self.popen_fixture = self.useFixture(MockCmdRunnerPopenFixture()) |
855 | self.useFixture(MockSomethingFixture( |
856 | self.config, 'make_boot_files', self.save_args)) |
857 | @@ -2984,6 +3079,7 @@ |
858 | self.prepare_config(boards.BoardConfig) |
859 | self.config.bootloader_flavor = "bootloader_flavor" |
860 | self.config.bootloader_file_in_boot_part = True |
861 | + self.config.bootloader = "u_boot" |
862 | self.call_populate_boot(self.config) |
863 | expected_calls = self.expected_calls[:] |
864 | expected_calls.insert(2, |
865 | @@ -3003,7 +3099,7 @@ |
866 | expected_calls, self.popen_fixture.mock.commands_executed) |
867 | self.assertEquals(self.expected_args, self.saved_args) |
868 | |
869 | - def test_populate_boot_copy_files(self): |
870 | + def test_populate_bootloader_copy_files(self): |
871 | self.prepare_config_v3(boards.BoardConfig) |
872 | self.config.bootloader_flavor = "bootloader_flavor" |
873 | # Test that copy_files works per spec (puts stuff in boot partition) |
874 | @@ -3011,12 +3107,35 @@ |
875 | self.config.bootloader_file_in_boot_part = False |
876 | self.call_populate_boot(self.config) |
877 | expected_calls = self.expected_calls[:] |
878 | - expected_calls.insert(2, |
879 | + expected_calls.insert(2, '%s mkdir -p boot_disk/boot' % sudo_args) |
880 | + expected_calls.insert(3, |
881 | '%s cp -v file1 ' |
882 | - 'boot_disk' % sudo_args) |
883 | - expected_calls.insert(3, |
884 | + 'boot_disk/boot/' % sudo_args) |
885 | + expected_calls.insert(4, '%s mkdir -p boot_disk/boot/grub' % sudo_args) |
886 | + expected_calls.insert(5, |
887 | '%s cp -v file2 ' |
888 | - 'boot_disk' % sudo_args) |
889 | + 'boot_disk/boot/grub/renamed' % sudo_args) |
890 | + self.assertEquals( |
891 | + expected_calls, self.popen_fixture.mock.commands_executed) |
892 | + self.assertEquals(self.expected_args, self.saved_args) |
893 | + |
894 | + def test_populate_bootloader_copy_files_bootloader_set(self): |
895 | + self.prepare_config_v3(boards.BoardConfig) |
896 | + self.config.bootloader_flavor = "bootloader_flavor" |
897 | + # Test that copy_files works per spec (puts stuff in boot partition) |
898 | + # even if bootloader not in_boot_part. |
899 | + self.config.bootloader_file_in_boot_part = False |
900 | + self.config.bootloader = "u_boot" |
901 | + self.call_populate_boot(self.config) |
902 | + expected_calls = self.expected_calls[:] |
903 | + expected_calls.insert(2, '%s mkdir -p boot_disk/boot' % sudo_args) |
904 | + expected_calls.insert(3, |
905 | + '%s cp -v file1 ' |
906 | + 'boot_disk/boot/' % sudo_args) |
907 | + expected_calls.insert(4, '%s mkdir -p boot_disk/boot/grub' % sudo_args) |
908 | + expected_calls.insert(5, |
909 | + '%s cp -v file2 ' |
910 | + 'boot_disk/boot/grub/renamed' % sudo_args) |
911 | self.assertEquals( |
912 | expected_calls, self.popen_fixture.mock.commands_executed) |
913 | self.assertEquals(self.expected_args, self.saved_args) |
Just for the record, I don't think such extreme refactoring was required, and it being started, is not complete - instead of moving entire file processing to be package based and happen on the level of linaro- media-create, this still leaves bootloader.file, bootloader.spl_file as it was, just flips copy_files handling - instead of being done consistent with how bootloader. file/bootloader .spl_file works, it is now package based, but that means we have file-extraction code spread around both linaro- hwpack- create and linaro- media-create.
But for the purpose of having it done, it's ok.
(more comments to come.)