Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,18 @@
import java.util.stream.Stream;

import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.attributes.Usage;
import org.gradle.api.plugins.JavaPlugin;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;

import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.processor.SpecContext;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.util.AsyncCache;
import net.fabricmc.loom.util.Constants;
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.GradleUtils;

/**
* @param modDependencies External mods that are depended on
Expand All @@ -65,20 +61,24 @@ public record SpecContextImpl(
List<FabricModJson> localMods,
List<ModHolder> compileRuntimeMods) implements SpecContext {
public static SpecContextImpl create(Project project) {
AsyncCache<List<FabricModJson>> fmjCache = new AsyncCache<List<FabricModJson>>();
return create(new SpecContextProjectView.Impl(project, LoomGradleExtension.get(project)));
}

@VisibleForTesting
public static SpecContextImpl create(SpecContextProjectView projectView) {
AsyncCache<List<FabricModJson>> fmjCache = new AsyncCache<>();
return new SpecContextImpl(
getDependentMods(project, fmjCache),
FabricModJsonHelpers.getModsInProject(project),
getCompileRuntimeMods(project, fmjCache)
getDependentMods(projectView, fmjCache),
projectView.getMods(),
getCompileRuntimeMods(projectView, fmjCache)
);
}

// Reruns a list of mods found on both the compile and/or runtime classpaths
private static List<FabricModJson> getDependentMods(Project project, AsyncCache<List<FabricModJson>> fmjCache) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
private static List<FabricModJson> getDependentMods(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache) {
var futures = new ArrayList<CompletableFuture<List<FabricModJson>>>();

for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) {
for (RemapConfigurationSettings entry : projectView.extension().getRemapConfigurations()) {
final Set<File> artifacts = entry.getSourceConfiguration().get().resolve();

for (File artifact : artifacts) {
Expand All @@ -90,30 +90,29 @@ private static List<FabricModJson> getDependentMods(Project project, AsyncCache<
}
}

// TODO provide a project isolated way of doing this.
if (!extension.isProjectIsolationActive() && !GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS)) {
if (!projectView.disableProjectDependantMods()) {
// Add all the dependent projects
for (Project dependentProject : getDependentProjects(project).toList()) {
for (Project dependentProject : getDependentProjects(projectView).toList()) {
futures.add(fmjCache.get(dependentProject.getPath(), () -> FabricModJsonHelpers.getModsInProject(dependentProject)));
}
}

return sorted(AsyncCache.joinList(futures));
}

private static Stream<Project> getDependentProjects(Project project) {
final Stream<Project> runtimeProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
final Stream<Project> compileProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
private static Stream<Project> getDependentProjects(SpecContextProjectView projectView) {
final Stream<Project> runtimeProjects = projectView.getLoomProjectDependencies(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
final Stream<Project> compileProjects = projectView.getLoomProjectDependencies(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME);

return Stream.concat(runtimeProjects, compileProjects)
.distinct();
}

// Returns a list of mods that are on both to compile and runtime classpath
private static List<ModHolder> getCompileRuntimeMods(Project project, AsyncCache<List<FabricModJson>> fmjCache) {
var mods = new ArrayList<>(getCompileRuntimeModsFromRemapConfigs(project, fmjCache));
private static List<ModHolder> getCompileRuntimeMods(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache) {
var mods = new ArrayList<>(getCompileRuntimeModsFromRemapConfigs(projectView, fmjCache));

for (Project dependentProject : getCompileRuntimeProjectDependencies(project).toList()) {
for (Project dependentProject : getCompileRuntimeProjectDependencies(projectView).toList()) {
List<FabricModJson> projectMods = fmjCache.getBlocking(dependentProject.getPath(), () -> {
return FabricModJsonHelpers.getModsInProject(dependentProject);
});
Expand All @@ -127,49 +126,49 @@ private static List<ModHolder> getCompileRuntimeMods(Project project, AsyncCache
}

// Returns a list of jar mods that are found on the compile and runtime remapping configurations
private static List<ModHolder> getCompileRuntimeModsFromRemapConfigs(Project project, AsyncCache<List<FabricModJson>> fmjCache) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);

private static List<ModHolder> getCompileRuntimeModsFromRemapConfigs(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache) {
// A set of mod ids from all remap configurations that are considered for dependency transforms.
final Set<String> runtimeModIds = getModIds(
project,
projectView,
fmjCache,
extension.getRuntimeRemapConfigurations().stream()
projectView.extension().getRuntimeRemapConfigurations().stream()
.filter(settings -> settings.getApplyDependencyTransforms().get())
);

// A set of mod ids that are found on one or more remap configurations that target the common source set.
// Null when split source sets are not enabled, meaning all mods are common.
final Set<String> commonModIds = extension.areEnvironmentSourceSetsSplit() ? getModIds(
project,
final Set<String> commonRuntimeModIds = projectView.extension().areEnvironmentSourceSetsSplit() ? getModIds(
projectView,
fmjCache,
extension.getRuntimeRemapConfigurations().stream()
projectView.extension().getRuntimeRemapConfigurations().stream()
.filter(settings -> settings.getSourceSet().map(sourceSet -> !sourceSet.getName().equals(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME)).get())
.filter(settings -> settings.getApplyDependencyTransforms().get()))
: null;

return getMods(
project,
Stream<FabricModJson> compileMods = getMods(
projectView,
fmjCache,
extension.getCompileRemapConfigurations().stream()
.filter(settings -> settings.getApplyDependencyTransforms().get()))
projectView.extension().getCompileRemapConfigurations().stream()
.filter(settings -> settings.getApplyDependencyTransforms().get()));

return compileMods
// Only check based on the modid, as there may be differing versions used between the compile and runtime classpath.
// We assume that the version used at runtime will be binary compatible with the version used to compile against.
// It's not perfect but better than silently not supplying the mod, and this could happen with regular API that you compile against anyway.
.filter(fabricModJson -> runtimeModIds.contains(fabricModJson.getId()))
.sorted(Comparator.comparing(FabricModJson::getId))
.map(fabricModJson -> new ModHolder(fabricModJson, commonModIds == null || commonModIds.contains(fabricModJson.getId())))
.map(fabricModJson -> new ModHolder(fabricModJson, commonRuntimeModIds == null || commonRuntimeModIds.contains(fabricModJson.getId())))
.toList();
}

private static Stream<FabricModJson> getMods(Project project, AsyncCache<List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
return stream.flatMap(resolveArtifacts(project, true))
private static Stream<FabricModJson> getMods(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
return stream.flatMap(projectView.resolveArtifacts(true))
.map(modFromZip(fmjCache))
.filter(Objects::nonNull);
}

private static Set<String> getModIds(Project project, AsyncCache<List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
return getMods(project, fmjCache, stream)
private static Set<String> getModIds(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
return getMods(projectView, fmjCache, stream)
.map(FabricModJson::getId)
.collect(Collectors.toSet());
}
Expand All @@ -185,43 +184,19 @@ private static Set<String> getModIds(Project project, AsyncCache<List<FabricModJ
};
}

private static Function<RemapConfigurationSettings, Stream<Path>> resolveArtifacts(Project project, boolean runtime) {
final Usage usage = project.getObjects().named(Usage.class, runtime ? Usage.JAVA_RUNTIME : Usage.JAVA_API);

return settings -> {
final Configuration configuration = settings.getSourceConfiguration().get().copyRecursive();
configuration.setCanBeConsumed(false);
configuration.attributes(attributes -> attributes.attribute(Usage.USAGE_ATTRIBUTE, usage));
return configuration.resolve().stream().map(File::toPath);
};
}

// Returns a list of Loom Projects found in both the runtime and compile classpath
private static Stream<Project> getCompileRuntimeProjectDependencies(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);

// TODO provide a project isolated way of doing this.
if (extension.isProjectIsolationActive()
|| GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS)) {
private static Stream<Project> getCompileRuntimeProjectDependencies(SpecContextProjectView projectView) {
if (projectView.disableProjectDependantMods()) {
return Stream.empty();
}

final Stream<Project> runtimeProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
final List<Project> compileProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)).toList();
final Stream<Project> runtimeProjects = projectView.getLoomProjectDependencies(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
final List<Project> compileProjects = projectView.getLoomProjectDependencies(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME).toList();

return runtimeProjects
.filter(compileProjects::contains); // Use the intersection of the two configurations.
}

// Returns a list of Loom Projects found in the provided Configuration
private static Stream<Project> getLoomProjectDependencies(Project project, Configuration configuration) {
return configuration.getAllDependencies()
.withType(ProjectDependency.class)
.stream()
.map((d) -> project.project(d.getPath()))
.filter(GradleUtils::isLoomProject);
}

// Sort to ensure stable caching
private static List<FabricModJson> sorted(List<FabricModJson> mods) {
return mods.stream().sorted(Comparator.comparing(FabricModJson::getId)).toList();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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.configuration.processors;

import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;

import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.attributes.Usage;

import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.FabricModJsonHelpers;
import net.fabricmc.loom.util.gradle.GradleUtils;

// Used to abstract out the Gradle API usage to ease unit testing.
public interface SpecContextProjectView {
LoomGradleExtension extension();

// Returns a list of Loom Projects found in the specified Configuration
Stream<Project> getLoomProjectDependencies(String name);

Function<RemapConfigurationSettings, Stream<Path>> resolveArtifacts(boolean runtime);

List<FabricModJson> getMods();

boolean disableProjectDependantMods();

record Impl(Project project, LoomGradleExtension extension) implements SpecContextProjectView {
@Override
public Stream<Project> getLoomProjectDependencies(String name) {
final Configuration configuration = project.getConfigurations().getByName(name);
return configuration.getAllDependencies()
.withType(ProjectDependency.class)
.stream()
.map((d) -> project.project(d.getPath()))
.filter(GradleUtils::isLoomProject);
}

@Override
public Function<RemapConfigurationSettings, Stream<Path>> resolveArtifacts(boolean runtime) {
final Usage usage = project.getObjects().named(Usage.class, runtime ? Usage.JAVA_RUNTIME : Usage.JAVA_API);

return settings -> {
final Configuration configuration = settings.getSourceConfiguration().get().copyRecursive();
configuration.setCanBeConsumed(false);
configuration.attributes(attributes -> attributes.attribute(Usage.USAGE_ATTRIBUTE, usage));
return configuration.resolve().stream().map(File::toPath);
};
}

@Override
public List<FabricModJson> getMods() {
return FabricModJsonHelpers.getModsInProject(project);
}

@Override
public boolean disableProjectDependantMods() {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
// TODO provide a project isolated way of doing this.
return extension.isProjectIsolationActive()
|| GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS);
}
}
}
Loading
Loading