Gradle share properties in multi-project - gradle

I'm quite new to Gradle, trying to make multi-project. In root project declares all common libs (also their versions as properties) and apply plugins.
For example, root and child common projects.
In root's settings.gradle type:
rootProject.name = 'root'
include 'common'
In root's build.gradle type:
buildscript {
ext.kotlin_version = '1.3.11'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
subprojects {
buildscript {
repositories {
mavenCentral()
}
}
apply plugin: 'kotlin'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
}
And now I'd like to use another library only in specific child project. Do this in common's build.gradle:
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
}
}
It's work fine when running gradle commands from root's folder, but failed with message Could not get unknown property 'kotlin_version' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler when running from common's folder.
What I'm doing wrong? Or is there any way around? And what are best practices for sharing libs and properties in multi-project?
For me, it looks like common know nothing about it's "parent" project, all relations defined in root's settings.

The reason why Gradle cannot resolve the property is because the project in folder common is named commons. This is caused by a spelling mistake in common's settings.gradle. This is fortunately easy to fix (common/settings.gradle) :
rootProject.name = 'common'
Alternatively, just delete the common/settings.gradle, it's fully optional in this case.
Consider reading the official Gradle documentation for authoring multi-project builds and the guide create multi-project builds for more information and best practices around multi-project builds.

Related

Configuring Repositories for All Projects in Gradle

