blob: d48971862e29430eb4925573579f5d52564ac4d5 [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"
44 info " --project=DIR Sub-directory to build, eg examples/lighting-app/linux"
45 info ""
46 info "Project options (mapped to GN build args):"
47 info " --enable-<ARG>[=no] Enables (or disables with '=no') a bool build arg"
48 info " --<ARG>=<VALUE> Sets a (non-bool) build arg to the given value"
49 info " GN argument names can be specified with '-' instead of '_' and prefixes"
50 info " like 'chip_' can be ommitted from names. For the full list of available"
51 info " build arguments, see the generated args.configured file."
52 info ""
53 info " By default, the toolchain for the GN build will be configured from the usual"
54 info " environment variables (CC, CXX, AR, CFLAGS, CXXFLAGS, ...), falling back to"
55 info " default tool names (CC=cc, ...). When using this script within an external"
56 info " build system, toolchain environment variables should be populated."
57 exit "$1"
Karsten Sperling3902c352023-05-03 15:41:32 +120058}
59
60function main() { # ...
Karsten Sperling3902c352023-05-03 15:41:32 +120061 CHIP_ROOT=$(cd "$(dirname "$0")/.." && pwd)
62 BUILD_ENV_DEPS=(
63 "${CHIP_ROOT}/scripts/setup/requirements.build.txt"
64 "${CHIP_ROOT}/scripts/setup/constraints.txt"
65 "${CHIP_ROOT}/scripts/setup/zap.version"
66 )
67
Karsten Sperling65a3b382023-05-10 13:30:55 +120068 # Parse global options, process VAR=VALUE style arguments, and collect project options
69 BUILD_ENV_DIR=
Karsten Sperling3902c352023-05-03 15:41:32 +120070 PROJECT=
Karsten Sperling65a3b382023-05-10 13:30:55 +120071 PROJECT_ARGS=()
72 while [[ $# -gt 0 ]]; do
Karsten Sperling3902c352023-05-03 15:41:32 +120073 case "$1" in
Karsten Sperling65a3b382023-05-10 13:30:55 +120074 --help) usage 0 ;;
Karsten Sperling3902c352023-05-03 15:41:32 +120075 --build-env-dir=*) BUILD_ENV_DIR="${1#*=}" ;;
76 --project=*) PROJECT="${1#*=}" ;;
Karsten Sperling65a3b382023-05-10 13:30:55 +120077 +([A-Z_])=*) export "$1" ;;
78 *)
79 [[ -n "$PROJECT" ]] || fail "Invalid argument: '$1'"
80 PROJECT_ARGS+=("$1")
81 ;;
Karsten Sperling3902c352023-05-03 15:41:32 +120082 esac
83 shift
84 done
85
Karsten Sperling65a3b382023-05-10 13:30:55 +120086 # Ensure we have something to do
87 [[ -n "$PROJECT" || -n "$BUILD_ENV_DIR" ]] || usage 1
88
Karsten Sperling3902c352023-05-03 15:41:32 +120089 if [[ -n "$PROJECT" ]]; then
90 local subdir="$(cd "${CHIP_ROOT}/${PROJECT}" 2>/dev/null && pwd)"
91 [[ -n "$subdir" && -r "${subdir}/.gn" ]] || fail "Invalid project '${PROJECT}'"
92 PROJECT="${subdir#${CHIP_ROOT}/}"
93 [[ "$subdir" == "${CHIP_ROOT}/${PROJECT}" ]] || fail "Unable to determine project path"
94 fi
95
96 check_binary gn GN
97 check_binary ninja NINJA
Karsten Sperling65a3b382023-05-10 13:30:55 +120098
99 # Work out build and environment directories
100 if [[ "$PWD" == "$CHIP_ROOT" ]]; then
101 BUILD_DIR="out/configured"
102 NINJA_HINT="ninja -C ${BUILD_DIR}"
103 else
104 BUILD_DIR="."
105 NINJA_HINT="ninja"
106 fi
107
108 if [[ -n "$BUILD_ENV_DIR" ]]; then
109 mkdir -p "$BUILD_ENV_DIR"
110 BUILD_ENV_PATH="$(cd "$BUILD_ENV_DIR" && pwd)"
111 [[ -n "$BUILD_ENV_PATH" ]] || fail "Invalid build-env-dir '${BUILD_ENV_DIR}'"
112 BUILD_ENV_DIR="$BUILD_ENV_PATH" # absolute
113 else
114 BUILD_ENV_DIR="build-env" # relative to BUILD_DIR
115 BUILD_ENV_PATH="${BUILD_DIR}/${BUILD_ENV_DIR}"
116 fi
117
118 # Create the build environment if necessary
119 if ! check_build_env; then
Karsten Sperling3902c352023-05-03 15:41:32 +1200120 check_python
121 configure_python_env
122 if ! check_binary zap-cli; then
123 download_zap
124 fi
125 finalize_build_env
126 fi
127
Karsten Sperling65a3b382023-05-10 13:30:55 +1200128 # Configure the project (if requested)
Karsten Sperling3902c352023-05-03 15:41:32 +1200129 if [[ -z "$PROJECT" ]]; then
130 info "Build environment created. (Specify --project=DIR to configure a build.)"
131 return
132 fi
133
Karsten Sperling65a3b382023-05-10 13:30:55 +1200134 [[ "$BUILD_DIR" != "." ]] && info "Configuring in-tree, will build in ${BUILD_DIR}"
135
Karsten Sperling3902c352023-05-03 15:41:32 +1200136 create_empty_pw_env
Karsten Sperling65a3b382023-05-10 13:30:55 +1200137 guess_toolchain
138 gn_generate "${PROJECT_ARGS[@]}"
Karsten Sperling3902c352023-05-03 15:41:32 +1200139 create_ninja_wrapper
Karsten Sperling65a3b382023-05-10 13:30:55 +1200140 info "You can now run ./ninja-build (or $NINJA_HINT)"
Karsten Sperling3902c352023-05-03 15:41:32 +1200141}
142
143function create_empty_pw_env() {
144 # The Pigweed environment ("//build_overrides/pigweed_environment.gni") is
145 # imported unconditionally in various build files, so ensure it exists.
146 local gni="build_overrides/pigweed_environment.gni"
147 if [[ -d "${CHIP_ROOT}/$(dirname "$gni")" ]]; then
148 if safe_to_clobber "$gni"; then
149 info "Creating empty $gni in source tree"
150 echo "# ${CONFIGURE_MARKER}" >"${CHIP_ROOT}/${gni}"
151 else
152 info "Warning: Leaving existing $gni in place, this might affect the build configuration."
153 fi
154 fi
155}
156
Karsten Sperling65a3b382023-05-10 13:30:55 +1200157function guess_toolchain() {
158 # There is no widely used standard command for the C++ compiler (analogous to
159 # `cc` for the C compiler), so if neither CC nor CXX are defined try to guess.
160 if [[ -z "$CC" && -z "$CXX" ]] && have_binary cc; then
161 local probe="$(cc -E - <<<'gnu=__GNUC__ clang=__clang__' 2>/dev/null)"
162 # Check for clang first because it also defines __GNUC__
163 if [[ "$probe" =~ clang=[1-9] ]] && have_binary clang && have_binary clang++; then
164 info "Guessing CC=clang CXX=clang++ because cc appears to be clang"
165 export CC=clang CXX=clang++
166 elif [[ "$probe" =~ gnu=[1-9] ]] && have_binary gcc && have_binary g++; then
167 info "Guessing CC=gcc CXX=g++ because cc appears to be gcc"
168 export CC=gcc CXX=g++
169 else
170 info "Unable to guess c++ compiler: $probe"
171 fi
172 fi
173}
174
Karsten Sperling3902c352023-05-03 15:41:32 +1200175function gn_generate() { # [project options]
Karsten Sperling65a3b382023-05-10 13:30:55 +1200176 mkdir -p "${BUILD_DIR}"
177 ensure_no_clobber "${BUILD_DIR}/args.gn"
Karsten Sperling3902c352023-05-03 15:41:32 +1200178
Karsten Sperling65a3b382023-05-10 13:30:55 +1200179 # Pass --script-executable to all `gn` calls so scripts run in our venv
180 local gn=(gn --script-executable="${BUILD_ENV_DIR}/bin/python" --root="${CHIP_ROOT}/${PROJECT}")
Karsten Sperling3902c352023-05-03 15:41:32 +1200181
Karsten Sperling65a3b382023-05-10 13:30:55 +1200182 # Run gn gen with an empty args.gn first so we can list all arguments
183 info "Configuring gn build arguments (see $BUILD_DIR/args.configured for full list)"
184 {
185 echo "# ${CONFIGURE_MARKER}"
186 echo "# project root: ${PROJECT}"
187 } >"${BUILD_DIR}/args.gn"
188 "${gn[@]}" -q gen "$BUILD_DIR"
Karsten Sperling3902c352023-05-03 15:41:32 +1200189
Karsten Sperling65a3b382023-05-10 13:30:55 +1200190 # Use the argument list to drive the mapping of our command line options to GN args
191 call_impl process_project_args <("${gn[@]}" args "$BUILD_DIR" --list --json) "$@" >>"${BUILD_DIR}/args.gn"
192 "${gn[@]}" args "$BUILD_DIR" --list >"${BUILD_DIR}/args.configured"
193
194 # Now gn gen with the arguments we have configured.
195 info "Running gn gen to generate ninja files"
196 "${gn[@]}" -q gen "$BUILD_DIR"
Karsten Sperling3902c352023-05-03 15:41:32 +1200197}
198
199function create_ninja_wrapper() {
Karsten Sperling3902c352023-05-03 15:41:32 +1200200 local wrapper="ninja-build"
201 ensure_no_clobber "$wrapper"
Karsten Sperling65a3b382023-05-10 13:30:55 +1200202 {
203 echo "#!/bin/bash -e"
204 echo "# ${CONFIGURE_MARKER}"
205 if [[ "$BUILD_DIR" != "." ]]; then
206 echo 'args=(-C "$(dirname "$0")/'"${BUILD_DIR}"'")'
207 else
208 echo 'args=() dir="$(dirname "$0")"'
209 echo '[[ "$dir" != "." ]] && args=(-C "$dir")'
210 fi
211 echo 'exec ninja "${args[@]}" "$@"'
212 } >"$wrapper"
Karsten Sperling3902c352023-05-03 15:41:32 +1200213 chmod a+x "$wrapper"
214}
215
Karsten Sperling65a3b382023-05-10 13:30:55 +1200216function check_build_env() {
Karsten Sperling3902c352023-05-03 15:41:32 +1200217 generate_build_env_cksum # re-used by finalize_build_env
Karsten Sperling65a3b382023-05-10 13:30:55 +1200218 [[ -r "${BUILD_ENV_PATH}/.cksum" ]] || return 1
219 read -r <"${BUILD_ENV_PATH}/.cksum" || true
Karsten Sperling3902c352023-05-03 15:41:32 +1200220 [[ "$REPLY" == "$CURRENT_ENV_CKSUM" ]] || return 1
221
Karsten Sperling65a3b382023-05-10 13:30:55 +1200222 [[ -r "${BUILD_ENV_PATH}/bin/activate" ]] || return 1
223 info "Using existing build environment: ${BUILD_ENV_PATH}"
224 PYTHON="${BUILD_ENV_PATH}/bin/python"
Karsten Sperling3902c352023-05-03 15:41:32 +1200225}
226
227function configure_python_env() {
228 progress "Setting up Python venv"
Karsten Sperling65a3b382023-05-10 13:30:55 +1200229 "$PYTHON" -m venv --clear "$BUILD_ENV_PATH"
230 info "$BUILD_ENV_PATH"
231
232 # Install our auto-loading venvactivate module so that running scripts via
233 # the venv python has the side-effect of fully activating the environment.
234 local sitepkgs=("${BUILD_ENV_PATH}/lib/python"*"/site-packages")
235 [[ -d "$sitepkgs" ]] || fail "Failed to locate venv site-packages"
236 cp "${CHIP_ROOT}/scripts/configure.venv/venvactivate".{pth,py} "${sitepkgs}/"
Karsten Sperling3902c352023-05-03 15:41:32 +1200237
238 progress "Installing Python build dependencies"
Karsten Sperling65a3b382023-05-10 13:30:55 +1200239 "${BUILD_ENV_PATH}/bin/pip" install --require-virtualenv --quiet --upgrade pip wheel
240 "${BUILD_ENV_PATH}/bin/pip" install --require-virtualenv --quiet \
Karsten Sperling3902c352023-05-03 15:41:32 +1200241 -r "${CHIP_ROOT}/scripts/setup/requirements.build.txt" \
242 -c "${CHIP_ROOT}/scripts/setup/constraints.txt"
243 info "ok"
244}
245
246function generate_build_env_cksum() {
247 # Conservatively assume that any change to this script or BUILD_ENV_DEPS invalidates the environment
248 CURRENT_ENV_CKSUM="$(cat "$0" "${BUILD_ENV_DEPS[@]}" | cksum)"
249 [[ -n "$CURRENT_ENV_CKSUM" ]] || fail "Failed to generate build environment checksum"
250}
251
252function finalize_build_env() {
Karsten Sperling65a3b382023-05-10 13:30:55 +1200253 echo "$CURRENT_ENV_CKSUM" >"${BUILD_ENV_PATH}/.cksum"
Karsten Sperling3902c352023-05-03 15:41:32 +1200254}
255
256function download_zap() {
Boris Zbarskyc2e12e62023-05-17 18:00:59 -0400257 local version platform arch
Karsten Sperling3902c352023-05-03 15:41:32 +1200258 read -r version <"${CHIP_ROOT}/scripts/setup/zap.version"
259 case "$OSTYPE" in
260 linux*) platform=linux ;;
261 darwin*) platform=mac ;;
262 *) fail "Unable to determine zap platform for OSTYPE='$OSTYPE'" ;;
263 esac
Boris Zbarskyc2e12e62023-05-17 18:00:59 -0400264 case "$(uname -m)" in
265 arm64) arch=arm64 ;;
266 x86_64) arch=x64 ;;
267 *) fail "Unable to determine zap architecture for 'uname -m'='$(uname -m)'" ;;
268 esac
269 local url="https://github.com/project-chip/zap/releases/download/${version}/zap-${platform}-${arch}.zip"
Karsten Sperling3902c352023-05-03 15:41:32 +1200270
271 progress "Installing zap-cli from $url"
Karsten Sperling65a3b382023-05-10 13:30:55 +1200272 call_impl download_and_extract_zip "$url" "${BUILD_ENV_PATH}/bin" zap-cli
273 chmod a+x "${BUILD_ENV_PATH}/bin/zap-cli" # ZipFile.extract() does not handle permissions
Karsten Sperling3902c352023-05-03 15:41:32 +1200274 info "ok"
275}
276
277function call_impl() { # func ...
278 "$PYTHON" "${CHIP_ROOT}/scripts/configure.impl.py" "$@"
279}
280
281function check_python() {
282 progress "Checking for Python 3"
283 if have_binary python3; then
284 PYTHON="$(hash -t python3)"
285 elif have_binary python; then
286 PYTHON="$(hash -t python)"
287 local ver="$("$PYTHON" --version)"
288 if [[ "$ver" != "Python 3."* ]]; then
289 info "need Python 3 but found $ver"
290 return 1
291 fi
292 else
293 info "not found"
294 return 1
295 fi
296 info "$PYTHON"
297}
298
299function check_binary() { # binary [VAR]
300 progress "Checking for $1"
301 if ! have_binary "$1"; then
302 info "not found"
303 return 1
304 fi
305 local path="$(hash -t "$1")"
306 [[ -n "$2" ]] && eval "$2=\$path"
307 info "$path"
308}
309
310function have_binary() { # binary
311 hash "$1" 2>/dev/null
312}
313
314function ensure_no_clobber() { # file
315 safe_to_clobber "$1" || fail "Won't overwrite file not generated by configure: $1"
316}
317
318function safe_to_clobber() { # file
319 CONFIGURE_MARKER="Auto-generated by configure, do not edit"
320 [[ -s "$1" ]] || return 0
321 read -r -n 512 -d '' <"$1" || true
322 [[ "${REPLY/$CONFIGURE_MARKER/}" != "$REPLY" ]] && return 0
323 return 1
324}
325
326function info() { # message
327 echo "$*" >&2
328}
329
330function progress() { # message
331 echo -n "$*... " >&2
332}
333
334function fail() { # message
335 echo "Error: $*" >&2
336 exit 1
337}
338
339main "$@"