xref: /aosp_15_r20/external/gsc-utils/firmware_builder.py (revision 4f2df630800bdcf1d4f0decf95d8a1cb87344f5f)
1#!/usr/bin/env python3
2# Copyright 2023 The ChromiumOS Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Build, bundle, or test all of the EC boards.
7
8This is the entry point for the custom firmware builder workflow recipe.  It
9gets invoked by chromite/api/controller/firmware.py.
10"""
11
12import argparse
13import multiprocessing
14import os
15import subprocess
16import sys
17
18# pylint: disable=import-error
19from google.protobuf import json_format
20
21# TODO(crbug/1181505): Code outside of chromite should not be importing from
22# chromite.api.gen.  Import json_format after that so we get the matching one.
23from chromite.api.gen.chromite.api import firmware_pb2
24
25
26def build(opts):
27    """Builds all targets in extra/usb_updater
28
29    Note that when we are building unit tests for code coverage, we don't
30    need this step. It builds EC **firmware** targets, but unit tests with
31    code coverage are all host-based. So if the --code-coverage flag is set,
32    we don't need to build the firmware targets and we can return without
33    doing anything but creating the metrics file and giving an informational
34    message.
35    """
36    # Write empty metrics file as there is nothing to report but recipe needs
37    # the file to exist.
38    metrics = firmware_pb2.FwBuildMetricList()  # pylint: disable=no-member
39    with open(opts.metrics, "w", encoding="utf-8") as f:
40        f.write(json_format.MessageToJson(metrics))
41
42    cmd = [
43        "make",
44        "BOARD=cr50",
45        f"-j{opts.cpus}",
46        "-C",
47        "extra/usb_updater",
48    ]
49    print(f'# Running {" ".join(cmd)}.')
50    subprocess.run(cmd, cwd=os.path.dirname(__file__), check=True)
51
52
53def bundle(opts):
54    """Bundles all of the EC targets."""
55    # Provide an empty metadata file since the file is required, but we
56    # don't have any artifacts that needs to be uploadeddd
57    if opts.metadata:
58        metadata = (
59            firmware_pb2.FirmwareArtifactInfo()  # pylint: disable=no-member
60        )
61        with open(opts.metadata, "w", encoding="utf-8") as f:
62            f.write(json_format.MessageToJson(metadata))
63
64
65def test(opts):
66    """Tests all of the EC targets."""
67    del opts  # Unused.
68
69
70def main(args):
71    """Builds, bundles, or tests all of the EC targets.
72
73    Additionally, the tool reports build metrics.
74    """
75    opts = parse_args(args)
76
77    if not hasattr(opts, "func"):
78        print("Must select a valid sub command!")
79        return -1
80
81    # Run selected sub command function
82    try:
83        opts.func(opts)
84    except subprocess.CalledProcessError:
85        return 1
86    else:
87        return 0
88
89
90def parse_args(args):
91    """Parses command-line arguments."""
92    parser = argparse.ArgumentParser(description=__doc__)
93
94    parser.add_argument(
95        "--cpus",
96        default=multiprocessing.cpu_count(),
97        help="The number of cores to use.",
98    )
99
100    parser.add_argument(
101        "--metrics",
102        dest="metrics",
103        required=True,
104        help="File to write the json-encoded MetricsList proto message.",
105    )
106
107    parser.add_argument(
108        "--metadata",
109        required=False,
110        help="Full pathname for the file in which to write build artifact "
111        "metadata.",
112    )
113
114    parser.add_argument(
115        "--output-dir",
116        required=False,
117        help="Full pathanme for the directory in which to bundle build "
118        "artifacts.",
119    )
120
121    parser.add_argument(
122        "--code-coverage",
123        required=False,
124        action="store_true",
125        help="Build host-based unit tests for code coverage.",
126    )
127
128    parser.add_argument(
129        "--bcs-version",
130        dest="bcs_version",
131        default="",
132        required=False,
133        # TODO(b/180008931): make this required=True.
134        help="BCS version to include in metadata.",
135    )
136
137    # Would make this required=True, but not available until 3.7
138    sub_cmds = parser.add_subparsers()
139
140    build_cmd = sub_cmds.add_parser("build", help="Builds all firmware targets")
141    build_cmd.set_defaults(func=build)
142
143    build_cmd = sub_cmds.add_parser(
144        "bundle", help="Does nothing, kept for compatibility"
145    )
146    build_cmd.set_defaults(func=bundle)
147
148    test_cmd = sub_cmds.add_parser(
149        "test", help="Does nothing, kept for compatibility"
150    )
151    test_cmd.set_defaults(func=test)
152
153    return parser.parse_args(args)
154
155
156if __name__ == "__main__":
157    sys.exit(main(sys.argv[1:]))
158