blob: f9897a69a85589d01a51f954fb05ef140ff5c7e5 [file] [log] [blame]
#!/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 logging
import os
import sys
import click
import coloredlogs
import build
from glob_matcher import GlobMatcher
from runner import PrintOnlyRunner, ShellRunner
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
@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=['all'],
type=click.Choice(
['all'] + [t.name for t in build.ALL_TARGETS], case_sensitive=False),
multiple=True,
help='Build target(s). Note that "all" includes glob blacklisted targets'
)
@click.option(
'--target-glob',
default=None,
help='Glob matching for targets to include'
)
@click.option(
'--skip-target-glob',
default=None,
help='Glob matching for targets to explicitly exclude'
)
@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(
'--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.pass_context
def main(context, log_level, target, target_glob, skip_target_glob, repo,
out_prefix, clean, dry_run, dry_run_output, enable_flashbundle,
no_log_timestamps):
# 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)
if 'all' in target:
# NOTE: The "all" target includes things that are glob blacklisted
# (so that 'targets' works and displays all)
targets = build.ALL_TARGETS
else:
requested_targets = set([t.lower for t in target])
targets = [
target for target in build.ALL_TARGETS
if target.name.lower in requested_targets
]
actual_targes = set([t.name.lower for t in targets])
if requested_targets != actual_targes:
logging.error('Targets not found: %s',
CommaSeparate(actual_targes))
if target_glob:
matcher = GlobMatcher(target_glob)
targets = [t for t in targets if matcher.matches(
t.name) and not t.IsGlobBlacklisted]
if skip_target_glob:
matcher = GlobMatcher(skip_target_glob)
targets = [t for t in targets if not matcher.matches(t.name)]
# force consistent sorting
targets.sort(key=lambda t: t.name)
logging.info('Building targets: %s',
CommaSeparate([t.name for t in targets]))
context.obj = build.Context(
repository_path=repo, output_prefix=out_prefix, runner=runner)
context.obj.SetupBuilders(
targets=targets, enable_flashbundle=enable_flashbundle)
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=('List the targets that would be generated/built given '
'the input arguments'))
@click.pass_context
def cmd_targets(context):
for builder in context.obj.builders:
if builder.target.IsGlobBlacklisted:
print("%s (NOGLOB: %s)" %
(builder.target.name, builder.target.GlobBlacklistReason))
else:
print(builder.target.name)
@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()