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