Background
(Please keep in mind I've simplified the problem for purposes of discussion here)
I've got a set of applications and dependent libraries, something like this (each with a src/ directory and build.gradle):
appa/
appb/
libx/
liby/
libz/
In build.gradle, the dependencies are currently declared like this:
appa/build.gradle:
compile "com.asdf:libx:1.0"
compile "com.asdf:liby:1.0"
appb/build.gradle:
compile "com.asdf:liby:1.0"
liby/build.gradle:
compile "com.asdf:libz:1.0"
What problem am I trying to solve
Say I'm working on appa, and I need to make changes to libx. I need to do multiple steps:
Pull libx from source control and make changes locally
Rebuild and push changes to some repo (not prod!)
Rebuild appa (pulling the recently updated libx from repo)
If my testing reveals a bug in libx, I've got to repeat that over again.
This is super-annoying when working in IDEs like Eclipse, where even though my projects are logically using other projects, I've got to still use the artifacts as dependencies.
Wouldn't it be great if I can just pull the project locally, and projects that logically depend on it will automatically use the source project instead of artifact for building?
What I've done so far
I've written a small gradle plugin (referenced in each project's build.gradle) that identifies com.asdf dependencies, and uses dependency substitution to replace the artifact dependency with a project dependency if that project exists locally.
configurations.all {
resolutionStrategy.dependencySubstitution {
all { DependencySubstitution dependency ->
if (dependency.requested instanceof ModuleComponentSelector && dependency.requested.group == 'com.asdf') {
def targetProject = findProject(":${dependency.requested.module}")
if (targetProject != null) {
dependency.useTarget targetProject
}
}
}
}
}
Yay! With a few modifications to settings.gradle (see below), I've accomplished my goal... Except...
Where I'm stuck
I need to modify settings.gradle to include lines like this for every dependency (otherwise findProject doesn't resolve the dependent project during build):
include ':libx'
project(':libx').projectDir = new File(settingsDir, '../libx')
While it's possible to go through all the settings.gradle files and do this (I've done it for a handful as my proof-of-concept), it's ugly, repetitive, and is logically the same information that is being passed to compile arguments for the build.gradle dependencies.
It's also error-prone when someone adds a new dependency but doesn't update settings.gradle, or introduces a typo between them.
I've also tried making settings.gradle just define projects for all directories it finds at that level, but then building any project turns into a mega-build of all projects.
(I've tried several other things, but my question is getting long in the tooth already)
My question
What's a better way to do this, without duplicating information between settings.gradle and build.gradle? I want to make it so adding new dependencies is still just as easy as adding the compile reference in build.gradle, without touching settings.gradle...
I'm still rather new to groovy/gradle, so maybe I'm missing something that's obvious to the more experienced gradle master?
I believe your use-case is the motivation for Composite Builds.
I have a demo here, which writes to a jars folder as a mock publishing of artifacts. Be sure to check-out the README.md as the demo is a mini-laboratory for trying out the use-case before and after composite builds.
In the demo mainBuild is appa; utils is libx. The key syntax in mainBuild/settings.gradle (here) is:
includeBuild '../utils'
This tells Gradle to use the local codebase instead of the published artifact. Of course, one would not commit this line to source-control.
Related
I have the following modules with the top one depending on the next depending on the next ( These links have VERY SIMPLE build.gradle and settings.gradle files)
https://github.com/deanhiller/webpieces/tree/master/core/core-ssl
https://github.com/deanhiller/webpieces/tree/master/core/core-datawrapper
https://github.com/deanhiller/webpieces/tree/master/core/core-util
https://github.com/deanhiller/webpieces/tree/master/core/core-logging
I temporarily added an throw new RuntimeException to a test in core-datawrapper and core-util and built the project core-ssl (in the repo, ../../gradlew build)
The settings of core-ssl (found in above link and pasted here) is
includeBuild '../core-datawrapper'
includeBuild '../core-mock'
The settings of core-datawrapper (again in above links)
includeBuild '../core-util'
I clear out core-util/build and I see these targets run
> Task :core-util:compileJava
> Task :core-util:classes
> Task :core-util:jar
That is it. Why is the tests not running? I thought build depended on assemble and test separately?
The same for ../../gradlew clean and ../../gradlew publish
Ideally, I want my target to affect all transitive projects as well. As developers add projects, I don't want to have to add code to each gradle project in the transitive deps list either.
Yes, that’s correct.
This is most probably due to the original use-case of composite builds.
You have some binary dependency on some library, and then you want to change something in the library and test it in your project before even committing or publishing the library.
So the composite build result replaces the binary dependeny and only as much work as really necessary is done, meaning the jar is built.
If you use composite builds as normal structuring element, you either need to wire the lifecycle tasks you want to have wired yourself or search for a plugin that maybe does it.
As developers add projects, I don't want to have to add code to each gradle project in the transitive deps list either.
You can programmatically iterate through the included builds using gradle.includedBuilds, so you can do the wiring in a generic way.
We have a directory structure like so
java
build/build.gradle (This does NOT exist yet, but we want this)
servers
server1/build.gradle
server2/build.gradle
libraries
lib1/build.gradle
lib2/build.gradle
We have 11 servers and 14 libraries with varying uses of dependencies. EACH server is a composite build ONLY depending on libraries (we don’t allow servers to depend on each other). In this way, as our mono-repo grows, opening up server1 does NOT get slower and slower as more and more gradle code is added(ie. gradle only loads server1 and all it’s libraries and none of the other libraries OR servers are loaded keeping things FAST).
Ok, so one problem we are running into is duplication now which is why we need build/build.gradle file AND we want EVERY module in our mono repo to include that somehow for a few goals(each goal may need a different solution)
GOAL 1: To have an ext { … } section containing a Map of Strings to gradle dependencies much like so
deps = [
'web-webserver': "org.webpieces:http-webserver:${webpiecesVersion}",
'web-webserver-test': "org.webpieces:http-webserver-test:${webpiecesVersion}",
'web-devrouter': "org.webpieces:http-router-dev:${webpiecesVersion}"
]
In this way, we want ALL our projects to them import dependencies like so
compile deps['web-webserver']
GOAL 2: We want to 'include' a standard list of plugins so we are versioning all gradle plugins the same across the repo. While the above configures all jars to avoid jar hell in a mono-repo, we would like to do the same with just this section
plugins {
id 'com.github.sherter.google-java-format' version '0.9'
}
Of course, it each project may also want to add a few more plugins OR even not depend on this section(in case of an emergency and trying to just get the job done).
GOAL 3: We want checkstyle configuration (or any plugin config) to be defined the SAME for all projects (eventually!!!). We would like the checkstyle gradle to live in a common area but have all libraries somehow pull it in. Again, it would be nice for it to be optional in that, I can pull the gradle section into my build.gradle OR can create a new one in case of emergencies so I don't have to fix all projects in the monorepo right away.
IDEALLY, perhaps I kind of want configuration injection where when I run server1/build.gradle, it actually runs java/build/build.grade as it’s parent somehow but with overrides (IF I declare 'extends xxx.gradle' maybe) then all libraries it uses also use java/build/build.gradle as their parent. I am not sure this is possible or feasible. I am pretty sure 'extends xxx' doesn't exist in gradle.
Are any of these GOALS possible?
thanks,
Dean
I have been working on a monorepo with the exact same requirement as you, using gradle composite builds as well. The way we have solved this problem is by using pre compiled plugins
You need to do a new gradle project with only the code you want to share. This will create a plugin, that you can just add as a composite build and apply to the other projects.
I'm a bit confused by why you don't just use a "standard" gradle top level build file and compose the others as subprojects.
This solves all 3 of your goals
If you are concerned by build speed, you can target each server individually simply by running
./gradlew :server1:build
But if you are not able to do this for some reason you can use the apply from: syntax as described here
What is the key difference between includeBuild(...) and implementation(project(...)) in the Gradle build system? I cannot really see the usecase difference after reading the documentation:
https://docs.gradle.org/current/userguide/declaring_dependencies.html#sub:project_dependencies
https://docs.gradle.org/current/userguide/composite_builds.html#separate_composite
What I am trying to do: Share same codebase for two separate projects: data classes (kotlix.serialization), external database dao, interfaces. It is not a full library but just some code snippets.
How can I connect the two projects in Intellij so that type hinting works?
Composite Build (by using includeBuild) is a way to create a dependency between autonomous Gradle Projects.
Project import, is a way to create a dependency between two modules within a same Gradle Project.
Composite Build is far more powerful and is also meant to be the new way of factorizing gradle configuration between multiple projects that you traditionally do with the buildSrc technique.
I found this "Structuring Large Projects" article to be easier to read than the "Composite Builds" doc.
An excellent sample project that demonstrates the power of Composite Builds can be found in Gradle sample_structuring_software_projects.
Project dependency case
The tree would look like this:
settings.gradle.kts
module1/build.gradle.kts
module2/build.gradle.kts
And you are declaring a dependency in module1/build.gradle.kts like this:
dependencies {
implementation(project("com.domain:module2"))
}
The dependency will be resolved only if both projects are declared as sub-modules of a common root project.
It means you have a root settings.gradle.kts like this:
rootProject.name = "rootProject"
include(":module1")
include(":module2")
Composite build case
The projects do not need to have common "umbrella" root project.
Each project is a totally independent project.
One project can simply declare a dependency on the other project (without even the target project knowing about it).
Tree:
project1/build.gradle.kts
project1/settings.gradle.kts
project2/build.gradle.kts
project2/settings.gradle.kts
In project1/settings.gradle.kts:
rootProject.name = "project1"
includeBuild("../project2") //No more ':' as it is not a module
In project2/settings.gradle.kts:
rootProject.name = "project2"
In project1/build.gradle.kts like this:
dependencies {
implementation("com.domain:project2")
}
I have the same problem. Reading on the first link, the next para says:
Local forks of module dependencies
A module dependency can be substituted by a dependency to a local fork
of the sources of that module, if the module itself is built with
Gradle. This can be done by utilising composite builds. This allows
you, for example, to fix an issue in a library you use in an
application by using, and building, a locally patched version instead
of the published binary version. The details of this are described in
the section on composite builds.
So, it should be implementation project according to me.
P.S. The code completion is working on one of my sub project but not on the other. I am still trying to figure that out
I am using gradle and its local repository is at \.gradle\caches\modules-2\files-2.1 which has all the downloaded jar but not my modules.Is there any specific place I should be searching it for ?
I need it as is in settings.gradle I am having a dependency path specified like :
include ':model'
project (':model').projectDir = new File(settingsDir, './model')
in a new project. Also I don't want to give path in that way because if I have a dependency from multiple projects on this project then mentioning path will be difficult and weird.
How can I make gradle search it from local maven or gradle repositories.
I'm still not sure what is being asked here, and I suspect there is some confusion over how multi-project builds work. So I'm going to attempt to provide a general-purpose answer.
The first question you need to answer is whether you're interested in dependencies between projects that are part of the same build — as in part of a multi-project build — or in separate builds.
Project dependencies (multi-project builds)
Project dependencies are covered in the user manual and only apply to multi-project builds. They use a logical path, using colons as 'path' separators, to specify the location of the target module, like so:
dependencies {
implementation project(":model")
}
At this point, Gradle needs to know where ":model" exists on the file system. There's no getting around that. You have a few options:
Follow the convention of directory structure matching the logical path structure, i.e. have a MyBigProject/model directory containing the ":model" child project
Specify the file path of ":model" in settings.gradle, e.g. with project(":model").projectDir = new File(rootDir, "unusual/path/to/model")
Automate the discovery of projects
The most common approach is the first one. The second is not unusual, particularly if you want to put child projects into a separate directory, like subprojects — something the build of Gradle itself does. I haven't seen the last option done, and I don't know whether it runs into problems.
For the sake of completeness, and at your own risk if you use something like it, here's an example of automatic discovery of projects in the settings.gradle file:
rootDir.eachDir { File dir ->
if ("build.gradle" in dir.listFiles()*.name) {
include dir.name
}
}
This fragment basically looks for directories within the root project folder that have a build.gradle file in them and adds them as child projects. The child projects' directory names become the projects' names.
It's not particularly clever, and you should really use different names for the build files, but it may give you some ideas to work with.
Non-project dependencies
As with project dependencies, Gradle needs to know where to get the corresponding JAR or other form of artifact for a specified module. You normally specify Maven Central or something similar for this, but there are other useful, but less common, options:
Copy a project's artifacts into the local Maven repository — both the Maven Plugin and Maven Publish Plugin support this
Publish to a Maven-compatible repository using a file:// URL rather than an HTTP/HTTPS one, which protects your projects from corruption of Maven Local
Worth noting is that Gradle supports composite builds that allow you to substitute a normal dependency with (effectively) a project dependency from another build. So if model were part of a separate build but you had the source code and build locally, you could make changes and immediately test them in another build's project without going through the whole "install" intermediate step that's common in the Maven world (and Gradle pre-composite-builds).
Hope all this makes sense.
What I'd ultimately like to achieve is having a top level project that can clone all of my deps, and make them rebuild as necessary. While still allowing them to build by themselves in my CI environment.
my project structure looks like this
rpf/
is/
sec/
sec has a dependency on is, is does have some deps on some of my other libraries, but currently they are not pulled in to the project for convenience sake in rapid development (though I would like them to be, ultimately they shouldn't be changing frequently which is why I want them separate).
this is a snippet of my dependency resolution in sec
dependencies {
implementation project(':is')
implementation 'com.xenoterracide:entity-api'
implementation 'com.xenoterracide:entity-jpa:0.1.0-SNAPSHOT'
what I'd like to do, is reference :is as implementation com.xenoterracide:is:0.1.0-SNAPSHOT while retaining the compile project before this one, so that if I change is, I don't have to install the dep before I can use it.
using gradle 4.3.1
update
tried this for the composite build, in my rpf/settings.gradle
include 'is', 'sec'
includeBuild('sec') {
dependencySubstitution {
substitute module('com.xenoterracide.rpf:is') with project(':is')
}
}
but
org.gradle.api.GradleException: Included build 'sec' collides with subproject of the same name.
given the example in the docs for a composite build, I'm uncertain which parameters should be which for my use case