#!/usr/bin/env -S python3 -B # Copyright (c) 2021 Project CHIP Authors # # 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. import json import logging import os import sys import click import coloredlogs from builders.builder import BuilderOptions from runner import PrintOnlyRunner, ShellRunner import build sys.path.append(os.path.abspath(os.path.dirname(__file__))) # Supported log levels, mapping string values required for argument # parsing into logging constants __LOG_LEVELS__ = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warn': logging.WARN, 'fatal': logging.FATAL, } def CommaSeparate(items) -> str: return ', '.join([x for x in items]) def ValidateRepoPath(context, parameter, value): """ Validates that the given path looks like a valid chip repository checkout. """ if value.startswith('/TEST/'): # Hackish command to allow for unit testing return value for name in ['BUILD.gn', '.gn', os.path.join('scripts', 'bootstrap.sh')]: expected_file = os.path.join(value, name) if not os.path.exists(expected_file): raise click.BadParameter( ("'%s' does not look like a valid repository path: " "%s not found.") % (value, expected_file)) return value def ValidateTargetNames(context, parameter, values): """ Validates that the given target name is valid. """ for value in values: if not any(target.StringIntoTargetParts(value.lower()) for target in build.targets.BUILD_TARGETS): raise click.BadParameter( "'%s' is not a valid target name." % value) return values @click.group(chain=True) @click.option( '--log-level', default='INFO', type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False), help='Determines the verbosity of script output.') @click.option( '--target', default=[], multiple=True, callback=ValidateTargetNames, help='Build target(s)' ) @click.option( '--enable-flashbundle', default=False, is_flag=True, help='Also generate the flashbundles for the app.' ) @click.option( '--repo', default='.', callback=ValidateRepoPath, help='Path to the root of the CHIP SDK repository checkout.') @click.option( '--out-prefix', default='./out', type=click.Path(file_okay=False, resolve_path=True), help='Prefix for the generated file output.') @click.option( '--pregen-dir', default=None, type=click.Path(file_okay=False, resolve_path=True), help='Directory where generated files have been pre-generated.') @click.option( '--clean', default=False, is_flag=True, help='Clean output directory before running the command') @click.option( '--dry-run', default=False, is_flag=True, help='Only print out shell commands that would be executed') @click.option( '--dry-run-output', default="-", type=click.File("wt"), help='Where to write the dry run output') @click.option( '--no-log-timestamps', default=False, is_flag=True, help='Skip timestaps in log output') @click.option( '--pw-command-launcher', help=( 'Set pigweed command launcher. E.g.: "--pw-command-launcher=ccache" ' 'for using ccache when building examples.')) @click.pass_context def main(context, log_level, target, repo, out_prefix, pregen_dir, clean, dry_run, dry_run_output, enable_flashbundle, no_log_timestamps, pw_command_launcher): # Ensures somewhat pretty logging of what is going on log_fmt = '%(asctime)s %(levelname)-7s %(message)s' if no_log_timestamps: log_fmt = '%(levelname)-7s %(message)s' coloredlogs.install(level=__LOG_LEVELS__[log_level], fmt=log_fmt) if 'PW_PROJECT_ROOT' not in os.environ: raise click.UsageError(""" PW_PROJECT_ROOT not set in the current environment. Please make sure you `source scripts/bootstrap.sh` or `source scripts/activate.sh` before running this script. """.strip()) if dry_run: runner = PrintOnlyRunner(dry_run_output, root=repo) else: runner = ShellRunner(root=repo) requested_targets = set([t.lower() for t in target]) logging.info('Building targets: %s', CommaSeparate(requested_targets)) context.obj = build.Context( repository_path=repo, output_prefix=out_prefix, runner=runner) context.obj.SetupBuilders(targets=requested_targets, options=BuilderOptions( enable_flashbundle=enable_flashbundle, pw_command_launcher=pw_command_launcher, pregen_dir=pregen_dir, )) if clean: context.obj.CleanOutputDirectories() @main.command( 'gen', help='Generate ninja/makefiles (but does not run the compilation)') @click.pass_context def cmd_generate(context): context.obj.Generate() @main.command( 'targets', help=('Lists the targets that can be used with the build and gen commands')) @click.option( '--format', default='summary', type=click.Choice(['summary', 'expanded', 'json'], case_sensitive=False), help=""" summary - list of shorthand strings summarzing the available targets; expanded - list all possible targets rather than the shorthand string; json - a JSON representation of the available targets """) @click.pass_context def cmd_targets(context, format): if format == 'expanded': for target in build.targets.BUILD_TARGETS: build.target.report_rejected_parts = False for s in target.AllVariants(): print(s) elif format == 'json': print(json.dumps([target.ToDict() for target in build.targets.BUILD_TARGETS], indent=4)) else: for target in build.targets.BUILD_TARGETS: print(target.HumanString()) @main.command('build', help='generate and run ninja/make as needed to compile') @click.option( '--copy-artifacts-to', default=None, type=click.Path(file_okay=False, resolve_path=True), help='Prefix for the generated file output.') @click.option( '--create-archives', default=None, type=click.Path(file_okay=False, resolve_path=True), help='Prefix of compressed archives of the generated files.') @click.pass_context def cmd_build(context, copy_artifacts_to, create_archives): context.obj.Build() if copy_artifacts_to: context.obj.CopyArtifactsTo(copy_artifacts_to) if create_archives: context.obj.CreateArtifactArchives(create_archives) if __name__ == '__main__': main(auto_envvar_prefix='CHIP')