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

Time:01-13

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

CodePudding user response:

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.

CodePudding user response:

First, you have to decide if this is a multi-project build or a composite build.

  • If the two projects are highly coupled (i.e., two modules of the same conceptual project), then you should be using a multi-project build.

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

  • If the two projects are relatively independent of each other, but one depends on the other like any other dependency, then you might consider a composite build.

    An example of a composite build is below.


Example Composite Build

There are at least two ways to include a build in another.

  1. Use --include-build on the command line.

  2. Use includeBuild in settings.gradle[.kts] (not include; that's for multi-project builds).

    • There's a version of this where you create a "pseudo" parent project whose sole purpose is to define a settings.gradle[.kts] file that simply includes the desired "real" builds. That way you don't have to use --include-build or modify the Gradle files of one or more of the "real" projects. See here.

This example uses --include-build. I also used the Kotlin DSL, but translating to the Groovy DSL should not be too difficult if you prefer that.

My example does not declare any repositories, but that does not mean you cannot do so. You may want to if you do not always want to include a build, but still want to use that other build's published binaries (or, of course, if you have other dependencies). Here's a note on how dependencies work in composite builds:

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.

Project 1

Here's the source for "project 1", which depends on "project 2".

settings.gradle.kts:

rootProject.name = "project1"

build.gradle.kts:

plugins {
    application
}

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

application {
    mainClass.set("sample.project1.Main")
}

dependencies {
    implementation("sample.project2:project2:1.0")
}

Main.java:

package sample.project1;

import sample.project2.Greeter;

public class Main {
    
    public static void main(String[] args) {
        Greeter.greet();
    }
}

Project 2

Here's the source for "project 2".

settings.gradle.kts:

rootProject.name = "project2"

build.gradle.kts:

plugins {
    `java-library`
}

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

Greeter.java:

package sample.project2;

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

Project Structure

This is the layout of the two projects (they are in sibling directories):

...\DEMO
├───project1
│   │   build.gradle.kts
│   │   gradlew
│   │   gradlew.bat
│   │   settings.gradle.kts
│   │
│   ├───gradle
│   │   └───wrapper
│   │           gradle-wrapper.jar
│   │           gradle-wrapper.properties
│   │
│   └───src
│       └───main
│           └───java
│               └───sample
│                   └───project1
│                           Main.java
│
└───project2
    │   build.gradle.kts
    │   gradlew
    │   gradlew.bat
    │   settings.gradle.kts
    │
    ├───gradle
    │   └───wrapper
    │           gradle-wrapper.jar
    │           gradle-wrapper.properties
    │
    └───src
        └───main
            └───java
                └───sample
                    └───project2
                            Greeter.java

Running Project 1

And here's an example of running project 1 while including project 2:

...\demo\project1> .\gradlew run --include-build ..\project2

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

How to Modify Your Code

Given you seem to want a composite build, you need to make a few changes. For instance, declaring a dependency on an included build looks like declaring a dependency on any other external artifact (i.e., it uses Maven coordinates):

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'

    // REPLACE WITH REAL groupID, artifactID, AND version
    implementation 'com.example:project1:1.0'
}

And then your settings.gradle file for project 2 should simply look like:

rootProject.name = 'project2'

Or, if you don't want to use --include-build:

rootProject.name = 'project2'

includeBuild '../project1'
  • Related