Kotlin Modules with Gradle Multi-Project - gradle

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?

Related

Defining common dependencies and plugin versions for different projects using Gradle Kotlin DSL

I have a collection of related Gradle projects, not all of them in the same repository (hence not sharing the same gradle.properties or settings.gradle.kts files).
My objective is having my main build scripts (build.gradle.kts files) completely clean from dependency version numbers and at the same time avoiding to specify the same version number in more than once settings or properties file.
I am looking for the best practices to accomplish this, taking into consideration that these version numbers can appear both in Gradle dependencies or plugin declarations and often need to be kept in sync between them.
As a use case, lets configure projects that require spring boot.
In this case, I can use a Gradle platform project to define common dependency versions. As an example, the build.gradle.ktsof my platform project looks a bit like this:
plugins {
`java-platform`
}
javaPlatform {
allowDependencies()
}
dependencies {
api(platform("org.springframework.boot:spring-boot-dependencies:2.3.0.RELEASE"))
constraints {
api("io.mockk:mockk:1.10.0")
api("org.junit.jupiter:junit-jupiter:5.6.2")
api("io.kotest:kotest-runner-junit5-jvm:4.0.6")
...
}
}
So I am conveniently specifying all the spring boot dependencies + my own dependencies in one single platform project.
But other projects which use spring boot dependencies also require the org.springframework.boot plugin, where its version should better be the same than the spring boot dependency required in the platform project.
As said at the beginning, those projects are located in other repositories, hence they do not share the same configuration files as the platform project.
In those projects I am adding in their settings.gradle.kts file the following block:
pluginManagement {
plugins {
id("org.springframework.boot").version("2.3.0.RELEASE")
...
}
}
It works, since after importing the platform project I can keep my main build file clean from version numbers, as intended (neither the spring boot dependencies nor the spring boot plugin itself will require anymore to specify their versions).
However, this just does not feel good. Now I have the same version of spring boot (2.3.0.RELEASE) hard-coded in both my platform project and in the settings file of the project using the Spring boot plugin. Now I have the problem of keeping both numbers, located in distinct projects, synchronized.
Which is the "correct" Gradle way to solve this? Is there a way to make a Gradle platform project to also specify plugin version numbers in a similar way preferred dependency versions can be declared there?

How to add project dependencies to specific platform targets created by the `kotlin-multiplatform` plugin?

