Skip to content

Build release/0.13.1/0.13.1 - 1 #433

Build release/0.13.1/0.13.1 - 1

Build release/0.13.1/0.13.1 - 1 #433

Workflow file for this run

name: Build AppFlowy Android
run-name: Build ${{ github.event.inputs.branch }}/${{ github.event.inputs.build_name }} - ${{ github.event.inputs.internal_build }}
on:
workflow_dispatch:
inputs:
repo:
description: "Repo"
required: true
default: "AppFlowy-IO/AppFlowy-Premium"
branch:
description: "Branch"
required: true
default: "main"
build_name:
description: "Build Version (it should match the version in pubspec.yaml)"
required: true
default: "0.13.1"
build_number:
description: "Build Number (it should be unique)"
required: true
default: "29130101"
build_type:
type: choice
description: "Build Type"
required: true
options:
- "apk"
- "appbundle"
- "both"
internal_build:
type: choice
description: "Internal Build Type (1 for internal, 0 for external)"
required: true
default: "1"
options:
- "0"
- "1"
workflow_call:
inputs:
repo:
description: "Repo"
required: true
type: string
branch:
description: "Branch"
required: true
type: string
build_name:
description: "Build Version"
required: true
type: string
build_number:
description: "Build Number"
required: true
type: string
build_type:
description: "Build Type"
required: true
type: string
internal_build:
description: "Internal Build Type"
required: true
type: string
upload_url:
description: "Upload URL for release assets"
required: false
type: string
env:
FLUTTER_VERSION: "3.41.9"
RUST_TOOLCHAIN: "1.92.0"
jobs:
build:
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest]
include:
- os: ubuntu-latest
target: aarch64-linux-android
runs-on: ${{ matrix.os }}
steps:
# the following step is required to avoid running out of space
- name: Maximize build space
if: matrix.os == 'ubuntu-latest'
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- name: Checkout source code
uses: actions/checkout@v2
with:
repository: ${{ inputs.repo }}
ref: ${{ inputs.branch }}
token: ${{ secrets.PRIVATE_REPO_TOKEN }}
- uses: actions/setup-java@v4
with:
distribution: "temurin" # See 'Supported distributions' for available options
java-version: "17"
- name: Install Rust toolchain
id: rust_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
target: ${{ matrix.target }}
override: true
profile: minimal
- name: Install flutter
id: flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Install Android NDK
run: |
# Install NDK version 28.2.13676358 for 16 KB page-size support.
echo "Installing Android NDK 28.2.13676358..."
echo "ANDROID_HOME=$ANDROID_HOME"
# Accept licenses first
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true
# Install the specific NDK version
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "ndk;28.2.13676358"
# Verify installation
echo "Checking NDK installation..."
ls -la $ANDROID_HOME/ndk/ || echo "NDK directory not found"
# Set environment variables
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> $GITHUB_ENV
echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/28.2.13676358" >> $GITHUB_ENV
echo "NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> $GITHUB_ENV
echo "$ANDROID_HOME/ndk/28.2.13676358" >> $GITHUB_PATH
# Verify the NDK path exists
if [ -d "$ANDROID_HOME/ndk/28.2.13676358" ]; then
echo "NDK 28.2.13676358 installed successfully at $ANDROID_HOME/ndk/28.2.13676358"
else
echo "Error: NDK 28.2.13676358 not found at expected path"
echo "Available NDK versions:"
ls -la $ANDROID_HOME/ndk/
exit 1
fi
- uses: Swatinem/rust-cache@v2
with:
prefix-key: ${{ matrix.os }}
workspaces: |
frontend/rust-lib
- uses: davidB/rust-cargo-make@v1
with:
version: "0.37.18"
- name: Install prerequisites
working-directory: frontend
run: |
rustup target install aarch64-linux-android
cargo install duckscript_cli --force --locked
cargo install --version 4.1.2 cargo-ndk --locked
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
sudo apt-get install keybinder-3.0 libnotify-dev
sudo apt-get install gcc-multilib
sudo apt-get -y install libmpv-dev mpv
cargo make appflowy-flutter-deps-tools
shell: bash
- name: Generate env file
working-directory: frontend/appflowy_flutter
run: |
echo "INTERNAL_BUILD=${{ inputs.internal_build }}" >> .env
echo "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
shell: bash
- name: Configure release sign
working-directory: frontend/appflowy_flutter/android/app
run: |
# replace signingConfig signingConfigs.debug with signingConfig signingConfigs.release
sed -i 's/signingConfig signingConfigs.debug/signingConfig signingConfigs.release/g' build.gradle
- name: Configure key.properties
working-directory: frontend/appflowy_flutter/android
run: |
echo "${{ secrets.ANDROID_UPLOAD_KEYSTORE_BASE64 }}" | base64 --decode > app/upload-keystore.jks
echo -e "storePassword=${{ secrets.ANDROID_UPLOAD_KEYSTORE_STORE_PASSWORD }}\nkeyPassword=${{ secrets.ANDROID_UPLOAD_KEYSTORE_KEY_PASSWORD }}\nkeyAlias=upload\nstoreFile=upload-keystore.jks" > key.properties
shell: bash
- name: Configure Git credentials for Cargo
run: |
git config --global credential.helper store
echo "https://${{ secrets.ADMIN_GITHUB_TOKEN }}:x-oauth-basic@github.com" > ~/.git-credentials
- name: Build AppFlowy
working-directory: frontend
env:
# Match the app's minSdkVersion. RocksDB 10.4 requires Android API 23+
# for POSIX_MADV_* definitions, while cargo-ndk otherwise defaults to 21.
CARGO_NDK_PLATFORM: "29"
run: |
cargo make --profile production-android appflowy-core-dev-android
cargo make --profile production-android code_generation
cd appflowy_flutter
if [ ${{ inputs.build_type }} = "apk" ]; then
flutter build apk --target-platform android-arm64 --build-name ${{ inputs.build_name }} --build-number ${{ inputs.build_number }} --split-debug-info=./debug_info --obfuscate
elif [ ${{ inputs.build_type }} = "appbundle" ]; then
flutter build appbundle --target-platform android-arm64 --build-name ${{ inputs.build_name }} --build-number ${{ inputs.build_number }} --split-debug-info=./debug_info --obfuscate
elif [ ${{ inputs.build_type }} = "both" ]; then
flutter build apk --target-platform android-arm64 --build-name ${{ inputs.build_name }} --build-number ${{ inputs.build_number }} --split-debug-info=./debug_info --obfuscate
flutter build appbundle --target-platform android-arm64 --build-name ${{ inputs.build_name }} --build-number ${{ inputs.build_number }} --split-debug-info=./debug_info --obfuscate
fi
- name: Verify Android 16 KB ELF alignment
working-directory: frontend/appflowy_flutter
run: |
python3 <<'PY'
import struct
import zipfile
from pathlib import Path
page_size = 16384
pt_load = 1
artifacts = [
Path("build/app/outputs/flutter-apk/app-release.apk"),
Path("build/app/outputs/bundle/release/app-release.aab"),
]
def load_segments(data):
if data[:4] != b"\x7fELF":
return []
elf_class = data[4]
encoding = data[5]
endian = "<" if encoding == 1 else ">"
if elf_class == 2:
phoff = struct.unpack_from(endian + "Q", data, 32)[0]
phentsize = struct.unpack_from(endian + "H", data, 54)[0]
phnum = struct.unpack_from(endian + "H", data, 56)[0]
segments = []
for index in range(phnum):
offset = phoff + index * phentsize
p_type = struct.unpack_from(endian + "I", data, offset)[0]
if p_type == pt_load:
p_offset = struct.unpack_from(endian + "Q", data, offset + 8)[0]
p_vaddr = struct.unpack_from(endian + "Q", data, offset + 16)[0]
p_align = struct.unpack_from(endian + "Q", data, offset + 48)[0]
segments.append((p_offset, p_vaddr, p_align))
return segments
if elf_class == 1:
phoff = struct.unpack_from(endian + "I", data, 28)[0]
phentsize = struct.unpack_from(endian + "H", data, 42)[0]
phnum = struct.unpack_from(endian + "H", data, 44)[0]
segments = []
for index in range(phnum):
offset = phoff + index * phentsize
p_type = struct.unpack_from(endian + "I", data, offset)[0]
if p_type == pt_load:
p_offset = struct.unpack_from(endian + "I", data, offset + 4)[0]
p_vaddr = struct.unpack_from(endian + "I", data, offset + 8)[0]
p_align = struct.unpack_from(endian + "I", data, offset + 28)[0]
segments.append((p_offset, p_vaddr, p_align))
return segments
return []
failures = []
for artifact in artifacts:
if not artifact.exists():
continue
print(f"Checking {artifact}")
with zipfile.ZipFile(artifact) as archive:
native_libs = [
entry for entry in archive.infolist()
if entry.filename.endswith(".so")
and (entry.filename.startswith("lib/") or "/lib/" in entry.filename)
]
if not native_libs:
failures.append(f"{artifact}: no native libraries found")
continue
for entry in native_libs:
segments = load_segments(archive.read(entry))
bad_segments = [
(p_offset, p_vaddr, p_align)
for p_offset, p_vaddr, p_align in segments
if p_align < page_size or p_offset % page_size != p_vaddr % page_size
]
min_align = min((p_align for _, _, p_align in segments), default=0)
print(f" {entry.filename}: min PT_LOAD align=0x{min_align:x}")
for p_offset, p_vaddr, p_align in bad_segments:
failures.append(
f"{artifact}:{entry.filename} has PT_LOAD "
f"offset=0x{p_offset:x} vaddr=0x{p_vaddr:x} align=0x{p_align:x}"
)
if failures:
print("16 KB ELF alignment check failed:")
for failure in failures:
print(f" - {failure}")
raise SystemExit(1)
print("16 KB ELF alignment check passed.")
PY
shell: bash
- name: Compress debug symbols
working-directory: frontend/appflowy_flutter
run: |
tar -czf debug-symbols.tar.gz debug_info
- name: Rename APK
if: inputs.build_type == 'apk' || inputs.build_type == 'both'
run: |
mv frontend/appflowy_flutter/build/app/outputs/flutter-apk/app-release.apk frontend/appflowy_flutter/build/app/outputs/flutter-apk/AppFlowy-${{ inputs.build_name }}-android.apk
shell: bash
- name: Upload APK
if: inputs.build_type == 'apk' || inputs.build_type == 'both'
uses: actions/upload-artifact@v4
with:
name: AppFlowy-${{ inputs.build_name }}-android-apk
path: frontend/appflowy_flutter/build/app/outputs/flutter-apk/AppFlowy-${{ inputs.build_name }}-android.apk
- name: Rename AAB
if: inputs.build_type == 'appbundle' || inputs.build_type == 'both'
run: |
mv frontend/appflowy_flutter/build/app/outputs/bundle/release/app-release.aab frontend/appflowy_flutter/build/app/outputs/bundle/release/AppFlowy-${{ inputs.build_name }}.aab
shell: bash
- name: Upload AAB
if: inputs.build_type == 'appbundle' || inputs.build_type == 'both'
uses: actions/upload-artifact@v4
with:
name: AppFlowy-${{ inputs.build_name }}-android-aab
path: frontend/appflowy_flutter/build/app/outputs/bundle/release/AppFlowy-${{ inputs.build_name }}.aab
- name: Upload debug symbols
uses: actions/upload-artifact@v4
with:
name: AppFlowy-${{ inputs.build_name }}-android-${{ inputs.build_type }}-debug-symbols
path: frontend/appflowy_flutter/debug-symbols.tar.gz
- name: Upload APK to Release
if: (inputs.build_type == 'apk' || inputs.build_type == 'both') && inputs.upload_url != ''
run: |
filename="AppFlowy-${{ inputs.build_name }}-android.apk"
filepath="frontend/appflowy_flutter/build/app/outputs/flutter-apk/$filename"
# URL encode the filename
encoded_filename=$(echo -n "$filename" | jq -sRr @uri)
# Remove the {?name,label} template from upload URL if present
upload_url=$(echo "${{ inputs.upload_url }}" | sed 's/{[^}]*}//g')
echo "Uploading $filename to release..."
curl -L \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$filepath" \
"${upload_url}?name=${encoded_filename}" \
--fail-with-body
- name: Upload AAB to Release
if: (inputs.build_type == 'appbundle' || inputs.build_type == 'both') && inputs.upload_url != ''
run: |
filename="AppFlowy-${{ inputs.build_name }}.aab"
filepath="frontend/appflowy_flutter/build/app/outputs/bundle/release/$filename"
# URL encode the filename
encoded_filename=$(echo -n "$filename" | jq -sRr @uri)
# Remove the {?name,label} template from upload URL if present
upload_url=$(echo "${{ inputs.upload_url }}" | sed 's/{[^}]*}//g')
echo "Uploading $filename to release..."
curl -L \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$filepath" \
"${upload_url}?name=${encoded_filename}" \
--fail-with-body
- name: Setup Sentry CLI
uses: mathieu-bour/setup-sentry-cli@v1
with:
version: latest
token: ${{ SECRETS.SENTRY_TOKEN }}
organization: appflowy
project: appflowy
- name: Upload symbols to Sentry
run: sentry-cli debug-files upload -o appflowy -p appflowy frontend/appflowy_flutter/debug_info