Merge lp:~dooferlad/linaro-image-tools/copy-files-new-syntax into lp:linaro-image-tools/11.11

Proposed by James Tunnicliffe
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
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.

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://wiki.linaro.org/HardwarePacksV3

Unfortunately had to roll back some previous changes to make this work in a nice way.

To post a comment you must log in.
Revision history for this message
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal

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.)

Revision history for this message
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).

review: Needs Fixing
Revision history for this message
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal

It's not really clear what .get_last_used_keys() does, suggested to add more details docstring.

review: Needs Fixing
Revision history for this message
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal

call_for_all_boards_and_bootloaders(self, function) - somehow I got used to such functions be called foreach_board_and_bootloader() , which hints that it implements enumeration loop, though that's of course just a preference.

Revision history for this message
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)?

Revision history for this message
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.

Revision history for this message
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal
Revision history for this message
Paul Sokolovsky (pfalcon) wrote : Posted in a previous version of this proposal

> if not dest_path.startswith("/boot"):

Great, thanks for implementing this handling.

Revision history for this message
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(expected_file, content=contents))
345 + stored_package_names = [p.name for p in builder.packages]
346 + for package_name in package_names:
347 + self.assertIn(package_name, stored_package_names)

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.

review: Needs Fixing
Revision history for this message
James Tunnicliffe (dooferlad) wrote : Posted in a previous version of this proposal

On 11 September 2012 10:37, Paul Sokolovsky <email address hidden> wrote:
01234567890123456789012345678901234567890123456789012345678901234567890123456789
> 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.

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-hwpack-create. If they are, we need to discuss
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

Revision history for this message
Paul Sokolovsky (pfalcon) wrote :

Great work, thanks for updating and testing!

