diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 8c7ae953c..4da936ba4 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -66,6 +66,22 @@ public interface LoomGradleExtensionAPI { RegularFileProperty getAccessWidenerPath(); + /** + * Specifies the {@code fabric.mod.json} file location used in injected interface processing. + * + *

+ * By default, either {@code src/main/resources/fabric.mod.json} + * or {@code src/client/resources/fabric.mod.json} in the project directory is used. + *

+ * + *

+ * Providing a path to a different location allows using a shared or preprocessed + * file. However, at the end it must be put into the root of the processed + * resources directory (usually {@code build/resources/main}). + *

+ */ + RegularFileProperty getFabricModJsonPath(); + NamedDomainObjectContainer getDecompilerOptions(); void decompilers(Action> action); diff --git a/src/main/java/net/fabricmc/loom/api/fabricapi/DataGenerationSettings.java b/src/main/java/net/fabricmc/loom/api/fabricapi/DataGenerationSettings.java index 4b86632c4..0b47ba3ce 100644 --- a/src/main/java/net/fabricmc/loom/api/fabricapi/DataGenerationSettings.java +++ b/src/main/java/net/fabricmc/loom/api/fabricapi/DataGenerationSettings.java @@ -24,9 +24,13 @@ package net.fabricmc.loom.api.fabricapi; +import java.io.File; + import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; + /** * Represents the settings for data generation. */ @@ -67,4 +71,11 @@ public interface DataGenerationSettings { * Contains a boolean property indicating whether data generation will be compiled and ran with the client. */ Property getClient(); + + /** + * Sets {@link #getModId()} property based on the {@code id} field defined in the provided file. + */ + default void modId(File fabricModJsonFile) { + getModId().set(FabricModJsonFactory.createFromFile(fabricModJsonFile).getId()); + } } diff --git a/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java b/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java index 1e5561918..de6a7251e 100644 --- a/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java +++ b/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java @@ -24,10 +24,14 @@ package net.fabricmc.loom.api.fabricapi; +import java.io.File; + import org.gradle.api.provider.Property; import org.gradle.api.tasks.Optional; import org.jetbrains.annotations.ApiStatus; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; + /** * Represents the settings for game and/or client tests. */ @@ -89,4 +93,11 @@ public interface GameTestSettings { */ @Optional Property getUsername(); + + /** + * Sets {@link #getModId()} property based on the {@code id} field defined in the provided file. + */ + default void modId(File fabricModJsonFile) { + getModId().set(FabricModJsonFactory.createFromFile(fabricModJsonFile).getId()); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java index 22bec5966..9be2291d2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java @@ -24,8 +24,6 @@ package net.fabricmc.loom.configuration.fabricapi; -import java.io.IOException; - import javax.inject.Inject; import org.gradle.api.Project; @@ -64,17 +62,13 @@ protected SourceSet configureSourceSet(Property modId, boolean isClient) }); modId.convention(getProject().provider(() -> { - try { - final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), sourceSet); - - if (fabricModJson == null) { - throw new RuntimeException("Could not find a fabric.mod.json file in the data source set or a value for DataGenerationSettings.getModId()"); - } + final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), sourceSet); - return fabricModJson.getId(); - } catch (IOException e) { - throw new org.gradle.api.UncheckedIOException("Failed to read mod id from the datagen source set.", e); + if (fabricModJson == null) { + throw new RuntimeException("Could not find a fabric.mod.json file in the data source set or a value for DataGenerationSettings.getModId()"); } + + return fabricModJson.getId(); })); extension.getMods().create(modId.get(), mod -> { diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index e547ac61b..b7ccb43fa 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -25,8 +25,8 @@ package net.fabricmc.loom.extension; import java.io.File; -import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -34,7 +34,6 @@ import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.NamedDomainObjectList; import org.gradle.api.Project; -import org.gradle.api.UncheckedIOException; import org.gradle.api.artifacts.Dependency; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; @@ -74,7 +73,7 @@ import net.fabricmc.loom.util.DeprecationHelper; import net.fabricmc.loom.util.MirrorUtil; import net.fabricmc.loom.util.fmj.FabricModJson; -import net.fabricmc.loom.util.fmj.FabricModJsonFactory; +import net.fabricmc.loom.util.fmj.FabricModJsonHelpers; import net.fabricmc.loom.util.gradle.SourceSetHelper; /** @@ -86,6 +85,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA protected final ListProperty jarProcessors; protected final ConfigurableFileCollection log4jConfigs; protected final RegularFileProperty accessWidener; + protected final RegularFileProperty fabricModJsonPath; protected final ManifestLocations versionsManifests; protected final Property customMetadata; protected final SetProperty knownIndyBsms; @@ -118,6 +118,7 @@ protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) { .empty(); this.log4jConfigs = project.files(directories.getDefaultLog4jConfigFile()); this.accessWidener = project.getObjects().fileProperty(); + this.fabricModJsonPath = project.getObjects().fileProperty(); this.versionsManifests = new ManifestLocations(); this.versionsManifests.add("mojang", MirrorUtil.getVersionManifests(project), -2); this.versionsManifests.add("fabric_experimental", MirrorUtil.getExperimentalVersions(project), -1); @@ -205,6 +206,11 @@ public RegularFileProperty getAccessWidenerPath() { return accessWidener; } + @Override + public RegularFileProperty getFabricModJsonPath() { + return fabricModJsonPath; + } + @Override public NamedDomainObjectContainer getDecompilerOptions() { return decompilers; @@ -293,17 +299,13 @@ public SetProperty getKnownIndyBsms() { @Override public String getModVersion() { - try { - final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), SourceSetHelper.getMainSourceSet(getProject())); + List fabricModJsons = FabricModJsonHelpers.getModsInProject(getProject()); - if (fabricModJson == null) { - throw new RuntimeException("Could not find a fabric.mod.json file in the main sourceset"); - } - - return fabricModJson.getModVersion(); - } catch (IOException e) { - throw new UncheckedIOException("Failed to read mod version from main sourceset.", e); + if (fabricModJsons.isEmpty()) { + throw new RuntimeException("Could not find a fabric.mod.json file in the main sourceset"); } + + return fabricModJsons.getFirst().getModVersion(); } @Override diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java index 0cfec9c6f..bb0d2a31a 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java @@ -107,27 +107,38 @@ public static Optional createFromZipOptional(Path zipPath) { return Optional.ofNullable(createFromZipNullable(zipPath)); } + public static FabricModJson createFromFile(File file) { + JsonObject modJson = readFmjJsonObject(file); + return create(modJson, new FabricModJsonSource.DirectorySource(file.toPath().getParent())); + } + @Nullable - public static FabricModJson createFromSourceSetsNullable(Project project, SourceSet... sourceSets) throws IOException { - final File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, project, sourceSets); + public static FabricModJson createFromSourceSetsNullable(Project project, SourceSet... sourceSets) { + File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, project, sourceSets); if (file == null) { return null; } + try { + JsonObject modJson = readFmjJsonObject(file); + return create(modJson, new FabricModJsonSource.SourceSetSource(project, sourceSets)); + } catch (JsonSyntaxException e) { + LOGGER.warn("Failed to parse fabric.mod.json: {}", file.getAbsolutePath()); + return null; + } + } + + private static JsonObject readFmjJsonObject(File file) { try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { final JsonObject modJson = LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class); if (modJson == null) { // fromJson returns null if the file is empty LOGGER.warn("Failed to parse empty fabric.mod.json: {}", file.getAbsolutePath()); - return null; } - return create(modJson, new FabricModJsonSource.SourceSetSource(project, sourceSets)); - } catch (JsonSyntaxException e) { - LOGGER.warn("Failed to parse fabric.mod.json: {}", file.getAbsolutePath()); - return null; + return modJson; } catch (IOException e) { throw new UncheckedIOException("Failed to read " + file.getAbsolutePath(), e); } diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonHelpers.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonHelpers.java index 43e0e4745..4481283af 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonHelpers.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonHelpers.java @@ -24,22 +24,32 @@ package net.fabricmc.loom.util.fmj; -import java.io.IOException; -import java.io.UncheckedIOException; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.gradle.api.Project; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; -import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.util.gradle.SourceSetHelper; +import net.fabricmc.loom.LoomGradleExtension; public class FabricModJsonHelpers { - // Returns a list of Mods found in the provided project's main or client sourcesets + /** + * Returns the list of mods provided by either {@link LoomGradleExtensionAPI#getFabricModJsonPath()} + * or {@code fabric.mod.json} in main or client resources. + */ public static List getModsInProject(Project project) { final LoomGradleExtension extension = LoomGradleExtension.get(project); + Provider overrideFile = extension.getFabricModJsonPath().getAsFile(); + + if (overrideFile.isPresent()) { + return List.of(FabricModJsonFactory.createFromFile(overrideFile.get())); + } + var sourceSets = new ArrayList(); sourceSets.add(SourceSetHelper.getMainSourceSet(project)); @@ -47,14 +57,10 @@ public static List getModsInProject(Project project) { sourceSets.add(SourceSetHelper.getSourceSetByName("client", project)); } - try { - final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(project, sourceSets.toArray(SourceSet[]::new)); + final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(project, sourceSets.toArray(SourceSet[]::new)); - if (fabricModJson != null) { - return List.of(fabricModJson); - } - } catch (IOException e) { - throw new UncheckedIOException(e); + if (fabricModJson != null) { + return List.of(fabricModJson); } return Collections.emptyList(); diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/InterfaceInjectionTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/InterfaceInjectionTest.groovy index 947bb8267..52946286d 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/InterfaceInjectionTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/InterfaceInjectionTest.groovy @@ -24,6 +24,7 @@ package net.fabricmc.loom.test.integration +import org.gradle.testkit.runner.BuildResult import spock.lang.Specification import spock.lang.Unroll @@ -49,4 +50,34 @@ class InterfaceInjectionTest extends Specification implements GradleProjectTestT where: version << STANDARD_TEST_VERSIONS } + + @Unroll + def "Resolve custom FMJ"() { + setup: + GradleProject gradle = gradleProject(project: "fmjPathConfig", version: version) + + when: + BuildResult result = gradle.run(task: "build", args: ["-PoverrideFMJ=true"]) + + then: + result.task(":build").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } + + @Unroll + def "Fail to find FMJ"() { + setup: + GradleProject gradle = gradleProject(project: "fmjPathConfig", version: version) + + when: + BuildResult result = gradle.run(task: "build", expectFailure: true) + + then: + result.task(":build") == null + + where: + version << STANDARD_TEST_VERSIONS + } } diff --git a/src/test/resources/projects/fmjPathConfig/build.gradle b/src/test/resources/projects/fmjPathConfig/build.gradle new file mode 100644 index 000000000..287825cf1 --- /dev/null +++ b/src/test/resources/projects/fmjPathConfig/build.gradle @@ -0,0 +1,37 @@ +// This is used by a range of tests that append to this file before running the gradle tasks. +// Can be used for tests that require minimal custom setup +plugins { + id 'fabric-loom' + id 'maven-publish' +} + +version = "1.0.0" +group = "com.example" + +// In multi-version setup this would be a separate project, +// but a source set will suffice for a test. +sourceSets { + custom { + } + main { + compileClasspath += sourceSets.custom.compileClasspath + runtimeClasspath += sourceSets.custom.runtimeClasspath + } +} + + +dependencies { + minecraft "com.mojang:minecraft:1.17.1" + mappings "net.fabricmc:yarn:1.17.1+build.59:v2" + modImplementation "net.fabricmc:fabric-loader:0.11.6" +} + +base { + archivesName = "fabric-example-mod" +} + +if (project.hasProperty("overrideFMJ")) { + loom { + fabricModJsonPath = file("src/custom/resources/fabric.mod.json") + } +} \ No newline at end of file diff --git a/src/test/resources/projects/fmjPathConfig/src/custom/resources/fabric.mod.json b/src/test/resources/projects/fmjPathConfig/src/custom/resources/fabric.mod.json new file mode 100644 index 000000000..64b2e239a --- /dev/null +++ b/src/test/resources/projects/fmjPathConfig/src/custom/resources/fabric.mod.json @@ -0,0 +1,11 @@ +{ + "schemaVersion": 1, + "id": "testmod", + "version": "1", + "name": "Test Mod", + "custom": { + "loom:injected_interfaces": { + "net/minecraft/class_310": ["InjectedInterface"] + } + } +} diff --git a/src/test/resources/projects/fmjPathConfig/src/main/java/ExampleMod.java b/src/test/resources/projects/fmjPathConfig/src/main/java/ExampleMod.java new file mode 100644 index 000000000..cd72e67d4 --- /dev/null +++ b/src/test/resources/projects/fmjPathConfig/src/main/java/ExampleMod.java @@ -0,0 +1,10 @@ +import net.minecraft.client.MinecraftClient; + +import net.fabricmc.api.ModInitializer; + +public class ExampleMod implements ModInitializer { + @Override + public void onInitialize() { + MinecraftClient.getInstance().newMethodThatDidNotExist(); + } +} diff --git a/src/test/resources/projects/fmjPathConfig/src/main/java/InjectedInterface.java b/src/test/resources/projects/fmjPathConfig/src/main/java/InjectedInterface.java new file mode 100644 index 000000000..a8225d0c3 --- /dev/null +++ b/src/test/resources/projects/fmjPathConfig/src/main/java/InjectedInterface.java @@ -0,0 +1,4 @@ +public interface InjectedInterface { + default void newMethodThatDidNotExist() { + } +}