284 lines
14 KiB
Bash
Executable File
284 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Linux producer for the particle Android AAR and RN package handoff bundle.
|
|
# The generated artifacts are intentionally ignored by Git.
|
|
set -euo pipefail
|
|
|
|
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
readonly REPOSITORY_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
|
|
readonly ANDROID_ROOT="${REPOSITORY_ROOT}/platform/android"
|
|
readonly RN_PACKAGE_ROOT="${REPOSITORY_ROOT}/packages/maplibre-particle-rn"
|
|
readonly ARTIFACT_ROOT="${REPOSITORY_ROOT}/artifacts/maplibre-particle"
|
|
readonly THROTTLE_RUNNER="${BUILD_OUTPUT_THROTTLE:-/root/.codex/skills/codex-build-output-throttle/build-output-throttle/scripts/run_limited.py}"
|
|
readonly MAVEN_REPOSITORY="${MAVEN_LOCAL_REPOSITORY:-${HOME}/.m2/repository}"
|
|
readonly GROUP_PATH="cc/navsea/maplibre"
|
|
readonly ARTIFACT_ID="android-sdk-particle-opengl"
|
|
readonly EXPECTED_RENDERER="opengl"
|
|
readonly EXPECTED_ABIS="armeabi-v7a,arm64-v8a,x86,x86_64"
|
|
|
|
reuse_existing=false
|
|
preflight_only=false
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: tools/particle_android_pipeline/linux_build_publish.sh [--reuse-existing] [--preflight]
|
|
|
|
Default: build four-ABI OpenGL Release AAR, publish it to Maven Local, typecheck the
|
|
RN package, pack the npm tarball, and write artifacts/maplibre-particle/<version>/build-manifest.json.
|
|
|
|
--reuse-existing Do not compile or publish. Require a complete matching Maven Local
|
|
publication and use it to create a handoff bundle. This is for a
|
|
previously completed native build, never a substitute for a release build.
|
|
--preflight Perform only fail-closed version/SDK/tooling checks. No build, publish,
|
|
package, or artifact write occurs.
|
|
USAGE
|
|
}
|
|
|
|
die() {
|
|
printf 'PIPELINE_ERROR: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
note() {
|
|
printf 'PIPELINE: %s\n' "$*"
|
|
}
|
|
|
|
require_command() {
|
|
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
|
|
}
|
|
|
|
sha256_file() {
|
|
sha256sum "$1" | awk '{print $1}'
|
|
}
|
|
|
|
file_bytes() {
|
|
stat -c '%s' "$1"
|
|
}
|
|
|
|
while (($#)); do
|
|
case "$1" in
|
|
--reuse-existing) reuse_existing=true ;;
|
|
--preflight) preflight_only=true ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) die "unknown argument: $1" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
[[ "$preflight_only" == false || "$reuse_existing" == false ]] || die "--preflight and --reuse-existing cannot be combined"
|
|
[[ -d "$ANDROID_ROOT" ]] || die "Android project is missing: $ANDROID_ROOT"
|
|
[[ -d "$RN_PACKAGE_ROOT" ]] || die "RN package is missing: $RN_PACKAGE_ROOT"
|
|
[[ -x "$ANDROID_ROOT/gradlew" ]] || die "Gradle wrapper is missing or not executable"
|
|
[[ -f "$THROTTLE_RUNNER" ]] || die "build-output-throttle runner not found: $THROTTLE_RUNNER (set BUILD_OUTPUT_THROTTLE to its run_limited.py path)"
|
|
|
|
require_command git
|
|
require_command node
|
|
require_command npm
|
|
require_command python3
|
|
require_command sha256sum
|
|
require_command tar
|
|
require_command stat
|
|
|
|
native_version="$(tr -d '\r\n' < "$ANDROID_ROOT/VERSION")"
|
|
rn_version="$(node -p "require(process.argv[1]).version" "$RN_PACKAGE_ROOT/package.json")"
|
|
[[ -n "$native_version" ]] || die "platform/android/VERSION is empty"
|
|
[[ "$native_version" == "$rn_version" ]] || die "version mismatch: native=$native_version rn=$rn_version; update both explicitly before running the pipeline"
|
|
[[ "$native_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-particle\.[0-9]+$ ]] || die "unsupported particle version format: $native_version"
|
|
|
|
git_head="$(git -C "$REPOSITORY_ROOT" rev-parse HEAD)"
|
|
git_branch="$(git -C "$REPOSITORY_ROOT" branch --show-current)"
|
|
[[ -n "$git_branch" ]] || die "detached HEAD is not a releasable pipeline input"
|
|
dirty_count="$(git -C "$REPOSITORY_ROOT" status --porcelain=v1 | wc -l | tr -d ' ')"
|
|
if [[ "$dirty_count" == 0 ]]; then
|
|
dirty_state="clean"
|
|
else
|
|
dirty_state="dirty"
|
|
fi
|
|
note "gitHead=$git_head branch=$git_branch dirty=$dirty_state entries=$dirty_count (reported only; no cleanup is performed)"
|
|
note "version=$native_version renderer=$EXPECTED_RENDERER abis=$EXPECTED_ABIS"
|
|
|
|
resolve_android_sdk() {
|
|
local candidate=""
|
|
for candidate in "${ANDROID_HOME:-}" "${ANDROID_SDK_ROOT:-}"; do
|
|
if [[ -n "$candidate" && -d "$candidate" ]]; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
local properties_file="$ANDROID_ROOT/local.properties"
|
|
if [[ -f "$properties_file" ]]; then
|
|
candidate="$(sed -n 's@^[[:space:]]*sdk\.dir[[:space:]]*=[[:space:]]*@@p' "$properties_file" | tail -n 1)"
|
|
candidate="${candidate//\\:/:}"
|
|
candidate="${candidate//\\\\/\\}"
|
|
if [[ -n "$candidate" && -d "$candidate" ]]; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
fi
|
|
die "Android SDK cannot be resolved: set ANDROID_HOME or ANDROID_SDK_ROOT, or configure $properties_file"
|
|
}
|
|
|
|
android_sdk="$(resolve_android_sdk)"
|
|
note "androidSdk=$android_sdk"
|
|
|
|
ensure_untracked_sdk_properties() {
|
|
local target="$1"
|
|
local parent
|
|
parent="$(dirname "$target")"
|
|
[[ -d "$parent" ]] || return 0
|
|
if git -C "$REPOSITORY_ROOT" ls-files --error-unmatch "${target#"$REPOSITORY_ROOT/"}" >/dev/null 2>&1; then
|
|
die "refusing to write tracked machine-specific SDK configuration: $target"
|
|
fi
|
|
if [[ -f "$target" ]] && ! git -C "$REPOSITORY_ROOT" check-ignore -q "${target#"$REPOSITORY_ROOT/"}"; then
|
|
die "refusing to overwrite non-ignored SDK configuration: $target"
|
|
fi
|
|
mkdir -p "$parent"
|
|
printf 'sdk.dir=%s\n' "${android_sdk//\\/\\\\}" > "$target"
|
|
note "ensured ignored SDK configuration: $target"
|
|
}
|
|
|
|
if [[ "$preflight_only" == false ]]; then
|
|
# Generated Test Host Android projects may be present on Linux. Do not create a host
|
|
# Android tree merely for this setting and never write a tracked local.properties.
|
|
ensure_untracked_sdk_properties "$ANDROID_ROOT/local.properties"
|
|
ensure_untracked_sdk_properties "$REPOSITORY_ROOT/test/android/local.properties"
|
|
ensure_untracked_sdk_properties "$REPOSITORY_ROOT/integration/react-native/MapLibreParticleRNTestHost/android/local.properties"
|
|
fi
|
|
|
|
publication_dir="$MAVEN_REPOSITORY/$GROUP_PATH/$ARTIFACT_ID/$native_version"
|
|
readonly AAR_NAME="$ARTIFACT_ID-$native_version.aar"
|
|
readonly POM_NAME="$ARTIFACT_ID-$native_version.pom"
|
|
readonly MODULE_NAME="$ARTIFACT_ID-$native_version.module"
|
|
aar_source="$publication_dir/$AAR_NAME"
|
|
pom_source="$publication_dir/$POM_NAME"
|
|
module_source="$publication_dir/$MODULE_NAME"
|
|
legacy_publication_dir="$MAVEN_REPOSITORY/$GROUP_PATH/$ARTIFACT_ID/13.4.1-particle.2"
|
|
|
|
if [[ "$preflight_only" == true ]]; then
|
|
note "PRECHECK_PASS version and SDK resolution succeeded; no files were written"
|
|
exit 0
|
|
fi
|
|
|
|
# .2 is a protected historical handoff. Snapshot its three contract files before native work.
|
|
for legacy_file in "$legacy_publication_dir/android-sdk-particle-opengl-13.4.1-particle.2.aar" "$legacy_publication_dir/android-sdk-particle-opengl-13.4.1-particle.2.pom" "$legacy_publication_dir/android-sdk-particle-opengl-13.4.1-particle.2.module"; do
|
|
[[ -f "$legacy_file" ]] || die "protected legacy Maven Local artifact is missing: $legacy_file"
|
|
done
|
|
legacy_before="$(mktemp)"
|
|
trap 'rm -f "$legacy_before"' EXIT
|
|
sha256sum "$legacy_publication_dir"/android-sdk-particle-opengl-13.4.1-particle.2.{aar,pom,module} > "$legacy_before"
|
|
|
|
if [[ "$reuse_existing" == false ]]; then
|
|
note "building four ABI OpenGL Release AAR"
|
|
python3 "$THROTTLE_RUNNER" --log-dir "$REPOSITORY_ROOT/.codex-build-logs/particle_android_pipeline" -- \
|
|
env ANDROID_HOME="$android_sdk" ANDROID_SDK_ROOT="$android_sdk" RENDERER=opengl BUILDTYPE=Release make -C "$ANDROID_ROOT" apackage
|
|
note "publishing OpenGL Release publication to Maven Local"
|
|
python3 "$THROTTLE_RUNNER" --log-dir "$REPOSITORY_ROOT/.codex-build-logs/particle_android_pipeline" -- \
|
|
env ANDROID_HOME="$android_sdk" ANDROID_SDK_ROOT="$android_sdk" "$ANDROID_ROOT/gradlew" -p "$ANDROID_ROOT" -Pmaplibre.abis=all :MapLibreAndroid:publishOpenglreleasePublicationToMavenLocal --console=plain
|
|
else
|
|
note "reuse-existing selected: requiring existing Maven Local publication; native build/publish intentionally skipped"
|
|
fi
|
|
|
|
for artifact_file in "$aar_source" "$pom_source" "$module_source"; do
|
|
[[ -f "$artifact_file" ]] || die "required Maven Local artifact is missing after publication: $artifact_file"
|
|
done
|
|
grep -Fq "<version>$native_version</version>" "$pom_source" || die "POM version does not match current version: $pom_source"
|
|
cmp --silent "$legacy_before" <(sha256sum "$legacy_publication_dir"/android-sdk-particle-opengl-13.4.1-particle.2.{aar,pom,module}) || die "protected 13.4.1-particle.2 Maven Local artifacts changed"
|
|
note "protected 13.4.1-particle.2 Maven Local artifacts remain byte-identical"
|
|
|
|
artifact_dir="$ARTIFACT_ROOT/$native_version"
|
|
[[ ! -e "$artifact_dir/build-manifest.json" ]] || die "refusing to overwrite prior handoff bundle: $artifact_dir (bump the explicit version or move the old bundle aside)"
|
|
mkdir -p "$artifact_dir"
|
|
for existing_file in "$artifact_dir"/*; do
|
|
[[ -e "$existing_file" ]] || continue
|
|
case "$(basename "$existing_file")" in
|
|
"$AAR_NAME"|"$POM_NAME"|"$MODULE_NAME"|"navsea-maplibre-particle-rn-$native_version.tgz") ;;
|
|
*) die "unexpected file in incomplete handoff directory; refusing to mix artifacts: $existing_file" ;;
|
|
esac
|
|
done
|
|
copy_handoff_file() {
|
|
local source="$1"
|
|
local destination="$2"
|
|
if [[ -e "$destination" ]]; then
|
|
cmp --silent "$source" "$destination" || die "refusing to overwrite different partial handoff file: $destination"
|
|
note "resuming matching partial handoff file without overwrite: $destination"
|
|
return 0
|
|
fi
|
|
cp --preserve=mode,timestamps "$source" "$destination"
|
|
}
|
|
copy_handoff_file "$aar_source" "$artifact_dir/$AAR_NAME"
|
|
copy_handoff_file "$pom_source" "$artifact_dir/$POM_NAME"
|
|
copy_handoff_file "$module_source" "$artifact_dir/$MODULE_NAME"
|
|
|
|
note "typechecking RN package"
|
|
python3 "$THROTTLE_RUNNER" --log-dir "$REPOSITORY_ROOT/.codex-build-logs/particle_android_pipeline" -- \
|
|
npx tsc --noEmit -p "$RN_PACKAGE_ROOT/tsconfig.json"
|
|
|
|
expected_tgz="navsea-maplibre-particle-rn-$native_version.tgz"
|
|
[[ ! -e "$artifact_dir/$expected_tgz" ]] || die "refusing to overwrite prior npm tarball: $artifact_dir/$expected_tgz"
|
|
note "packing RN package"
|
|
python3 "$THROTTLE_RUNNER" --log-dir "$REPOSITORY_ROOT/.codex-build-logs/particle_android_pipeline" -- \
|
|
bash -lc 'cd "$1" && npm pack --pack-destination "$2"' -- "$RN_PACKAGE_ROOT" "$artifact_dir"
|
|
[[ -f "$artifact_dir/$expected_tgz" ]] || die "npm pack did not create expected tarball: $artifact_dir/$expected_tgz"
|
|
tgz_version="$(tar -xOf "$artifact_dir/$expected_tgz" package/package.json | node -e 'let input=""; process.stdin.on("data", chunk => input += chunk); process.stdin.on("end", () => process.stdout.write(JSON.parse(input).version));')"
|
|
[[ "$tgz_version" == "$native_version" ]] || die "tarball version mismatch: tgz=$tgz_version native=$native_version"
|
|
|
|
export MANIFEST_PATH="$artifact_dir/build-manifest.json"
|
|
export MANIFEST_GIT_HEAD="$git_head"
|
|
export MANIFEST_BRANCH="$git_branch"
|
|
export MANIFEST_DIRTY_STATE="$dirty_state"
|
|
export MANIFEST_DIRTY_COUNT="$dirty_count"
|
|
export MANIFEST_VERSION="$native_version"
|
|
export MANIFEST_COORDINATE="cc.navsea.maplibre:$ARTIFACT_ID:$native_version"
|
|
export MANIFEST_ABIS="$EXPECTED_ABIS"
|
|
export MANIFEST_RENDERER="$EXPECTED_RENDERER"
|
|
export MANIFEST_NATIVE_RESULT="$([[ "$reuse_existing" == true ]] && printf 'PASS_REUSED_EXISTING_MAVEN_LOCAL' || printf 'PASS_BUILT_AND_PUBLISHED')"
|
|
export MANIFEST_AAR_PATH="$(realpath "$artifact_dir/$AAR_NAME")"
|
|
export MANIFEST_POM_PATH="$(realpath "$artifact_dir/$POM_NAME")"
|
|
export MANIFEST_MODULE_PATH="$(realpath "$artifact_dir/$MODULE_NAME")"
|
|
export MANIFEST_TGZ_PATH="$(realpath "$artifact_dir/$expected_tgz")"
|
|
export MANIFEST_AAR_BYTES="$(file_bytes "$artifact_dir/$AAR_NAME")"
|
|
export MANIFEST_POM_BYTES="$(file_bytes "$artifact_dir/$POM_NAME")"
|
|
export MANIFEST_MODULE_BYTES="$(file_bytes "$artifact_dir/$MODULE_NAME")"
|
|
export MANIFEST_TGZ_BYTES="$(file_bytes "$artifact_dir/$expected_tgz")"
|
|
export MANIFEST_AAR_SHA256="$(sha256_file "$artifact_dir/$AAR_NAME")"
|
|
export MANIFEST_POM_SHA256="$(sha256_file "$artifact_dir/$POM_NAME")"
|
|
export MANIFEST_MODULE_SHA256="$(sha256_file "$artifact_dir/$MODULE_NAME")"
|
|
export MANIFEST_TGZ_SHA256="$(sha256_file "$artifact_dir/$expected_tgz")"
|
|
export MANIFEST_TIMESTAMP="$(date --utc +%Y-%m-%dT%H:%M:%SZ)"
|
|
|
|
node <<'NODE'
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const env = process.env;
|
|
const file = (kind) => ({
|
|
path: env[`MANIFEST_${kind}_PATH`],
|
|
relativePath: path.basename(env[`MANIFEST_${kind}_PATH`]),
|
|
bytes: Number(env[`MANIFEST_${kind}_BYTES`]),
|
|
sha256: env[`MANIFEST_${kind}_SHA256`],
|
|
});
|
|
const manifest = {
|
|
schemaVersion: 1,
|
|
gitHead: env.MANIFEST_GIT_HEAD,
|
|
branch: env.MANIFEST_BRANCH,
|
|
dirtyState: env.MANIFEST_DIRTY_STATE,
|
|
dirtyEntryCount: Number(env.MANIFEST_DIRTY_COUNT),
|
|
rnPackageVersion: env.MANIFEST_VERSION,
|
|
nativeVersion: env.MANIFEST_VERSION,
|
|
mavenCoordinate: env.MANIFEST_COORDINATE,
|
|
renderer: env.MANIFEST_RENDERER,
|
|
abis: env.MANIFEST_ABIS.split(','),
|
|
aar: file('AAR'),
|
|
pom: file('POM'),
|
|
module: file('MODULE'),
|
|
tgz: file('TGZ'),
|
|
buildTimestampUtc: env.MANIFEST_TIMESTAMP,
|
|
linuxValidationResult: env.MANIFEST_NATIVE_RESULT,
|
|
};
|
|
fs.writeFileSync(env.MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
NODE
|
|
|
|
note "AAR bytes=$(file_bytes "$artifact_dir/$AAR_NAME") sha256=$(sha256_file "$artifact_dir/$AAR_NAME") path=$(realpath "$artifact_dir/$AAR_NAME")"
|
|
note "TGZ bytes=$(file_bytes "$artifact_dir/$expected_tgz") sha256=$(sha256_file "$artifact_dir/$expected_tgz") path=$(realpath "$artifact_dir/$expected_tgz")"
|
|
note "manifest=$(realpath "$artifact_dir/build-manifest.json")"
|
|
note "PIPELINE_PASS linuxValidationResult=$MANIFEST_NATIVE_RESULT"
|