diff --git a/src/main/java/net/fabricmc/loom/api/ModSettings.java b/src/main/java/net/fabricmc/loom/api/ModSettings.java index af31a2cc..ebe53ac2 100644 --- a/src/main/java/net/fabricmc/loom/api/ModSettings.java +++ b/src/main/java/net/fabricmc/loom/api/ModSettings.java @@ -27,21 +27,20 @@ package net.fabricmc.loom.api; import javax.inject.Inject; import org.gradle.api.Named; +import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.provider.ListProperty; import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.ApiStatus; +import net.fabricmc.loom.util.gradle.SourceSetHelper; +import net.fabricmc.loom.util.gradle.SourceSetReference; + /** * A {@link Named} object for setting mod-related values. The {@linkplain Named#getName() name} should match the mod id. */ @ApiStatus.Experimental public abstract class ModSettings implements Named { - /** - * List of classpath directories, used to populate the `fabric.classPathGroups` Fabric Loader system property. - */ - public abstract ListProperty getModSourceSets(); - /** * List of classpath directories, or jar files used to populate the `fabric.classPathGroups` Fabric Loader system property. */ @@ -54,9 +53,37 @@ public abstract class ModSettings implements Named { } /** - * Mark a {@link SourceSet} output directories part of the named mod. + * Add {@link SourceSet}'s output directories from the current project to be grouped with the named mod. */ public void sourceSet(SourceSet sourceSet) { - getModSourceSets().add(sourceSet); + Project project = getProject(); + + if (!SourceSetHelper.isSourceSetOfProject(sourceSet, project)) { + getProject().getLogger().info("Computing owner project for SourceSet {} as it is not a sourceset of {}", sourceSet.getName(), project.getPath()); + project = SourceSetHelper.getSourceSetProject(sourceSet); + + if (project == getProject()) { + throw new IllegalStateException("isSourceSetOfProject lied, report to loom devs."); + } + } + + sourceSet(sourceSet, project); } + + /** + * Add {@link SourceSet}'s output directories from supplied project to be grouped with the named mod. + */ + public void sourceSet(SourceSet sourceSet, Project project) { + getModSourceSets().add(new SourceSetReference(sourceSet, project)); + } + + /** + * List of classpath directories, used to populate the `fabric.classPathGroups` Fabric Loader system property. + * Use the {@link ModSettings#sourceSet} methods to add to this. + */ + @ApiStatus.Internal + public abstract ListProperty getModSourceSets(); + + @Inject + public abstract Project getProject(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java index cd834b1e..8a010518 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -53,6 +53,7 @@ import net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask; import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; import net.fabricmc.loom.configuration.providers.BundleMetadata; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.gradle.SourceSetReference; public class RunConfig { public String configName; @@ -157,7 +158,7 @@ public class RunConfig { RunConfig runConfig = new RunConfig(); runConfig.configName = configName; populate(project, extension, runConfig, environment); - runConfig.ideaModuleName = IdeaUtils.getIdeaModuleName(project, sourceSet); + runConfig.ideaModuleName = IdeaUtils.getIdeaModuleName(new SourceSetReference(sourceSet, project)); runConfig.runDirIdeaUrl = "file://$PROJECT_DIR$/" + runDir; runConfig.runDir = runDir; runConfig.sourceSet = sourceSet; diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaUtils.java b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaUtils.java index fd1e26ab..3c5f8263 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaUtils.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaUtils.java @@ -27,7 +27,8 @@ package net.fabricmc.loom.configuration.ide.idea; import java.util.Objects; import org.gradle.api.Project; -import org.gradle.api.tasks.SourceSet; + +import net.fabricmc.loom.util.gradle.SourceSetReference; public class IdeaUtils { public static boolean isIdeaSync() { @@ -46,8 +47,9 @@ public class IdeaUtils { return major > 2021 || (major == 2021 && minor >= 3); } - public static String getIdeaModuleName(Project project, SourceSet srcs) { - String module = project.getName() + "." + srcs.getName(); + public static String getIdeaModuleName(SourceSetReference reference) { + Project project = reference.project(); + String module = project.getName() + "." + reference.sourceSet().getName(); while ((project = project.getParent()) != null) { module = project.getName() + "." + module; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java index ef9427ae..1e049343 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java @@ -195,11 +195,6 @@ public abstract sealed class MinecraftSourceSets permits MinecraftSourceSets.Sin .plus(mainSourceSet.getOutput()) ); - loomExtension.mixin(mixinExtension -> { - // Generate a refmap for mixins in the new source set. - mixinExtension.add(clientOnlySourceSet, "client-" + mixinExtension.getDefaultRefmapName().get(), (p) -> { }); - }); - // Include the client only output in the jars project.getTasks().named(mainSourceSet.getJarTaskName(), Jar.class).configure(jar -> { jar.from(clientOnlySourceSet.getOutput().getClassesDirs()); diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java index c6f0687a..5c31fce7 100644 --- a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java @@ -122,9 +122,19 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx @Override public void init() { if (isDefault) { - project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().forEach(this::add); + initDefault(); } isDefault = false; } + + private void initDefault() { + project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().forEach(sourceSet -> { + if (sourceSet.getName().equals("main")) { + add(sourceSet); + } else { + add(sourceSet, sourceSet.getName() + "-" + getDefaultRefmapName().get()); + } + }); + } } diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 7ec675af..5ce99e76 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -59,6 +59,10 @@ public final class LoomTasks { }); tasks.register("generateDLIConfig", GenerateDLIConfigTask.class, t -> { t.setDescription("Generate the DevLaunchInjector config file"); + + // Must allow these IDE files to be generated first + t.mustRunAfter(tasks.named("eclipse")); + t.mustRunAfter(tasks.named("idea")); }); tasks.register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> { t.setDescription("Generate the log4j config file"); diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index 87c977e5..1355279d 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -108,7 +108,7 @@ public class Constants { * Constants for versions of dependencies. */ public static final class Versions { - public static final String MIXIN_COMPILE_EXTENSIONS = "0.4.7"; + public static final String MIXIN_COMPILE_EXTENSIONS = "0.5.0"; public static final String DEV_LAUNCH_INJECTOR = "0.2.1+build.8"; public static final String TERMINAL_CONSOLE_APPENDER = "1.2.0"; public static final String JETBRAINS_ANNOTATIONS = "23.0.0"; diff --git a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java index 4950be4f..e4dd21bf 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java @@ -37,8 +37,13 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.internal.tasks.DefaultSourceSetOutput; +import org.gradle.api.internal.tasks.DefaultTaskDependency; +import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetOutput; +import org.gradle.api.tasks.TaskProvider; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; @@ -55,6 +60,42 @@ public final class SourceSetHelper { private SourceSetHelper() { } + /** + * Returns true when the provided project contains the {@link SourceSet}. + */ + public static boolean isSourceSetOfProject(SourceSet sourceSet, Project project) { + if (System.getProperty("fabric-loom.unit.testing") != null) return true; + + final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class); + return javaExtension.getSourceSets().stream() + .anyMatch(test -> test == sourceSet); // Ensure we have an identical reference + } + + /** + * Attempts to compute the owning project for the {@link SourceSet} + * + *

A bit of a hack, would be nice for this to be added to the Gradle API. + */ + public static Project getSourceSetProject(SourceSet sourceSet) { + final DefaultSourceSetOutput sourceSetOutput = (DefaultSourceSetOutput) sourceSet.getOutput(); + final DefaultTaskDependency taskDependency = (DefaultTaskDependency) sourceSetOutput.getClassesContributors(); + Project project = null; + + for (Object object : taskDependency.getMutableValues()) { + if (object instanceof Task task) { + project = task.getProject(); + } else if (object instanceof TaskProvider provider) { + project = provider.get().getProject(); + } + } + + if (project == null) { + throw new NullPointerException("Unable to determine owning project for SourceSet: " + sourceSet.getName()); + } + + return project; + } + public static List getClasspath(ModSettings modSettings, Project project) { final List files = new ArrayList<>(); @@ -66,18 +107,18 @@ public final class SourceSetHelper { return Collections.unmodifiableList(files); } - public static List getClasspath(SourceSet sourceSet, Project project) { - final List classpath = getGradleClasspath(sourceSet); + public static List getClasspath(SourceSetReference reference, Project project) { + final List classpath = getGradleClasspath(reference); - classpath.addAll(getIdeaClasspath(sourceSet, project)); - classpath.addAll(getEclipseClasspath(sourceSet, project)); - classpath.addAll(getVscodeClasspath(sourceSet, project)); + classpath.addAll(getIdeaClasspath(reference, project)); + classpath.addAll(getEclipseClasspath(reference, project)); + classpath.addAll(getVscodeClasspath(reference, project)); return classpath; } - private static List getGradleClasspath(SourceSet sourceSet) { - final SourceSetOutput output = sourceSet.getOutput(); + private static List getGradleClasspath(SourceSetReference reference) { + final SourceSetOutput output = reference.sourceSet().getOutput(); final File resources = output.getResourcesDir(); final List classpath = new ArrayList<>(); @@ -92,7 +133,7 @@ public final class SourceSetHelper { } @VisibleForTesting - public static List getIdeaClasspath(SourceSet sourceSet, Project project) { + public static List getIdeaClasspath(SourceSetReference reference, Project project) { final File projectDir = project.getRootDir(); final File dotIdea = new File(projectDir, ".idea"); @@ -116,7 +157,7 @@ public final class SourceSetHelper { outputDirUrl = outputDirUrl.replaceAll("^file:", ""); final File productionDir = new File(outputDirUrl, "production"); - final File outputDir = new File(productionDir, IdeaUtils.getIdeaModuleName(project, sourceSet)); + final File outputDir = new File(productionDir, IdeaUtils.getIdeaModuleName(reference)); return Collections.singletonList(outputDir); } @@ -135,7 +176,7 @@ public final class SourceSetHelper { } @VisibleForTesting - public static List getEclipseClasspath(SourceSet sourceSet, Project project) { + public static List getEclipseClasspath(SourceSetReference reference, Project project) { // Somewhat of a guess, I'm unsure if this is correct for multi-project builds final File projectDir = project.getProjectDir(); final File classpath = new File(projectDir, ".classpath"); @@ -144,11 +185,11 @@ public final class SourceSetHelper { return Collections.emptyList(); } - return getBinDirClasspath(projectDir, sourceSet); + return getBinDirClasspath(projectDir, reference); } @VisibleForTesting - public static List getVscodeClasspath(SourceSet sourceSet, Project project) { + public static List getVscodeClasspath(SourceSetReference reference, Project project) { // Somewhat of a guess, I'm unsure if this is correct for multi-project builds final File projectDir = project.getProjectDir(); final File dotVscode = new File(projectDir, ".vscode"); @@ -157,16 +198,16 @@ public final class SourceSetHelper { return Collections.emptyList(); } - return getBinDirClasspath(projectDir, sourceSet); + return getBinDirClasspath(projectDir, reference); } - private static List getBinDirClasspath(File projectDir, SourceSet sourceSet) { + private static List getBinDirClasspath(File projectDir, SourceSetReference reference) { final File binDir = new File(projectDir, "bin"); if (!binDir.exists()) { return Collections.emptyList(); } - return Collections.singletonList(new File(binDir, sourceSet.getName())); + return Collections.singletonList(new File(binDir, reference.sourceSet().getName())); } } diff --git a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetReference.java b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetReference.java new file mode 100644 index 00000000..6876446e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetReference.java @@ -0,0 +1,41 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.gradle; + +import com.google.common.base.Preconditions; +import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; + +/** + * A reference to a {@link SourceSet} from a {@link Project}. + */ +public record SourceSetReference(SourceSet sourceSet, Project project) { + public SourceSetReference { + Preconditions.checkArgument( + SourceSetHelper.isSourceSetOfProject(sourceSet, project), + "SourceSet (%s) does not own to (%s) project".formatted(sourceSet.getName(), project.getName()) + ); + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy index 45642ec9..ddad9497 100644 --- a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy @@ -28,7 +28,7 @@ import org.gradle.util.GradleVersion class LoomTestConstants { public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion() - public final static String PRE_RELEASE_GRADLE = "7.5-20220415181004+0000" + public final static String PRE_RELEASE_GRADLE = "7.6-20220516224938+0000" public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE] } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/SourceSetHelperTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/SourceSetHelperTest.groovy index 22499db5..d0ceb893 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/SourceSetHelperTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/SourceSetHelperTest.groovy @@ -25,16 +25,19 @@ package net.fabricmc.loom.test.unit import net.fabricmc.loom.util.gradle.SourceSetHelper +import net.fabricmc.loom.util.gradle.SourceSetReference import org.gradle.api.Project import org.gradle.api.tasks.SourceSet import org.intellij.lang.annotations.Language import spock.lang.Shared import spock.lang.Specification +import spock.util.environment.RestoreSystemProperties class SourceSetHelperTest extends Specification { @Shared private static File projectDir = File.createTempDir() + @RestoreSystemProperties def "idea classpath"() { given: def miscXml = new File(projectDir, ".idea/misc.xml") @@ -48,8 +51,11 @@ class SourceSetHelperTest extends Specification { mockProject.getRootDir() >> projectDir mockSourceSet.getName() >> "main" + System.setProperty("fabric-loom.unit.testing", "true") + + def ref = new SourceSetReference(mockSourceSet, mockProject) when: - def result = SourceSetHelper.getIdeaClasspath(mockSourceSet, mockProject) + def result = SourceSetHelper.getIdeaClasspath(ref, mockProject) then: result.size() == 1 @@ -58,6 +64,7 @@ class SourceSetHelperTest extends Specification { println(result[0].toString()) } + @RestoreSystemProperties def "eclipse classpath"() { given: def classpath = new File(projectDir, ".classpath") @@ -73,14 +80,18 @@ class SourceSetHelperTest extends Specification { mockProject.getProjectDir() >> projectDir mockSourceSet.getName() >> "main" + System.setProperty("fabric-loom.unit.testing", "true") + + def ref = new SourceSetReference(mockSourceSet, mockProject) when: - def result = SourceSetHelper.getEclipseClasspath(mockSourceSet, mockProject) + def result = SourceSetHelper.getEclipseClasspath(ref, mockProject) then: result.size() == 1 println(result[0].toString()) } + @RestoreSystemProperties def "vscode classpath"() { given: def dotVscode = new File(projectDir, ".vscode") @@ -96,8 +107,11 @@ class SourceSetHelperTest extends Specification { mockProject.getProjectDir() >> projectDir mockSourceSet.getName() >> "main" + System.setProperty("fabric-loom.unit.testing", "true") + + def ref = new SourceSetReference(mockSourceSet, mockProject) when: - def result = SourceSetHelper.getVscodeClasspath(mockSourceSet, mockProject) + def result = SourceSetHelper.getVscodeClasspath(ref, mockProject) then: result.size() == 1 diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy index c5893a12..4dcecedd 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy @@ -48,6 +48,11 @@ trait GradleProjectTestTrait { setupProject(options, projectDir) + println([ + projectDir: projectDir.absolutePath, + gradleHomeDir: gradleHomeDir.absolutePath + ]) + return new GradleProject( gradleVersion: gradleVersion, projectDir: projectDir.absolutePath, diff --git a/src/test/resources/projects/multiproject/build.gradle b/src/test/resources/projects/multiproject/build.gradle index 17480a88..0a90269a 100644 --- a/src/test/resources/projects/multiproject/build.gradle +++ b/src/test/resources/projects/multiproject/build.gradle @@ -42,7 +42,20 @@ allprojects { java { withSourcesJar() } +} +loom { + mods { + core { + sourceSet project(':core').sourceSets.main + } + example { + sourceSet project(':example').sourceSets.main + } + root { + sourceSet sourceSets.main + } + } } dependencies {