Skip to content

fix(repro): handle same-file mv in per-ABI build script #99

fix(repro): handle same-file mv in per-ABI build script

fix(repro): handle same-file mv in per-ABI build script #99

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
# REPRODUCIBILITY: Set deterministic locale and timezone for consistent builds
env:
TZ: UTC
LANG: en_US.UTF-8
LC_ALL: en_US.UTF-8
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
# REPRODUCIBILITY: Pin Java 17 to match F-Droid build server
# Different Java versions can produce different R8/DEX output
- name: Setup Java 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
# REPRODUCIBILITY: Install FRESH SDK components to match F-Droid exactly
# GitHub may have outdated cached versions - remove and reinstall to get latest revision
# F-Droid downloads platform-34-ext7_r03.zip - we need the same
- name: Install pinned SDK components
run: |
# Remove pre-installed platform to ensure we get latest revision
rm -rf $ANDROID_HOME/platforms/android-34
sdkmanager "platforms;android-34" "build-tools;34.0.0"
echo "BUILD_TOOLS_VERSION=34.0.0" >> $GITHUB_ENV
# Show what revision was installed
ls -la $ANDROID_HOME/platforms/android-34/
- name: Add Android SDK tools to PATH
run: |
# Use build-tools 34.0.0 for F-Droid reproducibility (v35+ breaks apksigcopier)
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/34.0.0"
echo "$BUILD_TOOLS_DIR" >> $GITHUB_PATH
echo "BUILD_TOOLS_DIR=$BUILD_TOOLS_DIR" >> $GITHUB_ENV
echo "📦 Build tools: $BUILD_TOOLS_DIR"
echo "📦 Java: $(java -version 2>&1 | head -1)"
ls -la $BUILD_TOOLS_DIR/zipalign $BUILD_TOOLS_DIR/apksigner 2>/dev/null || echo "Tools check"
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Make gradlew executable
run: chmod +x gradlew
- name: Extract version info
run: |
TAG_NAME=${GITHUB_REF#refs/tags/}
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV
# Parse semantic version: vMAJOR.MINOR.PATCH -> code = MAJOR*10000 + MINOR*100 + PATCH
VERSION=${TAG_NAME#v}
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3 | sed 's/[^0-9].*//')
VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH))
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "VERSION_CODE=$VERSION_CODE" >> $GITHUB_ENV
echo "📦 Version: $VERSION (code: $VERSION_CODE)"
- name: Verify tag matches build.gradle version
run: |
TAG_VERSION=${GITHUB_REF_NAME#v}
# Extract VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH from build.gradle
GRADLE_MAJOR=$(grep "VERSION_MAJOR" build.gradle | head -1 | sed 's/.*= *//' | tr -d ' ')
GRADLE_MINOR=$(grep "VERSION_MINOR" build.gradle | head -1 | sed 's/.*= *//' | tr -d ' ')
GRADLE_PATCH=$(grep "VERSION_PATCH" build.gradle | head -1 | sed 's/.*= *//' | tr -d ' ')
GRADLE_VERSION="${GRADLE_MAJOR}.${GRADLE_MINOR}.${GRADLE_PATCH}"
echo "🏷️ Git Tag Version: $TAG_VERSION"
echo "📦 Gradle Version: $GRADLE_VERSION"
if [ "$TAG_VERSION" != "$GRADLE_VERSION" ]; then
echo "::error::VERSION MISMATCH! Git tag ($TAG_VERSION) does not match build.gradle ($GRADLE_VERSION)"
echo "Please update VERSION_MAJOR/MINOR/PATCH in build.gradle to match the tag."
exit 1
fi
echo "✅ Version match confirmed!"
# Decode keystore for later signing (do NOT set RELEASE_KEYSTORE env var here
# to ensure build.gradle builds unsigned APK)
- name: Decode signing keystore
run: echo "${{ secrets.SIGNING_KEY }}" | base64 -d > ${{ github.workspace }}/release.keystore
- name: Print R8 Version
run: ./gradlew printR8Version
- name: Clone reproducible-apk-tools
run: git clone https://github.com/obfusk/reproducible-apk-tools.git
# REPRODUCIBILITY: Build EACH ABI separately to match F-Droid
# F-Droid builds each ABI as a separate job with sed to modify build.gradle
# Building all ABIs together causes different AAPT2 resource compilation
- name: Build APKs per-ABI (matching F-Droid)
run: |
mkdir -p build/outputs/apk/release
# Array of ABIs to build (matching F-Droid metadata order)
declare -A ABI_MAP=(
["armeabi-v7a"]="armeabi-v7a"
["arm64-v8a"]="arm64-v8a"
["x86_64"]="x86_64"
)
for ABI in armeabi-v7a arm64-v8a x86_64; do
echo "========================================="
echo "🔨 Building $ABI APK..."
echo "========================================="
# Reset build.gradle to original state
git checkout build.gradle
# Apply same sed command as F-Droid prebuild
# This changes: include 'armeabi-v7a', 'arm64-v8a', 'x86_64'
# To: include '$ABI'
sed -i -e "s/include 'armeabi-v7a'.*/include '$ABI'/" build.gradle
# Verify the change
echo "Modified splits.abi.include:"
grep "include '" build.gradle | grep -v "//"
# Clean and build this single ABI
./gradlew clean assembleRelease --stacktrace --no-build-cache
# Find and rename the output APK
BUILT_APK=$(find build/outputs/apk/release -name "*.apk" -type f | head -1)
if [ -n "$BUILT_APK" ]; then
# Extract version from filename
VERSION_NAME=$(echo "$BUILT_APK" | grep -oP 'v\d+\.\d+\.\d+' || echo "v${{ env.VERSION }}")
FINAL_NAME="CleverKeys-${VERSION_NAME}-${ABI}.apk"
# Apply F-Droid postbuild steps to this APK
echo "📦 Applying reproducibility fixes to $FINAL_NAME..."
# POSTBUILD STEP 1: Remove non-deterministic META-INF files
python3 -c "import zipfile,sys,tempfile,shutil;a=sys.argv[1];t=tempfile.mktemp('.apk');z=zipfile.ZipFile(a);o=zipfile.ZipFile(t,'w');[o.writestr(i,z.read(i.filename))for i in z.infolist()if not(i.filename.startswith('META-INF/com/')or i.filename.startswith('META-INF/services/'))];z.close();o.close();shutil.move(t,a)" "$BUILT_APK"
# POSTBUILD STEP 2: Fix pg-map-id and zipalign
python3 ${{ github.workspace }}/reproducible-apk-tools/inplace-fix.py \
--page-size 16 --internal fix-pg-map-id "$BUILT_APK" 0000000
# Rename if needed (Gradle may already use correct name)
FINAL_PATH="build/outputs/apk/release/$FINAL_NAME"
if [ "$BUILT_APK" != "$FINAL_PATH" ]; then
mv "$BUILT_APK" "$FINAL_PATH"
fi
echo "✅ Built: $FINAL_NAME"
else
echo "❌ ERROR: No APK found for $ABI"
exit 1
fi
echo ""
done
# Restore original build.gradle
git checkout build.gradle
echo "========================================="
echo "📦 All APKs built:"
ls -la build/outputs/apk/release/*.apk
echo "========================================="
# Sign the APKs with our release key after fixes
# inplace-fix.py already did zipalign with -P 16
- name: Sign release APKs
run: |
cd build/outputs/apk/release
for apk in *.apk; do
echo "Signing $apk..."
# Sign with apksigner (APK already zipaligned by inplace-fix.py)
$BUILD_TOOLS_DIR/apksigner sign \
--ks ${{ github.workspace }}/release.keystore \
--ks-pass pass:${{ secrets.KEY_STORE_PASSWORD }} \
--ks-key-alias ${{ secrets.ALIAS }} \
--key-pass pass:${{ secrets.KEY_PASSWORD }} \
"$apk"
# Verify signature
$BUILD_TOOLS_DIR/apksigner verify "$apk" && echo "✅ $apk verified"
done
- name: List release APKs
run: |
cd build/outputs/apk/release
echo "=== Release APKs ==="
ls -la CleverKeys-*.apk || echo "No APKs found!"
echo ""
echo "📦 Version: ${{ env.VERSION }} (code: ${{ env.VERSION_CODE }})"
- name: Generate changelog
run: |
# Extract changelog from recent commits
echo "## What's New in ${{ env.TAG_NAME }}" > CHANGELOG.md
echo "" >> CHANGELOG.md
# Get commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
git log $LAST_TAG..HEAD --pretty=format:"- %s (%h)" --grep="feat\|fix\|perf\|refactor" >> CHANGELOG.md
else
echo "- Initial release with neural swipe typing" >> CHANGELOG.md
echo "- Complete ONNX transformer integration" >> CHANGELOG.md
echo "- Hardware acceleration support" >> CHANGELOG.md
echo "- Privacy-first local processing" >> CHANGELOG.md
fi
echo "" >> CHANGELOG.md
echo "## Build Information" >> CHANGELOG.md
echo "- **Version**: ${{ env.VERSION }}" >> CHANGELOG.md
echo "- **VersionCode**: ${{ env.VERSION_CODE }} (base)" >> CHANGELOG.md
echo "- **Package**: tribixbite.cleverkeys" >> CHANGELOG.md
echo "- **Min Android**: 5.0 (API 21)" >> CHANGELOG.md
echo "- **Target Android**: 14 (API 34)" >> CHANGELOG.md
echo "" >> CHANGELOG.md
echo "## Install Options" >> CHANGELOG.md
echo "| Source | Link |" >> CHANGELOG.md
echo "|--------|------|" >> CHANGELOG.md
echo "| GitHub | Download APK below for your device |" >> CHANGELOG.md
echo "| F-Droid | Coming soon (auto-update enabled) |" >> CHANGELOG.md
echo "" >> CHANGELOG.md
echo "## APK Variants" >> CHANGELOG.md
echo "| ABI | Devices | VersionCode |" >> CHANGELOG.md
echo "|-----|---------|-------------|" >> CHANGELOG.md
echo "| arm64 | Most modern phones (2016+) | $((${{ env.VERSION_CODE }} * 10 + 2)) |" >> CHANGELOG.md
echo "| armv7 | Older 32-bit devices | $((${{ env.VERSION_CODE }} * 10 + 1)) |" >> CHANGELOG.md
echo "| x86_64 | Emulators, Chromebooks | $((${{ env.VERSION_CODE }} * 10 + 3)) |" >> CHANGELOG.md
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: build/outputs/apk/release/CleverKeys-*.apk
name: CleverKeys ${{ env.TAG_NAME }}
tag_name: ${{ env.TAG_NAME }}
body_path: CHANGELOG.md
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload to artifact
uses: actions/upload-artifact@v4
with:
name: cleverkeys-release-${{ env.TAG_NAME }}
path: |
build/outputs/apk/release/CleverKeys-*.apk
CHANGELOG.md
retention-days: 365
- name: Release summary
run: |
echo "## Release ${{ env.TAG_NAME }} Published!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Version Info" >> $GITHUB_STEP_SUMMARY
echo "- **Version**: ${{ env.VERSION }}" >> $GITHUB_STEP_SUMMARY
echo "- **Base VersionCode**: ${{ env.VERSION_CODE }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Download" >> $GITHUB_STEP_SUMMARY
echo "- [Release Page](https://github.com/${{ github.repository }}/releases/tag/${{ env.TAG_NAME }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### APK Variants" >> $GITHUB_STEP_SUMMARY
echo "| ABI | VersionCode | Use For |" >> $GITHUB_STEP_SUMMARY
echo "|-----|-------------|---------|" >> $GITHUB_STEP_SUMMARY
echo "| arm64 | $((${{ env.VERSION_CODE }} * 10 + 2)) | Most modern phones |" >> $GITHUB_STEP_SUMMARY
echo "| armv7 | $((${{ env.VERSION_CODE }} * 10 + 1)) | Older 32-bit devices |" >> $GITHUB_STEP_SUMMARY
echo "| x86_64 | $((${{ env.VERSION_CODE }} * 10 + 3)) | Emulators, Chromebooks |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Distribution" >> $GITHUB_STEP_SUMMARY
echo "- GitHub Release: ✅ Available now" >> $GITHUB_STEP_SUMMARY
echo "- F-Droid: Auto-update will pick up this version" >> $GITHUB_STEP_SUMMARY
notify:
runs-on: ubuntu-latest
needs: release
if: success()
steps:
- name: Release notification
run: |
echo "CleverKeys ${{ github.ref_name }} has been successfully released!"
echo "Users can now download the latest APK with neural swipe typing."