From 30c8f5f1de635e16b8901318ab5f864d98a1038d Mon Sep 17 00:00:00 2001 From: Marc Hermans Date: Thu, 18 Jul 2024 16:04:19 +0200 Subject: [PATCH] [Fix]: Add support for lazily modified configurations that use DependencyCollectors (#228) --- .../gradle/common/CommonProjectPlugin.java | 7 +- .../replacement/ReplacementLogic.java | 185 ++++++++++++----- .../extensions/repository/IvyRepository.java | 5 +- .../runs/ide/IdeRunIntegrationManager.java | 15 +- .../gradle/common/runs/run/RunImpl.java | 92 ++++----- .../definition/CommonRuntimeDefinition.java | 8 +- .../tasks/SourceAccessTransformer.java | 14 +- .../gradle/common/util/run/RunsUtil.java | 122 ++++++------ .../replacement/ReplacementLogicTest.java | 9 +- .../replacement/ReplacementAware.groovy | 9 +- .../gradle/dsl/common/runs/run/Run.groovy | 18 +- .../dsl/common/runs/type/RunType.groovy | 9 - .../extensions/NeoFormRuntimeExtension.java | 3 +- .../extensions/DynamicProjectExtension.java | 116 ++++++----- .../gradle/userdev/ConfigurationTests.groovy | 78 ++++++++ .../neoforged/gradle/userdev/RunTests.groovy | 8 +- .../dependency/UserDevReplacementResult.java | 30 ++- .../definition/UserDevRuntimeDefinition.java | 3 +- .../extension/UserDevRuntimeExtension.java | 16 -- .../gradle/util/TransformerUtils.java | 187 ++++++++++++++++++ .../vanilla/AccessTransformerTests.groovy | 5 + 21 files changed, 672 insertions(+), 267 deletions(-) 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 813e0a7ba..004400c2b 100644 --- a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java +++ b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java @@ -138,6 +138,11 @@ public void apply(Project project) { runs ); + runs.configureEach(run -> { + RunsUtil.configureModClasses(run); + RunsUtil.createTasks(project, run); + }); + setupAccessTransformerConfigurations(project, accessTransformers); IdeRunIntegrationManager.getInstance().setup(project); @@ -351,7 +356,7 @@ private void setupAccessTransformerConfigurations(Project project, AccessTransfo private void applyAfterEvaluate(final Project project) { //We now eagerly get all runs and configure them. final NamedDomainObjectContainer runs = (NamedDomainObjectContainer) project.getExtensions().getByName(RunsConstants.Extensions.RUNS); - runs.forEach(run -> { + runs.configureEach(run -> { if (run instanceof RunImpl) { run.configure(); diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java b/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java index 910f73158..f9c1c8e7a 100644 --- a/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java +++ b/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java @@ -17,11 +17,7 @@ import org.gradle.api.GradleException; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.*; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; @@ -30,8 +26,10 @@ import org.jetbrains.annotations.VisibleForTesting; import javax.inject.Inject; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; /** * Defines the implementation of the @{link DependencyReplacement} extension. @@ -43,6 +41,7 @@ public abstract class ReplacementLogic implements ConfigurableDSLElement> dependencyReplacementInformation = HashBasedTable.create(); + private final Table repositoryEntries = HashBasedTable.create(); private final Table originalDependencyLookup = HashBasedTable.create(); private final NamedDomainObjectContainer dependencyReplacementHandlers; @@ -60,21 +59,36 @@ public ReplacementLogic(Project project) { public void handleConfiguration(Configuration configuration) { //TODO: Figure out if there is any way to do this lazily. //TODO: Configure each runs in an immutable context, so we can't add a listener to the dependencies. - configuration.getDependencies().whenObjectAdded(dependency -> { + configuration.getDependencies().configureEach(dependency -> { //We need to check if our configuration is unhandled, we can only do this here and not in the register because of way we register unhandled configurations after their creation: - //TODO: Find a better way to handle this. if (ConfigurationUtils.isUnhandledConfiguration(configuration)) { //We don't handle this configuration. return; } //We only support module based dependencies. - if (dependency instanceof ModuleDependency) { - final ModuleDependency moduleDependency = (ModuleDependency) dependency; + if (dependency instanceof ModuleDependency moduleDependency) { //Try replacing the dependency. - handleDependency(configuration, moduleDependency); + onDependencyAdded(configuration, moduleDependency); } }); + + configuration.withDependencies(dependencyContainer -> { + //We need to check if our configuration is unhandled, we can only do this here and not in the register because of way we register unhandled configurations after their creation: + if (ConfigurationUtils.isUnhandledConfiguration(configuration)) { + //We don't handle this configuration. + return; + } + + final Set currentDependencies = new HashSet<>(dependencyContainer); + currentDependencies.forEach(dependency -> { + //We only support module based dependencies. + if (dependency instanceof ModuleDependency moduleDependency) { + //Try replacing the dependency. + handleDependency(configuration, dependencyContainer, moduleDependency); + } + }); + }); } @Override @@ -105,14 +119,13 @@ public Dependency optionallyConvertBackToOriginal(@NotNull final Dependency depe } /** - * Handle the dependency replacement for the given dependency. + * Determine the replacement result for the given dependency. * * @param configuration The configuration that the dependency is being added to. * @param dependency The dependency that is being added. - * @implNote Currently short circuits on the first replacement handler that returns a replacement, might want to change this in the future. + * @return The replacement result for the given dependency. */ - @VisibleForTesting - void handleDependency(final Configuration configuration, final ModuleDependency dependency) { + private Optional determineReplacementResult(final Configuration configuration, final ModuleDependency dependency) { Optional candidate; final Repository repository = project.getExtensions().getByType(Repository.class); //First check if we have an already dynamic dependency. @@ -126,7 +139,7 @@ else if (dependencyReplacementInformation.contains(dependency, configuration)) { //If so, use the cached result. candidate = dependencyReplacementInformation.get(dependency, configuration); //Validate that we have a sane state. - if (candidate == null || !candidate.isPresent()) { + if (candidate == null || candidate.isEmpty()) { //State is insane, invalidate the cache. candidate = Optional.empty(); dependencyReplacementInformation.remove(dependency, configuration); @@ -162,11 +175,60 @@ else if (dependencyReplacementInformation.contains(dependency, configuration)) { } } + return candidate; + } + + /** + * Handle the dependency replacement for the given dependency. + * + * @param configuration The configuration that the dependency is being added to. + * @param dependency The dependency that is being added. + * @implNote Currently short circuits on the first replacement handler that returns a replacement, might want to change this in the future. + */ + private void onDependencyAdded(final Configuration configuration, final ModuleDependency dependency) { + //Check if we are going to replace this. + final Optional candidate = determineReplacementResult(configuration, dependency); + + //If so handle the prospective replacement data. + if (candidate.isPresent()) { + final ReplacementResult result = candidate.get(); + handleProspectiveDependencyReplacement(dependency, result); + } + } + + /** + * Handle the dependency replacement for the given dependency. + * + * @param configuration The configuration that the dependency is being added to. + * @param dependency The dependency that is being added. + * @implNote Currently short circuits on the first replacement handler that returns a replacement, might want to change this in the future. + */ + private void handleDependency(final Configuration configuration, final DependencySet dependencyContainer, final ModuleDependency dependency) { + final Optional candidate = determineReplacementResult(configuration, dependency); + //Check if we have a candidate if(candidate.isPresent()) { //We have a candidate, handle the replacement. final ReplacementResult result = candidate.get(); - handleDependencyReplacement(configuration, dependency, result); + handleDependencyReplacement(configuration, dependencyContainer, dependency, result); + } + } + + /** + * Method that ensure that the data needed for future replacement is stored and ready. + * Creates tasks, and ensures that dependency data is present. + * + * @param dependency The dependency that is being replaced. + * @param result The replacement result from one of the handlers. + */ + private void handleProspectiveDependencyReplacement(final ModuleDependency dependency, final ReplacementResult result) { + //Create a new dependency in our dummy repo + final Entry newRepoEntry = createDummyDependency(dependency, result); + + registerTasks(dependency, result, newRepoEntry); + + if (result instanceof ReplacementAware replacementAware) { + replacementAware.onTargetDependencyAdded(); } } @@ -179,17 +241,55 @@ else if (dependencyReplacementInformation.contains(dependency, configuration)) { * @implNote This method is responsible for removing the dependency from the configuration and adding the dependency provider task to the configuration. * @implNote Currently the gradle importer is always used, the ide replacer is however only invoked when an IDE is detected. */ - @VisibleForTesting - void handleDependencyReplacement(Configuration configuration, Dependency dependency, ReplacementResult result) { + private void handleDependencyReplacement(Configuration configuration, DependencySet configuredSet, Dependency dependency, ReplacementResult result) { //Remove the initial dependency. - configuration.getDependencies().remove(dependency); + configuredSet.remove(dependency); + + //Create a new dependency in our dummy repo + final Entry newRepoEntry = createDummyDependency(dependency, result); + + //Create and register the tasks that build the replacement dependency. + final TaskProvider rawTask = registerTasks(dependency, result, newRepoEntry); //Find the configurations that the dependency should be replaced in. final List targetConfigurations = ConfigurationUtils.findReplacementConfigurations(project, configuration); - //Create a new dependency in our dummy repo - final Entry newRepoEntry = createDummyDependency(dependency, result); + //For each configuration that we target we now need to add the new dependencies to. + for (Configuration targetConfiguration : targetConfigurations) { + try { + //Create a dependency from the tasks that copies the raw jar to the repository. + //The sources jar is not needed here. + final Provider replacedFiles = createDependencyFromTask(rawTask); + + //Add the new dependency to the target configuration. + final DependencySet targetDependencies = targetConfiguration == configuration ? + configuredSet : + targetConfiguration.getDependencies(); + + final Provider replacedDependencies = replacedFiles + .map(files -> project.getDependencies().create(files)); + + //Add the new dependency to the target configuration. + targetDependencies.addLater(replacedDependencies); + targetDependencies.add(newRepoEntry.getDependency()); + + //Keep track of the original dependency, so we can convert back if needed. + originalDependencyLookup.put(newRepoEntry.getDependency(), targetConfiguration, dependency); + } catch (Exception exception) { + throw new GradleException("Failed to add the replaced dependency to the configuration " + targetConfiguration.getName() + ": " + exception.getMessage(), exception); + } + } + } + /** + * Method invoked to register the tasks needed to replace a given dependency using the given replacement result. + * + * @param dependency The dependency that is being replaced. + * @param result The replacement result from one of the handlers. + * @param newRepoEntry The new repository entry that the dependency is being replaced with. + * @return The task that selects the raw artifact from the dependency and puts it in the Ivy repository. + */ + private TaskProvider registerTasks(Dependency dependency, ReplacementResult result, Entry newRepoEntry) { final boolean requiresSourcesJar = result.getSourcesJar() != null; //Determine the task names for the tasks that copy the artifacts to the repository. @@ -205,8 +305,7 @@ void handleDependencyReplacement(Configuration configuration, Dependency depende final TaskProvider sourceTask = requiresSourcesJar ? createOrLookupSourcesTask(dependency, result, sourceArtifactSelectorName, newRepoEntry) : null; //If the result is replacement aware we need to notify it. - if (result instanceof ReplacementAware) { - final ReplacementAware replacementAware = (ReplacementAware) result; + if (result instanceof ReplacementAware replacementAware) { replacementAware.onTasksCreated( rawTask, sourceTask @@ -231,22 +330,7 @@ void handleDependencyReplacement(Configuration configuration, Dependency depende result.getAdditionalIdePostSyncTasks().forEach(ideManagementExtension::registerTaskToRun); } } - - //For each configuration that we target we now need to add the new dependencies to. - for (Configuration targetConfiguration : targetConfigurations) { - //Create a dependency from the tasks that copies the raw jar to the repository. - //The sources jar is not needed here. - final Provider replacedDependency = createDependencyFromTask(rawTask); - - //Add the new dependency to the target configuration. - project.getDependencies().addProvider(targetConfiguration.getName(), replacedDependency); - - //Add the new dependency to the target configuration. - targetConfiguration.getDependencies().add(newRepoEntry.getDependency()); - - //Keep track of the original dependency, so we can convert back if needed. - originalDependencyLookup.put(newRepoEntry.getDependency(), targetConfiguration, dependency); - } + return rawTask; } /** @@ -271,7 +355,9 @@ private TaskProvider createOrLookupSourcesTask(Dependency artifactFromOutput.setGroup("neogradle/dependencies"); artifactFromOutput.setDescription(String.format("Selects the source artifact from the %s dependency and puts it in the Ivy repository", dependency)); - artifactFromOutput.getInput().set(result.getSourcesJar().flatMap(WithOutput::getOutput)); + if (result.getSourcesJar() != null) { + artifactFromOutput.getInput().set(result.getSourcesJar().flatMap(WithOutput::getOutput)); + } artifactFromOutput.getOutput().set(repository.createOutputFor(newRepoEntry, Repository.Variant.SOURCES_CLASSIFIER)); artifactFromOutput.dependsOn(result.getSourcesJar()); }); @@ -315,22 +401,26 @@ private TaskProvider createOrLookupRawTask(Dependency depe @VisibleForTesting Entry createDummyDependency(final Dependency dependency, final ReplacementResult result) { // Check if the dependency is an external module dependency. - if (!(dependency instanceof ExternalModuleDependency)) { + if (!(dependency instanceof ExternalModuleDependency externalModuleDependency)) { // Only ExternalModuleDependency is supported for dependency replacement. throw new IllegalStateException("Only ExternalModuleDependency is supported for dependency replacement"); } + //Check if we already have a repository entry for this dependency. + if (repositoryEntries.contains(dependency, result)) { + return repositoryEntries.get(dependency, result); + } + // Create a new repository entry for the dependency, using the replacement result. - ExternalModuleDependency externalModuleDependency = (ExternalModuleDependency) dependency; //Check if the result is replacement aware. - if (result instanceof ReplacementAware) { - final ReplacementAware replacementAware = (ReplacementAware) result; + if (result instanceof ReplacementAware replacementAware) { //Let it alter the dependency, this allows support for version ranges, and strict versioning. externalModuleDependency = replacementAware.getReplacementDependency(externalModuleDependency); } + //Construct a new entry final Repository extension = project.getExtensions().getByType(Repository.class); - return extension.withEntry( + final Entry entry = extension.withEntry( project.getObjects().newInstance( RepoEntryDefinition.class, project, @@ -339,6 +429,11 @@ Entry createDummyDependency(final Dependency dependency, final ReplacementResult result.getSourcesJar() != null ) ); + + //Store it so that we do not rebuild it. + repositoryEntries.put(dependency, result, entry); + + return entry; } public Provider createDependencyFromTask(TaskProvider task) { diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/repository/IvyRepository.java b/common/src/main/java/net/neoforged/gradle/common/extensions/repository/IvyRepository.java index 5783a1507..a0d2d8d0a 100644 --- a/common/src/main/java/net/neoforged/gradle/common/extensions/repository/IvyRepository.java +++ b/common/src/main/java/net/neoforged/gradle/common/extensions/repository/IvyRepository.java @@ -168,7 +168,10 @@ public Set getEntries() { } private void create(Entry entry) { - this.entries.add(entry); + if (!this.entries.add(entry)) { + return; + } + write(entry); } diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/ide/IdeRunIntegrationManager.java b/common/src/main/java/net/neoforged/gradle/common/runs/ide/IdeRunIntegrationManager.java index 434f568d2..c9d850608 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runs/ide/IdeRunIntegrationManager.java +++ b/common/src/main/java/net/neoforged/gradle/common/runs/ide/IdeRunIntegrationManager.java @@ -287,16 +287,11 @@ private TaskProvider createIdeBeforeRunTask(Project project, String name, Run final TaskProvider ideBeforeRunTask = project.getTasks().register(CommonRuntimeUtils.buildTaskName("ideBeforeRun", name), task -> { RunsUtil.addRunSourcesDependenciesToTask(task, run); }); - - if (!runImpl.getTaskDependencies().isEmpty()) { - ideBeforeRunTask.configure(task -> { - runImpl.getTaskDependencies().forEach(dep -> { - //noinspection Convert2MethodRef Creates a compiler error regarding incompatible types. - task.dependsOn(dep); - }); - }); - } - + + ideBeforeRunTask.configure(task -> { + task.getDependsOn().add(runImpl.getDependsOn()); + }); + return ideBeforeRunTask; } diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunImpl.java index 919d35db4..2e64d622d 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunImpl.java +++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunImpl.java @@ -4,11 +4,11 @@ import com.google.common.collect.Sets; import net.minecraftforge.gdi.ConfigurableDSLElement; import net.neoforged.gradle.common.util.constants.RunsConstants; -import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems; import net.neoforged.gradle.dsl.common.runs.run.Run; import net.neoforged.gradle.dsl.common.runs.run.RunSourceSets; import net.neoforged.gradle.dsl.common.runs.type.RunType; import net.neoforged.gradle.util.StringCapitalizationUtils; +import net.neoforged.gradle.util.TransformerUtils; import org.gradle.api.GradleException; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; @@ -30,7 +30,6 @@ public abstract class RunImpl implements ConfigurableDSLElement, Run { private final Project project; private final String name; private final ListProperty runTypes; - private final Set> dependencies = Sets.newHashSet(); private final RunSourceSets modSources; private final RunSourceSets unitTestSources; @@ -160,32 +159,57 @@ public void overrideSystemProperties(MapProperty systemPropertie this.systemProperties = systemProperties; } - @Internal - public Set> getTaskDependencies() { - return ImmutableSet.copyOf(this.dependencies); - } - @Override public final void configure() { if (getConfigureFromTypeWithName().get()) { - configureInternally(getRunTypeByName(name)); + runTypes.add(getRunTypeByName(name)); } - for (RunType runType : runTypes.get()) { - configureInternally(runType); - } + getEnvironmentVariables().putAll(runTypes.flatMap(TransformerUtils.combineAllMaps( + getProject(), + String.class, + String.class, + RunType::getEnvironmentVariables + ))); + getMainClass().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getMainClass))); + getProgramArguments().addAll(runTypes.flatMap(TransformerUtils.combineAllLists( + getProject(), + String.class, + RunType::getArguments + ))); + getJvmArguments().addAll(runTypes.flatMap(TransformerUtils.combineAllLists( + getProject(), + String.class, + RunType::getJvmArguments + ))); + getIsSingleInstance().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsSingleInstance))); + getSystemProperties().putAll(runTypes.flatMap(TransformerUtils.combineAllMaps( + getProject(), + String.class, + String.class, + RunType::getSystemProperties + ))); + getIsClient().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsClient))); + getIsServer().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsServer))); + getIsDataGenerator().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsDataGenerator))); + getIsGameTest().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsGameTest))); + getIsJUnit().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsJUnit))); + getClasspath().from(runTypes.map(TransformerUtils.combineFileCollections( + getProject(), + RunType::getClasspath + ))); } @Override public final void configure(final @NotNull String name) { getConfigureFromTypeWithName().set(false); // Don't re-configure - runTypes.add(project.provider(() -> getRunTypeByName(name))); + runTypes.add(getRunTypeByName(name)); } @Override public final void configure(final @NotNull RunType runType) { getConfigureFromTypeWithName().set(false); // Don't re-configure - this.runTypes.add(runType); + this.runTypes.add(project.provider(() -> runType)); } @Override @@ -194,32 +218,6 @@ public void configure(@NotNull Provider typeProvider) { this.runTypes.add(typeProvider); } - @SafeVarargs - @Override - public final void dependsOn(TaskProvider... tasks) { - this.dependencies.addAll(Arrays.asList(tasks)); - } - - public void configureInternally(final @NotNull RunType spec) { - project.getLogger().debug("Configuring run {} with run type {}", name, spec.getName()); - getEnvironmentVariables().putAll(spec.getEnvironmentVariables()); - getMainClass().convention(spec.getMainClass()); - getProgramArguments().addAll(spec.getArguments()); - getJvmArguments().addAll(spec.getJvmArguments()); - getIsSingleInstance().convention(spec.getIsSingleInstance()); - getSystemProperties().putAll(spec.getSystemProperties()); - getIsClient().convention(spec.getIsClient()); - getIsServer().convention(spec.getIsServer()); - getIsDataGenerator().convention(spec.getIsDataGenerator()); - getIsGameTest().convention(spec.getIsGameTest()); - getIsJUnit().convention(spec.getIsJUnit()); - getClasspath().from(spec.getClasspath()); - - if (spec.getRunAdapter().isPresent()) { - spec.getRunAdapter().get().adapt(this); - } - } - @NotNull public List realiseJvmArguments() { final List args = new ArrayList<>(getJvmArguments().get()); @@ -237,15 +235,17 @@ public List realiseJvmArguments() { } @SuppressWarnings("unchecked") - private RunType getRunTypeByName(String name) { + private Provider getRunTypeByName(String name) { NamedDomainObjectContainer runTypes = (NamedDomainObjectContainer) project.getExtensions() .getByName(RunsConstants.Extensions.RUN_TYPES); - if (runTypes.getNames().contains(name)) { - return runTypes.getByName(name); - } else { - throw new GradleException("Could not find run type " + name + ". Available run types: " + - runTypes.getNames()); - } + return project.provider(() -> { + if (runTypes.getNames().contains(name)) { + return runTypes.getByName(name); + } else { + throw new GradleException("Could not find run type " + name + ". Available run types: " + + runTypes.getNames()); + } + }); } } diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java b/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java index 52b079f6d..996c0a495 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java @@ -8,23 +8,18 @@ import net.neoforged.gradle.common.util.VersionJson; import net.neoforged.gradle.common.util.run.RunsUtil; import net.neoforged.gradle.dsl.common.runtime.definition.Definition; -import net.neoforged.gradle.dsl.common.runtime.naming.NamingChannel; import net.neoforged.gradle.dsl.common.runtime.tasks.Runtime; import net.neoforged.gradle.dsl.common.tasks.ArtifactProvider; import net.neoforged.gradle.dsl.common.tasks.WithOutput; import net.neoforged.gradle.dsl.common.util.CommonRuntimeUtils; -import net.neoforged.gradle.dsl.common.util.ConfigurationUtils; import net.neoforged.gradle.dsl.common.util.GameArtifact; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.Dependency; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.io.File; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -181,7 +176,8 @@ public void configureRun(RunImpl run) { run.overrideSystemProperties(interpolate(run.getSystemProperties(), workingInterpolationData)); if (run.getIsClient().get() || run.getIsDataGenerator().get()) { - run.dependsOn(getAssets(), getNatives()); + run.getDependsOn().add(getAssets()); + run.getDependsOn().add(getNatives()); } } diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/SourceAccessTransformer.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/SourceAccessTransformer.java index f9eb6d6f4..166a7a741 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/SourceAccessTransformer.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/SourceAccessTransformer.java @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import net.neoforged.gradle.common.util.ToolUtilities; import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems; +import org.apache.commons.io.FileUtils; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.plugins.JavaPluginExtension; @@ -55,6 +56,18 @@ public SourceAccessTransformer() { getLogLevel().set(LogLevel.DISABLED); } + @Override + public File doExecute() throws Throwable { + //We need a separate check here that skips the execute call if there are no transformers. + if (getTransformers().isEmpty()) { + final File output = ensureFileWorkspaceReady(getOutput()); + FileUtils.copyFile(getInputFile().get().getAsFile(), output); + return output; + } + + return super.doExecute(); + } + @InputFile @PathSensitive(PathSensitivity.NONE) public abstract RegularFileProperty getInputFile(); @@ -69,7 +82,6 @@ public SourceAccessTransformer() { public abstract ConfigurableFileCollection getClasspath(); @InputFiles - @SkipWhenEmpty @PathSensitive(PathSensitivity.NONE) public abstract ConfigurableFileCollection getTransformers(); } diff --git a/common/src/main/java/net/neoforged/gradle/common/util/run/RunsUtil.java b/common/src/main/java/net/neoforged/gradle/common/util/run/RunsUtil.java index 563d067c2..1b2aefad1 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/run/RunsUtil.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/run/RunsUtil.java @@ -4,6 +4,7 @@ import com.google.common.collect.Multimap; import net.neoforged.gradle.common.runs.run.RunImpl; import net.neoforged.gradle.common.util.ClasspathUtils; +import net.neoforged.gradle.common.util.ProjectUtils; import net.neoforged.gradle.common.util.SourceSetUtils; import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems; import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.Runs; @@ -51,64 +52,69 @@ public static String createTaskName(final String prefix, final Run run) { return createTaskName(prefix, run.getName()); } + public static void createTasks(Project project, Run run) { + if (!run.getIsJUnit().get()) { + //Create run exec tasks for all non-unit test runs + project.getTasks().register(createTaskName(run.getName()), JavaExec.class, runExec -> { + runExec.setDescription("Runs the " + run.getName() + " run."); + runExec.setGroup("NeoGradle/Runs"); + + JavaToolchainService service = project.getExtensions().getByType(JavaToolchainService.class); + runExec.getJavaLauncher().convention(service.launcherFor(project.getExtensions().getByType(JavaPluginExtension.class).getToolchain())); + + final File workingDir = run.getWorkingDirectory().get().getAsFile(); + if (!workingDir.exists()) { + workingDir.mkdirs(); + } + + runExec.getMainClass().convention(run.getMainClass()); + runExec.setWorkingDir(workingDir); + runExec.args(run.getProgramArguments().get()); + runExec.jvmArgs(run.getJvmArguments().get()); + runExec.systemProperties(run.getSystemProperties().get()); + runExec.environment(run.getEnvironmentVariables().get()); + run.getModSources().all().get().values().stream() + .map(SourceSet::getRuntimeClasspath) + .forEach(runExec::classpath); + runExec.classpath(run.getDependencies().get().getRuntimeConfiguration()); + runExec.classpath(run.getClasspath()); + + updateRunExecClasspathBasedOnPrimaryTask(runExec, run); + + addRunSourcesDependenciesToTask(runExec, run); + + if (run instanceof RunImpl runImpl) { + runExec.getDependsOn().add(runImpl.getDependsOn()); + } + }); + } else { + createOrReuseTestTask(project, run.getName(), run); + } + } + + public static void configureModClasses(Run run) { + //Create a combined provider for the mod and unit test sources + Provider> sourceSets = run.getModSources().all().zip( + run.getUnitTestSources().all(), + (modSources, unitTestSources) -> { + if (!run.getIsJUnit().get()) + //No Unit test sources for non-unit test runs + return modSources; + + //Combine mod sources with unit test sources + final HashMultimap combined = HashMultimap.create(modSources); + combined.putAll(unitTestSources); + return combined; + }); + //Set the mod classes environment variable + run.getEnvironmentVariables().put("MOD_CLASSES", buildGradleModClasses(sourceSets)); + } + public static Run create(final Project project, final String name) { final RunImpl run = project.getObjects().newInstance(RunImpl.class, project, name); - //Configure mod sources env vars - project.afterEvaluate(evaluatedProject -> { - //Create a combined provider for the mod and unit test sources - Provider> sourceSets = run.getModSources().all().zip( - run.getUnitTestSources().all(), - (modSources, unitTestSources) -> { - if (!run.getIsJUnit().get()) - //No Unit test sources for non-unit test runs - return modSources; - - //Combine mod sources with unit test sources - final HashMultimap combined = HashMultimap.create(modSources); - combined.putAll(unitTestSources); - return combined; - }); - //Set the mod classes environment variable - run.getEnvironmentVariables().put("MOD_CLASSES", buildGradleModClasses(sourceSets)); - }); + ProjectUtils.afterEvaluate(project, () -> { - project.afterEvaluate(evaluatedProject -> { - if (!run.getIsJUnit().get()) { - //Create run exec tasks for all non-unit test runs - project.getTasks().register(createTaskName(name), JavaExec.class, runExec -> { - runExec.setDescription("Runs the " + name + " run."); - runExec.setGroup("NeoGradle/Runs"); - - JavaToolchainService service = evaluatedProject.getExtensions().getByType(JavaToolchainService.class); - runExec.getJavaLauncher().convention(service.launcherFor(evaluatedProject.getExtensions().getByType(JavaPluginExtension.class).getToolchain())); - - final File workingDir = run.getWorkingDirectory().get().getAsFile(); - if (!workingDir.exists()) { - workingDir.mkdirs(); - } - - runExec.getMainClass().convention(run.getMainClass()); - runExec.setWorkingDir(workingDir); - runExec.args(run.getProgramArguments().get()); - runExec.jvmArgs(run.getJvmArguments().get()); - runExec.systemProperties(run.getSystemProperties().get()); - runExec.environment(run.getEnvironmentVariables().get()); - run.getModSources().all().get().values().stream() - .map(SourceSet::getRuntimeClasspath) - .forEach(runExec::classpath); - runExec.classpath(run.getDependencies().get().getRuntimeConfiguration()); - runExec.classpath(run.getClasspath()); - - updateRunExecClasspathBasedOnPrimaryTask(runExec, run); - - addRunSourcesDependenciesToTask(runExec, run); - - run.getTaskDependencies().forEach(runExec::dependsOn); - }); - } else { - createOrReuseTestTask(project, name, run); - } }); return run; @@ -136,7 +142,7 @@ private static void updateRunExecClasspathBasedOnPrimaryTask(final JavaExec runE } } - private static void createOrReuseTestTask(Project project, String name, RunImpl run) { + private static void createOrReuseTestTask(Project project, String name, Run run) { final Set currentProjectsModSources = run.getModSources().all().get().values() .stream() .filter(sourceSet -> SourceSetUtils.getProject(sourceSet).equals(project)) @@ -166,17 +172,17 @@ private static void createOrReuseTestTask(Project project, String name, RunImpl createNewTestTask(project, name, run); } - private static void createNewTestTask(Project project, String name, RunImpl run) { + private static void createNewTestTask(Project project, String name, Run run) { //Create a test task for unit tests TaskProvider newTestTask = project.getTasks().register(createTaskName("test", name), Test.class); configureTestTask(project, newTestTask, run); project.getTasks().named("check", check -> check.dependsOn(newTestTask)); } - private static void configureTestTask(Project project, TaskProvider testTaskProvider, RunImpl run) { + private static void configureTestTask(Project project, TaskProvider testTaskProvider, Run run) { testTaskProvider.configure(testTask -> { addRunSourcesDependenciesToTask(testTask, run); - run.getTaskDependencies().forEach(testTask::dependsOn); + testTask.getDependsOn().add(run.getDependsOn()); testTask.setWorkingDir(run.getWorkingDirectory().get()); testTask.getSystemProperties().putAll(run.getSystemProperties().get()); diff --git a/common/src/test/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogicTest.java b/common/src/test/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogicTest.java index 937e3af7f..39225441f 100644 --- a/common/src/test/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogicTest.java +++ b/common/src/test/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogicTest.java @@ -3,6 +3,7 @@ import net.neoforged.gradle.common.extensions.IdeManagementExtension; import net.neoforged.gradle.common.tasks.DependencyGenerationTask; import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.ReplacementResult; +import net.neoforged.gradle.dsl.common.extensions.repository.Entry; import net.neoforged.gradle.dsl.common.extensions.repository.Repository; import org.gradle.api.Action; import org.gradle.api.Project; @@ -62,7 +63,7 @@ public void aCallbackHandlerIsAddedToEachConfigurationWhenOneIsConfigured() { final ReplacementLogic dependencyReplacementsExtension = new SystemUnderTest(project); - verify(dependencySet).whenObjectAdded(ArgumentMatchers.>any()); + verify(configuration).withDependencies(any()); } @Test @@ -121,9 +122,11 @@ public void callingHandleConfigurationRegistersDependencyMonitor() { return null; }).when(configurationContainer).configureEach(any()); + when(repository.withEntry(any())).thenReturn(mock(Entry.class)); + final ReplacementLogic dependencyReplacementsExtension = new SystemUnderTest(project); - verify(dependencySet).whenObjectAdded((Action) any()); + verify(dependencySet).configureEach(any()); } @Test @@ -149,6 +152,8 @@ public void aDependencyReplacementIsRegisteredForAnExternalModuleDependency() th return null; }).when(configurationContainer).configureEach(any()); + when(repository.withEntry(any())).thenReturn(mock(Entry.class)); + final ReplacementLogic dependencyReplacementsExtension = new SystemUnderTest(project); dependencyReplacementsExtension.createDummyDependency(mock(ExternalModuleDependency.class), mock(ReplacementResult.class)); diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/dependency/replacement/ReplacementAware.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/dependency/replacement/ReplacementAware.groovy index c610736f5..0dd768965 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/dependency/replacement/ReplacementAware.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/dependency/replacement/ReplacementAware.groovy @@ -26,5 +26,12 @@ interface ReplacementAware { * @param externalModuleDependency The dependency to get the replacement for. * @return The replacement dependency. */ - ExternalModuleDependency getReplacementDependency(ExternalModuleDependency externalModuleDependency) + ExternalModuleDependency getReplacementDependency(ExternalModuleDependency externalModuleDependency) + + /** + * Invoked when a dependency that is targeted by this replacement has been added to a configuration. + * Note: This might be invoked lazily when a provider based dependency is added to a configuration, and the + * configuration is about to be resolved. + */ + void onTargetDependencyAdded() } diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/Run.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/Run.groovy index 95741a1fb..e87ee3abe 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/Run.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/Run.groovy @@ -7,6 +7,7 @@ import net.minecraftforge.gdi.NamedDSLElement import net.minecraftforge.gdi.annotations.DSLProperty import net.minecraftforge.gdi.annotations.ProjectGetter import net.neoforged.gradle.dsl.common.runs.type.RunType +import org.gradle.api.Buildable import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.file.ConfigurableFileCollection @@ -15,6 +16,7 @@ import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* import org.jetbrains.annotations.NotNull @@ -274,6 +276,14 @@ interface Run extends BaseDSLElement, NamedDSLElement { @Optional abstract Property getConfigureFromDependencies(); + /** + * @return The tasks that this run depends on. + */ + @Internal + @DSLProperty + @Optional + abstract SetProperty getDependsOn(); + /** * Configures the run using the settings of the associated run type. *

@@ -303,12 +313,4 @@ interface Run extends BaseDSLElement, NamedDSLElement { * @param typeProvider The type provider to realise and configure with. */ void configure(@NotNull final Provider typeProvider); - - /** - * Configures the run to execute the given tasks before running the run. - * - * @param tasks The tasks to depend on. - */ - @SafeVarargs - abstract void dependsOn(@NotNull final TaskProvider... tasks); } diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunType.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunType.groovy index 84e5b2d48..3b818ffab 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunType.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunType.groovy @@ -161,15 +161,6 @@ abstract class RunType implements ConfigurableDSLElement, NamedDSLEleme @PathSensitive(PathSensitivity.NONE) abstract ConfigurableFileCollection getClasspath(); - /** - * An optional configurable run adapter which can be used to change the behaviour of already configured runs when the type is applied to them. - * - * @return The run adapter. - */ - @Internal - @DSLProperty - abstract Property getRunAdapter(); - /** * Copies this run type into a new instance. * diff --git a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java index 57ed425af..1107bd856 100644 --- a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java +++ b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java @@ -7,6 +7,7 @@ import net.neoforged.gradle.common.runtime.extensions.CommonRuntimeExtension; import net.neoforged.gradle.common.runtime.tasks.DefaultExecute; import net.neoforged.gradle.common.runtime.tasks.ListLibraries; +import net.neoforged.gradle.common.util.ProjectUtils; import net.neoforged.gradle.common.util.ToolUtilities; import net.neoforged.gradle.common.util.VersionJson; import net.neoforged.gradle.dsl.common.extensions.ConfigurationData; @@ -318,7 +319,7 @@ protected NeoFormRuntimeDefinition doCreate(final NeoFormRuntimeSpecification sp //TODO: Right now this is needed so that runs and other components can be order free in the buildscript, //TODO: We should consider making this somehow lazy and remove the unneeded complexity because of it. - spec.getProject().afterEvaluate(project -> this.bakeDefinition(definition)); + ProjectUtils.afterEvaluate(spec.getProject(), () -> this.bakeDefinition(definition)); return definition; } diff --git a/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java b/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java index 47e9a7ec4..956435e6a 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java @@ -71,10 +71,7 @@ import net.neoforged.gradle.vanilla.runtime.VanillaRuntimeDefinition; import net.neoforged.gradle.vanilla.runtime.extensions.VanillaRuntimeExtension; import org.apache.commons.lang3.StringUtils; -import org.gradle.api.Action; -import org.gradle.api.NamedDomainObjectContainer; -import org.gradle.api.Project; -import org.gradle.api.Task; +import org.gradle.api.*; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.Directory; import org.gradle.api.file.FileTree; @@ -869,52 +866,6 @@ private void configureRunType(final Project project, final RunType runType, fina runType.getEnvironmentVariables().put("NEOFORGE_SPEC", project.getVersion().toString().substring(0, project.getVersion().toString().lastIndexOf("."))); runType.getClasspath().from(runtimeClasspath); - - Provider assetsDir = DownloadAssets.getAssetsDirectory(project, project.provider(runtimeDefinition::getVersionJson)).map(Directory::getAsFile).map(File::getAbsolutePath); - Provider assetIndex = runtimeDefinition.getAssets().flatMap(DownloadAssets::getAssetIndex); - - runType.getRunAdapter().set(run -> { - if (run.getIsClient().get()) { - run.getProgramArguments().addAll("--username", "Dev"); - run.getProgramArguments().addAll("--version", project.getName()); - run.getProgramArguments().addAll("--accessToken", "0"); - run.getProgramArguments().addAll("--launchTarget", "forgeclientdev"); - } - - if (run.getIsServer().get()) { - run.getProgramArguments().addAll("--launchTarget", "forgeserverdev"); - } - - if (run.getIsGameTest().get()) { - run.getSystemProperties().put("neoforge.enableGameTest", "true"); - - if (run.getIsServer().get()) { - run.getSystemProperties().put("neoforge.gameTestServer", "true"); - } - } - - if (run.getIsDataGenerator().get()) { - run.getProgramArguments().addAll("--launchTarget", "forgedatadev"); - - run.getProgramArguments().addAll("--flat", "--all", "--validate"); - run.getProgramArguments().add("--output"); - run.getProgramArguments().add(project.getRootProject().file("src/generated/resources/").getAbsolutePath()); - mainSourceSet.getResources().getSrcDirs().forEach(file -> { - run.getProgramArguments().addAll("--existing", file.getAbsolutePath()); - }); - } - - if (run.getIsDataGenerator().get() || run.getIsClient().get() || runType.getIsJUnit().get()) { - run.getProgramArguments().add("--assetsDir"); - run.getProgramArguments().add(assetsDir); - run.getProgramArguments().add("--assetIndex"); - run.getProgramArguments().add(assetIndex); - } - - if (run.getIsJUnit().get()) { - run.getProgramArguments().addAll("--launchTarget", "forgejunitdev"); - } - }); } private static void configureInstallerTokens(final TokenizedTask tokenizedTask, final RuntimeDevRuntimeDefinition runtimeDefinition, final Collection ignoreConfigurations, final Configuration pluginLayerLibraries, final Configuration gameLayerLibraries) { @@ -965,12 +916,71 @@ private static Provider collectFilePaths(Configuration config, String pr } private void configureRun(final Run run, final RuntimeDevRuntimeDefinition runtimeDefinition) { + final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); + final SourceSet mainSourceSet = javaPluginExtension.getSourceSets().getByName("main"); + run.getConfigureAutomatically().set(true); run.getConfigureFromDependencies().set(false); - if (run.getIsClient().get()) { - run.dependsOn(runtimeDefinition.getAssets(), runtimeDefinition.getNatives()); - } + run.getDependsOn().addAll( + TransformerUtils.ifTrue(run.getIsClient(), runtimeDefinition.getAssets(), runtimeDefinition.getNatives()) + ); + + run.getProgramArguments().addAll( + TransformerUtils.ifTrue(run.getIsClient(), + "--username", "Dev", + "--version", project.getName(), + "--accessToken", "0", + "--launchTarget", "forgeclientdev") + + ); + + run.getProgramArguments().addAll( + TransformerUtils.ifTrue(run.getIsServer(), + "--launchTarget", "forgeserverdev") + ); + + run.getSystemProperties().putAll( + TransformerUtils.ifTrueMap(run.getIsGameTest(), + "neoforge.enableGameTest", "true") + ); + + run.getSystemProperties().putAll( + TransformerUtils.ifTrueMap( + run.getIsGameTest().flatMap(TransformerUtils.and(run.getIsServer())), + "neoforge.gameTestServer", "true") + ); + + run.getProgramArguments().addAll( + TransformerUtils.ifTrue(run.getIsDataGenerator(), + "--launchTarget", "forgedatadev", + "--flat", "--all", "--validate", + "--output", project.getRootProject().file("src/generated/resources/").getAbsolutePath()) + ); + + mainSourceSet.getResources().getSrcDirs().forEach(file -> { + run.getProgramArguments().addAll( + TransformerUtils.ifTrue(run.getIsDataGenerator(), + "--existing", file.getAbsolutePath()) + ); + }); + + Provider assetsDir = DownloadAssets.getAssetsDirectory(project, project.provider(runtimeDefinition::getVersionJson)).map(Directory::getAsFile).map(File::getAbsolutePath); + Provider assetIndex = runtimeDefinition.getAssets().flatMap(DownloadAssets::getAssetIndex); + + run.getProgramArguments().addAll( + TransformerUtils.ifTrue( + run.getIsDataGenerator().flatMap(TransformerUtils.or(run.getIsClient(), run.getIsJUnit())), + project.provider(() -> "--assetsDir"), + assetsDir, + project.provider(() -> "--assetIndex"), + assetIndex) + ); + + run.getProgramArguments().addAll( + TransformerUtils.ifTrue(run.getIsJUnit(), + "--launchTarget", "forgejunitdev") + ); } private TaskProvider createCleanProvider(final TaskProvider jarProvider, final RuntimeDevRuntimeDefinition runtimeDefinition, File workingDirectory) { diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationTests.groovy index 3faeb29d1..01ffe08b7 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationTests.groovy @@ -31,6 +31,16 @@ class ConfigurationTests extends BuilderBasedTestSpecification { modSources.add(project.getSourceSets().main) } } + + tasks.named("dependencies", t -> { + //Force fully check if we have dependencies + t.doFirst(task -> { + def configuration = project.configurations.getByName("implementation") + if (configuration.hasDependencies()) { + throw new RuntimeException("Still has dependencies") + } + }) + }) """) it.file("src/main/java/net/neoforged/gradle/userdev/FunctionalTests.java", """ package net.neoforged.gradle.userdev; @@ -114,4 +124,72 @@ class ConfigurationTests extends BuilderBasedTestSpecification { !run.file('build/publications/maven/module.json').text.contains("dependencies") !run.file('build/publications/maven/pom-default.xml').text.contains("dependencies") } + + def "a mod that lazily adds something to a dependency collector still gets its configuration resolved"() { + given: + def project = create("userdev_with_delayed_config_works", { + it.plugin('maven-publish') + it.build(""" + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + + dependencies { + implementation 'net.neoforged:neoforge:+' + } + + public interface ExampleDependencies extends Dependencies { + DependencyCollector getExample(); + } + + public abstract class ExampleExtensions { + + @Nested + public abstract ExampleDependencies getDependencies(); + + default void dependencies(Action action) { + action.execute(getDependencies()); + } + } + + project.getExtensions().create("example", ExampleExtensions) + + project.getConfigurations().create("exampleDependencies", conf -> { + conf.canBeResolved = true + conf.fromDependencyCollector(project.example.getDependencies().getExample()) + }); + + example.dependencies { + example("junit:junit:4.12") + } + + tasks.register("validateConfiguration", task -> { + task.doFirst({ + project.getConfigurations().getByName("exampleDependencies").getResolvedConfiguration().getFirstLevelModuleDependencies().forEach(dep -> { + if (!dep.getModuleGroup().equals("junit")) { + throw new RuntimeException("Expected junit dependency, got " + dep.getModuleGroup()) + } + }) + + if (project.getConfigurations().getByName("exampleDependencies").getResolvedConfiguration().getFirstLevelModuleDependencies().size() != 1) { + throw new RuntimeException("Expected 1 dependency, got " + project.getConfigurations().getByName("exampleDependencies").getResolvedConfiguration().getFirstLevelModuleDependencies().size()) + } + }) + }) + """) + it.withToolchains() + it.withGlobalCacheDirectory(tempDir) + }) + + when: + def run = project.run { + it.tasks('validateConfiguration') + it.stacktrace() + } + + then: + run.task(':validateConfiguration').outcome == TaskOutcome.SUCCESS + } } diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy index d2231d829..6bfcceb96 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy @@ -32,13 +32,7 @@ class RunTests extends BuilderBasedTestSpecification { resources.setSrcDirs(['src/main/modResources']) } } - - runs { - client { - modSource sourceSets.modRun - } - } - + dependencies { implementation "net.neoforged:neoforge:+" } diff --git a/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevReplacementResult.java b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevReplacementResult.java index b313079a5..3b89f121a 100644 --- a/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevReplacementResult.java +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevReplacementResult.java @@ -1,14 +1,21 @@ package net.neoforged.gradle.userdev.dependency; +import net.neoforged.gradle.common.util.ProjectUtils; +import net.neoforged.gradle.common.util.constants.RunsConstants; +import net.neoforged.gradle.common.util.run.TypesUtil; import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.ReplacementAware; import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.ReplacementResult; +import net.neoforged.gradle.dsl.common.runs.run.Run; +import net.neoforged.gradle.dsl.common.runs.type.RunType; import net.neoforged.gradle.dsl.common.tasks.WithOutput; import net.neoforged.gradle.userdev.runtime.definition.UserDevRuntimeDefinition; +import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.dsl.DependencyCollector; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.Nullable; @@ -16,8 +23,10 @@ /** * Special replacement result for userdev dependencies. - * Is needed because userdev needs to know where the neoforge jar is, so it can put it on the classpath + * Is needed because userdev needs to know where the neoforge jar is, so it can put it on the classpathm + * additionally we need to be notified when somebody registers us as a dependency and add the runtypes. */ +@SuppressWarnings("unchecked") public class UserDevReplacementResult extends ReplacementResult implements ReplacementAware { private final UserDevRuntimeDefinition definition; @@ -51,4 +60,23 @@ public ExternalModuleDependency getReplacementDependency(ExternalModuleDependenc return (ExternalModuleDependency) resolvedExactVersionDependency; } + + @Override + public void onTargetDependencyAdded() { + final NamedDomainObjectContainer runTypes = (NamedDomainObjectContainer) getProject().getExtensions().getByName(RunsConstants.Extensions.RUN_TYPES); + definition.getSpecification().getProfile().getRunTypes().forEach((type) -> { + TypesUtil.registerWithPotentialPrefix(runTypes, definition.getSpecification().getIdentifier(), type.getName(), type::copyTo); + }); + + final NamedDomainObjectContainer runs = (NamedDomainObjectContainer) getProject().getExtensions().getByName(RunsConstants.Extensions.RUNS); + ProjectUtils.afterEvaluate(definition.getSpecification().getProject(), () -> runs.stream() + .filter(run -> run.getIsJUnit().get()) + .flatMap(run -> run.getUnitTestSources().all().get().values().stream()) + .distinct() + .forEach(src -> { + DependencyCollector coll = definition.getSpecification().getProject().getObjects().dependencyCollector(); + definition.getSpecification().getProfile().getAdditionalTestDependencyArtifactCoordinates().get().forEach(coll::add); + definition.getSpecification().getProject().getConfigurations().getByName(src.getImplementationConfigurationName()).fromDependencyCollector(coll); + })); + } } diff --git a/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/definition/UserDevRuntimeDefinition.java b/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/definition/UserDevRuntimeDefinition.java index 45ede358f..14b3894a4 100644 --- a/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/definition/UserDevRuntimeDefinition.java +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/definition/UserDevRuntimeDefinition.java @@ -17,6 +17,7 @@ import net.neoforged.gradle.neoform.runtime.definition.NeoFormRuntimeDefinition; import net.neoforged.gradle.userdev.runtime.specification.UserDevRuntimeSpecification; import net.neoforged.gradle.userdev.runtime.tasks.ClasspathSerializer; +import org.gradle.api.NamedDomainObjectCollection; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.file.FileTree; @@ -136,7 +137,7 @@ protected Map buildRunInterpolationData(RunImpl run) { interpolationData.put("minecraft_classpath_file", minecraftClasspathSerializer.get().getOutput().get().getAsFile().getAbsolutePath()); - run.dependsOn(minecraftClasspathSerializer); + run.getDependsOn().add(minecraftClasspathSerializer); return interpolationData; } diff --git a/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/extension/UserDevRuntimeExtension.java b/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/extension/UserDevRuntimeExtension.java index 1f26b0e0f..20bddefd4 100644 --- a/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/extension/UserDevRuntimeExtension.java +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/extension/UserDevRuntimeExtension.java @@ -94,22 +94,6 @@ public UserDevRuntimeExtension(Project project) { spec.setMinecraftVersion(neoFormRuntimeDefinition.getSpecification().getMinecraftVersion()); - final NamedDomainObjectContainer runs = (NamedDomainObjectContainer) getProject().getExtensions().getByName(RunsConstants.Extensions.RUNS); - ProjectUtils.afterEvaluate(spec.getProject(), () -> runs.stream() - .filter(run -> run.getIsJUnit().get()) - .flatMap(run -> run.getUnitTestSources().all().get().values().stream()) - .distinct() - .forEach(src -> { - DependencyCollector coll = spec.getProject().getObjects().dependencyCollector(); - spec.getProfile().getAdditionalTestDependencyArtifactCoordinates().get().forEach(coll::add); - spec.getProject().getConfigurations().getByName(src.getImplementationConfigurationName()).fromDependencyCollector(coll); - })); - - final NamedDomainObjectContainer runTypes = (NamedDomainObjectContainer) getProject().getExtensions().getByName(RunsConstants.Extensions.RUN_TYPES); - userDevProfile.getRunTypes().forEach((type) -> { - TypesUtil.registerWithPotentialPrefix(runTypes, spec.getIdentifier(), type.getName(), type::copyTo); - }); - return new UserDevRuntimeDefinition( spec, neoFormRuntimeDefinition, diff --git a/utils/src/main/java/net/neoforged/gradle/util/TransformerUtils.java b/utils/src/main/java/net/neoforged/gradle/util/TransformerUtils.java index bd16d33d2..3b0b5116a 100644 --- a/utils/src/main/java/net/neoforged/gradle/util/TransformerUtils.java +++ b/utils/src/main/java/net/neoforged/gradle/util/TransformerUtils.java @@ -1,14 +1,22 @@ package net.neoforged.gradle.util; import groovyjarjarantlr4.v4.runtime.misc.NotNull; +import org.gradle.api.Project; import org.gradle.api.Transformer; +import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.Directory; import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Provider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; +import java.util.*; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; /** * Utility class which handles gradles transformers. @@ -158,6 +166,185 @@ public static Transformer ensureExists() { }); } + /** + * Creates a transformer which will combine all the values into a single map. + * + * @param project The project to use for creating the map property + * @param keyClass The class of the key of the map + * @param valueClass The class of the value of the map + * @param valueProvider The function to provide the map for each input + * @return The transformer which will combine all the maps into a single map + * @param The type of the key of the map + * @param The type of the value of the map + * @param The type of the input to the transformer + * @param The type of the collection of inputs + */ + public static > Transformer>, C> combineAllMaps(final Project project, final Class keyClass, final Class valueClass, final Function>> valueProvider) { + final MapProperty map = project.getObjects().mapProperty(keyClass, valueClass); + return guard(t -> { + for (I i : t) { + map.putAll(valueProvider.apply(i)); + } + return map; + }); + } + + /** + * Creates a transformer which will execute a callback on the inputs last value before passing it back as a result. + * @param project The project to use for creating the provider + * @param valueProvider The function to provide the value for the last input + * @return The transformer which will provide the value for the last input + * @param The type of the output of the transformer + * @param The type of the input to the transformer + * @param The type of the collection of inputs + */ + public static > Transformer, C> takeLast(final Project project, final Function> valueProvider) { + return guard(t -> { + Provider result = project.provider(() -> null); + if (t.isEmpty()) + return result; + + for (int i = t.size() - 1; i >= 0; i--) { + result = result.orElse(valueProvider.apply(t.get(i))); + } + + return result; + }); + } + + /** + * Creates a transformer which will combine all the values into a single list. + * + * @param project The project to use for creating the list property + * @param valueClass The class of the value of the list + * @param valueProvider The function to provide the list for each input + * @return The transformer which will combine all the lists into a single list + * @param The type of the value of the list + * @param The type of the input to the transformer + * @param The type of the collection of inputs + */ + public static > Transformer>, C> combineAllLists(final Project project, Class valueClass, Function>> valueProvider) { + return guard(t -> { + final ListProperty values = project.getObjects().listProperty(valueClass); + for (I i : t) { + values.addAll(valueProvider.apply(i)); + } + return values; + }); + } + + public static > Transformer combineFileCollections(final Project project, Function valueProvider) { + return guard(t -> { + final ConfigurableFileCollection files = project.files(); + for (I i : t) { + files.from(valueProvider.apply(i)); + } + return files; + }); + } + + /** + * Creates a transformed provider that returns a list of values if the predicate is true. + * + * @param predicate The predicate to check + * @param whenTrue The value to return if the predicate is true + * @return A transformed provider if the predicate is true, otherwise null + * @param The type of the value to return + */ + @SafeVarargs + public static Provider> ifTrue(Provider predicate, Provider... whenTrue) { + if (whenTrue.length == 0) { + return predicate.map(p -> List.of()); + } + + if (whenTrue.length == 1) { + return whenTrue[0].zip(predicate, (v, p) -> p ? List.of(v) : List.of()); + } + + Provider> zippedArray = whenTrue[0].zip(predicate, (v, p) -> p ? List.of(v) : List.of()); + for (int i = 1; i < whenTrue.length; i++) { + zippedArray = zippedArray.zip( + whenTrue[1].zip(predicate, (v, p) -> p ? List.of(v) : List.of()), + (BiFunction, List, List>) (vs, objects) -> { + final ArrayList ret = new ArrayList<>(vs); + ret.addAll(objects); + return ret; + } + ); + } + + return zippedArray; + } + + /** + * Creates a transformed provider that returns a list of values if the predicate is true. + * + * @param predicate The predicate to check + * @param whenTrue The value to return if the predicate is true + * @return A transformed provider if the predicate is true, otherwise null + * @param The type of the value to return + */ + @SafeVarargs + public static Provider> ifTrue(Provider predicate, V... whenTrue) { + if (whenTrue.length == 0) { + return predicate.map(p -> List.of()); + } + + return predicate.map(p -> p ? List.of(whenTrue) : List.of()); + } + + /** + * Creates a transformed provider that returns a map of with the key value pair. + * + * @param predicate The predicate to check + * @param keyWhenTrue The key to return if the predicate is true + * @param valueWhenTrue The value to return if the predicate is true + * @return A transformed provider if the predicate is true, otherwise null + * @param The type of the key to return + * @param The type of the value to return + */ + public static Provider> ifTrueMap(Provider predicate, K keyWhenTrue, V valueWhenTrue) { + return predicate.map(p -> p ? Map.of(keyWhenTrue, valueWhenTrue) : Map.of()); + } + + @SafeVarargs + public static Transformer, Boolean> and(Provider... rightProvider) { + if (rightProvider.length == 0) { + throw new IllegalStateException("No right provider provided"); + } + + if (rightProvider.length == 1) { + return left -> rightProvider[0].map(o -> left && o); + } + + return inputBoolean -> { + Provider result = rightProvider[0].map(o -> inputBoolean && o); + for (int i = 1; i < rightProvider.length; i++) { + result = result.zip(rightProvider[i], (l, r) -> l && r); + } + return result; + }; + } + + @SafeVarargs + public static Transformer, Boolean> or(Provider... rightProvider) { + if (rightProvider.length == 0) { + throw new IllegalStateException("No right provider provided"); + } + + if (rightProvider.length == 1) { + return left -> rightProvider[0].map(o -> left || o); + } + + return inputBoolean -> { + Provider result = rightProvider[0].map(o -> inputBoolean || o); + for (int i = 1; i < rightProvider.length; i++) { + result = result.zip(rightProvider[i], (l, r) -> l || r); + } + return result; + }; + } + /** * A definition for a transformer which can throw an exception. * diff --git a/vanilla/src/functionalTest/groovy/net/neoforged/gradle/vanilla/AccessTransformerTests.groovy b/vanilla/src/functionalTest/groovy/net/neoforged/gradle/vanilla/AccessTransformerTests.groovy index ab04bced9..77d2d35ee 100644 --- a/vanilla/src/functionalTest/groovy/net/neoforged/gradle/vanilla/AccessTransformerTests.groovy +++ b/vanilla/src/functionalTest/groovy/net/neoforged/gradle/vanilla/AccessTransformerTests.groovy @@ -40,6 +40,7 @@ class AccessTransformerTests extends BuilderBasedTestSpecification { } """) it.withToolchains() + it.withGlobalCacheDirectory(tempDir) }) when: @@ -81,6 +82,7 @@ class AccessTransformerTests extends BuilderBasedTestSpecification { } """) it.withToolchains() + it.withGlobalCacheDirectory(tempDir) }) when: @@ -123,6 +125,7 @@ class AccessTransformerTests extends BuilderBasedTestSpecification { } """) it.withToolchains() + it.withGlobalCacheDirectory(tempDir) }) when: @@ -166,6 +169,7 @@ class AccessTransformerTests extends BuilderBasedTestSpecification { } """) it.withToolchains() + it.withGlobalCacheDirectory(tempDir) }) when: @@ -209,6 +213,7 @@ class AccessTransformerTests extends BuilderBasedTestSpecification { } """) it.withToolchains() + it.withGlobalCacheDirectory(tempDir) }) when: