Scorb
Scorb

Reputation: 1950

How do I add a java gradle project on my file system as a dependency to another local project?

I have project2 that depends on project1. They are both next to each other on my file system.

When I try to build project2 (after successfully building project1) I get the error:

Could not determine the dependencies of task ':app:distTar'.
> Could not resolve all task dependencies for configuration ':app:runtimeClasspath'.
   > Could not resolve project :project1.
     Required by:
         project :app
      > No matching configuration of project :project1 was found. The consumer was configured to find a runtime of a library compatible with Java 11, packaged as a jar, preferably optimized for standard JVMs, and its dependencies declared externally but:
          - None of the consumable configurations have attributes.

Project2 adds the dependency to project1 as follows...

build.gradle

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java application project to get you started.
 * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
 * User Manual available at https://docs.gradle.org/7.2/userguide/building_java_projects.html
 */

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit test framework.
    testImplementation 'junit:junit:4.13.2'

    // This dependency is used by the application.
    implementation 'com.google.guava:guava:30.1.1-jre'

    implementation project(':project1')
    implementation files('../../project1/lib/build/libs/lib.jar')
}

application {
    // Define the main class for the application.
    mainClass = 'project2.App'
}

Settings.gradle

rootProject.name = 'project2'
include('app')

include   ':project1'
project(':project1').projectDir = new File(settingsDir, '../project1')

The source for project1...

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package project1;

public class Library {
    public boolean someLibraryMethod() {
        return true;
    }
}

The source for project2

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package project2;

import project1.*;

public class App {
    public String getGreeting() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        System.out.println(new App().getGreeting());
        bool someBool = Library.someLibraryMethod();
    }
}

the complete folder structure of the two projects. Although it looks like project2 is under project1, that is just how the copy pasted output looks, they are indeed sibling folders.

── project1
│   ├── gradle
│   │   └── wrapper
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── gradlew
│   ├── gradlew.bat
│   ├── lib
│   │   ├── bin
│   │   │   ├── main
│   │   │   │   └── project1
│   │   │   │       └── Library.class
│   │   │   └── test
│   │   │       └── project1
│   │   │           └── LibraryTest.class
│   │   ├── build

│   │   │   ├── libs
│   │   │   │   └── lib.jar

│   │   ├── build.gradle
│   │   └── src
│   │       ├── main
│   │       │   ├── java
│   │       │   │   └── project1
│   │       │   │       └── Library.java
│   │       │   └── resources
│   │       └── test
│   │           ├── java
│   │           │   └── project1
│   │           │       └── LibraryTest.java
│   │           └── resources
│   └── settings.gradle
└── project2
    ├── app
    │   ├── build.gradle
    │   └── src
    │       ├── main
    │       │   ├── java
    │       │   │   └── project2
    │       │   │       └── App.java
    │       │   └── resources
    │       └── test
    │           ├── java
    │           │   └── project2
    │           │       └── AppTest.java
    │           └── resources
    ├── gradle
    │   └── wrapper
    │       ├── gradle-wrapper.jar
    │       └── gradle-wrapper.properties
    ├── gradlew
    ├── gradlew.bat
    └── settings.gradle

69 directories, 37 files

Upvotes: 2

Views: 1095

Answers (2)

Slaw
Slaw

Reputation: 46275

Types of Builds

First you have to decide if this should be a multi-project build or a composite build.

Multi-Project Build

This is when you have a single Gradle project that is made up of multiple sub-projects. You should use a multi-project build if the various modules are highly coupled.

You create a sub-project by using include in the settings.gradle[.kts] file. But you should only have the one settings file. Each individual sub-project may have its own build.gradle[.kts] file, and typically does, but does not necessarily have to. There are various ways to cross-configure sub-projects. See the documentation linked above for details.

I do not provide an example of a multi-project build in this answer.

Composite Build

This is when you have two (or more?) relatively independent projects, but one depends on the other like any other external dependency. Yet you want to use the artifacts of the Gradle project directly rather than grab the binaries from a repository. At least some of the time, anyway.