review: Approve
Revision history for this message
Fathi Boudra (fboudra) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'linaro_image_tools/hwpack/builder.py'
--- linaro_image_tools/hwpack/builder.py 2012-09-03 17:12:23 +0000
+++ linaro_image_tools/hwpack/builder.py 2012-09-11 14:33:43 +0000
@@ -37,29 +37,13 @@
37 )37 )
3838
39from linaro_image_tools.hwpack.hwpack_fields import (39from linaro_image_tools.hwpack.hwpack_fields import (
40 FILE_FIELD,
41 PACKAGE_FIELD,40 PACKAGE_FIELD,
42 SPL_FILE_FIELD,
43 SPL_PACKAGE_FIELD,41 SPL_PACKAGE_FIELD,
44 COPY_FILES_FIELD,
45)42)
4643
47# The fields that hold packages to be installed.44# The fields that hold packages to be installed.
48PACKAGE_FIELDS = [PACKAGE_FIELD, SPL_PACKAGE_FIELD]45PACKAGE_FIELDS = [PACKAGE_FIELD, SPL_PACKAGE_FIELD]
49# Specification of files (boot related) to extract:
50# <field_containing_filepaths>: (<take_files_from_package>,
51# <put_into_this_hwpack_subdir>)
52# if <put_into_this_hwpack_subdir> is None, it will be <bootloader_name> for
53# global bootloader, or <board>-<bootloader_name> for board-specific
54# bootloader
55EXTRACT_FILES = {FILE_FIELD: (PACKAGE_FIELD, None),
56 SPL_FILE_FIELD: (SPL_PACKAGE_FIELD, None),
57 COPY_FILES_FIELD: (PACKAGE_FIELD, None)}
58
59
60logger = logging.getLogger(__name__)46logger = logging.getLogger(__name__)
61
62
63LOCAL_ARCHIVE_LABEL = 'hwpack-local'47LOCAL_ARCHIVE_LABEL = 'hwpack-local'
6448
6549
@@ -166,57 +150,72 @@
166 # Eliminate duplicates.150 # Eliminate duplicates.
167 return list(set(boot_packages))151 return list(set(boot_packages))
168152
169 def extract_bootloader_files(self, board, bootloader_name,153 def do_extract_file(self, package, source_path, dest_path):
170 bootloader_conf):154 """Extract specified file from package to dest_path."""
171 for key, value in bootloader_conf.iteritems():155 package_ref = self.find_fetched_package(self.packages, package)
172 if key in EXTRACT_FILES:156 return self.add_file_to_hwpack(package_ref, source_path, dest_path)
173 package_field, dest_path = EXTRACT_FILES[key]157
174 if not dest_path:158 def do_extract_files(self):
175 dest_path = bootloader_name159 """Go through a bootloader config, search for files to extract."""
176 if board:160 base_dest_path = ""
177 dest_path += "-" + board161 if self.config.board:
178 # Dereference package field to get actual package name162 base_dest_path = self.config.board
179 package = bootloader_conf.get(package_field)163 base_dest_path = os.path.join(base_dest_path, self.config.bootloader)
180 src_files = value164 # Extract bootloader file
181165 if self.config.bootloader_package and self.config.bootloader_file:
182 # Process scalar and list fields consistently below166 dest_path = os.path.join(base_dest_path,
183 field_value_scalar = False167 os.path.dirname(self.config.bootloader_file))
184 if type(src_files) != type([]):168 self.do_extract_file(self.config.bootloader_package,
185 src_files = [src_files]169 self.config.bootloader_file,
186 field_value_scalar = True170 dest_path)
187171
188 package_ref = self.find_fetched_package(172 # Extract SPL file
189 self.packages, package)173 if self.config.spl_package and self.config.spl_file:
190 added_files = []174 dest_path = os.path.join(base_dest_path,
191 for f in src_files:175 os.path.dirname(self.config.spl_file))
192 added_files.append(self.add_file_to_hwpack(176 self.do_extract_file(self.config.spl_package,
193 package_ref, f, dest_path))177 self.config.spl_file,
194 # Store within-hwpack file paths with the same178 dest_path)
195 # scalar/list type as original field.179
196 if field_value_scalar:180 def foreach_boards_and_bootloaders(self, function):
197 assert len(added_files) == 1181 """Call function for each board + bootloader combination in metadata"""
198 added_files = added_files[0]182 if self.config.bootloaders is not None:
199 bootloader_conf[key] = added_files183 for bootloader in self.config.bootloaders:
200184 self.config.set_board(None)
201 def extract_files(self, config_dictionary, is_bootloader_config,185 self.config.set_bootloader(bootloader)
202 board=None):186 function()
203 """Extract (boot) files based on EXTRACT_FILES spec and put187
204 them into hwpack."""188 if self.config.boards is not None:
205 self.remove_packages = []189 for board in self.config.boards:
206 if is_bootloader_config:190 if self.config.bootloaders is not None:
207 for bootl_name, bootl_conf in config_dictionary.iteritems():191 for bootloader in self.config.bootloaders:
208 self.extract_bootloader_files(board, bootl_name, bootl_conf)192 self.config.set_board(board)
209 else:193 self.config.set_bootloader(bootloader)
210 # This is board config194 function()
211 for board, board_conf in config_dictionary.iteritems():195
212 bootloaders = board_conf['bootloaders']196 def extract_files(self):
213 self.extract_files(bootloaders, True, board)197 """Find bootloaders in config that may contain files to extract."""
214198 if float(self.config.format.format_as_string) < 3.0:
215 # Clean up no longer needed packages.199 # extract files was introduced in version 3 configurations and is
216 for package in self.remove_packages:200 # a null operation for earlier configuration files
217 if package in self.packages:201 return
218 self.packages.remove(package)202
219 self.remove_packages = []203 self.foreach_boards_and_bootloaders(self.do_extract_files)
204
205 def do_find_copy_files_packages(self):
206 """Find packages referenced by copy_files (single board, bootloader)"""
207 copy_files = self.config.bootloader_copy_files
208 if copy_files:
209 self.copy_files_packages.extend(copy_files.keys())
210
211 def find_copy_files_packages(self):
212 """Find all packages referenced by copy_files sections in metadata."""
213 self.copy_files_packages = []
214 self.foreach_boards_and_bootloaders(
215 self.do_find_copy_files_packages)
216 packages = self.copy_files_packages
217 del(self.copy_files_packages)
218 return packages
220219
221 def build(self):220 def build(self):
222 for architecture in self.config.architectures:221 for architecture in self.config.architectures:
@@ -242,6 +241,8 @@
242 if self.config.boards is not None:241 if self.config.boards is not None:
243 self.packages.extend(self.find_bootloader_packages(242 self.packages.extend(self.find_bootloader_packages(
244 self.config.boards))243 self.config.boards))
244
245 self.packages.extend(self.find_copy_files_packages())
245 else:246 else:
246 if self.config.bootloader_package is not None:247 if self.config.bootloader_package is not None:
247 self.packages.append(self.config.bootloader_package)248 self.packages.append(self.config.bootloader_package)
@@ -268,14 +269,9 @@
268 # On a v3 hwpack, all the values we need to check are269 # On a v3 hwpack, all the values we need to check are
269 # in the bootloaders and boards section, so we loop270 # in the bootloaders and boards section, so we loop
270 # through both of them changing what is necessary.271 # through both of them changing what is necessary.
272
271 if self.config.format.format_as_string == '3.0':273 if self.config.format.format_as_string == '3.0':
272 if self.config.bootloaders is not None:274 self.extract_files()
273 self.extract_files(self.config.bootloaders,
274 True)
275 metadata.bootloaders = self.config.bootloaders
276 if self.config.boards is not None:
277 self.extract_files(self.config.boards, False)
278 metadata.boards = self.config.boards
279 else:275 else:
280 bootloader_package = None276 bootloader_package = None
281 if self.config.bootloader_file is not None:277 if self.config.bootloader_file is not None:
282278
=== modified file 'linaro_image_tools/hwpack/config.py'
--- linaro_image_tools/hwpack/config.py 2012-08-30 10:39:02 +0000
+++ linaro_image_tools/hwpack/config.py 2012-09-11 14:33:43 +0000
@@ -21,6 +21,7 @@
2121
22import ConfigParser22import ConfigParser
23from operator import attrgetter23from operator import attrgetter
24import os
24import re25import re
25import string26import string
26import yaml27import yaml
@@ -116,6 +117,8 @@
116 translate_v2_to_v3[BOOTLOADER_IN_BOOT_PART_KEY] = IN_BOOT_PART_FIELD117 translate_v2_to_v3[BOOTLOADER_IN_BOOT_PART_KEY] = IN_BOOT_PART_FIELD
117 BOOTLOADER_DD_KEY = 'u_boot_dd'118 BOOTLOADER_DD_KEY = 'u_boot_dd'
118 translate_v2_to_v3[BOOTLOADER_DD_KEY] = DD_FIELD119 translate_v2_to_v3[BOOTLOADER_DD_KEY] = DD_FIELD
120 last_used_keys = []
121 board = None
119122
120 def __init__(self, fp, bootloader=None, board=None,123 def __init__(self, fp, bootloader=None, board=None,
121 allow_unset_bootloader=False):124 allow_unset_bootloader=False):
@@ -354,9 +357,56 @@
354 return self._get_bootloader_option(SPL_IN_BOOT_PART_FIELD)357 return self._get_bootloader_option(SPL_IN_BOOT_PART_FIELD)
355358
356 @property359 @property
357 def boot_copy_files(self):360 def bootloader_copy_files(self):
358 """Extra files to copy to boot partition."""361 """Extra files to copy to boot partition.
359 return self._get_bootloader_option(COPY_FILES_FIELD)362
363 This can be stored in several formats. We always present in a common
364 one: {source_package: [{source_file_path: dest_file_path}].
365 dest_file_path (in the above example) is always absolute.
366 """
367 #copy_files:
368 # source_package:
369 # - source_file_path : dest_file_path
370 # - source_file_without_explicit_destination
371 #copy_files:
372 # - file1
373 # - file2: dest_path
374 #
375 # Note that the list of files is always that - a list.
376
377 copy_files = self._get_bootloader_option(COPY_FILES_FIELD)
378
379 if copy_files is None:
380 return None
381
382 if not isinstance(copy_files, dict):
383 copy_files = {self.bootloader_package: copy_files}
384
385 for package in copy_files:
386 new_list = []
387 for value in copy_files[package]:
388 if not isinstance(value, dict):
389 dest_path = "/boot"
390 source_path = value
391 else:
392 if len(value.keys()) > 1:
393 raise HwpackConfigError("copy_files entry found with"
394 "more than one destination")
395 source_path = value.keys()[0]
396 dest_path = value[source_path]
397
398 if not dest_path.startswith("/boot"):
399 # Target path should be relative, or start with /boot - we
400 # don't support to copying to anywhere other than /boot.
401 if dest_path[0] == "/":
402 raise HwpackConfigError("copy_files destinations must"
403 "be relative to /boot or start with /boot.")
404 dest_path = os.path.join("/boot", dest_path)
405
406 new_list.append({source_path: dest_path})
407 copy_files[package] = new_list
408
409 return copy_files
360410
361 @property411 @property
362 def spl_dd(self):412 def spl_dd(self):
@@ -426,8 +476,22 @@
426 key = self._v2_key_to_v3(key)476 key = self._v2_key_to_v3(key)
427 if result is not None:477 if result is not None:
428 result = result.get(key, None)478 result = result.get(key, None)
479 self.last_used_keys = keys
429 return result480 return result
430481
482 def get_last_used_keys(self):
483 """Used so you can work out which boards + boot loader was used.
484
485 Configuration data is stored in a dictionary. This returns a list of
486 keys used to traverse into the dictionary the last time an item was
487 looked up.
488
489 This can be used to see where a bit of information came from - we
490 store data that may be indexed differently depending on which board
491 and bootloader are set.
492 """
493 return self.last_used_keys
494
431 def get_option(self, name):495 def get_option(self, name):
432 """Return the value of an attribute by name.496 """Return the value of an attribute by name.
433497
434498
=== modified file 'linaro_image_tools/hwpack/tests/test_builder.py'
--- linaro_image_tools/hwpack/tests/test_builder.py 2012-09-04 10:42:27 +0000
+++ linaro_image_tools/hwpack/tests/test_builder.py 2012-09-11 14:33:43 +0000
@@ -31,7 +31,7 @@
31 HardwarePackBuilder,31 HardwarePackBuilder,
32 logger as builder_logger,32 logger as builder_logger,
33 )33 )
34from linaro_image_tools.hwpack.config import HwpackConfigError, Config34from linaro_image_tools.hwpack.config import HwpackConfigError
35from linaro_image_tools.hwpack.hardwarepack import Metadata35from linaro_image_tools.hwpack.hardwarepack import Metadata
36from linaro_image_tools.hwpack.packages import (36from linaro_image_tools.hwpack.packages import (
37 FetchedPackage,37 FetchedPackage,
@@ -55,7 +55,6 @@
55 MockSomethingFixture,55 MockSomethingFixture,
56 MockCmdRunnerPopenFixture,56 MockCmdRunnerPopenFixture,
57 )57 )
58from StringIO import StringIO
5958
6059
61class ConfigFileMissingTests(TestCase):60class ConfigFileMissingTests(TestCase):
@@ -431,11 +430,17 @@
431 Equals("Local package 'bar' not included"))430 Equals("Local package 'bar' not included"))
432431
433 def test_global_and_board_bootloader(self):432 def test_global_and_board_bootloader(self):
434 package_names = ['package0', 'package1']433 package_names = ['package0', 'package1', 'package2', 'package3']
435 files = {package_names[0]:434 files = {
436 ["usr/lib/u-boot/omap4_panda/u-boot.img",435 package_names[0]:
437 "usr/share/doc/u-boot-linaro-omap4-panda/copyright"],436 ["usr/lib/u-boot/omap4_panda/u-boot.img",
438 package_names[1]: ["usr/lib/u-boot/omap4_panda/u-boot.img"]}437 "usr/share/doc/u-boot-linaro-omap4-panda/copyright"],
438 package_names[1]:
439 ["usr/lib/u-boot/omap4_panda/u-boot.img",
440 "some/path/file"],
441 package_names[2]: [],
442 package_names[3]: [],
443 }
439444
440 config_v3 = self.config_v3 + "\n".join([445 config_v3 = self.config_v3 + "\n".join([
441 "bootloaders:",446 "bootloaders:",
@@ -443,13 +448,17 @@
443 self.bootloader_config,448 self.bootloader_config,
444 " file: " + files[package_names[0]][0],449 " file: " + files[package_names[0]][0],
445 " copy_files:",450 " copy_files:",
446 " - " + files[package_names[0]][1],451 " " + package_names[2] + ":",
452 " - some_file",
447 "boards:",453 "boards:",
448 " board1:",454 " board1:",
449 " bootloaders:",455 " bootloaders:",
450 " u_boot:",456 " u_boot:",
451 " package: %s",457 " package: %s",
452 " file: " + files[package_names[1]][0],458 " file: " + files[package_names[1]][0],
459 " copy_files:",
460 " " + package_names[3] + ":",
461 " - some_file",
453 " in_boot_part: true",462 " in_boot_part: true",
454 "sources:",463 "sources:",
455 " ubuntu: %s"])464 " ubuntu: %s"])
@@ -480,10 +489,6 @@
480489
481 config_file_fixture = self.useFixture(ConfigFileFixture(config_v3))490 config_file_fixture = self.useFixture(ConfigFileFixture(config_v3))
482491
483 # Parse the config
484 config = Config(StringIO(config_v3))
485 config.set_bootloader("u_boot")
486
487 # Build a hardware pack492 # Build a hardware pack
488 builder = HardwarePackBuilder(493 builder = HardwarePackBuilder(
489 config_file_fixture.filename, "1.0",494 config_file_fixture.filename, "1.0",
@@ -491,6 +496,9 @@
491 for package in available_packages])496 for package in available_packages])
492497
493 builder.build()498 builder.build()
499 stored_package_names = [p.name for p in builder.packages]
500 for package_name in package_names:
501 self.assertIn(package_name, stored_package_names)
494502
495 # Read the contents of the hardware pack, making sure it is as expected503 # Read the contents of the hardware pack, making sure it is as expected
496 tf = tarfile.open("hwpack_ahwpack_1.0_armel.tar.gz", mode="r:gz")504 tf = tarfile.open("hwpack_ahwpack_1.0_armel.tar.gz", mode="r:gz")
@@ -499,11 +507,9 @@
499 # files this is "<package_name> <originating path>" so they can be507 # files this is "<package_name> <originating path>" so they can be
500 # uniquely identified.508 # uniquely identified.
501 expected_files = [509 expected_files = [
502 ("u_boot/u-boot.img",510 ("u_boot/" + files[package_names[0]][0],
503 package_names[0] + " " + files[package_names[0]][0]),511 package_names[0] + " " + files[package_names[0]][0]),
504 ("u_boot/copyright",512 ("board1/u_boot/" + files[package_names[1]][0],
505 package_names[0] + " " + files[package_names[0]][1]),
506 ("u_boot-board1/u-boot.img",
507 package_names[1] + " " + files[package_names[1]][0])]513 package_names[1] + " " + files[package_names[1]][0])]
508514
509 for expected_file, contents in expected_files:515 for expected_file, contents in expected_files:
510516
=== modified file 'linaro_image_tools/media_create/boards.py'
--- linaro_image_tools/media_create/boards.py 2012-09-05 09:29:14 +0000
+++ linaro_image_tools/media_create/boards.py 2012-09-11 14:33:43 +0000
@@ -37,6 +37,7 @@
37import string37import string
38import logging38import logging
39from linaro_image_tools.hwpack.config import Config39from linaro_image_tools.hwpack.config import Config
40from linaro_image_tools.hwpack.builder import PackageUnpacker
4041
41from parted import Device42from parted import Device
4243
@@ -128,6 +129,7 @@
128 self.hwpack_tarfiles = []129 self.hwpack_tarfiles = []
129 self.bootloader = bootloader130 self.bootloader = bootloader
130 self.board = board131 self.board = board
132 self.tempdirs = {}
131133
132 class FakeSecHead(object):134 class FakeSecHead(object):
133 """ Add a fake section header to the metadata file.135 """ Add a fake section header to the metadata file.
@@ -162,9 +164,15 @@
162 if self.tempdir is not None and os.path.exists(self.tempdir):164 if self.tempdir is not None and os.path.exists(self.tempdir):
163 shutil.rmtree(self.tempdir)165 shutil.rmtree(self.tempdir)
164166
165 def get_field(self, field):167 for name in self.tempdirs:
168 tempdir = self.tempdirs[name]
169 if tempdir is not None and os.path.exists(tempdir):
170 shutil.rmtree(tempdir)
171
172 def get_field(self, field, return_keys=False):
166 data = None173 data = None
167 hwpack_with_data = None174 hwpack_with_data = None
175 keys = None
168 for hwpack_tarfile in self.hwpack_tarfiles:176 for hwpack_tarfile in self.hwpack_tarfiles:
169 metadata = hwpack_tarfile.extractfile(self.metadata_filename)177 metadata = hwpack_tarfile.extractfile(self.metadata_filename)
170 lines = metadata.readlines()178 lines = metadata.readlines()
@@ -181,9 +189,13 @@
181 new_data)189 new_data)
182 data = new_data190 data = new_data
183 hwpack_with_data = hwpack_tarfile191 hwpack_with_data = hwpack_tarfile
192 if return_keys:
193 keys = parser.get_last_used_keys()
184 except ConfigParser.NoOptionError:194 except ConfigParser.NoOptionError:
185 continue195 continue
186196
197 if return_keys:
198 return data, hwpack_with_data, keys
187 return data, hwpack_with_data199 return data, hwpack_with_data
188200
189 def get_format(self):201 def get_format(self):
@@ -207,7 +219,8 @@
207 file reference(s)219 file reference(s)
208 :return: path to a file or list of paths to files220 :return: path to a file or list of paths to files
209 """221 """
210 file_names, hwpack_tarfile = self.get_field(file_alias)222 file_names, hwpack_tarfile, keys = self.get_field(file_alias,
223 return_keys=True)
211 if not file_names:224 if not file_names:
212 return file_names225 return file_names
213 single = False226 single = False
@@ -215,7 +228,24 @@
215 single = True228 single = True
216 file_names = [file_names]229 file_names = [file_names]
217 out_files = []230 out_files = []
231
232 # Depending on if board and/or bootloader were used to look up the
233 # file we are getting, we need to prepend those names to the path
234 # to get the correct extracted file from the hardware pack.
235 config_names = [("board", "boards"), ("bootloader", "bootloaders")]
236 base_path = ""
237 if keys:
238 # If keys is non-empty, we have a V3 config option that was
239 # modified by the bootloader and/or boot option...
240 for name, key in config_names:
241 if self.get_field(name):
242 value = self.get_field(name)[0]
243 if keys[0] == key:
244 base_path = os.path.join(base_path, value)
245 keys = keys[1:]
246
218 for f in file_names:247 for f in file_names:
248 f = os.path.join(base_path, f)
219 hwpack_tarfile.extract(f, self.tempdir)249 hwpack_tarfile.extract(f, self.tempdir)
220 f = os.path.join(self.tempdir, f)250 f = os.path.join(self.tempdir, f)
221 out_files.append(f)251 out_files.append(f)
@@ -223,6 +253,104 @@
223 return out_files[0]253 return out_files[0]
224 return out_files254 return out_files
225255
256 def list_packages(self):
257 """Return list of (package names, TarFile object containing them)"""
258 packages = []
259 for tf in self.hwpack_tarfiles:
260 for name in tf.getnames():
261 if name.startswith("pkgs/") and name.endswith(".deb"):
262 packages.append((tf, name))
263 return packages
264
265 def find_package_for(self, name, version=None, revision=None,
266 architecture=None):
267 """Find a package that matches the name, version, rev and arch given.
268
269 Packages are named according to the debian specification:
270 http://www.debian.org/doc/manuals/debian-faq/ch-pkg_basics.en.html
271 <name>_<Version>-<DebianRevisionNumber>_<DebianArchitecture>.deb
272 DebianRevisionNumber seems to be optional.
273 Use this spec to return a package matching the requirements given.
274 """
275 for tar_file, package in self.list_packages():
276 file_name = os.path.basename(package)
277 dpkg_chunks = re.search("^(.+)_(.+)_(.+)\.deb$",
278 file_name)
279 assert dpkg_chunks, "Could not split package file name into"\
280 "<name>_<Version>_<DebianArchitecture>.deb"
281
282 pkg_name = dpkg_chunks.group(1)
283 pkg_version = dpkg_chunks.group(2)
284 pkg_architecture = dpkg_chunks.group(3)
285
286 ver_chunks = re.search("^(.+)-(.+)$", pkg_version)
287 if ver_chunks:
288 pkg_version = ver_chunks.group(1)
289 pkg_revision = ver_chunks.group(2)
290 else:
291 pkg_revision = None
292
293 if name != pkg_name:
294 continue
295 if version != None and str(version) != pkg_version:
296 continue
297 if revision != None and str(revision) != pkg_revision:
298 continue
299 if (architecture != None and
300 str(architecture) != pkg_architecture):
301 continue
302
303 # Got a matching package - return its path inside the tarball
304 return tar_file, package
305
306 # Failed to find a matching package - return None
307 return None
308
309 def get_file_from_package(self, file_path, package_name,
310 package_version=None, package_revision=None,
311 package_architecture=None):
312 """Extract named file from package specified by name, ver, rev, arch.
313
314 File is extracted from the package matching the given specification
315 to a temporary directory. The absolute path to the extracted file is
316 returned.
317 """
318
319 package_info = self.find_package_for(package_name,
320 package_version,
321 package_revision,
322 package_architecture)
323 if package_info is None:
324 return None
325 tar_file, package = package_info
326
327 # Avoid unpacking hardware pack more than once by assigning each one
328 # its own tempdir to unpack into.
329 # TODO: update logic that uses self.tempdir so we can get rid of this
330 # by sharing nicely.
331 if not package in self.tempdirs:
332 self.tempdirs[package] = tempfile.mkdtemp()
333 tempdir = self.tempdirs[package]
334
335 # We extract everything in the hardware pack so we don't have to worry
336 # about chasing links (extract a link, find where it points to, extract
337 # that...). This is slower, but more reliable.
338 tar_file.extractall(tempdir)
339 package_path = os.path.join(tempdir, package)
340
341 with PackageUnpacker() as self.package_unpacker:
342 extracted_file = self.package_unpacker.get_file(package_path,
343 file_path)
344 after_tmp = re.sub(self.package_unpacker.tempdir, "",
345 extracted_file).lstrip("/\\")
346 extract_dir = os.path.join(tempdir, "extracted",
347 os.path.dirname(after_tmp))
348 os.makedirs(extract_dir)
349 shutil.move(extracted_file, extract_dir)
350 extracted_file = os.path.join(extract_dir,
351 os.path.basename(extracted_file))
352 return extracted_file
353
226354
227class BoardConfig(object):355class BoardConfig(object):
228 board = None356 board = None
@@ -465,6 +593,13 @@
465 cls.SAMSUNG_V310_BL2_START = (cls.SAMSUNG_V310_ENV_START +593 cls.SAMSUNG_V310_BL2_START = (cls.SAMSUNG_V310_ENV_START +
466 cls.SAMSUNG_V310_ENV_LEN)594 cls.SAMSUNG_V310_ENV_LEN)
467595
596 cls.bootloader_copy_files = cls.hardwarepack_handler.get_field(
597 "bootloader_copy_files")[0]
598
599 cls.bootloader = cls.hardwarepack_handler.get_field(
600 "bootloader")
601 cls.board = board
602
468 @classmethod603 @classmethod
469 def get_file(cls, file_alias, default=None):604 def get_file(cls, file_alias, default=None):
470 # XXX remove the 'default' parameter when V1 support is removed!605 # XXX remove the 'default' parameter when V1 support is removed!
@@ -793,7 +928,6 @@
793 bootloader_parts_dir = os.path.join(chroot_dir, parts_dir)928 bootloader_parts_dir = os.path.join(chroot_dir, parts_dir)
794 cmd_runner.run(['mkdir', '-p', boot_disk]).wait()929 cmd_runner.run(['mkdir', '-p', boot_disk]).wait()
795 with partition_mounted(boot_partition, boot_disk):930 with partition_mounted(boot_partition, boot_disk):
796 boot_files = []
797 with cls.hardwarepack_handler:931 with cls.hardwarepack_handler:
798 if cls.bootloader_file_in_boot_part:932 if cls.bootloader_file_in_boot_part:
799 # <legacy v1 support>933 # <legacy v1 support>
@@ -813,22 +947,48 @@
813 assert bootloader_bin is not None, (947 assert bootloader_bin is not None, (
814 "bootloader binary could not be found")948 "bootloader binary could not be found")
815949
816 boot_files.append(bootloader_bin)
817
818 copy_files = cls.get_file('boot_copy_files')
819 if copy_files:
820 boot_files.extend(copy_files)
821
822 for f in boot_files:
823 proc = cmd_runner.run(950 proc = cmd_runner.run(
824 ['cp', '-v', f, boot_disk], as_root=True)951 ['cp', '-v', bootloader_bin, boot_disk], as_root=True)
825 proc.wait()952 proc.wait()
826953
954 # Handle copy_files field.
955 cls.copy_files(boot_disk)
956
827 cls.make_boot_files(957 cls.make_boot_files(
828 bootloader_parts_dir, is_live, is_lowmem, consoles, chroot_dir,958 bootloader_parts_dir, is_live, is_lowmem, consoles, chroot_dir,
829 rootfs_id, boot_disk, boot_device_or_file)959 rootfs_id, boot_disk, boot_device_or_file)
830960
831 @classmethod961 @classmethod
962 def copy_files(cls, boot_disk):
963 """Handle the copy_files metadata field."""
964
965 # Extract anything specified by copy_files sections
966 # self.bootloader_copy_files is always of the form:
967 # {'source_package':
968 # [
969 # {'source_path': 'dest_path'}
970 # ]
971 # }
972 if cls.bootloader_copy_files is None:
973 return
974
975 for source_package, file_list in cls.bootloader_copy_files.iteritems():
976 for file_info in file_list:
977 for source_path, dest_path in file_info.iteritems():
978 source = cls.hardwarepack_handler.get_file_from_package(
979 source_path, source_package)
980 dest_path = dest_path.lstrip("/\\")
981 dirname = os.path.dirname(dest_path)
982 dirname = os.path.join(boot_disk, dirname)
983 if not os.path.exists(dirname):
984 cmd_runner.run(["mkdir", "-p", dirname],
985 as_root=True).wait()
986 proc = cmd_runner.run(
987 ['cp', '-v', source,
988 os.path.join(boot_disk, dest_path)], as_root=True)
989 proc.wait()
990
991 @classmethod
832 def _get_kflavor_files(cls, path):992 def _get_kflavor_files(cls, path):
833 """Search for kernel, initrd and optional dtb in path."""993 """Search for kernel, initrd and optional dtb in path."""
834 if cls.kernel_flavors is None:994 if cls.kernel_flavors is None:
835995
=== modified file 'linaro_image_tools/media_create/tests/test_media_create.py'
--- linaro_image_tools/media_create/tests/test_media_create.py 2012-09-04 13:53:43 +0000
+++ linaro_image_tools/media_create/tests/test_media_create.py 2012-09-11 14:33:43 +0000
@@ -36,6 +36,7 @@
36from testtools import TestCase36from testtools import TestCase
3737
38from linaro_image_tools import cmd_runner38from linaro_image_tools import cmd_runner
39from linaro_image_tools.hwpack.packages import PackageMaker
39import linaro_image_tools.media_create40import linaro_image_tools.media_create
40from linaro_image_tools.media_create import (41from linaro_image_tools.media_create import (
41 android_boards,42 android_boards,
@@ -124,6 +125,7 @@
124 )125 )
125from linaro_image_tools.utils import find_command, preferred_tools_dir126from linaro_image_tools.utils import find_command, preferred_tools_dir
126127
128from linaro_image_tools.hwpack.testing import ContextManagerFixture
127129
128chroot_args = " ".join(cmd_runner.CHROOT_ARGS)130chroot_args = " ".join(cmd_runner.CHROOT_ARGS)
129sudo_args = " ".join(cmd_runner.SUDO_ARGS)131sudo_args = " ".join(cmd_runner.SUDO_ARGS)
@@ -309,36 +311,95 @@
309 test_file = hp.get_file('bootloader_file')311 test_file = hp.get_file('bootloader_file')
310 self.assertEquals(data, open(test_file, 'r').read())312 self.assertEquals(data, open(test_file, 'r').read())
311313
312 def test_get_file_v3(self):314 def test_list_packages(self):
313 # Test that get_file() works as expected with hwpackv3 and315 metadata = ("format: 3.0\nname: ahwpack\nversion: 4\narchitecture: "
314 # supports its new file fields.316 "armel\norigin: linaro\n")
315 metadata = textwrap.dedent("""\317 format = "3.0\n"
316 format: 3.0318 tarball = self.add_to_tarball([
317 name: ahwpack319 ("FORMAT", format),
318 version: 4320 ("metadata", metadata),
319 architecture: armel321 ("pkgs/foo_1-1_all.deb", ''),
320 origin: linaro322 ("pkgs/bar_1-1_all.deb", ''),
321 bootloaders:323 ])
322 u_boot:324
323 file: a_file325 hp = HardwarepackHandler([tarball], board='panda', bootloader='uefi')
324 copy_files:326 with hp:
325 - file1327 packages = hp.list_packages()
326 - file2328 names = [p[1] for p in packages]
327 uefi:329 self.assertIn('pkgs/foo_1-1_all.deb', names)
328 file: b_file330 self.assertIn('pkgs/bar_1-1_all.deb', names)
329 """)331 self.assertEqual(len(packages), 2)
330 files = {'FORMAT': '3.0\n', 'metadata': metadata,332
331 'a_file': 'a_file content', 'file1': 'file1 content',333 def test_find_package_for(self):
332 'file2': 'file2 content'}334 metadata = ("format: 3.0\nname: ahwpack\nversion: 4\narchitecture: "
333 tarball = self.add_to_tarball(files.items())335 "armel\norigin: linaro\n")
334 hp = HardwarepackHandler([tarball], bootloader='u_boot')336 format = "3.0\n"
335 with hp:337 tarball = self.add_to_tarball([
336 test_file = hp.get_file('bootloader_file')338 ("FORMAT", format),
337 self.assertEquals(files['a_file'], open(test_file, 'r').read())339 ("metadata", metadata),
338 test_files = hp.get_file('boot_copy_files')340 ("pkgs/foo_1-3_all.deb", ''),
339 self.assertEquals(len(test_files), 2)341 ("pkgs/foo_2-5_arm.deb", ''),
340 self.assertEquals(files['file1'], open(test_files[0], 'r').read())342 ("pkgs/bar_1-3_arm.deb", ''),
341 self.assertEquals(files['file2'], open(test_files[1], 'r').read())343 ])
344
345 hp = HardwarepackHandler([tarball], board='panda', bootloader='uefi')
346 with hp:
347 self.assertEqual(hp.find_package_for("foo")[1],
348 "pkgs/foo_1-3_all.deb")
349 self.assertEqual(hp.find_package_for("bar")[1],
350 "pkgs/bar_1-3_arm.deb")
351 self.assertEqual(hp.find_package_for("foo", version=2)[1],
352 "pkgs/foo_2-5_arm.deb")
353 self.assertEqual(hp.find_package_for("foo", version=2,
354 revision=5)[1],
355 "pkgs/foo_2-5_arm.deb")
356 self.assertEqual(hp.find_package_for("foo", version=2, revision=5,
357 architecture="arm")[1],
358 "pkgs/foo_2-5_arm.deb")
359 self.assertEqual(hp.find_package_for("foo", architecture="arm")[1],
360 "pkgs/foo_2-5_arm.deb")
361 self.assertEqual(hp.find_package_for("foo", architecture="all")[1],
362 "pkgs/foo_1-3_all.deb")
363
364 def test_get_file_from_package(self):
365 metadata = ("format: 3.0\nname: ahwpack\nversion: 4\narchitecture: "
366 "armel\norigin: linaro\n")
367 format = "3.0\n"
368
369 names = ['package0', 'package1', 'package2']
370 files = {names[0]:
371 ["usr/lib/u-boot/omap4_panda/u-boot.img",
372 "usr/share/doc/u-boot-linaro-omap4-panda/copyright"],
373 names[1]: ["usr/lib/u-boot/omap4_panda/u-boot2.img",
374 "foo/bar",
375 "flim/flam"],
376 names[2]: ["some/path/config"]}
377
378 # Generate some test packages
379 maker = PackageMaker()
380 self.useFixture(ContextManagerFixture(maker))
381
382 tarball_content = [("FORMAT", format), ("metadata", metadata)]
383
384 package_names = []
385 for package_name in names:
386 # The files parameter to make_package is a list of files to create.
387 # These files are text files containing package_name and their
388 # path. Since package_name is different for each package, this
389 # gives each file a unique content.
390 deb_file_path = maker.make_package(package_name, '1.0', {},
391 files=files[package_name])
392 name = os.path.basename(deb_file_path)
393 tarball_content.append((os.path.join("pkgs", name),
394 open(deb_file_path).read()))
395 package_names.append(name)
396
397 tarball = self.add_to_tarball(tarball_content)
398
399 hp = HardwarepackHandler([tarball], board='panda', bootloader='uefi')
400 with hp:
401 path = hp.get_file_from_package("some/path/config", "package2")
402 self.assertTrue(path.endswith("some/path/config"))
342403
343404
344class TestSetMetadata(TestCaseWithFixtures):405class TestSetMetadata(TestCaseWithFixtures):
@@ -574,6 +635,23 @@
574 self.assertRaises(635 self.assertRaises(
575 AssertionError, config.set_metadata, 'ahwpack.tar.gz')636 AssertionError, config.set_metadata, 'ahwpack.tar.gz')
576637
638 def test_sets_copy_files(self):
639 self.useFixture(MockSomethingFixture(
640 linaro_image_tools.media_create.boards, 'HardwarepackHandler',
641 self.MockHardwarepackHandler))
642 field_to_test = 'bootloader_copy_files'
643 data_to_set = {'package':
644 [{"source1": "dest1"},
645 {"source2": "dest2"}]}
646 self.MockHardwarepackHandler.metadata_dict = {
647 field_to_test: data_to_set,
648 }
649
650 class config(BoardConfig):
651 pass
652 config.set_metadata('ahwpack.tar.gz')
653 self.assertEquals(data_to_set, config.bootloader_copy_files)
654
577655
578class TestGetMLOFile(TestCaseWithFixtures):656class TestGetMLOFile(TestCaseWithFixtures):
579657
@@ -2937,6 +3015,16 @@
2937 self.popen_fixture = self.useFixture(MockCmdRunnerPopenFixture())3015 self.popen_fixture = self.useFixture(MockCmdRunnerPopenFixture())
2938 self.useFixture(MockSomethingFixture(3016 self.useFixture(MockSomethingFixture(
2939 self.config, 'make_boot_files', self.save_args))3017 self.config, 'make_boot_files', self.save_args))
3018 self.config.hardwarepack_handler.get_file_from_package = \
3019 self.get_file_from_package
3020 self.config.bootloader_copy_files = None
3021
3022 def get_file_from_package(self, source_path, source_package):
3023 if source_package in self.config.bootloader_copy_files:
3024 for file_info in self.config.bootloader_copy_files[source_package]:
3025 if source_path in file_info:
3026 return source_path
3027 return None
29403028
2941 def prepare_config_v3(self, config):3029 def prepare_config_v3(self, config):
2942 class c(config):3030 class c(config):
@@ -2949,6 +3037,13 @@
2949 self.config.hardwarepack_handler.get_format = lambda: '3.0'3037 self.config.hardwarepack_handler.get_format = lambda: '3.0'
2950 self.config.hardwarepack_handler.get_file = \3038 self.config.hardwarepack_handler.get_file = \
2951 lambda file_alias: ['file1', 'file2']3039 lambda file_alias: ['file1', 'file2']
3040 self.config.hardwarepack_handler.get_file_from_package = \
3041 self.get_file_from_package
3042 self.config.bootloader_copy_files = {
3043 "package1":
3044 [{"file1": "/boot/"},
3045 {"file2": "/boot/grub/renamed"}]}
3046
2952 self.popen_fixture = self.useFixture(MockCmdRunnerPopenFixture())3047 self.popen_fixture = self.useFixture(MockCmdRunnerPopenFixture())
2953 self.useFixture(MockSomethingFixture(3048 self.useFixture(MockSomethingFixture(
2954 self.config, 'make_boot_files', self.save_args))3049 self.config, 'make_boot_files', self.save_args))
@@ -2984,6 +3079,7 @@
2984 self.prepare_config(boards.BoardConfig)3079 self.prepare_config(boards.BoardConfig)
2985 self.config.bootloader_flavor = "bootloader_flavor"3080 self.config.bootloader_flavor = "bootloader_flavor"
2986 self.config.bootloader_file_in_boot_part = True3081 self.config.bootloader_file_in_boot_part = True
3082 self.config.bootloader = "u_boot"
2987 self.call_populate_boot(self.config)3083 self.call_populate_boot(self.config)
2988 expected_calls = self.expected_calls[:]3084 expected_calls = self.expected_calls[:]
2989 expected_calls.insert(2,3085 expected_calls.insert(2,
@@ -3003,7 +3099,7 @@
3003 expected_calls, self.popen_fixture.mock.commands_executed)3099 expected_calls, self.popen_fixture.mock.commands_executed)
3004 self.assertEquals(self.expected_args, self.saved_args)3100 self.assertEquals(self.expected_args, self.saved_args)
30053101
3006 def test_populate_boot_copy_files(self):3102 def test_populate_bootloader_copy_files(self):
3007 self.prepare_config_v3(boards.BoardConfig)3103 self.prepare_config_v3(boards.BoardConfig)
3008 self.config.bootloader_flavor = "bootloader_flavor"3104 self.config.bootloader_flavor = "bootloader_flavor"
3009 # Test that copy_files works per spec (puts stuff in boot partition)3105 # Test that copy_files works per spec (puts stuff in boot partition)
@@ -3011,12 +3107,35 @@
3011 self.config.bootloader_file_in_boot_part = False3107 self.config.bootloader_file_in_boot_part = False
3012 self.call_populate_boot(self.config)3108 self.call_populate_boot(self.config)
3013 expected_calls = self.expected_calls[:]3109 expected_calls = self.expected_calls[:]
3014 expected_calls.insert(2,3110 expected_calls.insert(2, '%s mkdir -p boot_disk/boot' % sudo_args)
3111 expected_calls.insert(3,
3015 '%s cp -v file1 '3112 '%s cp -v file1 '
3016 'boot_disk' % sudo_args)3113 'boot_disk/boot/' % sudo_args)
3017 expected_calls.insert(3,3114 expected_calls.insert(4, '%s mkdir -p boot_disk/boot/grub' % sudo_args)
3115 expected_calls.insert(5,
3018 '%s cp -v file2 '3116 '%s cp -v file2 '
3019 'boot_disk' % sudo_args)3117 'boot_disk/boot/grub/renamed' % sudo_args)
3118 self.assertEquals(
3119 expected_calls, self.popen_fixture.mock.commands_executed)
3120 self.assertEquals(self.expected_args, self.saved_args)
3121
3122 def test_populate_bootloader_copy_files_bootloader_set(self):
3123 self.prepare_config_v3(boards.BoardConfig)
3124 self.config.bootloader_flavor = "bootloader_flavor"
3125 # Test that copy_files works per spec (puts stuff in boot partition)
3126 # even if bootloader not in_boot_part.
3127 self.config.bootloader_file_in_boot_part = False
3128 self.config.bootloader = "u_boot"
3129 self.call_populate_boot(self.config)
3130 expected_calls = self.expected_calls[:]
3131 expected_calls.insert(2, '%s mkdir -p boot_disk/boot' % sudo_args)
3132 expected_calls.insert(3,
3133 '%s cp -v file1 '
3134 'boot_disk/boot/' % sudo_args)
3135 expected_calls.insert(4, '%s mkdir -p boot_disk/boot/grub' % sudo_args)
3136 expected_calls.insert(5,
3137 '%s cp -v file2 '
3138 'boot_disk/boot/grub/renamed' % sudo_args)
3020 self.assertEquals(3139 self.assertEquals(
3021 expected_calls, self.popen_fixture.mock.commands_executed)3140 expected_calls, self.popen_fixture.mock.commands_executed)
3022 self.assertEquals(self.expected_args, self.saved_args)3141 self.assertEquals(self.expected_args, self.saved_args)

Subscribers

People subscribed via source and target branches