Supporting multiple gradle versions in single build.gradle - gradle

Due to build tools, my gradle file has to be compatible both with Android Gradle 2.1.3 and the latest Android Gradle version. As the latest Android Gradle Plugin has introduced the new implementation and api configuration, and is planning to remove the compile configuration, I am trying to figure out a way to write script that it should bebe able to support both versions.
The idea would be to use something like
def _api = api
and in the dependencies use _api instead of api.
Later we plan to add add some code like;
if (oldVersion)
_api = compile
I have tried this code as is, but it is invalid code.
Any ideas how it should be coded?
Thanks in advance

The notation:
dependencies {
api "org.example:example:1.0"
}
is really syntactic sugar for
dependencies {
add("api", "org.example:example:1.0")
}
so you could solve that problem by defining String variables, whose value depends on the Gradle version.

Related

Is it possible to use the Realm Kotlin Multiplatform SDK with only IntelliJ (not Android Studio)?

In my Kotlin Multiplatform app I use IntelliJ for the shared libraries rather than Android Studio. So far that approach has worked fine but when I looked at using the Realm Kotlin Multiplatform SDK - to my surprise - the documentation states that it requires Android Studio.
I thought I would give it a try anyway but I didn't have any success. Is this actually possible?
https://www.mongodb.com/docs/realm/sdk/kotlin/install/kotlin-multiplatform/
Initially I added
plugins {
id("io.realm.kotlin")
}
and
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt")
implementation("io.realm.kotlin:library-sync:1.0.0")
}
}
but not this since I don't have an Android Studio style project level build.gradle. Since I'm not using a mono repo I don't have two separate build.gradle files. The one I have is at the project level but has the contents of what the app level build.gradle would look like.
classpath("io.realm.kotlin:gradle-plugin:1.0.0") // did not add this
At this point the project would not complete a gradle sync.
I have a settings.gradle file with
if (requested.id.namespace == "com.android" || requested.id.name == "kotlin-android-extensions") {
useModule("com.android.tools.build:gradle:4.1.3")
}
so I tried
if (requested.id.namespace == "io.realm" || requested.id.name == "kotlin-android-extensions") {
useModule("io.realm.kotlin:library-sync:1.0.0")
}
thinking maybe that could substitute for not using classpath("io.realm.kotlin:gradle-plugin:1.0.0") anywhere but it didn't help.
I was able to get the Gradle sync to complete successfully
by using id("io.realm.kotlin") version "1.0.0" in the plugin block instead of just id("io.realm.kotlin") as specified in the documentation.
However it won't compile when I try to publish to mavel local
I get this sort of error. I say this sort of error because if I run the publish to maven local task again the target will be different; it cycles through them each time for some reason. So I don't literally get the same error each time I run the task.
Could not determine the dependencies of task ':compileKotlinIosArm64'.
> Could not resolve all task dependencies for configuration ':iosArm64CompileKlibraries'.
> Could not resolve org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt.
Required by:
project :
project : > io.realm.kotlin:library-sync:1.0.0 > io.realm.kotlin:library-sync-iosarm64:1.0.0
project : > io.realm.kotlin:library-sync:1.0.0 > io.realm.kotlin:library-sync-iosarm64:1.0.0 > io.realm.kotlin:library-base:1.0.0 > io.realm.kotlin:library-base-iosarm64:1.0.0
...
I tried adding the coroutines dependency to each target specifically but that didn't help either.
Also, if I comment out the realm plugin and dependency but just leave the coroutines dependency there it publishes to maven local without any issues.
I asked on the Kotlin slack group in the realm channel and it was suggested that it "should" work so wondering if anyone else knows the trick or if you know if it's NOT possible.
Thanks.
Edit
I created a test library and was able to get it to compile. I was also able to reproduce the issue by adding ktor libraries which seem to conflict with implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt"). I still wasn't able to find a solution though.
It says to use strictly here but not sure how to make ktor use that version https://kotlinlang.org/docs/multiplatform-mobile-concurrency-and-coroutines.html#multithreaded-coroutines.
Also, even without ktor it still doesn't compile if I add iosSimulatorArm64 as a target. In that case I get this error:
Could not determine the dependencies of task ':compileKotlinIosSimulatorArm64'.
> Could not resolve all task dependencies for configuration ':iosSimulatorArm64CompileKlibraries'.
> Could not resolve io.realm.kotlin:library-sync:1.0.1.
Edit 2
I thought what the heck I'll try to create a multiplatform library with Android Studio (chipmonk) just to see if that works. Nope. Even worse. Without doing anything except create the project from the wizard I get these sorts of errors all over the place in the build files with no clear indication of what's causing it:
Cannot access 'java.lang.Comparable' which is a supertype of 'org.gradle.kotlin.dsl.KotlinBuildScript'. Check your module classpath for missing or conflicting dependencies
Cannot access 'java.lang.Object' which is a supertype of 'org.gradle.kotlin.dsl.KotlinBuildScript'. Check your module classpath for missing or conflicting dependencies
I think I'm gonna have to either find a different way to store and sync local data or put KMP on hold for now until things are a bit more stable and just do full native and write all the code multiple times. I seem to be pretty much out of options for solving this.

Is there a way to define property to be used in both settings.gradle.kts and projects/subprojects build.gradle.kts with gradle 6.0?

We have multi-module android app with build logic written in gradle kotlin dsl. We use buildSrc to extract common logic like dependencies versions. We have something like:
buildSrc/src/main/kotlin/Dependencies.kt:
object Versions {
const val fooVersion = "1.2.3"
const val barVersion = "4.5.6"
}
object Libraries {
val foo = "com.example.foo:foo:$fooVersion"
val bar = "com.example.bar:bar:$barVersion"
}
object Modules {
const val app = ":app"
const val base = ":base"
const val baz = ":baz"
}
Then we can use these in modules' dependencies block to avoid hardcoded/duplicated values:
app/build.gradle.kts:
dependencies {
implementation(Libs.foo)
implementation(Libs.bar)
implementation(project(Modules.base))
implementation(project(Modules.baz))
}
And we also use it in settings.gradle.kts:
settings.gradle.kts:
include(
Modules.app,
Modules.base,
Modules.baz
)
This works ok with gradle 5.6. When I upgrade to 6.0, I get Unresolved reference: Modules in settings.gradle.kts file. I found it mentioned in migration guide:
Previously, the buildSrc project was built before applying the project’s settings script and its classes were visible within the script. Now, buildSrc is built after the settings script and its classes are not visible to it. The buildSrc classes remain visible to project build scripts and script plugins.
Custom logic can be used from a settings script by declaring external dependencies.
So I know what broke the build and I can fix the build by using hardcoded values in settings.gradle.kts:
include(
":app",
":base",
":baz"
)
Is it possible to avoid this duplication with gradle 6.0?
Please make sure you read the updates down below.
Original answer
See ticket #11090 "Definitions from buildSrc/ not found in settings.gradle.kts using gradle 6.0-rc-1". As you already noticed this changed recently:
This has changed in 6.0, and was deprecated in 5.6. Please see: https://docs.gradle.org/current/userguide/upgrading_version_5.html#buildsrc_usage_in_gradle_settings
-- https://github.com/gradle/gradle/issues/11090#issuecomment-544473179
One of the maintainers describes the reasons behind the decision:
Unfortunately, there are pros and cons to both arrangements (settings-then-buildSrc and buildSrc-then-settings), and we opted for the former after considering.
(...)
The pros that compelled us to make the change:
Settings plugins can influence buildSrc and main build (i.e. apply a build plugin to both)
Build cache configuration is applied to buildSrc
buildSrc behaves more like a regular included build
-- https://github.com/gradle/gradle/issues/11090#issuecomment-545697268
And finally some bad news:
We won't be changing the behaviour back to the pre Gradle 6 arrangement. Please let us know if you would like more detail on how to use one of the alternative mechanisms for using complex logic in a settings script.
-- https://github.com/gradle/gradle/issues/11090#issuecomment-545697268
Workarounds
In the aforementioned post the author proposes some workarounds:
The con of this is exactly what you have hit. It's now less convenient to use complex logic in your settings script. Now, you have to either:
Inline the logic into the settings file
Move the logic to a shared script that can be used where it needs to
Move the logic to a pre-built binary that you load in the settings file (i.e. a settings plugin)
-- https://github.com/gradle/gradle/issues/11090#issuecomment-545697268
#1 is pretty straightforward, but I can only assume what #2 and #3 mean. I come from the Groovy world and only recently started making friends with Kotlin DSL. Having said that let's give it a try.
In #3 the author might be talking about developing an external plugin and applying it in both scripts. I'm not really sure if this is something that would make sense to implement (it gives you strong typing though).
"#2 Move the logic to a shared script that can be used where it needs to"
I think it's about having a common script plugin and including it in both settings.gradle and build.gradle files. The plugin would put the static information in the ExtraPropertiesExtension of the ExtensionAware in scope (Settings in case of a settings.gradle script plugin and Project in case of build.gradle). This is described in this answer to "Include scripts with Gradle Kotlin DSL":
How can I put all common constants (such as dependency versions) to the separate file to include them just by using something like springBootVersion or Constants.springBootVersion with compile-time checks?
There is no good way to do it currently. You can use extra properties, but it won't guarantee compile time checks. Something like that:
// $rootDir/dependencies.gradle.kts
// this will try to take configuration from existing ones
val compile by configurations
val api by configurations
dependencies {
compile("commons-io:commons-io:1.2.3")
api("some.dep")
}
// This will put your version into extra extension
extra["springBootVersion"] = "1.2.3"
And you can use it like this:
// $rootDir/build.gradle.kts
subprojects {
apply {
plugin<JavaLibraryPlugin>()
from("$rootDir/dependencies.gradle.kts")
}
And in your module:
// $rootDir/module/build.gradle.kts
// This will take existing dependency from extra
val springBootVersion: String by extra
dependencies {
compile("org.spring:boot:$springBootVersion")
}
-- Include scripts with Gradle Kotlin DSL
UPDATE 1
The issue has become popular and gained attraction from Gradle maintainers:
Since there are so many comments still on this issue, let me clarify a few things about where we are and where we are actively moving to right now.
For the general topic "I want buildSrc to be done before anything else" there will be a solution soon with included builds and setting plugins. Most likely in the next release. Then you will be able to include a build, using a new DSL method, that is available earlier. This build can then contain a settings plugin which you can apply in settings. This makes the Jar containing the plugin available on the settings classpath. (Although ideally you would define a plugin extension and not use classes/static methods directly.)
settings.gradle.kts
pluginManagement {
includeBuildEarly("my-build-logic") //<- WIP - new API under development
}
plugins {
apply("my.settings.plugin) // this is defined in the "my-build-logic" build
}
For the topic of sharing dependencies and versions, we are also working on general improvements that might make some "custom solutions" unnecessary in the future.
Generally, you should try to avoid using buildscript {} or using resolutionStrategy {} in settings. Instead you can define plugins in included builds. You can then manage all dependencies (also to plugins) in the build files of these builds.
If you want to share constants between all these builds, you can have one build only for these constants that you include in all others. Like the libraries build in this example..
See also the sample about sharing convention plugins with build logic build.
Hope these are some helpful pointers.
-- https://github.com/gradle/gradle/issues/11090#issuecomment-734795353
UPDATE 2
Nowadays (Gradle 7+) you could use Version Catalogs to share dependency versions across buildSrc and the regular build. Please refer to the official documentation and this answer.
AFAIK - no, and that is what aforementioned migration guide says: settings.gradle is the first to be evaluated, thus, at that stage, objects defined in buildSrc don't even exist yet.
I can image some hacky workaround - just for the science, but in real project it would smell so bad that, IMO, not worth it.
P. S.: I guess you could reverse it and try to create instance of some enumeration by going through submodules in buildSrc code. But again, ways to achieve this might be quite exotic and should only be used for the sole purpose of proving this can work )

Should I shadow Kotlin when writing a Gradle Plugin

I'm writing a plugin to extract some boilerplate from a selection of existing Gradle build scripts. The existing build scripts are primarily written in Groovy and compiling Java.
To build my plugin I'm using the Gradle Kotlin DSL and figured I'd take the opportunity to write the plugin in Kotlin too. This all works but now my plugin has a huge dependency on Kotlin - and the Gradle docs specifically recommend minimizing external libraries.
Java and Groovy plugins avoid this because Java & Groovy are a shared dependency with Gradle, but Kotlin isn't a shared pre-requirement and so we then have to be concerned about potentially conflicting Kotlin versions needed by different plugins.
I figure I should move forward with one of the following approaches but am not clear which:
Just list Kotlin's stdlib as a standard dependency and trust Gradle to sort things out.
This works for one plugin, but should I expect problems when another plugin is also being used but depending on a different Kotlin?
Build some sort of uber shadowJar shadowing Kotlin libraries for my plugin
Implying that every plugin I write like this will be 10s of MB bigger than necessary.
Give up on Kotlin based plugins and rewrite in Java/Groovy
Would be a shame to give up on the new goodness but might be better to avoid the above sins.
Recommendations welcome!
Since your plugin is replacing boilerplate and is presumably not destined for public release, would it make sense to write it as a script plugin in the Gradle Kotlin DSL? That way a new enough Gradle should be able to understand it natively.
Raised this in Gradle Community Slack and was recommended to use Gradle's kotlin-dsl plugin to automatically configure dependencies on gradleApi() and embeddedKotlin() versions, and therefore whatever Kotlin version is bundled with Gradle's Kotlin DSL support.
I was concerned that this might introduce a dependency on the calling script using Kotlin DSL, but I've tested with a Groovy script and have been able to use my plugin. I assume that it does still depend on a version of Gradle with Kotlin DSL support though - i.e. 4.0+.

Kotlin dependency not behaving like Kotlin

I have a Kotlin dependency that I wrote and am now trying to use it in a separate project. When I add it to the other project variables declared like val hits = 0 can not be referenced by using the variable hits but by using getHits() which would make sense if I was using java in the second project but I am not. In other Kotlin projects I have worked on in the past this was not an issue and the only thing that I can think of that is different in the build is that I have set transitive = false on the gradle dependency because that was causing another issue. Anyway I am confused on what this would be caused by and appreciate any help, thanks!
In the Intellij decompiler is was considering a class that I know was written in Kotlin a java class so I tried to change some things. The problem was one of two things:
I was using the maven plugin for gradle.
I had both a compileKotlin and a compileJava when I was only using Kotlin.
I belive that the problem was caused by the former because I have used both compileKotlin and compileJava fine together before but I had never used the maven plugin but I added it because something else was complaining that it needed it.

Kotlin Modules with Gradle Multi-Project

Kotlin got rid of package level visibility for module level visibility. I think that was a great idea, except it's difficult to get multiple Kotlin modules working in a Gradle multi-project setup.
I'm using Gradle Wrapper 3.1, with Android Studio 2.2 and Kotlin 1.0.4
Here is my structure:
Project
----myapp (android application)
----lib (android library)
----commons (android library)
// myapp/build.gradle
dependencies {
compile project(':lib')
}
// lib/build.gradle
dependencies {
compile project(':commons')
}
When I try to sync or build I get the following error:
Error:A problem occurred configuring project ':lib'.
Cannot change dependencies of configuration ':lib:default' after it has been included in dependency resolution.
The only thing that has worked is to make sure that the module names have a lexical order that matches the order they need to be built in, which is ridiculous, and not a proper solution.
I know I can publish lib and commons as artifacts, but I'd really prefer to not do that.
Anyone have any suggestions?

Resources