I want to integrate C code into a Kotlin Multiplatform Mobile project using the cinterop tool. I already spent some time on the documentation on Kotlin Multiplatform and Kotlin/Native but I can't seem to get it working.
Here is my build.gradle.kts:
kotlin {
android {
publishLibraryVariants("release", "debug")
publishLibraryVariantsGroupedByFlavor = true
}
ios()
androidNativeArm32 {
compilations.getByName("main") {
val myInterop by cinterops.creating {
defFile(project.file("foobar.def"))
packageName("org.sample")
}
}
}
...
}
I want to do the same for the native iOS part.
Watching the gradle output, it seems to me that the cinterop configuration is completely ignored. It doesn't matter whether the def file exists or not. It does not make a difference.
Sorry, if I miss the obvious here. I am a bit confused by all the different Kotlin extensions and especially how they are supposed to work together.
What am I missing in my configuration? Is it even possible to use cinterop in a KMM project.
Related
I hope someone can explain this to me.
I was moving some code from a Gradle build file into a Gradle plugin.
Below are two code snippes calling the same from function (based on Ideas indexing)
I noticed that there are some strange differences between how the apis can be used in those two contexts.
I know that Gradle is adding some extra syntax suger around the build files which is why I need to manually cast the task in the Plugin.kt file, but I cannot find anything that explains why from in context of the Build file has this as context where in the plugin the function uses it to access the into function.
It is not just Idea that reports this, running Gradle also shows that it must be like this.
I assume this is something special to Kotlin's way of handling the Action interface in different contexts:
kts file (No wrapping class)
kt file (with class)
Here are the two samples
hostedStaticFiles is gradle configuration that will be used to configure web frontend from a separate build.
build.gradle.kts
tasks.getByName<ProcessResources>("processResources") {
this.from(hostedStaticFiles) {
this#from.into("static") // <-- Note use of this here
}
}
Plugin.kt
project.tasks.getByName("processResources").let<Task, ProcessResources> {
if (it !is ProcessResources) {
throw IllegalStateException("The processResources task in Project is not of type ${ProcessResources::class.java}")
}
it
}.apply {
dependsOn(hostedStaticFiles)
this#apply.from(hostedStaticFiles) { it -> // <-- Note use of it here and below
it.into("static")
}
}
dependencies {
hostedStaticFiles(project("client"))
}
I hobe someone can point me to an explanation or preferably documentation on why this behaves this way :)
Gradle version 7.4.1
###################
After getting the answer from #Joffrey I updated my buildSrc/build.gradle.kts with the below plugin configuration and it all started working as expected.
plugins {
`java-gradle-plugin`
`kotlin-dsl`
}
Gradle uses the HasImplicitReceiver annotation on some function types (like Action), so you can use this instead of it. It leverages Kotlin's SAM-with-receiver compiler plugin.
In Kotlin build scripts (.gradle.kts files) you benefit from this automatically because the Kotlin compiler used to compile your scripts is already properly configured. However, in custom plugin projects, you are in control of the build and you need to apply the kotlin-dsl plugin yourself. As mentioned in the documentation, it does a few things for you, including:
Configures the Kotlin compiler with the same settings that are used for Kotlin DSL scripts, ensuring consistency between your build logic and those scripts.
I am trying to write tests in the shared module of a KMM project. In the shared module's build.gradle.kts file I have the following:
sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
//TODO: Hamcrest
//implementation("junit:junit:4.12")
//implementation("org.hamcrest:hamcrest-library:1.3")
//TODO: Mockk
//implementation("io.mockk:mockk:1.10.4")
}
}
//...
}
I have also tried:
implementation(kotlin("[library]"))
with the same result: The tests are no longer recognised by the IDE and I cannot run them.
Unfortunately there isn't a mocking library that has K/N support (AFAIK).
Here's Mockk's K/N and Mockk's K/JS issue for future reference or you could also check out Touchlab's Karmok
For Hamcrest, see their issue here
Edit/Update
In 2022 above answer doesn't stand true anymore. Mockk now supports mocking in shared modules. Please check here mockk.io Add the following dependency and you should be good to go
testImplementation "io.mockk:mockk-common:{version}"
The mocking experience is seamless, just like a regular Android unit test case.
I got a response from the KMM team - thought I would put it here for reference
You can use only multiplatform dependencies that support all declared targets in common source-set, because this could will be used for compilation for all the targets. Junit is not a multiplatform library, it’s JVM, so you should add it to your jvm target source-set (androidMain if you declared android() target). Check this project: https://github.com/Kotlin/kmm-sample/blob/master/shared/build.gradle.kts for example.
The same issue relates to other dependencies - they are not multiplatform, so you can’t use them in a commons source-set.
Trying to figure out what I'm doing wrong. I created a small video of exactly what I'm doing in IntelliJ on Windows.
https://www.youtube.com/watch?v=nIH_55Zbxus&feature=youtu.be
And I'll describe it here in text.
Create a new project
Tick the Gradle > Kotlin/JS for browser template and untick everything else
Add implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8' to the dependencies block in the build.gradle.
Sync the gradle files
Attempt to use something from the kotlinx.coroutines namespace
Hopefully its just a silly thing I'm missing. I expected to just have to add the coroutines library to be able to actually import it. It looks like the library is listed in the project structure for the main module so I'm not sure what else might be wrong. Here is a repo of the project too.
plugins {
id 'org.jetbrains.kotlin.js' version '1.3.72'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-js"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
testImplementation "org.jetbrains.kotlin:kotlin-test-js"
}
kotlin.target.browser { }
You should add kotlinx-coroutines-core-js dependency. See the documentation: https://github.com/Kotlin/kotlinx.coroutines#js
I'm trying to call Swift/Objective-C code from Kotlin in a multiplatform project. There are no problems with calls to platform code. But when I'm trying to call some library (or framework, not sure how it is properly called as I'm not an iOS dev) it fails. Docs states that it is possible to call Objective-C code and Swift if it is properly exported:
Kotlin/Native provides bidirectional interoperability with Objective-C. Objective-C frameworks and libraries can be used in Kotlin code if properly imported to the build (system frameworks are imported by default). See e.g. "Using cinterop" in Gradle plugin documentation. A Swift library can be used in Kotlin code if its API is exported to Objective-C with #objc. Pure Swift modules are not yet supported.
But it does not say anything about how can I import them properly. It only point to gradle plugin description that describes old version of gradle plugin. So it does not work for me. Finally I figured out something might be the way to import Objective-C code:
fromPreset(presets.iosX64, 'ios') {
compilations.main.outputKinds('FRAMEWORK')
compilations.main {
cinterops {
firebase {
def pods = '${System.getProperty("user.home")}/Projects/kmpp/iosApp/Pods/'
includeDirs '${pods}Firebase/CoreOnly/Sources',
'${pods}FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers'
}
}
}
}
Build runs without failures, but it does not import anything. What am I doing wrong? Is it possible to import such a lib at all?
UPD:
here I found an example of usage cinterop tool like this:
cd samples/gitchurn
../../dist/bin/cinterop -def src/main/c_interop/libgit2.def \
-compilerOpts -I/usr/local/include -o libgit2
It looks like cinterop tool should be in /dist/bin/ folder in my projects but there is no such folder. Where do I get cinterop tool ?
I ended up with this cinterops section in build.gradle
fromPreset(presets.iosX64, 'ios') {
// This preset is for iPhone emulator
// Switch here to presets.iosArm64 (or iosArm32) to build library for iPhone device
compilations.main {
outputKinds('FRAMEWORK')
cinterops {
firebase {
defFile "$projectDir/src/iosMain/cinterop/firebase.def"
includeDirs {
allHeaders "$projectDir/../iosApp/Pods/FirebaseCore/Firebase/Core/Public",
"$projectDir/../iosApp/Pods/FirebaseDatabase/Firebase/Database/Public"
}
compilerOpts "-F$projectDir/../iosApp/Pods/Firebase -F$projectDir/../iosApp/Pods/FirebaseCore -F$projectDir/../iosApp/Pods/FirebaseDatabase"
linkerOpts "-F$projectDir/../iosApp/Pods/Firebase -F$projectDir/../iosApp/Pods/FirebaseCore -F$projectDir/../iosApp/Pods/FirebaseDatabase"
}
}
}
}
end this .def file:
language = Objective-C
headers = FirebaseCore.h FirebaseDatabase.h
What's going on here? Cocopods frameworks are placed in Pods directory in your Xcode project. Navigating this folder a bit, you'll find what you need. I'm not sure if there is some standard but firebase place main header file in Public folder. And it contains references to other header files it needs... So you specify these file names in your .def file in the headers section.
Next, you need to specify where to look for these files and others referenced by them. You can do it in the .def file in includeDirs or in build.gradle file. I prefer to use the build.gradle file as it can use variables. So you specify the path to these Public folders. (This is enough for kotlin to see library API, but in order to be able to run the app you need to compile and link this library...)
Then the compiler and linker need to know where library/framework is. So you specify path to root folder of the framework in compilerOpts and linkerOpts prefixing them with -F if it is a framework or -L if it is a library.
It looks like you are going to use a cocoapods library. Currently the Gradle plugin has no support for cocapods out of the box. But may be a dependency on your library can be configured "manully". Could you please share a link to your project?
This may seem like an odd question. I want to compile LESS into CSS without installing a plug-in into my local gradle. There are several gradle plug-ins out there that compile LESS into CSS, which is great, but I would like to compile and use the plug-ins at runtime rather than install them prior.
Is this possible? If there was a standard plug-in like for CoffeeScript, it would be no big deal and easy, but there isn't one for LESS. I'm rather new to Gradle, so I'm unsure of how to move forward. The obvious solution is to bloody install the plug-in, but given a constraint that I can't, is it possible to compile/use it at runtime?
https://github.com/koenongena/lesscss-gradle-plugin
https://github.com/skhome/gradle-less-plugin
https://github.com/msgilligan/gradle-lesscss-plugin
In most cases you don't install plugins into your Gradle installation. Rather, you declare the usage of the plugin as part of your build.gradle. At build runtime, it then resolves any plugins that are needed for your build script, loads them, and then runs the build. The particular plugins you listed are a bit unusual in that (as far as I can tell) they haven't yet been published in a publicly accessible Maven repository (such as Maven Central or Bintray). After a quick search, I found one that appears to be in Maven Central:
https://github.com/obecker/gradle-lesscss-plugin
To use it, you don't need to install anything ahead of time. Instead, you declare it in your build.gradle like this:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'de.obqo.gradle:gradle-lesscss-plugin:1.0-1.3.3'
}
}
apply plugin: 'lesscss'
If you don't want to use a plugin, you can pretty easily compile LESS files in Gradle without using any plugin at all. That's because the Gradle plugin architecture builds on exactly the same DSL and deep API as you use in build scripts. The main difference is one of intent; a plugin has been packaged in a way that is intended for re-use in other projects.
The bare minimum needed is access to a library that performs LESS compilation, and a declaration of a task that uses it. Here's a quick example using lesscss-java. It only compiles a single LESS file, but it should be easy to extend for whatever your requirements are.
build.gradle:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.lesscss:lesscss:1.3.0"
}
}
task lessCompile << {
def compiler = new org.lesscss.LessCompiler()
compiler.compile(file("some.less"), file("some.css"))
}
some.less:
#mainColor: black;
body {
background-color: #mainColor;
}
After running gradle lessCompile, the result is some.css:
body {
background-color: #000000;
}