Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

package net.fabricmc.loom.configuration.fabricapi;

import java.io.IOException;

import javax.inject.Inject;

import org.gradle.api.Project;
Expand Down Expand Up @@ -64,17 +62,13 @@ protected SourceSet configureSourceSet(Property<String> 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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
package net.fabricmc.loom.extension;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Expand All @@ -34,7 +33,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;
Expand Down Expand Up @@ -73,8 +71,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;

/**
Expand All @@ -86,6 +83,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;
Expand Down Expand Up @@ -118,6 +116,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);
Expand Down Expand Up @@ -205,6 +204,11 @@ public RegularFileProperty getAccessWidenerPath() {
return accessWidener;
}

@Override
public RegularFileProperty getFabricModJsonPath() {
return fabricModJsonPath;
}

@Override
public NamedDomainObjectContainer<DecompilerOptions> getDecompilerOptions() {
return decompilers;
Expand Down Expand Up @@ -293,17 +297,13 @@ public SetProperty<String> getKnownIndyBsms() {

@Override
public String getModVersion() {
try {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), SourceSetHelper.getMainSourceSet(getProject()));
var 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
Expand Down
25 changes: 18 additions & 7 deletions src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,27 +107,38 @@ public static Optional<FabricModJson> createFromZipOptional(Path zipPath) {
return Optional.ofNullable(createFromZipNullable(zipPath));
}

public static FabricModJson createFromFile(File file) {
var 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) {
var file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, project, sourceSets);

if (file == null) {
return null;
}

try {
var 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);
}
Expand Down
26 changes: 15 additions & 11 deletions src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,41 @@

package net.fabricmc.loom.util.fmj;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.gradle.api.Project;
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);
var overrideFile = extension.getFabricModJsonPath().getAsFile();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: I see you like to use var wherever possible, this isnt something we tend to do. In other fabric projects we disallow var other than for new instance creataion. e.g var a = new Object() is fine as you can clearly see the type. Loom doesnt enforce this as there are some places where gradle forces you to use an obtuse type, but for simple types such a File in this case I wouldnt choose to use var as it makes the code harder to read.

Dont worry about it for this PR its just a minor nit pick.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing out. I used var for consistency with the other cases I've seen in the area, but I can replace it if needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ive done it :) I also moved the tests to one of the existing files, this is becuase each test file gets a new github actions job, there was nothing wrong with having its own file but it will be a tiny bit quicker. Im happy with this PR now :) Thanks.


if (overrideFile.isPresent()) {
return List.of(FabricModJsonFactory.createFromFile(overrideFile.get()));
}

var sourceSets = new ArrayList<SourceSet>();
sourceSets.add(SourceSetHelper.getMainSourceSet(project));

if (extension.areEnvironmentSourceSetsSplit()) {
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 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.test.integration

import org.gradle.testkit.runner.BuildResult
import spock.lang.Specification
import spock.lang.Unroll

import net.fabricmc.loom.test.util.GradleProjectTestTrait

import static org.gradle.testkit.runner.TaskOutcome.SUCCESS

class FabricModJsonPathTest extends Specification implements GradleProjectTestTrait {
@Unroll
def "Resolve custom FMJ"() {
setup:
GradleProject gradle = gradleProject(project: "fmjPathConfig")

when:
BuildResult result = gradle.run(task: "build", args: ["-PoverrideFMJ=true"])

then:
result.task(":build").outcome == SUCCESS
}

@Unroll
def "Fail to find FMJ"() {
setup:
GradleProject gradle = gradleProject(project: "fmjPathConfig")

when:
BuildResult result = gradle.run(task: "build", expectFailure: true)

then:
result.task(":build") == null
}
}
37 changes: 37 additions & 0 deletions src/test/resources/projects/fmjPathConfig/build.gradle
Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"schemaVersion": 1,
"id": "testmod",
"version": "1",
"name": "Test Mod",
"custom": {
"loom:injected_interfaces": {
"net/minecraft/class_310": ["InjectedInterface"]
}
}
}
Loading
Loading