Skip to content

Commit

Permalink
Improve source set support (#17)
Browse files Browse the repository at this point in the history
Allow the modding depedencies to be added to source sets that do not extend from the main source set.
Also allow setting of the primary source set for runs, which also translates into that source set being set as the IntelliJ classpath module for that run.
  • Loading branch information
shartte authored Jun 14, 2024
1 parent 69d1398 commit 412a52a
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 46 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ neoForge {
// You can change the name used for this run in your IDE
ideName = "Run Game Tests"
// Changes the source set whose runtime classpath is used for this run. This defaults to "main"
// Eclipse does not support having multiple runtime classpaths per project (except for unit tests).
sourceSet = sourceSets.main
}
}
}
Expand All @@ -167,6 +171,34 @@ neoForge {
}
```

### Isolated Source Sets

If you work with source sets that do not extend from `main`, and would like the modding dependencies to be available
in those source sets, you can use the following api:

```
sourceSets {
anotherSourceSet // example
}
neoForge {
// ...
addModdingDependenciesTo sourceSets.anotherSourceSet
mods {
mymod {
sourceSet sourceSets.main
// Do not forget to add additional source-sets here!
sourceSet sourceSets.anotherSourceSet
}
}
}
dependencies {
implementation sourceSets.anotherSourceSet.output
}
```

### Better Minecraft Parameter Names / Javadoc (Parchment)

You can use community-sourced parameter-names and Javadoc for Minecraft source code
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package net.neoforged.moddevgradle.dsl;

import net.neoforged.moddevgradle.internal.ModDevPlugin;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;

import javax.inject.Inject;
import java.util.List;
Expand All @@ -15,6 +19,7 @@
public abstract class NeoForgeExtension {
public static final String NAME = "neoForge";

private final Project project;
private final NamedDomainObjectContainer<ModModel> mods;
private final NamedDomainObjectContainer<RunModel> runs;
private final Parchment parchment;
Expand All @@ -23,6 +28,7 @@ public abstract class NeoForgeExtension {

@Inject
public NeoForgeExtension(Project project) {
this.project = project;
mods = project.container(ModModel.class);
runs = project.container(RunModel.class);
parchment = project.getObjects().newInstance(Parchment.class);
Expand All @@ -40,6 +46,23 @@ public NeoForgeExtension(Project project) {
}));
}

/**
* Adds the necessary dependencies to develop a Minecraft mod to the given source set.
* The plugin automatically adds these dependencies to the main source set.
*/
public void addModdingDependenciesTo(SourceSet sourceSet) {
var configurations = project.getConfigurations();
var sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
if (!sourceSets.contains(sourceSet)) {
throw new GradleException("Cannot add to the source set in another project.");
}

configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName())
.extendsFrom(configurations.getByName(ModDevPlugin.CONFIGURATION_RUNTIME_DEPENDENCIES));
configurations.getByName(sourceSet.getCompileClasspathConfigurationName())
.extendsFrom(configurations.getByName(ModDevPlugin.CONFIGURATION_COMPILE_DEPENDENCIES));
}

/**
* NeoForge version number. You have to set either this or {@link #getNeoFormVersion()}.
*/
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.neoforged.moddevgradle.dsl;

import net.neoforged.moddevgradle.internal.utils.ExtensionUtils;
import net.neoforged.moddevgradle.internal.utils.StringUtils;
import org.gradle.api.Named;
import org.gradle.api.Project;
Expand All @@ -11,6 +12,7 @@
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.SourceSet;
import org.slf4j.event.Level;

import javax.inject.Inject;
Expand Down Expand Up @@ -49,6 +51,8 @@ public RunModel(String name, Project project) {
ideName = project.getName() + " - " + ideName;
}
getIdeName().convention(ideName);

getSourceSet().convention(ExtensionUtils.getSourceSets(project).getByName(SourceSet.MAIN_SOURCE_SET_NAME));
}

@Override
Expand Down Expand Up @@ -113,6 +117,14 @@ public Configuration getAdditionalRuntimeClasspathConfiguration() {

public abstract Property<Level> getLogLevel();

/**
* Sets the source set to be used as the main classpath of this run.
* Defaults to the {@code main} source set.
* Eclipse does not support having multiple different classpaths per project beyond a separate unit-testing
* classpath.
*/
public abstract Property<SourceSet> getSourceSet();

@Override
public String toString() {
return "Run[" + getName() + "]";
Expand Down
117 changes: 80 additions & 37 deletions src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.neoforged.moddevgradle.internal.utils.ExtensionUtils;
import net.neoforged.moddevgradle.internal.utils.IdeDetection;
import net.neoforged.moddevgradle.tasks.JarJar;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
Expand All @@ -28,6 +29,7 @@
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
Expand Down Expand Up @@ -74,10 +76,16 @@ public class ModDevPlugin implements Plugin<Project> {
private static final String INTERNAL_TASK_GROUP = "mod development/internal";

/**
* Name of the configuration in which we place our generated artifacts for use in the runtime classpath,
* without having them leak to dependents.
* Name of the configuration in which we place the required dependencies to develop mods for use in the runtime-classpath.
* We cannot use "runtimeOnly", since the contents of that are published.
*/
private static final String CONFIGURATION_GENERATED_ARTIFACTS = "neoForgeGeneratedArtifacts";
public static final String CONFIGURATION_RUNTIME_DEPENDENCIES = "neoForgeRuntimeDependencies";

/**
* Name of the configuration in which we place the required dependencies to develop mods for use in the compile-classpath.
* While compile only is not published, we also use a configuration here to be consistent.
*/
public static final String CONFIGURATION_COMPILE_DEPENDENCIES = "neoForgeCompileDependencies";

private Runnable configureTesting = null;

Expand Down Expand Up @@ -227,8 +235,8 @@ public void apply(Project project) {
minecraftClassesArtifact = createArtifacts.map(task -> project.files(task.getCompiledArtifact()));
}

var localRuntime = configurations.create(CONFIGURATION_GENERATED_ARTIFACTS, config -> {
config.setDescription("Minecraft artifacts that were generated locally by NFRT");
var runtimeDependenciesConfig = configurations.create(CONFIGURATION_RUNTIME_DEPENDENCIES, config -> {
config.setDescription("The runtime dependencies to develop a mod for NeoForge, including Minecraft classes.");
config.setCanBeResolved(false);
config.setCanBeConsumed(false);
config.withDependencies(dependencies -> {
Expand All @@ -248,14 +256,26 @@ public void apply(Project project) {
});
});

configurations.create(CONFIGURATION_COMPILE_DEPENDENCIES, config -> {
config.setDescription("The compile-time dependencies to develop a mod for NeoForge, including Minecraft classes.");
config.setCanBeResolved(false);
config.setCanBeConsumed(false);
config.withDependencies(dependencies -> {
dependencies.addLater(minecraftClassesArtifact.map(dependencyFactory::create));
dependencies.addLater(neoForgeModDevLibrariesDependency);
});
});

var sourceSets = ExtensionUtils.getSourceSets(project);
var mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
extension.addModdingDependenciesTo(mainSourceSet);

configurations.named(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME).configure(configuration -> {
configuration.withDependencies(dependencies -> {
dependencies.addLater(minecraftClassesArtifact.map(dependencyFactory::create));
dependencies.addLater(neoForgeModDevLibrariesDependency);
});
});
var runtimeClasspath = configurations.named(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
runtimeClasspath.configure(files -> files.extendsFrom(localRuntime));

// Try to give people at least a fighting chance to run on the correct java version
project.afterEvaluate(ignored -> {
Expand All @@ -282,38 +302,39 @@ public void apply(Project project) {
})));
});

var neoForgeModDevModules = project.getConfigurations().create("neoForgeModuleOnly", spec -> {
spec.setDescription("Libraries that should be placed on the JVMs boot module path.");
spec.setCanBeResolved(true);
spec.setCanBeConsumed(false);
spec.shouldResolveConsistentlyWith(runtimeClasspath.get());
// NOTE: When running in vanilla mode, this configuration is simply empty
spec.withDependencies(set -> {
set.addLater(extension.getVersion().map(version -> {
return dependencyFactory.create("net.neoforged:neoforge:" + version)
.capabilities(caps -> {
caps.requireCapability("net.neoforged:neoforge-moddev-module-path");
})
// TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep.
.exclude(Map.of("group", "org.jetbrains", "module", "annotations"));
}));
set.add(dependencyFactory.create(RunUtils.DEV_LAUNCH_GAV));
});
});

var ideSyncTask = tasks.register("neoForgeIdeSync");

Map<RunModel, TaskProvider<PrepareRun>> prepareRunTasks = new IdentityHashMap<>();
extension.getRuns().configureEach(run -> {
var type = RunUtils.getRequiredType(project, run);

var sourceSet = ExtensionUtils.getExtension(project, "sourceSets", SourceSetContainer.class).getByName("main");
var runtimeClasspathConfig = run.getSourceSet().map(sourceSet -> sourceSet.getRuntimeClasspathConfigurationName())
.map(name -> configurations.getByName(name));

var neoForgeModDevModules = project.getConfigurations().create(InternalModelHelper.nameOfRun(run, "", "modulesOnly"), spec -> {
spec.setDescription("Libraries that should be placed on the JVMs boot module path for run " + run.getName() + ".");
spec.setCanBeResolved(true);
spec.setCanBeConsumed(false);
spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get());
// NOTE: When running in vanilla mode, this configuration is simply empty
spec.withDependencies(set -> {
set.addLater(extension.getVersion().map(version -> {
return dependencyFactory.create("net.neoforged:neoforge:" + version)
.capabilities(caps -> {
caps.requireCapability("net.neoforged:neoforge-moddev-module-path");
})
// TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep.
.exclude(Map.of("group", "org.jetbrains", "module", "annotations"));
}));
set.add(dependencyFactory.create(RunUtils.DEV_LAUNCH_GAV));
});
});

var legacyClasspathConfiguration = configurations.create(InternalModelHelper.nameOfRun(run, "", "legacyClasspath"), spec -> {
spec.setDescription("Contains all dependencies of the " + run.getName() + " run that should not be considered boot classpath modules.");
spec.setCanBeResolved(true);
spec.setCanBeConsumed(false);
spec.shouldResolveConsistentlyWith(runtimeClasspath.get());
spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get());
spec.attributes(attributes -> {
attributes.attributeProvider(ATTRIBUTE_DISTRIBUTION, type.map(t -> t.equals("client") ? "client" : "server"));
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME));
Expand Down Expand Up @@ -367,7 +388,7 @@ public void apply(Project project) {
task.getJavaLauncher().set(toolchainService.launcherFor(spec -> spec.getLanguageVersion().set(javaExtension.getToolchain().getLanguageVersion())));
// Note: this contains both the runtimeClasspath configuration and the sourceset's outputs.
// This records a dependency on compiling and processing the resources of the source set.
task.getClasspathProvider().from(sourceSet.getRuntimeClasspath());
task.getClasspathProvider().from(mainSourceSet.getRuntimeClasspath());
task.getGameDirectory().set(run.getGameDirectory());

task.getEnvironmentProperty().set(run.getEnvironment());
Expand All @@ -381,14 +402,12 @@ public void apply(Project project) {
});
});


setupJarJar(project);

configureTesting = () -> setupTesting(
project,
modDevBuildDir,
userDevConfigOnly,
neoForgeModDevModules,
downloadAssets,
ideSyncTask,
createArtifacts,
Expand Down Expand Up @@ -515,7 +534,6 @@ public void setupTesting() {
private void setupTesting(Project project,
Provider<Directory> modDevDir,
Configuration userDevConfigOnly,
Configuration neoForgeModDevModules,
TaskProvider<DownloadAssetsTask> downloadAssets,
TaskProvider<Task> ideSyncTask,
TaskProvider<CreateMinecraftArtifactsTask> createArtifacts,
Expand Down Expand Up @@ -550,15 +568,35 @@ private void setupTesting(Project project,
});
});

configurations.named(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, files -> {
files.extendsFrom(configurations.getByName(CONFIGURATION_GENERATED_ARTIFACTS));
var testRuntimeClasspathConfig = configurations.named(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, files -> {
files.extendsFrom(configurations.getByName(CONFIGURATION_RUNTIME_DEPENDENCIES));
files.extendsFrom(testFixtures);
});

var neoForgeModDevModules = project.getConfigurations().create("neoForgeTestModules", spec -> {
spec.setDescription("Libraries that should be placed on the JVMs boot module path for unit tests.");
spec.setCanBeResolved(true);
spec.setCanBeConsumed(false);
spec.shouldResolveConsistentlyWith(testRuntimeClasspathConfig.get());
// NOTE: When running in vanilla mode, this configuration is simply empty
spec.withDependencies(set -> {
set.addLater(extension.getVersion().map(version -> {
return dependencyFactory.create("net.neoforged:neoforge:" + version)
.capabilities(caps -> {
caps.requireCapability("net.neoforged:neoforge-moddev-module-path");
})
// TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep.
.exclude(Map.of("group", "org.jetbrains", "module", "annotations"));
}));
set.add(dependencyFactory.create(RunUtils.DEV_LAUNCH_GAV));
});
});

var legacyClasspathConfiguration = configurations.create("neoForgeTestLibraries", spec -> {
spec.setDescription("Contains the legacy classpath of the test run");
spec.setDescription("Contains the legacy classpath of unit tests.");
spec.setCanBeResolved(true);
spec.setCanBeConsumed(false);
spec.shouldResolveConsistentlyWith(testRuntimeClasspathConfig.get());
spec.attributes(attributes -> {
attributes.attribute(ATTRIBUTE_DISTRIBUTION, "client");
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME));
Expand Down Expand Up @@ -681,8 +719,13 @@ private static void addIntelliJRunConfiguration(Project project,
RunModel run,
PrepareRun prepareTask) {
var appRun = new Application(run.getIdeName().get(), project);
var sourceSets = ExtensionUtils.getExtension(project, "sourceSets", SourceSetContainer.class);
appRun.setModuleRef(new ModuleRef(project, sourceSets.getByName("main")));
var sourceSets = ExtensionUtils.getSourceSets(project);
var sourceSet = run.getSourceSet().get();
// Validate that the source set is part of this project
if (!sourceSets.contains(sourceSet)) {
throw new GradleException("Cannot use source set from another project for run " + run.getName());
}
appRun.setModuleRef(new ModuleRef(project, sourceSet));
appRun.setWorkingDirectory(run.getGameDirectory().get().getAsFile().getAbsolutePath());
appRun.setEnvs(run.getEnvironment().get());

Expand Down
11 changes: 5 additions & 6 deletions src/main/java/net/neoforged/moddevgradle/internal/RunUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.neoforged.moddevgradle.dsl.NeoForgeExtension;
import net.neoforged.moddevgradle.dsl.RunModel;
import net.neoforged.moddevgradle.internal.utils.ExtensionUtils;
import net.neoforged.moddevgradle.internal.utils.IdeDetection;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection;
Expand Down Expand Up @@ -246,14 +247,12 @@ private static Provider<Map<String, ModFolder>> getModFoldersForGradle(Project p

/**
* Returns the configured output directory, only if "Build and run using" is set to "IDEA".
* In other cases, returns {@null}.
* In other cases, returns {@code null}.
*/
@Nullable
static File getIntellijOutputDirectory(Project project) {
// TODO: this doesn't work in our little testproject because the .idea folder is one level above the root...
var projectDir = project.getRootDir();
var ideaDir = new File(projectDir, ".idea");
if (!ideaDir.exists()) {
var ideaDir = IdeDetection.getIntellijProjectDir(project);
if (ideaDir == null) {
return null;
}

Expand All @@ -272,7 +271,7 @@ static File getIntellijOutputDirectory(Project project) {
outputDirUrl = "file://$PROJECT_DIR$/out";
}

outputDirUrl = outputDirUrl.replace("$PROJECT_DIR$", projectDir.getAbsolutePath());
outputDirUrl = outputDirUrl.replace("$PROJECT_DIR$", project.getProjectDir().getAbsolutePath());
outputDirUrl = outputDirUrl.replaceAll("^file:", "");

// The output dir can start with something like "//C:\"; File can handle it.
Expand Down
Loading

0 comments on commit 412a52a

Please sign in to comment.