blob: 56389ebf303612f8ffec07b3e804d2bb2c3a1b68 [file] [log] [blame]
Karsten Sperling3902c352023-05-03 15:41:32 +12001#!/bin/bash -e
2
3# Copyright (c) 2023 Project CHIP Authors
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17# Usage: configure [OPTIONS] [--project=... [PROJECT OPTIONS]]
18#
19# Configures a stand-alone build for a CHIP application in the current
20# directory and creates a `ninja-build` wrapper script to build it. Should
21# generally be run from an empty build directory (i.e. out-of-tree).
22#
23# This is intended to be used in the context of an external build system and
24# represents a light-weight alternative to bootstrapping the full Pigweed build
25# environment (via scripts/activate.sh). The pigweed git sub-module must still
26# be present though.
27#
28# External tool dependencies: bash, python3, gn, ninja
29#
30# The zap-cli code generator and a small number of Python modules are
31# downloaded if necessary (see scripts/setup/requirements.build.txt) and
32# installed in a build environment directory. By default this is local to
33# the build directory, but an external directory can be specified using the
34# --build-env-dir option. The build environment directory can be shared by any
35# number of build directories, independently of target / tool chain.
Karsten Sperling3902c352023-05-03 15:41:32 +120036
Karsten Sperling65a3b382023-05-10 13:30:55 +120037set -o pipefail
38shopt -s extglob
39
40function usage() { # status
Karsten Sperling3902c352023-05-03 15:41:32 +120041 info "Usage: $0 [OPTIONS] [--project=... [PROJECT OPTIONS]]"
42 info "Options:"
Karsten Sperling65a3b382023-05-10 13:30:55 +120043 info " --build-env-dir=DIR Directory to create (host) build environment in"
Lukas Zellerc5340702023-10-10 03:33:30 +020044 info " --project=DIR directory to build, absolute or relative to chip root,"
45 info " eg examples/lighting-app/linux or /my/dir/my/app"
Karsten Sperling65a3b382023-05-10 13:30:55 +120046 info ""
47 info "Project options (mapped to GN build args):"
48 info " --enable-<ARG>[=no] Enables (or disables with '=no') a bool build arg"
49 info " --<ARG>=<VALUE> Sets a (non-bool) build arg to the given value"
50 info " GN argument names can be specified with '-' instead of '_' and prefixes"
51 info " like 'chip_' can be ommitted from names. For the full list of available"
52 info " build arguments, see the generated args.configured file."
53 info ""
54 info " By default, the toolchain for the GN build will be configured from the usual"
55 info " environment variables (CC, CXX, AR, CFLAGS, CXXFLAGS, ...), falling back to"
56 info " default tool names (CC=cc, ...). When using this script within an external"
57 info " build system, toolchain environment variables should be populated."
Karsten Sperling1a30a4a2023-12-06 10:07:38 +130058 info ""
59 info "Code generation:"
60 info " By default, some code generation will happen at build time using zap-cli."
61 info " If zap-cli is not available on PATH, configure will attempt to download it."
62 info " Alternatively, if a directory called 'zzz_pregenerated' exists at the root"
63 info " of the project or the root of the SDK, pre-generated code from this directory"
64 info " will be used. In this case, build time code generation will be disabled and"
65 info " zap-cli is not required."
Karsten Sperling65a3b382023-05-10 13:30:55 +120066 exit "$1"
Karsten Sperling3902c352023-05-03 15:41:32 +120067}
68
69function main() { # ...
Karsten Sperling3902c352023-05-03 15:41:32 +120070 CHIP_ROOT=$(cd "$(dirname "$0")/.." && pwd)
71 BUILD_ENV_DEPS=(
72 "${CHIP_ROOT}/scripts/setup/requirements.build.txt"
73 "${CHIP_ROOT}/scripts/setup/constraints.txt"
Karsten Sperling1a30a4a2023-12-06 10:07:38 +130074 )
75 BUILD_ENV_DEPS_CODEGEN=(
Karsten Sperling3902c352023-05-03 15:41:32 +120076 "${CHIP_ROOT}/scripts/setup/zap.version"
77 )
78
Karsten Sperling65a3b382023-05-10 13:30:55 +120079 # Parse global options, process VAR=VALUE style arguments, and collect project options
80 BUILD_ENV_DIR=
Karsten Sperling3902c352023-05-03 15:41:32 +120081 PROJECT=
Lukas Zellerc5340702023-10-10 03:33:30 +020082 PROJECT_PATH=
Karsten Sperling65a3b382023-05-10 13:30:55 +120083 PROJECT_ARGS=()
84 while [[ $# -gt 0 ]]; do
Karsten Sperling3902c352023-05-03 15:41:32 +120085 case "$1" in
Karsten Sperling65a3b382023-05-10 13:30:55 +120086 --help) usage 0 ;;
Karsten Sperling3902c352023-05-03 15:41:32 +120087 --build-env-dir=*) BUILD_ENV_DIR="${1#*=}" ;;
88 --project=*) PROJECT="${1#*=}" ;;
Karsten Sperling65a3b382023-05-10 13:30:55 +120089 +([A-Z_])=*) export "$1" ;;
90 *)
91 [[ -n "$PROJECT" ]] || fail "Invalid argument: '$1'"
92 PROJECT_ARGS+=("$1")
93 ;;
Karsten Sperling3902c352023-05-03 15:41:32 +120094 esac
95 shift
96 done
97
Karsten Sperling65a3b382023-05-10 13:30:55 +120098 # Ensure we have something to do
99 [[ -n "$PROJECT" || -n "$BUILD_ENV_DIR" ]] || usage 1
100
Karsten Sperling3902c352023-05-03 15:41:32 +1200101 if [[ -n "$PROJECT" ]]; then
Lukas Zellerc5340702023-10-10 03:33:30 +0200102 PROJECT_PATH="$(cd "${CHIP_ROOT}" 2>/dev/null && cd "${PROJECT}" 2>/dev/null && pwd)"
103 [[ -n "$PROJECT_PATH" && -r "${PROJECT_PATH}/.gn" ]] || fail "Invalid project '${PROJECT}' - missing .gn at '${PROJECT_PATH}'"
Karsten Sperling3902c352023-05-03 15:41:32 +1200104 fi
105
Karsten Sperlingb3a5bf12023-11-21 02:43:54 +1300106 if [[ -n "$PW_ROOT" ]]; then
107 info "WARNING: A Pigweed environment appears to be active, this is usually a misconfiguration."
108 fi
109
Karsten Sperling1a30a4a2023-12-06 10:07:38 +1300110 # Check for pre-generated code. CHIP_PREGEN_DIR will be picked up by process_project_args.
111 local pregen_dir="zzz_pregenerated"
112 if [[ -n "$PROJECT_PATH" && -d "${PROJECT_PATH}/${pregen_dir}" ]]; then
113 info "Will use pre-generated code from ${PROJECT}/${pregen_dir}, no zap-cli required."
114 export CHIP_PREGEN_DIR="//${pregen_dir}"
115 elif [[ -d "${CHIP_ROOT}/${pregen_dir}" ]]; then
116 info "Will use pre-generated code from ${pregen_dir}, no zap-cli required."
117 export CHIP_PREGEN_DIR="\${chip_root}/${pregen_dir}"
118 else
119 BUILD_ENV_DEPS+=("${BUILD_ENV_DEPS_CODEGEN[@]}")
120 unset CHIP_PREGEN_DIR
121 fi
Karsten Sperling65a3b382023-05-10 13:30:55 +1200122
123 # Work out build and environment directories
124 if [[ "$PWD" == "$CHIP_ROOT" ]]; then
125 BUILD_DIR="out/configured"
126 NINJA_HINT="ninja -C ${BUILD_DIR}"
127 else
128 BUILD_DIR="."
129 NINJA_HINT="ninja"
130 fi
131
132 if [[ -n "$BUILD_ENV_DIR" ]]; then
133 mkdir -p "$BUILD_ENV_DIR"
134 BUILD_ENV_PATH="$(cd "$BUILD_ENV_DIR" && pwd)"
135 [[ -n "$BUILD_ENV_PATH" ]] || fail "Invalid build-env-dir '${BUILD_ENV_DIR}'"
136 BUILD_ENV_DIR="$BUILD_ENV_PATH" # absolute
137 else
138 BUILD_ENV_DIR="build-env" # relative to BUILD_DIR
139 BUILD_ENV_PATH="${BUILD_DIR}/${BUILD_ENV_DIR}"
140 fi
141
Karsten Sperling1a30a4a2023-12-06 10:07:38 +1300142 # Check required tools are present
143 check_binary gn GN
144 check_binary ninja NINJA
145
Karsten Sperling65a3b382023-05-10 13:30:55 +1200146 # Create the build environment if necessary
147 if ! check_build_env; then
Karsten Sperling3902c352023-05-03 15:41:32 +1200148 check_python
149 configure_python_env
Karsten Sperling1a30a4a2023-12-06 10:07:38 +1300150 if [[ -z "$CHIP_PREGEN_DIR" ]] && ! check_binary zap-cli; then
Karsten Sperling3902c352023-05-03 15:41:32 +1200151 download_zap
152 fi
153 finalize_build_env
154 fi
155
Karsten Sperling65a3b382023-05-10 13:30:55 +1200156 # Configure the project (if requested)
Karsten Sperling3902c352023-05-03 15:41:32 +1200157 if [[ -z "$PROJECT" ]]; then
158 info "Build environment created. (Specify --project=DIR to configure a build.)"
159 return
160 fi
161
Karsten Sperling65a3b382023-05-10 13:30:55 +1200162 [[ "$BUILD_DIR" != "." ]] && info "Configuring in-tree, will build in ${BUILD_DIR}"
163
Karsten Sperling3902c352023-05-03 15:41:32 +1200164 create_empty_pw_env
Karsten Sperling65a3b382023-05-10 13:30:55 +1200165 guess_toolchain
166 gn_generate "${PROJECT_ARGS[@]}"
Karsten Sperling3902c352023-05-03 15:41:32 +1200167 create_ninja_wrapper
Karsten Sperling65a3b382023-05-10 13:30:55 +1200168 info "You can now run ./ninja-build (or $NINJA_HINT)"
Karsten Sperling3902c352023-05-03 15:41:32 +1200169}
170
171function create_empty_pw_env() {
172 # The Pigweed environment ("//build_overrides/pigweed_environment.gni") is
173 # imported unconditionally in various build files, so ensure it exists.
174 local gni="build_overrides/pigweed_environment.gni"
175 if [[ -d "${CHIP_ROOT}/$(dirname "$gni")" ]]; then
176 if safe_to_clobber "$gni"; then
177 info "Creating empty $gni in source tree"
178 echo "# ${CONFIGURE_MARKER}" >"${CHIP_ROOT}/${gni}"
179 else
Karsten Sperlingb3a5bf12023-11-21 02:43:54 +1300180 info "WARNING: Leaving existing $gni in place, this might affect the build configuration."
Karsten Sperling3902c352023-05-03 15:41:32 +1200181 fi
182 fi
183}
184
Karsten Sperling65a3b382023-05-10 13:30:55 +1200185function guess_toolchain() {
186 # There is no widely used standard command for the C++ compiler (analogous to
187 # `cc` for the C compiler), so if neither CC nor CXX are defined try to guess.
188 if [[ -z "$CC" && -z "$CXX" ]] && have_binary cc; then
189 local probe="$(cc -E - <<<'gnu=__GNUC__ clang=__clang__' 2>/dev/null)"
190 # Check for clang first because it also defines __GNUC__
191 if [[ "$probe" =~ clang=[1-9] ]] && have_binary clang && have_binary clang++; then
192 info "Guessing CC=clang CXX=clang++ because cc appears to be clang"
193 export CC=clang CXX=clang++
194 elif [[ "$probe" =~ gnu=[1-9] ]] && have_binary gcc && have_binary g++; then
195 info "Guessing CC=gcc CXX=g++ because cc appears to be gcc"
196 export CC=gcc CXX=g++
197 else
198 info "Unable to guess c++ compiler: $probe"
199 fi
200 fi
201}
202
Karsten Sperling3902c352023-05-03 15:41:32 +1200203function gn_generate() { # [project options]
Karsten Sperling65a3b382023-05-10 13:30:55 +1200204 mkdir -p "${BUILD_DIR}"
205 ensure_no_clobber "${BUILD_DIR}/args.gn"
Karsten Sperling3902c352023-05-03 15:41:32 +1200206
Karsten Sperling65a3b382023-05-10 13:30:55 +1200207 # Pass --script-executable to all `gn` calls so scripts run in our venv
Lukas Zellerc5340702023-10-10 03:33:30 +0200208 local gn=(gn --script-executable="${BUILD_ENV_DIR}/bin/python" --root="${PROJECT_PATH}")
Karsten Sperling3902c352023-05-03 15:41:32 +1200209
Karsten Sperling65a3b382023-05-10 13:30:55 +1200210 # Run gn gen with an empty args.gn first so we can list all arguments
211 info "Configuring gn build arguments (see $BUILD_DIR/args.configured for full list)"
212 {
213 echo "# ${CONFIGURE_MARKER}"
Lukas Zellerc5340702023-10-10 03:33:30 +0200214 echo "# project root: ${PROJECT_PATH}"
Karsten Sperling1a30a4a2023-12-06 10:07:38 +1300215 echo "import(\"//build_overrides/chip.gni\")"
Karsten Sperling65a3b382023-05-10 13:30:55 +1200216 } >"${BUILD_DIR}/args.gn"
217 "${gn[@]}" -q gen "$BUILD_DIR"
Karsten Sperling3902c352023-05-03 15:41:32 +1200218
Karsten Sperling65a3b382023-05-10 13:30:55 +1200219 # Use the argument list to drive the mapping of our command line options to GN args
220 call_impl process_project_args <("${gn[@]}" args "$BUILD_DIR" --list --json) "$@" >>"${BUILD_DIR}/args.gn"
221 "${gn[@]}" args "$BUILD_DIR" --list >"${BUILD_DIR}/args.configured"
222
223 # Now gn gen with the arguments we have configured.
224 info "Running gn gen to generate ninja files"
225 "${gn[@]}" -q gen "$BUILD_DIR"
Karsten Sperling3902c352023-05-03 15:41:32 +1200226}
227
228function create_ninja_wrapper() {
Karsten Sperling3902c352023-05-03 15:41:32 +1200229 local wrapper="ninja-build"
230 ensure_no_clobber "$wrapper"
Karsten Sperling65a3b382023-05-10 13:30:55 +1200231 {
232 echo "#!/bin/bash -e"
233 echo "# ${CONFIGURE_MARKER}"
234 if [[ "$BUILD_DIR" != "." ]]; then
235 echo 'args=(-C "$(dirname "$0")/'"${BUILD_DIR}"'")'
236 else
237 echo 'args=() dir="$(dirname "$0")"'
238 echo '[[ "$dir" != "." ]] && args=(-C "$dir")'
239 fi
240 echo 'exec ninja "${args[@]}" "$@"'
241 } >"$wrapper"
Karsten Sperling3902c352023-05-03 15:41:32 +1200242 chmod a+x "$wrapper"
243}
244
Karsten Sperling65a3b382023-05-10 13:30:55 +1200245function check_build_env() {
Karsten Sperling3902c352023-05-03 15:41:32 +1200246 generate_build_env_cksum # re-used by finalize_build_env
Karsten Sperling65a3b382023-05-10 13:30:55 +1200247 [[ -r "${BUILD_ENV_PATH}/.cksum" ]] || return 1
248 read -r <"${BUILD_ENV_PATH}/.cksum" || true
Karsten Sperling3902c352023-05-03 15:41:32 +1200249 [[ "$REPLY" == "$CURRENT_ENV_CKSUM" ]] || return 1
250
Karsten Sperling65a3b382023-05-10 13:30:55 +1200251 [[ -r "${BUILD_ENV_PATH}/bin/activate" ]] || return 1
252 info "Using existing build environment: ${BUILD_ENV_PATH}"
253 PYTHON="${BUILD_ENV_PATH}/bin/python"
Karsten Sperling3902c352023-05-03 15:41:32 +1200254}
255
256function configure_python_env() {
257 progress "Setting up Python venv"
Karsten Sperling367a0c62023-08-08 13:20:47 +1200258 # Debian and Ubuntu ship python3 with a broken venv module unless the
259 # python3-venv package is installed (https://bugs.launchpad.net/bugs/1290847)
260 local withoutpip=() pip="${BUILD_ENV_PATH}/bin/pip"
261 if ! "$PYTHON" -m ensurepip --version >/dev/null 2>&1; then
262 withoutpip=(--without-pip) pip="${pip}.pyz" # bootstrapped below
263 fi
264 "$PYTHON" -m venv --clear "${withoutpip[@]}" "$BUILD_ENV_PATH"
Karsten Sperling65a3b382023-05-10 13:30:55 +1200265 info "$BUILD_ENV_PATH"
266
Karsten Sperling367a0c62023-08-08 13:20:47 +1200267 # Download a standalone pip.pyz from pypa.io if necessary
268 if [[ -n "$withoutpip" ]]; then
269 progress "Bootstrapping pip via pypa.io (venv module is missing ensurepip dependency)"
270 call_impl download https://bootstrap.pypa.io/pip/pip.pyz "$pip"
271 info "ok"
272 fi
273
Karsten Sperling65a3b382023-05-10 13:30:55 +1200274 # Install our auto-loading venvactivate module so that running scripts via
275 # the venv python has the side-effect of fully activating the environment.
276 local sitepkgs=("${BUILD_ENV_PATH}/lib/python"*"/site-packages")
277 [[ -d "$sitepkgs" ]] || fail "Failed to locate venv site-packages"
278 cp "${CHIP_ROOT}/scripts/configure.venv/venvactivate".{pth,py} "${sitepkgs}/"
Karsten Sperling3902c352023-05-03 15:41:32 +1200279
280 progress "Installing Python build dependencies"
Karsten Sperling367a0c62023-08-08 13:20:47 +1200281 # Ensure pip and wheel are up to date first (using pip.pyz if necessary)
282 "${BUILD_ENV_PATH}/bin/python3" "$pip" install --require-virtualenv --quiet --upgrade pip wheel
283
Karsten Sperling65a3b382023-05-10 13:30:55 +1200284 "${BUILD_ENV_PATH}/bin/pip" install --require-virtualenv --quiet \
Karsten Sperling3902c352023-05-03 15:41:32 +1200285 -r "${CHIP_ROOT}/scripts/setup/requirements.build.txt" \
286 -c "${CHIP_ROOT}/scripts/setup/constraints.txt"
287 info "ok"
288}
289
290function generate_build_env_cksum() {
291 # Conservatively assume that any change to this script or BUILD_ENV_DEPS invalidates the environment
292 CURRENT_ENV_CKSUM="$(cat "$0" "${BUILD_ENV_DEPS[@]}" | cksum)"
293 [[ -n "$CURRENT_ENV_CKSUM" ]] || fail "Failed to generate build environment checksum"
294}
295
296function finalize_build_env() {
Karsten Sperling65a3b382023-05-10 13:30:55 +1200297 echo "$CURRENT_ENV_CKSUM" >"${BUILD_ENV_PATH}/.cksum"
Karsten Sperling3902c352023-05-03 15:41:32 +1200298}
299
300function download_zap() {
Karsten Sperling367a0c62023-08-08 13:20:47 +1200301 local version
Karsten Sperling3902c352023-05-03 15:41:32 +1200302 read -r version <"${CHIP_ROOT}/scripts/setup/zap.version"
Karsten Sperling367a0c62023-08-08 13:20:47 +1200303 local platform="$(uname -sm)" flavor
304 case "$platform" in
305 Linux\ x86_64) flavor=linux-x64 ;;
306 Linux\ arm64) flavor=linux-arm64 ;;
Michael Maroszek92796282024-01-04 00:48:13 +0100307 Linux\ aarch64) flavor=linux-arm64 ;;
308 Darwin\ x86_64) flavor=mac-x64 ;;
309 Darwin\ arm64) flavor=mac-arm64 ;;
Karsten Sperling367a0c62023-08-08 13:20:47 +1200310 *) fail "Unable to determine zap flavor for $platform" ;;
Karsten Sperling3902c352023-05-03 15:41:32 +1200311 esac
Karsten Sperling367a0c62023-08-08 13:20:47 +1200312 local url="https://github.com/project-chip/zap/releases/download/${version}/zap-${flavor}.zip"
Karsten Sperling3902c352023-05-03 15:41:32 +1200313
314 progress "Installing zap-cli from $url"
Karsten Sperling65a3b382023-05-10 13:30:55 +1200315 call_impl download_and_extract_zip "$url" "${BUILD_ENV_PATH}/bin" zap-cli
316 chmod a+x "${BUILD_ENV_PATH}/bin/zap-cli" # ZipFile.extract() does not handle permissions
Karsten Sperling3902c352023-05-03 15:41:32 +1200317 info "ok"
318}
319
320function call_impl() { # func ...
321 "$PYTHON" "${CHIP_ROOT}/scripts/configure.impl.py" "$@"
322}
323
324function check_python() {
325 progress "Checking for Python 3"
326 if have_binary python3; then
327 PYTHON="$(hash -t python3)"
328 elif have_binary python; then
329 PYTHON="$(hash -t python)"
330 local ver="$("$PYTHON" --version)"
331 if [[ "$ver" != "Python 3."* ]]; then
332 info "need Python 3 but found $ver"
333 return 1
334 fi
335 else
336 info "not found"
337 return 1
338 fi
339 info "$PYTHON"
340}
341
342function check_binary() { # binary [VAR]
343 progress "Checking for $1"
344 if ! have_binary "$1"; then
345 info "not found"
346 return 1
347 fi
348 local path="$(hash -t "$1")"
349 [[ -n "$2" ]] && eval "$2=\$path"
350 info "$path"
351}
352
353function have_binary() { # binary
354 hash "$1" 2>/dev/null
355}
356
357function ensure_no_clobber() { # file
358 safe_to_clobber "$1" || fail "Won't overwrite file not generated by configure: $1"
359}
360
361function safe_to_clobber() { # file
362 CONFIGURE_MARKER="Auto-generated by configure, do not edit"
363 [[ -s "$1" ]] || return 0
364 read -r -n 512 -d '' <"$1" || true
365 [[ "${REPLY/$CONFIGURE_MARKER/}" != "$REPLY" ]] && return 0
366 return 1
367}
368
369function info() { # message
370 echo "$*" >&2
371}
372
373function progress() { # message
374 echo -n "$*... " >&2
375}
376
377function fail() { # message
378 echo "Error: $*" >&2
379 exit 1
380}
381
382main "$@"