# Copyright 2024, The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.
"""Build script for the CI `test_suites` target."""
import argparse from dataclasses import dataclass from collections import defaultdict import json import logging import os import pathlib import re import subprocess import sys from typing import Callable from build_context import BuildContext import optimized_targets import metrics_agent import test_discovery_agent
REQUIRED_ENV_VARS = frozenset(['TARGET_PRODUCT', 'TARGET_RELEASE', 'TOP', 'DIST_DIR'])
SOONG_UI_EXE_REL_PATH = 'build/soong/soong_ui.bash'
LOG_PATH = 'logs/build_test_suites.log' # Currently, this prevents the removal of those tags when they exist. In the future we likely # want the script to supply 'dist directly
REQUIRED_BUILD_TARGETS = frozenset(['dist', 'droid', 'checkbuild'])
class BuildPlanner: """Class in charge of determining how to optimize build targets.
Given the build context and targets to build it will determine a final list of
targets to build along with getting a set of packaging functions to package up
any output zip files needed by the build. """
ifnot self.build_context.test_infos:
logging.warning('Build context has no test infos, skipping optimizations.') for target in self.args.extra_targets:
get_metrics_agent().report_unoptimized_target(target, 'BUILD_CONTEXT has no test infos.') return BuildPlan(set(self.args.extra_targets), set())
build_targets = set()
packaging_commands_getters = [] # In order to roll optimizations out differently between test suites and # device builds, we have separate flags.
enable_discovery = (('test_suites_zip_test_discovery' in self.build_context.enabled_build_features andnot self.args.device_build
) or ( 'device_zip_test_discovery' in self.build_context.enabled_build_features and self.args.device_build
)) andnot self.args.test_discovery_info_mode
logging.info(f'Discovery mode is enabled= {enable_discovery}')
preliminary_build_targets = self._collect_preliminary_build_targets(enable_discovery)
for target in preliminary_build_targets:
target_optimizer_getter = self.target_optimizations.get(target, None) ifnot target_optimizer_getter:
build_targets.add(target) continue
def _collect_preliminary_build_targets(self, enable_discovery: bool):
build_targets = set() try:
test_discovery_zip_regexes = self._get_test_discovery_zip_regexes()
logging.info(f'Discovered test discovery regexes: {test_discovery_zip_regexes}') except test_discovery_agent.TestDiscoveryError as e:
optimization_rationale = e.message
logging.warning(f'Unable to perform test discovery: {optimization_rationale}')
for target in self.args.extra_targets:
get_metrics_agent().report_unoptimized_target(target, optimization_rationale) return self._legacy_collect_preliminary_build_targets()
for target in self.args.extra_targets: if target in REQUIRED_BUILD_TARGETS:
build_targets.add(target)
get_metrics_agent().report_unoptimized_target(target, 'Required build target.') continue # If nothing is discovered without error, that means nothing is needed. ifnot test_discovery_zip_regexes:
get_metrics_agent().report_optimized_target(target) continue
regex = r'\b(%s.*)\b' % re.escape(target) for opt in test_discovery_zip_regexes: try: if re.search(regex, opt):
get_metrics_agent().report_unoptimized_target(target, 'Test artifact used.')
build_targets.add(target) # proceed to next target evaluation break
get_metrics_agent().report_optimized_target(target) except Exception as e: # In case of exception report as unoptimized
build_targets.add(target)
get_metrics_agent().report_unoptimized_target(target, f'Error in parsing test discovery output for {target}: {repr(e)}')
logging.error(f'unable to parse test discovery output: {repr(e)}') break # If discovery is not enabled, return the original list ifnot enable_discovery: return self._legacy_collect_preliminary_build_targets()
return build_targets
def _legacy_collect_preliminary_build_targets(self):
build_targets = set() for target in self.args.extra_targets: if self._unused_target_exclusion_enabled(
target
) andnot self.build_context.build_target_used(target): continue
argparser.add_argument( 'extra_targets', nargs='*', help='Extra test suites to build.'
)
argparser.add_argument( '--device-build',
action='store_true',
help='Flag to indicate running a device build.',
)
argparser.add_argument( '--test_discovery_info_mode',
action='store_true',
help='Flag to enable running test discovery in info only mode.',
)
return argparser.parse_args(argv)
def check_required_env(): """Check for required env vars.
Raises:
RuntimeError: If any required env vars are not found. """
missing_env_vars = sorted(v for v in REQUIRED_ENV_VARS if v notin os.environ)
ifnot missing_env_vars: return
t = ','.join(missing_env_vars) raise Error(f'Missing required environment variables: {t}')
def load_build_context():
build_context_path = pathlib.Path(os.environ.get('BUILD_CONTEXT', '')) if build_context_path.is_file(): try: with open(build_context_path, 'r') as f: return json.load(f) except json.decoder.JSONDecodeError as e: raise Error(f'Failed to load JSON file: {build_context_path}')
def execute_build_plan(build_plan: BuildPlan):
build_command = []
build_command.append(get_top().joinpath(SOONG_UI_EXE_REL_PATH))
build_command.append('--make-mode')
build_command.extend(build_plan.build_targets)
logging.info(f'Running build command: {build_command}') try:
run_command(build_command) except subprocess.CalledProcessError as e: raise BuildFailureError(e.returncode) from e
logging.info('executing packaging commands')
get_metrics_agent().packaging_start() try: for packaging_commands_getter in build_plan.packaging_commands_getters: for packaging_command in packaging_commands_getter():
run_command(packaging_command) except subprocess.CalledProcessError as e: raise BuildFailureError(e.returncode) from e finally:
get_metrics_agent().packaging_end()
logging.info('done with packaging commands')
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.