Skip to content

ci: stabilize branch cache workflow #28

ci: stabilize branch cache workflow

ci: stabilize branch cache workflow #28

name: Build TensorFlow Android ARM64 JNI
on:
workflow_dispatch:
push:
branches:
- main
- codex/fix-android-arm64-build
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 360
env:
TFJAVA_COMMIT: c065b70c05acefe703fee21a156561e76e54c66d
ANDROID_PLATFORM: android-arm64
ANDROID_API_LEVEL: "29"
ANDROID_BUILD_TOOLS_VERSION: "29.0.3"
ANDROID_NDK_API_LEVEL: "21"
ANDROID_NDK_VERSION: 18.1.5063045
USE_BAZEL_VERSION: 2.0.0
steps:
- uses: actions/checkout@v4
- name: Set Up Bazelisk
uses: bazelbuild/setup-bazelisk@v3
- name: Set Up Android SDK
uses: android-actions/setup-android@v3
- name: Install Android Components
shell: bash
run: |
sdkmanager \
"platform-tools" \
"platforms;android-${ANDROID_API_LEVEL}" \
"build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \
"ndk;${ANDROID_NDK_VERSION}"
- name: Restore Bazel Repository Cache
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.bazel-cache/repository
key: ${{ runner.os }}-bazel-${{ env.ANDROID_PLATFORM }}-${{ env.TFJAVA_COMMIT }}-${{ hashFiles('scripts/patch_tfjava.py', '.github/workflows/build-android-arm64.yml') }}
restore-keys: |
${{ runner.os }}-bazel-${{ env.ANDROID_PLATFORM }}-${{ env.TFJAVA_COMMIT }}-
${{ runner.os }}-bazel-${{ env.ANDROID_PLATFORM }}-
- name: Clone TensorFlow Java
shell: bash
run: |
git clone https://github.com/tensorflow/java.git tfjava
cd tfjava
git checkout "${TFJAVA_COMMIT}"
- name: Set Up Java 11
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "11"
cache: maven
cache-dependency-path: |
tfjava/pom.xml
tfjava/**/pom.xml
- name: Patch TensorFlow Java For Android ARM64
shell: bash
run: |
python3 scripts/patch_tfjava.py tfjava
- name: Write TensorFlow Configure Overrides
shell: bash
run: |
cat > tfjava/tensorflow-core/tensorflow-core-api/.tf_configure.bazelrc <<EOF
build --action_env=ANDROID_HOME=${ANDROID_SDK_ROOT}
build --action_env=ANDROID_NDK_HOME=${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION}
build --action_env=ANDROID_NDK_API_LEVEL=${ANDROID_NDK_API_LEVEL}
build --action_env=ANDROID_SDK_HOME=${ANDROID_SDK_ROOT}
build --action_env=ANDROID_SDK_API_LEVEL=${ANDROID_API_LEVEL}
build --action_env=ANDROID_BUILD_TOOLS_VERSION=${ANDROID_BUILD_TOOLS_VERSION}
build --repo_env=ANDROID_HOME=${ANDROID_SDK_ROOT}
build --repo_env=ANDROID_NDK_HOME=${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION}
build --repo_env=ANDROID_NDK_API_LEVEL=${ANDROID_NDK_API_LEVEL}
build --repo_env=ANDROID_SDK_HOME=${ANDROID_SDK_ROOT}
build --repo_env=ANDROID_SDK_API_LEVEL=${ANDROID_API_LEVEL}
build --repo_env=ANDROID_BUILD_TOOLS_VERSION=${ANDROID_BUILD_TOOLS_VERSION}
EOF
- name: Show Patched Snippets
shell: bash
run: |
cd tfjava
sed -n '1,120p' tensorflow-core/tensorflow-core-api/build.sh
echo '---'
grep -n 'PLATFORM>${javacpp.platform}' tensorflow-core/tensorflow-core-api/pom.xml || true
echo '---'
grep -n 'androidndk\\|androidsdk' tensorflow-core/tensorflow-core-api/WORKSPACE || true
echo '---'
grep -n 'android-arm64' tensorflow-core/tensorflow-core-api/src/main/java/org/tensorflow/internal/c_api/presets/tensorflow.java || true
echo '---'
cat tensorflow-core/tensorflow-core-api/.tf_configure.bazelrc
- name: Build Android ARM64 Native Libraries
id: build_android
shell: bash
run: |
set -o pipefail
export ANDROID_HOME="${ANDROID_SDK_ROOT}"
export ANDROID_SDK_HOME="${ANDROID_SDK_ROOT}"
export ANDROID_NDK_HOME="${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION}"
export ANDROID_NDK_ROOT="${ANDROID_NDK_HOME}"
export ANDROID_SDK_API_LEVEL="${ANDROID_API_LEVEL}"
export ANDROID_BUILD_TOOLS_VERSION="${ANDROID_BUILD_TOOLS_VERSION}"
export ANDROID_NDK_API_LEVEL="${ANDROID_NDK_API_LEVEL}"
export JAVA_HOME="${JAVA_HOME_11_X64}"
export LANG="C.UTF-8"
export LC_ALL="C.UTF-8"
export PATH="${JAVA_HOME}/bin:${PATH}"
export BAZEL_REPOSITORY_CACHE="${GITHUB_WORKSPACE}/.bazel-cache/repository"
mkdir -p "${BAZEL_REPOSITORY_CACHE}"
mkdir -p "${GITHUB_WORKSPACE}/artifacts"
cd tfjava
mvn -B -e \
-Dproject.build.sourceEncoding=UTF-8 \
-Dproject.reporting.outputEncoding=UTF-8 \
-DskipTests \
-Djavacpp.platform="${ANDROID_PLATFORM}" \
-Djavacpp.platform.properties="${ANDROID_PLATFORM}" \
-Djavacpp.platform.root="${ANDROID_NDK_HOME}" \
-pl tensorflow-core/tensorflow-core-api \
-am \
package 2>&1 | tee "${GITHUB_WORKSPACE}/artifacts/build.log"
- name: Summarize First Build Failure
if: failure() && steps.build_android.outcome == 'failure'
shell: bash
run: |
python3 - <<'PY'
import re
from pathlib import Path
log_path = Path("artifacts/build.log")
if not log_path.exists():
print("::error::build.log was not produced")
raise SystemExit(0)
lines = log_path.read_text(encoding="utf-8", errors="ignore").splitlines()
primary_patterns = [
re.compile(r"^\s*.+?:\d+(?::\d+)?: (?:fatal )?error: .+$"),
re.compile(r"^\s*.+?: error: .+$"),
re.compile(r"^\s*(?:ld(?:\.lld)?|clang(?:\+\+)?): error: .+$"),
re.compile(r"^\s*undefined reference to .+$"),
re.compile(r"^\s*cannot find -l.+$"),
re.compile(r"^\s*collect2: error: .+$"),
re.compile(r"^\s*.+?UnsatisfiedLinkError.+$"),
re.compile(r"^\s*.+?NoSuch.+$"),
]
skip = (
"Failed to execute goal org.bytedeco:javacpp",
"Execution javacpp-build of goal",
"Process exited with an error: 1",
"BUILD FAILURE",
"compilation of rule",
"Linking of rule",
"Failed to restore: Cache service responded with 400",
)
def is_skippable(line: str) -> bool:
return any(token in line for token in skip)
hit = None
for i, line in enumerate(lines):
if is_skippable(line):
continue
if any(p.search(line) for p in primary_patterns):
hit = i
break
if hit is None:
for i in range(len(lines) - 1, -1, -1):
line = lines[i]
normalized = line.strip()
if not normalized or is_skippable(line):
continue
if "error:" in normalized or "undefined reference" in normalized:
hit = i
break
if hit is None:
for i, line in enumerate(lines):
if is_skippable(line):
continue
if line.startswith("ERROR: ") or line.startswith("FAILED: "):
hit = i
break
if hit is None:
hit = max(0, len(lines) - 80)
start = max(0, hit - 8)
end = min(len(lines), hit + 20)
snippet = "\n".join(lines[start:end]).strip()
if not snippet:
snippet = "\n".join(lines[-40:]).strip()
summary_path = Path("artifacts/first_failure_summary.txt")
summary_path.write_text(snippet + "\n", encoding="utf-8")
headline = lines[hit].strip() if lines else "Build failed without captured output"
headline = headline.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
print(f"::error::{headline[:400]}")
print("First failure summary:")
print(snippet)
PY
- name: Collect Artifacts
if: always()
shell: bash
run: |
set -e
mkdir -p "${GITHUB_WORKSPACE}/artifacts/output"
if [ -d tfjava/tensorflow-core/tensorflow-core-api/target/native ]; then
cp -R tfjava/tensorflow-core/tensorflow-core-api/target/native/. "${GITHUB_WORKSPACE}/artifacts/output/" || true
fi
if [ -d tfjava/tensorflow-core/tensorflow-core-api/target ]; then
find tfjava/tensorflow-core/tensorflow-core-api/target -maxdepth 1 -type f \( -name '*.jar' -o -name '*.pom' \) -exec cp {} "${GITHUB_WORKSPACE}/artifacts/output/" \; || true
fi
if [ -f "${GITHUB_WORKSPACE}/artifacts/output/org/tensorflow/internal/c_api/android-arm64/libjnitensorflow.so" ]; then
cp "${GITHUB_WORKSPACE}/artifacts/output/org/tensorflow/internal/c_api/android-arm64/libjnitensorflow.so" \
"${GITHUB_WORKSPACE}/artifacts/output/org/tensorflow/internal/c_api/android-arm64/libtensorflow_jni.so"
fi
find "${GITHUB_WORKSPACE}/artifacts" -maxdepth 5 -type f | sort > "${GITHUB_WORKSPACE}/artifacts/manifest.txt"
if [ -d "${GITHUB_WORKSPACE}/artifacts/output" ]; then
(cd "${GITHUB_WORKSPACE}/artifacts/output" && find . -type f -print0 | sort -z | xargs -0 sha256sum) > "${GITHUB_WORKSPACE}/artifacts/sha256.txt" || true
fi
- name: Upload Artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: tensorflow-android-arm64-build
path: artifacts
if-no-files-found: warn