How to configure the javafx plugin in a custom gradle plugin? - kotlin

I am new to gradle. I am working on a kotlin library that requires JavaFX. I wanted to use gradle to manage my dependencies and make it easier to include my library in a new project.
My library project applies and configures the javafx plugin and its build file looks like this (kotlin dsl):
plugins {
`java-library`
id("org.openjfx.javafxplugin")
}
group = "me.shai"
version = "1.0"
javafx {
version = "15.0.1"
modules = listOf("javafx.controls", "javafx.fxml")
}
dependencies {
// Other dependencies
testImplementation(kotlin("test-junit5"))
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.0")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
explicitApi()
}
Apparently, a project that wants to use my library also has to apply and configure JavaFX. Here's an example for a build file of a demo project that consumes my library:
plugins {
id("org.openjfx.javafxplugin")
}
group = "me.shai"
version = "1.0"
javafx {
version = "15.0.1"
modules = listOf("javafx.controls", "javafx.fxml")
}
dependencies {
implementation(project(":MyLib")) // Pretend the library is published so we use a url instead of local project
}
This is slightly annoying, so I decided to do something similar to JavaFX: write a custom plugin to include my project. The plugin needs to:
Add my library as a dependency
Apply the JavaFX plugin
Configure the JavaFX plugin
I managed to achieve steps 1 and 2, but I am stuck at step 3. How can I configure the JavaFX plugin from a custom plugin kotlin class?
Here's what I have so far:
package mylib.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.DependencyResolutionListener
import org.gradle.api.artifacts.ResolvableDependencies
import org.gradle.kotlin.dsl.project
class MyLibPlugin : Plugin<Project> {
override fun apply(project: Project) {
// Apply the javafx plugin
project.plugins.apply("org.openjfx.javafxplugin")
// How to configure javafx here?
val implementationDeps = project.configurations.getByName("implementation").dependencies
project.gradle.addListener(object : DependencyResolutionListener {
override fun beforeResolve(dependencies: ResolvableDependencies) {
// Add dependency on my library project.
implementationDeps.add(project.dependencies.project(":MyLib"))
project.gradle.removeListener(this)
}
override fun afterResolve(dependencies: ResolvableDependencies) {}
})
}
}
I am basically trying to write the equivalent of the build script block:
javafx {
version = "15.0.1"
modules = listOf("javafx.controls", "javafx.fxml")
}
And I can't figure out how to achieve this. I'd appreciate any help on the subject.

You would configure the javafx extension just as you would in the build file:
// build.gradle[.kts]
dependencies {
implementation("org.openjfx:javafx-plugin:0.0.10")
}
And the plugin implementation:
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.openjfx.gradle.JavaFXOptions
open class MyLibPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.apply("org.openjfx.javafxplugin")
val myLibDependency = project.dependencies.project(mapOf("path" to ":MyLib"))
project.dependencies.add(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME, myLibDependency)
project.extensions.configure(JavaFXOptions::class.java) {
it.version = "15.0.1"
it.modules = listOf("javafx.controls", "javafx.fxml")
}
}
}

Related

Convention plugin error org.gradle.accessors.dm.LibrariesForDeps not present

I'm trying to build convention plugins for a common configuration, e.g. ktlint. They are stored under build-configuration/plugin/convention.
class KtlintConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
return with(target) {
pluginManager.apply(deps.plugins.ktlint.get().pluginId)
extensions.configure<KtlintExtension> {
// ...
}
}
}
}
build-configuration/plugin/convention/build.gradle.kts
plugins {
`kotlin-dsl`
}
group = "com.example"
dependencies {
compileOnly(deps.kotlin.jvm.gradle.plugin)
compileOnly(deps.ktor.gradle.plugin)
compileOnly(deps.ktlint.gradle.plugin)
compileOnly(files(deps.javaClass.superclass.protectionDomain.codeSource.location))
}
gradlePlugin {
plugins {
register("ktlint-plugin") {
id = "ktlint-plugin"
implementationClass = "KtlintConventionPlugin"
}
}
}
I also have an extension function to get dependencies defined in TOML file (stored in gradle/deps.versions.toml) in build-configuration/plugin/convention/src/main/kotlin/com/example/VersionCatalog.kt
import org.gradle.accessors.dm.LibrariesForDeps
import org.gradle.api.Project
import org.gradle.kotlin.dsl.the
internal val Project.deps: LibrariesForDeps
get() = the()
build-configuration/settings.gradle.kts
dependencyResolutionManagement {
repositories {
gradlePluginPortal()
mavenLocal()
mavenCentral()
}
versionCatalogs {
create("deps") {
from(files("../gradle/deps.versions.toml"))
}
}
}
rootProject.name = "build-configuration"
include(":plugin:convention")
When I include ktlint plugin in root build.gradle.kts, Gradle synchronization always fails.
An exception occurred applying plugin request [id: 'ktlint-plugin']
> Failed to apply plugin 'ktlint-plugin'.
> Type org.gradle.accessors.dm.LibrariesForDeps not present
I can see the class generated in .gradle/.../LibrariesForDeps, but under the project's root, not the plugin's one.
The whole build-configuration module is included in root settings.gradle.kts:
includeBuild("build-configuration")
Using the libs doesn't work in convention plugins, but there are some workarounds in the issue: https://github.com/gradle/gradle/issues/15383
But I don't think you need it. Instead the version can be defined in a single place: the included build's build.gradle.kts, which has access to the version catalog DSL.
define the Maven coordinates of the ktlint plugin in libs.versions.toml
[libraries]
gradlePlugin-ktlint = { module = "org.jlleitschuh.gradle:ktlint-gradle", version = "11.1.0" }
I prefer prefixing such dependencies with gradlePlugin to distinguish them from 'regular' project dependencies.
(The Maven coordinates are listed in the Gradle Plugin Portal, under the 'legacy' application as a classpath dependency)
Add a dependency on ktlint in the included build, build-configuration/build.gradle.kts
plugins {
`kotlin-dsl`
}
dependencies {
implementation(libs.deps.gradlePlugin.ktlint)
}
Now the ktlint plugin will be available on the classpath, and you can apply it as normal in a convention plugin, no version necessary.
In a precompiled script plugin:
// ./build-configuration/src/main/kotlin/my-ktlint-convention.gradle.kts
plugins {
id("org.jlleitschuh.gradle.ktlint") // version is provided by build.gradle.kts
}
Or in a traditional class plugin (which will require registering via gradlePlugins {}):
// ./build-configuration/src/main/kotlin/KtlintConventionPlugin.kt
class KtlintConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.apply(plugin = "org.jlleitschuh.gradle.ktlint")
target.extensions.configure<KtlintExtension> {
// ...
}
}
}
See also
For more information about buildSrc convention plugins, these answers are related:
https://stackoverflow.com/a/71562588/4161471
https://stackoverflow.com/a/71892685/4161471
I have created a new project and followed this approach. There is only one difference. I had to have the block below in both settings.gradle.kts files, root, and build-configuration:
dependencyResolutionManagement {
versionCatalogs {
create("deps") {
from(files("../gradle/deps.versions.toml"))
}
}
}
It just doesn't work for me without this duplication.
Also, I'm not using any module within the build-configuration plugin.

Obfuscation for Compose Desktop application. Probably with ProGuard

Our main task is to make a GUI application on Windows (and preferably on Mac too) so it would be impossible/extremely difficult to find out the source code (Kotlin). Question is = how to do it?
Ideally, I would like to know:
.1. Tools to get the source code from our executable/files executable depends on.
.2. Tools for code obfuscation to the level where the tools of the previous point won't work.
.3. Next section is about what I did in Compose Desktop, but I do not limit myself to it. If you, for example, know how to make a working project with encrypted Kotlin code in JavaFx or something else, please let me know.
What has been done so far:
In Compose Desktop generated a .msi file, which on Windows creates files, and among them there are .jar files.
I opened one of the .jar in here and there was it = my beautiful source code, although in Java, but even the variables were not renamed!
Next I changed build.gradle.kts according to template
Important note = I do not understand this template. Perhaps I did something wrong.
I'll added the code, take a look.
But at least .msi got built and it installed the rest as previously. And again the same story = I open the .jar and see the source code in Java with variables not renamed!
But again = not even sure if this template is supposed to make the code in .jar unreadable.
Note = never done/learned custom obfuscation before. I work mainly with Kotlin and Android Studio. For Compose Desktop installed IntelliJ Idea.
Code:
build.gradle.kts
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
buildscript {
dependencies {
classpath("com.guardsquare:proguard-gradle:7.2.1")
}
}
repositories {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
plugins {
kotlin("jvm") version "1.6.10"
id("org.jetbrains.compose") version "1.1.1"
}
dependencies {
implementation(compose.desktop.currentOs)
}
val obfuscate by tasks.registering(proguard.gradle.ProGuardTask::class)
fun mapObfuscatedJarFile(file: File) =
File("${project.buildDir}/tmp/obfuscated/${file.nameWithoutExtension}.min.jar")
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Exe, TargetFormat.Msi, TargetFormat.Deb)
packageName = "untitled_04"
packageVersion = "1.0.0"
}
}
}
obfuscate.configure {
dependsOn(tasks.jar.get())
val allJars = tasks.jar.get().outputs.files + sourceSets.main.get().runtimeClasspath.filter { it.path.endsWith(".jar") }
.filterNot { it.name.startsWith("skiko-awt-") && !it.name.startsWith("skiko-awt-runtime-") } // walkaround https://github.com/JetBrains/compose-jb/issues/1971
for (file in allJars) {
injars(file)
outjars(mapObfuscatedJarFile(file))
}
libraryjars("${compose.desktop.application.javaHome ?: System.getProperty("java.home")}/jmods")
configuration("proguard-rules.pro")
}
proguard-rules.pro
-keepclasseswithmembers public class MainKt {
public static void main(java.lang.String[]);
}
-dontwarn kotlinx.coroutines.debug.*
-keep class kotlin.** { *; }
-keep class kotlinx.coroutines.** { *; }
-keep class org.jetbrains.skia.** { *; }
-keep class org.jetbrains.skiko.** { *; }
-assumenosideeffects public class androidx.compose.runtime.ComposerKt {
void sourceInformation(androidx.compose.runtime.Composer,java.lang.String);
void sourceInformationMarkerStart(androidx.compose.runtime.Composer,int,java.lang.String);
void sourceInformationMarkerEnd(androidx.compose.runtime.Composer);
}

Doesn't recognise the .proto file in android studio canary

Here is my gradle.
plugins {
// ...
id "com.google.protobuf" version "0.8.12"
}
dependencies {
// DataStore
implementation "androidx.datastore:datastore-core:1.0.0"
// Architectural Components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.0'
// Coroutine Lifecycle Scopes
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
// ...
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.10.0"
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
I make the proto folder to under src/main.
And then, create proto file to src/main/proto,
It show the [Register New File Type Association] like this.
If there's anything I can add, please let me know.
It was my mistake. I just install proto plugin to android studio.

Error: Resolving configuration 'ios32Api' directly is not allowed

I am trying to make a universal framework for iOS in KMP.
Here is my module build.gradle file
import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
buildscript {
ext.serialization_version = "0.20.0"
repositories {
mavenCentral()
jcenter()
maven { url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies" }
}
}
repositories {
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
apply plugin: 'kotlin-multiplatform'
apply plugin: 'kotlinx-serialization'
def serialization_version = "0.20.0"
kotlin{
targets {
fromPreset(presets.jvm, 'android')
iosArm32("ios32")
iosArm64("ios64")
iosX64("simulator")
configure([ios32, ios64, simulator]) {
binaries.framework('Shared')
}
}
//we have 3 different sourceSets for common, android and iOS.
//each sourceSet can have their own set of dependencies and configurations
sourceSets {
commonMain.dependencies {
api 'org.jetbrains.kotlin:kotlin-stdlib-common'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
}
androidMain.dependencies {
api 'org.jetbrains.kotlin:kotlin-stdlib'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"
}
iosMain {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version")
}
}
ios32.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version")
}
ios64.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version")
}
simulator.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version")
}
}
task fatFramework(type: FatFrameworkTask) {
// the fat framework must have the same base name as the initial frameworks
baseName = "Shared"
final File frameworkDir = new File(buildDir, "xcode-frameworks")
destinationDir = frameworkDir
// specify the frameworks to be merged
from(
targets.ios32.binaries.getFramework('Shared', 'RELEASE'),
targets.ios64.binaries.getFramework('Shared', 'RELEASE'),
targets.simulator.binaries.getFramework('Shared', 'RELEASE')
)
doLast {
new File(frameworkDir, 'gradlew').with {
text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$#\n"
setExecutable(true)
}
}
}
}
configurations {
compileClasspath
}
tasks.build.dependsOn fatFramework
When I try to build my Module it gives me this error
Execution failed for task ':Shared:linkSharedReleaseFrameworkIos32'.
> Resolving configuration 'ios32Api' directly is not allowed
Am I missing something in my configuration?
I was unable to reproduce the error as the question features only part of the project. So, while modifying it to make things work as a separate project rather than a module, I accidentally lost the original problem. But here are my thoughts on the possible cause.
This snippet seems to be missing the difference between source sets and targets. I mean, as there are three named targets ios32, ios64, simulator, the kotlin-multiplatform plugin creates six default source sets: ios32Main, ios64Main, simulatorMain, ios32Test, ios64Test, simulatorTest. But in this code, I see new source sets named ios32 etc. being created instead of setting defaults' dependencies. This can be a problem, as there are no explicit connections between those source sets and declared targets.

