Skip to content
211 changes: 211 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
name: ci

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
build-examples:
name: Build ${{ matrix.os }} ${{ matrix.swift_version }}
strategy:
fail-fast: false
matrix:
#swift_version: ['nightly-main', '6.3']
swift_version: ['6.3']
sdk_triple: ['aarch64-unknown-linux-android28']
ndk_version: ['r27d']
os: ['ubuntu-latest', 'macos-latest']
runs-on: ${{ matrix.os }}
env:
SDK_TRIPLE: ${{ matrix.sdk_triple }}
NDK_VERSION: ${{ matrix.ndk_version }}
SWIFT_VERSION: ${{ matrix.swift_version }}
steps:
- uses: actions/checkout@v6

- name: Set up JDK 25
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '25'

- name: Install swiftly
run: |
set -euxo pipefail
if [[ "${RUNNER_OS}" == "Linux" ]]; then
sudo apt-get -yq install curl jq gpg unzip libcurl4-openssl-dev
ARCH="$(uname -m)"
curl -L -O --retry 3 "https://download.swift.org/swiftly/linux/swiftly-${ARCH}.tar.gz"
tar -xzf "swiftly-${ARCH}.tar.gz"
./swiftly init \
--assume-yes \
--skip-install \
--no-modify-profile \
--quiet-shell-followup
rm -f "swiftly-${ARCH}.tar.gz" swiftly
# The example projects' Gradle scripts look for swiftly under
# $HOME/.local/share/swiftly/bin, which is also where the official
# installer puts it. Add it to PATH for subsequent steps.
echo "$HOME/.local/share/swiftly/bin" >> "$GITHUB_PATH"
"$HOME/.local/share/swiftly/bin/swiftly" --version
elif [[ "${RUNNER_OS}" == "macOS" ]]; then
curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && \
installer -pkg swiftly.pkg -target CurrentUserHomeDirectory && \
~/.swiftly/bin/swiftly init --quiet-shell-followup && \
. "${SWIFTLY_HOME_DIR:-$HOME/.swiftly}/env.sh" && \
hash -r

echo "$HOME/.swiftly/bin" >> "$GITHUB_PATH"
"$HOME/.swiftly/bin/swiftly" --version
else
echo "Unknown OS: ${RUNNER_OS}"
exit 1
fi

- name: Install Android NDK
run: |
set -euxo pipefail
OS="$(uname -s | tr '[A-Z]' '[a-z]')"
curl -L -o ndk.zip --retry 3 "https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-${OS}.zip"
unzip -q ndk.zip -d "$HOME"
rm ndk.zip
echo "ANDROID_NDK_HOME=$HOME/android-ndk-${NDK_VERSION}" >> "$GITHUB_ENV"

