I am trying to create a custom gradle plugin to make use of the gradle conventions for a multi-module project.
I have the buildSrc module where in the settings.gradle file I apply the "de.fayard.refreshVersions" plugin for managing the dependencies versions, while in the build.gradle file I registered the custom plugins and using kotlin-dsl.
I firstly tried to move the logic to the build-logic module following the nowinandroid project, but, because I am using the refreshVersions plugin, this could not be possible as the plugin does not support it.
The following code shows the build.gradle file in which I registered my custom plugin.
plugins {
val kotlinVersion = "1.7.10"
`kotlin-dsl`
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
}
dependencies {
compileOnly(Android.tools.build.gradlePlugin)
compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:_")
implementation(KotlinX.serialization.json)
}
gradlePlugin {
plugins {
register("feature-plugin") {
id = "com.example.myapplication.feature-plugin"
implementationClass = "com.example.myapplication.featureplugin.FeaturePlugin"
}
register("hilt-plugin") {
id = "com.example.myapplication.hilt-plugin"
implementationClass = "com.example.myapplication.hiltplugin.HiltPlugin"
}
}
}
While the code below is the actual custom plugin in which I applied some plugins, dependencies and, using the LibraryExtension, I configured the app flavours for the modules and some gradle features.
package com.example.myapplication.featureplugin
import AndroidX
import Consts
import com.android.build.api.dsl.LibraryExtension
import com.example.myapplication.projectextensions.configureKotlinAndroid
import com.example.myapplication.projectextensions.configureFlavors
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.project
class FeaturePlugin : Plugin<Project> {
override fun apply(project: Project) {
with(project) {
pluginManager.apply {
apply("com.android.library")
apply("kotlin-android")
apply("kotlin-kapt")
// Custom plugin for Hilt
apply("com.example.myapplication.hilt-plugin")
}
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = Consts.AndroidTargetSdk
configureFlavors(this)
}
dependencies {
add("implementation", AndroidX.core.ktx)
add("implementation", AndroidX.lifecycle.viewModelCompose)
add("implementation", AndroidX.hilt.navigationCompose)
}
}
}
}
In my project modules, where these configurations are needed, I added the custom plugin's id registered, also I deleted all the dependencies, plugins and features that those modules have in common.
plugins {
id("com.example.myapplication.feature-plugin")
}
android {
namespace = "com.example.myapplication.feature.login"
}
dependencies {
// Navigation
implementation(AndroidX.navigation.compose)
// region Compose
implementation(platform(AndroidX.compose.bom))
implementation(AndroidX.compose.ui.text)
implementation(AndroidX.compose.material.icons.core)
implementation(AndroidX.compose.material.icons.extended)
// Accompanist
implementation(Google.accompanist.pager)
implementation(Google.accompanist.pager.indicators)
// endregion
}
When I build the project I get this error:
Unable to load class 'com.android.build.api.dsl.LibraryExtension'.
This is an unexpected error. Please file a bug containing the idea.log file.
It all works fine if I don't include the following LibraryExtension's block in my custom plugin.
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = Consts.AndroidTargetSdk
configureFlavors(this)
}
Any idea of how to fix this problem and getting the plugin to be installed in my app modules recognising the LibraryExtension?
To note:
kotlin version = 1.7.10
android plugin = 7.3.1
gradle wrapper = 7.4.2
I also link the project's github repository here.
check this answer for your question, i believe it is gonna solve your problem.
Related
Working on an Android project that has a lot of KMM modules, so I've tough I would extract a common gradle file and simply use it from the project specific gradle files.
My common gradle file is shared-library.gradle.kts
package commons
import dependencies.Dependencies
import dependencies.TestDependencies
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.apollographql.apollo3")
id("com.android.library")
}
version = "1.0"
kotlin {
android()
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
val commonMain by getting {
dependencies {
implementation(Dependencies.Koin.CORE)
implementation(Dependencies.Result.KMM)
implementation(Dependencies.Coroutines.CORE)
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(TestDependencies.KOIN)
}
}
val androidMain by getting
val androidTest by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
}
android {
compileSdk = BuildAndroidConfig.COMPILE_SDK_VERSION
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = BuildAndroidConfig.MIN_SDK_VERSION
targetSdk = BuildAndroidConfig.TARGET_SDK_VERSION
}
}
And then I can go use it like this from a build.gradle.kts
import dependencies.Dependencies
plugins {
id("commons.shared-library")
}
....
This all works great except the id("com.apollographql.apollo3") part, when added in the shared gradle file I get the following compilation error
org.gradle.internal.exceptions.LocationAwareException: Precompiled script plugin '/Users/calin/Playground/SharedAppSample/buildSrc/src/main/kotlin/commons/shared-library.gradle.kts' line: 1
Plugin [id: 'com.apollographql.apollo3'] was not found in any of the following sources:
- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (plugin dependency must include a version number for this source)
I see that the plugin is available as a gradle plugin https://plugins.gradle.org/search?term=com.apollographql.apollo3
And I have setting.gradle.kts configured like this
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
But for some reason the KMM Gradle file will ignore this configuration (maybe?)
The error says that it can't use the plugin repositories because no version is provided.
- Plugin Repositories (plugin dependency must include a version number for this source)
There are a few different ways of providing a version. Using buildSrc is my preferred way.
If shared-library.gradle.kts is a buildSrc convention plugin, then add a dependency using the plugins Maven coordinates (not the plugin ID!) in ./buildSrc/build.gradle.kts.
The Maven coordinates are available from the Gradle plugin portal
// ./buildSrc/build.gradle.kts
...
dependencies {
implementation("com.apollographql.apollo3:apollo-gradle-plugin:3.5.0")
}
Try adding the version after the apollo plugin id
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.apollographql.apollo3").version("3.6.2")
id("com.android.library")
}
Make sure the version matches the apollo-runtime version in dependencies, if you have it.
I have a kotlin multiplatofrm library that is included into an Android and iOS app.
In my android project include it as a composite build (MyLib). But Intellisense is not working at all for all code from in MyLib, though the whole thing compiles fine. I am using Android Studio. What could be wrong and how can I debug it?
rootProject.name='xxx'
includeBuild 'MyLib'
include ':common'
include ':app'
MyLib's build.gradle.kts looks as follows:
plugins {
kotlin("multiplatform") version "1.5.31"
kotlin("native.cocoapods") version "1.5.31"
}
repositories {
mavenCentral()
maven { setUrl("https://dl.bintray.com/kotlin/kotlinx.html/") }
}
group = "com.xxx.MyLib"
// CocoaPods requires the podspec to have a version.
version = "1.0"
kotlin {
ios()
jvm {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
testRuns["test"].executionTask.configure {
useJUnit()
}
}
cocoapods {
ios.deploymentTarget = "11.4"
frameworkName = "MyLib"
summary = "xxx"
homepage = "xxx"
podfile = project.file("../../iOS-App/Podfile")
}
sourceSets {
commonMain {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.5.31")
implementation("com.badoo.reaktive:reaktive:1.2.0")
implementation("com.badoo.reaktive:reaktive-annotations:1.2.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.3.1")
implementation("com.russhwolf:multiplatform-settings-no-arg:0.8.1")
implementation("net.swiftzer.semver:semver:1.1.1")
}
}
}
}
tasks.withType<GenerateModuleMetadata> {
enabled = true
}
I think this is likely related to https://youtrack.jetbrains.com/issue/KTIJ-18903
I had the same issue and it drove me crazy. How can one write code nowadays without Intellisense (answer: you can't).
I tried a ton of things (all the usual and unusual stuff you do when Android Studio / IntelliJ act up). Ultimately I upgraded to Kotlin 1.6.0-RC2 (from 1.5.31) -> https://github.com/JetBrains/kotlin/releases/tag/v1.6.0-RC2.
Part of that is upgrading the Kotlin Plugin:
Another part is the Kotlin Gradle Plugin dependency:
org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0-RC2
And last but not least I had to downgrade the corouting dependency (from 1.5.2):
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-RC
After that everything was back to normal.
Can I include local library module on the android sourceset in kotlin multiplatform?
If so, how do we do that?
I have tried adding
api(project(":local-library-one"))
api(project(":local-library-two"))
in android source-set of build.gradle.kts file.
It fails.
You have to make your "local-library" multiplatform too. It can be only targeted to android, so you don't need to modify anything but build.gradle file, something like this:
plugins {
kotlin("multiplatform")
id("com.android.library")
}
android {
// your setup
}
kotlin {
android()
sourceSets {
val androidMain by getting {
dependencies {
// your deps
}
}
}
}
I'm trying to develop a gradle plugin to use it for generating some objects and methods for our api using some scheme.
I have followed some tutorials but they all seem not to work, atleast for me.
Some of these tutorials were:
https://musings.animus.design/kotlin-poet-building-a-gradle-plugin/
https://medium.com/#magicbluepenguin/how-to-create-your-first-custom-gradle-plugin-efc1333d4419
I have not used the buildSrc module because I'm already using it for Kotlin DSL, so I decided to create a new module and create my plugin there.
My plugin's module build.gradle.kts looks like this:
plugins {
id("java-gradle-plugin")
id("kotlin")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath(config.ClassPaths.androidBuildTools)
classpath(config.ClassPaths.kotlinGradlePlugin)
}
}
repositories {
google()
jcenter()
}
dependencies {
implementation(config.ClassPaths.androidBuildTools)
implementation(config.ClassPaths.kotlinGradlePlugin)
}
gradlePlugin {
plugins {
create("Generator") {
id = "Generator"
implementationClass = "Generator"
}
}
}
My projects settings.gradle.kts looks like this:
include(":SampleProject", ":scheme-generator")
And in my application module's build.gradle.kts I'm applying this plugin like this:
apply(plugin = "Generator")
The build script stops here with an error: plugin 'Generator' not found
My Generator class looks like this:
class Generator : Plugin<Project> {
override fun apply(target: Project) {
target.android().variants().all { variant ->
// Make a task for each combination of build type and product flavor
val myTask = "myFirstTask${variant.name.capitalize()}"
// Register a simple task as a lambda. We can later move this to its own
// class to make our code cleaner and also add some niceties.
target.tasks.create(myTask){task ->
// Group all our plugin's tasks together
task.group = "MyPluginTasks"
task.doLast {
File("${target.projectDir.path}/myFirstGeneratedFile.txt").apply {
writeText("Hello Gradle!\nPrinted at: ${SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())}")
}
}
}
}
}
}
The android and variants methods are declared in a utils file, they look like this:
object GeneratorUtils {
fun Project.android(): BaseExtension {
val android = project.extensions.findByType(BaseExtension::class.java)
if (android != null) {
return android
} else {
throw GradleException("Project $name is not an Android project")
}
}
fun BaseExtension.variants(): DomainObjectSet<out BaseVariant> {
return when (this) {
is AppExtension -> {
applicationVariants
}
is LibraryExtension -> {
libraryVariants
}
else -> throw GradleException("Unsupported BaseExtension type!")
}
}
}
I have tried many things, but I seem not to get this right.
EDIT:
Using the buildSrc module for my plugin works totally fine, the plugin is applied and the gradle tasks are visible. However, buildSrc is reserved for other purposes, and we would like our plugin to be in a separate module, so we will be able to use it in other projects.
EDIT 13/04/2021
I have managed to see the tasks that are added by my plugin in my app tasks list by including this module as a composite build.
My settings.gradle now looks like this:
pluginManagement {
includeBuild("generator")
}
include(":SampleProject")
build.gradle of my plugin looks like this:
apply plugin: 'java-gradle-plugin' // Allows us to create and configure custom plugins
apply plugin: 'kotlin' //Needed as we'll write our plugin in Kotlin
buildscript {
ext {
kotlin_version = '1.4.31'
gradle_version = '4.1.2'
}
repositories {
google()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.android.tools.build:gradle:$gradle_version"
}
}
repositories {
google()
jcenter()
}
dependencies {
// Android gradle plugin will allow us to access Android specific features
}
gradlePlugin {
plugins {
create("Generator") {
id = "Generator"
implementationClass = "com.example.Generator"
}
}
}
And my generator class now looks like this:
class Generator : Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("generationTask") { task ->
task.apply {
group = "generation"
actions.add(Action {
print("Hello from generation task!")
})
}
}
}
}
I can see generationTask in my tasks list and I can execute it normally. It prints the text without any problems.
The problem now is to include com.android.tools.build:gradle:4.1.2 in my dependecies to use it to access build types and flavors and their paths to save my generated code there. When I add it to my dependencies block, gradle fails with this error: Could not find com.android.tools.build:gradle:4.1.2.
How can I solve this problem?
Gradle build goes through specific set of phases, and the Configuration phase comes before the Execution phase. So you cannot use a plugin, which is built in the same build process, because by the time gradle tries to use it on Configuration phase, the plugin has not been built yet.
buildSrc directory is a special one, it's built not as part of the same build process, but in a separate build, before the main build process starts. This feature is called included or composite build. buildSrc is just a pre-defined way to set up a composite build and you can define your own included builds. So to make your plugin visible to the main build, you need to put it into a separate build and include this build into a composite build as described in the doc above. Here is an article describing how to transform a plugin defined in buildSrc into a composite build.
I´m trying to migrate my android project to using gradle kotlin dsl, replacing all build.gradle files with build.gradle.kts files and using kotlin there. Already before, I used to have a kotlin file containing object elements with library and version constants (in buildSrc -> src -> main -> kotlin), like:
object Versions {
const val anyLibVersion = "1.0.0"
}
object Lib {
const val anyLib = "x:y:${Versions.anyLibVersion}"
}
In build.gradle files, I can access these constants without problems, but as soon as I switch them to build.gradle.kts, it cannot resolve them anymore. Any explaination for that?
What you'd typically have is following in buildSrc/build.gradle.kts
import org.gradle.kotlin.dsl.`kotlin-dsl`
repositories {
jcenter()
}
plugins {
`kotlin-dsl`
}
and then have your versions/dependencies in say buildSrc/src/main/java/Dependencies.kt
You can fix it by completely removing the android {} block, doing a Gradle sync, and then adding the code block back.
Does NOT treat the buildSrc directory as an module in settings.gradle.kts (.)
Precompiled script plugins.
Then for apply from in Kotlin DSL, you should use plugins {}
build.gradle.kts (X:buildSrc)
plugins {
`java-gradle-plugin`
`kotlin-dsl`
`kotlin-dsl-precompiled-script-plugins`
}
repositories {
mavenCentral()
}
Precondition
buildSrc/src/resources/META-INF/gradlePlugins/some-package-id.properties file:
implementation-class=gradlePlugins.android.AndroidLibraryPlugin
buildSrc/src/main/kotlin/gradlePlugins/android/AndroidLibraryPlugin.kt
Pay attention to the package of implementation-class will be "some-package-id"
package gradlePlugins.android
import org.gradle.api.Plugin
import org.gradle.api.Project
class AndroidLibraryPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.configurePlugins()
project.configureAndroid()
project.configureDependencies()
}
}
Consume the plugin:
Check that "some-package-id" will be the file name of some-package-id.properties
feature.gradle.kts
plugins {
id("some-package-id")
}
GL
Precompiled script plugins
Use buildSrc to abstract imperative logic
Kotlin Dsl Samples
Source
Commit