| #!/bin/bash | |
| # | |
| # Copyright (C) 2026 The Android Open Source Project | |
| # | |
| # 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. | |
| # Print a debug message | |
| function info() { | |
| local timestamp="$(date +'%Y-%m-%d %H:%M:%S')" | |
| if [[ -t 1 ]]; then | |
| echo -e "\e[90m${timestamp} \e[33mINFO\e[0m $1" | |
| else | |
| echo -e "${timestamp} INFO $1" | |
| fi | |
| } | |
| # Print an error message | |
| function error() { | |
| local timestamp="$(date +'%Y-%m-%d %H:%M:%S')" | |
| if [[ -t 1 ]]; then | |
| echo -e "\e[90m${timestamp} \e[31mERROR\e[0m $1" | |
| else | |
| echo -e "${timestamp} ERROR $1" | |
| fi | |
| } | |
| # Calculate the top of the android source tree | |
| TOP="${ANDROID_BUILD_TOP:-$(dirname "${BASH_SOURCE[0]}")/../../../../..}" | |
| export ANDROID_BUILD_TOP="$TOP" | |
| # The build server sets DIST_DIR, use a sane default for local builds | |
| export DIST_DIR=${DIST_DIR:-$TOP/out/dist} | |
| # Directory that holds the static patches that are included in this script (in | |
| # contrast to the user supplied --patch-dir directory that is used to | |
| # dynamically create or apply patches) | |
| BUNDLED_PATCHES="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/patches)" | |
| # Add the soong and host bin directories to the PATH so that tools can be found by CI | |
| export PATH="$TOP/build/soong/bin:$PATH" | |
| export PATH="$TOP/${OUT_DIR:-out}/host/linux-x86/bin:$PATH" | |
| # This is only set if running on the build server | |
| RUNNING_ON_BUILD_SERVER=${BUILD_NUMBER:=} | |
| # The current build ID (set if running on a build server) | |
| BUILD_NUMBER=${BUILD_NUMBER:=local-build} | |
| # Ensure that TARGET_PRODUCT, TARGET_RELEASE, and TARGET_BUILD_VARIANT are set. | |
| # Sets them to default values if they are not set (sdk, sdk_finalization, and userdebug respectively). | |
| export TARGET_PRODUCT=${TARGET_PRODUCT:-sdk} | |
| export TARGET_RELEASE=${TARGET_RELEASE:-sdk_finalization} | |
| export TARGET_BUILD_VARIANT=${TARGET_BUILD_VARIANT:-userdebug} | |
| # Paths to all projects that this tool (potentially) modifies | |
| declare -a PROJECTS | |
| PROJECTS+=(build/release) | |
| PROJECTS+=(build/soong) | |
| PROJECTS+=(cts) | |
| PROJECTS+=(development) | |
| PROJECTS+=(frameworks/base) | |
| PROJECTS+=(frameworks/libs/modules-utils) | |
| PROJECTS+=(frameworks/opt/net/wifi) | |
| PROJECTS+=(libcore) | |
| PROJECTS+=(packages/apps/Settings) | |
| PROJECTS+=(packages/modules/Permission) | |
| PROJECTS+=(packages/modules/SdkExtensions) | |
| PROJECTS+=(packages/modules/common) | |
| PROJECTS+=(platform_testing) | |
| PROJECTS+=(prebuilts/abi-dumps/ndk) | |
| PROJECTS+=(prebuilts/abi-dumps/platform) | |
| PROJECTS+=(prebuilts/sdk) | |
| PROJECTS+=(tools/platform-compat) | |
| PROJECTS+=(vendor/google/release) | |
| PROJECTS+=(vendor/google_shared/build/release) | |
| PROJECTS+=($(cd $TOP && find prebuilts/module_sdk -mindepth 1 -maxdepth 1 -type d)) | |
| for project in $(cd $BUNDLED_PATCHES && find * -type f | xargs dirname | sort -u); do | |
| if [[ ! " ${PROJECTS[*]} " =~ " ${project} " ]]; then | |
| error "$project has bundled patches but is not part of PROJECTS" | |
| exit 1 | |
| fi | |
| done | |
| # Define the m function to run the build. | |
| function m() { | |
| "$TOP/build/soong/soong_ui.bash" --make-mode \ | |
| "TARGET_PRODUCT=$TARGET_PRODUCT" \ | |
| "TARGET_RELEASE=$TARGET_RELEASE" \ | |
| "TARGET_BUILD_VARIANT=$TARGET_BUILD_VARIANT" \ | |
| "$@" | |
| } | |
| function git() { | |
| if [[ $RUNNING_ON_BUILD_SERVER ]]; then | |
| $(which git) \ | |
| -c init.defaultBranch=main \ | |
| -c user.email=buildbot@google.com \ | |
| -c user.name=BuildBot \ | |
| "$@" | |
| else | |
| $(which git) "$@" | |
| fi | |
| } | |
| # Create a git commit. | |
| # | |
| # Will create the topic $BRANCH and add all modified files in a given project | |
| # before committing the changes. | |
| # | |
| # $1: project path relative to $TOP | |
| # stdin: commit message | |
| function git_commit() { | |
| local project="$1" | |
| pushd "$TOP/$project" | |
| if [[ "$(git branch --show-current)" != "$BRANCH" ]]; then | |
| git checkout -b "$BRANCH" goog/main | |
| fi | |
| git add . | |
| git commit -F - | |
| popd | |
| } | |
| # Fetch patches from a completed job on the build server | |
| # | |
| # $1: path to directory in which to store the patch files | |
| # $2: build target on the build server, e.g. finalize-ndk | |
| # $3: (optional) build server job ID; if not provided defaults to the latest | |
| # known good build | |
| function download_patches_to_patchdir() { | |
| local patch_dir="$1" | |
| local build_server_target="$2" | |
| local build_server_id="$3" | |
| if [[ -z "$build_server_id" ]]; then | |
| build_server_id="$(/google/bin/releases/android/ab/ab.par \ | |
| green_cl \ | |
| --branch git_main-sdk_finalization-release \ | |
| --target $build_server_target \ | |
| --custom_raw_format='{o[buildId]}')" | |
| fi | |
| mkdir -p $patch_dir | |
| pushd "$patch_dir" | |
| /google/bin/releases/android/fetch_artifact/fetch_artifact.par \ | |
| --parallelism 8 \ | |
| --preserve_directory_structure \ | |
| --bid $build_server_id \ | |
| --target $build_server_target \ | |
| 'patches/**/*' | |
| popd | |
| } | |
| # Traverse the Android tree and create patch files for all commits on the topic | |
| # $BRANCH. | |
| # | |
| # $1: path to directory in which to store the patch files | |
| function format_patches_into_patchdir() { | |
| local patch_dir="$1" | |
| mkdir -p $patch_dir | |
| for project in "${PROJECTS[@]}"; do | |
| pushd "$TOP/$project" | |
| if [[ "$(git branch --show-current)" == "$BRANCH" ]]; then | |
| mkdir -p "$patch_dir/$project" | |
| git format-patch -o "$patch_dir/$project" "$BRANCH" ^goog/main | |
| fi | |
| popd | |
| done | |
| } | |
| # Create the topic $BRANCH and apply patches to a given project | |
| # | |
| # $1: project path relative to $TOP | |
| # $2, ...: paths to patch files to apply | |
| function apply_patches() { | |
| local project="$1" | |
| shift | |
| pushd "$TOP/$project" | |
| if [[ "$(git branch --show-current)" != "$BRANCH" ]]; then | |
| if [[ $RUNNING_ON_BUILD_SERVER ]]; then | |
| git checkout -b "$BRANCH" | |
| else | |
| git checkout -b "$BRANCH" goog/main | |
| fi | |
| fi | |
| git am --whitespace=nowarn $* | |
| if [[ ! $RUNNING_ON_BUILD_SERVER ]]; then | |
| # the CLs were presumably downloaded from the build server; claim | |
| # ownership of them to be able to upload them to gerrit | |
| git rebase --exec 'git commit --amend --reset-author -C HEAD' goog/main | |
| fi | |
| popd | |
| } | |
| # Create the topic $BRANCH and apply patches to the Android tree | |
| # | |
| # The patches are expected to have been created by | |
| # format_patches_into_patchdir. | |
| # | |
| # $1: path to directory of patches | |
| function apply_patches_from_patchdir() { | |
| local patch_dir="$1" | |
| if [[ ! -d "$patch_dir" ]]; then | |
| info "nothing to do: patch directory does not exist: $patch_dir" | |
| return | |
| fi | |
| if [[ $(find "$patch_dir" -type f | wc -l) -eq 0 ]]; then | |
| info "nothing to do: no patches found in $patch_dir" | |
| return | |
| fi | |
| for absolute_project_path in $(find "$patch_dir" -type f | xargs dirname | sort -u); do | |
| relative_project_path="${absolute_project_path#$patch_dir/}" | |
| # Hack to remove stray patches/ folder. NOOP if there isn't one | |
| project="${relative_project_path#patches/}" | |
| apply_patches \ | |
| "$project" \ | |
| $(find "$absolute_project_path" -name "*.patch" | sort) | |
| done | |
| } | |
| # Prepare the Android tree for git write operations (needed if running on the | |
| # build server) | |
| # | |
| # The build servers use read-only git worktrees. Replace these with local git | |
| # repositories to allow the creation of git commits. | |
| function setup_build_server() { | |
| if [[ ! $RUNNING_ON_BUILD_SERVER ]]; then | |
| error "setup_build_server should only be called when running on a build server" | |
| exit 1 | |
| fi | |
| echo "== Build server debug info start ==" | |
| # Temporary debug info | |
| find --version | |
| local prebuilt_cached="$TOP/out/prebuilt_cached" | |
| if [[ -d "${prebuilt_cached}" ]]; then | |
| pushd ${prebuilt_cached} | |
| echo "These are the files passed in from previous builds:" | |
| find $(pwd) -type f | |
| popd | |
| else | |
| echo "No prebuilt_cached directory found at: ${prebuilt_cached}" | |
| fi | |
| # Might as well dump this | |
| env | |
| echo "== Build server debug info end ==" | |
| for project in "${PROJECTS[@]}"; do | |
| pushd "$TOP/$project" | |
| rm .git # regular file when using git worktrees | |
| git init | |
| git add . | |
| git commit --quiet --allow-empty -m "base commit" | |
| git tag goog/main | |
| popd | |
| done | |
| } | |
| # Function to get the project directory based on the release config. | |
| # | |
| # $1: the release config | |
| # @return {int} 0 on success, 1 on error. | |
| function get_project_for_release() { | |
| local release_config="$1" | |
| local project="" | |
| case "$release_config" in | |
| "next" | "trunk") | |
| project="vendor/google_shared/build/release" | |
| ;; | |
| "trunk_staging") | |
| project="build/release" | |
| ;; | |
| "sdk_finalization") | |
| project="vendor/google/release" | |
| ;; | |
| *) | |
| echo "Error: Unexpected release config '$release_config'" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| echo "$project" | |
| return 0 | |
| } | |
| # Set build flags for the given release config. | |
| # | |
| # $1: the release config | |
| # $2, ...: KEY=VALUE pairs of build flags and the value to assign them | |
| function set_build_flags() { | |
| local release_config="$1" | |
| shift | |
| local project=$(get_project_for_release "$release_config") | |
| build-flag --quiet --release=$release_config set --dir $project $@ | |
| git_commit $project <<EOF | |
| $release_config: update SDK related build flag(s) | |
| Bug: $BUG | |
| Test: N/A | |
| Flag: NONE platform SDK finalization | |
| EOF | |
| } | |
| # Temporarily set RELEASE_PLATFORM_PROSPECTIVE_SDK_VERSION_FULL. Caller is | |
| # expected to call clear_prospective_sdk_version_full before exiting. | |
| function set_prospective_sdk_version_full() { | |
| local value="$1" | |
| cat > $TOP/vendor/google_shared/build/release/flag_values/cp2a/RELEASE_PLATFORM_PROSPECTIVE_SDK_VERSION_FULL.textproto <<EOF | |
| name: "RELEASE_PLATFORM_PROSPECTIVE_SDK_VERSION_FULL" | |
| value: { | |
| string_value: "$value" | |
| } | |
| EOF | |
| trap "rm -f $TOP/vendor/google_shared/build/release/flag_values/cp2a/RELEASE_PLATFORM_PROSPECTIVE_SDK_VERSION_FULL.textproto" EXIT | |
| } | |
| # Unset RELEASE_PLATFORM_PROSPECTIVE_SDK_VERSION_FULL | |
| function clear_prospective_sdk_version_full() { | |
| # (build-flag doesn't allow unsetting flag values, so remove it explicitly. FIXME: hard-codes cp2a as next) | |
| rm -f $TOP/vendor/google_shared/build/release/flag_values/cp2a/RELEASE_PLATFORM_PROSPECTIVE_SDK_VERSION_FULL.textproto | |
| } |