Cannot use dependencies in commonMain for kotlin multiplatform

I cannot figure out how to get a commonMain dependency to work in a kotlin multiplatform project. I have read and re-read the documentation many times and have looked at many of the examples, but it just isn't working. Here is the smallest example that I believe should work. What am I doing wrong?
multiplatform-lib
plugins {
kotlin("multiplatform") version "1.3.61"
`maven-publish`
}
group = "github.fatalcatharsis"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
mavenLocal()
}
kotlin {
/* Targets configuration omitted.
* To find out how to configure the targets, please follow the link:
* https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets */
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
}
}
src/commonMain/kotlin/Test.kt
data class Test (
val test : Int
)
multiplatform-test
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "github.fatalcatharsis"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
mavenLocal()
}
kotlin {
/* Targets configuration omitted.
* To find out how to configure the targets, please follow the link:
* https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets */
js {
browser()
}
jvm()
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
implementation("github.fatalcatharsis:multiplatform-lib-metadata:1.0-SNAPSHOT")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
}
}
src/commonMain/kotlin/Tester.kt
import github.fatalcatharsis.Test
fun test() {
val meh = Test()
}
intellij says it resolved the dependency just fine on the project menu, but highlights github as Red. No autocomplete available for "Test". Errors with multiplatform-test\src\commonMain\kotlin\Tester.kt: (1, 8): Unresolved reference: github. Just looks like the dependency content isn't available in the commonMain. I feel like I've missed something subtle and obvious. Any ideas?
Edit: Doing the opposite of what the documentation says for common dependencies, if I change the dependency to:
implementation("github.fatalcatharsis:multiplatform-lib:1.0-SNAPSHOT")
it produces the error:
Could not determine the dependencies of task ':jsPackageJson'.
> Could not resolve all dependencies for configuration ':jsNpm'.
> Could not resolve github.fatalcatharsis:multiplatform-lib:1.0-SNAPSHOT.
Required by:
project :
> Unable to find a matching variant of github.fatalcatharsis:multiplatform-lib:1.0-SNAPSHOT:
- Variant 'metadata-api':
- Found org.gradle.status 'integration' but wasn't required.
- Required org.gradle.usage 'kotlin-runtime' and found incompatible value 'kotlin-api'.
- Required org.jetbrains.kotlin.platform.type 'js' and found incompatible value 'common'.
Assuming you've published locally, and that was successful, then the first thing to change is the dependency:
implementation("github.fatalcatharsis:multiplatform-lib:1.0-SNAPSHOT")
You probably don't want the metadata artifact.
Next, add the following to your test app's settings.gradle file.
enableFeaturePreview("GRADLE_METADATA")
After that, try building on command line. Sometimes intellij does see everything.
If things still aren't working, I'd start looking at your publish config.