blob: f959d382c8750411993a64914590ce392ea2327a [file] [log] [blame]
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Firmware builder controller.
Handle all firmware builder related functionality. Currently no service module
exists: all of the work is done here.
"""
import os
import tempfile
from chromite.third_party.google.protobuf import json_format
from chromite.api import controller
from chromite.api import faux
from chromite.api import validate
from chromite.api.gen.chromite.api import firmware_pb2
from chromite.api.gen.chromiumos import common_pb2
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import osutils
def get_fw_loc(fw_loc):
"""Get firmware_builder.py location.
Args:
fw_loc (enum): FwLocation enum.
Returns:
(str): path to firmware_builder.py for valid fw_loc.
"""
return {
common_pb2.PLATFORM_EC: 'src/platform/ec/',
common_pb2.PLATFORM_ZEPHYR: 'src/platform/ec/zephyr/',
common_pb2.PLATFORM_TI50: 'src/platform/ti50/common/',
common_pb2.PLATFORM_CR50: 'src/platform/cr50/'
}.get(fw_loc, '')
def _call_entry(fw_loc, metric_proto, subcmd, *args, **kwargs):
"""Calls into firmware_builder.py with the specified subcmd."""
fw_path = get_fw_loc(fw_loc)
if not fw_path:
cros_build_lib.Die(f'Unknown firmware location {fw_loc}.')
entry_point = os.path.join(constants.SOURCE_ROOT, fw_path,
'firmware_builder.py')
with tempfile.NamedTemporaryFile() as tmpfile:
cmd = [entry_point, '--metrics', tmpfile.name] + list(args)
for key, value in kwargs.items():
cmd += [f'--{key.replace("_", "-")}', value]
cmd += [subcmd]
result = cros_build_lib.run(cmd, check=False)
with open(tmpfile.name, 'r') as f:
response = f.read()
if metric_proto:
# Parse the entire metric file as our metric proto (as a passthru).
# TODO(b/177907747): BundleFirmwareArtifacts doesn't use this (yet?), but
# firmware_builder.py requires it.
json_format.Parse(response, metric_proto)
if result.returncode == 0:
return controller.RETURN_CODE_SUCCESS
else:
return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
def _BuildAllTotFirmwareResponse(_input_proto, output_proto, _config):
"""Add a fw region metric to a successful response."""
metric = output_proto.success.value.add()
metric.target_name = 'foo'
metric.platform_name = 'bar'
fw_section = metric.fw_section.add()
fw_section.region = 'EC_RO'
fw_section.used = 100
fw_section.total = 150
@faux.success(_BuildAllTotFirmwareResponse)
@faux.empty_completed_unsuccessfully_error
@validate.require('firmware_location')
@validate.validation_complete
def BuildAllTotFirmware(input_proto, output_proto, _config):
"""Build all of the firmware targets at the specified location."""
args = ['--code-coverage'] if input_proto.code_coverage else []
return _call_entry(input_proto.firmware_location, output_proto.metrics,
'build', *args)
def _TestAllTotFirmwareResponse(_input_proto, output_proto, _config):
"""Add a fw region metric to a successful response."""
metric = output_proto.success.value.add()
metric.name = 'foo-test'
@faux.success(_TestAllTotFirmwareResponse)
@faux.empty_completed_unsuccessfully_error
@validate.require('firmware_location')
@validate.validation_complete
def TestAllTotFirmware(input_proto, output_proto, _config):
"""Runs all of the firmware tests at the specified location."""
args = ['--code-coverage'] if input_proto.code_coverage else []
return _call_entry(input_proto.firmware_location, output_proto.metrics,
'test', *args)
def _BuildAllFirmwareResponse(_input_proto, output_proto, _config):
"""Add a fw region metric to a successful response."""
metric = output_proto.metrics.value.add()
metric.target_name = 'foo'
metric.platform_name = 'bar'
fw_section = metric.fw_section.add()
fw_section.region = 'EC_RO'
fw_section.used = 100
fw_section.total = 150
@faux.success(_BuildAllFirmwareResponse)
@faux.empty_completed_unsuccessfully_error
@validate.require('firmware_location')
@validate.validation_complete
def BuildAllFirmware(input_proto, output_proto, _config):
"""Build all of the firmware targets at the specified location."""
args = ['--code-coverage'] if input_proto.code_coverage else []
return _call_entry(input_proto.firmware_location, output_proto.metrics,
'build', *args)
def _TestAllFirmwareResponse(_input_proto, output_proto, _config):
"""Add a fw region metric to a successful response."""
metric = output_proto.success.value.add()
metric.name = 'foo-test'
@faux.success(_TestAllFirmwareResponse)
@faux.empty_completed_unsuccessfully_error
@validate.require('firmware_location')
@validate.validation_complete
def TestAllFirmware(input_proto, output_proto, _config):
"""Runs all of the firmware tests at the specified location."""
args = ['--code-coverage'] if input_proto.code_coverage else []
return _call_entry(input_proto.firmware_location, output_proto.metrics,
'test', *args)
def _BundleFirmwareArtifactsResponse(_input_proto, output_proto, _config):
"""Add a fw region metric to a successful response."""
metric = output_proto.success.value.add()
metric.name = 'foo-test'
@faux.success(_BundleFirmwareArtifactsResponse)
@faux.empty_completed_unsuccessfully_error
@validate.validation_complete
def BundleFirmwareArtifacts(input_proto, output_proto, _config):
"""Runs all of the firmware tests at the specified location."""
if len(input_proto.artifacts.output_artifacts) > 1:
raise ValueError('Must have exactly one output_artifact entry')
with osutils.TempDir(delete=False) as tmpdir:
info = input_proto.artifacts.output_artifacts[0]
metadata_path = os.path.join(tmpdir, 'firmware_metadata.jsonpb')
args = []
if input_proto.artifacts.FIRMWARE_LCOV in info.artifact_types:
args += ['--code-coverage']
resp = _call_entry(
info.location,
None,
'bundle',
*args,
output_dir=tmpdir,
metadata=metadata_path)
file_paths = []
if os.path.exists(metadata_path):
with open(metadata_path, 'r') as f:
metadata = json_format.Parse(f.read(),
firmware_pb2.FirmwareArtifactInfo())
else:
metadata = firmware_pb2.FirmwareArtifactInfo()
if input_proto.artifacts.FIRMWARE_TARBALL_INFO in info.artifact_types:
output_proto.artifacts.artifacts.add(
artifact_type=input_proto.artifacts.FIRMWARE_TARBALL_INFO,
location=info.location, paths=[
common_pb2.Path(
path=metadata_path, location=common_pb2.Path.INSIDE)
])
full_path = lambda x: common_pb2.Path(
path=os.path.join(tmpdir, x.file_name),
location=common_pb2.Path.INSIDE)
for typ, name in (
(input_proto.artifacts.FIRMWARE_TARBALL, 'tarball_info'),
(input_proto.artifacts.FIRMWARE_LCOV, 'lcov_info')):
file_paths = [
full_path(x) for x in metadata.objects
if x.WhichOneof('firmware_object_info') == name
]
if (file_paths and typ in info.artifact_types):
output_proto.artifacts.artifacts.add(
artifact_type=typ, paths=file_paths, location=info.location)
return resp