Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  build_test_suites.py

  Sprache: Python
 

# 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 Error(Exception):

  def __init__(self, message):
    super().__init__(message)


class BuildFailureError(Error):

  def __init__(self, return_code):
    super().__init__(f'Build command failed with return code: f{return_code}')
    self.return_code = return_code


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.
  """

  def __init__(
      self,
      build_context: BuildContext,
      args: argparse.Namespace,
      target_optimizations: dict[str, optimized_targets.OptimizedBuildTarget],
  ):
    self.build_context = build_context
    self.args = args
    self.target_optimizations = target_optimizations
    self.target_to_test_infos = defaultdict(list)

  def create_build_plan(self):

    if 'optimized_build' not in self.build_context.enabled_build_features:
      return BuildPlan(set(self.args.extra_targets), set())

    if not 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
        and not self.args.device_build
    ) or (
        'device_zip_test_discovery'
        in self.build_context.enabled_build_features
        and self.args.device_build
    )) and not 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)
      if not target_optimizer_getter:
        build_targets.add(target)
        continue

      target_optimizer = target_optimizer_getter(
          target, self.build_context, self.args, self.target_to_test_infos[target]
      )
      build_targets.update(target_optimizer.get_build_targets())
      packaging_commands_getters.append(
          target_optimizer.get_package_outputs_commands
      )

    return BuildPlan(build_targets, packaging_commands_getters)

  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.
      if not 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
    if not 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
      ) and not self.build_context.build_target_used(target):
        continue

      build_targets.add(target)
    return build_targets

  def _unused_target_exclusion_enabled(self, target: str) -> bool:
    return (
        f'{target}_unused_exclusion'
        in self.build_context.enabled_build_features
    )

  def _get_test_discovery_zip_regexes(self) -> set[str]:
    build_target_regexes = set()
    for test_info in self.build_context.test_infos:
      tf_command = self._build_tf_command(test_info)
      discovery_agent = test_discovery_agent.TestDiscoveryAgent(tradefed_args=tf_command)
      for regex in discovery_agent.discover_test_zip_regexes():
        for target in self.args.extra_targets:
          target_regex = r'\b(%s.*)\b' % re.escape(target)
          if re.search(target_regex, regex):
            self.target_to_test_infos[target].append(test_info)
        build_target_regexes.add(regex)
    return build_target_regexes


  def _build_tf_command(self, test_info) -> list[str]:
    command = [test_info.command]
    for extra_option in test_info.extra_options:
      if not extra_option.get('key'):
        continue
      arg_key = '--' + extra_option.get('key')
      if arg_key == '--build-id':
        command.append(arg_key)
        command.append(os.environ.get('BUILD_NUMBER'))
        continue
      if extra_option.get('values'):
        for value in extra_option.get('values'):
          command.append(arg_key)
          command.append(value)
      else:
        command.append(arg_key)

    return command

@dataclass(frozen=True)
class BuildPlan:
  build_targets: set[str]
  packaging_commands_getters: list[Callable[[], list[list[str]]]]


def build_test_suites(argv: list[str]) -> int:
  """Builds all test suites passed in, optimizing based on the build_context content.

  Args:
    argv: The command line arguments passed in.

  Returns:
    The exit code of the build.
  """
  get_metrics_agent().analysis_start()
  try:
    args = parse_args(argv)
    check_required_env()
    build_context = BuildContext(load_build_context())
    build_planner = BuildPlanner(
        build_context, args, optimized_targets.OPTIMIZED_BUILD_TARGETS
    )
    build_plan = build_planner.create_build_plan()
  except:
    raise
  finally:
    get_metrics_agent().analysis_end()

  try:
    execute_build_plan(build_plan)
  except BuildFailureError as e:
    logging.error('Build command failed! Check build_log for details.')
    return e.return_code
  finally:
    get_metrics_agent().end_reporting()

  return 0


def parse_args(argv: list[str]) -> argparse.Namespace:
  argparser = argparse.ArgumentParser()

  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 not in os.environ)

  if not 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}')

  logging.info('No BUILD_CONTEXT found, skipping optimizations.')
  return empty_build_context()


def empty_build_context():
  return {'enabledBuildFeatures': []}


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')


def get_top() -> pathlib.Path:
  return pathlib.Path(os.environ['TOP'])


def run_command(args: list[str], stdout=None):
  subprocess.run(args=args, check=True, stdout=stdout)


def get_metrics_agent():
  return metrics_agent.MetricsAgent.instance()


def main(argv):
  dist_dir = os.environ.get('DIST_DIR')
  if dist_dir:
    log_file = pathlib.Path(dist_dir) / LOG_PATH
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s %(levelname)s %(message)s',
        filename=log_file,
    )
  sys.exit(build_test_suites(argv))


if __name__ == '__main__':
  main(sys.argv[1:])

Messung V0.5 in Prozent
C=93 H=87 G=89

¤ Dauer der Verarbeitung: 0.20 Sekunden  (vorverarbeitet am  2026-06-28) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik