I have a common module that is consumed by JVM, JS, and Native projects. Within the common module, I would like to do something like the following:
fun currentPlatform(): String {
// return "JVM", "JS", or "Native" depending on where this code is executing.
}
In the common module, I have
enum class KotlinPlatform {
JVM,JS,Native
}
expect val currentPlatform: KotlinPlatform
In the JVM module, I have:
actual val currentPlatform = KotlinPlatform.JVM
And the above can be repeated for JS and any other modules as well.
Related
I want to write a task that takes a directory from , does something with the files in it and writes the result into some other directory to.
I've been led to believe this was the way to define such a task (kotlin dsl):
package my.app
abstract class FooBarTask : DefaultTask() {
#get:InputDirectory
abstract val from: Property<Directory>
#get:OutputDirectory
abstract val to: Property<Directory>
#TaskAction
fun doSomething() {
println("Hakuna Matata")
}
}
now how do I set the from and to value in a groovy-based build.gradle?
def myTask = tasks.register('myTask', FooBarTask) {
from = layout.projectDirectory.dir("foo")
to = layout.buildDirectory.dir("bar")
}
this results in
Could not create task ':my-subproject:myTask'.
> Please use the ObjectFactory.directoryProperty() method to create a property of type Directory.
and it shouldn't.
How do you correctly define a directory property in a custom task?
Gradle has the specialized DirectoryProperty, that offers some additional functionality, compared to the plain Property<Directory> which is one of the implemented interfaces. So this specialized type should be used when declaring directory inputs/outputs.
I'm actually not a 100% sure what caused the error you saw.
We have a plugin which defines additional properties and adds them as extension properties, like:
project.extra["copyright"] = "Copyright ..."
Then in the build scripts, I can access this like:
project.extra["copyright"]
I'd like to just write:
project.copyright
Some Gradle plugins seem to do something like this. I can access project.sourceSets or project.kotlin even though those certainly aren't in the Project interface.
Using an IDE, I can drill into those convenience methods, which then lands me in some autogenerated code, so I know it's being autogenerated somewhere, but I haven't been able to find any clues to how to get this to happen for our own plugin. The Gradle docs mention type-safe accessors which is ultimately what allowed me to phrase the question, but the docs don't say how to add new ones.
How do we get this treatment for our own plugin?
Creating DSL-like APIS is documented here: https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html#modeling_dsl_like_apis.
It's pretty easy to do. Here's a quick guide, and some tips.
Define an extension.
import org.gradle.api.provider.Property
interface MyExtension {
val copyright: Property<String>
}
It looks pretty boring! What's important is that it's either be an abstract class, or an interface - this is so Gradle can create a new instance (see 'Managed types'), and this is where the Gradle magic begins.
Aside: I've used Property<String> instead of String, although both will work. I recommend using types compatible with Lazy Configuration.
Register the extension.
import org.gradle.api.*
abstract class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val myExtension: MyExtension =
project.extensions.create("myPlugin", MyExtension::class.java)
}
}
This is the magic part. Simply by registering the extension against the project, Gradle will make the extension available and automatically generate Kotlin-DSL convenience methods.
Apply the plugin
// build.gradle.kts
plugins {
id("my-plugin")
}
myPlugin {
copyright.set("blah blah 2022")
}
Just like that, Gradle will generate Kotlin DSL accessors. Here's one of them:
// Accessorsajp3oxzka99ro52ctxwv0petb.kt
/**
* Configures the [myPlugin][MyExtension] extension.
*/
fun org.gradle.api.Project.`myPlugin`(configure: Action<MyExtension>): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("myPlugin", configure)
Use the extension values
Going back to the Plugin definition, lets say you want to register your own task:
// a demo task
abstract class MyTask : DefaultTask() {
#get:Input
abstract val copyright: Property<String>
#TaskAction
fun run() {
println("Copyright is ${copyright.get()}")
}
}
(Note that this task, like the extension, is a 'managed type').
Now the custom task can be registered, and a default value for copyright set.
abstract class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val myExtension: MyExtension = project.extensions.create("myPlugin", MyExtension::class.java)
project.tasks.register("myCustomTask", MyTask::class.java) {
copyright.set(myExtension.copyright)
}
}
}
It's good that both MyExtension and MyTask use Property<String> - the actual value will be evaluated lazily, and only if required.
Now if I run ./gradlew :myCustomTask, I see:
> Task :myCustomTask
Copyright is blah blah 2022
Further reading
What if you want to have multiple copyrights? Then you can create a configuration container
What if MyExtension has lots of properties and you want to provide them all to MyTask? Then you can use #Nested inputs
The extension properties can have default values.
The Gradle project is set by the JS plugin:
plugins {
kotlin("js") version("1.6.10")
}
and uses the LEGACY compilation backend:
kotlin {
js(LEGACY) {
// ...
}
}
My goal is to use the following dependencies in Kotlin sources:
dependencies {
implementation(npm("i18next", "21.6.11"))
implementation(npm("react-i18next", "11.15.4"))
implementation(npm("i18next-browser-languagedetector", "6.1.3"))
}
It was pretty easy to describe JS-Kotlin bridging for the first two dependencies:
#JsModule("i18next")
#JsNonModule
external val i18next: I18n
external interface I18n {
fun use(module: dynamic): I18n
}
#JsModule("react-i18next")
#JsNonModule
external val reactI18next: ReactI18next
external interface ReactI18next {
val initReactI18next: dynamic
}
Unfortunately, the last one - i18next-browser-languagedetector - is driving me some nuts with its configuration. Something like this:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
external val LanguageDetector: dynamic
doesn't work - the actual LanguageDetector provided by the declaration above is {}, so i18next doesn't consume it in Kotlin code (the JS code throws You are passing a wrong module! Please check the object you are passing to i18next.use()):
i18next.use(LanguageDetector) // fails
Can anyone please help me with a declaration of a JS-Kotlin bridge for the LanguageDetector?
Well, by debugging a little bit I've managed to solve this JS-Kotlin bridging issue. The working solution is the following declaration:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
external val i18nextBrowserLanguageDetector: I18nextBrowserLanguageDetector
external interface I18nextBrowserLanguageDetector {
#JsName("default")
val LanguageDetector: dynamic
}
Now it's possible to do first parts of the i18next initialization chain:
i18next
.use(i18nextBrowserLanguageDetector.LanguageDetector)
.use(reactI18next.initReactI18next)
// ...
Unfortunately, it's difficult to say that I'm getting any intuition behind it (maybe because of my huge blind spots in JS) - so any additional clarification or explanations would be helpful still.
My biggest concern is that LanguageDetector from the declaration above should be a class, but it seems like no way to use something else rather than dynamic property. When I try to lift up the #JsName("default") annotation to mark some class protocol with it, it doesn't compile:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
#JsName("default")
external class LanguageDetector
It's not possible to use a nested class inside of the interface as well in this case:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
external interface I18nextBrowserLanguageDetector {
#JsName("default")
class LanguageDetector
}
So while it seems to be solved, it's super-frustrating still.
I made a library. OrderedCollections. I import and use in an application. In the library is a junit5 test interface. I want to use the test interface in the application.
example, in library you have
interface BinaryTree<K:Comparable<K>,V> : Collection<Pair<K,V> {...}
in The tests for this library you have
interface TreeTests<K : Comparable<K>, V> {
val tree: BinaryTree<K, V>
#Test
fun sorted() {
val list = tree.toList().sortedBy { it.first }
assertIterableEquals(list, tree)
}
}
I made an implementation in the application of BinaryTree, but it is backed by sqlite. I want to use the test interface from the library on the implementation in the application. Like this
class TreeDB:BinaryTree<Int,String>(){...}
In the tests in the application
class TreeDBTests:TreeTests<Int,String> {
override val tree = TreeDB()
}
And then all the tests written for a possible implemention of BinaryTree are run on the TreeDB class.
However, code written in the tests of a library are not exported to consumers of the library. Anyway to make this work?
I recommend to treat test utils as just any other library that serves a purpose. If, as it seems to be in your case, those test utils are also required for testing the original lib, you will have to separate interface and implementation from the original lib, e.g.
bintree-interface <– bintree-testutils (TreeTests etc.)
^– bintree-implementation
Now the tests for bintree-implementation can use TreeTests and your tests for TreeDB can as well.
Currently, I'm creating a function, which is available for the dependencies block in Groovy with:
project.dependencies.ext.foo = { String value ->
project.files(extension.getFooDependency(project).jarFiles).asFileTree
}
Thanks to that, I'm able to do:
afterEvaluate {
dependencies {
compileOnly foo('junit')
}
}
I'm converting the Groovy code to Kotlin, and I'm wondering how to rewrite this foo extension.
I've ended up with:
project.dependencies.extensions.extraProperties.set("foo", Action { value: String ->
project.files(extension.getIdeaDependency(project).jarFiles).asFileTree
})
After calling foo('junit'), I get the following exception:
> Could not find method foo() for arguments [junit] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
I do not think that would work the same way in Kotlin DSL. Instead, you may declare a Kotlin extension function somewhere in the project. Then calling it would include all necessary receivers to you.
For multiple projects, I would recommend using a buildSrc project. Declarations there are visible to all project files below.
Speaking about Groovy and Kotlin support, I would do something like that:
private fun getFooImpl(scope: getFooImpl, name: String) { /*here is the implementation */ }
fun DependencyHandlerScope.getFoo(name:String) = getFooImpl(this, name)
//in Groovy
project.dependencies.extensions.extraProperties.set("foo", {getFooImpl(..)})
The same code could fit into a plugin as well. A more generic way could be to register a custom DLS extension, so to allow a custom block-like thisIsMyPlugin { .. } in the Gradle DSL and define all necessary helper functions in the extension class. Here the downside is in forcing users to wrap their code into the thisIsMyPlugin block.