Skip to content

Android SDK build scripts #467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,141 @@ jobs:
name: docker-logs
path: |
*.log

static-linux-build:
name: Build Static Linux image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build Docker images
working-directory: swift-ci/sdks/static-linux
run: ./build

android-build:
name: Build Android ${{ matrix.arch }} SDK
strategy:
fail-fast: false
matrix:
# blank arch builds all (aarch64,x86_64,armv7)
#arch: ['']
# builds only x86_64 to speed up the validation
#arch: ['x86_64']
# build both the quick (x86_64) and complete (aarch64,x86_64,armv7) SDKs
arch: ['x86_64', '']
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Dependencies
run: |
sudo apt install -q ninja-build patchelf
- name: Build Android SDK
working-directory: swift-ci/sdks/android
run: |
TARGET_ARCHS=${{ matrix.arch }} ./build
- name: Get artifact info
id: info
shell: bash
run: |
set -ex
SWIFT_ROOT=$(dirname ${{ runner.temp }}/swift-android-sdk/host-toolchain/*/usr)
echo "swift-root=${SWIFT_ROOT}" >> $GITHUB_OUTPUT
echo "swift-path=${SWIFT_ROOT}/usr/bin/swift" >> $GITHUB_OUTPUT

ARTIFACT_BUILD=$(realpath ${{ runner.temp }}/swift-android-sdk/build/*.artifactbundle)
ARTIFACT_PATH=$(realpath ${{ runner.temp }}/swift-android-sdk/products/*.artifactbundle.tar.gz)
echo "artifact-path=${ARTIFACT_PATH}" >> $GITHUB_OUTPUT
echo "sdk-id=x86_64-unknown-linux-android28" >> $GITHUB_OUTPUT

ARTIFACT_EXT=".artifactbundle.tar.gz"
ARTIFACT_NAME="$(basename ${ARTIFACT_PATH} ${ARTIFACT_EXT})"
# artifacts need a unique name so we suffix with the matrix arch(s)
if [[ ! -z "${{ matrix.arch }}" ]]; then
ARTIFACT_NAME="${ARTIFACT_NAME}-$(echo ${{ matrix.arch }} | tr ',' '-')"
fi
ARTIFACT_NAME="${ARTIFACT_NAME}${ARTIFACT_EXT}"

# There is no way to prevent even a single-file artifact from being zipped:
# https://github.com/actions/upload-artifact?tab=readme-ov-file#zip-archives
# so the actual artifact download will look like:
# swift-6.1-RELEASE_android-0.1-x86_64.artifactbundle.tar.gz.zip
echo "artifact-name=${ARTIFACT_NAME}" >> $GITHUB_OUTPUT

# validate some required paths in the artifactbundle
pushd ${ARTIFACT_BUILD}/*/*/usr
ls lib/swift/android
ls lib/swift/android/*
ls lib/swift/android/*/swiftrt.o
ls lib/*-linux-android/*/crtbegin_dynamic.o

ls lib/swift_static-*
ls lib/swift_static-*/android
ls lib/swift_static-*/android/libFoundationEssentials.a

ls lib/*-linux-android/libFoundationEssentials.so
ls lib/*-linux-android/libFoundationNetworking.so
ls lib/*-linux-android/libFoundationInternationalization.so
ls lib/*-linux-android/lib_FoundationICU.so
ls lib/*-linux-android/libFoundationXML.so
ls lib/*-linux-android/libTesting.so

ls lib/swift/clang/lib
ls lib/swift/clang/lib/linux
ls lib/swift/clang/lib/linux/*/libunwind.a
popd
- name: Upload SDK artifactbundle
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: ${{ steps.info.outputs.artifact-name }}
path: ${{ steps.info.outputs.artifact-path }}
- name: Cleanup
run: |
# need to free up some space or else when installing we get: No space left on device
rm -rf ${{ runner.temp }}/swift-android-sdk/{build,src}
- name: Install artifactbundle
shell: bash
run: |
set -ex
${{ steps.info.outputs.swift-path }} sdk install ${{ steps.info.outputs.artifact-path }}
${{ steps.info.outputs.swift-path }} sdk configure --show-configuration $(${{ steps.info.outputs.swift-path }} sdk list | head -n 1) ${{ steps.info.outputs.sdk-id }}
# recent releases require that ANDROID_NDK_ROOT *not* be set
# see https://github.com/finagolfin/swift-android-sdk/issues/207
echo "ANDROID_NDK_ROOT=" >> $GITHUB_ENV

- name: Create Demo Project
run: |
cd ${{ runner.temp }}
mkdir DemoProject
cd DemoProject
${{ steps.info.outputs.swift-path }} --version
${{ steps.info.outputs.swift-path }} package init
echo 'import Foundation' >> Sources/DemoProject/DemoProject.swift
echo 'import FoundationEssentials' >> Sources/DemoProject/DemoProject.swift
echo 'import FoundationXML' >> Sources/DemoProject/DemoProject.swift
echo 'import FoundationNetworking' >> Sources/DemoProject/DemoProject.swift
echo 'import Dispatch' >> Sources/DemoProject/DemoProject.swift
echo 'import Android' >> Sources/DemoProject/DemoProject.swift
- name: Test Demo Project on Android
uses: skiptools/swift-android-action@v2
with:
# only test for the complete arch SDK build to speed up CI
run-tests: ${{ matrix.arch == '' }}
package-path: ${{ runner.temp }}/DemoProject
installed-sdk: ${{ steps.info.outputs.sdk-id }}
installed-swift: ${{ steps.info.outputs.swift-root }}

- name: Checkout swift-algorithms
uses: actions/checkout@v4
with:
repository: apple/swift-algorithms
path: swift-algorithms
- name: Test swift-algorithms
uses: skiptools/swift-android-action@v2
with:
run-tests: ${{ matrix.arch == '' }}
package-path: swift-algorithms
installed-sdk: ${{ steps.info.outputs.sdk-id }}
installed-swift: ${{ steps.info.outputs.swift-root }}

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.*.swp
static-swift-linux-sources
43 changes: 43 additions & 0 deletions swift-ci/sdks/android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Build scripts for Swift Android SDK

This folder contains scripts to build a Swift Android SDK
in the form of an artifactbundle.

## Running

The top-level `./build` script installs a host toolchain and the
Android NDK, and then invokes `scripts/fetch-source.sh` which will
fetch tagged sources for libxml2, curl, boringssl, and swift.

It then applies some patches and invokes `scripts/build.sh`,
which will build the sources for each of the specified
architectures. Finally, it combines the NDK and the newly built
SDKs into a single artifactbundle.

## Specifying Architectures

By default all the supported Android architectures
(`aarch64`, `x86_64`, `aarmv7`)
will be built, but this can be reduced in order to speed
up the build. This can be useful, e.g., as part of a CI that
validates a pull request, as building a single architecture
takes around 30 minutes on a standard ubuntu-24.04 GitHub runner,
whereas building for all the architectures takes over an hour.

To build an artifactbundle for just the `x86_64` architecture, run:

```
TARGET_ARCHS=x86_64 ./build
```

## Installing and validating the SDK

The `.github/workflows/pull_request.yml` workflow
will create and upload an installable SDK named something like:
`swift-6.1-RELEASE_android-0.1.artifactbundle.tar.gz`

The workflow will also install the SDK locally and use
[swift-android-action](https://github.com/marketplace/actions/swift-android-action)
to build and test various Swift packages in an Android emulator.


91 changes: 91 additions & 0 deletions swift-ci/sdks/android/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/bin/bash -e
# Swift Android SDK: Top-level Build Script

# default architectures to build for
TARGET_ARCHS=${TARGET_ARCHS:-aarch64,x86_64,armv7}

ANDROID_NDK_VERSION=android-ndk-r27c
ANDROID_API=28

SDKROOT=${RUNNER_TEMP:-${TMPDIR:-'/tmp'}}/swift-android-sdk
mkdir -p ${SDKROOT}

# Install a Swift host toolchain if it isn't already present
SWIFT_ROOT=${SDKROOT}/host-toolchain
HOST_OS=ubuntu$(lsb_release -sr)
SWIFT_VERSION=6.1
SWIFT_TAG="swift-${SWIFT_VERSION}-RELEASE"
SWIFT_BRANCH="swift-$(echo $SWIFT_TAG | cut -d- -f2)-release"
SWIFT_BASE=$SWIFT_TAG-$HOST_OS

if [[ ! -d $SWIFT_ROOT ]]; then
mkdir -p $SWIFT_ROOT
pushd $SWIFT_ROOT

SWIFT_URL="https://download.swift.org/$SWIFT_BRANCH/$(echo $HOST_OS | tr -d '.')/$SWIFT_TAG/$SWIFT_BASE.tar.gz"
wget -q $SWIFT_URL
tar xzf $SWIFT_BASE.tar.gz

popd
fi

#HOST_TOOLCHAIN=${HOST_TOOLCHAIN:-$(dirname $(dirname $(which swiftc)))}
HOST_TOOLCHAIN=$SWIFT_ROOT/$SWIFT_BASE/usr
$HOST_TOOLCHAIN/bin/swift --version

# ensure the correct Swift is first in the PATH
export PATH=$HOST_TOOLCHAIN/bin:$PATH

export ANDROID_NDK_HOME=${SDKROOT}/ndk/${ANDROID_NDK_VERSION}
export ANDROID_NDK=${ANDROID_NDK_HOME}

if [[ ! -d ${ANDROID_NDK_HOME} ]]; then
mkdir -p $(dirname ${ANDROID_NDK_HOME})
pushd $(dirname ${ANDROID_NDK_HOME})
NDKFILE=$(basename $ANDROID_NDK_HOME)-linux.zip
wget -q https://dl.google.com/android/repository/${NDKFILE}
unzip -q ${NDKFILE}
popd
fi

mkdir -p ${SDKROOT}/products

# Check-out the sources
if [[ ! -d ${SDKROOT}/src ]]; then
scripts/fetch-source.sh --source-dir ${SDKROOT}/src --swift-tag ${SWIFT_TAG}
fi

# fetch and apply the patches
PATCHDIR=${SDKROOT}/patches
if [[ ! -d ${PATCHDIR} ]]; then
git clone https://github.com/finagolfin/swift-android-sdk.git ${PATCHDIR}

# TODO: need to selectively apply patches based on release or not release
pushd ${SDKROOT}/src/swift-project
echo "Applying patches"

# patch the patch, which seems to only be needed for an API less than 28
# https://github.com/finagolfin/swift-android-sdk/blob/main/swift-android.patch#L110
perl -pi -e 's/#if os\(Windows\)/#if os\(Android\)/g' $PATCHDIR/swift-android.patch

# remove the need to link in android-execinfo
perl -pi -e 's/dispatch android-execinfo/dispatch/g' $PATCHDIR/swift-android.patch

git apply -v $PATCHDIR/swift-android.patch
# swift-android-ci.patch is not needed, since it is only used for llbuild, etc.
#git apply -C1 $PATCHDIR/swift-android-ci.patch
#git apply -v $PATCHDIR/swift-android-ci-release.patch
git apply -v $PATCHDIR/swift-android-testing-release.patch

perl -pi -e 's%String\(cString: getpass%\"fake\" //%' swiftpm/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift
# disable backtrace() for Android (needs either API33+ or libandroid-execinfo, or to manually add in backtrace backport)
perl -pi -e 's/os\(Android\)/os\(AndroidDISABLED\)/g' swift-testing/Sources/Testing/SourceAttribution/Backtrace.swift

# need to un-apply libandroid-spawn since we don't need it for API28+
perl -pi -e 's/MATCHES "Android"/MATCHES "AndroidDISABLED"/g' llbuild/lib/llvm/Support/CMakeLists.txt
perl -pi -e 's/ STREQUAL Android\)/ STREQUAL AndroidDISABLED\)/g' swift-corelibs-foundation/Sources/Foundation/CMakeLists.txt
popd
fi

./scripts/build.sh --products-dir ${SDKROOT}/products --source-dir ${SDKROOT}/src --build-dir ${SDKROOT}/build --ndk-home ${ANDROID_NDK_HOME} --android-api ${ANDROID_API} --host-toolchain ${HOST_TOOLCHAIN} --archs ${TARGET_ARCHS}

Loading