mirror of
git://f0xx.org/ac/ac-scripts
synced 2026-07-29 01:38:08 +03:00
369 lines
12 KiB
Bash
Executable File
369 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Host wrapper: build CI image + run pipeline with log capture.
|
|
#
|
|
# Usage:
|
|
# ./scripts/docker-build-runner.sh --build-id 42 --workdir /path/to/repo
|
|
#
|
|
# Env: see ci-build-and-publish-ota.sh
|
|
# When invoked from PHP (BUILD_LOG_STDOUT_ONLY=1), log via stdout only — parent redirects to build.log.
|
|
#
|
|
# Parallel builds: set BUILD_ISOLATE_SOURCE=1 (default from builder) to shallow-clone into
|
|
# $OUT_DIR/src instead of mutating the shared deploy checkout.
|
|
# Per-build step logs: $OUT_DIR/docker-build.log, $OUT_DIR/docker-run.log (also echoed to build.log).
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
IMAGE="${ANDROIDCAST_CI_IMAGE:-androidcast-ci:latest}"
|
|
CI_VERSION="${ANDROIDCAST_CI_VERSION:-00.01.00.1000}"
|
|
BUILD_ID="${BUILD_ID:-local}"
|
|
LOG_FILE="${BUILD_LOG_FILE:-${ROOT}/out/builds/${BUILD_ID}/build.log}"
|
|
WORK_DIR="${BUILD_WORK_DIR:-${ROOT}}"
|
|
OUT_DIR="${BUILD_OUT_DIR:-${WORK_DIR}/out/builds/${BUILD_ID}}"
|
|
LOG_STDOUT_ONLY="${BUILD_LOG_STDOUT_ONLY:-0}"
|
|
ISOLATE_SOURCE="${BUILD_ISOLATE_SOURCE:-1}"
|
|
CLONE_DEPTH="${BUILD_GIT_DEPTH:-1}"
|
|
MOBILE_SUBDIR="${BUILD_MOBILE_SUBDIR:-ac-mobile-android}"
|
|
HOST_SCRIPTS_DIR="${BUILD_SCRIPTS_DIR:-${SCRIPTS_DIR}}"
|
|
DOCKERFILE_PATH="${BUILD_DOCKERFILE:-}"
|
|
SOURCE_DIR="${OUT_DIR}/src"
|
|
DOCKER_WORKSPACE="${WORK_DIR}"
|
|
CONTAINER_NAME="androidcast-bld-${BUILD_ID}"
|
|
RESUME_FROM="${BUILD_RESUME_FROM:-}"
|
|
REUSE_SRC_DIR="${BUILD_REUSE_SRC_DIR:-}"
|
|
SSH_DEBUG="${BUILD_SSH_DEBUG:-0}"
|
|
SSH_DEBUG_SECONDS="${BUILD_SSH_DEBUG_SECONDS:-7200}"
|
|
|
|
mkdir -p "$(dirname "$LOG_FILE")" "$OUT_DIR"
|
|
|
|
log() {
|
|
if [[ "$LOG_STDOUT_ONLY" == "1" ]]; then
|
|
echo "$@"
|
|
else
|
|
echo "$@" | tee -a "$LOG_FILE"
|
|
fi
|
|
}
|
|
|
|
run_logged() {
|
|
if [[ "$LOG_STDOUT_ONLY" == "1" ]]; then
|
|
"$@"
|
|
else
|
|
"$@" 2>&1 | tee -a "$LOG_FILE"
|
|
fi
|
|
}
|
|
|
|
# Mirror command output to build.log (stdout) and a per-build step file for parallel debugging.
|
|
run_step() {
|
|
local step_name="$1"
|
|
shift
|
|
local step_log="${OUT_DIR}/${step_name}.log"
|
|
log "[runner] step ${step_name} -> ${step_log}"
|
|
"$@" 2>&1 | tee -a "${step_log}"
|
|
}
|
|
|
|
on_runner_err() {
|
|
local ec=$?
|
|
log "[runner] ERROR: command failed (exit ${ec}) at ${BASH_SOURCE[0]}:${LINENO}: ${BASH_COMMAND}"
|
|
if command -v docker >/dev/null 2>&1; then
|
|
if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "${CONTAINER_NAME}"; then
|
|
log "[runner] docker logs ${CONTAINER_NAME} (last 100 lines):"
|
|
docker logs --tail 100 "${CONTAINER_NAME}" 2>&1 | while IFS= read -r line; do log "$line"; done
|
|
fi
|
|
fi
|
|
echo "${ec}" > "${OUT_DIR}/runner.exit"
|
|
exit "${ec}"
|
|
}
|
|
|
|
on_runner_exit() {
|
|
local ec=$?
|
|
echo "${ec}" > "${OUT_DIR}/runner.exit"
|
|
}
|
|
|
|
trap on_runner_err ERR
|
|
trap on_runner_exit EXIT
|
|
|
|
# Git 2.35+ refuses repos owned by another uid (PHP-FPM nginx vs deploy user).
|
|
git_safe() {
|
|
git -c safe.directory="${WORK_DIR}" "$@"
|
|
}
|
|
|
|
resolve_git_remote() {
|
|
if [[ -n "${GIT_REMOTE:-}" ]]; then
|
|
echo "$GIT_REMOTE"
|
|
return
|
|
fi
|
|
if [[ -d "${WORK_DIR}/.git" ]]; then
|
|
git_safe -C "${WORK_DIR}" remote get-url origin 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
host_mobile_tree() {
|
|
if [[ -d "${WORK_DIR}/${MOBILE_SUBDIR}/app" ]]; then
|
|
echo "${WORK_DIR}/${MOBILE_SUBDIR}"
|
|
elif [[ -d "${WORK_DIR}/app" ]]; then
|
|
echo "${WORK_DIR}"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
isolated_mobile_tree() {
|
|
local base="$1"
|
|
if [[ -d "${base}/${MOBILE_SUBDIR}/app" ]]; then
|
|
echo "${base}/${MOBILE_SUBDIR}"
|
|
elif [[ -d "${base}/app" ]]; then
|
|
echo "${base}"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
third_party_incomplete() {
|
|
local tree="$1"
|
|
local p="$2"
|
|
local dir="${tree}/third-party/${p}"
|
|
if [[ ! -d "$dir" ]]; then
|
|
return 0
|
|
fi
|
|
case "$p" in
|
|
wireguard-android)
|
|
[[ ! -d "${dir}/tunnel/src/main/java" ]]
|
|
;;
|
|
libvpx)
|
|
[[ ! -d "${dir}/vp8" && ! -f "${dir}/configure" ]]
|
|
;;
|
|
opus|speex)
|
|
[[ ! -f "${dir}/configure" && ! -f "${dir}/CMakeLists.txt" && ! -d "${dir}/include" ]]
|
|
;;
|
|
*)
|
|
[[ -z "$(ls -A "${dir}" 2>/dev/null)" ]]
|
|
;;
|
|
esac
|
|
}
|
|
|
|
seed_third_party_from_host() {
|
|
local host_tree="$1"
|
|
local iso_tree="$2"
|
|
if [[ -z "$host_tree" || -z "$iso_tree" || ! -d "$host_tree/third-party" ]]; then
|
|
return 0
|
|
fi
|
|
mkdir -p "${iso_tree}/third-party"
|
|
local p
|
|
for p in libvpx opus speex wireguard-android; do
|
|
if [[ -d "${host_tree}/third-party/${p}" ]] && third_party_incomplete "$iso_tree" "$p"; then
|
|
log "[runner] seed third-party/${p} from host checkout"
|
|
rm -rf "${iso_tree}/third-party/${p}"
|
|
cp -r "${host_tree}/third-party/${p}" "${iso_tree}/third-party/"
|
|
fi
|
|
done
|
|
}
|
|
|
|
stage_ci_scripts() {
|
|
local iso_tree="$1"
|
|
if [[ -z "$iso_tree" || ! -d "$HOST_SCRIPTS_DIR" ]]; then
|
|
return 0
|
|
fi
|
|
mkdir -p "${iso_tree}/scripts"
|
|
local f
|
|
for f in ci-build-and-publish-ota.sh init-third-party-submodules.sh build-native-codecs.sh android-ndk.sh generate-ota-v0.sh native-build-cache.sh; do
|
|
if [[ -f "${HOST_SCRIPTS_DIR}/${f}" ]]; then
|
|
cp -f "${HOST_SCRIPTS_DIR}/${f}" "${iso_tree}/scripts/${f}"
|
|
chmod +x "${iso_tree}/scripts/${f}" 2>/dev/null || true
|
|
fi
|
|
done
|
|
}
|
|
|
|
init_mobile_submodule_only() {
|
|
local base="$1"
|
|
if [[ ! -d "${base}/.git" ]]; then
|
|
return 0
|
|
fi
|
|
if git -C "$base" config -f .gitmodules --get "submodule.${MOBILE_SUBDIR}.path" >/dev/null 2>&1; then
|
|
log "[runner] isolate: init workspace submodule ${MOBILE_SUBDIR} (no third-party recurse)"
|
|
run_step git-submodule-mobile git -c safe.directory="${base}" -C "${base}" submodule update --init "${MOBILE_SUBDIR}"
|
|
fi
|
|
}
|
|
|
|
prepare_isolated_source() {
|
|
local ref="${GIT_REF:-}"
|
|
local remote sha
|
|
remote="$(resolve_git_remote)"
|
|
if [[ -z "$ref" || -z "$remote" ]]; then
|
|
log "[runner] isolate: skip (need GIT_REF and origin remote)"
|
|
return 0
|
|
fi
|
|
|
|
rm -rf "${SOURCE_DIR}"
|
|
mkdir -p "${SOURCE_DIR}"
|
|
|
|
log "[runner] isolate: shallow clone depth=${CLONE_DEPTH} ref=${ref} remote=*** -> ${SOURCE_DIR}"
|
|
if git clone --depth "${CLONE_DEPTH}" \
|
|
--single-branch --branch "${ref}" "${remote}" "${SOURCE_DIR}"; then
|
|
:
|
|
else
|
|
log "[runner] isolate: branch clone failed; clone default branch then checkout ${ref}"
|
|
run_step git-clone git clone --depth "${CLONE_DEPTH}" "${remote}" "${SOURCE_DIR}"
|
|
run_step git-fetch git -c safe.directory="${SOURCE_DIR}" -C "${SOURCE_DIR}" fetch --depth "${CLONE_DEPTH}" origin "${ref}"
|
|
run_step git-checkout git -c safe.directory="${SOURCE_DIR}" -C "${SOURCE_DIR}" checkout "${ref}"
|
|
fi
|
|
|
|
if [[ -n "${GIT_SHA:-}" ]]; then
|
|
log "[runner] isolate: checkout pin GIT_SHA=${GIT_SHA}"
|
|
run_step git-pin git -c safe.directory="${SOURCE_DIR}" -C "${SOURCE_DIR}" checkout "${GIT_SHA}"
|
|
fi
|
|
|
|
init_mobile_submodule_only "${SOURCE_DIR}"
|
|
|
|
local host_tree iso_tree
|
|
host_tree="$(host_mobile_tree)"
|
|
iso_tree="$(isolated_mobile_tree "${SOURCE_DIR}")"
|
|
if [[ -n "$iso_tree" ]]; then
|
|
seed_third_party_from_host "$host_tree" "$iso_tree"
|
|
stage_ci_scripts "$iso_tree"
|
|
DOCKER_WORKSPACE="${iso_tree}"
|
|
else
|
|
DOCKER_WORKSPACE="${SOURCE_DIR}"
|
|
fi
|
|
|
|
run_step git-rev-parse git -c safe.directory="${SOURCE_DIR}" -C "${SOURCE_DIR}" rev-parse HEAD
|
|
}
|
|
|
|
sync_shared_checkout() {
|
|
if [[ -z "${GIT_REF:-}" ]] || [[ ! -d "${WORK_DIR}/.git" ]]; then
|
|
return 0
|
|
fi
|
|
log "[runner] syncing shared checkout ref=${GIT_REF}"
|
|
run_logged git_safe -C "${WORK_DIR}" fetch origin --prune || true
|
|
run_logged git_safe -C "${WORK_DIR}" checkout "${GIT_REF}"
|
|
run_logged git_safe -C "${WORK_DIR}" pull --rebase origin "${GIT_REF}" || true
|
|
if [[ -n "${GIT_SHA:-}" ]]; then
|
|
run_logged git_safe -C "${WORK_DIR}" checkout "${GIT_SHA}"
|
|
fi
|
|
run_logged git_safe -C "${WORK_DIR}" rev-parse HEAD
|
|
}
|
|
|
|
should_skip_git() {
|
|
[[ "$RESUME_FROM" == "docker-build" || "$RESUME_FROM" == "docker-run" ]]
|
|
}
|
|
|
|
should_skip_docker_build() {
|
|
[[ "$RESUME_FROM" == "docker-run" ]] && docker image inspect "$IMAGE" >/dev/null 2>&1
|
|
}
|
|
|
|
reuse_source_tree() {
|
|
if [[ -n "$REUSE_SRC_DIR" && -d "${REUSE_SRC_DIR}/.git" ]]; then
|
|
log "[runner] reuse: source tree from ${REUSE_SRC_DIR}"
|
|
rm -rf "${SOURCE_DIR}"
|
|
mkdir -p "${SOURCE_DIR}"
|
|
cp -a "${REUSE_SRC_DIR}/." "${SOURCE_DIR}/"
|
|
DOCKER_WORKSPACE="${SOURCE_DIR}"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
run_git_step() {
|
|
if [[ "$ISOLATE_SOURCE" == "1" ]]; then
|
|
prepare_isolated_source
|
|
else
|
|
sync_shared_checkout
|
|
DOCKER_WORKSPACE="${WORK_DIR}"
|
|
fi
|
|
}
|
|
|
|
start_ssh_debug_container() {
|
|
log "[runner] SSH debug: starting container ${CONTAINER_NAME} (${SSH_DEBUG_SECONDS}s)"
|
|
docker rm -f "${CONTAINER_NAME}" 2>/dev/null || true
|
|
run_step docker-run-ssh docker run -d --name "${CONTAINER_NAME}" \
|
|
-v "${DOCKER_WORKSPACE}:/workspace" \
|
|
-v "${ROOT}/out:/workspace/out" \
|
|
-w /workspace \
|
|
-e BUILD_ID="${BUILD_ID}" \
|
|
-e OUT_DIR="/workspace/out/builds/${BUILD_ID}" \
|
|
"$IMAGE" \
|
|
sleep "${SSH_DEBUG_SECONDS}"
|
|
printf '%s\n' 'ssh_debug' > "${OUT_DIR}/runner.mode"
|
|
local host
|
|
host="$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo builder)"
|
|
log "[runner] SSH debug: session open on ${host}"
|
|
log "[runner] SSH debug: ssh <you>@${host}"
|
|
log "[runner] SSH debug: docker exec -it ${CONTAINER_NAME} bash"
|
|
log "[runner] SSH debug: workspace is /workspace inside the container"
|
|
log "[runner] finished build_id=${BUILD_ID} (ssh debug — stop build or wait ${SSH_DEBUG_SECONDS}s)"
|
|
}
|
|
|
|
log "[runner] image=${IMAGE} ci_version=${CI_VERSION} build_id=${BUILD_ID} container=${CONTAINER_NAME}"
|
|
log "[runner] workdir=${WORK_DIR} out=${OUT_DIR} isolate=${ISOLATE_SOURCE} resume=${RESUME_FROM:-full}"
|
|
|
|
if should_skip_git; then
|
|
if reuse_source_tree; then
|
|
log "[runner] resume: skipped git (reused source)"
|
|
elif [[ "$ISOLATE_SOURCE" != "1" && -d "${WORK_DIR}/.git" ]]; then
|
|
DOCKER_WORKSPACE="${WORK_DIR}"
|
|
log "[runner] resume: skipped git (shared workdir ${WORK_DIR})"
|
|
else
|
|
log "[runner] resume: no reusable source; running git step"
|
|
run_git_step
|
|
fi
|
|
else
|
|
run_git_step
|
|
fi
|
|
|
|
log "[runner] docker workspace=${DOCKER_WORKSPACE}"
|
|
|
|
if should_skip_docker_build; then
|
|
log "[runner] resume: skipped docker-build (image ${IMAGE} exists)"
|
|
else
|
|
dockerfile="${DOCKERFILE_PATH}"
|
|
if [[ -z "$dockerfile" || ! -f "$dockerfile" ]]; then
|
|
for candidate in \
|
|
"${HOST_SCRIPTS_DIR}/Dockerfile.androidcast-ci" \
|
|
"${DOCKER_WORKSPACE}/Dockerfile" \
|
|
"${ROOT}/Dockerfile"; do
|
|
if [[ -f "$candidate" ]]; then
|
|
dockerfile="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
if [[ -z "$dockerfile" || ! -f "$dockerfile" ]]; then
|
|
log "[runner] ERROR: CI Dockerfile not found (set BUILD_DOCKERFILE or add Dockerfile.androidcast-ci under ac-scripts)"
|
|
exit 1
|
|
fi
|
|
docker_ctx="${DOCKER_WORKSPACE}"
|
|
if [[ "$(basename "$dockerfile")" == "Dockerfile.androidcast-ci" ]]; then
|
|
docker_ctx="$(dirname "$dockerfile")"
|
|
fi
|
|
run_step docker-build docker build \
|
|
--build-arg ANDROIDCAST_CI_VERSION="${CI_VERSION}" \
|
|
-t "$IMAGE" \
|
|
-f "${dockerfile}" \
|
|
"${docker_ctx}"
|
|
fi
|
|
|
|
if [[ "$SSH_DEBUG" == "1" ]]; then
|
|
start_ssh_debug_container
|
|
exit 0
|
|
fi
|
|
|
|
run_step docker-run docker run --rm --name "${CONTAINER_NAME}" \
|
|
--network host \
|
|
-v "${DOCKER_WORKSPACE}:/workspace" \
|
|
-v "${ROOT}/out:/workspace/out" \
|
|
-w /workspace \
|
|
-e BUILD_ID="${BUILD_ID}" \
|
|
-e OUT_DIR="/workspace/out/builds/${BUILD_ID}" \
|
|
-e OTA_BASE_URL="${OTA_BASE_URL:-}" \
|
|
-e OTA_CHANNEL="${OTA_CHANNEL:-staging}" \
|
|
-e GRADLE_TASK="${GRADLE_TASK:-assembleDebug}" \
|
|
-e RUN_UNIT_TESTS="${RUN_UNIT_TESTS:-1}" \
|
|
-e RUN_NATIVE="${RUN_NATIVE:-1}" \
|
|
-e AUTO_OTA="${AUTO_OTA:-0}" \
|
|
-e GIT_REF="${GIT_REF:-}" \
|
|
-e GIT_SHA="${GIT_SHA:-}" \
|
|
-e RELEASE_NOTES="${RELEASE_NOTES:-}" \
|
|
"$IMAGE" \
|
|
./scripts/ci-build-and-publish-ota.sh
|
|
|
|
log "[runner] finished build_id=${BUILD_ID}"
|