From 71148072af1a97cb15f1d7e9767a00af167a967e Mon Sep 17 00:00:00 2001 From: Eliezer Graber Date: Tue, 27 Dec 2022 11:35:07 -0500 Subject: [PATCH] Plugins that make working with Gradle simpler (#101) --- .ci-java-version | 1 + .github/dependabot.yml | 14 -- .github/workflows/pr.yml | 28 +-- .github/workflows/publish.yml | 22 +- .github/workflows/publish_snapshot.yml | 10 +- gradle/libs.versions.toml | 10 + renovate.json | 2 +- settings.gradle.kts | 5 + .../com/eygraber/gradle/detekt/detekt.kt | 45 +++- .../gradle/detekt/detekt_common_test.kt | 15 +- .../gradle/kotlin/KotlinFreeCompilerArg.kt | 5 + .../com/eygraber/gradle/kotlin/KotlinOptIn.kt | 10 + .../eygraber/gradle/kotlin/dependencies.kt | 21 ++ .../kotlin/com/eygraber/gradle/kotlin/kgp.kt | 41 +++- utils-plugin/build.gradle.kts | 17 +- .../src/main/kotlin/ProjectExtensions.kt | 47 ++++ ...eygraber.gradle-android-library.gradle.kts | 182 +++++++++++++++ ...graber.gradle-compose-jetbrains.gradle.kts | 74 +++++++ ...eygraber.gradle-compose-jetpack.gradle.kts | 13 ++ .../com.eygraber.gradle-compose.gradle.kts | 29 +++ ...om.eygraber.gradle-dependencies.gradle.kts | 52 +++++ .../com.eygraber.gradle-detekt.gradle.kts | 38 ++++ ....eygraber.gradle-kotlin-library.gradle.kts | 36 +++ ...ber.gradle-kotlin-multiplatform.gradle.kts | 4 + ....eygraber.gradle-publish-github.gradle.kts | 38 ++++ ...er.gradle-publish-maven-central.gradle.kts | 12 + ...com.eygraber.gradle-publish-spm.gradle.kts | 65 ++++++ .../com/eygraber/gradle/GradleUtilsPlugin.kt | 108 ++++++++- .../gradle/GradleUtilsPluginExtension.kt | 208 ++++++++++++++++++ .../gradle/android/GradleUtilsAndroid.kt | 70 ++++++ .../gradle/compose/GradleUtilsCompose.kt | 26 +++ .../dependencies/GradleUtilsDependencies.kt | 32 +++ .../gradle/detekt/GradleUtilsDetekt.kt | 31 +++ .../gradle/github/GradleUtilsGitHub.kt | 6 + .../gradle/kotlin/GradleUtilsKotlin.kt | 15 ++ .../com/eygraber/gradle/spm/GradleUtilsSpm.kt | 7 + utils-plugin/src/main/kotlin/kmp.kt | 105 +++++++++ utils-plugin/src/main/kotlin/ksp.kt | 76 +++++++ 38 files changed, 1451 insertions(+), 69 deletions(-) create mode 100644 .ci-java-version delete mode 100644 .github/dependabot.yml create mode 100644 utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/KotlinFreeCompilerArg.kt create mode 100644 utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/KotlinOptIn.kt create mode 100644 utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/dependencies.kt create mode 100644 utils-plugin/src/main/kotlin/ProjectExtensions.kt create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-android-library.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-compose-jetbrains.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-compose-jetpack.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-compose.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-dependencies.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-detekt.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-kotlin-library.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-kotlin-multiplatform.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-github.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-maven-central.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-spm.gradle.kts create mode 100644 utils-plugin/src/main/kotlin/com/eygraber/gradle/GradleUtilsPluginExtension.kt create mode 100644 utils-plugin/src/main/kotlin/com/eygraber/gradle/android/GradleUtilsAndroid.kt create mode 100644 utils-plugin/src/main/kotlin/com/eygraber/gradle/compose/GradleUtilsCompose.kt create mode 100644 utils-plugin/src/main/kotlin/com/eygraber/gradle/dependencies/GradleUtilsDependencies.kt create mode 100644 utils-plugin/src/main/kotlin/com/eygraber/gradle/detekt/GradleUtilsDetekt.kt create mode 100644 utils-plugin/src/main/kotlin/com/eygraber/gradle/github/GradleUtilsGitHub.kt create mode 100644 utils-plugin/src/main/kotlin/com/eygraber/gradle/kotlin/GradleUtilsKotlin.kt create mode 100644 utils-plugin/src/main/kotlin/com/eygraber/gradle/spm/GradleUtilsSpm.kt create mode 100644 utils-plugin/src/main/kotlin/kmp.kt create mode 100644 utils-plugin/src/main/kotlin/ksp.kt diff --git a/.ci-java-version b/.ci-java-version new file mode 100644 index 0000000..98d9bcb --- /dev/null +++ b/.ci-java-version @@ -0,0 +1 @@ +17 diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 6a58475..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: 2 - -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - time: "08:00" - timezone: "America/New_York" - target-branch: "master" - commit-message: - prefix: "Dependabot" - labels: - - "gh-actions" diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 28ffd89..4e7231e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -7,10 +7,10 @@ jobs: danger: runs-on: ubuntu-latest steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@v3 - name: Danger - uses: docker://ghcr.io/danger/danger-kotlin:1.1.0 + uses: docker://ghcr.io/danger/danger-kotlin:1.2.0 with: args: --failOnErrors --no-publish-check env: @@ -19,7 +19,7 @@ jobs: no_accessors: runs-on: ubuntu-latest steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@v3 - name: Don't allow any accessor imports run: if grep --include=\*.{kt,kts} -rne 'import gradle\.kotlin\.dsl\.accessors\._' .; then false; else true; fi @@ -27,17 +27,17 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@v3 - - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b + - uses: actions/setup-java@v3.9.0 with: distribution: 'zulu' - java-version: 17 + java-version-file: .ci-java-version - - uses: gradle/wrapper-validation-action@55e685c48d84285a5b0418cd094606e199cca3b6 + - uses: gradle/wrapper-validation-action@v1 - name: Setup Gradle - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef + uses: gradle/gradle-build-action@v2 with: gradle-version: wrapper @@ -47,22 +47,22 @@ jobs: detektWithTypeResolution: runs-on: ubuntu-latest steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@v3 - - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b + - uses: actions/setup-java@v3.9.0 with: distribution: 'zulu' - java-version: 17 + java-version-file: .ci-java-version - - uses: gradle/wrapper-validation-action@55e685c48d84285a5b0418cd094606e199cca3b6 + - uses: gradle/wrapper-validation-action@v1 - name: Setup Gradle - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef + uses: gradle/gradle-build-action@v2 with: gradle-version: wrapper - name: Ensure project builds - run: ./gradlew detektMain + run: ./gradlew :utils-base:detektMain :utils-detekt:detektMain :utils-kotlin:detektMain env: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b308e55..44e4ee5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,24 +16,24 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@v3 - name: Generate versions - uses: HardNorth/github-version-generate@91998704a2b37be20604933d9c989ebade9c5f89 + uses: HardNorth/github-version-generate@v1.3.0 with: version-source: file version-file: ${{ env.VERSION_FILE }} version-file-extraction-pattern: ${{ env.VERSION_EXTRACT_PATTERN }} - - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b + - uses: actions/setup-java@v3.9.0 with: distribution: 'zulu' - java-version: 17 + java-version-file: .ci-java-version - - uses: gradle/wrapper-validation-action@55e685c48d84285a5b0418cd094606e199cca3b6 + - uses: gradle/wrapper-validation-action@v1 - name: Setup Gradle - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef + uses: gradle/gradle-build-action@v2 with: gradle-version: wrapper @@ -45,7 +45,7 @@ jobs: run: ./gradlew publish -PVERSION_NAME=${{ env.RELEASE_VERSION }} - name: Import GPG Key - uses: crazy-max/ghaction-import-gpg@111c56156bcc6918c056dbef52164cfa583dc549 + uses: crazy-max/ghaction-import-gpg@v5.2.0 with: gpg_private_key: ${{ secrets.GIT_SIGNING_PRIVATE_KEY }} passphrase: ${{ secrets.GIT_SIGNING_PRIVATE_KEY_PASSWORD }} @@ -55,7 +55,7 @@ jobs: - name: Create tag id: create_tag - uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 + uses: actions/github-script@v6 with: script: | github.rest.git.createRef({ @@ -67,7 +67,7 @@ jobs: - name: Build changelog id: build_changelog - uses: mikepenz/release-changelog-builder-action@d0814e580b0dffbc99d2b25e54cd42d2eb533a97 + uses: mikepenz/release-changelog-builder-action@v3 with: configuration: "changelog_config.json" toTag: ${{ env.RELEASE_VERSION }} @@ -76,7 +76,7 @@ jobs: - name: Create release id: create_release - uses: ncipollo/release-action@a2e71bdd4e7dab70ca26a852f29600c98b33153e + uses: ncipollo/release-action@v1.12.0 with: body: ${{ steps.build_changelog.outputs.changelog }} commit: release @@ -84,7 +84,7 @@ jobs: tag: ${{ env.RELEASE_VERSION }} token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@v3 with: ref: 'master' token: ${{ secrets.PUSH_PAT }} diff --git a/.github/workflows/publish_snapshot.yml b/.github/workflows/publish_snapshot.yml index 5013553..7f65f61 100644 --- a/.github/workflows/publish_snapshot.yml +++ b/.github/workflows/publish_snapshot.yml @@ -10,17 +10,17 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@v3 - - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b + - uses: actions/setup-java@v3.9.0 with: distribution: 'zulu' - java-version: 17 + java-version-file: .ci-java-version - - uses: gradle/wrapper-validation-action@55e685c48d84285a5b0418cd094606e199cca3b6 + - uses: gradle/wrapper-validation-action@v1 - name: Setup Gradle - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef + uses: gradle/gradle-build-action@v2 with: gradle-version: wrapper diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2d334c..404981d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,10 @@ [versions] +android-cacheFix = "2.6.0" + +android-plugin = "8.0.0-alpha10" + +compose-jetbrains = "1.2.2" + detekt = "1.22.0" detektEygraber = "1.0.11" @@ -22,9 +28,13 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } publish = { id = "com.vanniktech.maven.publish", version.ref = "publish" } [libraries] +buildscript-android = { module = "com.android.tools.build:gradle", version.ref = "android-plugin" } +buildscript-androidCacheFix = { module = "gradle.plugin.org.gradle.android:android-cache-fix-gradle-plugin", version.ref = "android-cacheFix" } +buildscript-compose-jetbrains = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose-jetbrains" } buildscript-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } buildscript-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } buildscript-ejson = { module = "com.eygraber:ejson-gradle", version.ref = "ejson" } +buildscript-foojay = "org.gradle.toolchains:foojay-resolver:0.4.0" buildscript-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } buildscript-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "publish" } buildscript-utils-detekt = { module = "com.eygraber:gradle-utils-detekt", version.ref = "gradleUtils" } diff --git a/renovate.json b/renovate.json index eb51187..fc8ce65 100644 --- a/renovate.json +++ b/renovate.json @@ -2,7 +2,7 @@ "extends": [ "config:base" ], - "enabledManagers": ["gradle"], + "enabledManagers": ["gradle", "gradle-wrapper", "github-actions"], "labels": ["dependencies"], "prHourlyLimit": 3 } diff --git a/settings.gradle.kts b/settings.gradle.kts index e087965..be653b1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,6 +9,11 @@ pluginManagement { } gradlePluginPortal() mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") { + content { + includeGroupByRegex("org\\.jetbrains\\.compose.*") + } + } } } diff --git a/utils-detekt/src/main/kotlin/com/eygraber/gradle/detekt/detekt.kt b/utils-detekt/src/main/kotlin/com/eygraber/gradle/detekt/detekt.kt index b62b6f0..b066fa6 100644 --- a/utils-detekt/src/main/kotlin/com/eygraber/gradle/detekt/detekt.kt +++ b/utils-detekt/src/main/kotlin/com/eygraber/gradle/detekt/detekt.kt @@ -4,22 +4,38 @@ import io.gitlab.arturbosch.detekt.Detekt import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.Action import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.plugins.ExtensionAware import org.gradle.api.provider.Provider import org.gradle.internal.Actions -import java.io.File public fun Project.configureDetekt( jdkVersion: Provider, - configFile: File = File("$rootDir/detekt.yml"), + useRootConfigFile: Boolean = true, + useProjectConfigFile: Boolean = true, + configFiles: ConfigurableFileCollection = files(), + ignoredAndroidFlavors: List = emptyList(), + ignoredAndroidVariants: List = emptyList(), configure: Action = Actions.doNothing() ) { - configureDetekt(jdkVersion.get(), configFile, configure) + configureDetekt( + jdkVersion.get(), + useRootConfigFile, + useProjectConfigFile, + configFiles, + ignoredAndroidFlavors, + ignoredAndroidVariants, + configure + ) } public fun Project.configureDetekt( jdkVersion: String, - configFile: File = File("$rootDir/detekt.yml"), + useRootConfigFile: Boolean = true, + useProjectConfigFile: Boolean = true, + configFiles: ConfigurableFileCollection = files(), + ignoredAndroidFlavors: List = emptyList(), + ignoredAndroidVariants: List = emptyList(), configure: Action = Actions.doNothing() ) { detekt { @@ -30,7 +46,20 @@ public fun Project.configureDetekt( buildUponDefaultConfig = true - config = files(configFile) + config = configFiles.apply { + val rootConfig = rootProject.file("detekt.yml") + if(useRootConfigFile && rootConfig.exists()) { + from(rootConfig) + } + + val projectConfig = project.file("detekt.yml") + if(useProjectConfigFile && projectConfig.exists()) { + from(projectConfig) + } + } + + ignoredFlavors = ignoredFlavors + ignoredAndroidFlavors + ignoredVariants = ignoredVariants + ignoredAndroidVariants configure.execute(this) } @@ -38,6 +67,12 @@ public fun Project.configureDetekt( tasks.withType(Detekt::class.java).configureEach { // Target version of the generated JVM bytecode. It is used for type resolution. jvmTarget = jdkVersion + val projectDir = projectDir + val buildDir = project.buildDir + + exclude { + it.file.relativeTo(projectDir).startsWith(buildDir.relativeTo(projectDir)) + } } } diff --git a/utils-detekt/src/main/kotlin/com/eygraber/gradle/detekt/detekt_common_test.kt b/utils-detekt/src/main/kotlin/com/eygraber/gradle/detekt/detekt_common_test.kt index 512713c..fd356c9 100644 --- a/utils-detekt/src/main/kotlin/com/eygraber/gradle/detekt/detekt_common_test.kt +++ b/utils-detekt/src/main/kotlin/com/eygraber/gradle/detekt/detekt_common_test.kt @@ -2,13 +2,20 @@ package com.eygraber.gradle.detekt import com.eygraber.gradle.kotlin.kmp.kmpSourceSets import org.gradle.api.Project +import org.gradle.api.UnknownTaskException import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper public fun Project.registerCommonTestDetektTask() { plugins.withType(KotlinMultiplatformPluginWrapper::class.java) { - registerDetektTask( - name = "metadataTest", - sourceSet = kmpSourceSets.getByName("commonTest") - ) + try { + tasks.named("detektMetadataTest") + } + catch(_: UnknownTaskException) { + // only register the task if it hasn't already been registered + registerDetektTask( + name = "metadataTest", + sourceSet = kmpSourceSets.getByName("commonTest") + ) + } } } diff --git a/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/KotlinFreeCompilerArg.kt b/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/KotlinFreeCompilerArg.kt new file mode 100644 index 0000000..02ef640 --- /dev/null +++ b/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/KotlinFreeCompilerArg.kt @@ -0,0 +1,5 @@ +package com.eygraber.gradle.kotlin + +public sealed class KotlinFreeCompilerArg(public val value: String) { + public class Unknown(value: String) : KotlinFreeCompilerArg(value) +} diff --git a/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/KotlinOptIn.kt b/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/KotlinOptIn.kt new file mode 100644 index 0000000..7589e4e --- /dev/null +++ b/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/KotlinOptIn.kt @@ -0,0 +1,10 @@ +package com.eygraber.gradle.kotlin + +public sealed class KotlinOptIn(public val value: String) { + public object ExperimentalCoroutines : KotlinOptIn("kotlinx.coroutines.ExperimentalCoroutinesApi") + public object ExperimentalTime : KotlinOptIn("kotlin.time.ExperimentalTime") + public object FlowPreview : KotlinOptIn("kotlinx.coroutines.FlowPreview") + public object JsExport : KotlinOptIn("kotlin.js.ExperimentalJsExport") + public object RequiresOptIn : KotlinOptIn("kotlin.RequiresOptIn") + public class Unknown(value: String) : KotlinOptIn(value) +} diff --git a/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/dependencies.kt b/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/dependencies.kt new file mode 100644 index 0000000..e8da88e --- /dev/null +++ b/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/dependencies.kt @@ -0,0 +1,21 @@ +package com.eygraber.gradle.kotlin + +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency + +public fun Project.doOnFirstMatchingIncomingDependencyBeforeResolution( + configurationName: String, + dependencyPredicate: Dependency.() -> Boolean, + onMatch: (Dependency) -> Unit +) { + configurations.named(configurationName).configure { + incoming.beforeResolve { + for(dependency in dependencies) { + if(dependencyPredicate(dependency)) { + onMatch(dependency) + break + } + } + } + } +} diff --git a/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/kgp.kt b/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/kgp.kt index 6aec398..0049f86 100644 --- a/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/kgp.kt +++ b/utils-kotlin/src/main/kotlin/com/eygraber/gradle/kotlin/kgp.kt @@ -1,6 +1,7 @@ package com.eygraber.gradle.kotlin import org.gradle.api.Project +import org.gradle.api.plugins.JavaBasePlugin import org.gradle.api.provider.Provider import org.gradle.api.tasks.compile.JavaCompile import org.gradle.jvm.toolchain.JavaLanguageVersion @@ -9,21 +10,26 @@ import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper +import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper import org.jetbrains.kotlin.gradle.tasks.KotlinCompile public fun Project.configureKgp( jdkVersion: Provider, + jvmDistribution: JvmVendorSpec? = null, allWarningsAsErrors: Boolean = true, explicitApiMode: ExplicitApiMode = ExplicitApiMode.Disabled, configureJavaCompatibility: Boolean = true, - freeCompilerArgs: List = emptyList(), - vararg optIns: String + useK2: Boolean = false, + freeCompilerArgs: List = emptyList(), + vararg optIns: KotlinOptIn ) { configureKgp( jdkVersion.get(), + jvmDistribution, allWarningsAsErrors, explicitApiMode, configureJavaCompatibility, + useK2, freeCompilerArgs, *optIns ) @@ -31,11 +37,13 @@ public fun Project.configureKgp( public fun Project.configureKgp( jdkVersion: String, + jvmDistribution: JvmVendorSpec? = null, allWarningsAsErrors: Boolean = true, explicitApiMode: ExplicitApiMode = ExplicitApiMode.Disabled, configureJavaCompatibility: Boolean = true, - freeCompilerArgs: List = emptyList(), - vararg optIns: String + useK2: Boolean = false, + freeCompilerArgs: List = emptyList(), + vararg optIns: KotlinOptIn ) { if(configureJavaCompatibility) { tasks.withType(JavaCompile::class.java) { @@ -45,21 +53,28 @@ public fun Project.configureKgp( } plugins.withType(KotlinBasePluginWrapper::class.java) { + val isKmp = this is KotlinMultiplatformPluginWrapper with(extensions.getByType(KotlinProjectExtension::class.java)) { when(explicitApiMode) { ExplicitApiMode.Strict -> explicitApi() ExplicitApiMode.Warning -> explicitApiWarning() - ExplicitApiMode.Disabled -> {} + ExplicitApiMode.Disabled -> explicitApi = null } - jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(jdkVersion.removePrefix("1."))) - vendor.set(JvmVendorSpec.AZUL) + plugins.withType(JavaBasePlugin::class.java) { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(jdkVersion.removePrefix("1."))) + if(jvmDistribution != null) { + vendor.set(jvmDistribution) + } + } } - sourceSets.configureEach { - for(optIn in optIns) { - languageSettings.optIn(optIn) + if(isKmp) { + sourceSets.configureEach { + for(optIn in optIns) { + languageSettings.optIn(optIn.value) + } } } } @@ -67,7 +82,9 @@ public fun Project.configureKgp( tasks.withType(KotlinCompile::class.java).configureEach { kotlinOptions.allWarningsAsErrors = allWarningsAsErrors kotlinOptions.jvmTarget = jdkVersion - kotlinOptions.freeCompilerArgs = kotlinOptions.freeCompilerArgs + freeCompilerArgs + kotlinOptions.useK2 = useK2 + kotlinOptions.freeCompilerArgs += freeCompilerArgs.map { freeCompilerArg -> freeCompilerArg.value } + if(!isKmp) kotlinOptions.freeCompilerArgs += optIns.map { optIn -> "-opt-in=${optIn.value}" } } } } diff --git a/utils-plugin/build.gradle.kts b/utils-plugin/build.gradle.kts index d9075de..ff09d06 100644 --- a/utils-plugin/build.gradle.kts +++ b/utils-plugin/build.gradle.kts @@ -1,10 +1,16 @@ +import com.eygraber.gradle.kotlin.configureKgp + plugins { `kotlin-dsl` id("com.eygraber.detekt") - id("com.eygraber.kotlin") id("com.eygraber.publish") } +configureKgp( + jdkVersion = libs.versions.jdk, + optIns = arrayOf("kotlin.RequiresOptIn") +) + gradlePlugin { plugins { create("gradleUtils") { @@ -18,4 +24,13 @@ dependencies { api(project(":utils-base")) api(project(":utils-detekt")) api(project(":utils-kotlin")) + + implementation(libs.buildscript.android) + implementation(libs.buildscript.androidCacheFix) + implementation(libs.buildscript.compose.jetbrains) + implementation(libs.buildscript.detekt) + implementation(libs.buildscript.dokka) + implementation(libs.buildscript.foojay) + implementation(libs.buildscript.kotlin) + implementation(libs.buildscript.publish) } diff --git a/utils-plugin/src/main/kotlin/ProjectExtensions.kt b/utils-plugin/src/main/kotlin/ProjectExtensions.kt new file mode 100644 index 0000000..cadb0ae --- /dev/null +++ b/utils-plugin/src/main/kotlin/ProjectExtensions.kt @@ -0,0 +1,47 @@ +@file:Suppress("NOTHING_TO_INLINE") + +import com.android.build.api.variant.LibraryAndroidComponentsExtension +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.LibraryExtension +import com.vanniktech.maven.publish.MavenPublishBaseExtension +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.plugins.ExtensionAware +import org.gradle.api.publish.PublishingExtension +import org.gradle.kotlin.dsl.add +import org.jetbrains.compose.ComposeExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension + +internal inline fun Project.android(action: Action) { + action.execute(extensions.getByType(BaseExtension::class.java)) +} + +internal inline fun Project.androidLibraryComponents(action: Action) { + action.execute(extensions.getByType(LibraryAndroidComponentsExtension::class.java)) +} + +internal inline fun Project.androidLibrary(action: Action) { + action.execute(extensions.getByType(LibraryExtension::class.java)) +} + +internal val Project.compose: ComposeExtension + get() = + (this as ExtensionAware).extensions.getByName("compose") as ComposeExtension + +internal val Project.kotlin: KotlinProjectExtension + get() = + (this as ExtensionAware).extensions.getByName("kotlin") as KotlinProjectExtension + +internal inline fun Project.publishing(action: Action) { + action.execute(extensions.getByType(PublishingExtension::class.java)) +} + +@Suppress("UnstableApiUsage") +internal inline fun Project.mavenPublishing(action: Action) { + action.execute(extensions.getByType(MavenPublishBaseExtension::class.java)) +} + +internal fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? = + add("implementation", dependencyNotation) diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-android-library.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-android-library.gradle.kts new file mode 100644 index 0000000..b4f8b6a --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-android-library.gradle.kts @@ -0,0 +1,182 @@ +@file:Suppress("UnstableApiUsage") + +import com.eygraber.gradle.gradleUtilsDefaultsService +import com.eygraber.gradle.gradleUtilsExtension +import com.eygraber.gradle.kotlin.doOnFirstMatchingIncomingDependencyBeforeResolution +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("com.android.library") + id("org.gradle.android.cache-fix") +} + +val ext = gradleUtilsExtension +val androidDefaults = gradleUtilsDefaultsService.android +val kotlinDefaults = gradleUtilsDefaultsService.kotlin + +ext.android.compileSdk = androidDefaults.compileSdk +ext.android.targetSdk = androidDefaults.targetSdk +ext.android.minSdk = androidDefaults.minSdk +ext.android.publishEverything = androidDefaults.publishEverything +ext.android.coreLibraryDesugaringDependency = androidDefaults.coreLibraryDesugaringDependency +ext.android.flavors = androidDefaults.flavors +ext.android.optInsToDependencyPredicate = androidDefaults.optInsToDependencyPredicate +ext.kotlin.jdkVersion = kotlinDefaults.jdkVersion + +var isAndroidPublishingConfigured = false + +@Suppress("LabeledExpression") +ext.awaitKotlinConfigured { isKotlinUserConfigured -> + ext.awaitAndroidConfigured { isAndroidUserConfigured -> + val isSdkVersionsConfigured = compileSdk > 0 && minSdk > 0 + if(!isSdkVersionsConfigured && !isAndroidUserConfigured) return@awaitAndroidConfigured + + if(jdkVersion == null && !isKotlinUserConfigured) return@awaitAndroidConfigured + + check(compileSdk > 0) { + "android.compileSdk doesn't have a value set" + } + + check(minSdk > 0) { + "android.minSdk doesn't have a value set" + } + + val androidCompileSdk = compileSdk + val androidMinSdk = minSdk + + androidLibrary { + compileSdk = androidCompileSdk + + val kmpManifestFilePath = "src/androidMain/AndroidManifest.xml" + if(layout.projectDirectory.file(kmpManifestFilePath).asFile.exists()) { + sourceSets.named("main") { + manifest.srcFile(kmpManifestFilePath) + } + } + + val kmpResPath = "src/androidMain/res" + if(layout.projectDirectory.file(kmpResPath).asFile.exists()) { + sourceSets.named("main") { + res.srcDir(kmpResPath) + } + } + + val kmpResourcesPath = "src/commonMain/resources" + if(layout.projectDirectory.file(kmpResourcesPath).asFile.exists()) { + sourceSets.named("main") { + res.srcDir(kmpResourcesPath) + } + } + + defaultConfig { + consumerProguardFile(project.file("consumer-rules.pro")) + + minSdk = androidMinSdk + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + val jdkVersion = jdkVersion + if(jdkVersion != null) { + compileOptions { + if(coreLibraryDesugaringDependency != null) { + isCoreLibraryDesugaringEnabled = true + } + sourceCompatibility = JavaVersion.toVersion(jdkVersion) + targetCompatibility = JavaVersion.toVersion(jdkVersion) + } + } + + packagingOptions { + resources.pickFirsts += "META-INF/*" + } + + buildTypes { + named("release") { + isMinifyEnabled = false + } + named("debug") { + isMinifyEnabled = false + } + } + + for((dimension, flavorsToRegister) in flavors) { + if(dimension !in flavorDimensions) flavorDimensions += dimension + + for(flavor in flavorsToRegister) { + // register throws if the name is already registered + // and this block can be called multiple times + runCatching { + productFlavors.register(flavor.name) + } + } + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } + + if(publishEverything) { + // we can't configure this twice and awaitAndroidConfigured can be called twice (default and user config) + // since publishing doesn't depend on values from the extension we can guard against this without issue + if(!isAndroidPublishingConfigured) { + publishing { + multipleVariants { + allVariants() + withJavadocJar() + withSourcesJar() + } + } + isAndroidPublishingConfigured = true + } + } + + coreLibraryDesugaringDependency?.let { desugaringDependency -> + dependencies { + add("coreLibraryDesugaring", desugaringDependency) + } + } + } + + androidLibraryComponents { + val disabledFlavors = flavors.mapNotNull { (dimension, flavorsToRegister) -> + val disabledFlavors = flavorsToRegister.filterNot { it.enabled } + (dimension to disabledFlavors).takeIf { disabledFlavors.isNotEmpty() } + } + if(disabledFlavors.isNotEmpty()) { + val selector = selector().let { + var s = it + for((dimension, disabled) in disabledFlavors) { + for(disabledFlavor in disabled) { + s = s.withFlavor(dimension to disabledFlavor.name) + } + } + s + } + + beforeVariants(selector) { variant -> + variant.enable = false + } + } + + for((optIns, dependencyPredicate) in optInsToDependencyPredicate) { + onVariants { variant -> + doOnFirstMatchingIncomingDependencyBeforeResolution( + configurationName = "${variant.name}RuntimeClasspath", + dependencyPredicate = dependencyPredicate + ) { + tasks.withType(KotlinCompile::class.java).configureEach { + kotlinOptions { + for(optIn in optIns) { + freeCompilerArgs = freeCompilerArgs + "-opt-in=${optIn.value}" + } + } + } + } + } + } + } + } +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-compose-jetbrains.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-compose-jetbrains.gradle.kts new file mode 100644 index 0000000..6d2b4f9 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-compose-jetbrains.gradle.kts @@ -0,0 +1,74 @@ +import com.android.build.gradle.BasePlugin +import com.eygraber.gradle.gradleUtilsExtension +import org.jetbrains.compose.ComposeCompilerKotlinSupportPlugin +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType + +plugins { + id("org.jetbrains.compose") + id("com.eygraber.gradle-compose") +} + +gradleUtilsExtension.awaitComposeConfigured { + if(applyToAndroidAndJvmOnly) { + // only apply to android/jvm targets if we're in a multiplatform project + plugins.withId("org.jetbrains.kotlin.multiplatform") { + plugins.removeAll { + it is ComposeCompilerKotlinSupportPlugin + } + + class ComposeOnlyJvmPlugin : KotlinCompilerPluginSupportPlugin by ComposeCompilerKotlinSupportPlugin() { + override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = + when(kotlinCompilation.target.platformType) { + KotlinPlatformType.androidJvm, KotlinPlatformType.jvm -> true + else -> false + } + } + + apply() + } + } + + if(useAndroidComposeCompilerVersionForJetbrainsComposeCompilerVersion) { + androidComposeCompilerVersionOverride?.let { compilerVersion -> + compose.kotlinCompilerPlugin.set("androidx.compose.compiler:compiler:$compilerVersion") + } + } + + plugins.withType { + android { + dependencies { + // jetbrains compose plugin rewrites compose dependencies for android to point to androidx + // if we want to use the compose BOM we need to rewrite the rewritten dependencies to not include a version + if(androidComposeDependencyBomVersion != null) { + components { + all { + val isCompiler = id.group.endsWith("compiler") + val isCompose = id.group.startsWith("androidx.compose") + val isBom = id.name == "compose-bom" + + val override = isCompose && !isCompiler && !isBom + if(override) { + // copied from Jetbrains Compose RedirectAndroidVariants - https://shorturl.at/dioY9 + listOf( + "debugApiElements-published", + "debugRuntimeElements-published", + "releaseApiElements-published", + "releaseRuntimeElements-published" + ).forEach { variantNameToAlter -> + withVariant(variantNameToAlter) { + withDependencies { + removeAll { true } // remove androidx artifact with version + add("${id.group}:${id.name}") // add androidx artifact without version + } + } + } + } + } + } + } + } + } + } +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-compose-jetpack.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-compose-jetpack.gradle.kts new file mode 100644 index 0000000..1b681b2 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-compose-jetpack.gradle.kts @@ -0,0 +1,13 @@ +import org.gradle.kotlin.dsl.withType +import com.android.build.gradle.BasePlugin as AndroidBasePlugin + +plugins { + id("com.eygraber.gradle-compose") +} + +plugins.withType { + android { + @Suppress("UnstableApiUsage") + buildFeatures.compose = true + } +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-compose.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-compose.gradle.kts new file mode 100644 index 0000000..4bb159a --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-compose.gradle.kts @@ -0,0 +1,29 @@ +import com.eygraber.gradle.gradleUtilsDefaultsService +import com.eygraber.gradle.gradleUtilsExtension +import com.android.build.gradle.BasePlugin as AndroidBasePlugin + +val ext = gradleUtilsExtension +val composeDefaults = gradleUtilsDefaultsService.compose + +ext.compose.applyToAndroidAndJvmOnly = composeDefaults.applyToAndroidAndJvmOnly +ext.compose.androidComposeCompilerVersionOverride = composeDefaults.androidComposeCompilerVersionOverride +ext.compose.androidComposeDependencyBomVersion = composeDefaults.androidComposeDependencyBomVersion +ext.compose.useAndroidComposeCompilerVersionForJetbrainsComposeCompilerVersion = + composeDefaults.useAndroidComposeCompilerVersionForJetbrainsComposeCompilerVersion + +ext.awaitComposeConfigured { + plugins.withType { + android { + androidComposeCompilerVersionOverride?.let { versionOverride -> + @Suppress("UnstableApiUsage") + composeOptions.kotlinCompilerExtensionVersion = versionOverride + } + + dependencies { + androidComposeDependencyBomVersion?.let { bomVersion -> + implementation(platform("androidx.compose:compose-bom:$bomVersion")) + } + } + } + } +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-dependencies.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-dependencies.gradle.kts new file mode 100644 index 0000000..710687f --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-dependencies.gradle.kts @@ -0,0 +1,52 @@ +import com.eygraber.gradle.dependencies.ConventionDependencyHandler +import com.eygraber.gradle.dependencies.ResolutionVersionSelector +import com.eygraber.gradle.gradleUtilsDefaultsService +import com.eygraber.gradle.gradleUtilsExtension + +val ext = gradleUtilsExtension +val dependenciesDefaults = gradleUtilsDefaultsService.dependencies +ext.dependencies.resolutionVersionSelector = dependenciesDefaults.resolutionVersionSelector +ext.dependencies.projectDependencies = dependenciesDefaults.projectDependencies + +ext.awaitDependenciesConfigured { + // com.github.ben-manes.versions sets the version of dependencies + // to + in order to find the latest versions and we don't want to mess with that + if("dependencyUpdates" !in gradle.startParameter.taskNames) { + configurations.configureEach { + val configurationName = name + + resolutionStrategy { + eachDependency { + resolutionVersionSelector?.let { selector -> + requested.selector( + object : ResolutionVersionSelector { + override val configurationName = configurationName + override val useVersion = { version: Any -> + val versionError = { + error( + "Version must be either a String or a Provider (was ${version.javaClass.canonicalName})" + ) + } + when(version) { + is String -> useVersion(version) + is Provider<*> -> when(val versionString = version.get()) { + is String -> useVersion(versionString) + else -> versionError() + } + + else -> versionError() + } + } + } + ) + } + } + } + } + } + + dependencies { + val handler = ConventionDependencyHandler(this, project) + projectDependencies?.invoke(handler) + } +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-detekt.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-detekt.gradle.kts new file mode 100644 index 0000000..15ef450 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-detekt.gradle.kts @@ -0,0 +1,38 @@ +import com.eygraber.gradle.detekt.configureDetekt +import com.eygraber.gradle.detekt.registerCommonTestDetektTask +import com.eygraber.gradle.gradleUtilsDefaultsService +import com.eygraber.gradle.gradleUtilsExtension + +plugins { + id("io.gitlab.arturbosch.detekt") +} + +val ext = gradleUtilsExtension +val detektDefaults = gradleUtilsDefaultsService.detekt +val kotlinDefaults = gradleUtilsDefaultsService.kotlin + +ext.detekt.ignoredAndroidFlavors = detektDefaults.ignoredAndroidFlavors +ext.detekt.ignoredAndroidVariants = detektDefaults.ignoredAndroidVariants +ext.detekt.detektPluginDependencies = detektDefaults.detektPluginDependencies +ext.kotlin.jdkVersion = kotlinDefaults.jdkVersion + +@Suppress("LabeledExpression") +ext.awaitKotlinConfigured { isKotlinUserConfigured -> + ext.awaitDetektConfigured { + if(jdkVersion == null && !isKotlinUserConfigured) return@awaitDetektConfigured + + configureDetekt( + jdkVersion = requireNotNull(jdkVersion) { + "Please set jdkVersion in the gradleUtils kotlin extension" + } + ) + + dependencies { + for(dependency in detektPluginDependencies) { + add("detektPlugins", dependency) + } + } + + project.registerCommonTestDetektTask() + } +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-kotlin-library.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-kotlin-library.gradle.kts new file mode 100644 index 0000000..d2a217e --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-kotlin-library.gradle.kts @@ -0,0 +1,36 @@ +import com.eygraber.gradle.gradleUtilsDefaultsService +import com.eygraber.gradle.gradleUtilsExtension +import com.eygraber.gradle.kotlin.configureKgp + +val kotlinDefaults = gradleUtilsDefaultsService.kotlin + +with(gradleUtilsExtension) { + with(kotlin) { + jdkVersion = kotlinDefaults.jdkVersion + jvmDistribution = kotlinDefaults.jvmDistribution + allWarningsAsErrors = kotlinDefaults.allWarningsAsErrors + explicitApiMode = kotlinDefaults.explicitApiMode + configureJava = kotlinDefaults.configureJava + useK2 = kotlinDefaults.useK2 + freeCompilerArgs = kotlinDefaults.freeCompilerArgs + optIns = kotlinDefaults.optIns + } + + awaitKotlinConfigured { isUserConfigured -> + val jdkVersion = jdkVersion ?: if(isUserConfigured) null else "" + + if(jdkVersion != "") { + configureKgp( + jdkVersion = requireNotNull(jdkVersion) { + "Please set jdkVersion in the gradleUtils kotlin extension" + }, + jvmDistribution = jvmDistribution, + allWarningsAsErrors = allWarningsAsErrors, + explicitApiMode = explicitApiMode, + configureJavaCompatibility = configureJava, + freeCompilerArgs = freeCompilerArgs.toList(), + optIns = optIns.toTypedArray() + ) + } + } +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-kotlin-multiplatform.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-kotlin-multiplatform.gradle.kts new file mode 100644 index 0000000..32352ef --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-kotlin-multiplatform.gradle.kts @@ -0,0 +1,4 @@ +plugins { + kotlin("multiplatform") + id("com.eygraber.gradle-kotlin-library") +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-github.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-github.gradle.kts new file mode 100644 index 0000000..e515660 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-github.gradle.kts @@ -0,0 +1,38 @@ +import com.eygraber.gradle.gradleUtilsDefaultsService +import com.eygraber.gradle.gradleUtilsExtension +import com.eygraber.gradle.publishing.githubPackagesPublishing + +plugins { + id("org.jetbrains.dokka") + id("com.vanniktech.maven.publish") +} + +val gitHubDefaults = gradleUtilsDefaultsService.github + +@Suppress("LabeledExpression") +with(gradleUtilsExtension) { + with(github) { + owner = gitHubDefaults.owner + repoName = gitHubDefaults.repoName + } + + awaitGitHubConfigured { isUserConfigured -> + if((owner == null || repoName == null) && !isUserConfigured) return@awaitGitHubConfigured + + publishing { + repositories.githubPackagesPublishing( + owner = requireNotNull(owner) { + "Please set owner in the gradleUtils github extension" + }, + repo = requireNotNull(repoName) { + "Please set repoName in the gradleUtils github extension" + } + ) + } + + mavenPublishing { + @Suppress("UnstableApiUsage") + signAllPublications() + } + } +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-maven-central.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-maven-central.gradle.kts new file mode 100644 index 0000000..ba3a6af --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-maven-central.gradle.kts @@ -0,0 +1,12 @@ +import com.vanniktech.maven.publish.SonatypeHost + +plugins { + id("org.jetbrains.dokka") + id("com.vanniktech.maven.publish") +} + +@Suppress("UnstableApiUsage") +mavenPublishing { + publishToMavenCentral(SonatypeHost.S01, automaticRelease = true) + signAllPublications() +} diff --git a/utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-spm.gradle.kts b/utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-spm.gradle.kts new file mode 100644 index 0000000..a3e5d48 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com.eygraber.gradle-publish-spm.gradle.kts @@ -0,0 +1,65 @@ +import com.eygraber.gradle.gradleUtilsDefaultsService +import com.eygraber.gradle.gradleUtilsExtension +import com.eygraber.gradle.kotlin.kmp.spm.registerPublishSpmToMavenTasks +import com.eygraber.gradle.publishing.githubPackagesPublishing + +plugins { + `maven-publish` +} + +val gitHubDefaults = gradleUtilsDefaultsService.github +val spmDefaults = gradleUtilsDefaultsService.spm + +@Suppress("LabeledExpression") +with(gradleUtilsExtension) { + with(github) { + owner = gitHubDefaults.owner + repoName = gitHubDefaults.repoName + } + + with(spm) { + frameworkName = spmDefaults.frameworkName + version = spmDefaults.version + includeMacos = spmDefaults.includeMacos + } + + awaitGitHubConfigured { isGitHubUserConfigured -> + if((owner == null || repoName == null) && !isGitHubUserConfigured) return@awaitGitHubConfigured + + val owner = requireNotNull(owner) { + "Please set owner in the gradleUtils github extension" + } + + val repoName = requireNotNull(repoName) { + "Please set repoName in the gradleUtils github extension" + } + + if(owner.isNotBlank() && repoName.isNotBlank()) { + publishing { + repositories.githubPackagesPublishing( + owner = owner, + repo = repoName + ) + } + + awaitSpmConfigured { isSpmUserConfigured -> + if((frameworkName == null || version == null) && !isSpmUserConfigured) return@awaitSpmConfigured + + val frameworkName = requireNotNull(frameworkName) { + "Please set frameworkName in the gradleUtils spm extension" + } + + val version = requireNotNull(version) { + "Please set version in the gradleUtils spm extension" + } + + registerPublishSpmToMavenTasks( + frameworkName = frameworkName, + artifactVersion = version + ) { + !it.name.startsWith("macos") || includeMacos + } + } + } + } +} diff --git a/utils-plugin/src/main/kotlin/com/eygraber/gradle/GradleUtilsPlugin.kt b/utils-plugin/src/main/kotlin/com/eygraber/gradle/GradleUtilsPlugin.kt index 50dc25b..c552ce4 100644 --- a/utils-plugin/src/main/kotlin/com/eygraber/gradle/GradleUtilsPlugin.kt +++ b/utils-plugin/src/main/kotlin/com/eygraber/gradle/GradleUtilsPlugin.kt @@ -1,8 +1,112 @@ package com.eygraber.gradle +import com.eygraber.gradle.android.GradleUtilsAndroid +import com.eygraber.gradle.compose.GradleUtilsCompose +import com.eygraber.gradle.dependencies.GradleUtilsDependencies +import com.eygraber.gradle.detekt.GradleUtilsDetekt +import com.eygraber.gradle.github.GradleUtilsGitHub +import com.eygraber.gradle.kotlin.GradleUtilsKotlin +import com.eygraber.gradle.spm.GradleUtilsSpm import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters.None +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.jvm +import org.gradle.kotlin.dsl.registerIfAbsent +import org.gradle.toolchains.foojay.FoojayToolchainResolver +import org.gradle.toolchains.foojay.FoojayToolchainsPlugin -public abstract class GradleUtilsPlugin : Plugin { - override fun apply(target: Project) {} +internal abstract class GradleUtilsDefaults : BuildService { + val android = GradleUtilsAndroid() + val compose = GradleUtilsCompose() + val dependencies = GradleUtilsDependencies() + val detekt = GradleUtilsDetekt() + val github = GradleUtilsGitHub() + val kotlin = GradleUtilsKotlin() + val spm = GradleUtilsSpm() } + +abstract class GradleUtilsPlugin : Plugin { + override fun apply(target: Settings) { + target.gradle.rootProject { + with(extensions.create("gradleUtilsDefaults")) { + if(applyFoojayToolchainResolver) { + target.applyFoojayToolchainResolver() + } + + with(gradleUtilsDefaultsService) { + awaitAndroidConfigured { + android.compileSdk = compileSdk + android.targetSdk = targetSdk + android.minSdk = minSdk + android.publishEverything = publishEverything + android.coreLibraryDesugaringDependency = coreLibraryDesugaringDependency + android.flavors = flavors + android.optInsToDependencyPredicate = optInsToDependencyPredicate + } + + awaitComposeConfigured { + compose.applyToAndroidAndJvmOnly = applyToAndroidAndJvmOnly + compose.androidComposeCompilerVersionOverride = androidComposeCompilerVersionOverride + compose.androidComposeDependencyBomVersion = androidComposeDependencyBomVersion + compose.useAndroidComposeCompilerVersionForJetbrainsComposeCompilerVersion = + useAndroidComposeCompilerVersionForJetbrainsComposeCompilerVersion + } + + awaitDependenciesConfigured { + dependencies.resolutionVersionSelector = resolutionVersionSelector + dependencies.projectDependencies = projectDependencies + } + + awaitDetektConfigured { + detekt.ignoredAndroidFlavors = ignoredAndroidFlavors + detekt.ignoredAndroidVariants = ignoredAndroidVariants + detekt.detektPluginDependencies = detektPluginDependencies + } + + awaitGitHubConfigured { + github.owner = owner + github.repoName = repoName + } + + awaitKotlinConfigured { + kotlin.jdkVersion = jdkVersion + kotlin.jvmDistribution = jvmDistribution + kotlin.allWarningsAsErrors = allWarningsAsErrors + kotlin.explicitApiMode = explicitApiMode + kotlin.configureJava = configureJava + kotlin.useK2 = useK2 + kotlin.freeCompilerArgs = freeCompilerArgs + kotlin.optIns = optIns + } + + awaitSpmConfigured { + spm.frameworkName = frameworkName + spm.version = version + spm.includeMacos = includeMacos + } + } + } + } + } +} + +private fun Settings.applyFoojayToolchainResolver() { + plugins.apply(FoojayToolchainsPlugin::class.java) + + @Suppress("UnstableApiUsage") + toolchainManagement { + jvm { + javaRepositories { + repository("foojay") { + resolverClass.set(FoojayToolchainResolver::class.java) + } + } + } + } +} + +internal val Project.gradleUtilsDefaultsService + get() = gradle.sharedServices.registerIfAbsent("gradleUtilsDefaults", GradleUtilsDefaults::class) {}.get() diff --git a/utils-plugin/src/main/kotlin/com/eygraber/gradle/GradleUtilsPluginExtension.kt b/utils-plugin/src/main/kotlin/com/eygraber/gradle/GradleUtilsPluginExtension.kt new file mode 100644 index 0000000..19879b2 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com/eygraber/gradle/GradleUtilsPluginExtension.kt @@ -0,0 +1,208 @@ +package com.eygraber.gradle + +import com.eygraber.gradle.android.GradleUtilsAndroid +import com.eygraber.gradle.compose.GradleUtilsCompose +import com.eygraber.gradle.dependencies.GradleUtilsDependencies +import com.eygraber.gradle.detekt.GradleUtilsDetekt +import com.eygraber.gradle.github.GradleUtilsGitHub +import com.eygraber.gradle.kotlin.GradleUtilsKotlin +import com.eygraber.gradle.spm.GradleUtilsSpm +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.kotlin.dsl.create +import java.util.concurrent.CopyOnWriteArrayList + +internal interface GradleUtilsConfigurableListener { + fun GradleUtilsAndroid.onAndroidConfigured(isUserConfigured: Boolean) {} + fun GradleUtilsCompose.onComposeConfigured(isUserConfigured: Boolean) {} + fun GradleUtilsDependencies.onDependenciesConfigured(isUserConfigured: Boolean) {} + fun GradleUtilsDetekt.onDetektConfigured(isUserConfigured: Boolean) {} + fun GradleUtilsGitHub.onGitHubConfigured(isUserConfigured: Boolean) {} + fun GradleUtilsKotlin.onKotlinConfigured(isUserConfigured: Boolean) {} + fun GradleUtilsSpm.onSpmConfigured(isUserConfigured: Boolean) {} +} + +abstract class GradleUtilsPluginExtension { + private val configureListeners = CopyOnWriteArrayList() + + var applyFoojayToolchainResolver = true + + internal fun awaitAndroidConfigured( + configure: GradleUtilsAndroid.(isConfigured: Boolean) -> Unit + ) { + android.configure(isAndroidConfigured) + configureListeners += object : GradleUtilsConfigurableListener { + override fun GradleUtilsAndroid.onAndroidConfigured(isUserConfigured: Boolean) { + configure(isUserConfigured) + } + } + } + + internal fun awaitComposeConfigured( + configure: GradleUtilsCompose.(isConfigured: Boolean) -> Unit + ) { + compose.configure(isComposeConfigured) + configureListeners += object : GradleUtilsConfigurableListener { + override fun GradleUtilsCompose.onComposeConfigured(isUserConfigured: Boolean) { + configure(isUserConfigured) + } + } + } + + internal fun awaitDependenciesConfigured( + configure: GradleUtilsDependencies.(isConfigured: Boolean) -> Unit + ) { + dependencies.configure(isDependenciesConfigured) + configureListeners += object : GradleUtilsConfigurableListener { + override fun GradleUtilsDependencies.onDependenciesConfigured(isUserConfigured: Boolean) { + configure(isUserConfigured) + } + } + } + + internal fun awaitDetektConfigured( + configure: GradleUtilsDetekt.(isConfigured: Boolean) -> Unit + ) { + detekt.configure(isDetektConfigured) + configureListeners += object : GradleUtilsConfigurableListener { + override fun GradleUtilsDetekt.onDetektConfigured(isUserConfigured: Boolean) { + configure(isUserConfigured) + } + } + } + + internal fun awaitGitHubConfigured( + configure: GradleUtilsGitHub.(isConfigured: Boolean) -> Unit + ) { + github.configure(isGitHubConfigured) + configureListeners += object : GradleUtilsConfigurableListener { + override fun GradleUtilsGitHub.onGitHubConfigured(isUserConfigured: Boolean) { + configure(isUserConfigured) + } + } + } + + internal fun awaitKotlinConfigured( + configure: GradleUtilsKotlin.(isConfigured: Boolean) -> Unit + ) { + kotlin.configure(isKotlinConfigured) + configureListeners += object : GradleUtilsConfigurableListener { + override fun GradleUtilsKotlin.onKotlinConfigured(isUserConfigured: Boolean) { + configure(isUserConfigured) + } + } + } + + internal fun awaitSpmConfigured( + configure: GradleUtilsSpm.(isConfigured: Boolean) -> Unit + ) { + spm.configure(isSpmConfigured) + configureListeners += object : GradleUtilsConfigurableListener { + override fun GradleUtilsSpm.onSpmConfigured(isUserConfigured: Boolean) { + configure(isUserConfigured) + } + } + } + + private var isAndroidConfigured: Boolean = false + internal val android = GradleUtilsAndroid() + + fun android(action: Action) { + action.execute(android) + isAndroidConfigured = true + configureListeners.forEach { listener -> + with(listener) { + android.onAndroidConfigured(isAndroidConfigured) + } + } + } + + private var isComposeConfigured: Boolean = false + internal val compose = GradleUtilsCompose() + + fun compose(action: Action) { + action.execute(compose) + isComposeConfigured = true + configureListeners.forEach { listener -> + with(listener) { + compose.onComposeConfigured(isComposeConfigured) + } + } + } + + private var isDependenciesConfigured: Boolean = false + internal val dependencies = GradleUtilsDependencies() + + fun dependencies(action: Action) { + action.execute(dependencies) + isDependenciesConfigured = true + configureListeners.forEach { listener -> + with(listener) { + dependencies.onDependenciesConfigured(isDependenciesConfigured) + } + } + } + + private var isDetektConfigured: Boolean = false + internal val detekt = GradleUtilsDetekt() + + fun detekt(action: Action) { + action.execute(detekt) + isDetektConfigured = true + configureListeners.forEach { listener -> + with(listener) { + detekt.onDetektConfigured(isDetektConfigured) + } + } + } + + private var isGitHubConfigured: Boolean = false + internal val github = GradleUtilsGitHub() + + fun github(action: Action) { + action.execute(github) + isGitHubConfigured = true + configureListeners.forEach { listener -> + with(listener) { + github.onGitHubConfigured(isGitHubConfigured) + } + } + } + + private var wasKotlinUserConfigured: Boolean = false + private var isKotlinConfigured: Boolean = false + internal val kotlin = GradleUtilsKotlin() + get() { + wasKotlinUserConfigured = true + + return field + } + + fun kotlin(action: Action) { + action.execute(kotlin) + isKotlinConfigured = true + configureListeners.forEach { listener -> + with(listener) { + kotlin.onKotlinConfigured(isKotlinConfigured) + } + } + } + + private var isSpmConfigured: Boolean = false + internal val spm = GradleUtilsSpm() + + fun spm(action: Action) { + action.execute(spm) + isSpmConfigured = true + configureListeners.forEach { listener -> + with(listener) { + spm.onSpmConfigured(isSpmConfigured) + } + } + } +} + +internal val Project.gradleUtilsExtension: GradleUtilsPluginExtension + get() = + extensions.findByName("gradleUtils") as? GradleUtilsPluginExtension + ?: extensions.create("gradleUtils") diff --git a/utils-plugin/src/main/kotlin/com/eygraber/gradle/android/GradleUtilsAndroid.kt b/utils-plugin/src/main/kotlin/com/eygraber/gradle/android/GradleUtilsAndroid.kt new file mode 100644 index 0000000..81dcb58 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com/eygraber/gradle/android/GradleUtilsAndroid.kt @@ -0,0 +1,70 @@ +package com.eygraber.gradle.android + +import com.eygraber.gradle.kotlin.KotlinOptIn +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.provider.Provider + +data class ProductFlavor( + val name: String, + val enabled: Boolean = true +) + +class GradleUtilsAndroid { + var compileSdk: Int = 0 + var targetSdk: Int = 0 + var minSdk: Int = 0 + + var publishEverything: Boolean = true + + internal var coreLibraryDesugaringDependency: Any? = null + + internal var flavors: MutableList>> = mutableListOf() + + internal var optInsToDependencyPredicate: MutableList, Dependency.() -> Boolean>> = + mutableListOf() + + fun sdkVersions( + compileSdk: Provider, + minSdk: Provider + ) { + this.compileSdk = compileSdk.get().toInt() + this.minSdk = minSdk.get().toInt() + } + + fun sdkVersions( + compileSdk: Provider, + targetSdk: Provider, + minSdk: Provider + ) { + this.compileSdk = compileSdk.get().toInt() + this.targetSdk = targetSdk.get().toInt() + this.minSdk = minSdk.get().toInt() + } + + fun useCoreLibraryDesugaring( + dependency: Provider + ) { + coreLibraryDesugaringDependency = dependency + } + + fun useCoreLibraryDesugaring( + dependency: String + ) { + coreLibraryDesugaringDependency = dependency + } + + fun addProductFlavors( + dimension: String, + flavors: List + ) { + this.flavors += dimension to flavors + } + + fun addOptInsIfDependencyIsPresent( + optIns: List, + predicate: Dependency.() -> Boolean + ) { + optInsToDependencyPredicate += optIns to predicate + } +} diff --git a/utils-plugin/src/main/kotlin/com/eygraber/gradle/compose/GradleUtilsCompose.kt b/utils-plugin/src/main/kotlin/com/eygraber/gradle/compose/GradleUtilsCompose.kt new file mode 100644 index 0000000..0a67489 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com/eygraber/gradle/compose/GradleUtilsCompose.kt @@ -0,0 +1,26 @@ +package com.eygraber.gradle.compose + +import org.gradle.api.provider.Provider + +class GradleUtilsCompose { + var applyToAndroidAndJvmOnly: Boolean = true + var androidComposeCompilerVersionOverride: String? = null + var androidComposeDependencyBomVersion: String? = null + var useAndroidComposeCompilerVersionForJetbrainsComposeCompilerVersion: Boolean = false + + fun overrideAndroidComposeVersions( + compilerVersion: Provider? = null, + bomVersion: Provider? = null + ) { + compilerVersion?.let { androidComposeCompilerVersionOverride = it.get() } + bomVersion?.let { androidComposeDependencyBomVersion = it.get() } + } + + fun overrideAndroidComposeVersions( + compilerVersion: String? = null, + bomVersion: String? = null + ) { + compilerVersion?.let { androidComposeCompilerVersionOverride = it } + bomVersion?.let { androidComposeDependencyBomVersion = it } + } +} diff --git a/utils-plugin/src/main/kotlin/com/eygraber/gradle/dependencies/GradleUtilsDependencies.kt b/utils-plugin/src/main/kotlin/com/eygraber/gradle/dependencies/GradleUtilsDependencies.kt new file mode 100644 index 0000000..8c568bf --- /dev/null +++ b/utils-plugin/src/main/kotlin/com/eygraber/gradle/dependencies/GradleUtilsDependencies.kt @@ -0,0 +1,32 @@ +package com.eygraber.gradle.dependencies + +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ModuleVersionSelector +import org.gradle.api.artifacts.dsl.DependencyHandler + +interface ResolutionVersionSelector { + val configurationName: String + val useVersion: (Any) -> Unit +} + +class ConventionDependencyHandler( + dependencyHandler: DependencyHandler, + val project: Project +) : DependencyHandler by dependencyHandler { + fun api(dependencyNotation: Any): Dependency? = add("api", dependencyNotation) + fun compileOnly(dependencyNotation: Any): Dependency? = add("compileOnly", dependencyNotation) + fun implementation(dependencyNotation: Any): Dependency? = add("implementation", dependencyNotation) +} + +class GradleUtilsDependencies { + var resolutionVersionSelector: (ModuleVersionSelector.(ResolutionVersionSelector) -> Unit)? = null + + internal var projectDependencies: (ConventionDependencyHandler.() -> Unit)? = null + + fun projectDependencies( + dependencies: ConventionDependencyHandler.() -> Unit + ) { + projectDependencies = dependencies + } +} diff --git a/utils-plugin/src/main/kotlin/com/eygraber/gradle/detekt/GradleUtilsDetekt.kt b/utils-plugin/src/main/kotlin/com/eygraber/gradle/detekt/GradleUtilsDetekt.kt new file mode 100644 index 0000000..83a1f3c --- /dev/null +++ b/utils-plugin/src/main/kotlin/com/eygraber/gradle/detekt/GradleUtilsDetekt.kt @@ -0,0 +1,31 @@ +package com.eygraber.gradle.detekt + +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.provider.Provider + +class GradleUtilsDetekt { + internal var ignoredAndroidFlavors: MutableList = mutableListOf() + internal var ignoredAndroidVariants: MutableList = mutableListOf() + + internal var detektPluginDependencies: List = emptyList() + + fun ignoreAndroidFlavors(vararg flavors: String) { + ignoredAndroidFlavors += flavors + } + + fun ignoreAndroidVariants(vararg variants: String) { + ignoredAndroidVariants += variants + } + + fun plugins( + vararg dependencies: Provider + ) { + detektPluginDependencies = dependencies.toList() + } + + fun pluginsCoordinates( + vararg dependencies: String + ) { + detektPluginDependencies = dependencies.toList() + } +} diff --git a/utils-plugin/src/main/kotlin/com/eygraber/gradle/github/GradleUtilsGitHub.kt b/utils-plugin/src/main/kotlin/com/eygraber/gradle/github/GradleUtilsGitHub.kt new file mode 100644 index 0000000..3173573 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com/eygraber/gradle/github/GradleUtilsGitHub.kt @@ -0,0 +1,6 @@ +package com.eygraber.gradle.github + +class GradleUtilsGitHub { + var owner: String? = null + var repoName: String? = null +} diff --git a/utils-plugin/src/main/kotlin/com/eygraber/gradle/kotlin/GradleUtilsKotlin.kt b/utils-plugin/src/main/kotlin/com/eygraber/gradle/kotlin/GradleUtilsKotlin.kt new file mode 100644 index 0000000..cf0187c --- /dev/null +++ b/utils-plugin/src/main/kotlin/com/eygraber/gradle/kotlin/GradleUtilsKotlin.kt @@ -0,0 +1,15 @@ +package com.eygraber.gradle.kotlin + +import org.gradle.jvm.toolchain.JvmVendorSpec +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode + +class GradleUtilsKotlin { + var jdkVersion: String? = null + var jvmDistribution: JvmVendorSpec? = null + var allWarningsAsErrors: Boolean = true + var explicitApiMode: ExplicitApiMode = ExplicitApiMode.Disabled + var configureJava: Boolean = false + var useK2: Boolean = false + var freeCompilerArgs: Set = emptySet() + var optIns: Set = emptySet() +} diff --git a/utils-plugin/src/main/kotlin/com/eygraber/gradle/spm/GradleUtilsSpm.kt b/utils-plugin/src/main/kotlin/com/eygraber/gradle/spm/GradleUtilsSpm.kt new file mode 100644 index 0000000..b4bd3e6 --- /dev/null +++ b/utils-plugin/src/main/kotlin/com/eygraber/gradle/spm/GradleUtilsSpm.kt @@ -0,0 +1,7 @@ +package com.eygraber.gradle.spm + +class GradleUtilsSpm { + var frameworkName: String? = null + var version: String? = null + var includeMacos: Boolean = true +} diff --git a/utils-plugin/src/main/kotlin/kmp.kt b/utils-plugin/src/main/kotlin/kmp.kt new file mode 100644 index 0000000..32406c5 --- /dev/null +++ b/utils-plugin/src/main/kotlin/kmp.kt @@ -0,0 +1,105 @@ +import com.eygraber.gradle.detekt.registerDetektKmpIntermediateTask +import com.eygraber.gradle.detekt.registerSourceSetDetektTask +import com.eygraber.gradle.kotlin.kmp.createNestedSharedSourceSetForTargets +import com.eygraber.gradle.kotlin.kmp.createSharedSourceSet +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget + +fun KotlinMultiplatformExtension.kmpTargets( + project: Project, + android: Boolean = false, + jvm: Boolean = false, + ios: Boolean = false, + macos: Boolean = false, + js: Boolean = false, + isJsLeafModule: Boolean = false, + jsModuleName: String? = null, + requireAtLeastOneTarget: Boolean = true +) { + if(requireAtLeastOneTarget) { + check(android || jvm || ios || macos || js) { + "At least one of android, jvm, ios, macos, or js needs to be set to true" + } + } + + if(android) { + android { + publishAllLibraryVariants() + } + } + + if(ios || macos) { + createSharedSourceSet( + project = project, + name = "apple" + ) + project.registerSourceSetDetektTask("apple", "ios", "macos") + + if(ios) { + val targets = listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ) + + createNestedAppleSharedSourceSet( + project = project, + name = "ios", + targets = targets + ) + } + + if(macos) { + val targets = listOf( + macosX64(), + macosArm64() + ) + + createNestedAppleSharedSourceSet( + project = project, + name = "macos", + targets = targets + ) + } + } + + if(js) { + js(IR) { + if(jsModuleName != null) { + moduleName = jsModuleName + } + + browser { + if(isJsLeafModule) { + binaries.executable() + } + } + } + } + + if(jvm) { + jvm() + } +} + +private fun KotlinMultiplatformExtension.createNestedAppleSharedSourceSet( + project: Project, + name: String, + targets: List +) { + createNestedSharedSourceSetForTargets( + project = project, + name = name, + targets = targets, + parentSourceSetName = "apple" + ) { target -> + target.compilations.all { + kotlinOptions { + freeCompilerArgs = freeCompilerArgs + listOf("-linker-options", "-application_extension") + } + } + } + + project.registerDetektKmpIntermediateTask(name, targets) +} diff --git a/utils-plugin/src/main/kotlin/ksp.kt b/utils-plugin/src/main/kotlin/ksp.kt new file mode 100644 index 0000000..26dabc7 --- /dev/null +++ b/utils-plugin/src/main/kotlin/ksp.kt @@ -0,0 +1,76 @@ +import org.gradle.kotlin.dsl.dependencies +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension +import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi +import org.jetbrains.kotlin.gradle.kpm.external.project +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +interface KspDependencies { + fun ksp(dependencyNotation: Any) +} + +fun KotlinProjectExtension.configureKspSourceSets() { + check(this !is KotlinMultiplatformExtension) { + """ + |configureKspSourceSets isn't meant to be used with KMP. + |Please use one of the functions meant for use with KMP: + | KotlinTarget.kspDependencies + | KotlinMultiplatformExtension.kspDependenciesForAllTargets + | KotlinMultiplatformExtension.commonMainKspDependencies + """.trimMargin() + } + + sourceSets.configureEach { + kotlin.srcDir("build/generated/ksp/$name/kotlin") + kotlin.srcDir("build/generated/ksp/$name/java") + resources.srcDir("build/generated/ksp/$name/resources") + } +} + +fun KotlinTarget.kspDependencies(block: KspDependencies.() -> Unit) { + val configurationName = "ksp${targetName.capitalize()}" + project.dependencies { + object : KspDependencies { + override fun ksp(dependencyNotation: Any) { + add(configurationName, dependencyNotation) + } + }.block() + } + + compilations.configureEach { + kotlinSourceSets.forEach { sourceSet -> + sourceSet.kotlin.srcDir("build/generated/ksp/$targetName/${sourceSet.name}/kotlin") + sourceSet.resources.srcDir("build/generated/ksp/$targetName/${sourceSet.name}/resources") + } + } +} + +fun KotlinMultiplatformExtension.kspDependenciesForAllTargets(block: KspDependencies.() -> Unit) { + targets.configureEach { + if(targetName != "metadata") { + kspDependencies(block) + } + } +} + +@OptIn(ExternalVariantApi::class) +fun KotlinMultiplatformExtension.commonMainKspDependencies(block: KspDependencies.() -> Unit) { + project.dependencies { + object : KspDependencies { + override fun ksp(dependencyNotation: Any) { + add("kspCommonMainMetadata", dependencyNotation) + } + }.block() + } + + sourceSets.named("commonMain").configure { + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") + } + + project.tasks.withType(KotlinCompile::class.java).configureEach { + if(name != "kspCommonMainKotlinMetadata") { + dependsOn("kspCommonMainKotlinMetadata") + } + } +}