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