Marcel
11/18/2024, 4:27 PMembedAndSignAppleFrameworkForXcode
and an alternative command that only reuses the previously built framework? Right now it's rebuilding the whole framework every time we press run, even if it's not really needed because we just built it.
I'm trying to achieve something like this from our iOS Run Script and switch the schema/configuration when we don't want to rebuild the framework:
if [ "$CONFIGURATION" = "Prebuilt framework (debug)" ];
then
echo "✅ Skipping KMP framework rebuild"
exit 0
fi
echo "⚙️ Not skipping KMP framework rebuild"
cd "$SRCROOT/../.."
./gradlew :umbrella:embedAndSignAppleFrameworkForXcode
Nicklas Jensen
11/18/2024, 5:36 PM#!/usr/bin/env bash
set -e
GENERATED_DIR="$SRCROOT/Generated"
XCFILELIST="$GENERATED_DIR/shared.xcfilelist"
echo "Generating list of Kotlin files for iOS in: $XCFILELIST"
mkdir -p "$GENERATED_DIR"
cd "$SRCROOT/../shared/src/commonMain" && find ~+ -type f > "$XCFILELIST"
cd "$SRCROOT/../shared/src/iosMain" && find ~+ -type f >> "$XCFILELIST"
This makes it so Xcode is not even calling out to Gradle to build the KMP framework, if none of the files making up the framework has changed.
Since you're using an umbrella framework, you might have to expand the content of the file list to include all of your KMP modules.Marcel
11/19/2024, 7:05 AMembedAndSignAppleFrameworkForXcode
to generate the framework, or something different? Would you mind sharing how you generate the framework and define the right output file?
This is how my script and input/output options currently look, but they are not working 🤔Nicklas Jensen
11/19/2024, 1:10 PMpackForXcode
task like this:
val packForXcode by tasks.creating(Sync::class) {
val mode = System.getenv("KOTLIN_FRAMEWORK_BUILD_TYPE") ?: System.getenv("CONFIGURATION") ?: "DEBUG"
val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator18.0"
val framework = kotlin.targets.getByName<KotlinNativeTarget>("ios").binaries.getFramework(mode)
val targetDir = layout.buildDirectory.dir("xcode-frameworks")
group = "build"
dependsOn(framework.linkTaskProvider)
inputs.property("mode", mode)
inputs.property("sdkName", sdkName)
from({ framework.outputDirectory })
into(targetDir)
}
tasks.getByName("build").dependsOn(packForXcode)
This uses environment variables passed by Xcode (CONFIGURATION
, SDK_NAME
) to determine which iOS target to build for, ensuring seamless transition between running on device and on the iPhone Simulator. The Xcode Build Phases has the side-effect of requiring the presence of the built framework to even consider starting the build, so we use a script with the following code when setting up a new machine (and in CI):
#!/usr/bin/env bash
export CONFIGURATION="${CONFIGURATION:=Debug}"
export PLATFORM_NAME="${PLATFORM_NAME:=iphonesimulator}"
export SDK_NAME="${SDK_NAME:=${PLATFORM_NAME}${IPHONE_SDK_VERSION}}"
echo "${_GREEN}[Gradle] Building shared${_RESET}"
echo " NATIVE_ARCH=$NATIVE_ARCH"
echo " CONFIGURATION=$CONFIGURATION"
echo " PLATFORM_NAME=$PLATFORM_NAME"
echo " SDK_NAME=$SDK_NAME"
echo ""
echo " $ ./gradlew :shared:packForXcode"
echo ""
./gradlew :shared:packForXcode
echo ""
In CI we pass the same environment variables as we expect the build to use, so we don't end up first building the framework for Debug
, only to build it for Release
when building the Xcode application right after.Nicklas Jensen
11/19/2024, 1:13 PMembedAndSignAppleFrameworkForXcode
task, so I don't know if that would solve the same issue.Marcel
11/21/2024, 8:28 AM