Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git repository with Maven project as a Gradle source dependency

This article describes an interesting feature of Gradle 4.10+ called a source dependency:

https://blog.gradle.org/introducing-source-dependencies

It allows to use a Git (for example a GitHub) source code repository to build a dependency from it. However it seems like it supports only Gradle projects as source dependencies. Is it possible to use a Maven project as well and if it's possible, please show an example.

When I try to use this feature with Maven project Gradle tries to find the build.gradle file there anyway (I see it when run Gradle with the --info option) and fails with an error message like:

Git repository at https://github.com/something/something.git did not contain a project publishing the specified dependency.

like image 889
Rostislav Krasny Avatar asked Aug 28 '20 13:08

Rostislav Krasny


People also ask

Can Gradle use Maven repositories?

Gradle can consume dependencies available in the local Maven repository. Declaring this repository is beneficial for teams that publish to the local Maven repository with one project and consume the artifacts by Gradle in another project.

Can we convert Maven project to Gradle?

gradle which you can now use to run all your builds using gradle commands. Please note that gradle init automatically detects the pom. xml and creates a gradle build with the java and maven plugin loaded. It means that existing Maven dependencies are automatically converted and added to your gradle build file.

How do I add a Gradle project in Maven?

To convert Gradle to Maven, first, Maven plugin need to be added in the build. gradle file. Then, simply run Gradle install in the directory. Lastly, simply run gradle install and the directory containing build.


1 Answers

The short answer

... is: "no".

Under the hood, source dependencies are composite builds. These needs to be Gradle projects as the external projects are sort of merged with the main project.

The long answer

... is: "yes but it is hard".

It is actually mentioned in the same blog post you linked to (emphasis mine):

Source dependencies make these use cases simpler to implement. Gradle takes care of automatically checking out the correct versions of dependencies, making sure the binaries are built when required. It does this everywhere that the build is run. The checked out project doesn’t even need to have an existing Gradle build. This example shows a Gradle build consuming two source dependencies that have no build system by injecting a Gradle build via plugins. The injected configuration could do anything a regular Gradle plugin can do, such as wrapping an existing CMake or Maven build.

Because it sounded like it wasn't the biggest thing in the world to create bridge between a Maven and a Gradle project in source dependencies, I gave it a shot. And I have it working except for transitive dependencies. You will basically need to do what is shown in the examples linked to above, but instead of building native libraries, you make a call-out to Maven (e.g. using a Maven plugin for Gradle).

However, the scripts I ended up with are complex enough that I would suggest you instead build the Maven project yourself, deploy it to a local Maven repository and then add that repository to the Gradle project.

<edit>

The loooooooong answer

Alright, so here is how to actually do it. The feature is poorly documented, and appears to be mostly targeted towards native projects (like C++ or Swift).

Root project setup

Take a normal Gradle project with the Java plugin applied. I did a "gradle init" in an empty folder. Assume that in this project, you are depending on a library called `` that you later want to include as a source dependency:

// [root]/build.gradle
dependencies {
    implementation 'org.example:my-maven-project:1.1'
}

Note that the version number defined here must match a Git tag in the repository. This is the code revision that will be checkout out.

Then in the settings file, we define a source dependency mapping for it:

// [root]/settings.gradle
rootProject.name = 'my-project'

includeBuild('plugins') // [1]

sourceControl {
    gitRepository("https://github.com/jitpack/maven-simple") { // [2]
        producesModule("org.example:my-maven-project") // [3]
        plugins {
            id "external-maven-build" // [4]
        }
    }
}
  • [1]: This includes a Gradle project called plugins that will be explained later.
  • [2]: This is just an arbitrary Maven project that I found, which was relatively simple. Substitute with the actual repository you have.
  • [3]: This is the name of the Maven module (the same as in the dependency block) that we are defining a source build for
  • [4]: This defines a custom settings plugin called external-maven-build that is defined in the plugins project, which will be explained later.

Plugins project structure

Inside the root project, we define a new Gradle project. Again, you can use gradle init to initialize it as a Groovy (or whatever you like) project. Delete all generated sources and tests.

// [root]/plugins/settings.gradle
// Empty, but used to mark this as a stand-alone project (and not part of a multi-build)
// [root]/plugins/build.gradle
plugins {
    id 'groovy'
    id 'java-gradle-plugin' // [1]
}

repositories {
    gradlePluginPortal() // [2]
}

dependencies {
    implementation "gradle.plugin.com.github.dkorotych.gradle.maven.exec:gradle-maven-exec-plugin:2.2.1" // [3]
}

gradlePlugin {
    plugins {
        "external-maven-build" { // [4]
            id = "external-maven-build"
            implementationClass = "org.example.ExternalMavenBuilder"
        }
    }
}
  • [1]: In this project, we are defining a new Gradle plugin. This is a standard way to do that.
  • [2]: To invoke Maven, I am using another 3rd party plugin, so we need to add the Gradle plugin portal as a repository.
  • [3]: This is the plugin used to invoke Maven. I am not too familiar with it, and I don't know how production ready it is. One thing I noticed is that it does not model inputs and outputs, so there are no built-in support for up-to-date checking. But this can be added retrospectively.
  • [4]: This defines the custom plugin. Notice that it has the same ID as used in the settings file in the root project.

Plugin implementation class

Now comes the fun stuff. I chose to do it in Groovy, but it can be done in any supported JVM languages of cause.

The plugin structure is just like any other Gradle plugin. One thing to note is that it is a Settings plugin, whereas you normally do Project plugins. This is needed as it we are basically defining a Gradle project at run-time, which needs to be done as part of the initialization phase.

// [root]/plugins/src/main/groovy/org/example/ExternalMavenBuilder.groovy
package org.example

import com.github.dkorotych.gradle.maven.exec.MavenExec
import org.gradle.api.Plugin
import org.gradle.api.artifacts.ConfigurablePublishArtifact
import org.gradle.api.initialization.Settings

class ExternalMavenBuilder implements Plugin<Settings> {
    void apply(Settings settings) {
        settings.with {
            rootProject.name = 'my-maven-project' // [1]
            gradle.rootProject {
                group = "org.example" //[2]
                pluginManager.apply("base") // [3]
                pluginManager.apply("com.github.dkorotych.gradle-maven-exec") // [4]

                def mavenBuild = tasks.register("mavenBuild", MavenExec) {
                    goals('clean', 'package') // [5]
                }

                artifacts.add("default", file("$projectDir/target/maven-simple-0.2-SNAPSHOT.jar")) { ConfigurablePublishArtifact a ->
                    a.builtBy(mavenBuild) // [6]
                }
            }
        }
    }
}

  • [1]: Must match the Maven module name
  • [2]: Must match the Maven module group
  • [3]: Defines tasks like "build" and "clean"
  • [4]: The 3rd party plugin that makes it more easy to invoke Maven
  • [5]: For options, see https://github.com/dkorotych/gradle-maven-exec-plugin
  • [6]: Adds the Maven output as an artifact in the "default" configuration

Be aware that it does not model transitive dependencies, and it is never up-to-date due to missing inputs and outputs.

This is as far as I got with a few hours of playing around with it. I think it can be generalized into a generic plugin published to the Gradle portal. But I think I have too much on my plate as it is already. If anyone would like to continue on from here, you have my blessing :)

like image 101
Bjørn Vester Avatar answered Oct 14 '22 03:10

Bjørn Vester