- name: Install Android Swift SDK and matching host toolchain
# Looks up the Android Swift SDK URL and checksum from the swift.org
# install API and installs it via `swift sdk install`.
run: |
set -euxo pipefail
case "$SWIFT_VERSION" in
nightly-*)
nightly_version="${SWIFT_VERSION#nightly-}"
sdk_json=$(curl -fsSL "https://www.swift.org/api/v1/install/dev/${nightly_version}/android-sdk.json")
snapshot_tag=$(echo "$sdk_json" | jq -r '.[0].dir')
sdk_checksum=$(echo "$sdk_json" | jq -r '.[0].checksum')
if [ "$nightly_version" = "main" ]; then
branch="development"
else
branch="swift-${nightly_version}-branch"
fi
sdk_url="https://download.swift.org/${branch}/android-sdk/${snapshot_tag}/${snapshot_tag}_android.artifactbundle.tar.gz"
;;
*)
releases_json=$(curl -fsSL "https://www.swift.org/api/v1/install/releases.json")
# Pick the highest patch release whose name starts with the
# requested version (e.g. "6.3" -> "6.3.1" if it exists).
latest_version=$(echo "$releases_json" | jq -r --arg v "$SWIFT_VERSION" \
'[.[] | select(.name | startswith($v))]
| sort_by(.name | split(".") | map(tonumber? // 0))
| last
| .name')
if [ -z "$latest_version" ] || [ "$latest_version" = "null" ]; then
echo "Error: no Swift release matching '$SWIFT_VERSION' found in releases.json" >&2
exit 1
fi
sdk_checksum=$(echo "$releases_json" | jq -r --arg v "$latest_version" \
'.[] | select(.name == $v) | .platforms[] | select(.platform == "android-sdk") | .checksum')
snapshot_tag="swift-${latest_version}-RELEASE"
sdk_url="https://download.swift.org/swift-${latest_version}-release/android-sdk/${snapshot_tag}/${snapshot_tag}_android.artifactbundle.tar.gz"
;;
esac

swift_install=${snapshot_tag}
# trim leading "swift-" and trailing "-RELEASE"
swift_install=${swift_install#swift-}
swift_install=${swift_install/-RELEASE/}

echo "Installing Android Swift SDK and host toolchain"
echo " tag: $swift_install"
echo " url: $sdk_url"
echo " checksum: $sdk_checksum"
swiftly install "${swift_install}"
swift sdk install "$sdk_url" --checksum "$sdk_checksum"
swift sdk list

# set environment variables used by swift-android.gradle.kts
echo "SWIFT_VERSION=${swift_install}" >> $GITHUB_ENV
echo "SWIFT_ANDROID_SDK_VERSION=${snapshot_tag#swift-}_android" >> $GITHUB_ENV

- name: Configure Swift Android SDK
run: |
set -euo pipefail
cd $HOME/.swiftpm/swift-sdks/*android*.artifactbundle || cd $HOME/.config/swiftpm/swift-sdks/*android*.artifactbundle || cd $HOME/Library/org.swift.swiftpm/swift-sdks/*android*.artifactbundle
# Link the SDK against the NDK we installed in the previous step.
# Someday we might not need this script, so gracefully skip it
# if it does not exist.
if [[ -x "./swift-android/scripts/setup-android-sdk.sh" ]]; then
"./swift-android/scripts/setup-android-sdk.sh"
fi

- name: Publish swift-java packages to local Maven
# The hashing-lib, weather-lib, and hello-cpp-swift/swift-lib modules
# depend on org.swift.swiftkit:swiftkit-core:1.0-SNAPSHOT, which is not
# published to a public Maven repo. The hashing-lib README documents
# publishing it to mavenLocal from the swift-java checkout that
# SwiftPM resolves into .build/checkouts/swift-java.
working-directory: hello-swift-java/hashing-lib
run: |
set -euxo pipefail
swift package resolve
./.build/checkouts/swift-java/gradlew \
--project-dir .build/checkouts/swift-java \
:SwiftKitCore:publishToMavenLocal

- name: Build hello-swift-raw-jni APK
run: ./gradlew :hello-swift-raw-jni:assembleDebug --stacktrace

- name: Build hello-swift-raw-jni-callback APK
run: ./gradlew :hello-swift-raw-jni-callback:assembleDebug --stacktrace

- name: Build hello-swift-raw-jni-library
run: ./gradlew :hello-swift-raw-jni-library:assembleDebug --stacktrace

- name: Build native-activity APK
run: ./gradlew :native-activity:assembleDebug --stacktrace

- name: Build hello-swift-java APK
run: ./gradlew :hello-swift-java-hashing-app:assembleDebug --stacktrace

- name: Build swift-java-weather-app APK
run: ./gradlew :swift-java-weather-app-weather-app:assembleDebug --stacktrace

- name: Build hello-cpp-swift APK
run: |
cd hello-cpp-swift/cpp-lib
./build-android-static.sh
cd -
./gradlew :hello-cpp-swift:app:assembleDebug --stacktrace

- name: Summarize APK artifacts
if: always()
run: |
echo "## APK Artifacts (${{ matrix.os }} / swift:${{ matrix.swift_version }} / ${{ matrix.sdk_triple }})" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Project | APK | Size |" >> "$GITHUB_STEP_SUMMARY"
echo "|---------|-----|------|" >> "$GITHUB_STEP_SUMMARY"
found=0
while IFS= read -r apk; do
found=1
# Derive a human-readable project name from the path
project=$(echo "$apk" | sed -E 's#^\./##; s#/build/outputs/.*##')
name=$(basename "$apk")
# Human-readable size (du -h works on both Linux and macOS)
size=$(du -h "$apk" | cut -f1 | tr -d '[:space:]')
echo "| \`$project\` | \`$name\` | $size |" >> "$GITHUB_STEP_SUMMARY"
done < <(find . -path '*/build/outputs/apk/*.apk' -type f | sort)
if [ "$found" -eq 0 ]; then
echo "| _(none)_ | — | — |" >> "$GITHUB_STEP_SUMMARY"
fi

- name: Upload APK artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: apks-${{ matrix.os }}-${{ matrix.swift_version }}-${{ matrix.sdk_triple }}-${{ matrix.ndk_version }}
path: '**/build/outputs/apk/**/*.apk'
if-no-files-found: warn

11 changes: 9 additions & 2 deletions hello-cpp-swift/swift-lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,15 @@ def swiftRuntimeLibs = [
"swiftSynchronization"
]

def sdkName = "swift-6.3-RELEASE_android.artifactbundle"
def swiftVersion = "6.3"
// Swift toolchain version passed to swiftly (e.g. "6.3", "main-snapshot").
// Can be overridden via the SWIFT_VERSION environment variable, which is
// useful for CI matrices that test multiple toolchains.
def swiftVersion = System.getenv("SWIFT_VERSION") ?: "6.3"
// Android Swift SDK artifactbundle suffix. Substituted into the bundle
// directory name as "swift-${androidSdkVersion}.artifactbundle". Can be
// overridden via the SWIFT_ANDROID_SDK_VERSION environment variable.
def androidSdkVersion = System.getenv("SWIFT_ANDROID_SDK_VERSION") ?: "${swiftVersion}-RELEASE_android"
def sdkName = "swift-${androidSdkVersion}.artifactbundle"
def minSdk = android.defaultConfig.minSdkVersion.apiLevel

def abis = [
Expand Down
11 changes: 9 additions & 2 deletions hello-swift-java/hashing-lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,15 @@ def swiftRuntimeLibs = [
"swiftSynchronization"
]

def sdkName = "swift-6.3-RELEASE_android.artifactbundle"
def swiftVersion = "6.3"
// Swift toolchain version passed to swiftly (e.g. "6.3", "main-snapshot").
// Can be overridden via the SWIFT_VERSION environment variable, which is
// useful for CI matrices that test multiple toolchains.
def swiftVersion = System.getenv("SWIFT_VERSION") ?: "6.3"
// Android Swift SDK artifactbundle suffix. Substituted into the bundle
// directory name as "swift-${androidSdkVersion}.artifactbundle". Can be
// overridden via the SWIFT_ANDROID_SDK_VERSION environment variable.
def androidSdkVersion = System.getenv("SWIFT_ANDROID_SDK_VERSION") ?: "${swiftVersion}-RELEASE_android"
def sdkName = "swift-${androidSdkVersion}.artifactbundle"
def minSdk = android.defaultConfig.minSdkVersion.apiLevel
/**
* Android ABIs and their Swift triple mappings
Expand Down
12 changes: 9 additions & 3 deletions swift-android.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ data class SwiftConfig(
var releaseExtraBuildFlags: List<String> = emptyList(),
var swiftlyPath: String? = null, // Optional custom swiftly path
var swiftSDKPath: String? = null, // Optional custom Swift SDK path
var swiftVersion: String = "6.3", // Swift version
var androidSdkVersion: String = "6.3-RELEASE_android" // SDK version
// Swift toolchain version passed to swiftly (e.g. "6.3", "main-snapshot").
// Can be overridden via the SWIFT_VERSION environment variable, which is
// useful for CI matrices that test multiple toolchains.
var swiftVersion: String = System.getenv("SWIFT_VERSION")?.takeIf { it.isNotEmpty() } ?: "6.3",
// Android Swift SDK artifactbundle suffix. Substituted into the bundle
// directory name as "swift-${androidSdkVersion}.artifactbundle". Can be
// overridden via the SWIFT_ANDROID_SDK_VERSION environment variable.
var androidSdkVersion: String = System.getenv("SWIFT_ANDROID_SDK_VERSION")?.takeIf { it.isNotEmpty() } ?: "${swiftVersion}-RELEASE_android"
)

// Architecture definitions
Expand Down Expand Up @@ -296,4 +302,4 @@ project.afterEvaluate {
} else {
throw GradleException("Android extension not found. Make sure to apply this script after the Android plugin.")
}
}
}
11 changes: 9 additions & 2 deletions swift-java-weather-app/weather-lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,15 @@ def swiftRuntimeLibs = [
"_FoundationICU",
"swiftSynchronization"
]
def sdkName = "swift-6.3-RELEASE_android.artifactbundle"
def swiftVersion = "6.3"
// Swift toolchain version passed to swiftly (e.g. "6.3", "main-snapshot").
// Can be overridden via the SWIFT_VERSION environment variable, which is
// useful for CI matrices that test multiple toolchains.
def swiftVersion = System.getenv("SWIFT_VERSION") ?: "6.3"
// Android Swift SDK artifactbundle suffix. Substituted into the bundle
// directory name as "swift-${androidSdkVersion}.artifactbundle". Can be
// overridden via the SWIFT_ANDROID_SDK_VERSION environment variable.
def androidSdkVersion = System.getenv("SWIFT_ANDROID_SDK_VERSION") ?: "${swiftVersion}-RELEASE_android"
def sdkName = "swift-${androidSdkVersion}.artifactbundle"
def minSdk = android.defaultConfig.minSdkVersion.apiLevel
/**
* Android ABIs and their Swift triple mappings
Expand Down