Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for dynamic runtime dependencies #52

Merged
merged 2 commits into from
Nov 25, 2023

Conversation

marchermans
Copy link
Contributor

Run specific dependency management

This implements run specific dependency management for the classpath of a run.
In the past this had to happen via a manual modification of the "minecraft_classpath" token, however tokens don't exist anymore as a component that can be configured on a run.
It was as such not possible to add none FML aware libraries to your classpath of a run.
This PR enables this feature again.

Usage:

Direct

dependencies {
    implementation 'some:library:1.2.3'
}

runs {
   testRun {
      dependencies {
         runtime 'some:library:1.2.3'
      }
   }
}

Configuration

configurations {
   libraries {}
   implementation.extendsFrom libraries
}

dependencies {
    libraries 'some:library:1.2.3'
}

runs {
   testRun {
      dependencies {
         runtime configuration(project.configurations.libraries)
      }
   }
}

General description

The dependency handler on a run works very similar to a projects own dependency handler, however it has only one "configuration" available to add dependencies to: "runtime". Additionally it provides a method to use when you want to turn an entire configuration into a runtime dependency.

PR Details

Requirements:

As in F/NG6 you still need to add the library dependency either to the compile or runtime classpath of the sourceset that you use as a mod sourceset.
Beyond that you will now need to tell each run that needs it, what dependency it should load.

API Changes

  • Add support for Configurations on a runs dependency handler.
  • Added a prerun task that needs to be ran so that the minecraft classpath file can be generated for your run.

Implementation changes:

  • Previously the task to generate the relevant minecraft classpath file was configured on a per runtime basis in the userdev, this is now on a per run basis. Given that a run can only have a single userdev artifact this should not cause any issues with respect to task naming.
  • The minecraft classpath file generator now also consumes the runs own dependency runtime handler results.
  • The minecraft classpath file generator is now eagerly configured as it is created on a per run basis.
  • The repo writing task for each userdev artifact is stored for later configurations and eagerly configured runs are added to a delayed configuration list.

@SquidDev
Copy link
Contributor

SquidDev commented Nov 20, 2023

Thank you for working on this! Had a quick test, and hit a couple of issues:

Duplicate writeMinecraftClasspathXyz task

I have a split source-set mod, with both a main (for common code) and client (for client code). However, because runs are re-configured for each source-set, this causes the writeMinecraftClasspath task to be created multiple times.

Error message + stack trace
org.gradle.api.internal.tasks.DefaultTaskContainer$DuplicateTaskException: Cannot add task 'writeMinecraftClasspathClient' as a task with that name already exists.
        at org.gradle.api.internal.tasks.DefaultTaskContainer.failOnDuplicateTask(DefaultTaskContainer.java:257)
        at org.gradle.api.internal.tasks.DefaultTaskContainer.registerTask(DefaultTaskContainer.java:404)
        at org.gradle.api.internal.tasks.DefaultTaskContainer.register(DefaultTaskContainer.java:381)
        at net.neoforged.gradle.userdev.runtime.definition.UserDevRuntimeDefinition.buildRunInterpolationData(UserDevRuntimeDefinition.java:135)
        at net.neoforged.gradle.common.runtime.definition.CommonRuntimeDefinition.configureRun(CommonRuntimeDefinition.java:174)
        at net.neoforged.gradle.common.CommonProjectPlugin.lambda$null$8(CommonProjectPlugin.java:142)
        at net.neoforged.gradle.common.CommonProjectPlugin.lambda$null$9(CommonProjectPlugin.java:142)
        at com.google.common.collect.ImmutableList.forEach(ImmutableList.java:422)
        at net.neoforged.gradle.common.CommonProjectPlugin.lambda$null$10(CommonProjectPlugin.java:139)
        at net.neoforged.gradle.common.CommonProjectPlugin.lambda$applyAfterEvaluate$11(CommonProjectPlugin.java:131)
        at org.gradle.internal.extensibility.ExtensionsStorage$ExtensionHolder.configure(ExtensionsStorage.java:173)
        at org.gradle.internal.extensibility.ExtensionsStorage.configureExtension(ExtensionsStorage.java:64)
        at org.gradle.internal.extensibility.DefaultConvention.configure(DefaultConvention.java:195)
        at net.neoforged.gradle.common.CommonProjectPlugin.applyAfterEvaluate(CommonProjectPlugin.java:131)

ObjectInstantiationException for ConfigurationRunDependencyImpl:

Trying to use the example code in the OP (dependencies { runtime(configuration(project.configurations["minecraftLibrary"])) }), results in the following error:

