Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unified build script: dry run and unit test #8295

Merged
merged 29 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
380bdf9
Imported a general chip builder script, currently covering a few plat…
andy31415 Jul 8, 2021
38c0512
Move build requirements into global script/requirements.txt so that t…
andy31415 Jul 8, 2021
a7d619f
Update script to assume and require bootstrapping
andy31415 Jul 8, 2021
e0f7e91
Code review comments
andy31415 Jul 9, 2021
ba41920
Support building the lock app for ESP32
andy31415 Jul 9, 2021
7161ca7
Remove obsolete todo
andy31415 Jul 9, 2021
249144f
Fix the duplicated accept for efr32 lock app
andy31415 Jul 9, 2021
3a12596
Add a dry run option for the build runner, showing what commands woul…
andy31415 Jul 9, 2021
15690fd
Merge branch 'master' into unified_build_script
andy31415 Jul 12, 2021
0f5e71b
Merge branch 'unified_build_script' into 02_dry_run_build_script
andy31415 Jul 12, 2021
3ec0c97
Add support for a "test" to validate that the build generator execute…
andy31415 Jul 12, 2021
9f4e2ad
Update the command comparison: output directory of the build script h…
andy31415 Jul 12, 2021
e80f9f6
Fix some naming and use `get_target_outputs`
andy31415 Jul 12, 2021
c8c8e48
Address some code review comments
andy31415 Jul 13, 2021
24b0749
Rename chipbuild to build_examples
andy31415 Jul 13, 2021
04760bf
Merge branch 'unified_build_script' into 02_dry_run_build_script
andy31415 Jul 13, 2021
f864715
Fixup naming for the unit tests after build script renaming
andy31415 Jul 13, 2021
d6e154b
Fix names
andy31415 Jul 13, 2021
5a66c7a
Restyle
andy31415 Jul 13, 2021
7759751
Merge branch 'master' into unified_build_script
andy31415 Jul 14, 2021
c385777
Merge branch 'unified_build_script' into 02_dry_run_build_script
andy31415 Jul 14, 2021
8b3bb56
Use difflib instead of diff binary for checking changes
andy31415 Jul 14, 2021
0c0133d
Fix diffs (generator vs lines) and logging
andy31415 Jul 14, 2021
0c105ce
Start converting python logic from build into pw_python_module
andy31415 Jul 14, 2021
d348340
Tests pass
andy31415 Jul 14, 2021
fc5edcb
Restyle
andy31415 Jul 14, 2021
5ac796c
Code review comments
andy31415 Jul 15, 2021
5533864
Add comment for all_platform_commands.txt
andy31415 Jul 15, 2021
cc7bc88
Move expected txt data into inputs
andy31415 Jul 15, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") {

group("check") {
if (chip_link_tests) {
deps = [ "//src:tests_run" ]
deps = [
"//scripts/build:build_examples.tests",
"//src:tests_run",
]
}
}

Expand Down
44 changes: 44 additions & 0 deletions scripts/build/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 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("//build_overrides/build.gni")
import("//build_overrides/chip.gni")

import("//build_overrides/pigweed.gni")
import("$dir_pw_build/python.gni")

pw_python_package("build_examples") {
setup = [ "setup.py" ]
inputs = [
# Dependency for the unit test
"expected_all_platform_commands.txt",
]

sources = [
"build/__init__.py",
"build/factory.py",
"build/targets.py",
"build_examples.py",
"builders/__init__.py",
"builders/builder.py",
"builders/efr32.py",
"builders/esp32.py",
"builders/linux.py",
"builders/qpg.py",
"runner/__init__.py",
"runner/printonly.py",
"runner/shell.py",
]
tests = [ "test.py" ]
}
16 changes: 7 additions & 9 deletions scripts/build/build/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ class Context:
to generate make/ninja instructions and to compile.
"""

def __init__(self, repository_path:str, output_prefix:str):
def __init__(self, runner, repository_path:str, output_prefix:str):
self.builders = []
self.repository_path = repository_path
self.output_prefix = output_prefix
self.builder_factory = BuilderFactory(runner, repository_path,
output_prefix)
self.completed_steps = set()

def SetupBuilders(self, platforms: Sequence[Platform],
Expand Down Expand Up @@ -85,12 +85,10 @@ def SetupBuilders(self, platforms: Sequence[Platform],
boards_with_builders = set()
applications_with_builders = set()

factory = BuilderFactory(self.repository_path, self.output_prefix)

for platform in platforms:
for board in boards:
for application in applications:
builder = factory.Create(platform, board, application)
for platform in sorted(platforms):
for board in sorted(boards):
for application in sorted(applications):
builder = self.builder_factory.Create(platform, board, application)
if not builder:
logging.debug('Builder not supported for tuple %s/%s/%s', platform,
board, application)
Expand Down
15 changes: 10 additions & 5 deletions scripts/build/build/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def AcceptApplicationForBoard(self, __app_key: Application, __board: Board,
def AcceptBoard(self, __board_key: Board, **kargs):
self.board_arguments[__board_key] = kargs

def Create(self, __board_key: Board, __app_key: Application, repo_path: str,
**kargs):
def Create(self, runner, __board_key: Board, __app_key: Application,
repo_path: str, **kargs):
"""Creates a new builder for the given board/app. """
if not __board_key in self.board_arguments:
return None
Expand All @@ -59,7 +59,7 @@ def Create(self, __board_key: Board, __app_key: Application, repo_path: str,
kargs.update(self.board_arguments[__board_key])
kargs.update(extra_app_args)

return self.builder_class(repo_path, **kargs)
return self.builder_class(repo_path, runner=runner, **kargs)


# Builds a list of acceptable application/board combination for every platform
Expand Down Expand Up @@ -96,7 +96,8 @@ def Create(self, __board_key: Board, __app_key: Application, repo_path: str,
class BuilderFactory:
"""Creates application builders."""

def __init__(self, repository_path: str, output_prefix: str):
def __init__(self, runner, repository_path: str, output_prefix: str):
self.runner = runner
self.repository_path = repository_path
self.output_prefix = output_prefix

Expand All @@ -108,7 +109,11 @@ def Create(self, platform: Platform, board: Board, app: Application):

output_directory = os.path.join(self.output_prefix, identifier)
builder = _MATCHERS[platform].Create(
board, app, self.repository_path, output_dir=output_directory)
self.runner,
board,
app,
self.repository_path,
output_dir=output_directory)

if builder:
builder.identifier = identifier
Expand Down
8 changes: 4 additions & 4 deletions scripts/build/build/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import os
import shutil

from enum import Enum, auto
from enum import IntEnum, auto


class Platform(Enum):
class Platform(IntEnum):
"""Represents a supported build platform for compilation."""
LINUX = auto()
QPG = auto()
Expand All @@ -24,7 +24,7 @@ def FromArgName(name):
raise KeyError()


class Board(Enum):
class Board(IntEnum):
"""Represents Specific boards within a platform."""
# Host builds
NATIVE = auto()
Expand All @@ -51,7 +51,7 @@ def FromArgName(name):
raise KeyError()


class Application(Enum):
class Application(IntEnum):
"""Example applications that can be built."""
ALL_CLUSTERS = auto()
LIGHT = auto()
Expand Down
26 changes: 24 additions & 2 deletions scripts/build/build_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
sys.path.append(os.path.abspath(os.path.dirname(__file__)))

import build
from runner import PrintOnlyRunner, ShellRunner

# Supported log levels, mapping string values required for argument
# parsing into logging constants
Expand All @@ -22,6 +23,10 @@

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):
Expand Down Expand Up @@ -73,8 +78,19 @@ def ValidateRepoPath(context, parameter, value):
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.pass_context
def main(context, log_level, platform, board, app, repo, out_prefix, clean):
def main(context, log_level, platform, board, app, repo, out_prefix, clean,
dry_run, dry_run_output):
# Ensures somewhat pretty logging of what is going on
coloredlogs.install(
level=__LOG_LEVELS__[log_level],
Expand All @@ -92,7 +108,13 @@ def main(context, log_level, platform, board, app, repo, out_prefix, clean):
if 'all' in platform:
platform = build.PLATFORMS

context.obj = build.Context(repository_path=repo, output_prefix=out_prefix)
if dry_run:
runner = PrintOnlyRunner(dry_run_output)
else:
runner = ShellRunner()

context.obj = build.Context(
repository_path=repo, output_prefix=out_prefix, runner=runner)
context.obj.SetupBuilders(
platforms=[build.Platform.FromArgName(name) for name in platform],
boards=[build.Board.FromArgName(name) for name in board],
Expand Down
11 changes: 5 additions & 6 deletions scripts/build/builders/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import shutil
from abc import ABC, abstractmethod

from shellrunner import ShellRunner


class Builder(ABC):
"""Generic builder base class for CHIP.
Expand All @@ -16,10 +14,11 @@ class Builder(ABC):

"""

def __init__(self, root, output_dir='out'):
def __init__(self, root, runner, output_dir='out'):
self.root = os.path.abspath(root)
self._runner = ShellRunner()
self._runner = runner
self.output_dir = output_dir
self.identifier = None

@abstractmethod
def generate(self):
Expand All @@ -40,8 +39,8 @@ def outputs(self):
"""
raise NotImplementedError()

def _Execute(self, cmdarray, **args):
self._runner.Run(cmdarray, **args)
def _Execute(self, cmdarray, cwd=None, title=None):
self._runner.Run(cmdarray, cwd=cwd, title=title)