I am trying to configure repositories for all subprojects.
I have the main build.gradle:
buildscript {
repositories {
mavenLocal()
mavenCentral()
google()
jcenter()
...
}
dependencies {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
plugins {
id 'base'
}
allprojects {
apply plugin: 'base'
repositories {
mavenLocal()
mavenCentral()
google()
jcentre()
...
}
wrapper{
gradleVersion = '6.5.1'
distributionType = Wrapper.DistributionType.ALL
}
dependencies {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
In the subprojects build.gradle I just have:
...
dependencies {
implementation ....
}
I am getting:
Execution failed for task ':compileJava'.
> Could not resolve all files for configuration ':compileClasspath'.
> Cannot resolve external dependency .... because no repositories are defined.
Required by:
project :
I want to define repositories once in the main file as these do not change in subprojects.
In the settings.gradle of the main project I have:
rootProject.name = 'main-project-name'
include 'sub-project-name'
And in the settings.gradle of the sub project I have:
rootProject.name = 'sub-project-name'
A multi-project build in Gradle may have multiple build.gradle files, but only one settings.gradle file (usually in the root project directory). Your second settings.gradle files defines a second setup that only contains a single project. You can check this by running gradle projects. Just delete the second settings.gradle file to solve your problem.
Usually you can simply define the names of your sub-projects by naming the respective directories and then calling include. The name of the rootProject may be defined inside settings.gradle, because the name of the directory is often not stored in version control systems like Git. Developers may check out the repository to different directories causing Gradle to use different names for the root project. If you want a subproject to have a different name than its containing directory, use include with the desired name and then change the project directory via project(':foo').projectDir = file('path/to/foo').
Modern Gradle versions provide a recommended way to declare dependencies in a centralized way.
TLDR: Use the dependencyResolutionManagement DSL in your settings files to configure the repositories in all the subprojects. Both Groovy and Kotlin DSL look the same 👇
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
Read more in the docs: "Centralizing repositories declaration".

build.gradle buildscript dependencies vs. dependencies?

Can someone explain to me how depedencies listed in the "buildscript" in the build.gradle file are different than regular dependencies listed in the dependencies block { } ? and why they have to be listed with the syntax "implementation"? I've googled this and responses say the dependencies in the buildscript and used to "build the project" but I don't understand this? can anyone give a more clear picture and answer?
buildscript:
buildscript
{
repositories
{
maven {
url 'myMavenFeed'
credentials {
username "myUsername"
password myPassword
}
}
mavenCentral()
jcenter()
}
dependencies
{
classpath "com.microsoft.azure.sdk.iot:iot-device-client:1.14.1"
}
}
Dependencies block:
dependencies
{
compile group: 'com.microsoft.azure.sdk.iot', name: 'iot-device-client', version: '1.16.0'
}
Can someone explain to me how depedencies listed in the "buildscript" in the build.gradle file are different than regular dependencies listed in the dependencies block { } ?
Dependencies defined in the buildscript { } block are dependencies to use to build your project. These dependencies are available to use in your Gradle build file (build.gradle or build.gradle.kts)
Dependencies defined in the dependencies { } are for your application code.
So for your samples in your questions, does it make sense for Gradle (the build system) to have iot-device-client on its classpath? Why does a build system need iot-device-client on its classpath to build your project? It doesn't make sense therefore it should be removed.
Now let's say you are developing an application the requires some functionality or class from iot-device-client. You need a way to add this library to your application's code/classpath. You when then declare it as a dependency as you have done above:
dependencies {
implementation("com.microsoft.azure.sdk.iot:iot-device-client:1.16.0")
}
References:
External dependencies for the build script
Declaring depenedncies
and why they have to be listed with the syntax "implementation"?
implementation is known as a configuration: A Configuration represents a group of artifacts and their dependencies
There are many more configurations depending on the plugins you apply to your project. For example, if you apply the Java plugin:
plugins {
id("java")
}
The following configurations are available to use:
implementation
compileOnly
compileClasspath
...and many more
Each one has their own meaning/usage and I strongly suggest reading about them here.

Can I Define The buildscript repositories In The Root Project For All Modules

I have code similar to the following in each of my sub-modules but with different plugins
buildscript {
ext {
springBootVersion = '2.0.0.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
Can the repositories be set in the root project so I don't repeat it?
I already have the main repositories section set in the root like this but this question is for the plugins and the buildscript section.
allprojects {
repositories {
jcenter()
maven { url 'https://jitpack.io' }
}
}
The  buildscript block can't be put into a separate file and then imported, it's a special one.
What you can do on the other hand is to put your buildscript block into the build.gradle file of the root project and then all other build files from subprojects will inherit this block.
You can copy the repositories from the buildscript of the root project, like this:
buildscript {
repositories.addAll rootProject.buildscript.repositories
// plugin dependencies, etc, go here
}
You still have to copy that one line to the buildscript block of your subsidiary Gradle file, but at least it is just one line. This works both for subprojects, and also for additional .gradle files you include using apply from:
You can even put that one-liner into a method in build.gradle:
rootProject.ext.setupPluginRepos = {
it.repositories.addAll rootProject.buildscript.repositories
}
So then your subproject (or Gradle file) just has to do this:
buildscript {
rootProject.ext.setupPluginRepos(it)
// plugin dependencies, etc, go here
}
I wrote a custom Gradle plugin, used for certain internal projects at my employer. It adds an extension to every project, let us just call it xyzzy (not its actual name). So I added the above setupPluginRepos to my plugin, and now I just have to do:
buildscript {
xyzzy.setupPluginRepos(it)
// plugin dependencies, etc, go here
}

Gradle buildSrc and buildscript

We have a Gradle build that includes a buildSrc with some custom plugins. Those plugins apply yet other plugins. For example, our plugin applies com.android.tools.build:gradle. For annotation processing that library needs to be on Gradle's classpath during compilation. So, putting this in our main build.gradle works:
buildscript {
repositories {
google()
}
dependencies {
classpath "com.android.tools.build:gradle:$gToolsVersion"
}
}
However, that means that for a user to apply this plugin they must (1) apply our plugin and (2) add that buildscript boilerplate. It seems like that shouldn't be necessary. We can also add a project.buildscript block inside our plugin but that too seems unnecessary and, due to this bug is problematic: https://developer.android.com/studio/build/gradle-plugin-3-0-0.html?utm_source=android-studio#known_issues.
I added the com.android.tools.build:gradle dependency to buildSrc/build.gradle as a runtime dependency. It seems like that should work: I thought that tells Gradle that in order to run my plugin that library (and its dependencies) need to be on the classpath. However, gradle buildEnvironment (and the fact that our build fails) makes it clear that's not the case.
So, questions:
What's the difference between a runtime dependency specified in buildSrc/build.gradle and a classpath dependency specified in a buildscript block in a regular build.gradle?
How can I arrange things so that users can apply the plugin from buildSrc and not have to also add the buildscript block to their build.gradle?
I got a slightly different problem and found an acceptable solution that might help with for your second question: I wanted to apply the same repositories in the buildSrc/build.gradle and twice in the root build.gradle.
repositories.gradle in the project root:
repositories {
if (project.hasProperty('nexus')) {
maven {
url 'http://localhost:8081/repository/JCenter/'
}
maven {
url 'http://localhost:8081/repository/Maven_Google/'
}
} else {
jcenter()
google()
}
}
ext {
androidGradleBuildToolsDependency = 'com.android.tools.build:gradle:3.1.3'
}
buildSrc/build.gradle:
buildscript {
apply from: '../repositories.gradle'
}
allprojects {
apply from: '../repositories.gradle'
}
dependencies {
// androidGradleBuildToolsDependency is defined in repositories.gradle
implementation androidGradleBuildToolsDependency
}
Root build.gradle:
buildscript {
apply from: 'repositories.gradle'
}
allprojects {
// this line will also be executed from the build.gradles in subprojects, so the working
// directory isn't always the same, so we use the absolute path here
apply from: "${rootProject.projectDir}/repositories.gradle"
}
Note that you do not need the classpath dependency inside the buildscript block of the root build.gradle as you normally would. The implementation dependency in the repositories.gradle seems to auto apply it.
My solution probably doesn't work when the build.gradles are supplied via a dependency.
Stumbled upon this while digging into a tangential problem.
I've been successfully using buildSrc/build.gradle as the place to define dependencies that would normally belong in the root-project's buildscript classpath for a few of my projects.
You can see a working example here: https://github.com/episode6/chop/blob/develop/buildSrc/build.gradle
I used to use compile dependencie but just switched to runtimeClasspath which feels more appropriate and also works. I don't think your classpath dependencies were working because they would be on the classpath of the buildSrc project, but not compiled into or run along side it.
If you decide to go this route, you may run into the problem I was just digging into which only came up because of this approach.
When I tried this approach with the dokka plugin, I got the following error
Could not resolve all files for configuration ':detachedConfiguration1'.
> Cannot resolve external dependency org.jetbrains.dokka:dokka-fatjar:0.9.17 because no repositories are defined
I was able to workaround this by adding jcenter() to the root project's buildscript repositories: https://github.com/episode6/chop/blob/develop/build.gradle#L2

Gradle: Inject plugins to subprojects

I came across gradle plugin to help me to deal with dotted property names. It works fine in single project when used like this:
apply plugin: 'config'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.esyfur:gradle-config-plugin:0.4.+'
}
}
task printProps() {
println(config.grafana.url)
}
However, I want to use this plugin in multiple projects (multi-module) and would like idealy not to repeat such initialization in every project but rather inject it somehow to have it more manageable.
I have failed to find out how to do it or if it can be done. I tried e.g. in parent build.gradle use this:
allprojects {
apply plugin: 'config'
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.esyfur:gradle-config-plugin:0.4.+'
}
}
but it does not work. Gradle complains that it can not find property 'config' on task.
Update
After Peter's comment I started wondering around and creating an example project from the scratch and incrementally get it closer to my 'real project'. I was not completely precise in describing my setup. In settings.gradle I use
rootProject.children.each { project ->
project.buildFileName = "${project.name}.gradle"
}
which causes this problem. Everything works fine When switched back to build.gradle names.
The problem is that your settings.gradle doesn't configure rootProject.buildFileName to match the non-standard filename used for the build script. Therefore the build script doesn't get evaluated, and its buildscript block never takes effect.

Resources