How to setup Xcode Cloud with Kotlin Multiplatform (KMM/KMP)
When starting with Kotlin Multiplatform, it can get a little bit overwhelming with all the new changes you have to make to your project. You also might be tempted to move all the CI/CD logic into Github actions (centralized for iOS and Android) but it's a pity to leave the generous 25 hours of allowance that Xcode Cloud offers.
Here's a straightforward guide to enabling Xcode Cloud to build your Kotlin Multiplatform project:
Step 1: Project or Workspace Configuration
Set the project path for your workflow to include the iOSApp
folder. This can be done either from the Xcode Cloud website or within Xcode:
- Go to
Xcode -> Integrate -> Manage Workflow -> Select the workflow -> General
. - Set the path to include the
iOSApp
folder (e.g.,/iosApp/Example.xcodeproj
).
Replace iosApp
with the name of your iOS folder in the Kotlin Multiplatform project.
Step 2: CI Script Setup
Create a folder named ci_scripts
in the root directory of your iOS project. This folder will contain scripts to run before or after the build process. Inside ci_scripts
, create a file named ci_post_clone.sh
with the following script (credit):
#!/bin/sh
root_dir=$CI_WORKSPACE_PATH
repo_dir=$CI_PRIMARY_REPOSITORY_PATH
jdk_dir="${CI_DERIVED_DATA_PATH}/JDK"
gradle_dir="${repo_dir}/Common"
cache_dir="${CI_DERIVED_DATA_PATH}/.gradle"
jdk_version="20.0.1"
# Check if we stored gradle caches in DerivedData.
recover_cache_files() {
echo "\nRecover cache files"
if [ ! -d $cache_dir ]; then
echo " - No valid caches found, skipping"
return 0
fi
echo " - Copying gradle cache to ${gradle_dir}"
rm -rf "${gradle_dir}/.gradle"
cp -r $cache_dir $gradle_dir
return 0
}
# Install the JDK
install_jdk_if_needed() {
echo "\nInstall JDK if needed"
if [[ $(uname -m) == "arm64" ]]; then
echo " - Detected M1"
arch_type="macos-aarch64"
else
echo " - Detected Intel"
arch_type="macos-x64"
fi
# Location of version / arch detection file.
detect_loc="${jdk_dir}/.${jdk_version}.${arch_type}"
if [ -f $detect_loc ]; then
echo " - Found a valid JDK installation, skipping install"
return 0
fi
echo " - No valid JDK installation found, installing..."
tar_name="jdk-${jdk_version}_${arch_type}_bin.tar.gz"
# Download and un-tar JDK to our defined location.
curl -OL "https://download.oracle.com/java/20/archive/${tar_name}"
tar xzf $tar_name -C $root_dir
# Move the JDK to our desired location.
rm -rf $jdk_dir
mkdir -p $jdk_dir
mv "${root_dir}/jdk-${jdk_version}.jdk/Contents/Home" $jdk_dir
# Some cleanup.
rm -r "${root_dir}/jdk-${jdk_version}.jdk"
rm $tar_name
# Add the detection file for subsequent builds.
touch $detect_loc
echo " - Set JAVA_HOME in Xcode Cloud to ${jdk_dir}/Home"
return 0
}
recover_cache_files
install_jdk_if_needed
This script installs JDK in the environment to compile the shared logic part of KMM. Run the workflow and look for the message in the logs:
Set JAVA_HOME in Xcode Cloud to ${jdk_dir}/Home
Copy the JAVA_HOME
value (e.g., /Volumes/workspace/DerivedData/JDK/Home
) for the next step.
Step 3: Environment Variable Configuration
In Xcode Cloud, select the workflow and add a new environment variable:
JAVA_HOME=/Volumes/workspace/DerivedData/JDK/Home
Run the workflow, and your app should build successfully.
That's it! Happy coding!