def CopyArtifacts(self, target_dir: str):
for target_name, source_name in self.outputs().items():
Expand Down
10 changes: 7 additions & 3 deletions scripts/build/builders/efr32.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ class Efr32Builder(Builder):

def __init__(self,
root,
runner,
output_dir: str,
app: Efr32App = Efr32App.LIGHT,
board: Efr32Board = Efr32Board.BRD4161A):
super(Efr32Builder, self).__init__(root, output_dir)
super(Efr32Builder, self).__init__(root, runner, output_dir)

self.app = app
self.board = board
self.identifier = None

def generate(self):
if not os.path.exists(self.output_dir):
Expand All @@ -68,13 +70,15 @@ def generate(self):
'--root=%s' %
os.path.join(self.root, 'examples', self.app.ExampleName(), 'efr32'),
'--args=efr32_board="%s"' % self.board.GnArgName(), self.output_dir
])
],
title='Generate %s' % self.identifier)

def build(self):
logging.info('Compiling EFR32 at %s', self.output_dir)

self.generate()
self._Execute(['ninja', '-C', self.output_dir])
self._Execute(['ninja', '-C', self.output_dir],
title='Build %s' % self.identifier)

def outputs(self):
items = {
Expand Down
15 changes: 10 additions & 5 deletions scripts/build/builders/esp32.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,19 @@ class Esp32Builder(Builder):

def __init__(self,
root,
runner,
output_dir: str,
board: Esp32Board = Esp32Board.M5Stack,
app: Esp32App = Esp32App.ALL_CLUSTERS):
super(Esp32Builder, self).__init__(root, output_dir)
super(Esp32Builder, self).__init__(root, runner, output_dir)
self.board = board
self.app = app

def _IdfEnvExecute(self, cmd, **kargs):
def _IdfEnvExecute(self, cmd, cwd=None, title=None):
self._Execute(
['bash', '-c', 'source $IDF_PATH/export.sh; %s' % cmd], **kargs)
['bash', '-c', 'source $IDF_PATH/export.sh; %s' % cmd],
cwd=cwd,
title=title)

def generate(self):
if os.path.exists(os.path.join(self.output_dir, 'build.ninja')):
Expand All @@ -77,13 +80,15 @@ def generate(self):
cmd += ' -C examples/%s/esp32 -B %s reconfigure' % (self.app.ExampleName, shlex.quote(self.output_dir))

# This will do a 'cmake reconfigure' which will create ninja files without rebuilding
self._IdfEnvExecute(cmd, cwd=self.root)
self._IdfEnvExecute(
cmd, cwd=self.root, title='Generating ' + self.identifier)

def build(self):
logging.info('Compiling Esp32 at %s', self.output_dir)

self.generate()
self._IdfEnvExecute("ninja -C '%s'" % self.output_dir)
self._IdfEnvExecute(
"ninja -C '%s'" % self.output_dir, title='Building ' + self.identifier)

def outputs(self):
return {
Expand Down
10 changes: 6 additions & 4 deletions scripts/build/builders/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@

class LinuxBuilder(Builder):

def __init__(self, root, output_dir):
super(LinuxBuilder, self).__init__(root, output_dir)
def __init__(self, root, runner, output_dir):
super(LinuxBuilder, self).__init__(root, runner, output_dir)

def generate(self):
if not os.path.exists(self.output_dir):
self._Execute(['gn', 'gen', self.output_dir],
cwd=os.path.join(self.root,
'examples/all-clusters-app/linux/'))
'examples/all-clusters-app/linux/'),
title='Generating ' + self.identifier)

def build(self):
logging.info('Compiling Linux at %s', self.output_dir)

self.generate()
self._Execute(['ninja', '-C', self.output_dir])
self._Execute(['ninja', '-C', self.output_dir],
title='Building ' + self.identifier)

def outputs(self):
return {
Expand Down
10 changes: 6 additions & 4 deletions scripts/build/builders/qpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@

class QpgBuilder(Builder):

def __init__(self, root, output_dir):
super(QpgBuilder, self).__init__(root, output_dir)
def __init__(self, root, runner, output_dir):
super(QpgBuilder, self).__init__(root, runner, output_dir)

def generate(self):
if not os.path.exists(self.output_dir):
self._Execute(['gn', 'gen', self.output_dir],
cwd=os.path.join(self.root, 'examples/lock-app/qpg/'))
cwd=os.path.join(self.root, 'examples/lock-app/qpg/'),
title='Generating ' + self.identifier)

def build(self):
logging.info('Compiling QPG at %s', self.output_dir)

self.generate()
self._Execute(['ninja', '-C', self.output_dir])
self._Execute(['ninja', '-C', self.output_dir],
title='Building ' + self.identifier)

def outputs(self):
return {
Expand Down
Loading