blob: 85c3e8e349e850ce54e62373d7767883e6220712 [file]
#!/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
}