Skip to content

Instantly share code, notes, and snippets.

@JUSTINMKAUFMAN
Created November 3, 2021 18:53
Show Gist options
  • Save JUSTINMKAUFMAN/6627c3c8571563b36efc9c832f6fa2b1 to your computer and use it in GitHub Desktop.
Save JUSTINMKAUFMAN/6627c3c8571563b36efc9c832f6fa2b1 to your computer and use it in GitHub Desktop.
Kotlin Multiplatform Build Gradle Task for iOS/macOS Universal Framework
/**
* Kotlin Multiplatform Universal XCFramework Task
*
* Description:
* If your project needs to target *all* possible
* iOS and macOS variants (including Apple Silicon
* hardware, ARM64 iPhone simulators, and so on),
* this task will build the frameworks, lipo those
* that can be combined into a single binary, and
* place them where Xcode expects to find them in
* an XCFramework bundle.
*
* Usage:
* Copy everything in this file to the bottom of
* your target project's build.gradle.kts file
* and execute the `buildUniversalFramework` task
* (which you can find in the "Swift" task group)
*/
// You can change this to a more Apple-friendly
// version of your project name (e.g. title case)
val frameworkName: String = project.name
val clearGeneratedFrameworks = tasks.create("clearGeneratedFrameworks") {
group = "Swift"
description = "Removes all Apple framework build products generated by these custom tasks"
delete(layout.buildDirectory.dir("bin/ios-arm64-simulator_x86_64"))
delete(layout.buildDirectory.dir("bin/macos-arm64_x86_64"))
delete(layout.buildDirectory.dir("framework"))
}
val lipoAppleFrameworks = tasks.register("lipoAppleFrameworks") {
group = "Swift"
description = "Combine equivalent Apple platform target binaries with lipo"
dependsOn(duplicateMacosArm64Framework)
doLast {
val macosArm64OutputFile = buildDir.resolve("bin/macosArm64/releaseFramework/${frameworkName}.framework/Versions/A/${frameworkName}")
val macosX64OutputFile = buildDir.resolve("bin/macosX64/releaseFramework/${frameworkName}.framework/Versions/A/${frameworkName}")
val iosSimulatorArm64OutputFile = buildDir.resolve("bin/iosSimulatorArm64/releaseFramework/${frameworkName}.framework/${frameworkName}")
val iosX64OutputFile = buildDir.resolve("bin/iosX64/releaseFramework/${frameworkName}.framework/${frameworkName}")
mkdir("build/bin/macos-arm64_x86_64")
exec {
commandLine(
"lipo",
"-create",
macosArm64OutputFile.path,
macosX64OutputFile.path,
"-output",
buildDir.resolve("${frameworkName}MacOS").path
)
}
copy {
from(layout.buildDirectory.file("${frameworkName}MacOS"))
rename { frameworkName }
into(layout.buildDirectory.dir("bin/macos-arm64_x86_64/${frameworkName}.framework/Versions/A"))
}
delete(layout.buildDirectory.file("${frameworkName}MacOS"))
mkdir("build/bin/ios-arm64-simulator_x86_64")
exec {
commandLine(
"lipo",
"-create",
iosSimulatorArm64OutputFile.path,
iosX64OutputFile.path,
"-output",
buildDir.resolve("${frameworkName}iOS").path
)
}
copy {
from(layout.buildDirectory.dir("bin/iosSimulatorArm64/releaseFramework"))
into(layout.buildDirectory.dir("bin/ios-arm64-simulator_x86_64"))
}
copy {
from(layout.buildDirectory.file("${frameworkName}iOS"))
rename { frameworkName }
into(layout.buildDirectory.dir("bin/ios-arm64-simulator_x86_64/${frameworkName}.framework"))
}
delete(layout.buildDirectory.file("${frameworkName}iOS"))
}
}
val duplicateMacosArm64Framework by tasks.creating(Exec::class.java) {
group = "Swift"
description = "Creates a duplicate macOS ARM64 build to hold the combined macOS library"
dependsOn(
clearGeneratedFrameworks,
"linkReleaseFrameworkIosSimulatorArm64",
"linkReleaseFrameworkIosArm64",
"linkReleaseFrameworkIosX64",
"linkReleaseFrameworkMacosArm64",
"linkReleaseFrameworkMacosX64"
)
workingDir = buildDir
executable = "sh"
args("-c", """
cp -R \
${buildDir.resolve("bin/macosArm64/releaseFramework/").path} \
${buildDir.resolve("bin/macos-arm64_x86_64/").path}
""".trimIndent()
)
}
val buildUniversalFramework by tasks.creating(Exec::class.java) {
group = "Swift"
description = "Build a Universal XCFramework for Apple platforms"
workingDir = buildDir
executable = "sh"
mkdir("build/framework")
dependsOn(
lipoAppleFrameworks,
"linkReleaseFrameworkIosX64",
"linkReleaseFrameworkIosArm64",
"linkReleaseFrameworkMacosX64"
)
args(
"-c",
listOf(
"""xcodebuild \""",
"""-create-xcframework \""",
"""-framework ${buildDir.resolve("bin/iosArm64/releaseFramework/${frameworkName}.framework").path} \""",
"""-framework ${buildDir.resolve("bin/ios-arm64-simulator_x86_64/${frameworkName}.framework").path} \""",
"""-framework ${buildDir.resolve("bin/macos-arm64_x86_64/${frameworkName}.framework").path} \""",
"-output ${buildDir.path + "/framework/${frameworkName}.xcframework"}"
)
.joinToString("\n")
)
}
@JUSTINMKAUFMAN
Copy link
Author

To be clear, these tasks expect the following targets to be defined in the kotlin block of your project:

kotlin {
   iosx64()
   iosArm64()
   iosSimulatorArm64()
   macosX64()
   macosArm64()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment