blob: b82a7499d0669ee06ebe0ea448d8e452a1df3654 [file] [log] [blame]
Greg Edelston9cd16f52020-11-12 10:50:28 -07001#!/usr/bin/env python3
2# Copyright 2020 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Greg Edelston64fdc2e2020-11-19 15:04:18 -07006"""Print one fully-calculated fw-testing-config to stdout.
Greg Edelston9cd16f52020-11-12 10:50:28 -07007
8This script takes one platform name (and optionally a model name) as inputs,
9calculates the full config for that platform, and prints the results as
10plaintext JSON.
11
12The full config is calculated based on the fw-testing-configs inheritance
13model. Each platform config has an optional "parent" attribute. For each
14config field, if the platform does not explicitly set a value for that field,
15then the value is instead inherited from the parent config. This inheritance
16can be recursive: the parent may have a parent, and so on. At the top of the
17inheritance tree (when parent is None), any remaining fields inherit their
18values from DEFAULTS. Also, if the platform has a "models" attribute matching
19the passed-in model name, then its field-value pairs override everything else.
20"""
21
22import argparse
23import collections
24import json
25import os
26import re
27import sys
28
29
30# Regexes matching fields which describe config metadata: that is, they do not
31# contain actual config information. When calculating the final config, fields
32# matching any of these regexes will be ignored.
33META_FIELD_REGEXES = (
34 re.compile(r'^models$'),
35 re.compile(r'\.DOC$')
36)
37
38
39class PlatformNotFoundError(AttributeError):
40 """Error class for when the requested platform name is not found."""
41 pass
42
43
Greg Edelstonf70963a2020-11-17 14:36:06 -070044class FieldNotFoundError(AttributeError):
45 """Error class for when the requested field name is not found."""
46
47
Greg Edelston9cd16f52020-11-12 10:50:28 -070048def parse_args(argv):
49 """Determine input dir and output file from command-line args.
50
51 Args:
52 argv: List of command-line args, excluding the invoked script.
53 Typically, this should be set to sys.argv[1:].
54
55 Returns:
56 An argparse.Namespace with the following attributes:
57 condense_output: A bool determining whether to remove pretty
58 whitespace from the script's final output.
59 consolidated: The filepath to CONSOLIDATED.json
Greg Edelstonf70963a2020-11-17 14:36:06 -070060 field: If specified, then only this field's value will be printed.
Greg Edelston9cd16f52020-11-12 10:50:28 -070061 platform: The name of the board whose config should be calculated
62 model: The name of the model for the board
63
64 Raises:
65 ValueError: If the passed-in platform name ends in '.json'
66 """
67 parser = argparse.ArgumentParser()
68 parser.add_argument('platform',
69 help='The platform name to calculate a config for. '
70 'Should not include ".json" suffix.')
71 parser.add_argument('-m', '--model', default=None,
72 help='The model name of the board. If not specified, '
73 'then no model overrides will be used.')
Greg Edelstonf70963a2020-11-17 14:36:06 -070074 parser.add_argument('-f', '--field', default=None,
75 help="If specified, only print this field's value.")
Greg Edelston9cd16f52020-11-12 10:50:28 -070076 parser.add_argument('-c', '--consolidated', default='CONSOLIDATED.json',
77 help='The filepath to CONSOLIDATED.json')
78 parser.add_argument('--condense-output', action='store_true',
79 help='Print the output without pretty whitespace.')
80 args = parser.parse_args(argv)
81 return args
82
83
84def calculate_field_value(consolidated_json, field, platform, model=None):
85 """Calculate a platform's ultimate value for a single field.
86
87 Args:
88 consolidated_json: The key-value contents of CONSOLIDATED.json.
89 field: The name of the JSON field to calculate.
90 platform: The name of the platform to calculate for. If the
91 platform does not define the field, then recursively check its
92 parent's config (or DEFAULTS).
93 model: The name of the model to check overrides for.
94
95 Raises:
96 PlatformNotFoundError: If platform is not in consolidated_json.
97 """
98 if platform not in consolidated_json:
99 raise PlatformNotFoundError(platform)
100
101 # Model overrides are most important.
102 # Not all models have config overrides, so it's OK for the model name not
103 # to be present in models_json.
104 models_json = consolidated_json[platform].get('models', {})
105 if field in models_json.get(model, {}):
106 return models_json[model][field]
107
108 # Then check if the platform explicitly defines the value.
109 if field in consolidated_json[platform]:
110 return consolidated_json[platform][field]
111
112 # Finally, inherit from the parent (or DEFAULTS).
113 # The DEFAULTS config contains every field name, so this will terminate the
114 # recursion.
115 parent = consolidated_json[platform].get('parent', 'DEFAULTS')
116 return calculate_field_value(consolidated_json, field, parent, model)
117
118
Greg Edelston64fdc2e2020-11-19 15:04:18 -0700119def load_consolidated_json(consolidated_fp):
120 """Return the contents of consolidated_fp as a Python object."""
121 if not os.path.isfile(consolidated_fp):
122 raise FileNotFoundError(consolidated_fp)
123 with open(consolidated_fp) as consolidated_file:
124 return json.load(consolidated_file)
125
126
127def calculate_config(platform, model, consolidated_json):
Greg Edelston9cd16f52020-11-12 10:50:28 -0700128 """Calculate a platform's ultimate config values for all fields.
129
130 Args:
131 platform: The name of the platform to calculate values for.
132 model: The name of the model to check for overrides.
133 """
Greg Edelston9cd16f52020-11-12 10:50:28 -0700134 final_json = collections.OrderedDict()
135 for field in consolidated_json['DEFAULTS']:
136 if any(regex.search(field) for regex in META_FIELD_REGEXES):
137 continue
138 value = calculate_field_value(consolidated_json, field, platform, model)
139 final_json[field] = value
140 return final_json
141
142
143def main(argv):
144 """
145 Parse command-line args and print a JSON config to stdout.
146
147 Args:
148 argv: List of command-line args, excluding the invoked script.
149 Typically, this should be set to sys.argv[1:].
150 """
151 args = parse_args(argv)
Greg Edelston64fdc2e2020-11-19 15:04:18 -0700152 consolidated_json = load_consolidated_json(args.consolidated)
153 config_json = calculate_config(args.platform, args.model,
154 consolidated_json)
Greg Edelstonf70963a2020-11-17 14:36:06 -0700155 if args.field is None:
156 # indent=None means no newlines/indents in stringified JSON.
157 # indent=4 means 4-space indents.
158 indent = None if args.condense_output else 4
159 output = json.dumps(config_json, indent=indent)
160 elif args.field not in config_json:
161 raise FieldNotFoundError(args.field)
Greg Edelston9cd16f52020-11-12 10:50:28 -0700162 else:
Greg Edelstonf70963a2020-11-17 14:36:06 -0700163 output = config_json[args.field]
164 print(output, end='' if args.condense_output else '\n')
Greg Edelston9cd16f52020-11-12 10:50:28 -0700165
166
167if __name__ == '__main__':
168 main(sys.argv[1:])