diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5116158c..16636f9f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -148,6 +148,12 @@ jobs:
env:
GITHUB_DEPENDENCY_GRAPH_ENABLED: false
+ - name: 'Run check-compose-desktop'
+ working-directory: checks/compose-desktop
+ run: ./gradlew build assemble check packageReleaseDistributionForCurrentOS --continue --stacktrace --scan
+ env:
+ GITHUB_DEPENDENCY_GRAPH_ENABLED: false
+
- name: 'Run self check'
timeout-minutes: 4
working-directory: self
diff --git a/.run/check-compose-desktop.run.xml b/.run/check-compose-desktop.run.xml
new file mode 100644
index 00000000..e1e06f1c
--- /dev/null
+++ b/.run/check-compose-desktop.run.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/.run/dependencyGuardBaseline (compose-desktop).run.xml b/.run/dependencyGuardBaseline (compose-desktop).run.xml
new file mode 100644
index 00000000..901bcef5
--- /dev/null
+++ b/.run/dependencyGuardBaseline (compose-desktop).run.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/.run/dependencyGuardBaseline (check-main).run.xml b/.run/dependencyGuardBaseline (kmp).run.xml
similarity index 91%
rename from .run/dependencyGuardBaseline (check-main).run.xml
rename to .run/dependencyGuardBaseline (kmp).run.xml
index 988b6b26..507116fa 100644
--- a/.run/dependencyGuardBaseline (check-main).run.xml
+++ b/.run/dependencyGuardBaseline (kmp).run.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c48f5445..b171063b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ _You need to replace `setup*` calls to `fkcSetup*` ones like this:
`setupMultiplatform` => `fkcSetupMultiplatform`._
### Added
+- add Compose Desktop setup support and a test project for it.
- bundle toml version catalog with the plugin as a resolving fallback.
- add logging on auto changed yarn dependenciew for Kotlin/JS.
- enable `androidResources.generateLocaleConfig` in android apps by default.
diff --git a/ROADMAP.md b/ROADMAP.md
index 72ddbd55..1afb1b78 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -18,7 +18,6 @@
* https://github.com/arrow-kt/arrow-gradle-config/blob/cba09cc/arrow-gradle-config-publish/src/main/kotlin/internal/PublishMppRootModuleInPlatform.kt#L13
* Security setup
* Setup for securing API keys in `BuildConfigs` (generated code in general) and in the usual code.
-* Support JVM application with `application` plugin.
* GIT hooks
* Spotless
* Detekt
diff --git a/checks/compose-desktop/build.gradle.kts b/checks/compose-desktop/build.gradle.kts
new file mode 100644
index 00000000..ed970ad6
--- /dev/null
+++ b/checks/compose-desktop/build.gradle.kts
@@ -0,0 +1,64 @@
+import java.util.Calendar
+import org.jetbrains.compose.desktop.application.dsl.TargetFormat
+
+plugins {
+ alias(libs.plugins.kotlin.jvm)
+ alias(libs.plugins.jetbrains.compose)
+ id("io.github.fluxo-kt.fluxo-kmp-conf")
+}
+
+// https://github.com/JetBrains/compose-multiplatform/blob/e1aff75/tutorials/Native_distributions_and_local_execution/README.md#specifying-package-version
+val appVersion = libs.versions.version.get()
+ .substringBefore("-")
+ .let {
+ val (a, b) = it.split(".", limit = 3)
+ if (a == "0") "1.0.$b" else it
+ }
+
+version = appVersion
+group = "io.github.fluxo-kt"
+
+val mainClassName = "MainKt"
+tasks.named("jar") {
+ manifest.attributes["Main-Class"] = mainClassName
+}
+
+// See JB template and docs:
+// https://github.com/JetBrains/compose-multiplatform-desktop-template
+// https://github.com/JetBrains/compose-multiplatform/blob/e1aff75/tutorials/Native_distributions_and_local_execution/README.md
+
+fkcSetupKotlinApp {
+ replaceOutgoingJar = true
+ shrink { fullMode = true }
+ shrinkWithProGuard()
+}
+
+dependencies {
+ // `currentOs` should be used in launcher-sourceSet and in testMain only.
+ // For a library, use compose.desktop.common.
+ // With compose.desktop.common you will also lose @Preview functionality.
+ implementation(compose.desktop.currentOs)
+
+ testImplementation(compose.desktop.uiTestJUnit4)
+}
+
+compose.desktop.application {
+ mainClass = mainClassName
+
+ nativeDistributions {
+ targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
+ packageVersion = appVersion
+ packageName = "MyApp"
+ description = "MyDescription"
+ vendor = "MyCompany"
+
+ val year = Calendar.getInstance().get(Calendar.YEAR)
+ copyright = "© $year $packageName. All rights reserved."
+
+ windows {
+ shortcut = true
+ menuGroup = packageName
+ upgradeUuid = "3d4241d1-0400-401f-bd1f-000fdc8ae989"
+ }
+ }
+}
diff --git a/checks/compose-desktop/dependencies/classpath.txt b/checks/compose-desktop/dependencies/classpath.txt
new file mode 100644
index 00000000..f4508200
--- /dev/null
+++ b/checks/compose-desktop/dependencies/classpath.txt
@@ -0,0 +1,60 @@
+:plugin
+com.diffplug.durian:durian-collect:1.2.0
+com.diffplug.durian:durian-core:1.2.0
+com.diffplug.durian:durian-io:1.2.0
+com.diffplug.durian:durian-swt.os:4.2.2
+com.diffplug.spotless:spotless-lib-extra:2.45.0
+com.diffplug.spotless:spotless-lib:2.45.0
+com.diffplug.spotless:spotless-plugin-gradle:6.25.0
+com.dropbox.dependency-guard:com.dropbox.dependency-guard.gradle.plugin:0.5.0
+com.dropbox.dependency-guard:dependency-guard:0.5.0
+com.github.ben-manes.versions:com.github.ben-manes.versions.gradle.plugin:0.51.0
+com.github.ben-manes:gradle-versions-plugin:0.51.0
+com.googlecode.concurrent-trees:concurrent-trees:2.6.1
+com.googlecode.javaewah:JavaEWAH:1.2.3
+com.squareup.moshi:moshi-kotlin:1.12.0
+com.squareup.moshi:moshi:1.12.0
+com.squareup.okhttp3:okhttp-bom:4.12.0
+com.squareup.okhttp3:okhttp:4.12.0
+com.squareup.okio:okio-jvm:3.6.0
+com.squareup.okio:okio:3.6.0
+commons-codec:commons-codec:1.16.0
+dev.equo.ide:solstice:1.7.5
+io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.6
+org.antlr:antlr4-runtime:4.11.1
+org.checkerframework:checker-qual:3.21.2
+org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r
+org.eclipse.platform:org.eclipse.osgi:3.18.300
+org.jetbrains.compose:compose-gradle-plugin:1.6.2
+org.jetbrains.compose:org.jetbrains.compose.gradle.plugin:1.6.2
+org.jetbrains.intellij.deps:trove4j:1.0.20200330
+org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.9.23
+org.jetbrains.kotlin:kotlin-android-extensions:1.9.23
+org.jetbrains.kotlin:kotlin-build-tools-api:1.9.23
+org.jetbrains.kotlin:kotlin-compiler-embeddable:1.9.23
+org.jetbrains.kotlin:kotlin-compiler-runner:1.9.23
+org.jetbrains.kotlin:kotlin-daemon-client:1.9.23
+org.jetbrains.kotlin:kotlin-daemon-embeddable:1.9.23
+org.jetbrains.kotlin:kotlin-gradle-plugin-annotations:1.9.23
+org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.9.23
+org.jetbrains.kotlin:kotlin-gradle-plugin-idea-proto:1.9.23
+org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.9.23
+org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.9.23
+org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23
+org.jetbrains.kotlin:kotlin-gradle-plugins-bom:1.9.23
+org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.9.23
+org.jetbrains.kotlin:kotlin-native-utils:1.9.23
+org.jetbrains.kotlin:kotlin-project-model:1.9.23
+org.jetbrains.kotlin:kotlin-reflect:1.9.22
+org.jetbrains.kotlin:kotlin-scripting-common:1.9.23
+org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.9.23
+org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.9.23
+org.jetbrains.kotlin:kotlin-scripting-jvm:1.9.23
+org.jetbrains.kotlin:kotlin-tooling-core:1.9.23
+org.jetbrains.kotlin:kotlin-util-io:1.9.23
+org.jetbrains.kotlin:kotlin-util-klib:1.9.23
+org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0
+org.ow2.asm:asm:9.7
+org.slf4j:slf4j-api:1.7.36
+org.tomlj:tomlj:1.1.1
+org.tukaani:xz:1.9
diff --git a/checks/compose-desktop/detekt.yml b/checks/compose-desktop/detekt.yml
new file mode 120000
index 00000000..24b6af38
--- /dev/null
+++ b/checks/compose-desktop/detekt.yml
@@ -0,0 +1 @@
+../../detekt.yml
\ No newline at end of file
diff --git a/checks/compose-desktop/gradle b/checks/compose-desktop/gradle
new file mode 120000
index 00000000..1ce6c4c1
--- /dev/null
+++ b/checks/compose-desktop/gradle
@@ -0,0 +1 @@
+../../gradle
\ No newline at end of file
diff --git a/checks/compose-desktop/gradle.properties b/checks/compose-desktop/gradle.properties
new file mode 100644
index 00000000..34b6dc70
--- /dev/null
+++ b/checks/compose-desktop/gradle.properties
@@ -0,0 +1,4 @@
+org.gradle.jvmargs=-Xmx3G -Dfile.encoding=UTF-8 -XX:+UseParallelGC
+kotlin.daemon.jvmargs=-Xmx2G -XX:+UseParallelGC
+kotlin.daemon.useFallbackStrategy=false
+MAX_DEBUG=true
diff --git a/checks/compose-desktop/gradlew b/checks/compose-desktop/gradlew
new file mode 120000
index 00000000..343e0d2c
--- /dev/null
+++ b/checks/compose-desktop/gradlew
@@ -0,0 +1 @@
+../../gradlew
\ No newline at end of file
diff --git a/checks/compose-desktop/gradlew.bat b/checks/compose-desktop/gradlew.bat
new file mode 120000
index 00000000..cb5a9464
--- /dev/null
+++ b/checks/compose-desktop/gradlew.bat
@@ -0,0 +1 @@
+../../gradlew.bat
\ No newline at end of file
diff --git a/checks/compose-desktop/pg/assumenosideeffects.pro b/checks/compose-desktop/pg/assumenosideeffects.pro
new file mode 120000
index 00000000..114cb457
--- /dev/null
+++ b/checks/compose-desktop/pg/assumenosideeffects.pro
@@ -0,0 +1 @@
+../../../fluxo-kmp-conf/pg/assumenosideeffects.pro
\ No newline at end of file
diff --git a/checks/compose-desktop/pg/assumptions.pro b/checks/compose-desktop/pg/assumptions.pro
new file mode 120000
index 00000000..ec7f6c69
--- /dev/null
+++ b/checks/compose-desktop/pg/assumptions.pro
@@ -0,0 +1 @@
+../../../fluxo-kmp-conf/pg/assumptions.pro
\ No newline at end of file
diff --git a/checks/compose-desktop/pg/compose-desktop-rules.pro b/checks/compose-desktop/pg/compose-desktop-rules.pro
new file mode 100644
index 00000000..523287d3
--- /dev/null
+++ b/checks/compose-desktop/pg/compose-desktop-rules.pro
@@ -0,0 +1,55 @@
+# Compose Desktop ProGuard/R8 rules
+# https://github.com/JetBrains/compose-multiplatform/blob/382ad5b/gradle-plugins/compose/src/main/resources/default-compose-desktop-rules.pro
+
+#-keep class kotlin.** { *; }
+-keep,allowoptimization class org.jetbrains.skia.** { *; }
+-keep,allowoptimization 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);
+ boolean isTraceInProgress();
+ void traceEventStart(int, java.lang.String);
+ void traceEventEnd();
+}
+
+
+# Kotlinx Coroutines Rules
+# https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
+
+#-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
+#-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
+#-keepclassmembers class kotlinx.coroutines.** {
+# volatile ;
+#}
+#-keepclassmembers class kotlin.coroutines.SafeContinuation {
+# volatile ;
+#}
+#-dontwarn java.lang.instrument.ClassFileTransformer
+#-dontwarn sun.misc.SignalHandler
+#-dontwarn java.lang.instrument.Instrumentation
+#-dontwarn sun.misc.Signal
+#-dontwarn java.lang.ClassValue
+-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
+
+
+# https://github.com/Kotlin/kotlinx.coroutines/issues/2046
+-dontwarn android.annotation.SuppressLint
+
+# https://github.com/JetBrains/compose-jb/issues/2393
+-dontnote kotlin.coroutines.jvm.internal.**
+-dontnote kotlin.internal.**
+-dontnote kotlin.jvm.internal.**
+-dontnote kotlin.reflect.**
+-dontnote kotlinx.coroutines.debug.internal.**
+-dontnote kotlinx.coroutines.internal.**
+-keep,allowoptimization,allowobfuscation class kotlin.coroutines.Continuation
+-keep,allowoptimization,allowobfuscation class kotlin.coroutines.CoroutineContext
+-keep,allowoptimization,allowobfuscation class kotlinx.coroutines.CancellableContinuation
+-keep,allowoptimization,allowobfuscation class kotlinx.coroutines.CoroutineDispatcher
+-keep,allowoptimization,allowobfuscation class kotlinx.coroutines.CoroutineScope
+-keep,allowoptimization,allowobfuscation class kotlinx.coroutines.channels.Channel
+
+# this is a weird one, but breaks build on some combinations of OS and JDK (reproduced on Windows 10 + Corretto 16)
+-dontwarn org.graalvm.compiler.core.aarch64.AArch64NodeMatchRules_MatchStatementSet*
diff --git a/checks/compose-desktop/pg/keep.pro b/checks/compose-desktop/pg/keep.pro
new file mode 100644
index 00000000..ea0b3314
--- /dev/null
+++ b/checks/compose-desktop/pg/keep.pro
@@ -0,0 +1,12 @@
+
+# See `fluxo.shrink.ShrinkerKeepRulesBySeedsTest` for tests on what's supported by R8 and ProGuard.
+
+# The weakest rule that will keep annotations and attributes is
+# https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md#r8-full-mode
+# -keep[classmembers],allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification class-specification
+
+-keepclasseswithmembernames,includedescriptorclasses class * {
+ native ;
+}
+
+-dontwarn androidx.annotation.**
diff --git a/checks/compose-desktop/pg/proguard.pro b/checks/compose-desktop/pg/proguard.pro
new file mode 100644
index 00000000..73762418
--- /dev/null
+++ b/checks/compose-desktop/pg/proguard.pro
@@ -0,0 +1,10 @@
+
+-include rules.pro
+
+# ProGuard-only configuration.
+# Dangerous, can increase size of the artifact!
+# https://www.guardsquare.com/manual/configuration/optimizations#aggressive-optimization
+-optimizeaggressively
+
+# ProGuard-only configuration.
+#-skipnonpubliclibraryclasses
diff --git a/checks/compose-desktop/pg/rules.pro b/checks/compose-desktop/pg/rules.pro
new file mode 100644
index 00000000..fbeeb96f
--- /dev/null
+++ b/checks/compose-desktop/pg/rules.pro
@@ -0,0 +1,29 @@
+###
+# ProGuard/R8 rules
+###
+
+-optimizationpasses 8
+-repackageclasses
+
+-overloadaggressively
+-allowaccessmodification
+
+# Horizontal class merging increases size of the artifact.
+#-optimizations !class/merging/horizontal
+
+-adaptclassstrings
+-adaptresourcefilenames **.properties,**.gif,**.jpg,**.png,**.webp,**.svg,**.ttf,**.otf,**.txt,**.xml
+-adaptresourcefilecontents **.properties,**.MF
+
+# For problems reporting.
+-renamesourcefileattribute P
+-keepattributes SourceFile,LineNumberTable
+
+-include keep.pro
+-include compose-desktop-rules.pro
+
+# Note: -assumenosideeffects should not be used with constructors!
+# It leads to bugs like https://sourceforge.net/p/proguard/bugs/702/
+# Using -assumenoexternalsideeffects is fine though and should be used for that purpose.
+-include assumptions.pro
+-include assumenosideeffects.pro
diff --git a/checks/compose-desktop/settings.gradle.kts b/checks/compose-desktop/settings.gradle.kts
new file mode 100644
index 00000000..755edc12
--- /dev/null
+++ b/checks/compose-desktop/settings.gradle.kts
@@ -0,0 +1,20 @@
+pluginManagement {
+ repositories {
+ // For Gradle plugins only. Last because proxies to mavenCentral.
+ gradlePluginPortal()
+ }
+ includeBuild("../../")
+}
+
+plugins {
+ id("com.gradle.enterprise") version "3.16.2"
+}
+
+dependencyResolutionManagement {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "check-compose-desktop"
diff --git a/checks/compose-desktop/src/main/kotlin/Main.kt b/checks/compose-desktop/src/main/kotlin/Main.kt
new file mode 100644
index 00000000..bcec6267
--- /dev/null
+++ b/checks/compose-desktop/src/main/kotlin/Main.kt
@@ -0,0 +1,34 @@
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.application
+
+@Composable
+@Preview
+internal fun App() {
+ var text by remember { mutableStateOf("Hello, World!") }
+
+ MaterialTheme {
+ Button(
+ onClick = { text = "Hello, Desktop!" },
+ modifier = Modifier.testTag("button"),
+ ) {
+ Text(text)
+ }
+ }
+}
+
+fun main() = application {
+ Window(onCloseRequest = ::exitApplication) {
+ App()
+ }
+}
diff --git a/checks/compose-desktop/src/test/kotlin/MainTest.kt b/checks/compose-desktop/src/test/kotlin/MainTest.kt
new file mode 100644
index 00000000..43381a41
--- /dev/null
+++ b/checks/compose-desktop/src/test/kotlin/MainTest.kt
@@ -0,0 +1,23 @@
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import org.junit.Rule
+import org.junit.Test
+
+class MainTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun textChangesWhenButtonIsClicked() {
+ rule.setContent {
+ App()
+ }
+
+ val buttonNode = rule.onNodeWithTag("button")
+ buttonNode.assertTextEquals("Hello, World!")
+ buttonNode.performClick()
+ buttonNode.assertTextEquals("Hello, Desktop!")
+ }
+}
diff --git a/detekt.yml b/detekt.yml
index 40950e39..c3f7d906 100644
--- a/detekt.yml
+++ b/detekt.yml
@@ -23,13 +23,25 @@ exceptions:
TooGenericExceptionCaught:
active: false
+complexity:
+ LongParameterList:
+ ignoreDefaultParameters: true
+ TooManyFunctions:
+ ignoreDeprecated: true
+ ignoreOverridden: true
+ ignoreAnnotatedFunctions: ['Preview']
+
style:
LoopWithTooManyJumpStatements:
maxJumpCount: 2
MaxLineLength:
maxLineLength: 100
+ UnusedPrivateMember:
+ ignoreAnnotated: ['Preview']
naming:
+ FunctionNaming:
+ ignoreAnnotated: ['Composable']
NoNameShadowing:
# False positives with SAM receiver usage
active: false
diff --git a/fluxo-kmp-conf/api/plugin.api b/fluxo-kmp-conf/api/plugin.api
index d81f21ad..65c1d2e5 100644
--- a/fluxo-kmp-conf/api/plugin.api
+++ b/fluxo-kmp-conf/api/plugin.api
@@ -87,6 +87,8 @@ public final class Fkc {
public static synthetic fun setupIdeaPlugin$default (Lorg/gradle/api/Project;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static final fun setupKotlin (Lorg/gradle/api/Project;Ljava/lang/Boolean;Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun setupKotlin$default (Lorg/gradle/api/Project;Ljava/lang/Boolean;Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+ public static final fun setupKotlinApp (Lorg/gradle/api/Project;Ljava/lang/Boolean;Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
+ public static synthetic fun setupKotlinApp$default (Lorg/gradle/api/Project;Ljava/lang/Boolean;Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static final fun setupMultiplatform (Lorg/gradle/api/Project;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun setupMultiplatform$default (Lorg/gradle/api/Project;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static final fun setupRaw (Lorg/gradle/api/Project;Lorg/gradle/api/Action;)V
@@ -230,6 +232,8 @@ public abstract interface class fluxo/conf/dsl/FluxoConfigurationExtensionCommon
public abstract fun getSetupDependencies ()Z
public abstract fun getSetupKnownBoms ()Z
public abstract fun getSetupVerification ()Ljava/lang/Boolean;
+ public abstract fun isApplication ()Z
+ public abstract fun setApplication (Z)V
public abstract fun setEnableBuildConfig (Z)V
public abstract fun setEnableDetektAutoCorrect (Ljava/lang/Boolean;)V
public abstract fun setEnableDetektCompilerPlugin (Ljava/lang/Boolean;)V
diff --git a/fluxo-kmp-conf/detekt-baseline.xml b/fluxo-kmp-conf/detekt-baseline.xml
index bdd89057..7ca427bb 100644
--- a/fluxo-kmp-conf/detekt-baseline.xml
+++ b/fluxo-kmp-conf/detekt-baseline.xml
@@ -3,10 +3,11 @@
ArgumentListWrapping:ShrinkerReflectiveCaller.kt$ShrinkerReflectiveCaller$("$shrinker could not be loaded in-memory as $callType (class=$className)!")
+ CyclomaticComplexMethod:AbstractShrinkerTask.kt$AbstractShrinkerTask$@Suppress("LongMethod") private fun writeRootConfiguration( file: File, reportsDir: Directory, jarsConfigurationFile: File, shrinker: JvmShrinker, )
CyclomaticComplexMethod:SetupAndroidLint.kt$internal fun Project.setupAndroidLint( conf: FluxoConfigurationExtensionImpl, ignoredBuildTypes: List<String>, ignoredFlavors: List<String>, )
ForbiddenComment:AbstractShrinkerTask.kt$// TODO: Before Java 9, the runtime classes were packaged in a single jar file.
ForbiddenComment:AbstractShrinkerTask.kt$// TODO: Move into a separate file, loaded from resources
- ForbiddenComment:AbstractShrinkerTask.kt$AbstractShrinkerTask$// FIXME: Automatic main class detection from jar manifest,
+ ForbiddenComment:AbstractShrinkerTask.kt$AbstractShrinkerTask$// FIXME: Add automatic main class detection from the main jar manifest
ForbiddenComment:AbstractShrinkerTask.kt$AbstractShrinkerTask$// FIXME: Benchmark and adjust best options for R8/ProGuard.
ForbiddenComment:AbstractShrinkerTask.kt$AbstractShrinkerTask$// FIXME: Make all dirs to use the chain and id in chain!
ForbiddenComment:AbstractShrinkerTask.kt$AbstractShrinkerTask$// FIXME: When java toolchain used or JDK target is specified, read the specified JDK.
@@ -61,7 +62,6 @@
ForbiddenComment:SetupAndroid.kt$// TODO: Test for libraries bytecode.
ForbiddenComment:SetupAndroidLint.kt$// TODO: Setup Lint for NonAndroid projects
ForbiddenComment:SetupAndroidSigning.kt$// TODO: Support for ENV variables instead of file for more straightforward CI/CD
- ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Allow to call shrinker by task name even if it's disabled.
ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Check 'reproducibleArtifacts' with shrinker enabled.
ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Ensure the obfuscation mapping incrementality and compatibility when double-shrinking.
ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Provide unporcessed artifacts alongside the processed ones as a variant.
@@ -70,10 +70,12 @@
ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Shrinkers loose JARs reproducibility,
ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Support KMP JVM target minification with ProGuard.
ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Support auto-loading of the shrinking rules from the classpath for the ProGuard.
+ ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Support final artifact filtration step similar to the Android packaging options.
+ ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Support other shadowing/relocation processors in the chain.
ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Support shadow jar generation before shrinking artifacts.
- ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Support shadowing and/or relocation processors in the chain.
ForbiddenComment:SetupArtifactsProcessing.kt$// FIXME: Verify proper replacement of the original artifact with the processed one (map by file?).
- ForbiddenComment:SetupArtifactsProcessing.kt$// TODO: Support Android minification with ProGuard?
+ ForbiddenComment:SetupArtifactsProcessing.kt$// TODO: Allow to call shrinker by task name even if it's disabled.
+ ForbiddenComment:SetupArtifactsProcessing.kt$// TODO: Support Android minification with Fluxo processing chain.
ForbiddenComment:SetupArtifactsProcessing.kt$// TODO: Support auto-disabling kotlin null checks generation for the shrunken release builds.
ForbiddenComment:SetupDetekt.kt$// FIXME: Disable non-resolving tasks if resolving version is available.
ForbiddenComment:SetupDetekt.kt$// FIXME: Setup checks for the non source set kotlin files (e.g., *.kts scripts).
diff --git a/fluxo-kmp-conf/pg/assumptions.pro b/fluxo-kmp-conf/pg/assumptions.pro
index 9558163e..9b0bf91d 100644
--- a/fluxo-kmp-conf/pg/assumptions.pro
+++ b/fluxo-kmp-conf/pg/assumptions.pro
@@ -5,12 +5,21 @@
# Note: -assumenoexternalsideeffects and -assumenoexternalreturnvalues are not supported by R8 and ignored!
+-assumenoexternalsideeffects public final class kotlin.Unit {
+ public kotlin.Unit();
+}
+-assumenosideeffects public final class kotlin.Unit {
+ public static final kotlin.Unit INSTANCE;
+ public static java.lang.String toString();
+}
+
-assumenoexternalsideeffects public class java.lang.Object {
public java.lang.Object();
}
--assumenosideeffects public class java.lang.Object {
- public final java.lang.Class getClass();
-}
+# Causes problems with ProGuard
+#-assumenosideeffects public class java.lang.Object {
+# public final java.lang.Class getClass();
+#}
-assumenoexternalsideeffects public final class java.lang.Boolean {
public java.lang.Boolean(boolean);
diff --git a/fluxo-kmp-conf/pg/keep-api-autogenerated.pro b/fluxo-kmp-conf/pg/keep-api-autogenerated.pro
index b8d8432a..a2497959 100644
--- a/fluxo-kmp-conf/pg/keep-api-autogenerated.pro
+++ b/fluxo-kmp-conf/pg/keep-api-autogenerated.pro
@@ -2,7 +2,7 @@
# ProGuard/R8 keep rules
# Auto-generated by Fluxo task :generateShrinkerKeepRulesFromApi
# From API reports (with sha256short):
-# - api/plugin.api (8d4e4d5)
+# - api/plugin.api (3c95810)
# DO NOT EDIT MANUALLY!
-keep,includedescriptorclasses public final class Fkc {
@@ -94,6 +94,8 @@
public static synthetic void setupIdeaPlugin$default(org.gradle.api.Project, kotlin.jvm.functions.Function1, java.lang.String, java.lang.String, java.lang.String, java.lang.String, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, int, java.lang.Object);
public static final void setupKotlin(org.gradle.api.Project, java.lang.Boolean, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1);
public static synthetic void setupKotlin$default(org.gradle.api.Project, java.lang.Boolean, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, int, java.lang.Object);
+ public static final void setupKotlinApp(org.gradle.api.Project, java.lang.Boolean, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1);
+ public static synthetic void setupKotlinApp$default(org.gradle.api.Project, java.lang.Boolean, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, int, java.lang.Object);
public static final void setupMultiplatform(org.gradle.api.Project, kotlin.jvm.functions.Function1, java.lang.String, java.lang.Boolean, java.lang.Boolean, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1);
public static synthetic void setupMultiplatform$default(org.gradle.api.Project, kotlin.jvm.functions.Function1, java.lang.String, java.lang.Boolean, java.lang.Boolean, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, int, java.lang.Object);
public static final void setupRaw(org.gradle.api.Project, org.gradle.api.Action);
@@ -237,6 +239,8 @@
public abstract boolean getSetupDependencies();
public abstract boolean getSetupKnownBoms();
public abstract java.lang.Boolean getSetupVerification();
+ public abstract boolean isApplication();
+ public abstract void setApplication(boolean);
public abstract void setEnableBuildConfig(boolean);
public abstract void setEnableDetektAutoCorrect(java.lang.Boolean);
public abstract void setEnableDetektCompilerPlugin(java.lang.Boolean);
diff --git a/fluxo-kmp-conf/src/main/kotlin/FkcSetupAndroid.kt b/fluxo-kmp-conf/src/main/kotlin/FkcSetupAndroid.kt
index ebc45416..aa611817 100644
--- a/fluxo-kmp-conf/src/main/kotlin/FkcSetupAndroid.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/FkcSetupAndroid.kt
@@ -110,6 +110,8 @@ public fun Project.fkcSetupAndroidApp(
android: (BaseAppModuleExtension.() -> Unit)? = null,
) {
project.fluxoConfiguration {
+ isApplication = true
+
if (applicationId != null) {
this.androidNamespace = applicationId
this.androidApplicationId = applicationId
diff --git a/fluxo-kmp-conf/src/main/kotlin/FkcSetupKotlin.kt b/fluxo-kmp-conf/src/main/kotlin/FkcSetupKotlin.kt
index 33ea4119..efc75596 100644
--- a/fluxo-kmp-conf/src/main/kotlin/FkcSetupKotlin.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/FkcSetupKotlin.kt
@@ -9,7 +9,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
// TODO: Support JVM application with `application` plugin
/**
- * Lazily configures a Kotlin JVM module (Gradle [Project]).
+ * Lazily configures a Kotlin JVM library module (Gradle [Project]).
*
* @receiver The [Project] to configure.
*
@@ -22,6 +22,8 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
* @see org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
* @see fluxo.conf.dsl.FluxoConfigurationExtension.setupKsp
* @see fluxo.conf.dsl.FluxoConfigurationExtension.optIns
+ *
+ * @see fkcSetupKotlinApp for an app configuration defaults.
*/
@JvmName("setupKotlin")
public fun Project.fkcSetupKotlin(
@@ -31,16 +33,25 @@ public fun Project.fkcSetupKotlin(
config: (FluxoConfigurationExtension.() -> Unit)? = null,
) {
fluxoConfiguration {
- if (!optIns.isNullOrEmpty()) {
- this.optIns += optIns
- }
+ setupKotlin(optIns, setupKsp, config, kotlin)
+ }
+}
+
+internal fun FluxoConfigurationExtension.setupKotlin(
+ optIns: List?,
+ setupKsp: Boolean?,
+ config: (FluxoConfigurationExtension.() -> Unit)?,
+ kotlin: (KotlinJvmProjectExtension.() -> Unit)?,
+) {
+ if (!optIns.isNullOrEmpty()) {
+ this.optIns += optIns
+ }
- setupKsp?.let { this.setupKsp = it }
+ setupKsp?.let { this.setupKsp = it }
- config?.invoke(this)
+ config?.invoke(this)
- asJvm {
- kotlin?.let { this.kotlin(action = it) }
- }
+ asJvm {
+ kotlin?.let { this.kotlin(action = it) }
}
}
diff --git a/fluxo-kmp-conf/src/main/kotlin/FkcSetupKotlinApp.kt b/fluxo-kmp-conf/src/main/kotlin/FkcSetupKotlinApp.kt
new file mode 100644
index 00000000..2108aca4
--- /dev/null
+++ b/fluxo-kmp-conf/src/main/kotlin/FkcSetupKotlinApp.kt
@@ -0,0 +1,38 @@
+@file:JvmName("Fkc")
+@file:JvmMultifileClass
+
+import fluxo.conf.dsl.FluxoConfigurationExtension
+import fluxo.conf.dsl.fluxoConfiguration
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
+
+/**
+ * Lazily configures a Kotlin JVM app module (Gradle [Project]).
+ * Suitable for Compose, CLI, or any other JVM application.
+ *
+ * @receiver The [Project] to configure.
+ *
+ * @param setupKsp Whether to set up KSP (auto-detected if already applied).
+ * @param optIns List of the Kotlin opt-ins to add in the project.
+ *
+ * @param kotlin Configuration block for the [KotlinJvmProjectExtension].
+ * @param config Configuration block for the [FluxoConfigurationExtension].
+ *
+ * @see org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
+ * @see fluxo.conf.dsl.FluxoConfigurationExtension.setupKsp
+ * @see fluxo.conf.dsl.FluxoConfigurationExtension.optIns
+ *
+ * @see fkcSetupKotlinApp for a library configuration defaults.
+ */
+@JvmName("setupKotlinApp")
+public fun Project.fkcSetupKotlinApp(
+ setupKsp: Boolean? = null,
+ optIns: List? = null,
+ kotlin: (KotlinJvmProjectExtension.() -> Unit)? = null,
+ config: (FluxoConfigurationExtension.() -> Unit)? = null,
+) {
+ fluxoConfiguration {
+ isApplication = true
+ setupKotlin(optIns, setupKsp, config, kotlin)
+ }
+}
diff --git a/fluxo-kmp-conf/src/main/kotlin/PropsAndEnv.kt b/fluxo-kmp-conf/src/main/kotlin/PropsAndEnv.kt
index ea26dbff..24b71ff3 100644
--- a/fluxo-kmp-conf/src/main/kotlin/PropsAndEnv.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/PropsAndEnv.kt
@@ -55,8 +55,19 @@ public fun Project.isMaxDebugEnabled(): Provider = envOrPropFlag("MAX_D
internal fun Project.isFluxoVerbose(): Provider = envOrPropFlag("FLUXO_VERBOSE")
-/** @TODO: Disables whole artifact processing, not just shrinking; should be renamed. */
-public fun Project.isR8Disabled(): Provider = envOrPropFlag("DISABLE_R8")
+public fun Project.isShrinkerDisabled(): Provider = provider {
+ val disabled = envOrPropFlagValue("DISABLE_R8") ||
+ envOrPropFlagValue("DISABLE_SHRINKER") ||
+ envOrPropFlagValue("DISABLE_PROGUARD")
+
+ val enabled = envOrPropFlagValue("OPTIMIZE") ||
+ envOrPropFlagValue("SHRINK") ||
+ envOrPropFlagValue("ENABLE_R8") ||
+ envOrPropFlagValue("ENABLE_PROGUARD") ||
+ envOrPropFlagValue("ENABLE_SHRINKER")
+
+ disabled && !enabled
+}.memoizeSafe()
public fun Project?.buildNumber(): String? = envOrPropValueLenient("BUILD_NUMBER")
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/ComposeDesktopProcessing.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/ComposeDesktopProcessing.kt
new file mode 100644
index 00000000..edaa5fa4
--- /dev/null
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/ComposeDesktopProcessing.kt
@@ -0,0 +1,66 @@
+package fluxo.artifact.proc
+
+import fluxo.conf.dsl.impl.FluxoConfigurationExtensionImpl
+import fluxo.conf.impl.lc
+import fluxo.conf.impl.named
+import fluxo.log.e
+import fluxo.log.l
+import fluxo.log.w
+import fluxo.vc.l
+import fluxo.vc.v
+import org.gradle.api.Project
+import org.gradle.api.file.Directory
+import org.gradle.api.file.RegularFile
+import org.gradle.api.provider.Provider
+import org.jetbrains.compose.desktop.application.dsl.JvmApplication
+import org.jetbrains.compose.desktop.application.tasks.AbstractProguardTask
+
+/**
+ *
+ * @see org.jetbrains.compose.desktop.application.internal.configureProguardTask
+ * @see org.jetbrains.compose.desktop.application.internal.configurePackagingTasks
+ * @see org.jetbrains.compose.desktop.application.internal.configurePackageTask
+ * @see org.jetbrains.compose.desktop.application.tasks.AbstractProguardTask
+ * @see proguard.gradle.ProGuardTask
+ */
+internal fun Project.processComposeDesktopArtifact(
+ conf: FluxoConfigurationExtensionImpl,
+ jvmApp: JvmApplication,
+ jarProvider: Provider,
+ destinationDir: Provider,
+ builtBy: Any? = null,
+) {
+ // References:
+ // https://github.com/JetBrains/compose-multiplatform/issues/1174#issuecomment-1200122370
+
+ logger.l("Setup jar processing for Compose Desktop app..")
+
+ val composeProguard = jvmApp.buildTypes.release.proguard
+ if (composeProguard.isEnabled.orNull != true) {
+ logger.e(
+ "ProGuard is disabled for Compose Desktop app release build! " +
+ "Please enable it first!",
+ )
+ return
+ }
+ composeProguard.isEnabled.set(true)
+
+ // Set ProGuard version
+ val libs = conf.ctx.libs
+ val toolLc = JvmShrinker.ProGuard.name.lc()
+ val version = libs.v(toolLc) ?: libs.l(toolLc)?.version
+ if (!version.isNullOrEmpty()) {
+ composeProguard.version.set(version)
+ }
+
+ tasks.named(JB_COMPOSE_PROGUARD_TASK).configure {
+ builtBy?.let { dependsOn(it) }
+ actions.clear()
+ this.destinationDir.set(destinationDir)
+ this.mainJar.set(jarProvider)
+ doFirst {}
+ }
+ logger.w("Default Compose Desktop app ProGuard IS NOW REPLACED with Fluxo processing!")
+}
+
+private const val JB_COMPOSE_PROGUARD_TASK = "proguardReleaseJars"
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/MainClassesProvider.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/MainClassesProvider.kt
new file mode 100644
index 00000000..acb02d88
--- /dev/null
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/MainClassesProvider.kt
@@ -0,0 +1,91 @@
+package fluxo.artifact.proc
+
+import fluxo.conf.impl.kotlin.JETBRAINS_COMPOSE_PLUGIN_ID
+import fluxo.conf.impl.memoizeSafe
+import fluxo.log.w
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaApplication
+import org.gradle.api.provider.Provider
+import org.jetbrains.compose.ComposeExtension
+import org.jetbrains.compose.desktop.DesktopExtension
+import org.jetbrains.compose.desktop.application.dsl.JvmApplication
+
+/**
+ * Provides the main class of the JVM project if detected.
+ *
+ * [JavaApplication] and [ComposeExtension] are checked.
+ */
+internal fun Project.getMainClassesProvider(): Provider> {
+ val extensions = extensions
+ return provider p@{
+ // Java Application plugin
+ val app = extensions.findByName("application")
+ if (app != null) {
+ if (app is JavaApplication) {
+ val mainClass = app.mainClass.orNull
+ if (!mainClass.isNullOrBlank()) {
+ return@p listOf(mainClass)
+ }
+ logger.w("Main class is not set in the 'application' extension!")
+ } else {
+ logger.w("Unknown application plugin: $app (${app.javaClass.name})")
+ }
+ }
+
+ // Compose Desktop application
+ val jvmApp = composeDesktopApplication()
+ if (jvmApp != null && jvmApp is JvmApplication) {
+ val mainClass = jvmApp.mainClass
+ if (!mainClass.isNullOrBlank()) {
+ return@p listOf(mainClass)
+ }
+ logger.w("Main class is not set in the 'compose.desktop.application'!")
+ }
+
+ return@p emptyList()
+ }.memoizeSafe()
+}
+
+/**
+ *
+ * WARN: can be misleading if the plugin is not applied but will be later.
+ *
+ * @return the [JvmApplication] or null.
+ */
+internal fun Project.composeDesktopApplication(): Any? {
+ val compose = extensions.findByName("compose")
+ if (compose != null) {
+ try {
+ val desktop = (compose as ComposeExtension).extensions
+ .findByName("desktop") as DesktopExtension
+
+ if (desktop.isJvmApplication0) {
+ return desktop.application
+ }
+ } catch (e: Throwable) {
+ logger.w("JB Compose plugin error: $compose (${compose.javaClass.name})", e)
+ }
+ }
+ return null
+}
+
+internal fun Project.onComposeDesktopApplication(
+ action: (JvmApplication) -> Unit,
+) {
+ pluginManager.withPlugin(JETBRAINS_COMPOSE_PLUGIN_ID) {
+ extensions.configure("compose") c@{
+ this.extensions.configure("desktop") {
+ // No lazy API here, wait for the evaluation to be sure.
+ afterEvaluate {
+ if (isJvmApplication0) {
+ action(application)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+private val DesktopExtension.isJvmApplication0: Boolean
+ get() = _isJvmApplicationInitialized
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/OutgoingArtifactJar.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/OutgoingArtifactJar.kt
new file mode 100644
index 00000000..f9adc9ee
--- /dev/null
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/OutgoingArtifactJar.kt
@@ -0,0 +1,67 @@
+package fluxo.artifact.proc
+
+import fluxo.log.l
+import fluxo.log.vb
+import java.io.File
+import org.gradle.api.Project
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.file.RegularFile
+import org.gradle.api.logging.Logger
+import org.gradle.api.provider.Provider
+
+internal fun Project.replaceOutgoingArtifactJarInConfigurations(
+ jarProvider: Provider,
+ builtBy: Any? = null,
+) {
+ val project = this
+ val logger = project.logger
+ val projectDir = project.projectDir
+ logger.l("Replace outgoing artifact jar with processed one (in configurations)")
+ project.configurations.configureEach {
+ val confName = name
+ outgoing {
+ var removed = false
+ lateinit var artifactName: String
+ val iterator = artifacts.iterator()
+ for (artifact in iterator) {
+ if (!artifact.classifier.isNullOrBlank() ||
+ artifact.extension != "jar" ||
+ artifact.type != "jar"
+ ) {
+ continue
+ }
+ iterator.remove()
+ artifactName = artifact.name
+ removed = true
+ artifact.logRemoved(logger, confName, projectDir)
+ }
+ if (removed) {
+ artifact(jarProvider) {
+ // Pom and maven consumers do not like the
+ // `-all` or `-shadowed` classifiers.
+ classifier = ""
+ type = "jar"
+ extension = "jar"
+ name = artifactName
+ builtBy?.let { builtBy(it) }
+ }
+ }
+ }
+ }
+}
+
+private fun PublishArtifact.logRemoved(
+ logger: Logger,
+ confName: String,
+ projectDir: File,
+) = logger.vb {
+ append("Replaced non-classified artifact from configuration '")
+ append(confName).append("':\n ")
+ append('{')
+ append("name=").append(name).append(", ")
+ append("ext=").append(extension).append(", ")
+ append("type=").append(type).append(", ")
+ append("file=").append(file.toRelativeString(projectDir)).append(", ")
+ append("date=").append(date).append(", ")
+ append('}')
+}
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/ProcessorSetup.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/ProcessorSetup.kt
index a39e0af0..3e76bdce 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/ProcessorSetup.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/ProcessorSetup.kt
@@ -17,10 +17,11 @@ internal class ProcessorSetup? = null,
val chainState: ProcessorChainState? = null,
val chainForLog: String? = null,
+ val processAsApp: Boolean = false,
)
internal class ProcessorChainState(
val mainJar: Provider,
- val inputFiles: FileCollection,
+ val inputFiles: FileCollection?,
val mappingFile: Provider? = null,
)
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/SetupArtifactsProcessing.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/SetupArtifactsProcessing.kt
index 91ac6f16..c0f5eea2 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/SetupArtifactsProcessing.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/artifact/proc/SetupArtifactsProcessing.kt
@@ -7,7 +7,6 @@ import fluxo.conf.feat.API_DIR
import fluxo.conf.feat.bindToApiDumpTasks
import fluxo.conf.impl.register
import fluxo.log.l
-import fluxo.log.vb
import fluxo.log.w
import fluxo.shrink.SHRINKER_KEEP_GEN_TASK_NAME
import fluxo.shrink.ShrinkerVerificationTestTask
@@ -15,8 +14,8 @@ import fluxo.shrink.getShrinkerVerifyTaskName
import fluxo.shrink.registerShrinkerKeepRulesGenTask
import fluxo.shrink.registerShrinkerTask
import org.gradle.api.Action
-import org.gradle.api.Project
import org.gradle.api.Task
+import org.gradle.api.file.Directory
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.plugins.JavaPlugin.TEST_TASK_NAME
@@ -48,14 +47,17 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME
// Should be easy to switch between them in the consuming projects.
// Use variant attributes?
-// FIXME: Allow to call shrinker by task name even if it's disabled.
+// TODO: Allow to call shrinker by task name even if it's disabled.
// Or, at least, show an informative warning when it's disabled
// FIXME: Support shadow jar generation before shrinking artifacts.
// https://github.com/GradleUp/gr8
// https://github.com/johnrengelman/shadow
-// TODO: Support Android minification with ProGuard?
+// TODO: Support Android minification with Fluxo processing chain.
+
+// FIXME: Support final artifact filtration step similar to the Android packaging options.
+// e.g. android.packaging.resources.excludes
// region Notes and references:
// https://r8.googlesource.com/r8/
@@ -114,7 +116,11 @@ internal fun setupArtifactsProcessing(
val testTasks = mutableListOf>()
// Auto-generate keep rules from API reports
- if (conf.autoGenerateKeepRulesFromApis) {
+ val isApplication = conf.isApplication
+ val autoGenerateKeepRules = !isApplication &&
+ conf.autoGenerateKeepRulesFromApis &&
+ conf.enableApiValidation
+ if (autoGenerateKeepRules) {
val rulesGenRask = p.registerShrinkerKeepRulesGenTask(conf.autoGenerateKeepModifier)
when {
!processArtifacts -> dependencies += rulesGenRask
@@ -122,6 +128,8 @@ internal fun setupArtifactsProcessing(
}
}
+ val mainClassesProvider = p.getMainClassesProvider()
+
val testChainArtifact = !conf.ctx.testsDisabled
var replaceOutgoingJar = conf.replaceOutgoingJar
processorChains.forEachIndexed { chainId: Int, pc ->
@@ -129,6 +137,7 @@ internal fun setupArtifactsProcessing(
var chainTailTask: TaskProvider<*>? = null
var mainJar: Provider? = null
var inputFiles: FileCollection? = null
+ var destinationDir: Provider? = null
var chainState: ProcessorChainState? = null
val chainDependencies = dependencies.toMutableList()
val chainForLog = mutableListOf()
@@ -166,18 +175,30 @@ internal fun setupArtifactsProcessing(
runAfter = runAfter,
chainState = chainState,
chainForLog = chainForLog.joinToString(separator = " => ") { it.name },
+ processAsApp = isApplication,
)
- val shrinkTask = p.registerShrinkerTask(setup)
+ val shrinkTask = p.registerShrinkerTask(setup, mainClassesProvider)
if (hasNext || replaceOutgoingJar || testChainArtifact) {
mainJar = shrinkTask.flatMap { it.destinationFile }
+ destinationDir = shrinkTask.flatMap { it.destinationDir }
+
+ inputFiles = when {
+ // R8 Always provides only one output file.
+ // If more than one artifact is processed (e.g. app mode),
+ // it works as a shadowing (uber-jar).
+ shrinker === JvmShrinker.R8 && isApplication -> null
- // FIXME: Support shadowing and/or relocation processors in the chain.
- inputFiles = chainState?.inputFiles
- ?: p.objects.fileCollection().apply {
- from(shrinkTask.map { it.inputFiles })
+ // FIXME: Support other shadowing/relocation processors in the chain.
+ else -> when (chainState) {
+ null -> p.objects.fileCollection().apply {
+ from(shrinkTask.map { it.inputFiles })
+ }
+
+ else -> chainState.inputFiles
}
+ }
}
if (hasNext) {
chainDependencies += shrinkTask
@@ -189,7 +210,7 @@ internal fun setupArtifactsProcessing(
chainState = ProcessorChainState(
mainJar = requireNotNull(mainJar),
- inputFiles = requireNotNull(inputFiles),
+ inputFiles = inputFiles,
mappingFile = mappingFile,
)
} else {
@@ -208,21 +229,36 @@ internal fun setupArtifactsProcessing(
// Replace the original artifact with processed one, but only once.
// Replaces the default jar in outgoingVariants.
// FIXME: Provide unporcessed artifacts alongside the processed ones as a variant.
+ val jarProvider = requireNotNull(mainJar)
+ val lastChainTask = chainTailTask
if (replaceOutgoingJar) {
replaceOutgoingJar = false
- p.replaceOutgoingJar(
- jarProvider = requireNotNull(mainJar),
- builtBy = chainTailTask,
- )
+ if (!isApplication) {
+ p.replaceOutgoingArtifactJarInConfigurations(
+ jarProvider = jarProvider,
+ builtBy = lastChainTask,
+ )
+ } else {
+ val finalDestinationDir = requireNotNull(destinationDir)
+ p.onComposeDesktopApplication { jvmApp ->
+ p.processComposeDesktopArtifact(
+ conf = conf,
+ jvmApp = jvmApp,
+ jarProvider = jarProvider,
+ destinationDir = finalDestinationDir,
+ builtBy = lastChainTask,
+ )
+ }
+ }
}
- if (testChainArtifact && chainTailTask != null && mainJar != null) {
+ if (testChainArtifact && !isApplication && lastChainTask != null) {
val verifyTask = tasks.register(
name = getShrinkerVerifyTaskName(chainId = chainId),
) {
- dependsOn(chainTailTask)
+ dependsOn(lastChainTask)
- jar.set(mainJar)
+ jar.set(jarProvider)
this.inputFiles.setFrom(inputFiles)
this.chainForLog.set(chainForLog.joinToString(separator = " => ") { it.name })
@@ -252,55 +288,6 @@ internal fun setupArtifactsProcessing(
}
}
-private fun Project.replaceOutgoingJar(
- jarProvider: Provider,
- builtBy: Any? = null,
-) {
- val logger = logger
- logger.l("Replace outgoing jar with processed one")
- configurations.configureEach {
- val confName = name
- outgoing {
- var removed = false
- lateinit var artifactName: String
- val iterator = artifacts.iterator()
- for (artifact in iterator) {
- if (!artifact.classifier.isNullOrBlank() ||
- artifact.extension != "jar" ||
- artifact.type != "jar"
- ) {
- continue
- }
- iterator.remove()
- artifactName = artifact.name
- removed = true
- logger.vb {
- append("Replaced non-classified artifact from configuration '")
- append(confName).append("':\n ")
- append('{')
- append("name=").append(artifact.name).append(", ")
- append("ext=").append(artifact.extension).append(", ")
- append("type=").append(artifact.type).append(", ")
- append("file=").append(artifact.file.toRelativeString(projectDir)).append(", ")
- append("date=").append(artifact.date).append(", ")
- append('}')
- }
- }
- if (removed) {
- artifact(jarProvider) {
- // Pom and maven consumers do not like the
- // `-all` or `-shadowed` classifiers.
- classifier = ""
- type = "jar"
- extension = "jar"
- name = artifactName
- builtBy?.let { builtBy(it) }
- }
- }
- }
- }
-}
-
private fun isProcessingTaskCalled(conf: FluxoConfigurationExtensionImpl) =
conf.ctx.startTaskNames.any {
it.startsWith(PROCESS_TASK_PREFIX) || it == SHRINKER_KEEP_GEN_TASK_NAME
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/FluxoKmpConfContext.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/FluxoKmpConfContext.kt
index cfc2687e..13c965f2 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/FluxoKmpConfContext.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/FluxoKmpConfContext.kt
@@ -29,8 +29,8 @@ import isCI
import isDesugaringEnabled
import isFluxoVerbose
import isMaxDebugEnabled
-import isR8Disabled
import isRelease
+import isShrinkerDisabled
import javax.inject.Inject
import org.gradle.api.DomainObjectSet
import org.gradle.api.Project
@@ -200,7 +200,7 @@ internal abstract class FluxoKmpConfContext
isVerbose -> logger.w("FLUXO_VERBOSE is enabled!")
}
if (isDesugaringEnabled) logger.w("DESUGARING is enabled!")
- if (project.isR8Disabled().get()) {
+ if (project.isShrinkerDisabled().get()) {
logger.w("SHRINKING (R8/ProGuard) is disabled! (DISABLE_R8)")
}
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/FluxoKmpConfPlugin.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/FluxoKmpConfPlugin.kt
index 130df954..ae3c336d 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/FluxoKmpConfPlugin.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/FluxoKmpConfPlugin.kt
@@ -33,7 +33,7 @@ import fluxo.conf.impl.kotlin.KOTLIN_EXT
import fluxo.conf.impl.kotlin.configureKotlinJvm
import fluxo.conf.impl.kotlin.configureKotlinMultiplatform
import fluxo.conf.impl.kotlin.setupKmpYarnPlugin
-import isR8Disabled
+import isShrinkerDisabled
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
@@ -117,7 +117,7 @@ public class FluxoKmpConfPlugin : Plugin {
}
// Public API validation
- if (conf.enableApiValidation) {
+ if (conf.enableApiValidation && !conf.isApplication) {
setupBinaryCompatibilityValidator(conf)
}
@@ -126,7 +126,7 @@ public class FluxoKmpConfPlugin : Plugin {
// Artifacts processing: minification/shrinking, shadowing, etc.
val project = conf.project
- if (!project.isR8Disabled().get()) {
+ if (!project.isShrinkerDisabled().get()) {
setupArtifactsProcessing(conf)
}
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/FluxoConfigurationExtensionCommon.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/FluxoConfigurationExtensionCommon.kt
index 7ae73ae4..e3a5a219 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/FluxoConfigurationExtensionCommon.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/FluxoConfigurationExtensionCommon.kt
@@ -5,6 +5,18 @@ import org.gradle.api.Incubating
@FluxoKmpConfDsl
public interface FluxoConfigurationExtensionCommon {
+ /**
+ * Flags project as an application.
+ *
+ * Enables some settings targeted for applications.
+ * E.g. disables the binary compatibility validator.
+ *
+ * NOT inherited from the parent project if not set.
+ * Default value: `false`.
+ */
+ public var isApplication: Boolean
+
+
/**
* Flag that allows to disable dependency setup completely.
*
@@ -40,6 +52,8 @@ public interface FluxoConfigurationExtensionCommon {
public var enableBuildConfig: Boolean
+ // region Verification, Linting, and Formatting
+
/**
* Flag to turn on ALL the set of verification features, like Detekt, Spotless, and so on.
*
@@ -100,4 +114,6 @@ public interface FluxoConfigurationExtensionCommon {
* @see setupVerification
*/
public var enableSpotless: Boolean?
+
+ // endregion
}
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/FluxoConfigurationExtensionKotlin.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/FluxoConfigurationExtensionKotlin.kt
index 5f67fac9..610c96e8 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/FluxoConfigurationExtensionKotlin.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/FluxoConfigurationExtensionKotlin.kt
@@ -60,6 +60,8 @@ public interface FluxoConfigurationExtensionKotlin : FluxoConfigurationExtension
public var setupKapt: Boolean?
+ // region Compose
+
/**
* Flag to enable the Compose feature.
* Uses native capability for Android modules, multiplatform JetBrains Compose otherwise.
@@ -83,6 +85,10 @@ public interface FluxoConfigurationExtensionKotlin : FluxoConfigurationExtension
*/
public var suppressKotlinComposeCompatibilityCheck: Boolean?
+ // endregion
+
+
+ // region BinaryCompatibilityValidator
/**
* Flag to turn on the KotlinX BinaryCompatibilityValidator plugin.
@@ -113,6 +119,8 @@ public interface FluxoConfigurationExtensionKotlin : FluxoConfigurationExtension
apiValidation.apply(configure)
}
+ // endregion
+
/**
* Flag to use the KotlinX Dokka plugin as a documentation artifact generator.
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/impl/FluxoConfigurationExtensionKotlinImpl.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/impl/FluxoConfigurationExtensionKotlinImpl.kt
index 4b577b6a..2644e685 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/impl/FluxoConfigurationExtensionKotlinImpl.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/dsl/impl/FluxoConfigurationExtensionKotlinImpl.kt
@@ -178,6 +178,13 @@ internal interface FluxoConfigurationExtensionKotlinImpl :
set(value) = removeAssertionsInReleaseProp.set(value)
+ @get:Input
+ val applicationFlagProp: Property
+ override var isApplication: Boolean
+ get() = applicationFlagProp.orNull ?: false
+ set(value) = applicationFlagProp.set(value)
+
+
@get:Input
val setupDependenciesProp: Property
override var setupDependencies: Boolean
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/feat/SetupPublication.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/feat/SetupPublication.kt
index 4824833a..5ae807bf 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/feat/SetupPublication.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/feat/SetupPublication.kt
@@ -143,6 +143,8 @@ private fun Project.setupGradleProjectPublication(
tasks.withType {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
+ dirMode = "0755".toInt(radix = 8)
+ fileMode = "0644".toInt(radix = 8)
}
}
}
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/impl/android/SetupAndroidPackaging.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/impl/android/SetupAndroidPackaging.kt
index 2dae8b99..c0ae9bfe 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/impl/android/SetupAndroidPackaging.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/impl/android/SetupAndroidPackaging.kt
@@ -28,6 +28,7 @@ internal fun ApplicationExtension.setupPackagingOptions(
"**/org/apache/commons/**",
"**/version.properties",
"*.txt",
+ "META-INF/**.pro",
"META-INF/**.properties",
"META-INF/CHANGES**",
"META-INF/DEPENDENCIES**",
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/impl/kotlin/KotlinConfigSetup.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/impl/kotlin/KotlinConfigSetup.kt
index 77b50e5e..97737d77 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/impl/kotlin/KotlinConfigSetup.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/impl/kotlin/KotlinConfigSetup.kt
@@ -39,7 +39,9 @@ internal fun FluxoConfigurationExtensionImpl.KotlinConfig(
} else {
jvmTarget = jvmTargetInt.asJvmTargetVersion()
}
- val javaParameters = jvmTargetInt >= JRE_1_8 && javaParameters ?: false
+ val javaParameters = jvmTargetInt >= JRE_1_8 &&
+ javaParameters ?: false &&
+ !isApplication
// `jdk-release` requires Kotlin 1.7.0 or newer and JDK 9 or newer.
// Also, no sense to use it with the JVM toolchain.
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/jvm/JvmFilesProvider.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/jvm/JvmFilesProvider.kt
index 6eccc5da..7121c562 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/jvm/JvmFilesProvider.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/conf/jvm/JvmFilesProvider.kt
@@ -13,21 +13,34 @@ import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
internal sealed class JvmFilesProvider {
abstract fun jvmCompileFiles(project: Project): JvmFiles
+ abstract fun jvmRuntimeFiles(project: Project): JvmFiles
+
abstract class GradleJvmFilesProvider : JvmFilesProvider() {
protected abstract val jarTaskName: String
protected abstract val compileFiles: FileCollection
protected abstract val runtimeFiles: FileCollection
- override fun jvmCompileFiles(project: Project): JvmFiles {
+ override fun jvmCompileFiles(project: Project): JvmFiles =
+ jvmFiles(project, compileFiles)
+
+ override fun jvmRuntimeFiles(project: Project): JvmFiles =
+ jvmFiles(project, runtimeFiles)
+
+ private fun jvmFiles(project: Project, files: FileCollection): JvmFiles {
val jarTask = project.tasks.named(jarTaskName, Jar::class.java)
val mainJar = jarTask.flatMap { it.archiveFile }
val jarFiles = project.objects.fileCollection()
- val filterSpec = Spec { it.path.endsWith(".jar") }
- jarFiles.from(compileFiles.filter(filterSpec))
+ jarFiles.from(files.filter(JarFilterSpec))
return JvmFiles(mainJar, jarFiles, arrayOf(jarTask))
}
}
+ private object JarFilterSpec : Spec {
+ override fun isSatisfiedBy(file: File): Boolean =
+ file.path.endsWith(".jar")
+ }
+
+
class FromGradleSourceSet(private val sourceSet: SourceSet) :
GradleJvmFilesProvider() {
override val jarTaskName: String
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/gradle/ProviderUtils.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/gradle/ProviderUtils.kt
index cf26b8b0..865e3bcb 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/gradle/ProviderUtils.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/gradle/ProviderUtils.kt
@@ -36,5 +36,8 @@ internal inline fun ObjectFactory.setProperty(): SetProperty Provider.toProperty(objects: ObjectFactory): Property =
objects.property(T::class.java).value(this)
+
internal fun Provider.toBooleanProvider(defaultValue: Boolean): Provider =
orElse(defaultValue.toString()).map { "true" == it }
+
+internal operator fun Provider.not(): Provider = map { !it }
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/shrink/AbstractShrinkerTask.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/shrink/AbstractShrinkerTask.kt
index 8acf5fae..d53c69cb 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/shrink/AbstractShrinkerTask.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/shrink/AbstractShrinkerTask.kt
@@ -1,7 +1,4 @@
@file:Suppress(
- "KDocUnresolvedReference",
- "LeakingThis",
- "LongParameterList",
"LongParameterList",
"NestedBlockDepth",
"TooManyFunctions",
@@ -25,8 +22,10 @@ import fluxo.gradle.ioFileOrNull
import fluxo.gradle.listProperty
import fluxo.gradle.mkdirs
import fluxo.gradle.normalizedPath
+import fluxo.gradle.not
import fluxo.gradle.notNullProperty
import fluxo.gradle.nullableProperty
+import fluxo.gradle.setProperty
import fluxo.log.SHOW_DEBUG_LOGS
import fluxo.log.e
import fluxo.log.i
@@ -47,6 +46,7 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
+import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.CompileClasspath
@@ -61,13 +61,14 @@ import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
-import org.gradle.language.base.plugins.LifecycleBasePlugin
/**
*
* @see fluxo.shrink.ShrinkerKeepRulesBySeedsTest
*
* @see org.jetbrains.compose.desktop.application.tasks.AbstractProguardTask
+ * @see org.jetbrains.compose.desktop.application.internal.configureProguardTask
+ * @see org.jetbrains.compose.desktop.application.internal.configurePackagingTasks
* @see com.android.build.gradle.internal.tasks.ProguardConfigurableTask
* @see com.android.build.gradle.internal.tasks.R8Task
* @see proguard.gradle.ProGuardTask
@@ -96,11 +97,11 @@ internal abstract class AbstractShrinkerTask : AbstractExternalFluxoTask() {
@get:Optional
@get:Input
- val processApplication: Property = objects.notNullProperty(false)
+ val processAsApp: Property = objects.notNullProperty(false)
@get:Optional
@get:Input
- val filterMultireleaseJars: Property = objects.notNullProperty(true)
+ val filterMultireleaseJars: Property = objects.notNullProperty(!processAsApp)
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
@@ -151,7 +152,7 @@ internal abstract class AbstractShrinkerTask : AbstractExternalFluxoTask() {
@get:PathSensitive(PathSensitivity.RELATIVE)
val rulesFile: Provider = defaultRulesFile.map { file ->
objects.fileTree().from(file.asFile.parentFile)
- .filter { it.path.endsWith(".pro") }
+ .filter { it.path.endsWith(PRO_EXT) }
}
@get:Input
@@ -166,7 +167,7 @@ internal abstract class AbstractShrinkerTask : AbstractExternalFluxoTask() {
@get:Input
@get:Optional
- val mainClasses: ListProperty = objects.listProperty()
+ val mainClasses: SetProperty = objects.setProperty()
@get:Input
@get:Optional
@@ -216,7 +217,7 @@ internal abstract class AbstractShrinkerTask : AbstractExternalFluxoTask() {
init {
- group = LifecycleBasePlugin.BUILD_GROUP
+ group = FLUXO_TASK_GROUP
description = "Shrink and optimize final JVM artifact"
val p = project
@@ -280,7 +281,7 @@ internal abstract class AbstractShrinkerTask : AbstractExternalFluxoTask() {
// For a final application, we need to process all jars,
// but for a library, only the main jar.
- val onlyMainJar = !processApplication.get()
+ val processOnlyMainJar = !processAsApp.get()
val inputToOutputJars = LinkedHashMap()
var initialSize: Long = 0
@@ -293,7 +294,7 @@ internal abstract class AbstractShrinkerTask : AbstractExternalFluxoTask() {
for (inputFile in inputFiles) {
val outputFile = destinationDir.resolve(inputFile.name)
if (!inputFile.name.endsWith(".jar", ignoreCase = true)) {
- if (!onlyMainJar) {
+ if (!processOnlyMainJar) {
inputFile.copyTo(outputFile)
initialSize += inputFile.length()
}
@@ -301,7 +302,7 @@ internal abstract class AbstractShrinkerTask : AbstractExternalFluxoTask() {
}
val output = when {
- onlyMainJar -> null
+ processOnlyMainJar -> null
else -> outputFile
}
if (inputToOutputJars.putIfAbsent(inputFile, output) != null) {
@@ -497,7 +498,7 @@ internal abstract class AbstractShrinkerTask : AbstractExternalFluxoTask() {
add("--release") // (default) vs --debug
add("--classfile") // vs --dex (default)
cliArg("--lib", javaHome)
- cliArg("--output", outJars.single(), base = workDir)
+ cliArg("--output", outJars.first(), base = workDir)
// Only for DEX output
// androidMinSdk.orNull?.let { cliArg("--min-api", it) }
@@ -584,12 +585,14 @@ internal abstract class AbstractShrinkerTask : AbstractExternalFluxoTask() {
// -addconfigurationdebugging
// -dump
- // FIXME: Automatic main class detection from jar manifest,
- // processor configuration,
- // plugin configuration
+ // FIXME: Add automatic main class detection from the main jar manifest
mainClasses.get().ifNotEmpty {
writer.ln()
- forEach { mainClass ->
+ forEach mc@{ mainClass: String? ->
+ if (mainClass.isNullOrBlank()) {
+ return@mc
+ }
+
writer.ln(
"""
-keep public class $mainClass {
diff --git a/fluxo-kmp-conf/src/main/kotlin/fluxo/shrink/RegisterShrinkerTask.kt b/fluxo-kmp-conf/src/main/kotlin/fluxo/shrink/RegisterShrinkerTask.kt
index f4eae045..8edb3d23 100644
--- a/fluxo-kmp-conf/src/main/kotlin/fluxo/shrink/RegisterShrinkerTask.kt
+++ b/fluxo-kmp-conf/src/main/kotlin/fluxo/shrink/RegisterShrinkerTask.kt
@@ -1,3 +1,5 @@
+@file:Suppress("CyclomaticComplexMethod")
+
package fluxo.shrink
import MAIN_SOURCE_SET_NAME
@@ -20,6 +22,7 @@ import fluxo.conf.impl.register
import fluxo.conf.jvm.JvmFiles
import fluxo.conf.jvm.JvmFilesProvider
import fluxo.gradle.ioFile
+import fluxo.gradle.not
import fluxo.log.SHOW_DEBUG_LOGS
import fluxo.log.l
import fluxo.log.v
@@ -29,12 +32,21 @@ import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
+import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
+internal const val PRO_EXT = ".pro"
+
+/**
+ *
+ * @see org.jetbrains.compose.desktop.application.internal.configureProguardTask
+ * @see org.jetbrains.compose.desktop.application.internal.configurePackagingTasks
+ */
internal fun Project.registerShrinkerTask(
setup: ProcessorSetup,
+ mainClassesProvider: Provider>,
): TaskProvider = tasks.register(
name = setup.getShrinkerTaskName(),
) {
@@ -64,10 +76,11 @@ internal fun Project.registerShrinkerTask(
// Disable obfuscation by default as often suboptimal with much harder troubleshooting.
// If disabled by default, cleaner API uses flag name without a negation.
// That's why a task property follows ProGuard design, when DSL does the opposite.
- dontoptimize.set(settings.optimize.map { !it })
- dontobfuscate.set(settings.obfuscate.map { !it })
+ dontoptimize.set(!settings.optimize)
+ dontobfuscate.set(!settings.obfuscate)
val incrementObfuscation = settings.obfuscateIncrementally.get()
obfuscateIncrementally.set(incrementObfuscation)
+ processAsApp.set(setup.processAsApp)
maxHeapSize.set(settings.maxHeapSize)
callFallbackOrder.set(settings.callFallbackOrder)
@@ -75,22 +88,25 @@ internal fun Project.registerShrinkerTask(
(conf.androidMinSdk as? Int)?.let { androidMinSdk.set(it) }
conf.kotlinConfig.jvmTarget?.let { jvmTarget.set(it) }
+ mainClasses.set(mainClassesProvider)
+
val state = setup.chainState
if (state != null) {
- inputFiles.from(state.inputFiles)
mainJar.set(state.mainJar)
+ state.inputFiles?.let { inputFiles.from(it) }
if (incrementObfuscation) {
state.mappingFile?.let { applyMapping.set(it) }
}
} else {
- useClasspathFiles { files ->
+ useClasspathFiles(useRuntimeClasspath = setup.processAsApp) { files ->
inputFiles.from(files.allJars)
mainJar.set(files.mainJar)
}
}
val defaultRulesFile = defaultRulesFile.ioFile
- val shrinkerSpecificConf = defaultRulesFile.parentFile.resolve(shrinker.name.lc())
+ val shrinkerSpecificConf = defaultRulesFile.parentFile
+ .resolve(shrinker.name.lc() + PRO_EXT)
if (shrinkerSpecificConf.exists()) {
configurationFiles.from(shrinkerSpecificConf)
} else if (!defaultRulesFile.exists()) {
@@ -162,7 +178,10 @@ private fun DefaultTask.notifyThatToolIsStarting(tool: String, version: String?
}
}
-private fun T.useClasspathFiles(fn: T.(JvmFiles) -> Unit) {
+private fun T.useClasspathFiles(
+ useRuntimeClasspath: Boolean = false,
+ fn: T.(JvmFiles) -> Unit,
+) {
val project = project
if (project.pluginManager.hasPlugin(KOTLIN_MPP_PLUGIN_ID)) {
var isJvmTargetConfigured = false
@@ -184,24 +203,28 @@ private fun T.useClasspathFiles(fn: T.(JvmFiles) -> Unit) {
}
val filesProvider =
JvmFilesProvider.FromKotlinMppTarget(from)
- useClasspathFiles(filesProvider, fn)
+ useClasspathFiles(filesProvider, fn, useRuntimeClasspath)
}
}
} else if (project.pluginManager.hasPlugin(KOTLIN_JVM_PLUGIN_ID)) {
val mainSourceSet = project.javaSourceSets[MAIN_SOURCE_SET_NAME]
val filesProvider =
JvmFilesProvider.FromGradleSourceSet(mainSourceSet)
- useClasspathFiles(filesProvider, fn)
+ useClasspathFiles(filesProvider, fn, useRuntimeClasspath)
}
}
private fun T.useClasspathFiles(
provider: JvmFilesProvider,
fn: T.(JvmFiles) -> Unit,
+ useRuntimeClasspath: Boolean,
) {
- logger.v("Using classpath files ${provider.javaClass.simpleName} for task $name...")
- provider.jvmCompileFiles(project)
- .configureUsageBy(this, fn)
+ val type = if (useRuntimeClasspath) "runtime" else "compile"
+ logger.v("Using $type classpath files ${provider.javaClass.simpleName} for task $name...")
+ when {
+ useRuntimeClasspath -> provider.jvmRuntimeFiles(project)
+ else -> provider.jvmCompileFiles(project)
+ }.configureUsageBy(this, fn)
}
diff --git a/gradle.properties b/gradle.properties
index 69742f40..8435799f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,8 +3,8 @@
# Project-wide Gradle settings.
-org.gradle.jvmargs=-Xmx2G -Dfile.encoding=UTF-8 -XX:+UseParallelGC
-kotlin.daemon.jvmargs=-Xmx1G -XX:+UseParallelGC
+org.gradle.jvmargs=-Xmx2G -XX:+UseParallelGC -Dfile.encoding=UTF-8
+kotlin.daemon.jvmargs=-Xmx2G -XX:+UseParallelGC
org.gradle.caching=true
org.gradle.configureondemand=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 589ad08b..e863d15a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -234,6 +234,8 @@ fluxo-bcv-js = { id = "io.github.fluxo-kt.binary-compatibility-validator-js", ve
android-lib = { id = "com.android.library", version.ref = "android-gradle-plugin" }
+jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "jetbrains-compose" }
+
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
# Auto-completion and symbol resolution for all Kotlin/Native platforms on any OS.