Error message + stack trace
Caused by: org.gradle.api.reflect.ObjectInstantiationException: Could not create an instance of type net.neoforged.gradle.common.runs.run.ConfigurationRunDependencyImpl.
        at org.gradle.internal.instantiation.generator.DependencyInjectingInstantiator.doCreate(DependencyInjectingInstantiator.java:69)
        at org.gradle.internal.instantiation.generator.DependencyInjectingInstantiator.newInstance(DependencyInjectingInstantiator.java:55)
        at org.gradle.api.internal.model.DefaultObjectFactory.newInstance(DefaultObjectFactory.java:89)
        at net.neoforged.gradle.common.runs.run.DependencyHandlerImpl.configuration(DependencyHandlerImpl.java:77)
        at Build_gradle$5$1$2.execute(build.gradle.kts:54)
        at Build_gradle$5$1$2.execute(build.gradle.kts:53)
        at net.neoforged.gradle.dsl.common.runs.run.Run.dependencies(Run.groovy)
        at net.neoforged.gradle.dsl.common.runs.run.Run.dependencies(Run.groovy)
        at Build_gradle$5$1.execute(build.gradle.kts:53)
        at Build_gradle$5$1.execute(build.gradle.kts:39)
        at org.gradle.configuration.internal.DefaultUserCodeApplicationContext$CurrentApplication$1.execute(DefaultUserCodeApplicationContext.java:123)
        at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction$1.run(DefaultCollectionCallbackActionDecorator.java:110)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
        at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction.execute(DefaultCollectionCallbackActionDecorator.java:107)
        at org.gradle.api.internal.DefaultMutationGuard$1.execute(DefaultMutationGuard.java:45)
        at org.gradle.api.internal.DefaultMutationGuard$1.execute(DefaultMutationGuard.java:45)
        at org.gradle.internal.ImmutableActionSet$SetWithFewActions.execute(ImmutableActionSet.java:285)
        at org.gradle.api.internal.DefaultDomainObjectCollection.doAdd(DefaultDomainObjectCollection.java:262)
        at org.gradle.api.internal.DefaultNamedDomainObjectCollection.doAdd(DefaultNamedDomainObjectCollection.java:113)
        at org.gradle.api.internal.DefaultDomainObjectCollection.add(DefaultDomainObjectCollection.java:256)
        at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.tryCreate(DefaultNamedDomainObjectCollection.java:944)
        ... 261 more
Caused by: org.gradle.internal.instantiation.ClassGenerationException: Could not generate a decorated class for type ConfigurationRunDependencyImpl.
        at org.gradle.internal.instantiation.generator.AbstractClassGenerator.generateUnderLock(AbstractClassGenerator.java:247)
        at org.gradle.cache.internal.DefaultCrossBuildInMemoryCacheFactory$AbstractCrossBuildInMemoryCache.get(DefaultCrossBuildInMemoryCacheFactory.java:130)
        at org.gradle.internal.instantiation.generator.AbstractClassGenerator.generate(AbstractClassGenerator.java:173)
        at org.gradle.internal.instantiation.generator.AsmBackedClassGenerator.generate(AsmBackedClassGenerator.java:118)
        at org.gradle.internal.instantiation.generator.Jsr330ConstructorSelector.lambda$forType$0(Jsr330ConstructorSelector.java:56)
        at org.gradle.cache.Cache.lambda$get$0(Cache.java:31)
        at org.gradle.cache.internal.DefaultCrossBuildInMemoryCacheFactory$AbstractCrossBuildInMemoryCache.get(DefaultCrossBuildInMemoryCacheFactory.java:130)
        at org.gradle.cache.Cache.get(Cache.java:31)
        at org.gradle.internal.instantiation.generator.Jsr330ConstructorSelector.forType(Jsr330ConstructorSelector.java:53)
        at org.gradle.internal.instantiation.generator.Jsr330ConstructorSelector.forParams(Jsr330ConstructorSelector.java:48)
        at org.gradle.internal.instantiation.generator.DependencyInjectingInstantiator.doCreate(DependencyInjectingInstantiator.java:61)
        ... 288 more
Caused by: java.lang.IllegalArgumentException: Cannot have abstract method ProjectAssociatedDSLElement.getProject().
        at org.gradle.internal.instantiation.generator.AbstractClassGenerator.assertNotAbstract(AbstractClassGenerator.java:388)
        at org.gradle.internal.instantiation.generator.AbstractClassGenerator.inspectType(AbstractClassGenerator.java:314)
        at org.gradle.internal.instantiation.generator.AbstractClassGenerator.generateUnderLock(AbstractClassGenerator.java:216)
        ... 298 more

@SquidDev
Copy link
Contributor

Applying following patch fixed the two issues, but not sure if it's the best fix:

diff --git a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java
index 943458c5..a9157569 100644
--- a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java
+++ b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java
@@ -136,14 +136,12 @@ public class CommonProjectPlugin implements Plugin<Project> {
                 
                 if (run.getConfigureFromDependencies().get()) {
                     final RunImpl runImpl = (RunImpl) run;
-                    runImpl.getModSources().get().forEach(sourceSet -> {
-                        try {
-                            final Optional<CommonRuntimeDefinition<?>> definition = TaskDependencyUtils.findRuntimeDefinition(project, sourceSet);
-                            definition.ifPresent(def -> def.configureRun(runImpl));
-                        } catch (MultipleDefinitionsFoundException e) {
-                            throw new RuntimeException("Failed to configure run: " + run.getName() + " there are multiple runtime definitions found for the source set: " + sourceSet.getName(), e);
-                        }
-                    });
+                    try {
+                        final Optional<CommonRuntimeDefinition<?>> definition = TaskDependencyUtils.findRuntimeDefinition(project, runImpl.getModSources().get());
+                        definition.ifPresent(def -> def.configureRun(runImpl));
+                    } catch (MultipleDefinitionsFoundException e) {
+                        throw new RuntimeException("Failed to configure run " + run.getName() + ": multiple runtime definitions were found.", e);
+                    }
                 }
             }
         }));
