From ed5a151312190d2273987f87424a9377c979f610 Mon Sep 17 00:00:00 2001 From: Marc Hermans Date: Wed, 24 Jul 2024 14:12:46 +0200 Subject: [PATCH] [Fix]: Make runs entirely lazily, adding back support for handling VersionCatalogues (#230) --- README.md | 51 +++++- .../gradle/common/CommonProjectPlugin.java | 121 +++++--------- .../MinecraftArtifactCacheExtension.java | 33 +--- .../replacement/ReplacementLogic.java | 24 ++- .../extensions/repository/IvyRepository.java | 4 +- .../LaterAddedReplacedDependencyRule.java | 60 +++++++ .../gradle/common/runs/run/RunImpl.java | 150 ++++++++++++++++-- .../common/runs/run/RunSourceSetsImpl.java | 30 ++++ .../common/runs/run/RunTypeManagerImpl.java | 46 ++++++ .../gradle/common/runs/tasks/RunsReport.java | 2 +- .../definition/CommonRuntimeDefinition.java | 105 +++++++----- .../extensions/CommonRuntimeExtension.java | 16 +- .../common/runtime/tasks/DownloadAssets.java | 6 +- .../common/tasks/UnpackBundledServer.java | 4 +- .../common/util/BundledServerUtils.java | 5 + .../util/ConfigurationPhaseFileUtils.java | 31 +++- .../common/util/TaskDependencyUtils.java | 4 +- .../gradle/common/util/VersionJson.java | 5 + .../common/util/constants/RunsConstants.java | 1 - .../gradle/common/util/run/RunsUtil.java | 10 +- .../extensions/MinecraftArtifactCache.groovy | 12 +- .../replacement/DependencyReplacement.groovy | 23 +++ .../replacement/ReplacementAware.groovy | 6 +- .../gradle/dsl/common/runs/run/Run.groovy | 89 ++++++++++- .../dsl/common/runs/run/RunSourceSets.groovy | 8 + .../dsl/common/runs/type/RunAdapter.groovy | 16 -- .../common/runs/type/RunTypeManager.groovy | 32 ++++ gradle.properties | 2 +- gradlew | 2 +- img.png | Bin 0 -> 64198 bytes .../definition/NeoFormRuntimeDefinition.java | 10 +- .../extensions/NeoFormRuntimeExtension.java | 55 +++---- .../extensions/DynamicProjectExtension.java | 5 +- .../RuntimeDevRuntimeDefinition.java | 11 +- .../gradle/userdev/CentralCacheTests.groovy | 2 + .../userdev/ConfigurationCacheTests.groovy | 13 +- .../gradle/userdev/ConfigurationTests.groovy | 4 +- .../neoforged/gradle/userdev/RunTests.groovy | 59 ++++++- .../convention/RunConventionTests.groovy | 25 +-- .../SourceSetConventionTests.groovy | 8 + ...erDevAdditionalTestDependenciesParser.java | 64 ++++++++ .../dependency/UserDevDependencyManager.java | 59 ++++++- .../dependency/UserDevReplacementResult.java | 20 --- .../dependency/UserDevRunTypeParser.java | 56 +++++++ .../definition/UserDevRuntimeDefinition.java | 42 +++-- .../extension/UserDevRuntimeExtension.java | 25 +++ .../UserDevRuntimeSpecification.java | 12 +- .../runtime/tasks/ClasspathSerializer.java | 7 + .../gradle/util/TransformerUtils.java | 47 ++++-- .../vanilla/AccessTransformerTests.groovy | 1 + .../runtime/VanillaRuntimeDefinition.java | 54 +++++-- .../extensions/VanillaRuntimeExtension.java | 63 +++----- .../steps/CollectLibraryInformationStep.java | 4 - .../vanilla/util/ServerLaunchInformation.java | 29 ++-- 54 files changed, 1154 insertions(+), 419 deletions(-) create mode 100644 common/src/main/java/net/neoforged/gradle/common/rules/LaterAddedReplacedDependencyRule.java create mode 100644 common/src/main/java/net/neoforged/gradle/common/runs/run/RunTypeManagerImpl.java delete mode 100644 dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunAdapter.groovy create mode 100644 dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunTypeManager.groovy create mode 100644 img.png create mode 100644 userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevAdditionalTestDependenciesParser.java create mode 100644 userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevRunTypeParser.java diff --git a/README.md b/README.md index dbe26cece..ac566f134 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,57 @@ dependencies { } ``` +#### Dependency management by the userdev plugin When this plugin detects a dependency on NeoForge, it will spring into action and create the necessary NeoForm runtime tasks to build a usable Minecraft JAR-file that contains the requested NeoForge version. +It additionally (if configured to do so via conventions, which is the default) will create runs for your project, and add the necessary dependencies to the classpath of the run. + +##### Version Catalogues +Using gradles modern version catalog feature means that dependencies are added at the very last moment possible, regardless of that is during script evaluation or not. +This means that if you use this feature it might sometimes not be possible to configure the default runs exactly as you wished. +In that case it might be beneficial to disable the conventions for runs, and configure them manually. + +See the following example: + +_lib.versions.toml:_ +```toml +[versions] +# Neoforge Settings +neoforge = "+" + +[libraries] +neoforge = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge" } +``` +_build.gradle:_ +```groovy +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(libs.neoforge) +} + +runs { + configureEach { run -> + run.modSource sourceSets.main + } + + client { } + server { } + datagen { } + gameTestServer { } + junit { } +} +``` +You do not need to create all five of the different runs, only the ones you need. + +This is because at the point where gradle actually adds the dependency, we can not create any further runs. ### NeoForm Runtime Plugin @@ -62,7 +112,6 @@ from the [Parchment project](https://parchmentmc.org) can be applied to the Mine This is currently only supported when applying the NeoGradle userdev Plugin. The most basic configuration is using the following properties in gradle.properties: - ``` neogradle.subsystems.parchment.minecraftVersion=1.20.2 neogradle.subsystems.parchment.mappingsVersion=2023.12.10 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 004400c2b..7eebbe36a 100644 --- a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java +++ b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java @@ -6,8 +6,10 @@ import net.neoforged.gradle.common.extensions.dependency.replacement.ReplacementLogic; import net.neoforged.gradle.common.extensions.repository.IvyRepository; import net.neoforged.gradle.common.extensions.subsystems.SubsystemsExtension; +import net.neoforged.gradle.common.rules.LaterAddedReplacedDependencyRule; import net.neoforged.gradle.common.runs.ide.IdeRunIntegrationManager; import net.neoforged.gradle.common.runs.run.RunImpl; +import net.neoforged.gradle.common.runs.run.RunTypeManagerImpl; import net.neoforged.gradle.common.runs.tasks.RunsReport; import net.neoforged.gradle.common.runtime.definition.CommonRuntimeDefinition; import net.neoforged.gradle.common.runtime.extensions.RuntimesExtension; @@ -35,6 +37,7 @@ import net.neoforged.gradle.dsl.common.runs.run.Run; import net.neoforged.gradle.dsl.common.runs.run.RunDevLogin; import net.neoforged.gradle.dsl.common.runs.type.RunType; +import net.neoforged.gradle.dsl.common.runs.type.RunTypeManager; import net.neoforged.gradle.dsl.common.util.ConfigurationUtils; import net.neoforged.gradle.dsl.common.util.NamingConstants; import net.neoforged.gradle.util.UrlConstants; @@ -101,6 +104,9 @@ public void apply(Project project) { extensionManager.registerExtension("minecraft", Minecraft.class, (p) -> p.getObjects().newInstance(MinecraftExtension.class, p)); extensionManager.registerExtension("mappings", Mappings.class, (p) -> p.getObjects().newInstance(MappingsExtension.class, p)); + extensionManager.registerExtension("runTypes", RunTypeManager.class, (p) -> p.getObjects().newInstance(RunTypeManagerImpl.class, p)); + + project.getExtensions().create("clientExtraJarDependencyManager", ExtraJarDependencyManager.class, project); final ConfigurationData configurationData = project.getExtensions().create(ConfigurationData.class, "configurationData", ConfigurationDataExtension.class, project); @@ -127,16 +133,13 @@ public void apply(Project project) { sourceSet.getExtensions().add("runtimeDefinition", project.getObjects().property(CommonRuntimeDefinition.class)); }); - project.getExtensions().add( - RunsConstants.Extensions.RUN_TYPES, - project.getObjects().domainObjectContainer(RunType.class, name -> project.getObjects().newInstance(RunType.class, name)) - ); - final NamedDomainObjectContainer runs = project.getObjects().domainObjectContainer(Run.class, name -> RunsUtil.create(project, name)); project.getExtensions().add( RunsConstants.Extensions.RUNS, runs ); + //Register a task creation rule that checks for runs. + project.getTasks().addRule(new LaterAddedReplacedDependencyRule(project)); runs.configureEach(run -> { RunsUtil.configureModClasses(run); @@ -219,7 +222,6 @@ private void configureSourceSetConventions(Project project, Conventions conventi } - @SuppressWarnings("unchecked") private void configureRunConventions(Project project, Conventions conventions) { final Configurations configurations = conventions.getConfigurations(); final Runs runs = conventions.getRuns(); @@ -227,21 +229,6 @@ private void configureRunConventions(Project project, Conventions conventions) { if (!runs.getIsEnabled().get()) return; - if (runs.getShouldDefaultRunsBeCreated().get()) { - final NamedDomainObjectContainer runTypes = (NamedDomainObjectContainer) project.getExtensions().getByName(RunsConstants.Extensions.RUN_TYPES); - //Force none lazy resolve here. - runTypes.whenObjectAdded(runType -> { - project.getExtensions().configure(RunsConstants.Extensions.RUNS, (Action>) runContainer -> { - if (runContainer.getAsMap().containsKey(runType.getName())) - return; - - runContainer.create(runType.getName(), run -> { - run.configure(runType); - }); - }); - }); - } - if (!configurations.getIsEnabled().get()) return; @@ -357,7 +344,7 @@ 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.configureEach(run -> { - if (run instanceof RunImpl) { + if (run instanceof RunImpl runImpl) { run.configure(); // We add default junit sourcesets here because we need to know the type of the run first @@ -372,68 +359,38 @@ private void applyAfterEvaluate(final Project project) { throw new InvalidUserDataException("Run: " + run.getName() + " has no source sets configured. Please configure at least one source set."); } - if (run.getConfigureFromDependencies().get()) { - final RunImpl runImpl = (RunImpl) run; - - //Let's keep track of all runtimes that have been configured. - //TODO: Determine handling of multiple different runtimes, in multiple projects.... - final Map> definitionSet = new HashMap<>(); - - runImpl.getModSources().all().get().values().forEach(sourceSet -> { - try { - final Optional> definition = TaskDependencyUtils.findRuntimeDefinition(sourceSet); - if (definition.isPresent()) { - final CommonRuntimeDefinition runtimeDefinition = definition.get(); - //First time we see this runtime add, it. - if (!definitionSet.containsKey(runtimeDefinition.getSpecification().getIdentifier())) { - definitionSet.put(runtimeDefinition.getSpecification().getIdentifier(), runtimeDefinition); - } else if (SourceSetUtils.getProject(sourceSet) == project) { - //We have seen this runtime before, but this time it is our own, which we prefer. - definitionSet.put(runtimeDefinition.getSpecification().getIdentifier(), runtimeDefinition); - } - } - } 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); - } - }); - - definitionSet.forEach((identifier, definition) -> { - definition.configureRun(runImpl); - }); - - //Handle dev login. - final DevLogin devLogin = project.getExtensions().getByType(Subsystems.class).getDevLogin(); - final Tools tools = project.getExtensions().getByType(Subsystems.class).getTools(); - if (devLogin.getEnabled().get()) { - final RunDevLogin runsDevLogin = runImpl.getExtensions().getByType(RunDevLogin.class); - - //Dev login is only supported on the client side - if (runImpl.getIsClient().get() && runsDevLogin.getIsEnabled().get()) { - final String mainClass = runImpl.getMainClass().get(); - - //We add the dev login tool to a custom configuration which runtime classpath extends from the default runtime classpath - final SourceSet defaultSourceSet = runImpl.getModSources().all().get().entries().iterator().next().getValue(); - final String runtimeOnlyDevLoginConfigurationName = ConfigurationUtils.getSourceSetName(defaultSourceSet, devLogin.getConfigurationSuffix().get()); - final Configuration sourceSetRuntimeOnlyDevLoginConfiguration = project.getConfigurations().maybeCreate(runtimeOnlyDevLoginConfigurationName); - final Configuration sourceSetRuntimeClasspathConfiguration = project.getConfigurations().maybeCreate(defaultSourceSet.getRuntimeClasspathConfigurationName()); - - sourceSetRuntimeClasspathConfiguration.extendsFrom(sourceSetRuntimeOnlyDevLoginConfiguration); - sourceSetRuntimeOnlyDevLoginConfiguration.getDependencies().add(project.getDependencies().create(tools.getDevLogin().get())); - - //Update the program arguments to properly launch the dev login tool - run.getProgramArguments().add("--launch_target"); - run.getProgramArguments().add(mainClass); - - if (runsDevLogin.getProfile().isPresent()) { - run.getProgramArguments().add("--launch_profile"); - run.getProgramArguments().add(runsDevLogin.getProfile().get()); - } + //Handle dev login. + final DevLogin devLogin = project.getExtensions().getByType(Subsystems.class).getDevLogin(); + final Tools tools = project.getExtensions().getByType(Subsystems.class).getTools(); + if (devLogin.getEnabled().get()) { + final RunDevLogin runsDevLogin = runImpl.getExtensions().getByType(RunDevLogin.class); - //Set the main class to the dev login tool - run.getMainClass().set(devLogin.getMainClass()); - } else if (!runImpl.getIsClient().get() && runsDevLogin.getIsEnabled().get()) { - throw new InvalidUserDataException("Dev login is only supported on runs which are marked as clients! The run: " + runImpl.getName() + " is not a client run."); + //Dev login is only supported on the client side + if (runImpl.getIsClient().get() && runsDevLogin.getIsEnabled().get()) { + final String mainClass = runImpl.getMainClass().get(); + + //We add the dev login tool to a custom configuration which runtime classpath extends from the default runtime classpath + final SourceSet defaultSourceSet = runImpl.getModSources().all().get().entries().iterator().next().getValue(); + final String runtimeOnlyDevLoginConfigurationName = ConfigurationUtils.getSourceSetName(defaultSourceSet, devLogin.getConfigurationSuffix().get()); + final Configuration sourceSetRuntimeOnlyDevLoginConfiguration = project.getConfigurations().maybeCreate(runtimeOnlyDevLoginConfigurationName); + final Configuration sourceSetRuntimeClasspathConfiguration = project.getConfigurations().maybeCreate(defaultSourceSet.getRuntimeClasspathConfigurationName()); + + sourceSetRuntimeClasspathConfiguration.extendsFrom(sourceSetRuntimeOnlyDevLoginConfiguration); + sourceSetRuntimeOnlyDevLoginConfiguration.getDependencies().add(project.getDependencies().create(tools.getDevLogin().get())); + + //Update the program arguments to properly launch the dev login tool + run.getProgramArguments().add("--launch_target"); + run.getProgramArguments().add(mainClass); + + if (runsDevLogin.getProfile().isPresent()) { + run.getProgramArguments().add("--launch_profile"); + run.getProgramArguments().add(runsDevLogin.getProfile().get()); } + + //Set the main class to the dev login tool + run.getMainClass().set(devLogin.getMainClass()); + } else if (!runImpl.getIsClient().get() && runsDevLogin.getIsEnabled().get()) { + throw new InvalidUserDataException("Dev login is only supported on runs which are marked as clients! The run: " + runImpl.getName() + " is not a client run."); } } } diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/MinecraftArtifactCacheExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/MinecraftArtifactCacheExtension.java index b63dd776e..f2f0b5e35 100644 --- a/common/src/main/java/net/neoforged/gradle/common/extensions/MinecraftArtifactCacheExtension.java +++ b/common/src/main/java/net/neoforged/gradle/common/extensions/MinecraftArtifactCacheExtension.java @@ -87,21 +87,6 @@ public final Map getCacheFiles() { return ImmutableMap.copyOf(this.cacheFiles); } - @Override - public final Map cacheGameVersion(String gameVersion, DistributionType side) { - final MinecraftVersionAndUrl resolvedVersion = resolveVersion(gameVersion); - - final Map result = new EnumMap<>(GameArtifact.class); - - GameArtifact.VERSION_MANIFEST.doWhenRequired(side, () -> result.put(GameArtifact.VERSION_MANIFEST, this.cacheVersionManifest(resolvedVersion))); - GameArtifact.CLIENT_JAR.doWhenRequired(side, () -> result.put(GameArtifact.CLIENT_JAR, this.cacheVersionArtifact(resolvedVersion, DistributionType.CLIENT))); - GameArtifact.SERVER_JAR.doWhenRequired(side, () -> result.put(GameArtifact.SERVER_JAR, this.cacheVersionArtifact(resolvedVersion, DistributionType.SERVER))); - GameArtifact.CLIENT_MAPPINGS.doWhenRequired(side, () -> result.put(GameArtifact.CLIENT_MAPPINGS, this.cacheVersionMappings(resolvedVersion, DistributionType.CLIENT))); - GameArtifact.SERVER_MAPPINGS.doWhenRequired(side, () -> result.put(GameArtifact.SERVER_MAPPINGS, this.cacheVersionMappings(resolvedVersion, DistributionType.SERVER))); - - return result; - } - @Override @NotNull public final Map> cacheGameVersionTasks(final Project project, String gameVersion, final DistributionType side) { @@ -130,10 +115,12 @@ public final File cacheLauncherMetadata() { } @Override - public final File cacheVersionManifest(String gameVersion) { - final MinecraftVersionAndUrl resolvedVersion = resolveVersion(gameVersion); + public final Provider cacheVersionManifest(String gameVersion) { + return project.provider(() -> { + final MinecraftVersionAndUrl resolvedVersion = resolveVersion(gameVersion); - return this.cacheVersionManifest(resolvedVersion); + return this.cacheVersionManifest(resolvedVersion); + }); } public final File cacheVersionManifest(MinecraftVersionAndUrl resolvedVersion) { @@ -149,11 +136,6 @@ public final File cacheVersionArtifact(String gameVersion, DistributionType side return this.cacheFiles.computeIfAbsent(cacheFileSelector, selector -> downloadVersionArtifactToCache(project, getCacheDirectory().get().getAsFile(), resolvedVersion.getVersion(), side)); } - public final File cacheVersionArtifact(MinecraftVersionAndUrl resolvedVersion, DistributionType side) { - final CacheFileSelector cacheFileSelector = CacheFileSelector.forVersionJar(resolvedVersion.getVersion(), side.getName()); - return this.cacheFiles.computeIfAbsent(cacheFileSelector, selector -> downloadVersionArtifactToCache(project, getCacheDirectory().get().getAsFile(), resolvedVersion.getVersion(), side)); - } - @Override public final File cacheVersionMappings(@NotNull String gameVersion, DistributionType side) { final MinecraftVersionAndUrl resolvedVersion = resolveVersion(gameVersion); @@ -162,11 +144,6 @@ public final File cacheVersionMappings(@NotNull String gameVersion, Distribution return this.cacheFiles.computeIfAbsent(cacheFileSelector, selector -> downloadVersionMappingsToCache(project, getCacheDirectory().get().getAsFile(), resolvedVersion.getVersion(), side)); } - public final File cacheVersionMappings(@NotNull MinecraftVersionAndUrl resolvedVersion, DistributionType side) { - final CacheFileSelector cacheFileSelector = CacheFileSelector.forVersionMappings(resolvedVersion.getVersion(), side.getName()); - return this.cacheFiles.computeIfAbsent(cacheFileSelector, selector -> downloadVersionMappingsToCache(project, getCacheDirectory().get().getAsFile(), resolvedVersion.getVersion(), side)); - } - @Override public final File cache(final URL url, final CacheFileSelector selector) { return this.cache(url.toString(), selector); 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 f9c1c8e7a..7f699e528 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 @@ -14,6 +14,7 @@ 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 org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; @@ -26,10 +27,7 @@ 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; +import java.util.*; /** * Defines the implementation of the @{link DependencyReplacement} extension. @@ -45,6 +43,8 @@ public abstract class ReplacementLogic implements ConfigurableDSLElement originalDependencyLookup = HashBasedTable.create(); private final NamedDomainObjectContainer dependencyReplacementHandlers; + private final List whenDependencyReplaced = new ArrayList<>(); + @Inject public ReplacementLogic(Project project) { this.project = project; @@ -55,6 +55,15 @@ public ReplacementLogic(Project project) { this.dependencyReplacementHandlers = this.project.getObjects().domainObjectContainer(DependencyReplacementHandler.class, name -> this.project.getObjects().newInstance(Handler.class, this.project, name)); } + @Override + public void whenDependencyReplaced(DependencyReplacedCallback dependencyAction) { + this.whenDependencyReplaced.add(dependencyAction); + + for (Table.Cell dependencyConfigurationDependencyCell : this.originalDependencyLookup.cellSet()) { + dependencyAction.apply(dependencyConfigurationDependencyCell.getRowKey(), dependencyConfigurationDependencyCell.getColumnKey(), dependencyConfigurationDependencyCell.getValue()); + } + } + @Override public void handleConfiguration(Configuration configuration) { //TODO: Figure out if there is any way to do this lazily. @@ -268,13 +277,18 @@ private void handleDependencyReplacement(Configuration configuration, Dependency final Provider replacedDependencies = replacedFiles .map(files -> project.getDependencies().create(files)); + final Provider newRepoDependency = project.provider(newRepoEntry::getDependency); //Add the new dependency to the target configuration. targetDependencies.addLater(replacedDependencies); - targetDependencies.add(newRepoEntry.getDependency()); + targetDependencies.addLater(newRepoDependency); //Keep track of the original dependency, so we can convert back if needed. originalDependencyLookup.put(newRepoEntry.getDependency(), targetConfiguration, dependency); + + for (DependencyReplacedCallback dependencyReplacedCallback : this.whenDependencyReplaced) { + dependencyReplacedCallback.apply(newRepoEntry.getDependency(), targetConfiguration, dependency); + } } catch (Exception exception) { throw new GradleException("Failed to add the replaced dependency to the configuration " + targetConfiguration.getName() + ": " + exception.getMessage(), exception); } 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 a0d2d8d0a..5168e3a8b 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 @@ -204,14 +204,14 @@ private void writeDummyDataIfNeeded( //Create the raw artifact file and sources file if they don't exist. if (!ConfigurationPhaseFileUtils.isRegularFile(jarFile)) { FileUtils.delete(jarFile); - ConfigurationPhaseFileUtils.createFile(jarFile); + ConfigurationPhaseFileUtils.createEmptyZipFile(jarFile); } if (hasSource) { final Path sourcesFile = buildArtifactPath(entry, "sources"); if (!ConfigurationPhaseFileUtils.isRegularFile(sourcesFile)) { FileUtils.delete(sourcesFile); - ConfigurationPhaseFileUtils.createFile(sourcesFile); + ConfigurationPhaseFileUtils.createEmptyZipFile(sourcesFile); } } } diff --git a/common/src/main/java/net/neoforged/gradle/common/rules/LaterAddedReplacedDependencyRule.java b/common/src/main/java/net/neoforged/gradle/common/rules/LaterAddedReplacedDependencyRule.java new file mode 100644 index 000000000..554159879 --- /dev/null +++ b/common/src/main/java/net/neoforged/gradle/common/rules/LaterAddedReplacedDependencyRule.java @@ -0,0 +1,60 @@ +package net.neoforged.gradle.common.rules; + +import net.neoforged.gradle.common.util.constants.RunsConstants; +import net.neoforged.gradle.common.util.run.RunsUtil; +import net.neoforged.gradle.dsl.common.extensions.subsystems.Conventions; +import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems; +import net.neoforged.gradle.dsl.common.runs.run.Run; +import org.apache.tools.ant.TaskContainer; +import org.gradle.api.NamedDomainObjectCollection; +import org.gradle.api.NamedDomainObjectContainer; +import org.gradle.api.Project; +import org.gradle.api.Rule; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; + +import java.util.HashSet; +import java.util.Set; + +public class LaterAddedReplacedDependencyRule implements Rule { + + private final Project project; + private final NamedDomainObjectContainer runs; + + @SuppressWarnings("unchecked") + public LaterAddedReplacedDependencyRule(Project project) { + this.runs = (NamedDomainObjectContainer) project.getExtensions().getByName(RunsConstants.Extensions.RUNS); + this.project = project; + } + + @Override + public String getDescription() { + return "Pattern run: Runs the specified run."; + } + + @Override + public void apply(String domainObjectName) { + if (!domainObjectName.startsWith("run")) { + return; + } + + //This prevents things like runtime from triggering the task creation. + if (domainObjectName.length() < 4 || !Character.isUpperCase(domainObjectName.charAt(3))) { + return; + } + + final Conventions conventions = project.getExtensions().getByType(Subsystems.class).getConventions(); + if (conventions.getIsEnabled().get() && conventions.getRuns().getIsEnabled().get() && conventions.getRuns().getShouldDefaultRunsBeCreated().get()) { + final String runName = domainObjectName.substring(3); + + Run run = runs.findByName(runName); + if (run == null) { + final String decapitalizedRunName = runName.substring(0, 1).toLowerCase() + runName.substring(1); + run = runs.findByName(decapitalizedRunName); + if (run == null) { + runs.create(decapitalizedRunName); + } + } + } + } +} 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 2e64d622d..1f0ad88dc 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 @@ -1,29 +1,37 @@ package net.neoforged.gradle.common.runs.run; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; +import com.google.common.collect.Multimap; import net.minecraftforge.gdi.ConfigurableDSLElement; +import net.neoforged.gradle.common.runtime.definition.CommonRuntimeDefinition; +import net.neoforged.gradle.common.util.SourceSetUtils; +import net.neoforged.gradle.common.util.TaskDependencyUtils; import net.neoforged.gradle.common.util.constants.RunsConstants; +import net.neoforged.gradle.common.util.exceptions.MultipleDefinitionsFoundException; +import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.DependencyReplacement; 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.dsl.common.runs.type.RunTypeManager; 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; -import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.FileSystemLocation; 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.tasks.Internal; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.util.*; +import java.util.function.Function; +import java.util.stream.Stream; public abstract class RunImpl implements ConfigurableDSLElement, Run { @@ -64,14 +72,34 @@ public RunImpl(final Project project, final String name) { getConfigureAutomatically().convention(true); getConfigureFromTypeWithName().convention(getConfigureAutomatically()); getConfigureFromDependencies().convention(getConfigureAutomatically()); - + getWorkingDirectory().convention(project.getLayout().getProjectDirectory().dir("runs").dir(getName())); + + getRuntimeClasspath().from( + getModSources().all().map(Multimap::values) + .map(sourcesSets -> sourcesSets.stream().map(SourceSet::getRuntimeClasspath).toList()) + ); + getTestRuntimeClasspath().from(getRuntimeClasspath()); + getTestRuntimeClasspath().from( + getUnitTestSources().all().map(Multimap::values) + .map(sourcesSets -> sourcesSets.stream().map(SourceSet::getRuntimeClasspath).toList()) + ); + getCompileClasspath().from( + getModSources().all().map(Multimap::values) + .map(sourcesSets -> sourcesSets.stream().map(SourceSet::getCompileClasspath).toList()) + ); + getTestCompileClasspath().from(getCompileClasspath()); + getTestCompileClasspath().from( + getUnitTestSources().all().map(Multimap::values) + .map(sourcesSets -> sourcesSets.stream().map(SourceSet::getCompileClasspath).toList()) + ); } @Override public Project getProject() { return project; } + @Override public final String getName() { return name; @@ -159,10 +187,43 @@ public void overrideSystemProperties(MapProperty systemPropertie this.systemProperties = systemProperties; } + private Provider> getLooselyCoupledConfigurableFileCollectionElements(final ConfigurableFileCollection collection) { + //This is needed because the returned providers should be transformable. Which they are not by default. + //You can call get() on the provider returned by getElements, without any issue directly, regardless of task evaluation state. + //However, if you then transform the provider and call get() on the returned provider then a task state check is additionally added, and + //The execution crashes, even though we are not interested in the execution, just the locations. + return project.provider(() -> collection.getElements().get()); + } + + @Override + public Provider> getRuntimeClasspathElements() { + return getLooselyCoupledConfigurableFileCollectionElements(getRuntimeClasspath()); + } + + @Override + public Provider> getTestRuntimeClasspathElements() { + return getLooselyCoupledConfigurableFileCollectionElements(getTestRuntimeClasspath()); + } + + @Override + public Provider> getCompileClasspathElements() { + return getLooselyCoupledConfigurableFileCollectionElements(getCompileClasspath()); + } + + @Override + public Provider> getTestCompileClasspathElements() { + return getLooselyCoupledConfigurableFileCollectionElements(getTestCompileClasspath()); + } + + @Override + public void runType(@NotNull String string) { + configure(string); + } + @Override public final void configure() { if (getConfigureFromTypeWithName().get()) { - runTypes.add(getRunTypeByName(name)); + runTypes.addAll(getRunTypesByName(name)); } getEnvironmentVariables().putAll(runTypes.flatMap(TransformerUtils.combineAllMaps( @@ -194,16 +255,62 @@ public final void configure() { 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( + getRuntimeClasspath().from(runTypes.map(TransformerUtils.combineFileCollections( getProject(), RunType::getClasspath ))); + + final Set unconfiguredSourceSets = new HashSet<>(); + final Set> configuredDefinitions = new HashSet<>(); + + getModSources().whenSourceSetAdded(sourceSet -> { + // Only configure the run if the source set is from the same project + if (SourceSetUtils.getProject(sourceSet) != getProject()) + return; + + try { + final Optional> definition = TaskDependencyUtils.findRuntimeDefinition(sourceSet); + definition.ifPresentOrElse(def -> { + if (configuredDefinitions.add(def)) { + def.configureRun(this); + } + }, () -> unconfiguredSourceSets.add(sourceSet)); + } catch (MultipleDefinitionsFoundException e) { + throw new RuntimeException("Failed to configure run: " + getName() + " there are multiple runtime definitions found for the source set: " + sourceSet.getName(), e); + } + }); + + final DependencyReplacement replacementLogic = project.getExtensions().getByType(DependencyReplacement.class); + replacementLogic.whenDependencyReplaced((virtualDependency, targetConfiguration, originalDependency) -> { + if (unconfiguredSourceSets.isEmpty()) { + return; + } + + for (final Iterator iterator = unconfiguredSourceSets.iterator(); iterator.hasNext(); ) { + SourceSet unconfiguredSourceSet = iterator.next(); + // Only configure the run if the source set is from the same project + if (SourceSetUtils.getProject(unconfiguredSourceSet) != getProject()) + return; + + try { + final Optional> definition = TaskDependencyUtils.findRuntimeDefinition(unconfiguredSourceSet); + definition.ifPresent(def -> { + if (configuredDefinitions.add(def)) { + def.configureRun(this); + } + iterator.remove(); + }); + } catch (MultipleDefinitionsFoundException e) { + throw new RuntimeException("Failed to configure run: " + getName() + " there are multiple runtime definitions found for the source set: " + unconfiguredSourceSet.getName(), e); + } + } + }); } @Override public final void configure(final @NotNull String name) { getConfigureFromTypeWithName().set(false); // Don't re-configure - runTypes.add(getRunTypeByName(name)); + runTypes.addAll(getRunTypesByName(name)); } @Override @@ -234,18 +341,29 @@ public List realiseJvmArguments() { return args; } - @SuppressWarnings("unchecked") - private Provider getRunTypeByName(String name) { - NamedDomainObjectContainer runTypes = (NamedDomainObjectContainer) project.getExtensions() - .getByName(RunsConstants.Extensions.RUN_TYPES); + private Provider> getRunTypesByName(String name) { + RunTypeManager runTypes = project.getExtensions().getByType(RunTypeManager.class); return project.provider(() -> { if (runTypes.getNames().contains(name)) { - return runTypes.getByName(name); + return List.of(runTypes.getByName(name)); } else { - throw new GradleException("Could not find run type " + name + ". Available run types: " + - runTypes.getNames()); + return null; + } + }).orElse( + TransformerUtils.ifTrue(getConfigureFromDependencies(), + getCompileClasspathElements() + .map(files -> files.stream() + .map(FileSystemLocation::getAsFile) + .map(runTypes::parse) + .flatMap(Collection::stream) + .filter(runType -> runType.getName().equals(name)) + .toList() + ))).map(types -> { + if (types.isEmpty()) { + throw new GradleException("No run type found with name: " + name); } + return types; }); } } diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java index 414fd0c22..4f31fdc04 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java +++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java @@ -5,6 +5,7 @@ import net.minecraftforge.gdi.annotations.DSLProperty; import net.neoforged.gradle.common.util.SourceSetUtils; import net.neoforged.gradle.dsl.common.runs.run.RunSourceSets; +import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; @@ -13,12 +14,15 @@ import org.gradle.api.tasks.SourceSet; import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; public abstract class RunSourceSetsImpl implements RunSourceSets { private final Project project; private final Provider> provider; private final Multimap sourceSets; + private final List> callbacks = new ArrayList<>(); @Inject public RunSourceSetsImpl(Project project) { @@ -31,6 +35,10 @@ public RunSourceSetsImpl(Project project) { @Override public void add(SourceSet sourceSet) { this.sourceSets.put(SourceSetUtils.getModIdentifier(sourceSet, null), sourceSet); + + for (Action callback : callbacks) { + callback.execute(sourceSet); + } } @Override @@ -50,6 +58,10 @@ public void add(SourceSet... sourceSets) { @Override public void local(SourceSet sourceSet) { this.sourceSets.put(SourceSetUtils.getModIdentifier(sourceSet, project), sourceSet); + + for (Action callback : callbacks) { + callback.execute(sourceSet); + } } @Override @@ -69,11 +81,21 @@ public void local(SourceSet... sourceSets) { @Override public void add(String groupId, SourceSet sourceSet) { this.sourceSets.put(groupId, sourceSet); + + for (Action callback : callbacks) { + callback.execute(sourceSet); + } } @Override public void add(String groupId, Iterable sourceSets) { this.sourceSets.putAll(groupId, sourceSets); + + for (SourceSet sourceSet : sourceSets) { + for (Action callback : callbacks) { + callback.execute(sourceSet); + } + } } @Override @@ -93,4 +115,12 @@ public void add(String groupId, SourceSet... sourceSets) { public Provider> all() { return this.provider; } + + @Override + public void whenSourceSetAdded(Action action) { + this.callbacks.add(action); + for (SourceSet value : this.sourceSets.values()) { + action.execute(value); + } + } } diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTypeManagerImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTypeManagerImpl.java new file mode 100644 index 000000000..0ea1140ae --- /dev/null +++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTypeManagerImpl.java @@ -0,0 +1,46 @@ +package net.neoforged.gradle.common.runs.run; + +import net.neoforged.gradle.common.util.DelegatingDomainObjectContainer; +import net.neoforged.gradle.dsl.common.runs.type.RunType; +import net.neoforged.gradle.dsl.common.runs.type.RunTypeManager; +import org.gradle.api.Project; + +import javax.inject.Inject; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class RunTypeManagerImpl extends DelegatingDomainObjectContainer implements RunTypeManager { + + private final List parsers = new ArrayList<>(); + + @Inject + public RunTypeManagerImpl(Project project) { + super(project.getObjects().domainObjectContainer(RunType.class)); + } + + @Override + public Collection parse(File file) { + if (!file.exists()) + return Collections.emptyList(); + + return parsers.stream() + .flatMap(parser -> { + try { + return parser.parse(file).stream(); + } catch (Exception exception) { + return Stream.empty(); + } + }) + .collect(Collectors.toSet()); + } + + @Override + public void registerParser(Parser parser) { + parsers.add(parser); + } +} diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunsReport.java b/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunsReport.java index 83c38c1c5..91d31965c 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunsReport.java +++ b/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunsReport.java @@ -101,7 +101,7 @@ public RenderableRun(Run run) { this.isGameTest = run.getIsGameTest().get(); this.modSources = run.getModSources().all().get(); this.unitTestSources = run.getUnitTestSources().all().get(); - this.classpath = run.getClasspath().getFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet()); + this.classpath = run.getRuntimeClasspath().getFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet()); this.dependencies = run.getDependencies().get().getRuntimeConfiguration().getFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet()); } 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 996c0a495..b3433f14c 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 @@ -13,13 +13,19 @@ import net.neoforged.gradle.dsl.common.tasks.WithOutput; import net.neoforged.gradle.dsl.common.util.CommonRuntimeUtils; import net.neoforged.gradle.dsl.common.util.GameArtifact; +import net.neoforged.gradle.util.TransformerUtils; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.Directory; +import org.gradle.api.file.RegularFile; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.NotNull; +import javax.sql.rowset.spi.TransactionalWriter; +import java.io.File; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -56,7 +62,7 @@ public abstract class CommonRuntimeDefinition versionJson; protected CommonRuntimeDefinition( @NotNull final S specification, @@ -66,7 +72,7 @@ protected CommonRuntimeDefinition( @NotNull final Map> gameArtifactProvidingTasks, @NotNull final Configuration minecraftDependenciesConfiguration, @NotNull final Consumer> associatedTaskConsumer, - @NotNull final VersionJson versionJson) { + @NotNull final Provider versionJson) { this.specification = specification; this.taskOutputs = taskOutputs; this.sourceJarTask = sourceJarTask; @@ -150,7 +156,7 @@ public void configureAssociatedTask(@NotNull TaskProvider run public abstract TaskProvider getNatives(); @NotNull - public VersionJson getVersionJson() { + public Provider getVersionJson() { return versionJson; } @@ -161,66 +167,89 @@ public final ConfigurableFileCollection getAllDependencies() { } public void configureRun(RunImpl run) { - final Map runtimeInterpolationData = buildRunInterpolationData(run); + final MapProperty runtimeInterpolationData = getSpecification().getProject().getObjects().mapProperty(String.class, String.class); + buildRunInterpolationData(run, runtimeInterpolationData); - final Map workingInterpolationData = new HashMap<>(runtimeInterpolationData); - workingInterpolationData.put("source_roots", RunsUtil.buildGradleModClasses(run.getModSources().all()).get()); + runtimeInterpolationData.put("source_roots", RunsUtil.buildGradleModClasses(run.getModSources().all())); - if (run.getIsClient().get()) { - getVersionJson().getPlatformJvmArgs().forEach(arg -> run.getJvmArguments().add(arg)); - } - - run.overrideJvmArguments(interpolate(run.getJvmArguments(), workingInterpolationData)); - run.overrideProgramArguments(interpolate(run.getProgramArguments(), workingInterpolationData)); - run.overrideEnvironmentVariables(interpolate(run.getEnvironmentVariables(), workingInterpolationData)); - run.overrideSystemProperties(interpolate(run.getSystemProperties(), workingInterpolationData)); - - if (run.getIsClient().get() || run.getIsDataGenerator().get()) { - run.getDependsOn().add(getAssets()); - run.getDependsOn().add(getNatives()); - } - } + run.getJvmArguments().addAll( + TransformerUtils.ifTrue( + run.getIsClient(), + getVersionJson().map(VersionJson::getPlatformJvmArgs) + ) + ); + + run.overrideJvmArguments(interpolate(run.getJvmArguments(), runtimeInterpolationData)); + run.overrideProgramArguments(interpolate(run.getProgramArguments(), runtimeInterpolationData)); + run.overrideEnvironmentVariables(interpolate(run.getEnvironmentVariables(), runtimeInterpolationData)); + run.overrideSystemProperties(interpolate(run.getSystemProperties(), runtimeInterpolationData)); - protected Map buildRunInterpolationData(RunImpl run) { - final Map interpolationData = Maps.newHashMap(); + run.getDependsOn().addAll( + TransformerUtils.ifTrue( + run.getIsClient().flatMap(TransformerUtils.or(run.getIsDataGenerator())), + getAssets(), + getNatives() + ) + ); + } + protected void buildRunInterpolationData(RunImpl run, @NotNull MapProperty interpolationData) { interpolationData.put("runtime_name", specification.getVersionedName()); interpolationData.put("mc_version", specification.getMinecraftVersion()); - interpolationData.put("assets_root", DownloadAssets.getAssetsDirectory(specification.getProject(), specification.getProject().provider(this::getVersionJson)).get().getAsFile().getAbsolutePath()); - interpolationData.put("asset_index", getAssets().get().getAssetIndexFile().get().getAsFile().getName().substring(0, getAssets().get().getAssetIndexFile().get().getAsFile().getName().lastIndexOf('.'))); - interpolationData.put("natives", getNatives().get().getOutputDirectory().get().getAsFile().getAbsolutePath()); + interpolationData.put("assets_root", DownloadAssets.getAssetsDirectory(specification.getProject(), this.getVersionJson()) + .map(Directory::getAsFile) + .map(File::getAbsolutePath)); - return interpolationData; + interpolationData.put("asset_index", + getAssets().flatMap(DownloadAssets::getAssetIndexTargetFile).map(RegularFile::getAsFile).map(File::getName).map(s -> s.substring(0, s.lastIndexOf('.')))); + interpolationData.put("natives", getNatives().flatMap(ExtractNatives::getOutputDirectory).map(Directory::getAsFile).map(File::getAbsolutePath)); } - protected ListProperty interpolate(final ListProperty input, final Map values) { + protected ListProperty interpolate(final ListProperty input, final MapProperty values) { return interpolate(input, values, ""); } - protected ListProperty interpolate(final ListProperty input, final Map values, String patternPrefix) { + protected ListProperty interpolate(final ListProperty input, final MapProperty values, String patternPrefix) { final ListProperty delegated = getSpecification().getProject().getObjects().listProperty(String.class); - delegated.set(input.map(list -> list.stream().map(s -> interpolate(s, values, patternPrefix)).collect(Collectors.toList()))); + delegated.set(input.flatMap(list -> { + final ListProperty interpolated = getSpecification().getProject().getObjects().listProperty(String.class); + for (String s : list) { + interpolated.add(interpolate(s, values, patternPrefix)); + } + + return interpolated; + })); return delegated; } - protected MapProperty interpolate(final MapProperty input, final Map values) { + protected MapProperty interpolate(final MapProperty input, final MapProperty values) { return interpolate(input, values, ""); } - protected MapProperty interpolate(final MapProperty input, final Map values, String patternPrefix) { + protected MapProperty interpolate(final MapProperty input, final MapProperty values, String patternPrefix) { final MapProperty delegated = getSpecification().getProject().getObjects().mapProperty(String.class, String.class); - delegated.set(input.map(list -> list.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> interpolate(e.getValue(), values, patternPrefix))))); + delegated.set(input.flatMap(map -> { + final MapProperty interpolated = getSpecification().getProject().getObjects().mapProperty(String.class, String.class); + + for (final Map.Entry entry : map.entrySet()) { + interpolated.put(entry.getKey(), interpolate(entry.getValue(), values, patternPrefix)); + } + + return interpolated; + })); return delegated; } - private static String interpolate(final String input, final Map values, String patternPrefix) { + private static Provider interpolate(final String input, final MapProperty values, String patternPrefix) { if (input == null) throw new IllegalArgumentException("Input cannot be null"); - String result = input; - for (final Map.Entry entry : values.entrySet()) { - result = result.replace(patternPrefix + "{" + entry.getKey() + "}", entry.getValue()); - } - return result; + return values.map(data -> { + String result = input; + for (final Map.Entry entry : data.entrySet()) { + result = result.replace(patternPrefix + "{" + entry.getKey() + "}", entry.getValue()); + } + return result; + }); } } diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/extensions/CommonRuntimeExtension.java b/common/src/main/java/net/neoforged/gradle/common/runtime/extensions/CommonRuntimeExtension.java index 6ab9cba56..07b0c0697 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/extensions/CommonRuntimeExtension.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/extensions/CommonRuntimeExtension.java @@ -2,7 +2,6 @@ import com.google.common.collect.Maps; import net.neoforged.gradle.common.runtime.definition.CommonRuntimeDefinition; -import net.neoforged.gradle.common.runtime.definition.IDelegatingRuntimeDefinition; import net.neoforged.gradle.common.runtime.specification.CommonRuntimeSpecification; import net.neoforged.gradle.common.runtime.tasks.DownloadAssets; import net.neoforged.gradle.common.runtime.tasks.ExtractNatives; @@ -117,9 +116,14 @@ public final D create(final Action configurator) { final D runtime = doCreate(spec); definitions.put(spec.getIdentifier(), runtime); + afterRegistration(runtime); return runtime; } + protected void afterRegistration(D runtime) { + // no-op + } + @Override public D create(Dependency dependency, Action configurator) { final D result = create(configurator); @@ -185,17 +189,13 @@ public Set findIn(final Configuration configuration) { .collect(Collectors.toSet()); } - protected final TaskProvider createDownloadAssetsTasks(final CommonRuntimeSpecification specification, final Map symbolicDataSources, final File runtimeDirectory, final VersionJson versionJson) { + protected final TaskProvider createDownloadAssetsTasks(final CommonRuntimeSpecification specification, final Provider versionJson) { return specification.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(specification, "downloadAssets"), DownloadAssets.class, task -> { task.getVersionJson().set(versionJson); }); } - protected final TaskProvider createDownloadAssetsTasks(final CommonRuntimeSpecification specification, final File runtimeDirectory, final VersionJson versionJson) { - return createDownloadAssetsTasks(specification, Collections.emptyMap(), runtimeDirectory, versionJson); - } - - protected final TaskProvider createExtractNativesTasks(final CommonRuntimeSpecification specification, final Map symbolicDataSources, final File runtimeDirectory, final VersionJson versionJson) { + protected final TaskProvider createExtractNativesTasks(final CommonRuntimeSpecification specification, final Map symbolicDataSources, final File runtimeDirectory, final Provider versionJson) { return specification.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(specification, "extractNatives"), ExtractNatives.class, task -> { task.getVersionJson().set(versionJson); @@ -204,7 +204,7 @@ protected final TaskProvider createExtractNativesTasks(final Com }); } - protected final TaskProvider createExtractNativesTasks(final CommonRuntimeSpecification specification, final File runtimeDirectory, final VersionJson versionJson) { + protected final TaskProvider createExtractNativesTasks(final CommonRuntimeSpecification specification, final File runtimeDirectory, final Provider versionJson) { return createExtractNativesTasks(specification, Collections.emptyMap(), runtimeDirectory, versionJson); } } diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java index 7ca3fb365..ecb565ec8 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java @@ -37,7 +37,8 @@ public DownloadAssets() { this.assetsCache = getAssetsDirectory(getProject(), getVersionJson()); getAssetIndex().convention("asset-index"); getAssetIndexFileName().convention(getAssetIndex().map(index -> index + ".json")); - getAssetIndexFile().convention(getRegularFileInAssetsDirectory(getAssetIndexFileName().map(name -> "indexes/" + name))); + getAssetIndexTargetFile().convention(getRegularFileInAssetsDirectory(getAssetIndexFileName().map(name -> "indexes/" + name))); + getAssetIndexFile().convention(getAssetIndexTargetFile()); getVersionJson().convention(getVersionJsonFile().map(TransformerUtils.guard(file -> VersionJson.get(file.getAsFile())))); getAssetRepository().convention("https://resources.download.minecraft.net/"); getIsOffline().convention(getProject().getGradle().getStartParameter().isOffline()); @@ -123,6 +124,9 @@ private void downloadAssets() { @Input public abstract Property getAssetRepository(); + @Internal + public abstract RegularFileProperty getAssetIndexTargetFile(); + @OutputFile public abstract RegularFileProperty getAssetIndexFile(); diff --git a/common/src/main/java/net/neoforged/gradle/common/tasks/UnpackBundledServer.java b/common/src/main/java/net/neoforged/gradle/common/tasks/UnpackBundledServer.java index e29f5c6b5..2119ec365 100644 --- a/common/src/main/java/net/neoforged/gradle/common/tasks/UnpackBundledServer.java +++ b/common/src/main/java/net/neoforged/gradle/common/tasks/UnpackBundledServer.java @@ -12,9 +12,7 @@ @CacheableTask public abstract class UnpackBundledServer extends NeoGradleBase implements WithOutput { - - - + @TaskAction public void doUnpack() throws Exception { final File serverJar = getServerJar().get().getAsFile(); diff --git a/common/src/main/java/net/neoforged/gradle/common/util/BundledServerUtils.java b/common/src/main/java/net/neoforged/gradle/common/util/BundledServerUtils.java index 57e876b25..84319f30e 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/BundledServerUtils.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/BundledServerUtils.java @@ -1,6 +1,7 @@ package net.neoforged.gradle.common.util; import org.apache.commons.io.IOUtils; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileOutputStream; @@ -45,7 +46,11 @@ public static List getBundledDependencies(final File serverJar) { } } + @Nullable public static String getBundledMainClass(final File serverJar) { + if (!isBundledServer(serverJar)) + return null; + try(final ZipFile file = new ZipFile(serverJar)) { final InputStream inputStream = file.getInputStream(file.getEntry("META-INF/main-class")); final String mainClass = IOUtils.readLines(inputStream, Charset.defaultCharset()).get(0); diff --git a/common/src/main/java/net/neoforged/gradle/common/util/ConfigurationPhaseFileUtils.java b/common/src/main/java/net/neoforged/gradle/common/util/ConfigurationPhaseFileUtils.java index 8c2da35cc..e2e69964f 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/ConfigurationPhaseFileUtils.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/ConfigurationPhaseFileUtils.java @@ -3,13 +3,17 @@ import java.io.File; import java.io.FileFilter; import java.io.IOException; +import java.io.OutputStream; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.nio.channels.ByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.file.*; import java.nio.file.attribute.FileAttribute; import java.util.EnumSet; import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; public class ConfigurationPhaseFileUtils { @@ -18,6 +22,7 @@ public class ConfigurationPhaseFileUtils { private static final MethodType FILE_ARRAY_RETURN_TYPE_FILE_FILTER_ARGS = MethodType.methodType(File[].class, FileFilter.class); private static final MethodType BOOL_RETURN_TYPE_PATH_LINK_OPTIONS_ARRAY_ARGS = MethodType.methodType(boolean.class, Path.class, LinkOption[].class); private static final MethodType SEEKABLE_BYTE_CHANNEL_RETURN_TYPE_PATH_SET_OPEN_OPTION_FILE_ATTRIBUTE_ARRAY_ARGS = MethodType.methodType(SeekableByteChannel.class, Path.class, Set.class, FileAttribute[].class); + private static final MethodType OUTPUT_STREAM_RETURN_TYPE_PATH_OPEN_OPTIONS_ARRAY_ARGS = MethodType.methodType(OutputStream.class, Path.class, OpenOption[].class); public static boolean exists(File file) { try { @@ -51,18 +56,28 @@ public static boolean isRegularFile(Path path, LinkOption... options) { } } - public static Path createFile(Path path) throws IOException { - EnumSet options = - EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); - newByteChannel(path, options).close(); + public static Path createEmptyZipFile(Path path) throws IOException { + OpenOption[] options = + EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE).toArray(new OpenOption[2]); + final OutputStream outStream = newByteChannel(path, options); + final ZipOutputStream zipOutputStream = new ZipOutputStream(outStream); + + final ZipEntry entry = new ZipEntry("readme.md"); + + zipOutputStream.putNextEntry(entry); + + byte[] data = "The zip file containing this readme, will be generated by a gradle task".getBytes(); + zipOutputStream.write(data, 0, data.length); + zipOutputStream.closeEntry(); + zipOutputStream.close(); + + outStream.close(); return path; } - public static SeekableByteChannel newByteChannel(Path path, - Set options, - FileAttribute... attrs) { + public static OutputStream newByteChannel(Path path, OpenOption... options) { try { - return (SeekableByteChannel) LOOKUP.findStatic(Files.class, "newByteChannel", SEEKABLE_BYTE_CHANNEL_RETURN_TYPE_PATH_SET_OPEN_OPTION_FILE_ATTRIBUTE_ARRAY_ARGS).invoke(path, options, attrs); + return (OutputStream) LOOKUP.findStatic(Files.class, "newOutputStream", OUTPUT_STREAM_RETURN_TYPE_PATH_OPEN_OPTIONS_ARRAY_ARGS).invoke(path, options); } catch (Throwable e) { return null; } 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 bc0154828..613b197f9 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 @@ -233,7 +233,7 @@ private void processTask(Task task) { } private void processConfiguration(Configuration configuration) { - DependencySet dependencies = configuration.getAllDependencies(); + DependencySet dependencies = configuration.getDependencies(); //Grab the original dependencies if we have a replacement extension final DependencyReplacement replacement = project.getExtensions().findByType(DependencyReplacement.class); @@ -251,6 +251,8 @@ private void processConfiguration(Configuration configuration) { return false; } }).forEach(this::add); + + configuration.getExtendsFrom().forEach(this::add); } private void processSourceDirectorySet(SourceDirectorySet sourceDirectorySet) { diff --git a/common/src/main/java/net/neoforged/gradle/common/util/VersionJson.java b/common/src/main/java/net/neoforged/gradle/common/util/VersionJson.java index 19209b939..7884a559b 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/VersionJson.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/VersionJson.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList; import com.google.gson.*; import net.neoforged.gradle.dsl.common.util.Artifact; +import org.gradle.api.file.RegularFile; import java.io.*; import java.lang.reflect.Type; @@ -162,6 +163,10 @@ public String getMainClass() { return mainClass; } + public static VersionJson get(RegularFile regularFile) throws IOException { + return get(regularFile.getAsFile()); + } + public static class JavaVersion implements Serializable { private String component; private String majorVersion; diff --git a/common/src/main/java/net/neoforged/gradle/common/util/constants/RunsConstants.java b/common/src/main/java/net/neoforged/gradle/common/util/constants/RunsConstants.java index 3a0c96cc9..a86603740 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/constants/RunsConstants.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/constants/RunsConstants.java @@ -8,6 +8,5 @@ private RunsConstants() { public static final class Extensions { public static final String RUNS = "runs"; - public static final String RUN_TYPES = "runTypes"; } } 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 1b2aefad1..755ed39a0 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 @@ -77,7 +77,7 @@ public static void createTasks(Project project, Run run) { .map(SourceSet::getRuntimeClasspath) .forEach(runExec::classpath); runExec.classpath(run.getDependencies().get().getRuntimeConfiguration()); - runExec.classpath(run.getClasspath()); + runExec.classpath(run.getRuntimeClasspath()); updateRunExecClasspathBasedOnPrimaryTask(runExec, run); @@ -111,13 +111,7 @@ public static void configureModClasses(Run run) { } public static Run create(final Project project, final String name) { - final RunImpl run = project.getObjects().newInstance(RunImpl.class, project, name); - - ProjectUtils.afterEvaluate(project, () -> { - - }); - - return run; + return project.getObjects().newInstance(RunImpl.class, project, name); } private static void updateRunExecClasspathBasedOnPrimaryTask(final JavaExec runExec, final Run run) { diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/MinecraftArtifactCache.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/MinecraftArtifactCache.groovy index 72ce98585..42c9478d4 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/MinecraftArtifactCache.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/MinecraftArtifactCache.groovy @@ -34,16 +34,6 @@ interface MinecraftArtifactCache extends BaseDSLElement @NotNull Map getCacheFiles(); - /** - * Caches an entire game version eagerly. - * - * @param gameVersion The game version to cache. - * @param side The distribution side to cache. - * @return A map which contains all cached files. - */ - @NotNull - Map cacheGameVersion(@NotNull String gameVersion, @NotNull DistributionType side); - /** * Caches an entire game version lazily. * @@ -71,7 +61,7 @@ interface MinecraftArtifactCache extends BaseDSLElement * @return The cached game versions manifest. */ @NotNull - File cacheVersionManifest(@NotNull String gameVersion); + Provider cacheVersionManifest(@NotNull String gameVersion); /** * Eagerly caches the given artifact of the given game version. diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/dependency/replacement/DependencyReplacement.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/dependency/replacement/DependencyReplacement.groovy index 27bdc8074..97ed9e006 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/dependency/replacement/DependencyReplacement.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/dependency/replacement/DependencyReplacement.groovy @@ -3,6 +3,7 @@ package net.neoforged.gradle.dsl.common.extensions.dependency.replacement import groovy.transform.CompileStatic import net.minecraftforge.gdi.BaseDSLElement import net.minecraftforge.gdi.annotations.DSLProperty +import org.gradle.api.Action import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency @@ -39,4 +40,26 @@ interface DependencyReplacement extends BaseDSLElement { */ @NotNull Dependency optionallyConvertBackToOriginal(Dependency dependency, Configuration configuration) + + /** + * Invoked when a dependency is replaced. + * + * @param dependencyAction The action to invoke when a dependency is replaced, it is given the old dependency as argument. + */ + void whenDependencyReplaced(DependencyReplacedCallback dependencyAction); + + /** + * Callback definition for when a dependency is replaced. + */ + interface DependencyReplacedCallback { + + /** + * Invoked when a dependency is replaced. + * + * @param virtualDependency The virtual dependency. + * @param targetConfiguration The target configuration in which the virtual dependency resides. + * @param originalDependency The original dependency. + */ + void apply(Dependency virtualDependency, Configuration targetConfiguration, Dependency originalDependency); + } } 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 0dd768965..e183bbc2a 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 @@ -1,5 +1,7 @@ package net.neoforged.gradle.dsl.common.extensions.dependency.replacement +import groovy.transform.CompileStatic +import net.minecraftforge.gdi.annotations.DefaultMethods import net.neoforged.gradle.dsl.common.tasks.WithOutput import org.gradle.api.artifacts.ExternalModuleDependency import org.gradle.api.tasks.TaskProvider @@ -7,6 +9,8 @@ import org.gradle.api.tasks.TaskProvider /** * Defines an object that is aware of dynamic dependency replacement. */ +@CompileStatic +@DefaultMethods interface ReplacementAware { /** @@ -33,5 +37,5 @@ interface ReplacementAware { * 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() + default 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 e87ee3abe..e8366f133 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,11 +7,11 @@ 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 import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemLocation import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -227,15 +227,96 @@ interface Run extends BaseDSLElement, NamedDSLElement { /** * Gives access to the classpath for this run. - * Does not contain the full classpath since that is dependent on the actual run environment, but contains the additional classpath elements - * needed to run the game with this run. * * @return The property which holds the classpath. */ @InputFiles @Classpath @DSLProperty - abstract ConfigurableFileCollection getClasspath(); + abstract ConfigurableFileCollection getRuntimeClasspath(); + + /** + * Gives access to the runtime classpath elements for this run. + * + * @return A provider that provides the classpath elements. + * @implNote This is a loosely coupled provider, because if you call {@link ConfigurableFileCollection#getElements()} directly, it will return a provider that is not transformable. + */ + @Internal + abstract Provider> getRuntimeClasspathElements() + + /** + * Gives access to the classpath for this run. + * + * @return The property which holds the classpath. + */ + @InputFiles + @Classpath + @DSLProperty + abstract ConfigurableFileCollection getTestRuntimeClasspath(); + + /** + * Gives access to the test runtime classpath elements for this run. + * + * @return A provider that provides the classpath elements. + * @implNote This is a loosely coupled provider, because if you call {@link ConfigurableFileCollection#getElements()} directly, it will return a provider that is not transformable. + */ + @Internal + abstract Provider> getTestRuntimeClasspathElements() + + /** + * Gives access to the compile classpath classpath for this run. + * + * @return The property which holds the compile classpath. + */ + @InputFiles + @Classpath + @DSLProperty + abstract ConfigurableFileCollection getCompileClasspath(); + + /** + * Gives access to the compile classpath elements for this run. + * + * @return A provider that provides the classpath elements. + * @implNote This is a loosely coupled provider, because if you call {@link ConfigurableFileCollection#getElements()} directly, it will return a provider that is not transformable. + */ + @Internal + abstract Provider> getCompileClasspathElements() + + /** + * Gives access to the compile classpath classpath for this run. + * + * @return The property which holds the compile classpath. + */ + @InputFiles + @Classpath + @DSLProperty + abstract ConfigurableFileCollection getTestCompileClasspath() + + /** + * Gives access to the test compile classpath elements for this run. + * + * @return A provider that provides the classpath elements. + * @implNote This is a loosely coupled provider, because if you call {@link ConfigurableFileCollection#getElements()} directly, it will return a provider that is not transformable. + */ + @Internal + abstract Provider> getTestCompileClasspathElements() + + /** + * Defines the run types that are applied to this run. + * + * @return The run types that are applied to this run. + */ + @Nested + @DSLProperty + @Optional + abstract ListProperty getRunTypes(); + + /** + * Adds a run type to this run using the run type name. + * + * @param runType The run type to add. + */ + void runType(@NotNull final String string); /** * Defines the custom dependency handler for each run. diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunSourceSets.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunSourceSets.groovy index 45d7a388e..e2c966265 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunSourceSets.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunSourceSets.groovy @@ -4,6 +4,7 @@ import com.google.common.collect.Multimap import groovy.transform.CompileStatic import net.minecraftforge.gdi.ConfigurableDSLElement import net.minecraftforge.gdi.annotations.DSLProperty +import org.gradle.api.Action import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider @@ -109,4 +110,11 @@ interface RunSourceSets extends ConfigurableDSLElement { */ @Internal Provider> all(); + + /** + * Registers a callback that get called when a source set is added to this run + * + * @param action The action to call + */ + void whenSourceSetAdded(Action action) } diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunAdapter.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunAdapter.groovy deleted file mode 100644 index e65cb1657..000000000 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunAdapter.groovy +++ /dev/null @@ -1,16 +0,0 @@ -package net.neoforged.gradle.dsl.common.runs.type - -import groovy.transform.CompileStatic -import net.neoforged.gradle.dsl.common.runs.run.Run - -@CompileStatic -@FunctionalInterface -interface RunAdapter { - - /** - * Invoked to configure a run which has the type that owns this adapter. - * - * @param run The run adapter. - */ - void adapt(Run run); -} \ No newline at end of file diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunTypeManager.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunTypeManager.groovy new file mode 100644 index 000000000..075c292a1 --- /dev/null +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/type/RunTypeManager.groovy @@ -0,0 +1,32 @@ +package net.neoforged.gradle.dsl.common.runs.type + +import groovy.transform.CompileStatic +import org.gradle.api.NamedDomainObjectCollection +import org.gradle.api.NamedDomainObjectContainer + +@CompileStatic +interface RunTypeManager extends NamedDomainObjectContainer { + + /** + * Parses the given file and returns a collection of run types. + * + * @param file The file to parse + * @return The collection of run types + */ + Collection parse(File file); + + /** + * Registers a parser for the given file extension. + * + * @param parser The parser + */ + void registerParser(Parser parser); + + /** + * Represents a parser of run types that can be loaded from a file. + */ + static interface Parser { + + Collection parse(File file); + } +} diff --git a/gradle.properties b/gradle.properties index f57a53cb0..956b69d93 100644 --- a/gradle.properties +++ b/gradle.properties @@ -37,4 +37,4 @@ spock_version=2.1 spock_groovy_version=3.0 mockito_version=4.11.0 jimfs_version=1.2 -trainingwheels_version=1.0.49 \ No newline at end of file +trainingwheels_version=1.0.50 \ No newline at end of file diff --git a/gradlew b/gradlew index b740cf133..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/img.png b/img.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c040c5e693913d33a3d3b5621dadb543f292cf GIT binary patch literal 64198 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 OJ*rBYiskZVLH`G1k0Yu8 literal 0 HcmV?d00001 diff --git a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/definition/NeoFormRuntimeDefinition.java b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/definition/NeoFormRuntimeDefinition.java index 15bc5e666..43bd3fe78 100644 --- a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/definition/NeoFormRuntimeDefinition.java +++ b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/definition/NeoFormRuntimeDefinition.java @@ -13,6 +13,8 @@ import net.neoforged.gradle.dsl.neoform.runtime.definition.NeoFormDefinition; import net.neoforged.gradle.neoform.runtime.specification.NeoFormRuntimeSpecification; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.NotNull; @@ -38,7 +40,7 @@ public NeoFormRuntimeDefinition(@NotNull NeoFormRuntimeSpecification specificati @NotNull Map> gameArtifactProvidingTasks, @NotNull Configuration minecraftDependenciesConfiguration, @NotNull Consumer> associatedTaskConsumer, - @NotNull VersionJson versionJson, + @NotNull Provider versionJson, @NotNull NeoFormConfigConfigurationSpecV2 neoform, @NotNull TaskProvider assetsTaskProvider, @NotNull TaskProvider nativesTaskProvider) { @@ -83,14 +85,12 @@ public int hashCode() { } @Override - public Map buildRunInterpolationData(RunImpl run) { - final Map interpolationData = new HashMap<>(super.buildRunInterpolationData(run)); - + public void buildRunInterpolationData(RunImpl run, MapProperty interpolationData) { + super.buildRunInterpolationData(run, interpolationData); interpolationData.put("mcp_version", neoform.getVersion()); // NeoForge still references this in the environment variable MCP_MAPPINGS, which is unused since 1.20.2 // Remove this interpolation placeholder once NeoForge removes the environment variable from its config.json interpolationData.put("mcp_mappings", "UNUSED_DEPRECATED"); - return interpolationData; } @NotNull 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 1107bd856..38488f9e4 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 @@ -30,10 +30,10 @@ import net.neoforged.gradle.neoform.runtime.tasks.*; import net.neoforged.gradle.neoform.util.NeoFormRuntimeConstants; import net.neoforged.gradle.neoform.util.NeoFormRuntimeUtils; +import net.neoforged.gradle.util.TransformerUtils; import org.apache.commons.lang3.StringUtils; import org.gradle.api.GradleException; import org.gradle.api.Project; -import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFile; @@ -46,14 +46,7 @@ import javax.annotation.Nullable; import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; @SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "unused"}) // API Design @@ -104,7 +97,7 @@ private static TaskProvider createBuiltIn(final NeoFormRun case "listLibraries": return spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, step.getName()), ListLibraries.class, task -> { task.getDownloadedVersionJsonFile() - .fileProvider(task.newProvider(cache.cacheVersionManifest(spec.getMinecraftVersion()))); + .fileProvider(cache.cacheVersionManifest(spec.getMinecraftVersion())); }); case "inject": return spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, step.getName()), InjectZipContent.class, task -> { @@ -259,24 +252,23 @@ protected NeoFormRuntimeDefinition doCreate(final NeoFormRuntimeSpecification sp final File minecraftCache = artifactCacheExtension.getCacheDirectory().get().getAsFile(); - final Map gameArtifacts = artifactCacheExtension.cacheGameVersion(spec.getMinecraftVersion(), spec.getDistribution()); + final MinecraftArtifactCache artifactCache = spec.getProject().getExtensions().getByType(MinecraftArtifactCache.class); + final Map> gameArtifactTasks = buildDefaultArtifactProviderTasks(spec); - final VersionJson versionJson; - try { - versionJson = VersionJson.get(gameArtifacts.get(GameArtifact.VERSION_MANIFEST)); - } catch (IOException e) { - throw new RuntimeException(String.format("Failed to read VersionJson from the launcher metadata for the minecraft version: %s", spec.getMinecraftVersion()), e); - } + final Provider versionJson = artifactCache.cacheVersionManifest(spec.getMinecraftVersion()).map(TransformerUtils.guard(VersionJson::get)); final Configuration minecraftDependenciesConfiguration = ConfigurationUtils.temporaryUnhandledConfiguration( spec.getProject().getConfigurations(), "NeoFormMinecraftDependenciesFor" + spec.getIdentifier() ); - for (VersionJson.Library library : versionJson.getLibraries()) { - minecraftDependenciesConfiguration.getDependencies().add( - spec.getProject().getDependencies().create(library.getName()) - ); - } + + minecraftDependenciesConfiguration.getDependencies().addAllLater( + versionJson.map(VersionJson::getLibraries) + .map(libraries -> libraries.stream() + .map(library -> getProject().getDependencies().create(library.getName())) + .toList() + ) + ); final File neoFormDirectory = spec.getProject().getExtensions().getByType(ConfigurationData.class) .getLocation() @@ -290,7 +282,6 @@ protected NeoFormRuntimeDefinition doCreate(final NeoFormRuntimeSpecification sp spec.getProject().getDependencies().create(library) )); - final Map> gameArtifactTasks = buildDefaultArtifactProviderTasks(spec); final Map symbolicDataSources = buildDataFilesMap(neoFormConfig, spec.getDistribution()); @@ -301,7 +292,7 @@ protected NeoFormRuntimeDefinition doCreate(final NeoFormRuntimeSpecification sp task.getOutput().set(new File(neoFormDirectory, "raw.jar")); }); - final NeoFormRuntimeDefinition definition = new NeoFormRuntimeDefinition( + return new NeoFormRuntimeDefinition( spec, new LinkedHashMap<>(), sourceJarTask, @@ -313,15 +304,16 @@ protected NeoFormRuntimeDefinition doCreate(final NeoFormRuntimeSpecification sp }), versionJson, neoFormConfig, - createDownloadAssetsTasks(spec, symbolicDataSources, neoFormDirectory, versionJson), + createDownloadAssetsTasks(spec, versionJson), createExtractNativesTasks(spec, symbolicDataSources, neoFormDirectory, versionJson) ); + } + @Override + protected void afterRegistration(NeoFormRuntimeDefinition runtime) { //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. - ProjectUtils.afterEvaluate(spec.getProject(), () -> this.bakeDefinition(definition)); - - return definition; + ProjectUtils.afterEvaluate(runtime.getSpecification().getProject(), () -> this.bakeDefinition(runtime)); } @Override @@ -473,7 +465,12 @@ protected void bakeDefinition(NeoFormRuntimeDefinition definition) { forkOptions.setJvmArgs(settings.getJvmArgs().get()); task.getOptions().getCompilerArgumentProviders().add(settings.getArgs()::get); - task.getJavaVersion().set(JavaLanguageVersion.of(definition.getVersionJson().getJavaVersion().getMajorVersion())); + task.getJavaVersion().set( + definition.getVersionJson() + .map(VersionJson::getJavaVersion) + .map(VersionJson.JavaVersion::getMajorVersion) + .map(JavaLanguageVersion::of) + ); }); recompileTask.configure(neoFormRuntimeTask -> configureMcpRuntimeTaskWithDefaults(spec, neoFormDirectory, symbolicDataSources, neoFormRuntimeTask)); 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 956435e6a..6ceae24b8 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 @@ -25,6 +25,7 @@ import net.neoforged.gradle.dsl.common.extensions.Mappings; import net.neoforged.gradle.dsl.common.runs.run.Run; import net.neoforged.gradle.dsl.common.runs.type.RunType; +import net.neoforged.gradle.dsl.common.runs.type.RunTypeManager; import net.neoforged.gradle.dsl.common.runtime.naming.TaskBuildingContext; import net.neoforged.gradle.dsl.common.runtime.tasks.Runtime; import net.neoforged.gradle.dsl.common.tasks.WithOutput; @@ -265,7 +266,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re project.getConfigurations().getByName(mainSource.getApiConfigurationName()).extendsFrom(gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, installerConfiguration); project.getConfigurations().getByName(mainSource.getRuntimeClasspathConfigurationName()).extendsFrom(clientExtraConfiguration); - project.getExtensions().configure(RunsConstants.Extensions.RUN_TYPES, (Action>) types -> types.configureEach(type -> configureRunType(project, type, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, runtimeDefinition))); + project.getExtensions().configure(RunTypeManager.class, (Action>) types -> types.configureEach(type -> configureRunType(project, type, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, runtimeDefinition))); project.getExtensions().configure(RunsConstants.Extensions.RUNS, (Action>) runs -> runs.configureEach(run -> configureRun(run, runtimeDefinition))); project.getExtensions().create(net.neoforged.gradle.dsl.common.extensions.JarJar.class, JarJarExtension.EXTENSION_NAME, JarJarExtension.class, project); @@ -965,7 +966,7 @@ private void configureRun(final Run run, final RuntimeDevRuntimeDefinition runti ); }); - Provider assetsDir = DownloadAssets.getAssetsDirectory(project, project.provider(runtimeDefinition::getVersionJson)).map(Directory::getAsFile).map(File::getAbsolutePath); + Provider assetsDir = DownloadAssets.getAssetsDirectory(project, runtimeDefinition.getVersionJson()).map(Directory::getAsFile).map(File::getAbsolutePath); Provider assetIndex = runtimeDefinition.getAssets().flatMap(DownloadAssets::getAssetIndex); run.getProgramArguments().addAll( diff --git a/platform/src/main/java/net/neoforged/gradle/platform/runtime/runtime/definition/RuntimeDevRuntimeDefinition.java b/platform/src/main/java/net/neoforged/gradle/platform/runtime/runtime/definition/RuntimeDevRuntimeDefinition.java index c06398084..246631484 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/runtime/runtime/definition/RuntimeDevRuntimeDefinition.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/runtime/runtime/definition/RuntimeDevRuntimeDefinition.java @@ -10,6 +10,7 @@ import net.neoforged.gradle.dsl.common.tasks.WithOutput; import net.neoforged.gradle.neoform.runtime.definition.NeoFormRuntimeDefinition; import net.neoforged.gradle.platform.runtime.runtime.specification.RuntimeDevRuntimeSpecification; +import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.NotNull; @@ -48,11 +49,6 @@ public NeoFormRuntimeDefinition getJoinedNeoFormRuntimeDefinition() { return joinedNeoFormRuntimeDefinition.getMappingVersionData(); } - @Override - public void configureRun(RunImpl run) { - super.configureRun(run); - } - @NotNull @Override public TaskProvider getListLibrariesTaskProvider() { @@ -60,9 +56,8 @@ public TaskProvider getListLibrariesTaskProvider() { } @Override - protected Map buildRunInterpolationData(RunImpl run) { - final Map interpolationData = joinedNeoFormRuntimeDefinition.buildRunInterpolationData(run); - return interpolationData; + protected void buildRunInterpolationData(RunImpl run, MapProperty interpolationData) { + joinedNeoFormRuntimeDefinition.buildRunInterpolationData(run, interpolationData); } public TaskProvider getPatchBase() { diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy index 3c0f440cd..9a186bb07 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy @@ -35,6 +35,7 @@ class CentralCacheTests extends BuilderBasedTestSpecification { """) it.withToolchains() it.property(CentralCacheService.LOG_CACHE_HITS_PROPERTY, "true") + it.withGlobalCacheDirectory(tempDir) }) when: @@ -151,6 +152,7 @@ class CentralCacheTests extends BuilderBasedTestSpecification { it.withToolchains() it.property(CentralCacheService.IS_ENABLED_PROPERTY, "false") it.property(CentralCacheService.DEBUG_CACHE_PROPERTY, "true") + it.withGlobalCacheDirectory(tempDir) }) when: diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy index e021d574d..cf7c389c6 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy @@ -1,6 +1,7 @@ package net.neoforged.gradle.userdev import net.neoforged.gradle.common.caching.CentralCacheService +import net.neoforged.gradle.dsl.common.tasks.specifications.ExecuteSpecification import net.neoforged.trainingwheels.gradle.functional.BuilderBasedTestSpecification import net.neoforged.trainingwheels.gradle.functional.builder.Runtime import org.gradle.testkit.runner.TaskOutcome @@ -97,6 +98,11 @@ class ConfigurationCacheTests extends BuilderBasedTestSpecification { thirdRun.task(':compileJava').outcome == TaskOutcome.FROM_CACHE } + @Override + protected File getTestTempDirectory() { + return new File("build/test-temp") + } + def "run_tasks_supports_configuration_cache_build"() { given: def project = create("compile_supports_configuration_cache_build", { @@ -111,10 +117,15 @@ class ConfigurationCacheTests extends BuilderBasedTestSpecification { implementation 'net.neoforged:neoforge:+' } + runs { + data { } + } + afterEvaluate { //We don't care for the error here, we just want to run the task so that the config cache is created tasks.withType(JavaExec).named('runData') { ignoreExitValue = true + group = 'run' } } """) @@ -132,12 +143,12 @@ class ConfigurationCacheTests extends BuilderBasedTestSpecification { it.withToolchains() it.withGlobalCacheDirectory(tempDir) it.enableLocalBuildCache() - it.enableConfigurationCache() }) when: def run = project.run { it.tasks('runData') + } then: 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 01ffe08b7..d5b3a45b5 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationTests.groovy @@ -155,11 +155,13 @@ class ConfigurationTests extends BuilderBasedTestSpecification { } 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") 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 6bfcceb96..4c2f21363 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy @@ -12,6 +12,53 @@ class RunTests extends BuilderBasedTestSpecification { injectIntoAllProject = true; } + def "a mod using a version library should be able to run the game"() { + given: + def project = create("version_libs_runnable", { + it.file("gradle/libs.versions.toml", + """ + [versions] + # Neoforge Settings + neoforge = "+" + + [libraries] + neoforge = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge" } + """.trim()) + + it.build(""" + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + + repositories { + mavenCentral() + } + + dependencies { + implementation(libs.neoforge) + } + """) + it.withToolchains() + it.withGlobalCacheDirectory(tempDir) + }) + + when: + def run = project.run { + it.tasks(':runData') + //We are expecting this test to fail, since there is a mod without any files included so it is fine. + it.shouldFail() + it.stacktrace() + } + + then: + true + run.task(':writeMinecraftClasspathData').outcome == TaskOutcome.SUCCESS + run.output.contains("Error during pre-loading phase: ERROR: File null is not a valid mod file") || + run.output.contains("Caused by: java.io.IOException: Invalid paths argument, contained no existing paths") + } + def "configuring of the configurations after the dependencies block should work"() { given: def project = create("runs_configuration_after_dependencies", { @@ -37,6 +84,12 @@ class RunTests extends BuilderBasedTestSpecification { implementation "net.neoforged:neoforge:+" } + runs { + data { + modSource project.sourceSets.main + } + } + configurations { modRunImplementation.extendsFrom implementation } @@ -214,7 +267,7 @@ class RunTests extends BuilderBasedTestSpecification { } configurations { - runRuntime + runRuntime { } } dependencies { @@ -239,6 +292,8 @@ class RunTests extends BuilderBasedTestSpecification { when: def run = project.run { it.tasks(':writeMinecraftClasspathClient') + it.stacktrace() + } then: @@ -276,7 +331,7 @@ class RunTests extends BuilderBasedTestSpecification { } configurations { - runRuntime + runRuntime { } } dependencies { diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/RunConventionTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/RunConventionTests.groovy index 8f83f4771..532fde004 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/RunConventionTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/RunConventionTests.groovy @@ -50,8 +50,6 @@ class RunConventionTests extends BuilderBasedTestSpecification { then: run.output.contains("Run count: 0") - run.output.contains("Runtype count:") - !run.output.contains("Runtype count: 0") } def "disabling run conventions does not register runs"() { @@ -89,8 +87,7 @@ class RunConventionTests extends BuilderBasedTestSpecification { then: run.output.contains("Run count: 0") - run.output.contains("Runtype count:") - !run.output.contains("Runtype count: 0") + run.output.contains("Runtype count: 0") } def "disabling automatic registration does not register runs"() { @@ -128,13 +125,12 @@ class RunConventionTests extends BuilderBasedTestSpecification { then: run.output.contains("Run count: 0") - run.output.contains("Runtype count:") - !run.output.contains("Runtype count: 0") + run.output.contains("Runtype count: 0") } def "enabling automatic registration does not register runs"() { given: - def project = create("disable_automatic_registration_disables_registration", { + def project = create("enabling_automatic_run_registration_does_not_register_types", { it.build(""" java { toolchain { @@ -169,9 +165,8 @@ class RunConventionTests extends BuilderBasedTestSpecification { then: run.output.contains("Run count: ") !run.output.contains("Run count: 0") - run.output.contains("Runtype count:") - !run.output.contains("Runtype count: 0") - run.output.contains("Equal: true") + run.output.contains("Runtype count: 0") + run.output.contains("Equal: false") } def "disabling conventions globally prevents creation of runs configuration run"() { @@ -241,6 +236,11 @@ class RunConventionTests extends BuilderBasedTestSpecification { run.output.contains("Could not find method runs() for arguments [org.jgrapht:jgrapht-core:+] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.") } + @Override + protected File getTestTempDirectory() { + return new File("build/functionalTest") + } + def "using the run convention configuration puts the dependency on the runtime config"() { given: def project = create("run_can_download_runtime_elements", { @@ -271,6 +271,7 @@ class RunConventionTests extends BuilderBasedTestSpecification { when: def run = project.run { it.tasks(':dependencies') + it.stacktrace() } then: @@ -297,6 +298,10 @@ class RunConventionTests extends BuilderBasedTestSpecification { runs 'org.jgrapht:jgrapht-core:+' } + runs { + client { } + } + afterEvaluate { logger.lifecycle("Run contains cp entry: \${project.runs.client.dependencies.get().runtimeConfiguration.files.any { it.name.contains 'jgrapht' }}") } diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/SourceSetConventionTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/SourceSetConventionTests.groovy index f2cc66354..9b9bd9869 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/SourceSetConventionTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/SourceSetConventionTests.groovy @@ -483,6 +483,10 @@ class SourceSetConventionTests extends BuilderBasedTestSpecification { localRuntime 'org.jgrapht:jgrapht-core:+' } + runs { + client { } + } + afterEvaluate { logger.lifecycle("Run contains cp entry: \${project.runs.client.dependencies.get().runtimeConfiguration.files.any { it.name.contains 'jgrapht' }}") } @@ -520,6 +524,10 @@ class SourceSetConventionTests extends BuilderBasedTestSpecification { localRunRuntime 'org.jgrapht:jgrapht-core:+' } + runs { + client { } + } + afterEvaluate { logger.lifecycle("Run contains cp entry: \${project.runs.client.dependencies.get().runtimeConfiguration.files.any { it.name.contains 'jgrapht' }}") } diff --git a/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevAdditionalTestDependenciesParser.java b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevAdditionalTestDependenciesParser.java new file mode 100644 index 000000000..3d6a5bb78 --- /dev/null +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevAdditionalTestDependenciesParser.java @@ -0,0 +1,64 @@ +package net.neoforged.gradle.userdev.dependency; + +import net.neoforged.gradle.dsl.userdev.configurations.UserdevProfile; +import net.neoforged.gradle.util.TransformerUtils; +import org.gradle.api.Project; +import org.gradle.api.file.FileTree; +import org.gradle.api.file.RegularFile; +import org.gradle.api.provider.Provider; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class UserDevAdditionalTestDependenciesParser { + + final Project project; + + public UserDevAdditionalTestDependenciesParser(Project project) { + this.project = project; + } + + public Provider> parse(File file) { + if (!file.exists()) + return project.provider(Collections::emptyList); + + try { + return parseFileInternal(file); + } catch (Exception e) { + return project.provider(Collections::emptyList); + } + } + + private Provider> parseFileInternal(File file) { + final FileTree fileTree = file.getName().endsWith(".jar") || file.getName().endsWith(".zip") ? + project.zipTree(file) : + project.fileTree(file); + + final var providers = fileTree.matching(pattern -> pattern.include("config.json")) + .getElements() + .map(fls -> fls.stream() + .filter(RegularFile.class::isInstance) + .map(RegularFile.class::cast) + .map(RegularFile::getAsFile) + .collect(Collectors.toSet())) + .map(files -> files.stream() + .map(this::parseInternalFile) + .collect(Collectors.toList())); + + return providers.flatMap(TransformerUtils.combineAllLists(project, String.class, Function.identity())); + } + + private Provider> parseInternalFile(File file) { + try(final FileInputStream inputStream = new FileInputStream(file)) { + return UserdevProfile.get(project.getObjects(), inputStream) + .getAdditionalTestDependencyArtifactCoordinates(); + } catch (IOException e) { + return project.provider(Collections::emptyList); + } + } +} diff --git a/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevDependencyManager.java b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevDependencyManager.java index 29b4f02a8..e4878192d 100644 --- a/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevDependencyManager.java +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevDependencyManager.java @@ -1,16 +1,27 @@ package net.neoforged.gradle.userdev.dependency; +import net.neoforged.gradle.common.util.SourceSetUtils; +import net.neoforged.gradle.common.util.constants.RunsConstants; import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.DependencyReplacement; +import net.neoforged.gradle.dsl.common.runs.run.Run; +import net.neoforged.gradle.dsl.common.runs.type.RunTypeManager; import net.neoforged.gradle.dsl.common.util.ConfigurationUtils; import net.neoforged.gradle.dsl.common.util.DistributionType; import net.neoforged.gradle.userdev.runtime.definition.UserDevRuntimeDefinition; import net.neoforged.gradle.userdev.runtime.extension.UserDevRuntimeExtension; +import net.neoforged.gradle.util.TransformerUtils; +import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; +import org.gradle.api.Transformer; import org.gradle.api.artifacts.*; +import org.gradle.api.artifacts.dsl.DependencyCollector; +import org.gradle.api.file.FileSystemLocation; import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; public final class UserDevDependencyManager { private static final UserDevDependencyManager INSTANCE = new UserDevDependencyManager(); @@ -23,18 +34,58 @@ private UserDevDependencyManager() { } public void apply(final Project project) { + registerReplacementHandler(project); + registerRunTypeParser(project); + registerUnitTestDependencyMapping(project); + } + + @SuppressWarnings("unchecked") + private void registerUnitTestDependencyMapping(Project project) { + final NamedDomainObjectContainer runs = (NamedDomainObjectContainer) project.getExtensions().getByName(RunsConstants.Extensions.RUNS); + + runs.configureEach(run -> { + run.getUnitTestSources().whenSourceSetAdded(sourceSet -> { + final Configuration implementation = SourceSetUtils.getProject(sourceSet).getConfigurations().getByName(sourceSet.getImplementationConfigurationName()); + final UserDevAdditionalTestDependenciesParser parser = new UserDevAdditionalTestDependenciesParser(project); + + //Parse out all the additional test dependencies of a run + implementation.getDependencies().addAllLater( + TransformerUtils.ifTrue(run.getIsJUnit(), + run.getCompileClasspathElements() + .map(files -> files.stream() + .map(FileSystemLocation::getAsFile) + .map(parser::parse).toList()) + .flatMap(TransformerUtils.combineAllLists(project, String.class, Function.identity())) + .map(dependencyCoordinates -> { + final DependencyCollector collector = project.getObjects().dependencyCollector(); + dependencyCoordinates.forEach(collector::add); + return collector; + }) + .flatMap(DependencyCollector::getDependencies) + ) + ); + }); + }); + } + + private void registerRunTypeParser(Project project) { + final RunTypeManager runTypes = project.getExtensions().getByType(RunTypeManager.class); + runTypes.registerParser(new UserDevRunTypeParser(project)); + } + + private void registerReplacementHandler(Project project) { final DependencyReplacement dependencyReplacer = project.getExtensions().getByType(DependencyReplacement.class); dependencyReplacer.getReplacementHandlers().create("neoForge", dependencyReplacementHandler -> dependencyReplacementHandler.getReplacer().set(context -> { if (isNotAMatchingDependency(context.getDependency())) { return Optional.empty(); } - + if (!(context.getDependency() instanceof ExternalModuleDependency externalModuleDependency)) { return Optional.empty(); } final UserDevRuntimeDefinition runtimeDefinition = buildForgeUserDevRuntimeFrom(project, externalModuleDependency); - + final Configuration additionalDependenciesConfiguration = ConfigurationUtils.temporaryConfiguration( project, "NeoForgeUserDevAdditionalReplacementDependenciesFor" + runtimeDefinition.getSpecification().getIdentifier(), @@ -44,7 +95,7 @@ public void apply(final Project project) { configuration.extendsFrom(runtimeDefinition.getAdditionalUserDevDependencies()); } ); - + return Optional.of( new UserDevReplacementResult( project, @@ -56,7 +107,7 @@ public void apply(final Project project) { )); })); } - + private boolean isNotAMatchingDependency(final Dependency dependencyToCheck) { if (dependencyToCheck instanceof ExternalModuleDependency) { final ExternalModuleDependency externalModuleDependency = (ExternalModuleDependency) dependencyToCheck; 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 3b89f121a..2567c576d 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 @@ -26,7 +26,6 @@ * 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; @@ -60,23 +59,4 @@ 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/dependency/UserDevRunTypeParser.java b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevRunTypeParser.java new file mode 100644 index 000000000..da74ac58e --- /dev/null +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevRunTypeParser.java @@ -0,0 +1,56 @@ +package net.neoforged.gradle.userdev.dependency; + +import net.neoforged.gradle.dsl.common.runs.type.RunType; +import net.neoforged.gradle.dsl.common.runs.type.RunTypeManager; +import net.neoforged.gradle.dsl.userdev.configurations.UserdevProfile; +import org.gradle.api.Project; +import org.gradle.api.file.FileTree; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +public class UserDevRunTypeParser implements RunTypeManager.Parser { + + private final Project project; + + public UserDevRunTypeParser(Project project) { + this.project = project; + } + + @Override + public Collection parse(File file) { + if (!file.exists()) + return List.of(); + + try { + return parseInternal(file); + } catch (Exception e) { + return List.of(); + } + } + + private @NotNull List parseInternal(File file) { + final FileTree fileTree = file.getName().endsWith(".jar") || file.getName().endsWith(".zip") ? + project.zipTree(file) : + project.fileTree(file); + return fileTree.matching(pattern -> pattern.include("config.json")) + .getFiles() + .stream() + .flatMap(this::parseInternalFile) + .toList(); + } + + private Stream parseInternalFile(File file) { + try(final FileInputStream inputStream = new FileInputStream(file)) { + return UserdevProfile.get(project.getObjects(), inputStream) + .getRunTypes().stream(); + } catch (IOException e) { + return Stream.empty(); + } + } +} 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 14b3894a4..66c581d5b 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 @@ -10,6 +10,7 @@ import net.neoforged.gradle.common.util.run.RunsUtil; import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.DependencyReplacement; import net.neoforged.gradle.dsl.common.extensions.repository.Repository; +import net.neoforged.gradle.dsl.common.runs.run.DependencyHandler; import net.neoforged.gradle.dsl.common.runtime.definition.Definition; import net.neoforged.gradle.dsl.common.tasks.WithOutput; import net.neoforged.gradle.dsl.userdev.configurations.UserdevProfile; @@ -20,7 +21,11 @@ import org.gradle.api.NamedDomainObjectCollection; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.file.FileTree; +import org.gradle.api.file.RegularFile; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.NotNull; @@ -56,6 +61,12 @@ public UserDevRuntimeDefinition(@NotNull UserDevRuntimeSpecification specificati clientExtraJar ); + //Add the userdev artifact as a dependency. This makes it discoverable on the classpath, and as such discoverable + //by runs and other interested parties. + this.additionalUserDevDependencies.getDependencies().add( + this.getSpecification().getUserDevArtifact().toDependency(specification.getProject()) + ); + this.getAllDependencies().from(neoformRuntimeDefinition.getAllDependencies()); this.getAllDependencies().from(getAdditionalUserDevDependencies()); this.getAllDependencies().from(getUserdevConfiguration()); @@ -103,8 +114,8 @@ public TaskProvider getListLibrariesTaskProvider() { } @Override - protected Map buildRunInterpolationData(RunImpl run) { - final Map interpolationData = neoformRuntimeDefinition.buildRunInterpolationData(run); + protected void buildRunInterpolationData(RunImpl run, @NotNull MapProperty interpolationData) { + neoformRuntimeDefinition.buildRunInterpolationData(run, interpolationData); if (userdevConfiguration.getModules() != null && !userdevConfiguration.getModules().get().isEmpty()) { final String name = String.format("moduleResolverForgeUserDev%s", getSpecification().getVersionedName()); @@ -114,10 +125,19 @@ protected Map buildRunInterpolationData(RunImpl run) { } else { modulesCfg = getSpecification().getProject().getConfigurations().create(name); modulesCfg.setCanBeResolved(true); - userdevConfiguration.getModules().get().forEach(m -> modulesCfg.getDependencies().add(getSpecification().getProject().getDependencies().create(m))); + modulesCfg.getDependencies().addAllLater( + userdevConfiguration.getModules().map( + modules -> modules.stream().map( + m -> getSpecification().getProject().getDependencies().create(m) + ).collect(Collectors.toList()) + ) + ); } - interpolationData.put("modules", modulesCfg.resolve().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator))); + interpolationData.put("modules", modulesCfg.getIncoming().getArtifacts().getResolvedArtifacts().map(artifacts -> artifacts.stream() + .map(ResolvedArtifactResult::getFile) + .map(File::getAbsolutePath) + .collect(Collectors.joining(File.pathSeparator)))); } final TaskProvider minecraftClasspathSerializer = getSpecification().getProject().getTasks().register( @@ -128,18 +148,13 @@ protected Map buildRunInterpolationData(RunImpl run) { task.getInputFiles().from(this.additionalUserDevDependencies); task.getInputFiles().from(neoformRuntimeDefinition.getMinecraftDependenciesConfiguration()); task.getInputFiles().from(this.userdevClasspathElementProducer.flatMap(WithOutput::getOutput)); - - Configuration userDependencies = run.getDependencies().get().getRuntimeConfiguration(); - task.getInputFiles().from(userDependencies); + task.getInputFiles().from(run.getDependencies().map(DependencyHandler::getRuntimeConfiguration)); } ); configureAssociatedTask(minecraftClasspathSerializer); - - interpolationData.put("minecraft_classpath_file", minecraftClasspathSerializer.get().getOutput().get().getAsFile().getAbsolutePath()); + interpolationData.put("minecraft_classpath_file", minecraftClasspathSerializer.flatMap(ClasspathSerializer::getTargetFile).map(RegularFile::getAsFile).map(File::getAbsolutePath)); run.getDependsOn().add(minecraftClasspathSerializer); - - return interpolationData; } @Override @@ -147,11 +162,6 @@ public Definition getDelegate() { return neoformRuntimeDefinition; } - @Override - public @NotNull VersionJson getVersionJson() { - return getNeoFormRuntimeDefinition().getVersionJson(); - } - public TaskProvider getUserdevClasspathElementProducer() { return userdevClasspathElementProducer; } 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 20bddefd4..13edd75ba 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 @@ -8,6 +8,8 @@ import net.neoforged.gradle.common.util.run.TypesUtil; import net.neoforged.gradle.dsl.common.extensions.Mappings; import net.neoforged.gradle.dsl.common.extensions.Minecraft; +import net.neoforged.gradle.dsl.common.extensions.subsystems.Conventions; +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.type.RunType; import net.neoforged.gradle.dsl.common.runtime.tasks.tree.TaskTreeAdapter; @@ -44,6 +46,7 @@ public UserDevRuntimeExtension(Project project) { super(project); } + @SuppressWarnings("unchecked") @Override protected @NotNull UserDevRuntimeDefinition doCreate(UserDevRuntimeSpecification spec) { final NeoFormRuntimeExtension neoFormRuntimeExtension = getProject().getExtensions().getByType(NeoFormRuntimeExtension.class); @@ -91,6 +94,28 @@ public UserDevRuntimeExtension(Project project) { ); }); }); + + final Conventions conventions = getProject().getExtensions().getByType(Subsystems.class).getConventions(); + if (conventions.getIsEnabled().get() + && conventions.getRuns().getIsEnabled().get() + && conventions.getRuns().getShouldDefaultRunsBeCreated().get()) { + final NamedDomainObjectContainer runs = (NamedDomainObjectContainer) getProject().getExtensions().getByName(RunsConstants.Extensions.RUNS); + userDevProfile.getRunTypes().forEach(runType -> { + if (runs.getNames().contains(runType.getName())) { + return; + } + + try { + final Run run = runs.create(runType.getName()); + run.configure(runType); + run.getConfigureFromTypeWithName().set(false); + run.getConfigureFromDependencies().set(false); + } catch (IllegalStateException ignored) { + //thrown when the dependency is added lazily. This is fine. + } + + }); + } spec.setMinecraftVersion(neoFormRuntimeDefinition.getSpecification().getMinecraftVersion()); diff --git a/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/specification/UserDevRuntimeSpecification.java b/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/specification/UserDevRuntimeSpecification.java index 1b86a4e96..2b962ad4e 100644 --- a/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/specification/UserDevRuntimeSpecification.java +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/specification/UserDevRuntimeSpecification.java @@ -35,6 +35,7 @@ public final class UserDevRuntimeSpecification extends CommonRuntimeSpecificatio private final String forgeName; private final String forgeVersion; private final UserdevProfile profile; + private final Artifact userDevArtifact; @Nullable private String minecraftVersion = null; @@ -48,13 +49,15 @@ public UserDevRuntimeSpecification(Project project, Multimap> taskCustomizers, String forgeGroup, String forgeName, - String forgeVersion) { + String forgeVersion, + Artifact artifact) { super(project, "neoForge", version, distribution, preTaskTypeAdapters, postTypeAdapters, taskCustomizers, UserDevRuntimeExtension.class); this.userDevArchive = userDevArchive; this.profile = profile; this.forgeGroup = forgeGroup; this.forgeName = forgeName; this.forgeVersion = forgeVersion; + this.userDevArtifact = artifact; } @Override @@ -78,6 +81,10 @@ public UserdevProfile getProfile() { return profile; } + public Artifact getUserDevArtifact() { + return userDevArtifact; + } + @NotNull @Override public String getMinecraftVersion() { @@ -193,7 +200,8 @@ public Builder withForgeGroup(final String mcpGroup) { taskCustomizers, effectiveVersion.getGroup(), effectiveVersion.getName(), - effectiveVersion.getVersion() + effectiveVersion.getVersion(), + artifact ); } } diff --git a/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/tasks/ClasspathSerializer.java b/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/tasks/ClasspathSerializer.java index ed55fa3a0..ba064019b 100644 --- a/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/tasks/ClasspathSerializer.java +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/runtime/tasks/ClasspathSerializer.java @@ -2,6 +2,7 @@ import net.neoforged.gradle.common.runtime.tasks.DefaultRuntime; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.*; import java.io.File; @@ -16,6 +17,9 @@ public abstract class ClasspathSerializer extends DefaultRuntime { public ClasspathSerializer() { getOutputFileName().convention("classpath.txt"); + getTargetFile().convention(getOutputDirectory().flatMap(d -> getOutputFileName().orElse("output.jar").map(d::file))); + getOutput().set(getTargetFile()); + setGroup("NeoGradle/Runs"); setDescription("Serializes the classpath of the run to a file."); } @@ -44,4 +48,7 @@ public void run() throws Exception { @InputFiles @PathSensitive(PathSensitivity.NONE) public abstract ConfigurableFileCollection getInputFiles(); + + @Internal + public abstract RegularFileProperty getTargetFile(); } 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 3b0b5116a..a4f4df35a 100644 --- a/utils/src/main/java/net/neoforged/gradle/util/TransformerUtils.java +++ b/utils/src/main/java/net/neoforged/gradle/util/TransformerUtils.java @@ -223,7 +223,7 @@ public static > Transformer, C> ta * @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) { + 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) { @@ -276,6 +276,30 @@ public static Provider> ifTrue(Provider predicate 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 + */ + public static Provider> ifTrue(Provider predicate, Provider> whenTrue) { + return predicate.zip(whenTrue, (p, v) -> p ? List.copyOf(v) : List.of()); + } + + /** + * 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 + */ + public static Provider> ifTrue(Boolean predicate, Provider> whenTrue) { + return whenTrue.map(v -> predicate ? List.copyOf(v) : List.of()); + } + /** * Creates a transformed provider that returns a list of values if the predicate is true. * @@ -327,22 +351,25 @@ public static Transformer, Boolean> and(Provider... r } @SafeVarargs - public static Transformer, Boolean> or(Provider... rightProvider) { + public static Provider or(Boolean initial, 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 rightProvider[0].map(o -> initial || 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; - }; + Provider input = rightProvider[0].map(o -> initial || o); + for (int i = 1; i < rightProvider.length; i++) { + input = input.zip(rightProvider[i], (l, r) -> l || r); + } + return input; + } + + @SafeVarargs + public static Transformer, Boolean> or(Provider... rightProvider) { + return inputBoolean -> or(inputBoolean, rightProvider); } /** 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 77d2d35ee..df0c1106c 100644 --- a/vanilla/src/functionalTest/groovy/net/neoforged/gradle/vanilla/AccessTransformerTests.groovy +++ b/vanilla/src/functionalTest/groovy/net/neoforged/gradle/vanilla/AccessTransformerTests.groovy @@ -131,6 +131,7 @@ class AccessTransformerTests extends BuilderBasedTestSpecification { when: def initialRun = project.run { it.tasks('build') + } then: diff --git a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/VanillaRuntimeDefinition.java b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/VanillaRuntimeDefinition.java index 40f1ce60d..ff53b52d6 100644 --- a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/VanillaRuntimeDefinition.java +++ b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/VanillaRuntimeDefinition.java @@ -1,6 +1,5 @@ package net.neoforged.gradle.vanilla.runtime; -import com.google.common.collect.Maps; import net.neoforged.gradle.common.runs.run.RunImpl; import net.neoforged.gradle.common.runtime.definition.CommonRuntimeDefinition; import net.neoforged.gradle.common.runtime.tasks.DownloadAssets; @@ -14,9 +13,14 @@ import net.neoforged.gradle.vanilla.util.InterpolationConstants; import net.neoforged.gradle.vanilla.util.ServerLaunchInformation; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.Directory; +import org.gradle.api.file.RegularFile; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.NotNull; +import java.io.File; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -40,7 +44,7 @@ public VanillaRuntimeDefinition(@NotNull VanillaRuntimeSpecification specificati @NotNull Map> gameArtifactProvidingTasks, @NotNull Configuration minecraftDependenciesConfiguration, @NotNull Consumer> associatedTaskConsumer, - VersionJson versionJson, + Provider versionJson, TaskProvider assetsTaskProvider, TaskProvider nativesTaskProvider, Optional serverLaunchInformation) { @@ -73,34 +77,50 @@ public Optional getServerLaunchInformation() { } @Override - protected Map buildRunInterpolationData(RunImpl run) { - final Map interpolationData = Maps.newHashMap(); - - final String fgVersion = this.getClass().getPackage().getImplementationVersion(); + protected void buildRunInterpolationData(RunImpl run, MapProperty interpolationData) { + final String runtimeVersion = this.getClass().getPackage().getImplementationVersion(); interpolationData.put(InterpolationConstants.VERSION_NAME, getSpecification().getMinecraftVersion()); - interpolationData.put(InterpolationConstants.ASSETS_ROOT, DownloadAssets.getAssetsDirectory(run.getProject(), run.getProject().provider(this::getVersionJson)).get().getAsFile().getAbsolutePath()); - interpolationData.put(InterpolationConstants.ASSETS_INDEX_NAME, getAssets().get().getAssetIndexFile().get().getAsFile().getName().substring(0, getAssets().get().getAssetIndexFile().get().getAsFile().getName().lastIndexOf('.'))); + interpolationData.put(InterpolationConstants.ASSETS_ROOT, DownloadAssets.getAssetsDirectory(run.getProject(), getVersionJson()).map(Directory::getAsFile).map(File::getAbsolutePath)); + interpolationData.put(InterpolationConstants.ASSETS_INDEX_NAME, getAssets().flatMap(DownloadAssets::getAssetIndexFile).map(RegularFile::getAsFile).map(File::getName).map(s -> s.substring(0, s.lastIndexOf('.')))); interpolationData.put(InterpolationConstants.AUTH_ACCESS_TOKEN, "0"); interpolationData.put(InterpolationConstants.USER_TYPE, "legacy"); - interpolationData.put(InterpolationConstants.VERSION_TYPE, getVersionJson().getType()); - interpolationData.put(InterpolationConstants.NATIVES_DIRECTORY, getNatives().get().getOutputDirectory().get().getAsFile().getAbsolutePath()); + interpolationData.put(InterpolationConstants.VERSION_TYPE, getVersionJson().map(VersionJson::getType)); + interpolationData.put(InterpolationConstants.NATIVES_DIRECTORY, getNatives().flatMap(ExtractNatives::getOutputDirectory).map(Directory::getAsFile).map(File::getAbsolutePath)); interpolationData.put(InterpolationConstants.LAUNCHER_NAME, "NeoGradle-Vanilla"); - interpolationData.put(InterpolationConstants.LAUNCHER_VERSION, fgVersion == null ? "DEV" : fgVersion); - - return interpolationData; + interpolationData.put(InterpolationConstants.LAUNCHER_VERSION, runtimeVersion == null ? "DEV" : runtimeVersion); } @Override public void configureRun(RunImpl run) { if (getSpecification().getDistribution().isClient()) { - Arrays.stream(getVersionJson().getArguments().getGame()).filter(arg -> arg.getRules() == null || arg.getRules().length == 0).flatMap(arg -> arg.value.stream()).forEach(arg -> run.getProgramArguments().add(arg)); - Arrays.stream(getVersionJson().getArguments().getJvm()).filter(VersionJson.RuledObject::isAllowed).flatMap(arg -> arg.value.stream()).forEach(arg -> run.getJvmArguments().add(arg)); - run.getMainClass().set(getVersionJson().getMainClass()); + run.getProgramArguments().addAll( + getVersionJson().map(VersionJson::getArguments) + .map(VersionJson.Arguments::getGame) + .map(Arrays::stream) + .map(stream -> stream + .filter(VersionJson.RuledObject::isAllowed) + .flatMap(arg -> arg.value.stream()) + .toList() + ) + + ); + run.getJvmArguments().addAll( + getVersionJson().map(VersionJson::getArguments) + .map(VersionJson.Arguments::getJvm) + .map(Arrays::stream) + .map(stream -> stream + .filter(VersionJson.RuledObject::isAllowed) + .flatMap(arg -> arg.value.stream()) + .toList() + ) + ); + run.getMainClass().set(getVersionJson().map(VersionJson::getMainClass)); run.getIsClient().set(true); run.getIsSingleInstance().set(false); - final Map interpolationData = Maps.newHashMap(buildRunInterpolationData(run)); + final MapProperty interpolationData = run.getProject().getObjects().mapProperty(String.class, String.class); + buildRunInterpolationData(run, interpolationData); interpolationData.put(InterpolationConstants.GAME_DIRECTORY, run.getWorkingDirectory().get().getAsFile().getAbsolutePath()); run.overrideJvmArguments(interpolate(run.getJvmArguments(), interpolationData, "$")); diff --git a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/extensions/VanillaRuntimeExtension.java b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/extensions/VanillaRuntimeExtension.java index f0b4103b2..367aabaf8 100644 --- a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/extensions/VanillaRuntimeExtension.java +++ b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/extensions/VanillaRuntimeExtension.java @@ -5,6 +5,7 @@ import net.neoforged.gradle.common.runtime.extensions.CommonRuntimeExtension; import net.neoforged.gradle.common.tasks.UnpackBundledServer; import net.neoforged.gradle.common.util.BundledServerUtils; +import net.neoforged.gradle.common.util.ProjectUtils; import net.neoforged.gradle.dsl.common.util.ConfigurationUtils; import net.neoforged.gradle.common.util.VersionJson; import net.neoforged.gradle.dsl.common.util.DistributionType; @@ -18,6 +19,7 @@ import net.neoforged.gradle.dsl.common.tasks.WithOutput; import net.neoforged.gradle.dsl.common.util.CommonRuntimeUtils; import net.neoforged.gradle.dsl.common.util.Constants; +import net.neoforged.gradle.util.TransformerUtils; import net.neoforged.gradle.vanilla.runtime.VanillaRuntimeDefinition; import net.neoforged.gradle.vanilla.runtime.spec.VanillaRuntimeSpecification; import net.neoforged.gradle.vanilla.runtime.steps.*; @@ -25,6 +27,7 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.NotNull; @@ -61,46 +64,16 @@ protected VanillaRuntimeDefinition doCreate(final VanillaRuntimeSpecification sp final File minecraftCache = artifactCacheExtension.getCacheDirectory().get().getAsFile(); - final Map gameArtifacts = artifactCacheExtension.cacheGameVersion(spec.getMinecraftVersion(), spec.getDistribution()); - if (gameArtifacts.containsKey(GameArtifact.SERVER_JAR)) { - final File serverJar = gameArtifacts.get(GameArtifact.SERVER_JAR); - if (BundledServerUtils.isBundledServer(serverJar)) { - final File vanillaServerJar = new File(minecraftCache, String.format("minecraft_server.%s.jar", spec.getMinecraftVersion())); - BundledServerUtils.extractBundledVersion(serverJar, vanillaServerJar); - gameArtifacts.put(GameArtifact.SERVER_JAR, vanillaServerJar); - } - } - - final VersionJson versionJson; - try { - versionJson = VersionJson.get(gameArtifacts.get(GameArtifact.VERSION_MANIFEST)); - } catch (IOException e) { - throw new RuntimeException(String.format("Failed to read VersionJson from the launcher metadata for the minecraft version: %s", spec.getMinecraftVersion()), e); - } - - final Configuration minecraftDependenciesConfiguration = ConfigurationUtils.temporaryConfiguration(getProject(), "VanillaMinecraftDependenciesFor" + spec.getIdentifier()); - if (spec.getDistribution().isClient() || !BundledServerUtils.isBundledServer(gameArtifacts.get(GameArtifact.SERVER_JAR))) { - for (VersionJson.Library library : versionJson.getLibraries()) { - minecraftDependenciesConfiguration.getDependencies().add( - spec.getProject().getDependencies().create(library.getName()) - ); - } - } else { - BundledServerUtils.getBundledDependencies(gameArtifacts.get(GameArtifact.SERVER_JAR)).forEach( - dependency -> minecraftDependenciesConfiguration.getDependencies().add( - spec.getProject().getDependencies().create(dependency))); - } + final MinecraftArtifactCache artifactCache = spec.getProject().getExtensions().getByType(MinecraftArtifactCache.class); + final Map> gameArtifactTasks = buildDefaultArtifactProviderTasks(spec); final File vanillaDirectory = spec.getProject().getLayout().getBuildDirectory().dir(String.format("vanilla/%s", spec.getIdentifier())).get().getAsFile(); final File runtimeWorkingDirectory = new File(vanillaDirectory, "runtime"); final File stepsMcpDirectory = new File(vanillaDirectory, "steps"); - stepsMcpDirectory.mkdirs(); - - final Map> gameArtifactTasks = buildDefaultArtifactProviderTasks(spec); - if (gameArtifactTasks.containsKey(GameArtifact.SERVER_JAR) && BundledServerUtils.isBundledServer(gameArtifacts.get(GameArtifact.SERVER_JAR))) { + if (gameArtifactTasks.containsKey(GameArtifact.SERVER_JAR)) { final TaskProvider serverJarTask = gameArtifactTasks.get(GameArtifact.SERVER_JAR); - + final TaskProvider extractedBundleTask = project.getTasks().register(CommonRuntimeUtils.buildTaskName(spec, "extractBundle"), UnpackBundledServer.class, task -> { task.getServerJar().set(serverJarTask.flatMap(WithOutput::getOutput)); task.getOutput().fileValue(new File(vanillaDirectory, "files/server.jar")); @@ -110,6 +83,15 @@ protected VanillaRuntimeDefinition doCreate(final VanillaRuntimeSpecification sp gameArtifactTasks.put(GameArtifact.SERVER_JAR, extractedBundleTask); } + final Provider versionJson = artifactCache.cacheVersionManifest(spec.getMinecraftVersion()).map(TransformerUtils.guard(VersionJson::get)); + + final Configuration minecraftDependenciesConfiguration = ConfigurationUtils.temporaryConfiguration(getProject(), "VanillaMinecraftDependenciesFor" + spec.getIdentifier()); + minecraftDependenciesConfiguration.getDependencies().addAllLater( + versionJson.map(VersionJson::getLibraries).map(libraries -> libraries.stream().map(library -> spec.getProject().getDependencies().create(library.getName())).toList()) + ); + + stepsMcpDirectory.mkdirs(); + final TaskProvider sourceJarTask = spec.getProject().getTasks().register("supplySourcesFor" + spec.getIdentifier(), ArtifactProvider.class, task -> { task.getOutput().set(new File(runtimeWorkingDirectory, "sources.jar")); }); @@ -117,17 +99,18 @@ protected VanillaRuntimeDefinition doCreate(final VanillaRuntimeSpecification sp task.getOutput().set(new File(runtimeWorkingDirectory, "raw.jar")); }); - final Optional launchInformation = spec.getDistribution().isClient() ? Optional.empty() : Optional.of(ServerLaunchInformation.from(gameArtifacts.get(GameArtifact.SERVER_JAR))); + final Optional launchInformation = spec.getDistribution().isClient() ? Optional.empty() : Optional.of(ServerLaunchInformation.from(gameArtifactTasks.get(GameArtifact.SERVER_JAR))); - final VanillaRuntimeDefinition definition = new VanillaRuntimeDefinition(spec, new LinkedHashMap<>(), sourceJarTask, rawJarTask, gameArtifactTasks, minecraftDependenciesConfiguration, taskProvider -> taskProvider.configure(vanillaRuntimeTask -> { + return new VanillaRuntimeDefinition(spec, new LinkedHashMap<>(), sourceJarTask, rawJarTask, gameArtifactTasks, minecraftDependenciesConfiguration, taskProvider -> taskProvider.configure(vanillaRuntimeTask -> { configureCommonRuntimeTaskParameters(vanillaRuntimeTask, CommonRuntimeUtils.buildStepName(spec, vanillaRuntimeTask.getName()), spec, vanillaDirectory); - }), versionJson, createDownloadAssetsTasks(spec, runtimeWorkingDirectory, versionJson), createExtractNativesTasks(spec, runtimeWorkingDirectory, versionJson), launchInformation); + }), versionJson, createDownloadAssetsTasks(spec, versionJson), createExtractNativesTasks(spec, runtimeWorkingDirectory, versionJson), launchInformation); + } + @Override + protected void afterRegistration(VanillaRuntimeDefinition runtime) { //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(afterEvalProject -> bakeDefinition(definition)); - - return definition; + ProjectUtils.afterEvaluate(runtime.getSpecification().getProject(), () -> this.bakeDefinition(runtime)); } protected VanillaRuntimeSpecification.Builder createBuilder() { diff --git a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/steps/CollectLibraryInformationStep.java b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/steps/CollectLibraryInformationStep.java index b3709a859..b2d7c8c68 100644 --- a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/steps/CollectLibraryInformationStep.java +++ b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/steps/CollectLibraryInformationStep.java @@ -18,10 +18,6 @@ public class CollectLibraryInformationStep implements IStep { public TaskProvider buildTask(VanillaRuntimeDefinition definition, TaskProvider inputProvidingTask, @NotNull File minecraftCache, @NotNull File workingDirectory, @NotNull Map> pipelineTasks, @NotNull Map> gameArtifactTasks, @NotNull Consumer> additionalTaskConfigurator) { return definition.getSpecification().getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(definition, "libraries"), ListLibraries.class, task -> { task.getDownloadedVersionJsonFile().set(gameArtifactTasks.get(GameArtifact.VERSION_MANIFEST).flatMap(WithOutput::getOutput)); - - if (definition.getServerLaunchInformation().isPresent() && definition.getServerLaunchInformation().get().isBundledServer()) { - task.getServerBundleFile().set(gameArtifactTasks.get(GameArtifact.SERVER_JAR).flatMap(WithOutput::getOutput)); - } }); } diff --git a/vanilla/src/main/java/net/neoforged/gradle/vanilla/util/ServerLaunchInformation.java b/vanilla/src/main/java/net/neoforged/gradle/vanilla/util/ServerLaunchInformation.java index 0517a2307..d58b4c592 100644 --- a/vanilla/src/main/java/net/neoforged/gradle/vanilla/util/ServerLaunchInformation.java +++ b/vanilla/src/main/java/net/neoforged/gradle/vanilla/util/ServerLaunchInformation.java @@ -1,33 +1,40 @@ package net.neoforged.gradle.vanilla.util; import net.neoforged.gradle.common.util.BundledServerUtils; +import net.neoforged.gradle.dsl.common.tasks.WithOutput; +import org.gradle.api.file.RegularFile; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.TaskProvider; import java.io.File; public class ServerLaunchInformation { - public static ServerLaunchInformation from(final File serverFile) { - if (BundledServerUtils.isBundledServer(serverFile)) { - return new ServerLaunchInformation(BundledServerUtils.getBundledMainClass(serverFile), true); - } + public static ServerLaunchInformation from(final TaskProvider serverFile) { + final Provider isBundled = serverFile.flatMap(WithOutput::getOutput).map(RegularFile::getAsFile).map(BundledServerUtils::isBundledServer); - //TODO: Auto extract this from the manifest, but for now this will do. - return new ServerLaunchInformation("net.minecraft.server.Main", false); + final Provider mainClass = serverFile.flatMap(WithOutput::getOutput).map(RegularFile::getAsFile).map(BundledServerUtils::getBundledMainClass) + .orElse("net.minecraft.server.Main"); + + return new ServerLaunchInformation( + mainClass, + isBundled + ); } - private final String mainClass; - private final boolean isBundledServer; + private final Provider mainClass; + private final Provider isBundledServer; - private ServerLaunchInformation(String mainClass, boolean isBundledServer) { + private ServerLaunchInformation(Provider mainClass, Provider isBundledServer) { this.mainClass = mainClass; this.isBundledServer = isBundledServer; } - public String getMainClass() { + public Provider getMainClass() { return mainClass; } - public boolean isBundledServer() { + public Provider isBundledServer() { return isBundledServer; } }