• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Default pw build script for upstream Pigweed.
15
16Usage:
17
18   pw build --list
19   pw build --recipe default_gn
20   pw build --recipe default_* --watch
21   pw build --step gn_combined_build_check --step gn_python_*
22   pw build -C out/gn --watch
23"""
24
25import argparse
26import logging
27from pathlib import Path
28import shutil
29import sys
30
31import pw_cli.env
32import pw_presubmit.pigweed_presubmit
33from pw_presubmit.build import gn_args
34
35from pw_build.build_recipe import (
36    BuildCommand,
37    BuildRecipe,
38    should_gn_gen,
39    should_regenerate_cmake,
40)
41from pw_build.project_builder_presubmit_runner import (
42    get_parser,
43    main,
44)
45
46
47_LOG = logging.getLogger('pw_build')
48
49
50def gn_recipe() -> BuildRecipe:
51    """Return the default_gn recipe."""
52    default_gn_gen_command = [
53        'gn',
54        'gen',
55        # NOTE: Not an f-string. BuildRecipe will replace with the out dir.
56        '{build_dir}',
57        '--export-compile-commands',
58    ]
59
60    if shutil.which('ccache'):
61        default_gn_gen_command.append(gn_args(pw_command_launcher='ccache'))
62
63    return BuildRecipe(
64        build_dir=Path('out/gn'),
65        title='default_gn',
66        steps=[
67            BuildCommand(
68                run_if=should_gn_gen,
69                command=default_gn_gen_command,
70            ),
71            BuildCommand(
72                build_system_command='ninja',
73                targets=['default'],
74            ),
75        ],
76    )
77
78
79def bazel_recipe() -> BuildRecipe:
80    """Return the default_bazel recipe."""
81    default_bazel_targets = ['//...:all']
82
83    return BuildRecipe(
84        build_dir=Path('out/bazel'),
85        title='default_bazel',
86        steps=[
87            BuildCommand(
88                build_system_command='bazel',
89                build_system_extra_args=[
90                    'build',
91                    '--verbose_failures',
92                    '--worker_verbose',
93                ],
94                targets=default_bazel_targets,
95            ),
96            BuildCommand(
97                build_system_command='bazel',
98                build_system_extra_args=[
99                    'test',
100                    '--test_output=errors',
101                ],
102                targets=default_bazel_targets,
103            ),
104        ],
105    )
106
107
108def cmake_recipe(repo_root: Path, package_root: Path) -> BuildRecipe:
109    """Construct the default_cmake recipe."""
110    toolchain_path = (
111        repo_root / 'pw_toolchain' / 'host_clang' / 'toolchain.cmake'
112    )
113
114    cmake_generate_command = [
115        'cmake',
116        '--fresh',
117        '--debug-output',
118        '-DCMAKE_MESSAGE_LOG_LEVEL=WARNING',
119        '-S',
120        str(repo_root),
121        '-B',
122        # NOTE: Not an f-string. BuildRecipe will replace with the out dir.
123        '{build_dir}',
124        '-G',
125        'Ninja',
126        f'-DCMAKE_TOOLCHAIN_FILE={toolchain_path}',
127        '-DCMAKE_EXPORT_COMPILE_COMMANDS=1',
128        f'-Ddir_pw_third_party_nanopb={package_root / "nanopb"}',
129        '-Dpw_third_party_nanopb_ADD_SUBDIRECTORY=ON',
130        f'-Ddir_pw_third_party_emboss={package_root / "emboss"}',
131        f'-Ddir_pw_third_party_boringssl={package_root / "boringssl"}',
132    ]
133
134    if shutil.which('ccache'):
135        cmake_generate_command.append("-DCMAKE_C_COMPILER_LAUNCHER=ccache")
136        cmake_generate_command.append("-DCMAKE_CXX_COMPILER_LAUNCHER=ccache")
137
138    pw_package_install_steps = [
139        BuildCommand(
140            command=['pw', '--no-banner', 'package', 'install', package],
141        )
142        for package in ['emboss', 'nanopb', 'boringssl']
143    ]
144
145    return BuildRecipe(
146        build_dir=Path('out/cmake'),
147        title='default_cmake',
148        steps=pw_package_install_steps
149        + [
150            BuildCommand(
151                run_if=should_regenerate_cmake(cmake_generate_command),
152                command=cmake_generate_command,
153            ),
154            BuildCommand(
155                build_system_command='ninja',
156                targets=pw_presubmit.pigweed_presubmit.CMAKE_TARGETS,
157            ),
158        ],
159    )
160
161
162def _get_repo_and_package_root() -> tuple[Path, Path]:
163    pw_env = pw_cli.env.pigweed_environment()
164    repo_root = pw_cli.env.project_root()
165    package_root = pw_env.PW_PACKAGE_ROOT
166    if not package_root:
167        package_root = repo_root / 'environment/packages'
168    return (repo_root, package_root)
169
170
171def pigweed_upstream_main() -> int:
172    """Entry point for Pigweed upstream ``pw build`` command.
173
174    Defines one or more BuildRecipes and passes that along with all of Pigweed
175    upstream presubmit programs to the project_builder_presubmit_runner.main to
176    start a pw build invocation.
177
178    Returns:
179      An int representing the success or failure status of the build; 0 if
180      successful, 1 if failed.
181
182    Command line usage examples:
183
184    .. code-block:: bash
185
186       pw build --list
187       pw build --recipe default_gn
188       pw build --recipe default_* --watch
189       pw build --step gn_combined_build_check --step gn_python_*
190       pw build -C out/gn --watch
191    """
192    repo_root, package_root = _get_repo_and_package_root()
193
194    return main(
195        presubmit_programs=pw_presubmit.pigweed_presubmit.PROGRAMS,
196        build_recipes=[
197            gn_recipe(),
198            bazel_recipe(),
199            cmake_recipe(repo_root, package_root),
200        ],
201        default_root_logfile=Path('out/build.txt'),
202    )
203
204
205def _build_argument_parser() -> argparse.ArgumentParser:
206    return get_parser(
207        pw_presubmit.pigweed_presubmit.PROGRAMS,
208        [
209            gn_recipe(),
210            bazel_recipe(),
211            # Empty cmake recipe for argparse validation by display_name.
212            # This is needed because project_root can only be determined with
213            # bazel run and the sphinx docs build runs this function for
214            # argparse documentation.
215            BuildRecipe(
216                build_dir=Path('out/cmake'),
217                title='default_cmake',
218                steps=[],
219            ),
220        ],
221    )
222
223
224if __name__ == '__main__':
225    sys.exit(pigweed_upstream_main())
226