Make fabric.mod.json path configurable at configuration stage. (#1364)

* Add `fabric.mod.json` path property.

* Add path provider integration tests.

* Separate fabric.mod.json reading methods and move selection to the helper method.

* Separate FMJ getter methods and add configuration to datagen and testmod settings.

* Remove the `fabricModJsonPath` property from Fabric API source sets

* Address review requests by fixing formatting and removing redundant changes

* Fix build

* Move tests to existing file, and remove var usage.

* Fix build :)

---------

Co-authored-by: modmuss50 <modmuss50@gmail.com>
This commit is contained in:
KikuGie
2025-10-03 11:16:17 +02:00
committed by GitHub
parent 103db759f6
commit 7f95c3c60f
12 changed files with 185 additions and 41 deletions

View File

@@ -66,6 +66,22 @@ public interface LoomGradleExtensionAPI {
RegularFileProperty getAccessWidenerPath();
/**
* Specifies the {@code fabric.mod.json} file location used in injected interface processing.
*
* <p>
* By default, either {@code src/main/resources/fabric.mod.json}
* or {@code src/client/resources/fabric.mod.json} in the project directory is used.
* </p>
*
* <p>
* 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}).
* </p>
*/
RegularFileProperty getFabricModJsonPath();
NamedDomainObjectContainer<DecompilerOptions> getDecompilerOptions();
void decompilers(Action<NamedDomainObjectContainer<DecompilerOptions>> action);

View File

@@ -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<Boolean> 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());
}
}

View File

@@ -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<String> 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());
}
}

View File

@@ -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 @@ abstract class FabricApiAbstractSourceSet {
});
modId.convention(getProject().provider(() -> {
try {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), sourceSet);
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()");
}
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 -> {

View File

@@ -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.Action;
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.task.GenerateSourcesTask;
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<JarProcessor> jarProcessors;
protected final ConfigurableFileCollection log4jConfigs;
protected final RegularFileProperty accessWidener;
protected final RegularFileProperty fabricModJsonPath;
protected final ManifestLocations versionsManifests;
protected final Property<String> customMetadata;
protected final SetProperty<String> knownIndyBsms;
@@ -118,6 +118,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
.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 abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return accessWidener;
}
@Override
public RegularFileProperty getFabricModJsonPath() {
return fabricModJsonPath;
}
@Override
public NamedDomainObjectContainer<DecompilerOptions> getDecompilerOptions() {
return decompilers;
@@ -293,17 +299,13 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
@Override
public String getModVersion() {
try {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), SourceSetHelper.getMainSourceSet(getProject()));
List<FabricModJson> 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

View File

@@ -107,27 +107,38 @@ public final class FabricModJsonFactory {
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);
}

View File

@@ -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<FabricModJson> getModsInProject(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
Provider<File> overrideFile = extension.getFabricModJsonPath().getAsFile();
if (overrideFile.isPresent()) {
return List.of(FabricModJsonFactory.createFromFile(overrideFile.get()));
}
var sourceSets = new ArrayList<SourceSet>();
sourceSets.add(SourceSetHelper.getMainSourceSet(project));
@@ -47,14 +57,10 @@ public class FabricModJsonHelpers {
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();

View File

@@ -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
}
}

View File

@@ -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")
}
}

View File

@@ -0,0 +1,11 @@
{
"schemaVersion": 1,
"id": "testmod",
"version": "1",
"name": "Test Mod",
"custom": {
"loom:injected_interfaces": {
"net/minecraft/class_310": ["InjectedInterface"]
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,4 @@
public interface InjectedInterface {
default void newMethodThatDidNotExist() {
}
}