Compose an Xcode IDE Project
You can open this sample inside an IDE using the IntelliJ native importer or Eclipse Buildship. |
This sample shows how to use the Xcode IDE Plugin to create an Xcode project for 3rd party plugins. In this sample, we show how to integrate a JNI library implemented in Java and Objective-C with Xcode. The library has no dependencies, and the build has a minimal configuration.
build.gradle
import dev.nokee.language.nativebase.tasks.NativeSourceCompile
import dev.nokee.platform.jni.JvmJarBinary
import dev.nokee.platform.nativebase.SharedLibraryBinary
plugins {
id 'java'
id 'dev.nokee.jni-library'
id 'dev.nokee.objective-c-language'
id 'dev.nokee.xcode-ide-base'
}
library.variants.configureEach {
sharedLibrary.linkTask.configure {
linkerArgs.add('-lobjc')
}
}
//region Helper methods
Provider<JvmJarBinary> getJvmBinary() {
return library.binaries.withType(JvmJarBinary).elements.map { it.first() }
}
Provider<SharedLibraryBinary> getSharedLibraryBinary() {
return library.binaries.withType(SharedLibraryBinary).elements.map { it.first() }
}
Transformer<String, SharedLibraryBinary> asHeaderSearchPaths() {
return {
it.compileTasks.withType(NativeSourceCompile).get().first().headerSearchPaths.get().collect { "\"${it.asFile.absolutePath}\"" }.join(' ')
}
}
//endregion
xcode {
projects.register('jni-greeter') {
targets.register('JniJar') {
productReference = jvmBinary.flatMap { it.jarTask.get().archiveFileName }
productType = productTypes.of('com.apple.product-type.library.java.archive')
buildConfigurations.register('Default') {
productLocation = jvmBinary.flatMap { it.jarTask.get().archiveFile }
buildSettings.put('PRODUCT_NAME', ideTarget.productName)
}
sources.from(fileTree('src/main/java') { include('**/*.java') })
}
targets.register('JniSharedLibrary') {
productReference = sharedLibraryBinary.map { it.linkTask.get().linkedFile.get().asFile.getName() }
productType = productTypes.dynamicLibrary
buildConfigurations.register('Default') {
productLocation = sharedLibraryBinary.flatMap { it.linkTask.get().linkedFile }
buildSettings.put('HEADER_SEARCH_PATHS', sharedLibraryBinary.map(asHeaderSearchPaths()))
buildSettings.put('PRODUCT_NAME', ideTarget.productName)
}
sources.from(fileTree('src/main/objc') { include('**/*.m') })
sources.from(fileTree('src/main/headers') { include('**/*.h') })
}
}
}
build.gradle.kts
import dev.nokee.language.nativebase.tasks.NativeSourceCompile
import dev.nokee.platform.jni.JvmJarBinary
import dev.nokee.platform.nativebase.SharedLibraryBinary
plugins {
id("java")
id("dev.nokee.jni-library")
id("dev.nokee.objective-c-language")
id("dev.nokee.xcode-ide-base")
}
library.variants.configureEach {
sharedLibrary.linkTask.configure {
linkerArgs.add("-lobjc")
}
}
//region Helper methods
fun getJvmBinary(): Provider<JvmJarBinary> {
return library.binaries.withType(JvmJarBinary::class.java).elements.map { it.first() }
}
fun getSharedLibraryBinary(): Provider<SharedLibraryBinary> {
return library.binaries.withType(SharedLibraryBinary::class.java).elements.map { it.first() }
}
fun asHeaderSearchPaths(): (SharedLibraryBinary) -> String {
return {
it.compileTasks.withType(NativeSourceCompile::class.java).get().first().headerSearchPaths.get().map { "\"${it.asFile.absolutePath}\"" }.joinToString(" ")
}
}
//endregion
xcode {
projects.register("jni-greeter") {
targets.register("JniJar") {
productReference.set(getJvmBinary().flatMap { it.jarTask.get().archiveFileName })
productType.set(productTypes.of("com.apple.product-type.library.java.archive"))
buildConfigurations.register("Default") {
productLocation.set(getJvmBinary().flatMap { it.jarTask.get().archiveFile })
buildSettings.put("PRODUCT_NAME", ideTarget.productName)
}
sources.from(fileTree("src/main/java") { include("**/*.java") })
}
targets.register("JniSharedLibrary") {
productReference.set(getSharedLibraryBinary().map { it.linkTask.get().linkedFile.get().asFile.getName() })
productType.set(productTypes.dynamicLibrary)
buildConfigurations.register("Default") {
productLocation.set(getSharedLibraryBinary().flatMap { it.linkTask.get().linkedFile })
buildSettings.put("HEADER_SEARCH_PATHS", getSharedLibraryBinary().map(asHeaderSearchPaths()))
buildSettings.put("PRODUCT_NAME", ideTarget.productName)
}
sources.from(fileTree("src/main/objc") { include("**/*.m") })
sources.from(fileTree("src/main/headers") { include("**/*.h") })
}
}
}
To generate the Xcode workspace and project:
$ ./gradlew xcode > Task :xcode Generated Xcode workspace at file://xcode-ide-composing.xcworkspace/ BUILD SUCCESSFUL 3 actionable tasks: 3 executed
We can validate from the command line the generated workspace is buildable by Xcode IDE using the xcodebuild
tool:
$ xcodebuild -workspace xcode-ide-composing.xcworkspace -scheme JniJar note: Using new build system note: Planning build note: Constructing build description ExternalBuildToolExecution JniJar (in target 'JniJar' from project 'jni-greeter') cd ~ ~/gradlew -Pdev.nokee.internal.xcode.bridge.ACTION= -Pdev.nokee.internal.xcode.bridge.PRODUCT_NAME=xcode-ide-composing.jar -Pdev.nokee.internal.xcode.bridge.CONFIGURATION=Default -Pdev.nokee.internal.xcode.bridge.BUILT_PRODUCTS_DIR=/Users/daniel/Library/Developer/Xcode/DerivedData/xcode-ide-composing/Build/Products/Default -Pdev.nokee.internal.xcode.bridge.PROJECT_NAME=jni-greeter -Pdev.nokee.internal.xcode.bridge.TARGET_NAME=JniJar --init-script ~/.gradle/repo.init.gradle --init-script ~/.gradle/init.gradle :_xcode___jni-greeter_JniJar_Default > Task :compileJava > Task :processResources NO-SOURCE > Task :classes > Task :compileObjectiveC > Task :link > Task :jar > Task :_xcode___jni-greeter_JniJar_Default BUILD SUCCESSFUL 5 actionable tasks: 5 executed ** BUILD SUCCEEDED **
We can open the workspace through Gradle using the openXcode
task.
From Xcode, we can perform a build which delegates to Gradle:
We can also navigate to symbols:
For more information, see Xcode IDE Plugin and Developing with Xcode IDE chapters.