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

Subscribers

People subscribed via source and target branches