binman: Avoid changing a dict during iteration
[oweals/u-boot.git] / tools / binman / control.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Creates binary images from input files controlled by a description
6 #
7
8 from __future__ import print_function
9
10 from collections import OrderedDict
11 import os
12 import sys
13 import tools
14
15 import command
16 import elf
17 from image import Image
18 import state
19 import tout
20
21 # List of images we plan to create
22 # Make this global so that it can be referenced from tests
23 images = OrderedDict()
24
25 def _ReadImageDesc(binman_node):
26     """Read the image descriptions from the /binman node
27
28     This normally produces a single Image object called 'image'. But if
29     multiple images are present, they will all be returned.
30
31     Args:
32         binman_node: Node object of the /binman node
33     Returns:
34         OrderedDict of Image objects, each of which describes an image
35     """
36     images = OrderedDict()
37     if 'multiple-images' in binman_node.props:
38         for node in binman_node.subnodes:
39             images[node.name] = Image(node.name, node)
40     else:
41         images['image'] = Image('image', binman_node)
42     return images
43
44 def _FindBinmanNode(dtb):
45     """Find the 'binman' node in the device tree
46
47     Args:
48         dtb: Fdt object to scan
49     Returns:
50         Node object of /binman node, or None if not found
51     """
52     for node in dtb.GetRoot().subnodes:
53         if node.name == 'binman':
54             return node
55     return None
56
57 def WriteEntryDocs(modules, test_missing=None):
58     """Write out documentation for all entries
59
60     Args:
61         modules: List of Module objects to get docs for
62         test_missing: Used for testing only, to force an entry's documeentation
63             to show as missing even if it is present. Should be set to None in
64             normal use.
65     """
66     from entry import Entry
67     Entry.WriteDocs(modules, test_missing)
68
69 def Binman(options, args):
70     """The main control code for binman
71
72     This assumes that help and test options have already been dealt with. It
73     deals with the core task of building images.
74
75     Args:
76         options: Command line options object
77         args: Command line arguments (list of strings)
78     """
79     global images
80
81     if options.full_help:
82         pager = os.getenv('PAGER')
83         if not pager:
84             pager = 'more'
85         fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
86                             'README')
87         command.Run(pager, fname)
88         return 0
89
90     # Try to figure out which device tree contains our image description
91     if options.dt:
92         dtb_fname = options.dt
93     else:
94         board = options.board
95         if not board:
96             raise ValueError('Must provide a board to process (use -b <board>)')
97         board_pathname = os.path.join(options.build_dir, board)
98         dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
99         if not options.indir:
100             options.indir = ['.']
101         options.indir.append(board_pathname)
102
103     try:
104         # Import these here in case libfdt.py is not available, in which case
105         # the above help option still works.
106         import fdt
107         import fdt_util
108
109         tout.Init(options.verbosity)
110         elf.debug = options.debug
111         state.use_fake_dtb = options.fake_dtb
112         try:
113             tools.SetInputDirs(options.indir)
114             tools.PrepareOutputDir(options.outdir, options.preserve)
115             state.SetEntryArgs(options.entry_arg)
116
117             # Get the device tree ready by compiling it and copying the compiled
118             # output into a file in our output directly. Then scan it for use
119             # in binman.
120             dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
121             fname = tools.GetOutputFilename('u-boot.dtb.out')
122             tools.WriteFile(fname, tools.ReadFile(dtb_fname))
123             dtb = fdt.FdtScan(fname)
124
125             node = _FindBinmanNode(dtb)
126             if not node:
127                 raise ValueError("Device tree '%s' does not have a 'binman' "
128                                  "node" % dtb_fname)
129
130             images = _ReadImageDesc(node)
131
132             if options.image:
133                 skip = []
134                 new_images = OrderedDict()
135                 for name, image in images.items():
136                     if name in options.image:
137                         new_images[name] = image
138                     else:
139                         skip.append(name)
140                 images = new_images
141                 if skip and options.verbosity >= 2:
142                     print('Skipping images: %s' % ', '.join(skip))
143
144             state.Prepare(images, dtb)
145
146             # Prepare the device tree by making sure that any missing
147             # properties are added (e.g. 'pos' and 'size'). The values of these
148             # may not be correct yet, but we add placeholders so that the
149             # size of the device tree is correct. Later, in
150             # SetCalculatedProperties() we will insert the correct values
151             # without changing the device-tree size, thus ensuring that our
152             # entry offsets remain the same.
153             for image in images.values():
154                 image.ExpandEntries()
155                 if options.update_fdt:
156                     image.AddMissingProperties()
157                 image.ProcessFdt(dtb)
158
159             for dtb_item in state.GetFdts():
160                 dtb_item.Sync(auto_resize=True)
161                 dtb_item.Pack()
162                 dtb_item.Flush()
163
164             for image in images.values():
165                 # Perform all steps for this image, including checking and
166                 # writing it. This means that errors found with a later
167                 # image will be reported after earlier images are already
168                 # completed and written, but that does not seem important.
169                 image.GetEntryContents()
170                 image.GetEntryOffsets()
171                 try:
172                     image.PackEntries()
173                     image.CheckSize()
174                     image.CheckEntries()
175                 except Exception as e:
176                     if options.map:
177                         fname = image.WriteMap()
178                         print("Wrote map file '%s' to show errors"  % fname)
179                     raise
180                 image.SetImagePos()
181                 if options.update_fdt:
182                     image.SetCalculatedProperties()
183                     for dtb_item in state.GetFdts():
184                         dtb_item.Sync()
185                 image.ProcessEntryContents()
186                 image.WriteSymbols()
187                 image.BuildImage()
188                 if options.map:
189                     image.WriteMap()
190
191             # Write the updated FDTs to our output files
192             for dtb_item in state.GetFdts():
193                 tools.WriteFile(dtb_item._fname, dtb_item.GetContents())
194
195         finally:
196             tools.FinaliseOutputDir()
197     finally:
198         tout.Uninit()
199
200     return 0