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 000000000..f6c040c5e Binary files /dev/null and b/img.png differ 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; } }