diff --git a/common/src/main/java/net/neoforged/gradle/common/util/TaskDependencyUtils.java b/common/src/main/java/net/neoforged/gradle/common/util/TaskDependencyUtils.java
index baeeba54..89aacc0c 100644
--- a/common/src/main/java/net/neoforged/gradle/common/util/TaskDependencyUtils.java
+++ b/common/src/main/java/net/neoforged/gradle/common/util/TaskDependencyUtils.java
@@ -91,8 +91,8 @@ public final class TaskDependencyUtils {
     public static CommonRuntimeDefinition<?> extractRuntimeDefinition(Project project, Collection<SourceSet> sourceSets) throws MultipleDefinitionsFoundException, NoDefinitionsFoundException {
         return validateAndUnwrapDefinitions(project, "source sets", sourceSets.stream().map(SourceSet::getName).collect(Collectors.joining(", ", "[", "]")), findRuntimes(project, sourceSets));
     }
-    
-    public static Optional<CommonRuntimeDefinition<?>> findRuntimeDefinition(Project project, SourceSet sourceSet) throws MultipleDefinitionsFoundException {
+
+    public static Optional<CommonRuntimeDefinition<?>> findRuntimeDefinition(Project project, Collection<SourceSet> sourceSet) throws MultipleDefinitionsFoundException {
         return unwrapDefinitions(project, findRuntimes(project, sourceSet));
     }
 
diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunDependency.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunDependency.groovy
index b5be7d84..a771eb0a 100644
--- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunDependency.groovy
+++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunDependency.groovy
@@ -1,7 +1,7 @@
 package net.neoforged.gradle.dsl.common.runs.run
 
 import groovy.transform.CompileStatic
-import net.minecraftforge.gdi.BaseDSLElement
+import net.minecraftforge.gdi.ConfigurableDSLElement
 import net.minecraftforge.gdi.annotations.DSLProperty
 import org.gradle.api.file.ConfigurableFileCollection
 import org.gradle.api.provider.Property
@@ -11,7 +11,7 @@ import org.gradle.api.tasks.PathSensitive
 import org.gradle.api.tasks.PathSensitivity
 
 @CompileStatic
-interface RunDependency extends BaseDSLElement<RunDependency> {
+interface RunDependency extends ConfigurableDSLElement<RunDependency> {
 
     @InputFiles
     @PathSensitive(PathSensitivity.NONE)

@marchermans
Copy link
Contributor Author

@SquidDev Can you check if it is fixed with the commit I just pushed?
I would like to merge it, and it is now working for me.

1 similar comment
@marchermans
Copy link
Contributor Author

@SquidDev Can you check if it is fixed with the commit I just pushed?
I would like to merge it, and it is now working for me.

@SquidDev
Copy link
Contributor

Yep, that also works, thank you!

Is there any reason RunDependency needs to implement BaseDSLElement rather than ConfigurableDSLElement? NG never ends up needing to call getProject() on it,

@marchermans marchermans marked this pull request as ready for review November 24, 2023 01:39
@sciwhiz12 sciwhiz12 added the enhancement New feature or request label Nov 24, 2023
@marchermans
Copy link
Contributor Author

Yep, that also works, thank you!

Is there any reason RunDependency needs to implement BaseDSLElement rather than ConfigurableDSLElement? NG never ends up needing to call getProject() on it,

There are weird circumstances where if you do:

something {
   blah project.something.or.another
}

It can fail to find the project, because of the way the configuration mechanic works with closures.

In practice everything exposed in the DSL needs to be a BaseDSLElement so that at least the current project is always accessible to the user.

@marchermans marchermans merged commit 0421559 into NG_7.0 Nov 25, 2023
3 checks passed
@BenDol
Copy link

BenDol commented Nov 26, 2023

I am going to add feedback here, but if there is a better place for it I will move it.

I have tried to add an external library as described here on NF 7.0.50 and I get the following errors:

> Could not find run type testRun. Available run types: [client, data, gameTestServer, server]

Have I misconfigured it? I have followed the steps provided in this PR.

@marchermans
Copy link
Contributor Author

I am going to add feedback here, but if there is a better place for it I will move it.

I have tried to add an external library as described here on NF 7.0.50 and I get the following errors:

> Could not find run type testRun. Available run types: [client, data, gameTestServer, server]

Have I misconfigured it? I have followed the steps provided in this PR.

Please contact support on our discord

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants