Skip to content

Instantly share code, notes, and snippets.

@ntherning
Last active October 27, 2020 09:02
Show Gist options
  • Save ntherning/dafd5979bdfb660bbf516066ca056931 to your computer and use it in GitHub Desktop.
Save ntherning/dafd5979bdfb660bbf516066ca056931 to your computer and use it in GitHub Desktop.
Kotlin MPP iOS buildForXcode task - only build the configuration requested by Xcode and only invoke Gradle when needed

In your Xcode project add a Run Script phase with the following commands:

cd "$SRCROOT/.."
./gradlew :common:buildForXcode

Tick the Use discovered dependency file box and enter this as the path: $(SRCROOT)/../common/build/bin/ios/$(CONFIGURATION)-$(PLATFORM_NAME)-deps.d

Add the following path to your project's Framework Search Paths (both Debug and Release) under Build Settings:

$(SRCROOT)/../common/build/bin/ios/$(CONFIGURATION)-$(PLATFORM_NAME)

Last step is to add the common code framework to Frameworks, Libraries and Embedded Content under your project's General settings. Build the common code once by manually running the buildForXcode Gradle task. Then locate the built .framework folder under common/build/bin/ios/Debug-iphonesimulator/ and add it to this section in your Xcode project. Unfortunately there's no official way to tell Xcode to change this path depending on the values of the $CONFIGURATION and $PLATFORM_NAME build variables. We now have to modify the project's project.pbxproj file manually in a text editor. Open this file and locate the Begin PBXFileReference section section. There should be an entry in that section looking something like:

    850863702548163B006C970C /* ... */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SpamdrainCommon.framework; path = "../common/build/bin/ios/Debug-iphonesimulator/MyFramework.framework"; sourceTree = "<group>"; };

Change this to

    850863702548163B006C970C /* ... */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SpamdrainCommon.framework; path = "../common/build/bin/ios/$(CONFIGURATION)-$(PLATFORM_NAME)/MyFramework.framework"; sourceTree = "<group>"; };
kotlin {
val isIosDevice = getenv("PLATFORM_NAME")?.startsWith("iphoneos") == true
val iosTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget = if (isIosDevice) ::iosArm64 else ::iosX64
fun iosBuildDir(isDebug: Boolean, isDevice: Boolean): File {
return project.buildDir.resolve(buildString {
append("bin/ios/")
append(if (isDebug) "Debug" else "Release")
append("-")
append(if (isDevice) "iphoneos" else "iphonesimulator")
})
}
iosTarget("ios") {
binaries {
framework {
baseName = ...
outputDirectory = iosBuildDir(buildType == NativeBuildType.DEBUG, isIosDevice)
}
}
...
}
...
tasks.create("buildForXcode") {
val isDebug = getenv("CONFIGURATION")?.toLowerCase() != "release"
val linkTask = project.tasks.findByName(if (isDebug) "linkDebugFrameworkIos" else "linkReleaseFrameworkIos")!!
dependsOn(linkTask)
inputs.files(*linkTask.outputs.files.files.toTypedArray())
val buildDir = iosBuildDir(isDebug, isIosDevice)
val xcodeDepsFile = File(buildDir.parentFile, "${buildDir.name}-deps.d")
outputs.file(xcodeDepsFile)
doLast {
fun getGradleBuildFiles(p: Project): Set<File> {
return setOf(p.buildFile) + (p.parent?.let { getGradleBuildFiles(it) } ?: emptySet())
}
fun getTaskDependencies(t: Task): Set<Task> {
return gradle.taskGraph.getDependencies(t).let { s ->
s + s.flatMap { getTaskDependencies(it) }
}
}
val inputs = (getGradleBuildFiles(project) + getTaskDependencies(this).flatMap { it.inputs.files }).sorted()
xcodeDepsFile.writeText("dependencies: \\\n " + inputs.joinToString(separator = " \\\n ") + "\n")
println("Generated Xcode dependencies file ${xcodeDepsFile}")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment