neoforged / NeoGradle

Gradle plugin for NeoForge development
GNU Lesser General Public License v2.1
44 stars 24 forks source link

java-ecosystem-capabilities plugin reveals broken dependency graph, breaking build #11

Closed jpenilla closed 9 months ago

jpenilla commented 10 months ago

Not sure whether this should be reported here or to NeoForge, but the org.gradlex.java-ecosystem-capabilities plugin reveals a problem with the dependency graph of something in the toolchain.

The 1.20.2 MDK branch did not build for me locally, I made this change to get it building (not sure what exactly fixed it, but this is not the focus of the issue anyways):

diff --git a/build.gradle b/build.gradle
index 5eec844..fe78682 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,9 @@ version = mod_version
 group = mod_group_id

 repositories {
-    mavenLocal()
+maven {
+url = "https://maven.neoforged.net/releases/"
+}
 }

 base {
@@ -40,7 +42,6 @@ runs {
         // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
         systemProperty 'forge.logging.console.level', 'debug'

-        modSource project.sourceSets.main
     }

     client {
diff --git a/gradle.properties b/gradle.properties
index 94fa68a..5df5f70 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ minecraft_version=1.20.2
 # as they do not follow standard versioning conventions.
 minecraft_version_range=[1.20.2,1.21)
 # The Neo version must agree with the Minecraft version to get a valid artifact
-neo_version=20.2.1
+neo_version=20.2.3-beta
 # The Neo version range can use any version of Neo as bounds or match the loader version range
 neo_version_range=[20.2,)
 # The loader version range can only use the major version of Neo/FML as bounds

Then when I add the org.gradlex.java-ecosystem-capabilities plugin to detect and enforce resolution of capability conflicts:

diff --git a/build.gradle b/build.gradle
index 5eec844..7b6bd0c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,13 +4,16 @@ plugins {
     id 'idea'
     id 'maven-publish'
     id 'net.neoforged.gradle.userdev' version '7.0.5'
+    id("org.gradlex.java-ecosystem-capabilities") version "1.3.1"
 }

 version = mod_version
 group = mod_group_id

 repositories {
-    mavenLocal()
+maven {
+url = "https://maven.neoforged.net/releases/"
+}
 }

 base {
@@ -40,7 +43,6 @@ runs {
         // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
         systemProperty 'forge.logging.console.level', 'debug'

-        modSource project.sourceSets.main
     }

     client {
diff --git a/gradle.properties b/gradle.properties
index 94fa68a..5df5f70 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ minecraft_version=1.20.2
 # as they do not follow standard versioning conventions.
 minecraft_version_range=[1.20.2,1.21)
 # The Neo version must agree with the Minecraft version to get a valid artifact
-neo_version=20.2.1
+neo_version=20.2.3-beta
 # The Neo version range can use any version of Neo as bounds or match the loader version range
 neo_version_range=[20.2,)
 # The loader version range can only use the major version of Neo/FML as bounds

I end up with the following error when invoking ./gradlew build:

└─➤  ./gradlew build
To honour the JVM settings for this build a single-use Daemon process will be forked. See https://docs.gradle.org/8.1.1/userguide/gradle_daemon.html#sec:disabling_the_daemon.
Daemon will be stopped at the end of the build

> Task :neoFormApplyForgesAccessTransformer
[13:15:28] [main/INFO]: Access Transformer processor running version 8.0.7+8.0.7+master.43473d43
[13:15:28] [main/INFO]: Command line arguments [--inJar, /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/rename/output.jar, --outJar, /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar, --atFile, /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/_script_neoforge.cfg, --atFile, /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/accesstransformer.cfg]
[13:15:28] [main/INFO]: Reading from /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/rename/output.jar
[13:15:28] [main/INFO]: Writing to /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar
[13:15:28] [main/INFO]: Transformer file /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/_script_neoforge.cfg
[13:15:28] [main/INFO]: Transformer file /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/accesstransformer.cfg
[13:15:28] [main/WARN]: Found existing output jar /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar, overwriting
[13:15:30] [main/INFO]: JAR transformation complete /home/jason/IdeaProjects/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar

> Task :neoFormRecompile FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':neoFormRecompile'.
> Could not resolve all files for configuration ':detachedConfiguration2'.
   > Could not resolve com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava.
     Required by:
         project :
      > Module 'com.google.guava:listenablefuture' has been rejected:
           Cannot select module with conflict on capability 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' also provided by [com.google.guava:guava:31.1-jre(runtime)]
   > Could not resolve com.google.guava:guava:31.1-jre.
     Required by:
         project :
      > Module 'com.google.guava:guava' has been rejected:
           Cannot select module with conflict on capability 'com.google.guava:listenablefuture:1.0' also provided by [com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava(runtime)]

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1m 15s
30 actionable tasks: 30 executed
marchermans commented 10 months ago

Sadly I can not reproduce this with an up to date MDK. With our without that plugin.

jpenilla commented 10 months ago

I am still able to reproduce with a fresh clone of the MDK and adding the plugin:

┌─jason@jason-pc ~/temp
└─➤  git clone git@github.com:neoforged/MDK.git
Cloning into 'MDK'...
remote: Enumerating objects: 674, done.
remote: Counting objects: 100% (674/674), done.
remote: Compressing objects: 100% (306/306), done.
remote: Total 674 (delta 208), reused 627 (delta 175), pack-reused 0
Receiving objects: 100% (674/674), 181.32 KiB | 937.00 KiB/s, done.
Resolving deltas: 100% (208/208), done.
┌─jason@jason-pc ~/temp
└─➤  cd MDK
┌─jason@jason-pc ~/temp/MDK  ‹main›
└─➤  ./gradlew build
To honour the JVM settings for this build a single-use Daemon process will be forked. See https://docs.gradle.org/8.1.1/userguide/gradle_daemon.html#sec:disabling_the_daemon.
Daemon will be stopped at the end of the build

> Task :neoFormApplyForgesAccessTransformer
[10:07:24] [main/INFO]: Access Transformer processor running version 8.0.7+8.0.7+master.43473d43
[10:07:24] [main/INFO]: Command line arguments [--inJar, /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/rename/output.jar, --outJar, /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar, --atFile, /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/_script_neoforge.cfg, --atFile, /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/accesstransformer.cfg]
[10:07:24] [main/INFO]: Reading from /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/rename/output.jar
[10:07:24] [main/INFO]: Writing to /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar
[10:07:24] [main/INFO]: Transformer file /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/_script_neoforge.cfg
[10:07:24] [main/INFO]: Transformer file /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/accesstransformer.cfg
[10:07:24] [main/WARN]: Found existing output jar /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar, overwriting
[10:07:26] [main/INFO]: JAR transformation complete /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar

> Task :neoFormRecompile
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use or override a deprecated API that is marked for removal.
Note: Recompile with -Xlint:removal for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

BUILD SUCCESSFUL in 1m 53s
35 actionable tasks: 35 executed
┌─jason@jason-pc ~/temp/MDK  ‹main›
└─➤  vim build.gradle
┌─jason@jason-pc ~/temp/MDK  ‹main*›
└─➤  git diff HEAD | cat && git status
diff --git a/build.gradle b/build.gradle
index 5eec844..1fe341e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,6 +4,7 @@ plugins {
     id 'idea'
     id 'maven-publish'
     id 'net.neoforged.gradle.userdev' version '7.0.5'
+    id("org.gradlex.java-ecosystem-capabilities") version "1.3.1"
 }

 version = mod_version
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   build.gradle

no changes added to commit (use "git add" and/or "git commit -a")
┌─jason@jason-pc ~/temp/MDK  ‹main*›
└─➤  ./gradlew build
To honour the JVM settings for this build a single-use Daemon process will be forked. See https://docs.gradle.org/8.1.1/userguide/gradle_daemon.html#sec:disabling_the_daemon.
Daemon will be stopped at the end of the build

> Task :neoFormApplyForgesAccessTransformer
[10:10:05] [main/INFO]: Access Transformer processor running version 8.0.7+8.0.7+master.43473d43
[10:10:05] [main/INFO]: Command line arguments [--inJar, /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/rename/output.jar, --outJar, /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar, --atFile, /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/_script_neoforge.cfg, --atFile, /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/accesstransformer.cfg]
[10:10:05] [main/INFO]: Reading from /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/rename/output.jar
[10:10:05] [main/INFO]: Writing to /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar
[10:10:05] [main/INFO]: Transformer file /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/_script_neoforge.cfg
[10:10:05] [main/INFO]: Transformer file /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/accesstransformers/Forges/accesstransformer.cfg
[10:10:05] [main/WARN]: Found existing output jar /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar, overwriting
[10:10:07] [main/INFO]: JAR transformation complete /home/jason/temp/MDK/build/neoForm/neoFormJoined1.20.2-20231019.002635/steps/applyForgesAccessTransformer/output.jar

> Task :neoFormRecompile FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':neoFormRecompile'.
> Could not resolve all files for configuration ':detachedConfiguration2'.
   > Could not resolve com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava.
     Required by:
         project :
      > Module 'com.google.guava:listenablefuture' has been rejected:
           Cannot select module with conflict on capability 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' also provided by [com.google.guava:guava:31.1-jre(runtime)]
   > Could not resolve com.google.guava:guava:31.1-jre.
     Required by:
         project :
      > Module 'com.google.guava:guava' has been rejected:
           Cannot select module with conflict on capability 'com.google.guava:listenablefuture:1.0' also provided by [com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava(runtime)]

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1m 11s
30 actionable tasks: 30 executed
┌─jason@jason-pc ~/temp/MDK  ‹main*›
└─➤  git remote show origin                                                                                                                                                                                     1 ↵
* remote origin
  Fetch URL: git@github.com:neoforged/MDK.git
  Push  URL: git@github.com:neoforged/MDK.git
  HEAD branch: main
  Remote branches:
    features/20.2 tracked
    main          tracked
  Local branch configured for 'git pull':
    main merges with remote main
  Local ref configured for 'git push':
    main pushes to main (up to date)
┌─jason@jason-pc ~/temp/MDK  ‹main*›
└─➤  git fetch origin && git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   build.gradle

no changes added to commit (use "git add" and/or "git commit -a")
┌─jason@jason-pc ~/temp/MDK  ‹main*›
└─➤
Technici4n commented 9 months ago

What is this com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava that is mentioned in your log, @jpenilla?

zml2008 commented 9 months ago

It's a dummy dependency for a change in guava a while ago, as mentioned in the Guava readme.

Technici4n commented 9 months ago

This issue can be reproduced by running gradlew resolveConf with the following simple build script:

plugins {
    id 'java'
    id 'java-library'
    id 'org.gradlex.java-ecosystem-capabilities' version "1.3.1"
}

repositories {
    mavenCentral()
    maven {
        name = "neoforge"
        url = "https://maven.neoforged.net/releases"
    }
}

tasks.register("resolveConf") {
    it.doFirst {
        def conf = configurations.detachedConfiguration()
        def addDep = { conf.dependencies.add(dependencies.create it) }

        addDep "net.neoforged:JarJarMetadata:0.4.0"
        addDep "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava"

        conf.resolve().each { file ->
            println(file)
        }
    }
}

I don't think we are to blame for this?

Technici4n commented 9 months ago

Interestingly, the following works:

dependencies {
    implementation "net.neoforged:JarJarMetadata:0.4.0"
    implementation "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava"
}

tasks.register("resolveConf") {
    it.doFirst {
        configurations.runtimeClasspath.resolve().each { file ->
            println(file)
        }
    }
}
jpenilla commented 9 months ago

This issue can be reproduced by running gradlew resolveConf with the following simple build script:

plugins {
    id 'java'
    id 'java-library'
    id 'org.gradlex.java-ecosystem-capabilities' version "1.3.1"
}

repositories {
    mavenCentral()
    maven {
        name = "neoforge"
        url = "https://maven.neoforged.net/releases"
    }
}

tasks.register("resolveConf") {
    it.doFirst {
        def conf = configurations.detachedConfiguration()
        def addDep = { conf.dependencies.add(dependencies.create it) }

        addDep "net.neoforged:JarJarMetadata:0.4.0"
        addDep "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava"

        conf.resolve().each { file ->
            println(file)
        }
    }
}

I don't think we are to blame for this?

Those two dependencies are added by NeoGradle as they are listed in the userdev config. As mentioned in the original issue I am not sure whether the problem is with NG or NF, but I am quite sure it's something in the neo toolchain, as I don't run into this problem with any other modding platform. For now I can get my project to build by forcing Gradle to ignore the conflicting dependencies:

dependencies {
  components.withModule(GuavaListenableFutureRule.MODULES[0]) {
    // Ad-hoc rule to revert the effect of 'GuavaListenableFutureRule' (NeoForge has broken dependencies)
    allVariants {
      withCapabilities {
        removeCapability(GuavaListenableFutureRule.CAPABILITY_GROUP, GuavaListenableFutureRule.CAPABILITY_NAME)
      }
    }
  }
}

fwiw, this issue also presents itself when consuming NF with archloom, so if the issue is on the NG side then it would likely be with the userdev config creation.

Technici4n commented 9 months ago

The two dependencies are seemingly perfectly legal. Just because the issue happens with NG doesn't mean that NG is to blame. Please check the POM of JarJarMetadata and you'll see that it looks normal.

This issue only happens for detached configurations as far as I can tell, which points to an issue with the java ecosystem plugin IMO.

jpenilla commented 9 months ago

I think this has something to do with the dependency tree getting flattened, see https://github.com/gradlex-org/java-ecosystem-capabilities/blob/main/src/main/java/org/gradlex/javaecosystem/capabilities/rules/GuavaListenableFutureRule.java

The example you gave that "works" actually doesn't, it just doesn't error, see ./gradlew dependencies:

runtimeClasspath - Runtime classpath of source set 'main'.
+--- net.neoforged:JarJarMetadata:0.4.0
|    +--- org.apache.maven:maven-artifact:3.8.1
|    |    +--- org.codehaus.plexus:plexus-utils:3.2.1
|    |    \--- org.apache.commons:commons-lang3:3.8.1
|    +--- com.google.code.gson:gson:2.8.5
|    +--- com.google.guava:guava:31.0.1-jre -> com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
|    +--- com.machinezoo.noexception:noexception:1.7.1
|    |    \--- org.slf4j:slf4j-api:1.7.30
|    \--- org.slf4j:slf4j-api:1.7.30
\--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava

It seems like java-ecosystem-capabilities (reasonably) doesn't expect users to declare an explicit dependency on com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava. The difference between detached and normal configurations is definitely strange though... it might be an issue with Gradle itself, although probably worth raising an issue at java-ecosystem-capabilities for discussion.

As far as a solution to this specific issue though, I think simply filtering com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava out of dependency lists written to userdev & other configs is sensible (since changes to not flatten the tree would be non-trivial). Alternatively applying java-ecosystem-capabilities to NeoForge might also solve this?

Technici4n commented 9 months ago

Hmmm, what is the problem with that runtimeClasspath? It looks normal to me but I might be missing something?

jpenilla commented 9 months ago

The empty workaround jar is being selected over Guava.

marchermans commented 9 months ago

The empty workaround jar is being selected over Guava.

Which can have many reasons. Including but not limited to externally declared module metadata over which we have no control

Technici4n commented 9 months ago

OK. What is happening here is that NG is writing the full resolved contents of its installer configuration into the userdev jsons (you can check that the userdev jar contains this 9999 guava listenablefuture dependency), and then that gets written to a configuration userdev dependency. This one specifically: https://github.com/neoforged/NeoGradle/blob/ae319cfb8e74eb2894717b1fc41b88df9fdd23d9/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/extension/UserDevRuntimeExtension.java#L84

I don't really see a different way of doing this for us. If we do not flatten the configuration then we risk a version mismatch in the run arguments...

IMO this is a problem with the way the capability is declared for com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava. The simple example that I give should just work out of the box, and the fact that it does not points to an issue in the ecosystem plugin IMO. Possibly also an issue with Gradle because it should error given that the guava capability is missing. But this is not really our problem...