There are a few ways to create a composite build.

  1. Use --include-build on the command line. This requires the least amount of modification to either project (none).

  2. Use includeBuild in the settings.gradle[.kts] file (not include, as that's for multi-project builds). This requires modifying at least one of the project's configurations, which may not be desirable.

  3. Create a "parent" Gradle project for the "real" projects with a settings.gradle[.kts] file. Then add includeBuild for the "real" projects that you want to composite.

In a composite build, the way dependencies are resolved is described in the documentation linked above. Here's an excerpt:

Included builds interact with other builds via dependency substitution. If any build in the composite has a dependency that can be satisfied by the included build, then that dependency will be replaced by a project dependency on the included build. Because of the reliance on dependency substitution, composite builds may force configurations to be resolved earlier, when composing the task execution graph. This can have a negative impact on overall build performance, because these configurations are not resolved in parallel.

Note you don't use project("...") to define a dependency on an included build. You simply use the regular Maven coordinates like normal, and if the included build has a project matching those coordinates, then it will be substituted in.


Example Composite Build

This example uses --include-build to create a composite build. Although this example can be reproduced by following the instructions below, it can be a little error-prone and tedious, so here is a GitLab repository of the final example. I make no promise to keep the repository alive.

Source Code

Here is how the two projects were generated and any changes I made to the build configurations and or the code. Note I used Gradle 7.6 when running the init task. Also, when prompted with:

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no]

I answered, "no".

Project 1

This project is the "library". It was generated with the following command:

...\demo\project1> gradle init --type java-library --test-framework junit-jupiter --project-name project1 --package sample.project1 --dsl kotlin

I only made modifications to the following files:

lib\build.gradle.kts

I added:

group = "sample.project1"
version = "1.0"

lib\src\main\java\sample\project1\Library.java

Changed it to:

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package sample.project1;

public class Library {

    public static void printMessage() {
        System.out.println("Hello, this is a composite build!");
    }
}

lib\src\test\java\sample\project1\LibraryTest.java

I deleted this file.

Project 2

This project is the "application". It depends on project 1. It was generated with the following command:

...\demo\project2> gradle init --type java-application --test-framework junit-jupiter --project-name project2 --package sample.project2 --dsl kotlin

I only made modifications to the following files:

app\build.gradle.kts

I added:

group = "sample.project2"
version = "1.0"

And added the following to the dependencies block:

// ADD DEPENDENCY ON PROJECT 1
implementation("sample.project1:lib:1.0")

app\src\main\java\sample\project2\App.java

I changed it to:

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package sample.project2;

import sample.project1.Library;

public class App {

    public static void main(String[] args) {
        Library.printMessage();
    }
}

app\src\test\java\sample\project2\AppTest.java

I deleted this file.

Directory Structure

Here is the directory structure of the two projects after generating them and making the above modifications. The output excludes the .gradle directory.

...\DEMO
├───project1
│   │   .gitattributes
│   │   .gitignore
│   │   gradlew
│   │   gradlew.bat
│   │   settings.gradle.kts
│   │
│   ├───gradle
│   │   └───wrapper
│   │           gradle-wrapper.jar
│   │           gradle-wrapper.properties
│   │
│   └───lib
│       │   build.gradle.kts
│       │
│       └───src
│           ├───main
│           │   ├───java
│           │   │   └───sample
│           │   │       └───project1
│           │   │               Library.java
│           │   │
│           │   └───resources
│           └───test
│               ├───java
│               └───resources
└───project2
    │   .gitattributes
    │   .gitignore
    │   gradlew
    │   gradlew.bat
    │   settings.gradle.kts
    │
    ├───app
    │   │   build.gradle.kts
    │   │
    │   └───src
    │       ├───main
    │       │   ├───java
    │       │   │   └───sample
    │       │   │       └───project2
    │       │   │               App.java
    │       │   │
    │       │   └───resources
    │       └───test
    │           ├───java
    │           └───resources
    └───gradle
        └───wrapper
                gradle-wrapper.jar
                gradle-wrapper.properties

Running Example

Running the example with the following command:

...\demo\project2> .\gradlew app:run --include-build ..\project1 --console plain

Note: I used --console plain in order to see all the task output. That way you can see tasks from the included build were executed. You do not need to include this option if you don't want to.

Gave the following output:

> Task :app:processResources NO-SOURCE
> Task :project1:lib:compileJava
> Task :project1:lib:processResources NO-SOURCE
> Task :project1:lib:classes
> Task :project1:lib:jar
> Task :app:compileJava
> Task :app:classes

> Task :app:run
Hello, this is a composite build!

BUILD SUCCESSFUL in 1s
4 actionable tasks: 4 executed

Upvotes: 1

Martin Zeitler
Martin Zeitler

Reputation: 76879

Make directories project1 and project2 both modules, one application and one library module. Files gradle, gradlew, gradlew.bat, settings.gradle need to be moved one level up. The root project needs it's own build.gradle. Including the modules in settings.gradle is straightforward:

include ':project1'
include ':project2'

Then one can depend on module :project2 in module :project1:

dependencies {
    testImplementation project(':project2')
    api project(':project2')
}

The library could also be published to the default local repository mavenLocal(). Unless publishing a sources package to Maven (eg. lib-sources.jar), it's usually easier to debug with two modules. The library module still can be made a Git sub-module.

Upvotes: 0

Related Questions