Previously, building multiplatform projects using Gradle relied on separate Gradle plugins per targeted platform: kotlin-platform-common, kotlin-platform-js, and kotlin-platform-jvm.
Starting from Kotlin 1.3, this is now unified using one kotlin-multiplatform plugin. The current documentation specifies how to set up multiplatform projects using this approach.
However, in my project I have multiple multiplatform projects with some dependencies between them. With the old multiplatform configuration, each different platform is a separate module and adding dependencies between projects required adding project references in each platform-specific module to corresponding modules in the other project: e.g., project(':some-library:some-library-js') to add a dependency to the JS module from within another JS module.
While migrating to the new multiplatform configuration, I now need to add a dependency from a project configured using the old configuration method to a project configured using the new kotlin-multiplatform plugin.
Given that platform-specific modules are now specified and managed by the new plugin, how do I go about this?
I'm not certain whether the following approach is the recommended method, or I overlooked any potential issues, but my projects compile and all tests pass.
In Gradle, you can add dependencies to specific project configurations:
dependencies {
implementation project(path: ':some-library', configuration: 'configName')
}
By trial and error I figured out that specifying the following configuration dependencies per platform module type works:
common: configuration: 'archives'. Without this, Gradle configuration fails as code dependencies are not found.
Without the following, compilation fails:
jvm: configuration: 'jvmDefault'
js: configuration: 'jsDefault'
For example, for the JS module as specified in the question:
dependencies {
implementation project(path: ':some-library', configuration: 'jsDefault')
}
Basically, you don't need to specify the platform in project-to-project dependencies involving Kotlin Muiltiplatform projects. The Kotlin plugin sets up dependency resolution in a way that Gradle will choose the appropriate target's artifacts automatically.
So, for example, in a Kotlin/JVM single-target project, you can just use a project("...") dependency on a Multiplatform project:
dependencies {
implementation(project(":multiplatform-library")
}
If :multiplatform-library has a JVM target, this dependency will get resolved to the JVM target's artifact. Otherwise, you will encounter a dependency resolution failure with candidate configurartions listed.
This is described in the Kotlin reference, Building Multiplatform Projects with Gradle – Adding Dependencies, but is applicable to single-platform projects, too:
<...> a project('...') dependency on another multiplatform project is resolved to an appropriate target automatically. It is enough to specify a single project('...') dependency in a source set's dependencies, and the compilations that include the source set will receive a corresponding platform-specific artifact of that project, given that it has a compatible target.
If this does not happen for you, please post the specific dependency resolution failure log, or file an issue at https://kotl.in/issue.

What is the difference between an app dependency and a module dependency/plugin?

When using some 3rd party libraries, I add a dependency to my module's build.gradle file.
compile 'com.android.support:appcompat-v7:24.1.1'
Or I add a plugin
apply plugin: 'com.neenbedankt.android-apt'
Some other times, the library requires adding a dependency to my app's build.gradle file.
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
What is the difference between these dependencies and plugins?
Why can't they all be set in a single build.gradle file?
All suggestions are appreciated, I'm having trouble searching for info on this
Three things. Gradle plugin, module dependency, a build dependency which is placed on the classpath of the build tool.
A plugin is how Gradle knows what tasks to use. There are many plugins. For more info, see Gradle - Plugin Documentation
A dependency is a library that is compiled with your code. The following line makes your module depend on the Android AppCompat V7 library. For the most part, you search Maven or Jcenter for these.
compile 'com.android.support:appcompat-v7:24.1.1'
The classpath setting is needed for Gradle, not your app. For example, this allows this includes the Gradle Build Tools for Android into the classpath, and allows Gradle to build apps.
classpath 'com.android.tools.build:gradle:2.1.2'
Why can't they all be in one build.gradle file?
They probably can be. It is simply more modular to not.
I got this answer from a colleague, and this helped me understand. "A gradle plugin is like the tools you use to build the app. The dependencies are the libraries included in the app. A gradle plugin is usually the tasks - like ktlint, etc."
I didn't understand this myself so here is what i found. My answer is based on gradle build tool.
Plugins:
Add additional tasks, repositories, new DSL elements, configuration for classpaths/build/run or dependency management for subsequent development. Plugins are developed for a larger scope of development like java, kotlin or spring-boot.
Dependencies:
modules/libraries for tasks like http, serialization or database are dependencies stored remotely at repositories or locally that are needed at runTime, test or build are resolved by gradle in a configured fashion.
Sources:
Spring boot gradle plugin: https://github.com/spring-projects/spring-boot/tree/master/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin
Gradle documentation on plugins/dependencies: https://docs.gradle.org/current/userguide/plugins.html
https://docs.gradle.org/current/userguide/core_dependency_management.html
Remote repositories:
https://mvnrepository.com/
In simple words:
Plugins are used to add some additonal features to the software/tools(like Gradle). Gradle will use the added plugins at the time of building the App.
Dependecies are used to add some addtional code to your source code, so a dependency will make some extra code (like Classes in Java) in the form of library available for your source code.

How deal with separate dependencies in libgdx?

I wonder about dependencies in libgdx. Each sub project (Android, iOS etc) can have their own dependencies as I understand it (they have their own build.gradle file).
I thought that I would only import a dependency to for example the core-project and then all the other sub projects would use that dependency also. Is that not the case? Or do I always need to specify a dependency many times for each sub project, even though I imported it already in the core project?
In libgdx, every projects depends on the core project, so if the core project depends on a library the subproject will depend on it as well.
You can easily manage dependencies in gradle with Maven like this:
dependencies {
compile "group.name:artifactId:versionX"
}
Filled in for libgdx this would make:
dependencies {
compile "com.badlogicgames.gdx:gdx:1.4.1"
}
To add more dependencies add another compile "" on a new line.

How do I prevent Gradle from exporting a dependency?

I have two Android apps that share a subproject. The build.gradle in the subproject contains a dependency like this:
dependencies {
compile "my.library.dependency:${version}"
}
What I'd like is to use this dependency at compile time, but not runtime. This is because the two apps need to link against slightly different versions of the dependency (same API).
The Gradle docs describe dependency configurations like this:
compile The dependencies required to compile the production source of
the project.
runtime The dependencies required by the production classes at
runtime. By default, also includes the compile time dependencies.
If runtime also includes compile dependencies, does this mean the library is exported from the subproject to the parent projects (and included in my apk)? If so, how do I prevent this? I assume it's possible because it says "by default".
Thanks in advance...
It's not clear if your subproject is using the android-library plugin, but if it is, v0.8 has added a provided scope -- see http://tools.android.com/recent/androidstudio043released.
This should work:
dependencies {
provided "my.library.dependency:${version}"
}
If you upgrade to 0.8 of the plugin, you'll need to run Gradle 1.10 (update gradle-wrapper.properties if you're using the wrapper), and if you're using Android Studio, upgrade to 0.4.3.

Resources