]> Git Repo - u-boot.git/blob - tools/binman/etype/fit.py
Rename CONFIG_SYS_TEXT_BASE to CONFIG_TEXT_BASE
[u-boot.git] / tools / binman / etype / fit.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <[email protected]>
4 #
5
6 """Entry-type module for producing a FIT"""
7
8 import libfdt
9
10 from binman.entry import Entry, EntryArg
11 from binman.etype.section import Entry_section
12 from binman import elf
13 from dtoc import fdt_util
14 from dtoc.fdt import Fdt
15 from patman import tools
16
17 # Supported operations, with the fit,operation property
18 OP_GEN_FDT_NODES, OP_SPLIT_ELF = range(2)
19 OPERATIONS = {
20     'gen-fdt-nodes': OP_GEN_FDT_NODES,
21     'split-elf': OP_SPLIT_ELF,
22     }
23
24 class Entry_fit(Entry_section):
25
26     """Flat Image Tree (FIT)
27
28     This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
29     input provided.
30
31     Nodes for the FIT should be written out in the binman configuration just as
32     they would be in a file passed to mkimage.
33
34     For example, this creates an image containing a FIT with U-Boot SPL::
35
36         binman {
37             fit {
38                 description = "Test FIT";
39                 fit,fdt-list = "of-list";
40
41                 images {
42                     kernel@1 {
43                         description = "SPL";
44                         os = "u-boot";
45                         type = "rkspi";
46                         arch = "arm";
47                         compression = "none";
48                         load = <0>;
49                         entry = <0>;
50
51                         u-boot-spl {
52                         };
53                     };
54                 };
55             };
56         };
57
58     More complex setups can be created, with generated nodes, as described
59     below.
60
61     Properties (in the 'fit' node itself)
62     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
63
64     Special properties have a `fit,` prefix, indicating that they should be
65     processed but not included in the final FIT.
66
67     The top-level 'fit' node supports the following special properties:
68
69         fit,external-offset
70             Indicates that the contents of the FIT are external and provides the
71             external offset. This is passed to mkimage via the -E and -p flags.
72
73         fit,fdt-list
74             Indicates the entry argument which provides the list of device tree
75             files for the gen-fdt-nodes operation (as below). This is often
76             `of-list` meaning that `-a of-list="dtb1 dtb2..."` should be passed
77             to binman.
78
79     Substitutions
80     ~~~~~~~~~~~~~
81
82     Node names and property values support a basic string-substitution feature.
83     Available substitutions for '@' nodes (and property values) are:
84
85     SEQ:
86         Sequence number of the generated fdt (1, 2, ...)
87     NAME
88         Name of the dtb as provided (i.e. without adding '.dtb')
89
90     The `default` property, if present, will be automatically set to the name
91     if of configuration whose devicetree matches the `default-dt` entry
92     argument, e.g. with `-a default-dt=sun50i-a64-pine64-lts`.
93
94     Available substitutions for property values in these nodes are:
95
96     DEFAULT-SEQ:
97         Sequence number of the default fdt, as provided by the 'default-dt'
98         entry argument
99
100     Available operations
101     ~~~~~~~~~~~~~~~~~~~~
102
103     You can add an operation to an '@' node to indicate which operation is
104     required::
105
106         @fdt-SEQ {
107             fit,operation = "gen-fdt-nodes";
108             ...
109         };
110
111     Available operations are:
112
113     gen-fdt-nodes
114         Generate FDT nodes as above. This is the default if there is no
115         `fit,operation` property.
116
117     split-elf
118         Split an ELF file into a separate node for each segment.
119
120     Generating nodes from an FDT list (gen-fdt-nodes)
121     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
122
123     U-Boot supports creating fdt and config nodes automatically. To do this,
124     pass an `of-list` property (e.g. `-a of-list=file1 file2`). This tells
125     binman that you want to generates nodes for two files: `file1.dtb` and
126     `file2.dtb`. The `fit,fdt-list` property (see above) indicates that
127     `of-list` should be used. If the property is missing you will get an error.
128
129     Then add a 'generator node', a node with a name starting with '@'::
130
131         images {
132             @fdt-SEQ {
133                 description = "fdt-NAME";
134                 type = "flat_dt";
135                 compression = "none";
136             };
137         };
138
139     This tells binman to create nodes `fdt-1` and `fdt-2` for each of your two
140     files. All the properties you specify will be included in the node. This
141     node acts like a template to generate the nodes. The generator node itself
142     does not appear in the output - it is replaced with what binman generates.
143     A 'data' property is created with the contents of the FDT file.
144
145     You can create config nodes in a similar way::
146
147         configurations {
148             default = "@config-DEFAULT-SEQ";
149             @config-SEQ {
150                 description = "NAME";
151                 firmware = "atf";
152                 loadables = "uboot";
153                 fdt = "fdt-SEQ";
154             };
155         };
156
157     This tells binman to create nodes `config-1` and `config-2`, i.e. a config
158     for each of your two files.
159
160     Note that if no devicetree files are provided (with '-a of-list' as above)
161     then no nodes will be generated.
162
163     Generating nodes from an ELF file (split-elf)
164     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
165
166     This uses the node as a template to generate multiple nodes. The following
167     special properties are available:
168
169     split-elf
170         Split an ELF file into a separate node for each segment. This uses the
171         node as a template to generate multiple nodes. The following special
172         properties are available:
173
174         fit,load
175             Generates a `load = <...>` property with the load address of the
176             segment
177
178         fit,entry
179             Generates a `entry = <...>` property with the entry address of the
180             ELF. This is only produced for the first entry
181
182         fit,data
183             Generates a `data = <...>` property with the contents of the segment
184
185         fit,loadables
186             Generates a `loadable = <...>` property with a list of the generated
187             nodes (including all nodes if this operation is used multiple times)
188
189
190     Here is an example showing ATF, TEE and a device tree all combined::
191
192         fit {
193             description = "test-desc";
194             #address-cells = <1>;
195             fit,fdt-list = "of-list";
196
197             images {
198                 u-boot {
199                     description = "U-Boot (64-bit)";
200                     type = "standalone";
201                     os = "U-Boot";
202                     arch = "arm64";
203                     compression = "none";
204                     load = <CONFIG_TEXT_BASE>;
205                     u-boot-nodtb {
206                     };
207                 };
208                 @fdt-SEQ {
209                     description = "fdt-NAME.dtb";
210                     type = "flat_dt";
211                     compression = "none";
212                 };
213                 @atf-SEQ {
214                     fit,operation = "split-elf";
215                     description = "ARM Trusted Firmware";
216                     type = "firmware";
217                     arch = "arm64";
218                     os = "arm-trusted-firmware";
219                     compression = "none";
220                     fit,load;
221                     fit,entry;
222                     fit,data;
223
224                     atf-bl31 {
225                     };
226                 };
227
228                 @tee-SEQ {
229                     fit,operation = "split-elf";
230                     description = "TEE";
231                     type = "tee";
232                     arch = "arm64";
233                     os = "tee";
234                     compression = "none";
235                     fit,load;
236                     fit,entry;
237                     fit,data;
238
239                     tee-os {
240                     };
241                 };
242             };
243
244             configurations {
245                 default = "@config-DEFAULT-SEQ";
246                 @config-SEQ {
247                     description = "conf-NAME.dtb";
248                     fdt = "fdt-SEQ";
249                     firmware = "u-boot";
250                     fit,loadables;
251                 };
252             };
253         };
254
255     If ATF-BL31 is available, this generates a node for each segment in the
256     ELF file, for example::
257
258         images {
259             atf-1 {
260                 data = <...contents of first segment...>;
261                 data-offset = <0x00000000>;
262                 entry = <0x00040000>;
263                 load = <0x00040000>;
264                 compression = "none";
265                 os = "arm-trusted-firmware";
266                 arch = "arm64";
267                 type = "firmware";
268                 description = "ARM Trusted Firmware";
269             };
270             atf-2 {
271                 data = <...contents of second segment...>;
272                 load = <0xff3b0000>;
273                 compression = "none";
274                 os = "arm-trusted-firmware";
275                 arch = "arm64";
276                 type = "firmware";
277                 description = "ARM Trusted Firmware";
278             };
279         };
280
281     The same applies for OP-TEE if that is available.
282
283     If each binary is not available, the relevant template node (@atf-SEQ or
284     @tee-SEQ) is removed from the output.
285
286     This also generates a `config-xxx` node for each device tree in `of-list`.
287     Note that the U-Boot build system uses `-a of-list=$(CONFIG_OF_LIST)`
288     so you can use `CONFIG_OF_LIST` to define that list. In this example it is
289     set up for `firefly-rk3399` with a single device tree and the default set
290     with `-a default-dt=$(CONFIG_DEFAULT_DEVICE_TREE)`, so the resulting output
291     is::
292
293         configurations {
294             default = "config-1";
295             config-1 {
296                 loadables = "atf-1", "atf-2", "atf-3", "tee-1", "tee-2";
297                 description = "rk3399-firefly.dtb";
298                 fdt = "fdt-1";
299                 firmware = "u-boot";
300             };
301         };
302
303     U-Boot SPL can then load the firmware (U-Boot proper) and all the loadables
304     (ATF and TEE), then proceed with the boot.
305     """
306     def __init__(self, section, etype, node):
307         """
308         Members:
309             _fit: FIT file being built
310             _entries: dict from Entry_section:
311                 key: relative path to entry Node (from the base of the FIT)
312                 value: Entry_section object comprising the contents of this
313                     node
314             _priv_entries: Internal copy of _entries which includes 'generator'
315                 entries which are used to create the FIT, but should not be
316                 processed as real entries. This is set up once we have the
317                 entries
318             _loadables: List of generated split-elf nodes, each a node name
319         """
320         super().__init__(section, etype, node)
321         self._fit = None
322         self._fit_props = {}
323         self._fdts = None
324         self.mkimage = None
325         self._priv_entries = {}
326         self._loadables = []
327
328     def ReadNode(self):
329         super().ReadNode()
330         for pname, prop in self._node.props.items():
331             if pname.startswith('fit,'):
332                 self._fit_props[pname] = prop
333         self._fit_list_prop = self._fit_props.get('fit,fdt-list')
334         if self._fit_list_prop:
335             fdts, = self.GetEntryArgsOrProps(
336                 [EntryArg(self._fit_list_prop.value, str)])
337             if fdts is not None:
338                 self._fdts = fdts.split()
339         self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
340                                                                   str)])[0]
341
342     def _get_operation(self, base_node, node):
343         """Get the operation referenced by a subnode
344
345         Args:
346             node (Node): Subnode (of the FIT) to check
347
348         Returns:
349             int: Operation to perform
350
351         Raises:
352             ValueError: Invalid operation name
353         """
354         oper_name = node.props.get('fit,operation')
355         if not oper_name:
356             return OP_GEN_FDT_NODES
357         oper = OPERATIONS.get(oper_name.value)
358         if oper is None:
359             self._raise_subnode(node, f"Unknown operation '{oper_name.value}'")
360         return oper
361
362     def ReadEntries(self):
363         def _add_entries(base_node, depth, node):
364             """Add entries for any nodes that need them
365
366             Args:
367                 base_node: Base Node of the FIT (with 'description' property)
368                 depth: Current node depth (0 is the base 'fit' node)
369                 node: Current node to process
370
371             Here we only need to provide binman entries which are used to define
372             the 'data' for each image. We create an entry_Section for each.
373             """
374             rel_path = node.path[len(base_node.path):]
375             in_images = rel_path.startswith('/images')
376             has_images = depth == 2 and in_images
377             if has_images:
378                 # This node is a FIT subimage node (e.g. "/images/kernel")
379                 # containing content nodes. We collect the subimage nodes and
380                 # section entries for them here to merge the content subnodes
381                 # together and put the merged contents in the subimage node's
382                 # 'data' property later.
383                 entry = Entry.Create(self, node, etype='section')
384                 entry.ReadNode()
385                 # The hash subnodes here are for mkimage, not binman.
386                 entry.SetUpdateHash(False)
387                 image_name = rel_path[len('/images/'):]
388                 self._entries[image_name] = entry
389
390             for subnode in node.subnodes:
391                 _add_entries(base_node, depth + 1, subnode)
392
393         _add_entries(self._node, 0, self._node)
394
395         # Keep a copy of all entries, including generator entries, since these
396         # removed from self._entries later.
397         self._priv_entries = dict(self._entries)
398
399     def BuildSectionData(self, required):
400         """Build FIT entry contents
401
402         This adds the 'data' properties to the input ITB (Image-tree Binary)
403         then runs mkimage to process it.
404
405         Args:
406             required (bool): True if the data must be present, False if it is OK
407                 to return None
408
409         Returns:
410             bytes: Contents of the section
411         """
412         data = self._build_input()
413         uniq = self.GetUniqueName()
414         input_fname = tools.get_output_filename(f'{uniq}.itb')
415         output_fname = tools.get_output_filename(f'{uniq}.fit')
416         tools.write_file(input_fname, data)
417         tools.write_file(output_fname, data)
418
419         args = {}
420         ext_offset = self._fit_props.get('fit,external-offset')
421         if ext_offset is not None:
422             args = {
423                 'external': True,
424                 'pad': fdt_util.fdt32_to_cpu(ext_offset.value)
425                 }
426         if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
427                             **args) is None:
428             # Bintool is missing; just use empty data as the output
429             self.record_missing_bintool(self.mkimage)
430             return tools.get_bytes(0, 1024)
431
432         return tools.read_file(output_fname)
433
434     def _raise_subnode(self, node, msg):
435         """Raise an error with a paticular FIT subnode
436
437         Args:
438             node (Node): FIT subnode containing the error
439             msg (str): Message to report
440
441         Raises:
442             ValueError, as requested
443         """
444         rel_path = node.path[len(self._node.path) + 1:]
445         self.Raise(f"subnode '{rel_path}': {msg}")
446
447     def _build_input(self):
448         """Finish the FIT by adding the 'data' properties to it
449
450         Arguments:
451             fdt: FIT to update
452
453         Returns:
454             bytes: New fdt contents
455         """
456         def _process_prop(pname, prop):
457             """Process special properties
458
459             Handles properties with generated values. At present the only
460             supported property is 'default', i.e. the default device tree in
461             the configurations node.
462
463             Args:
464                 pname (str): Name of property
465                 prop (Prop): Property to process
466             """
467             if pname == 'default':
468                 val = prop.value
469                 # Handle the 'default' property
470                 if val.startswith('@'):
471                     if not self._fdts:
472                         return
473                     if not self._fit_default_dt:
474                         self.Raise("Generated 'default' node requires default-dt entry argument")
475                     if self._fit_default_dt not in self._fdts:
476                         self.Raise(
477                             f"default-dt entry argument '{self._fit_default_dt}' "
478                             f"not found in fdt list: {', '.join(self._fdts)}")
479                     seq = self._fdts.index(self._fit_default_dt)
480                     val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
481                     fsw.property_string(pname, val)
482                     return
483             elif pname.startswith('fit,'):
484                 # Ignore these, which are commands for binman to process
485                 return
486             elif pname in ['offset', 'size', 'image-pos']:
487                 # Don't add binman's calculated properties
488                 return
489             fsw.property(pname, prop.bytes)
490
491         def _gen_fdt_nodes(base_node, node, depth, in_images):
492             """Generate FDT nodes
493
494             This creates one node for each member of self._fdts using the
495             provided template. If a property value contains 'NAME' it is
496             replaced with the filename of the FDT. If a property value contains
497             SEQ it is replaced with the node sequence number, where 1 is the
498             first.
499
500             Args:
501                 node (None): Generator node to process
502                 depth: Current node depth (0 is the base 'fit' node)
503                 in_images: True if this is inside the 'images' node, so that
504                     'data' properties should be generated
505             """
506             if self._fdts:
507                 # Generate nodes for each FDT
508                 for seq, fdt_fname in enumerate(self._fdts):
509                     node_name = node.name[1:].replace('SEQ', str(seq + 1))
510                     fname = tools.get_input_filename(fdt_fname + '.dtb')
511                     with fsw.add_node(node_name):
512                         for pname, prop in node.props.items():
513                             if pname == 'fit,loadables':
514                                 val = '\0'.join(self._loadables) + '\0'
515                                 fsw.property('loadables', val.encode('utf-8'))
516                             elif pname == 'fit,operation':
517                                 pass
518                             elif pname.startswith('fit,'):
519                                 self._raise_subnode(
520                                     node, f"Unknown directive '{pname}'")
521                             else:
522                                 val = prop.bytes.replace(
523                                     b'NAME', tools.to_bytes(fdt_fname))
524                                 val = val.replace(
525                                     b'SEQ', tools.to_bytes(str(seq + 1)))
526                                 fsw.property(pname, val)
527
528                         # Add data for 'images' nodes (but not 'config')
529                         if depth == 1 and in_images:
530                             fsw.property('data', tools.read_file(fname))
531
532                         for subnode in node.subnodes:
533                             with fsw.add_node(subnode.name):
534                                 _add_node(node, depth + 1, subnode)
535             else:
536                 if self._fdts is None:
537                     if self._fit_list_prop:
538                         self.Raise('Generator node requires '
539                             f"'{self._fit_list_prop.value}' entry argument")
540                     else:
541                         self.Raise("Generator node requires 'fit,fdt-list' property")
542
543         def _gen_split_elf(base_node, node, elf_data, missing):
544             """Add nodes for the ELF file, one per group of contiguous segments
545
546             Args:
547                 base_node (Node): Template node from the binman definition
548                 node (Node): Node to replace (in the FIT being built)
549                 data (bytes): ELF-format data to process (may be empty)
550                 missing (bool): True if any of the data is missing
551
552             """
553             # If any pieces are missing, skip this. The missing entries will
554             # show an error
555             if not missing:
556                 try:
557                     segments, entry = elf.read_loadable_segments(elf_data)
558                 except ValueError as exc:
559                     self._raise_subnode(node,
560                                         f'Failed to read ELF file: {str(exc)}')
561                 for (seq, start, data) in segments:
562                     node_name = node.name[1:].replace('SEQ', str(seq + 1))
563                     with fsw.add_node(node_name):
564                         loadables.append(node_name)
565                         for pname, prop in node.props.items():
566                             if not pname.startswith('fit,'):
567                                 fsw.property(pname, prop.bytes)
568                             elif pname == 'fit,load':
569                                 fsw.property_u32('load', start)
570                             elif pname == 'fit,entry':
571                                 if seq == 0:
572                                     fsw.property_u32('entry', entry)
573                             elif pname == 'fit,data':
574                                 fsw.property('data', bytes(data))
575                             elif pname != 'fit,operation':
576                                 self._raise_subnode(
577                                     node, f"Unknown directive '{pname}'")
578
579         def _gen_node(base_node, node, depth, in_images, entry):
580             """Generate nodes from a template
581
582             This creates one node for each member of self._fdts using the
583             provided template. If a property value contains 'NAME' it is
584             replaced with the filename of the FDT. If a property value contains
585             SEQ it is replaced with the node sequence number, where 1 is the
586             first.
587
588             Args:
589                 base_node (Node): Base Node of the FIT (with 'description'
590                     property)
591                 node (Node): Generator node to process
592                 depth (int): Current node depth (0 is the base 'fit' node)
593                 in_images (bool): True if this is inside the 'images' node, so
594                     that 'data' properties should be generated
595             """
596             oper = self._get_operation(base_node, node)
597             if oper == OP_GEN_FDT_NODES:
598                 _gen_fdt_nodes(base_node, node, depth, in_images)
599             elif oper == OP_SPLIT_ELF:
600                 # Entry_section.ObtainContents() either returns True or
601                 # raises an exception.
602                 data = None
603                 missing_list = []
604                 entry.ObtainContents()
605                 entry.Pack(0)
606                 data = entry.GetData()
607                 entry.CheckMissing(missing_list)
608
609                 _gen_split_elf(base_node, node, data, bool(missing_list))
610
611         def _add_node(base_node, depth, node):
612             """Add nodes to the output FIT
613
614             Args:
615                 base_node (Node): Base Node of the FIT (with 'description'
616                     property)
617                 depth (int): Current node depth (0 is the base 'fit' node)
618                 node (Node): Current node to process
619
620             There are two cases to deal with:
621                 - hash and signature nodes which become part of the FIT
622                 - binman entries which are used to define the 'data' for each
623                   image, so don't appear in the FIT
624             """
625             # Copy over all the relevant properties
626             for pname, prop in node.props.items():
627                 _process_prop(pname, prop)
628
629             rel_path = node.path[len(base_node.path):]
630             in_images = rel_path.startswith('/images')
631
632             has_images = depth == 2 and in_images
633             if has_images:
634                 image_name = rel_path[len('/images/'):]
635                 entry = self._priv_entries[image_name]
636                 data = entry.GetData()
637                 fsw.property('data', bytes(data))
638
639             for subnode in node.subnodes:
640                 subnode_path = f'{rel_path}/{subnode.name}'
641                 if has_images and not (subnode.name.startswith('hash') or
642                                        subnode.name.startswith('signature')):
643                     # This subnode is a content node not meant to appear in
644                     # the FIT (e.g. "/images/kernel/u-boot"), so don't call
645                     # fsw.add_node() or _add_node() for it.
646                     pass
647                 elif self.GetImage().generate and subnode.name.startswith('@'):
648                     entry = self._priv_entries.get(subnode.name)
649                     _gen_node(base_node, subnode, depth, in_images, entry)
650                     # This is a generator (template) entry, so remove it from
651                     # the list of entries used by PackEntries(), etc. Otherwise
652                     # it will appear in the binman output
653                     to_remove.append(subnode.name)
654                 else:
655                     with fsw.add_node(subnode.name):
656                         _add_node(base_node, depth + 1, subnode)
657
658         # Build a new tree with all nodes and properties starting from the
659         # entry node
660         fsw = libfdt.FdtSw()
661         fsw.INC_SIZE = 65536
662         fsw.finish_reservemap()
663         to_remove = []
664         loadables = []
665         with fsw.add_node(''):
666             _add_node(self._node, 0, self._node)
667         self._loadables = loadables
668         fdt = fsw.as_fdt()
669
670         # Remove generator entries from the main list
671         for path in to_remove:
672             if path in self._entries:
673                 del self._entries[path]
674
675         # Pack this new FDT and scan it so we can add the data later
676         fdt.pack()
677         data = fdt.as_bytearray()
678         return data
679
680     def SetImagePos(self, image_pos):
681         """Set the position in the image
682
683         This sets each subentry's offsets, sizes and positions-in-image
684         according to where they ended up in the packed FIT file.
685
686         Args:
687             image_pos (int): Position of this entry in the image
688         """
689         super().SetImagePos(image_pos)
690
691         # If mkimage is missing we'll have empty data,
692         # which will cause a FDT_ERR_BADMAGIC error
693         if self.mkimage in self.missing_bintools:
694             return
695
696         fdt = Fdt.FromData(self.GetData())
697         fdt.Scan()
698
699         for image_name, section in self._entries.items():
700             path = f"/images/{image_name}"
701             node = fdt.GetNode(path)
702
703             data_prop = node.props.get("data")
704             data_pos = fdt_util.GetInt(node, "data-position")
705             data_offset = fdt_util.GetInt(node, "data-offset")
706             data_size = fdt_util.GetInt(node, "data-size")
707
708             # Contents are inside the FIT
709             if data_prop is not None:
710                 # GetOffset() returns offset of a fdt_property struct,
711                 # which has 3 fdt32_t members before the actual data.
712                 offset = data_prop.GetOffset() + 12
713                 size = len(data_prop.bytes)
714
715             # External offset from the base of the FIT
716             elif data_pos is not None:
717                 offset = data_pos
718                 size = data_size
719
720             # External offset from the end of the FIT, not used in binman
721             elif data_offset is not None: # pragma: no cover
722                 offset = fdt.GetFdtObj().totalsize() + data_offset
723                 size = data_size
724
725             # This should never happen
726             else: # pragma: no cover
727                 self.Raise(f'{path}: missing data properties')
728
729             section.SetOffsetSize(offset, size)
730             section.SetImagePos(self.image_pos)
731
732     def AddBintools(self, btools):
733         super().AddBintools(btools)
734         self.mkimage = self.AddBintool(btools, 'mkimage')
735
736     def CheckMissing(self, missing_list):
737         # We must use our private entry list for this since generator notes
738         # which are removed from self._entries will otherwise not show up as
739         # missing
740         for entry in self._priv_entries.values():
741             entry.CheckMissing(missing_list)
This page took 0.06915 seconds and 4 git commands to generate.