Merge pull request #737

New Jar Processor API
Shared Service improvements
FMJ 2.0 support
Move Minecraft jars to maven structure
Improve intelij source attachment
This commit is contained in:
modmuss50
2022-10-26 12:53:24 +01:00
committed by GitHub
92 changed files with 2917 additions and 1097 deletions

View File

@@ -39,8 +39,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.configuration.LoomDependencyManager;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
@@ -69,17 +68,13 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
LoomDependencyManager getDependencyManager();
void setJarProcessorManager(JarProcessorManager jarProcessorManager);
JarProcessorManager getJarProcessorManager();
MinecraftProvider getMinecraftProvider();
void setMinecraftProvider(MinecraftProvider minecraftProvider);
MappingsProviderImpl getMappingsProvider();
MappingConfiguration getMappingConfiguration();
void setMappingsProvider(MappingsProviderImpl mappingsProvider);
void setMappingConfiguration(MappingConfiguration mappingConfiguration);
NamedMinecraftProvider<?> getNamedMinecraftProvider();
@@ -91,8 +86,8 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
default List<Path> getMinecraftJars(MappingsNamespace mappingsNamespace) {
return switch (mappingsNamespace) {
case NAMED -> getNamedMinecraftProvider().getMinecraftJars();
case INTERMEDIARY -> getIntermediaryMinecraftProvider().getMinecraftJars();
case NAMED -> getNamedMinecraftProvider().getMinecraftJarPaths();
case INTERMEDIARY -> getIntermediaryMinecraftProvider().getMinecraftJarPaths();
case OFFICIAL -> getMinecraftProvider().getMinecraftJars();
};
}

View File

@@ -29,7 +29,6 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.ArtifactRepositoryContainer;
import org.gradle.api.artifacts.dsl.RepositoryHandler;
import org.gradle.api.artifacts.repositories.ArtifactRepository;
import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import org.gradle.api.initialization.Settings;
import org.gradle.api.invocation.Gradle;
@@ -62,10 +61,8 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> {
}
private void declareRepositories(RepositoryHandler repositories, LoomFiles files, ExtensionAware target) {
repositories.maven(repo -> {
repo.setName("UserLocalRemappedMods");
repo.setUrl(files.getRemappedModCache());
});
declareLocalRepositories(repositories, files);
repositories.maven(repo -> {
repo.setName("Fabric");
repo.setUrl(MirrorUtil.getFabricRepository(target));
@@ -93,17 +90,22 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> {
}
repositories.mavenCentral();
}
repositories.ivy(repo -> {
repo.setUrl(files.getUserCache());
repo.patternLayout(layout -> layout.artifact("[revision]/[artifact](-[classifier])(.[ext])"));
repo.metadataSources(IvyArtifactRepository.MetadataSources::artifact);
private void declareLocalRepositories(RepositoryHandler repositories, LoomFiles files) {
repositories.maven(repo -> {
repo.setName("LoomLocalRemappedMods");
repo.setUrl(files.getRemappedModCache());
});
repositories.ivy(repo -> {
repo.setUrl(files.getRootProjectPersistentCache());
repo.patternLayout(layout -> layout.artifact("[revision]/[artifact](-[classifier])(.[ext])"));
repo.metadataSources(IvyArtifactRepository.MetadataSources::artifact);
repositories.maven(repo -> {
repo.setName("LoomGlobalMinecraft");
repo.setUrl(files.getGlobalMinecraftRepo());
});
repositories.maven(repo -> {
repo.setName("LoomLocalMinecraft");
repo.setUrl(files.getLocalMinecraftRepo());
});
}

View File

@@ -38,15 +38,15 @@ public interface InterfaceInjectionExtensionAPI {
*/
Property<Boolean> getEnableDependencyInterfaceInjection();
Property<Boolean> getIsEnabled();
/**
* Contains a list of {@link SourceSet} that may contain a fabric.mod.json file with interfaces to inject.
* By default, this list contains only the main {@link SourceSet}.
*
* @return the list property containing the {@link SourceSet}
* @deprecated now uses the source sets defined in {@link LoomGradleExtensionAPI#getMods()}
*/
@Deprecated(forRemoval = true)
ListProperty<SourceSet> getInterfaceInjectionSourceSets();
default boolean isEnabled() {
return getEnableDependencyInterfaceInjection().get() || !getInterfaceInjectionSourceSets().get().isEmpty();
return getIsEnabled().get();
}
}

View File

@@ -42,6 +42,7 @@ import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.configuration.providers.mappings.NoOpIntermediateMappingsProvider;
@@ -62,12 +63,18 @@ public interface LoomGradleExtensionAPI {
void decompilers(Action<NamedDomainObjectContainer<DecompilerOptions>> action);
@Deprecated()
ListProperty<JarProcessor> getGameJarProcessors();
@Deprecated()
default void addJarProcessor(JarProcessor processor) {
getGameJarProcessors().add(processor);
}
ListProperty<MinecraftJarProcessor<?>> getMinecraftJarProcessors();
void addMinecraftJarProcessor(Class<? extends MinecraftJarProcessor<?>> clazz, Object... parameters);
ConfigurableFileCollection getLog4jConfigs();
Dependency officialMojangMappings();

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
* 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
@@ -22,13 +22,7 @@
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.mappings;
package net.fabricmc.loom.api.processor;
import java.io.File;
import java.nio.file.Path;
public interface MappingsProvider {
Path mappingsWorkingDir();
File intermediaryTinyFile();
public interface MappingProcessorContext {
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2022 FabricMC
* 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
@@ -22,34 +22,32 @@
* SOFTWARE.
*/
package net.fabricmc.loom.util;
package net.fabricmc.loom.api.processor;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import com.google.gson.JsonObject;
import org.gradle.api.Named;
import org.jetbrains.annotations.Nullable;
public final class ModUtils {
private ModUtils() {
}
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public static boolean isMod(File file) {
return isMod(file.toPath());
}
public interface MinecraftJarProcessor<S extends MinecraftJarProcessor.Spec> extends Named {
@Nullable
S buildSpec(SpecContext context);
public static boolean isMod(Path input) {
return ZipUtils.contains(input, "fabric.mod.json");
}
void processJar(Path jar, S spec, ProcessorContext context) throws IOException;
@Nullable
public static JsonObject getFabricModJson(Path path) {
try {
return ZipUtils.unpackGsonNullable(path, "fabric.mod.json", JsonObject.class);
} catch (IOException e) {
throw new UncheckedIOException("Failed to extract fabric.mod.json from " + path, e);
}
default MappingsProcessor<S> processMappings() {
return null;
}
interface Spec {
// Must make sure hashCode is correctly implemented.
}
interface MappingsProcessor<S> {
boolean transform(MemoryMappingTree mappings, S spec, MappingProcessorContext context);
}
}

View File

@@ -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.api.processor;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.tinyremapper.TinyRemapper;
public interface ProcessorContext {
MinecraftJarConfiguration getJarConfiguration();
boolean isMerged();
boolean includesClient();
boolean includesServer();
TinyRemapper createRemapper(MappingsNamespace from, MappingsNamespace to);
}

View File

@@ -0,0 +1,43 @@
/*
* 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.api.processor;
import java.util.List;
import java.util.stream.Stream;
import net.fabricmc.loom.util.fmj.FabricModJson;
public interface SpecContext {
List<FabricModJson> modDependencies();
List<FabricModJson> localMods();
// Returns mods that are both on the compile and runtime classpath
List<FabricModJson> modDependenciesCompileRuntime();
default List<FabricModJson> allMods() {
return Stream.concat(modDependencies().stream(), localMods().stream()).toList();
}
}

View File

@@ -45,7 +45,7 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.MixinMappingsService;
import net.fabricmc.loom.task.PrepareJarRemapTask;
import net.fabricmc.loom.util.Constants;
/**
@@ -93,8 +93,8 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
LoomGradleExtension loom = LoomGradleExtension.get(project);
String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get();
Map<String, String> args = new HashMap<>() {{
put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, loom.getMappingsProvider().tinyMappings.toFile().getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, MixinMappingsService.getMixinMappingFile(project, sourceSet).getCanonicalPath());
put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, loom.getMappingConfiguration().tinyMappings.toFile().getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, getMixinMappingsForSourceSet(project, sourceSet).getCanonicalPath());
put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, refmapName));
put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:" + loom.getMixin().getRefmapTargetNamespace().get());
put(Constants.MixinArguments.QUIET, "true");
@@ -111,6 +111,9 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
args.put("MSG_" + key, value);
});
// Ensure that all of the mixin mappings have been generated before we create the mixin mappings.
runBeforePrepare(project, task);
project.getLogger().debug("Outputting refmap to dir: " + getRefmapDestinationDir(task) + " for compile task: " + task);
args.forEach((k, v) -> passArgument(task, k, v));
} catch (IOException e) {
@@ -143,6 +146,12 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
}
}
private void runBeforePrepare(Project project, Task compileTask) {
project.getGradle().allprojects(otherProject -> {
otherProject.getTasks().withType(PrepareJarRemapTask.class, prepareRemapTask -> prepareRemapTask.mustRunAfter(compileTask));
});
}
private static void checkPattern(String input, Pattern pattern) {
final Matcher matcher = pattern.matcher(input);
@@ -150,4 +159,9 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
throw new IllegalArgumentException("Mixin argument (%s) does not match pattern (%s)".formatted(input, pattern.toString()));
}
}
public static File getMixinMappingsForSourceSet(Project project, SourceSet sourceSet) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
return new File(extension.getFiles().getProjectBuildCache(), "mixin-map-" + extension.getMappingConfiguration().mappingsIdentifier() + "." + sourceSet.getName() + ".tiny");
}
}

View File

@@ -53,8 +53,8 @@ import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.task.RemapTaskConfiguration;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
public final class IncludedJarFactory {
private final Project project;
@@ -143,7 +143,7 @@ public final class IncludedJarFactory {
}
private File getNestableJar(final File input, final Metadata metadata) {
if (ModUtils.isMod(input)) {
if (FabricModJsonFactory.isModJar(input)) {
// Input is a mod, nothing needs to be done.
return input;
}

View File

@@ -38,9 +38,9 @@ import com.google.gson.JsonObject;
import org.gradle.api.UncheckedIOException;
import org.slf4j.Logger;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
public class JarNester {
public static void nestJars(Collection<File> jars, File modJar, Logger logger) {
@@ -49,7 +49,7 @@ public class JarNester {
return;
}
Preconditions.checkArgument(ModUtils.isMod(modJar), "Cannot nest jars into none mod jar " + modJar.getName());
Preconditions.checkArgument(FabricModJsonFactory.isModJar(modJar), "Cannot nest jars into none mod jar " + modJar.getName());
try {
ZipUtils.add(modJar.toPath(), jars.stream().map(file -> {
@@ -69,7 +69,7 @@ public class JarNester {
for (File file : jars) {
String nestedJarPath = "META-INF/jars/" + file.getName();
Preconditions.checkArgument(ModUtils.isMod(file), "Cannot nest none mod jar: " + file.getName());
Preconditions.checkArgument(FabricModJsonFactory.isModJar(file), "Cannot nest none mod jar: " + file.getName());
for (JsonElement nestedJar : nestedJars) {
JsonObject jsonObject = nestedJar.getAsJsonObject();

View File

@@ -30,6 +30,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
import org.gradle.api.NamedDomainObjectProvider;
import org.gradle.api.Project;
@@ -42,6 +43,7 @@ import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.javadoc.Javadoc;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI;
import net.fabricmc.loom.build.mixin.GroovyApInvoker;
import net.fabricmc.loom.build.mixin.JavaApInvoker;
import net.fabricmc.loom.build.mixin.KaptApInvoker;
@@ -49,9 +51,9 @@ import net.fabricmc.loom.build.mixin.ScalaApInvoker;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
import net.fabricmc.loom.configuration.processors.ModJavadocProcessor;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
@@ -63,6 +65,8 @@ import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ExceptionUtil;
import net.fabricmc.loom.util.gradle.GradleUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
import net.fabricmc.loom.util.service.ScopedSharedServiceManager;
import net.fabricmc.loom.util.service.SharedServiceManager;
public final class CompileConfiguration {
private CompileConfiguration() {
@@ -126,7 +130,9 @@ public final class CompileConfiguration {
javadoc.setClasspath(main.getOutput().plus(main.getCompileClasspath()));
});
GradleUtils.afterSuccessfulEvaluation(project, () -> {
afterEvaluationWithService(project, (serviceManager) -> {
final ConfigContext configContext = new ConfigContextImpl(project, serviceManager, extension);
MinecraftSourceSets.get(project).afterEvaluate(project);
final boolean previousRefreshDeps = extension.refreshDeps();
@@ -137,14 +143,14 @@ public final class CompileConfiguration {
}
try {
setupMinecraft(project);
setupMinecraft(configContext);
} catch (Exception e) {
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to setup Minecraft", e);
}
LoomDependencyManager dependencyManager = new LoomDependencyManager();
extension.setDependencyManager(dependencyManager);
dependencyManager.handleDependencies(project);
dependencyManager.handleDependencies(project, serviceManager);
releaseLock(project);
extension.setRefreshDeps(previousRefreshDeps);
@@ -155,7 +161,7 @@ public final class CompileConfiguration {
setupMixinAp(project, mixin);
}
configureDecompileTasks(project);
configureDecompileTasks(configContext);
});
finalizedBy(project, "idea", "genIdeaWorkspace");
@@ -178,29 +184,31 @@ public final class CompileConfiguration {
}
// This is not thread safe across projects synchronize it here just to be sure, might be possible to move this further down, but for now this will do.
private static synchronized void setupMinecraft(Project project) throws Exception {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
private static synchronized void setupMinecraft(ConfigContext configContext) throws Exception {
final Project project = configContext.project();
final LoomGradleExtension extension = configContext.extension();
final MinecraftJarConfiguration jarConfiguration = extension.getMinecraftJarConfiguration().get();
// Provide the vanilla mc jars -- TODO share across projects.
final MinecraftProvider minecraftProvider = jarConfiguration.getMinecraftProviderFunction().apply(project);
final MinecraftProvider minecraftProvider = jarConfiguration.getMinecraftProviderFunction().apply(configContext);
extension.setMinecraftProvider(minecraftProvider);
minecraftProvider.provide();
final DependencyInfo mappingsDep = DependencyInfo.create(project, Constants.Configurations.MAPPINGS);
final MappingsProviderImpl mappingsProvider = MappingsProviderImpl.getInstance(project, mappingsDep, minecraftProvider);
extension.setMappingsProvider(mappingsProvider);
mappingsProvider.applyToProject(project, mappingsDep);
final MappingConfiguration mappingConfiguration = MappingConfiguration.create(project, configContext.serviceManager(), mappingsDep, minecraftProvider);
extension.setMappingConfiguration(mappingConfiguration);
mappingConfiguration.applyToProject(project, mappingsDep);
// Provide the remapped mc jars
final IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider = jarConfiguration.getIntermediaryMinecraftProviderBiFunction().apply(project, minecraftProvider);
NamedMinecraftProvider<?> namedMinecraftProvider = jarConfiguration.getNamedMinecraftProviderBiFunction().apply(project, minecraftProvider);
final IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider = jarConfiguration.getIntermediaryMinecraftProviderBiFunction().apply(configContext, minecraftProvider);
NamedMinecraftProvider<?> namedMinecraftProvider = jarConfiguration.getNamedMinecraftProviderBiFunction().apply(configContext, minecraftProvider);
final JarProcessorManager jarProcessorManager = createJarProcessorManager(project);
registerGameProcessors(configContext);
MinecraftJarProcessorManager minecraftJarProcessorManager = MinecraftJarProcessorManager.create(project);
if (jarProcessorManager.active()) {
if (minecraftJarProcessorManager != null) {
// Wrap the named MC provider for one that will provide the processed jars
namedMinecraftProvider = jarConfiguration.getProcessedNamedMinecraftProviderBiFunction().apply(namedMinecraftProvider, jarProcessorManager);
namedMinecraftProvider = jarConfiguration.getProcessedNamedMinecraftProviderBiFunction().apply(namedMinecraftProvider, minecraftJarProcessorManager);
}
extension.setIntermediaryMinecraftProvider(intermediaryMinecraftProvider);
@@ -210,43 +218,30 @@ public final class CompileConfiguration {
namedMinecraftProvider.provide(true);
}
private static JarProcessorManager createJarProcessorManager(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
private static void registerGameProcessors(ConfigContext configContext) {
final LoomGradleExtension extension = configContext.extension();
if (extension.getAccessWidenerPath().isPresent()) {
extension.getGameJarProcessors().add(new AccessWidenerJarProcessor(project));
extension.getGameJarProcessors().add(new AccessWidenerJarProcessor(configContext));
}
if (extension.getEnableTransitiveAccessWideners().get()) {
TransitiveAccessWidenerJarProcessor transitiveAccessWidenerJarProcessor = new TransitiveAccessWidenerJarProcessor(project);
TransitiveAccessWidenerJarProcessor transitiveAccessWidenerJarProcessor = new TransitiveAccessWidenerJarProcessor(configContext);
if (!transitiveAccessWidenerJarProcessor.isEmpty()) {
extension.getGameJarProcessors().add(transitiveAccessWidenerJarProcessor);
}
}
if (extension.getInterfaceInjection().isEnabled()) {
InterfaceInjectionProcessor jarProcessor = new InterfaceInjectionProcessor(project);
if (!jarProcessor.isEmpty()) {
extension.getGameJarProcessors().add(jarProcessor);
}
}
if (extension.getEnableModProvidedJavadoc().get()) {
// This doesn't do any processing on the compiled jar, but it does have an effect on the generated sources.
final ModJavadocProcessor javadocProcessor = ModJavadocProcessor.create(project);
if (javadocProcessor != null) {
extension.getGameJarProcessors().add(javadocProcessor);
}
extension.addMinecraftJarProcessor(ModJavadocProcessor.class, "fabric-loom:mod-javadoc");
}
JarProcessorManager processorManager = new JarProcessorManager(extension.getGameJarProcessors().get());
extension.setJarProcessorManager(processorManager);
processorManager.setupProcessors();
final InterfaceInjectionExtensionAPI interfaceInjection = extension.getInterfaceInjection();
return processorManager;
if (interfaceInjection.isEnabled()) {
extension.addMinecraftJarProcessor(InterfaceInjectionProcessor.class, "fabric-loom:interface-inject", interfaceInjection.getEnableDependencyInterfaceInjection().get());
}
}
private static void setupMixinAp(Project project, MixinExtension mixin) {
@@ -277,11 +272,11 @@ public final class CompileConfiguration {
}
}
private static void configureDecompileTasks(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
private static void configureDecompileTasks(ConfigContext configContext) {
final LoomGradleExtension extension = configContext.extension();
extension.getMinecraftJarConfiguration().get().getDecompileConfigurationBiFunction()
.apply(project, extension.getNamedMinecraftProvider()).afterEvaluation();
.apply(configContext, extension.getNamedMinecraftProvider()).afterEvaluation();
}
private static Path getLockFile(Project project) {
@@ -343,4 +338,12 @@ public final class CompileConfiguration {
private static void finalizedBy(Project project, String a, String b) {
project.getTasks().named(a).configure(task -> task.finalizedBy(project.getTasks().named(b)));
}
private static void afterEvaluationWithService(Project project, Consumer<SharedServiceManager> consumer) {
GradleUtils.afterSuccessfulEvaluation(project, () -> {
try (var serviceManager = new ScopedSharedServiceManager()) {
consumer.accept(serviceManager);
}
});
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.configuration;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.service.SharedServiceManager;
public interface ConfigContext {
Project project();
SharedServiceManager serviceManager();
LoomGradleExtension extension();
}

View File

@@ -0,0 +1,33 @@
/*
* 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.configuration;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.service.SharedServiceManager;
public record ConfigContextImpl(Project project, SharedServiceManager serviceManager, LoomGradleExtension extension) implements ConfigContext {
}

View File

@@ -44,9 +44,10 @@ import net.fabricmc.loom.configuration.mods.ModConfigurationRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.service.SharedServiceManager;
public class LoomDependencyManager {
public void handleDependencies(Project project) {
public void handleDependencies(Project project, SharedServiceManager serviceManager) {
List<Runnable> afterTasks = new ArrayList<>();
project.getLogger().info(":setting up loom dependencies");
@@ -75,10 +76,10 @@ public class LoomDependencyManager {
}
}
SourceRemapper sourceRemapper = new SourceRemapper(project, true);
String mappingsIdentifier = extension.getMappingsProvider().mappingsIdentifier();
SourceRemapper sourceRemapper = new SourceRemapper(project, serviceManager, true);
String mappingsIdentifier = extension.getMappingConfiguration().mappingsIdentifier();
ModConfigurationRemapper.supplyModConfigurations(project, mappingsIdentifier, extension, sourceRemapper);
ModConfigurationRemapper.supplyModConfigurations(project, serviceManager, mappingsIdentifier, extension, sourceRemapper);
sourceRemapper.remapAll();

View File

@@ -37,6 +37,7 @@ import org.gradle.api.Project;
import net.fabricmc.accesswidener.AccessWidener;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.ZipUtils;
@@ -44,15 +45,13 @@ import net.fabricmc.loom.util.ZipUtils;
public class AccessWidenerJarProcessor implements JarProcessor {
// Filename used to store hash of input access widener in processed jar file
private static final String HASH_FILENAME = "aw.sha256";
// The mod's own access widener file
private byte[] modAccessWidener;
private final AccessWidener accessWidener = new AccessWidener();
private final Project project;
// This is a SHA256 hash across the mod's and all transitive AWs
private byte[] inputHash;
public AccessWidenerJarProcessor(Project project) {
this.project = project;
public AccessWidenerJarProcessor(ConfigContext configContext) {
this.project = configContext.project();
}
@Override
@@ -66,6 +65,9 @@ public class AccessWidenerJarProcessor implements JarProcessor {
Path awPath = extension.getAccessWidenerPath().get().getAsFile().toPath();
// Read our own mod's access widener, used later for producing a version remapped to intermediary
// The mod's own access widener file
byte[] modAccessWidener;
try {
modAccessWidener = Files.readAllBytes(awPath);
} catch (NoSuchFileException e) {

View File

@@ -48,6 +48,7 @@ import net.fabricmc.accesswidener.TransitiveOnlyFilter;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.tinyremapper.TinyRemapper;
@@ -56,14 +57,16 @@ import net.fabricmc.tinyremapper.TinyRemapper;
* Applies transitive access wideners that are inherited from mod and api dependencies.
*/
public class TransitiveAccessWidenerJarProcessor implements JarProcessor {
private final ConfigContext configContext;
private final Project project;
private final LoomGradleExtension extension;
private final List<AccessWidenerFile> transitiveAccessWideners;
public TransitiveAccessWidenerJarProcessor(Project project) {
this.project = project;
this.extension = LoomGradleExtension.get(project);
public TransitiveAccessWidenerJarProcessor(ConfigContext configContext) {
this.configContext = configContext;
this.project = configContext.project();
this.extension = configContext.extension();
transitiveAccessWideners = getTransitiveAccessWideners();
@@ -166,7 +169,7 @@ public class TransitiveAccessWidenerJarProcessor implements JarProcessor {
private TinyRemapper createTinyRemapper() {
try {
TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(project, "intermediary", "named");
TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(project, configContext.serviceManager(), "intermediary", "named");
tinyRemapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project));

View File

@@ -30,7 +30,8 @@ import org.gradle.api.Project;
import org.gradle.api.tasks.TaskProvider;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.task.UnpickJarTask;
@@ -38,20 +39,20 @@ public abstract class DecompileConfiguration<T extends MappedMinecraftProvider>
protected final Project project;
protected final T minecraftProvider;
protected final LoomGradleExtension extension;
protected final MappingsProviderImpl mappingsProvider;
protected final MappingConfiguration mappingConfiguration;
public DecompileConfiguration(Project project, T minecraftProvider) {
this.project = project;
public DecompileConfiguration(ConfigContext configContext, T minecraftProvider) {
this.project = configContext.project();
this.minecraftProvider = minecraftProvider;
this.extension = LoomGradleExtension.get(project);
this.mappingsProvider = extension.getMappingsProvider();
this.extension = configContext.extension();
this.mappingConfiguration = extension.getMappingConfiguration();
}
public abstract void afterEvaluation();
protected final TaskProvider<UnpickJarTask> createUnpickJarTask(String name, File inputJar, File outputJar) {
return project.getTasks().register(name, UnpickJarTask.class, unpickJarTask -> {
unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
unpickJarTask.getUnpickDefinitions().set(mappingConfiguration.getUnpickDefinitionsFile());
unpickJarTask.getInputJar().set(inputJar);
unpickJarTask.getOutputJar().set(outputJar);
});

View File

@@ -28,29 +28,28 @@ import java.io.File;
import java.nio.file.Path;
import java.util.List;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.util.Constants;
public class SingleJarDecompileConfiguration extends DecompileConfiguration<MappedMinecraftProvider> {
public SingleJarDecompileConfiguration(Project project, MappedMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
public SingleJarDecompileConfiguration(ConfigContext configContext, MappedMinecraftProvider minecraftProvider) {
super(configContext, minecraftProvider);
}
@Override
public final void afterEvaluation() {
List<Path> minecraftJars = minecraftProvider.getMinecraftJars();
List<Path> minecraftJars = minecraftProvider.getMinecraftJarPaths();
assert minecraftJars.size() == 1;
final File namedJar = minecraftJars.get(0).toFile();
File mappedJar = namedJar;
if (mappingsProvider.hasUnpickDefinitions()) {
File outputJar = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar");
if (mappingConfiguration.hasUnpickDefinitions()) {
File outputJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar");
createUnpickJarTask("unpickJar", namedJar, outputJar);
mappedJar = outputJar;
@@ -70,7 +69,7 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration<Mapp
task.setDescription("Decompile minecraft using %s.".formatted(decompilerName));
task.setGroup(Constants.TaskGroup.FABRIC);
if (mappingsProvider.hasUnpickDefinitions()) {
if (mappingConfiguration.hasUnpickDefinitions()) {
task.dependsOn(project.getTasks().named("unpickJar"));
}
});

View File

@@ -27,19 +27,19 @@ package net.fabricmc.loom.configuration.decompile;
import java.io.File;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskProvider;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.task.UnpickJarTask;
import net.fabricmc.loom.util.Constants;
public final class SplitDecompileConfiguration extends DecompileConfiguration<MappedMinecraftProvider.Split> {
public SplitDecompileConfiguration(Project project, MappedMinecraftProvider.Split minecraftProvider) {
super(project, minecraftProvider);
public SplitDecompileConfiguration(ConfigContext configContext, MappedMinecraftProvider.Split minecraftProvider) {
super(configContext, minecraftProvider);
}
@Override
@@ -50,9 +50,9 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration<Ma
TaskProvider<UnpickJarTask> unpickCommonJar = null;
TaskProvider<UnpickJarTask> unpickClientOnlyJar = null;
if (mappingsProvider.hasUnpickDefinitions()) {
commonJarToDecompile = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar");
clientOnlyJarToDecompile = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar");
if (mappingConfiguration.hasUnpickDefinitions()) {
commonJarToDecompile = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar");
clientOnlyJarToDecompile = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar");
unpickCommonJar = createUnpickJarTask("unpickCommonJar", minecraftProvider.getCommonJar().toFile(), commonJarToDecompile);
unpickClientOnlyJar = createUnpickJarTask("unpickClientOnlyJar", minecraftProvider.getClientOnlyJar().toFile(), clientOnlyJarToDecompile);

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
* Copyright (c) 2021-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
@@ -24,20 +24,32 @@
package net.fabricmc.loom.configuration.ide.idea;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.gradle.StartParameter;
import org.gradle.TaskExecutionRequest;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.internal.DefaultTaskExecutionRequest;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.task.LoomTasks;
public class IdeaConfiguration {
private static final String INIT_SCRIPT_NAME = "ijmiscinit";
private static final Pattern NOTATION_PATTERN = Pattern.compile("'net\\.minecraft:(?<name>.*):(.*):sources'");
public static void setup(Project project) {
TaskProvider<IdeaSyncTask> ideaSyncTask = project.getTasks().register("ideaSyncTask", IdeaSyncTask.class, task -> {
if (LoomGradleExtension.get(project).getRunConfigs().stream().anyMatch(RunConfigSettings::isIdeConfigGenerated)) {
@@ -47,6 +59,12 @@ public class IdeaConfiguration {
}
});
project.getTasks().configureEach(task -> {
if (task.getName().equals("DownloadSources")) {
hookDownloadSources(project, task);
}
});
if (!IdeaUtils.isIdeaSync()) {
return;
}
@@ -57,4 +75,63 @@ public class IdeaConfiguration {
taskRequests.add(new DefaultTaskExecutionRequest(List.of("ideaSyncTask")));
startParameter.setTaskRequests(taskRequests);
}
/*
"Parse" the init script enough to figure out what jar we are talking about.
Intelij code: https://github.com/JetBrains/intellij-community/blob/a09b1b84ab64a699794c860bc96774766dd38958/plugins/gradle/java/src/util/GradleAttachSourcesProvider.java
*/
private static void hookDownloadSources(Project project, Task task) {
List<File> initScripts = project.getGradle().getStartParameter().getInitScripts();
for (File initScript : initScripts) {
if (!initScript.getName().contains(INIT_SCRIPT_NAME)) {
continue;
}
try {
final String script = Files.readString(initScript.toPath(), StandardCharsets.UTF_8);
final String notation = parseInitScript(project, script);
if (notation != null) {
task.dependsOn(getGenSourcesTaskName(LoomGradleExtension.get(project), notation));
}
} catch (IOException e) {
// Ignore
}
}
}
@Nullable
private static String parseInitScript(Project project, String script) {
if (!script.contains("Attempt to download sources from")
|| !script.contains("downloadSources_")
|| !script.contains("'%s'".formatted(project.getPath()))) {
// Failed some basic sanity checks.
return null;
}
// A little gross but should do the job nicely.
final Matcher matcher = NOTATION_PATTERN.matcher(script);
if (matcher.find()) {
return matcher.group("name");
}
return null;
}
private static String getGenSourcesTaskName(LoomGradleExtension extension, String notation) {
final MinecraftJarConfiguration configuration = extension.getMinecraftJarConfiguration().get();
if (configuration == MinecraftJarConfiguration.SPLIT) {
if (notation.contains("minecraft-clientOnly")) {
return "genClientOnlySources";
}
return "genCommonSources";
}
return "genSources";
}
}

View File

@@ -24,230 +24,157 @@
package net.fabricmc.loom.configuration.ifaceinject;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import javax.inject.Inject;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.Remapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
import net.fabricmc.loom.api.processor.ProcessorContext;
import net.fabricmc.loom.api.processor.SpecContext;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.tinyremapper.TinyRemapper;
public class InterfaceInjectionProcessor implements JarProcessor, GenerateSourcesTask.MappingsProcessor {
// Filename used to store hash of injected interfaces in processed jar file
private static final String HASH_FILENAME = "injected_interfaces.sha256";
public abstract class InterfaceInjectionProcessor implements MinecraftJarProcessor<InterfaceInjectionProcessor.Spec> {
private static final Logger LOGGER = LoggerFactory.getLogger(InterfaceInjectionProcessor.class);
private final Map<String, List<InjectedInterface>> injectedInterfaces;
private final Project project;
private final LoomGradleExtension extension;
private final InterfaceInjectionExtensionAPI interfaceInjectionExtension;
private final byte[] inputHash;
private Map<String, List<InjectedInterface>> remappedInjectedInterfaces;
private final String name;
private final boolean fromDependencies;
public InterfaceInjectionProcessor(Project project) {
this.project = project;
this.extension = LoomGradleExtension.get(project);
this.interfaceInjectionExtension = this.extension.getInterfaceInjection();
this.injectedInterfaces = getInjectedInterfaces().stream()
.collect(Collectors.groupingBy(InjectedInterface::className));
this.inputHash = hashInjectedInterfaces();
}
public boolean isEmpty() {
return injectedInterfaces.isEmpty();
@Inject
public InterfaceInjectionProcessor(String name, boolean fromDependencies) {
this.name = name;
this.fromDependencies = fromDependencies;
}
@Override
public String getId() {
Preconditions.checkArgument(!isEmpty());
return "loom:interface_injection:" + Checksum.toHex(inputHash);
public String getName() {
return name;
}
@Override
public void setup() {
public @Nullable InterfaceInjectionProcessor.Spec buildSpec(SpecContext context) {
List<InjectedInterface> injectedInterfaces = new ArrayList<>();
injectedInterfaces.addAll(InjectedInterface.fromMods(context.localMods()));
// Find the injected interfaces from mods that are both on the compile and runtime classpath.
// Runtime is also required to ensure that the interface and it's impl is present when running the mc jar.
if (fromDependencies) {
injectedInterfaces.addAll(InjectedInterface.fromMods(context.modDependenciesCompileRuntime()));
}
if (injectedInterfaces.isEmpty()) {
return null;
}
return new Spec(injectedInterfaces);
}
public record Spec(List<InjectedInterface> injectedInterfaces) implements MinecraftJarProcessor.Spec {
}
@Override
public void process(File jarFile) {
// Lazily remap from intermediary->named
if (remappedInjectedInterfaces == null) {
TinyRemapper tinyRemapper = createTinyRemapper();
Remapper remapper = tinyRemapper.getEnvironment().getRemapper();
public void processJar(Path jar, Spec spec, ProcessorContext context) throws IOException {
// Remap from intermediary->named
final TinyRemapper tinyRemapper = context.createRemapper(MappingsNamespace.INTERMEDIARY, MappingsNamespace.NAMED);
final Remapper remapper = tinyRemapper.getEnvironment().getRemapper();
final List<InjectedInterface> remappedInjectedInterfaces;
try {
remappedInjectedInterfaces = new HashMap<>(injectedInterfaces.size());
for (Map.Entry<String, List<InjectedInterface>> entry : injectedInterfaces.entrySet()) {
String namedClassName = remapper.map(entry.getKey());
remappedInjectedInterfaces.put(
namedClassName,
entry.getValue().stream()
.map(injectedInterface ->
new InjectedInterface(
injectedInterface.modId(),
namedClassName,
remapper.map(injectedInterface.ifaceName())
))
.toList()
);
}
} finally {
tinyRemapper.finish();
}
try {
remappedInjectedInterfaces = spec.injectedInterfaces().stream()
.map(injectedInterface -> remap(injectedInterface, remapper))
.toList();
} finally {
tinyRemapper.finish();
}
try {
ZipUtils.transform(jarFile.toPath(), getTransformers());
ZipUtils.transform(jar, getTransformers(remappedInjectedInterfaces));
} catch (IOException e) {
throw new RuntimeException("Failed to apply interface injections to " + jarFile, e);
throw new RuntimeException("Failed to apply interface injections to " + jar, e);
}
}
private List<Pair<String, ZipUtils.UnsafeUnaryOperator<byte[]>>> getTransformers() {
return remappedInjectedInterfaces.keySet().stream()
.map(string -> new Pair<>(string.replaceAll("\\.", "/") + ".class", getTransformer(string)))
.collect(Collectors.toList());
private InjectedInterface remap(InjectedInterface in, Remapper remapper) {
return new InjectedInterface(
in.modId(),
remapper.map(in.className()),
remapper.map(in.ifaceName())
);
}
private ZipUtils.UnsafeUnaryOperator<byte[]> getTransformer(String className) {
private List<Pair<String, ZipUtils.UnsafeUnaryOperator<byte[]>>> getTransformers(List<InjectedInterface> injectedInterfaces) {
return injectedInterfaces.stream()
.collect(Collectors.groupingBy(InjectedInterface::className))
.entrySet()
.stream()
.map(entry -> {
final String zipEntry = entry.getKey().replaceAll("\\.", "/") + ".class";
return new Pair<>(zipEntry, getTransformer(entry.getValue()));
}).toList();
}
private ZipUtils.UnsafeUnaryOperator<byte[]> getTransformer(List<InjectedInterface> injectedInterfaces) {
return input -> {
ClassReader reader = new ClassReader(input);
ClassWriter writer = new ClassWriter(0);
List<InjectedInterface> ifaces = remappedInjectedInterfaces.get(className);
ClassVisitor classVisitor = new InjectingClassVisitor(Constants.ASM_VERSION, writer, ifaces);
// Log which mods add which interface to the class
project.getLogger().info("Injecting interfaces into " + className + ": "
+ ifaces.stream().map(i -> i.ifaceName() + " [" + i.modId() + "]"
).collect(Collectors.joining(", ")));
final ClassReader reader = new ClassReader(input);
final ClassWriter writer = new ClassWriter(0);
final ClassVisitor classVisitor = new InjectingClassVisitor(Constants.ASM_VERSION, writer, injectedInterfaces);
reader.accept(classVisitor, 0);
return writer.toByteArray();
};
}
private List<InjectedInterface> getInjectedInterfaces() {
List<InjectedInterface> result = new ArrayList<>();
if (interfaceInjectionExtension.getEnableDependencyInterfaceInjection().get()) {
result.addAll(getDependencyInjectedInterfaces());
}
for (SourceSet sourceSet : interfaceInjectionExtension.getInterfaceInjectionSourceSets().get()) {
result.addAll(getSourceInjectedInterface(sourceSet));
}
return result;
}
// Find the injected interfaces from mods that are both on the compile and runtime classpath.
// Runtime is also required to ensure that the interface and it's impl is present when running the mc jar.
private List<InjectedInterface> getDependencyInjectedInterfaces() {
final Function<RemapConfigurationSettings, Stream<Path>> resolve = settings ->
settings.getSourceConfiguration().get().resolve().stream()
.map(File::toPath);
final List<Path> runtimeEntries = extension.getRuntimeRemapConfigurations().stream()
.flatMap(resolve)
.toList();
return extension.getCompileRemapConfigurations().stream()
.flatMap(resolve)
.filter(runtimeEntries::contains) // Use the intersection of the two configurations.
.flatMap(path -> InjectedInterface.fromModJar(path).stream())
.toList();
}
private List<InjectedInterface> getSourceInjectedInterface(SourceSet sourceSet) {
final File fabricModJson;
try {
fabricModJson = sourceSet.getResources()
.matching(patternFilterable -> patternFilterable.include("fabric.mod.json"))
.getSingleFile();
} catch (IllegalStateException e) {
// File not found
return Collections.emptyList();
}
final String jsonString;
try {
jsonString = Files.readString(fabricModJson.toPath(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read fabric.mod.json", e);
}
final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(jsonString, JsonObject.class);
return InjectedInterface.fromJson(jsonObject);
}
@Override
public boolean transform(MemoryMappingTree mappings) {
if (injectedInterfaces.isEmpty()) {
return false;
}
if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappings.getSrcNamespace())) {
throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappings.getSrcNamespace());
}
for (Map.Entry<String, List<InjectedInterface>> entry : injectedInterfaces.entrySet()) {
final String className = entry.getKey();
final List<InjectedInterface> injectedInterfaces = entry.getValue();
MappingTree.ClassMapping classMapping = mappings.getClass(className);
if (classMapping == null) {
final String modIds = injectedInterfaces.stream().map(InjectedInterface::modId).distinct().collect(Collectors.joining(","));
project.getLogger().warn("Failed to find class ({}) to add injected interfaces from mod(s) ({})", className, modIds);
continue;
public MappingsProcessor<Spec> processMappings() {
return (mappings, spec, context) -> {
if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappings.getSrcNamespace())) {
throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappings.getSrcNamespace());
}
classMapping.setComment(appendComment(classMapping.getComment(), injectedInterfaces));
}
Map<String, List<InjectedInterface>> map = spec.injectedInterfaces().stream()
.collect(Collectors.groupingBy(InjectedInterface::className));
return true;
for (Map.Entry<String, List<InjectedInterface>> entry : map.entrySet()) {
final String className = entry.getKey();
final List<InjectedInterface> injectedInterfaces = entry.getValue();
MappingTree.ClassMapping classMapping = mappings.getClass(className);
if (classMapping == null) {
final String modIds = injectedInterfaces.stream().map(InjectedInterface::modId).distinct().collect(Collectors.joining(","));
LOGGER.warn("Failed to find class ({}) to add injected interfaces from mod(s) ({})", className, modIds);
continue;
}
classMapping.setComment(appendComment(classMapping.getComment(), injectedInterfaces));
}
return true;
};
}
private static String appendComment(String comment, List<InjectedInterface> injectedInterfaces) {
@@ -267,33 +194,15 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
}
private record InjectedInterface(String modId, String className, String ifaceName) {
/**
* Reads the injected interfaces contained in a mod jar, or returns empty if there is none.
*/
public static List<InjectedInterface> fromModJar(Path modJarPath) {
final JsonObject jsonObject = ModUtils.getFabricModJson(modJarPath);
public static List<InjectedInterface> fromMod(FabricModJson fabricModJson) {
final String modId = fabricModJson.getId();
final JsonElement jsonElement = fabricModJson.getCustom(Constants.CustomModJsonKeys.INJECTED_INTERFACE);
if (jsonObject == null) {
if (jsonElement == null) {
return Collections.emptyList();
}
return fromJson(jsonObject);
}
public static List<InjectedInterface> fromJson(JsonObject jsonObject) {
final String modId = jsonObject.get("id").getAsString();
if (!jsonObject.has("custom")) {
return Collections.emptyList();
}
final JsonObject custom = jsonObject.getAsJsonObject("custom");
if (!custom.has(Constants.CustomModJsonKeys.INJECTED_INTERFACE)) {
return Collections.emptyList();
}
final JsonObject addedIfaces = custom.getAsJsonObject(Constants.CustomModJsonKeys.INJECTED_INTERFACE);
final JsonObject addedIfaces = jsonElement.getAsJsonObject();
final List<InjectedInterface> result = new ArrayList<>();
@@ -307,6 +216,13 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
return result;
}
public static List<InjectedInterface> fromMods(List<FabricModJson> fabricModJsons) {
return fabricModJsons.stream()
.map(InjectedInterface::fromMod)
.flatMap(List::stream)
.toList();
}
}
private static class InjectingClassVisitor extends ClassVisitor {
@@ -344,36 +260,4 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
super.visit(version, access, name, signature, superName, modifiedInterfaces.toArray(new String[0]));
}
}
private TinyRemapper createTinyRemapper() {
try {
TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(project, "intermediary", "named");
tinyRemapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project));
for (Path minecraftJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
tinyRemapper.readClassPath(minecraftJar);
}
return tinyRemapper;
} catch (IOException e) {
throw new RuntimeException("Failed to create tiny remapper for intermediary->named", e);
}
}
private byte[] hashInjectedInterfaces() {
// Hash the interfaces we're about to inject to not have to repeat this everytime
Hasher hasher = Hashing.sha256().newHasher();
for (Map.Entry<String, List<InjectedInterface>> entry : injectedInterfaces.entrySet()) {
hasher.putString("class:", StandardCharsets.UTF_8);
hasher.putString(entry.getKey(), StandardCharsets.UTF_8);
for (InjectedInterface ifaceName : entry.getValue()) {
hasher.putString("iface:", StandardCharsets.UTF_8);
hasher.putString(ifaceName.ifaceName(), StandardCharsets.UTF_8);
}
}
return hasher.hash().asBytes();
}
}

View File

@@ -25,18 +25,18 @@
package net.fabricmc.loom.configuration.mods;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.List;
import com.google.gson.JsonObject;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerRemapper;
import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
import net.fabricmc.loom.util.fmj.ModEnvironment;
public class AccessWidenerUtils {
/**
@@ -58,16 +58,20 @@ public class AccessWidenerUtils {
}
public static AccessWidenerData readAccessWidenerData(Path inputJar) throws IOException {
byte[] modJsonBytes = ZipUtils.unpack(inputJar, "fabric.mod.json");
JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
final FabricModJson fabricModJson = FabricModJsonFactory.createFromZip(inputJar);
final List<String> classTweakers = fabricModJson.getClassTweakers(ModEnvironment.UNIVERSAL);
if (!jsonObject.has("accessWidener")) {
if (classTweakers.isEmpty()) {
return null;
}
String accessWidenerPath = jsonObject.get("accessWidener").getAsString();
byte[] accessWidener = ZipUtils.unpack(inputJar, accessWidenerPath);
AccessWidenerReader.Header header = AccessWidenerReader.readHeader(accessWidener);
if (classTweakers.size() != 1) {
throw new UnsupportedOperationException("TODO: support multiple class tweakers");
}
final String accessWidenerPath = classTweakers.get(0);
final byte[] accessWidener = fabricModJson.getSource().read(accessWidenerPath);
final AccessWidenerReader.Header header = AccessWidenerReader.readHeader(accessWidener);
return new AccessWidenerData(accessWidenerPath, header, accessWidener);
}

View File

@@ -55,9 +55,10 @@ import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
import net.fabricmc.loom.configuration.mods.dependency.ModDependencyFactory;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.OperatingSystem;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
import net.fabricmc.loom.util.service.SharedServiceManager;
@SuppressWarnings("UnstableApiUsage")
public class ModConfigurationRemapper {
@@ -65,7 +66,7 @@ public class ModConfigurationRemapper {
// This can happen when the dependency is a FileCollectionDependency or from a flatDir repository.
public static final String MISSING_GROUP = "unspecified";
public static void supplyModConfigurations(Project project, String mappingsSuffix, LoomGradleExtension extension, SourceRemapper sourceRemapper) {
public static void supplyModConfigurations(Project project, SharedServiceManager serviceManager, String mappingsSuffix, LoomGradleExtension extension, SourceRemapper sourceRemapper) {
final DependencyHandler dependencies = project.getDependencies();
for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) {
@@ -88,7 +89,7 @@ public class ModConfigurationRemapper {
final List<ModDependency> modDependencies = new ArrayList<>();
for (ArtifactRef artifact : resolveArtifacts(project, sourceConfig)) {
if (!ModUtils.isMod(artifact.path())) {
if (!FabricModJsonFactory.isModJar(artifact.path())) {
artifact.applyToConfiguration(project, targetConfig);
continue;
}
@@ -110,7 +111,7 @@ public class ModConfigurationRemapper {
if (!toRemap.isEmpty()) {
try {
new ModProcessor(project, sourceConfig).processMods(toRemap);
new ModProcessor(project, sourceConfig, serviceManager).processMods(toRemap);
} catch (IOException e) {
throw new UncheckedIOException("Failed to remap mods", e);
}

View File

@@ -45,7 +45,7 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Pair;
@@ -53,6 +53,7 @@ import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.kotlin.KotlinClasspathService;
import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.tinyremapper.InputTag;
import net.fabricmc.tinyremapper.NonClassCopyMode;
import net.fabricmc.tinyremapper.OutputConsumerPath;
@@ -64,10 +65,12 @@ public class ModProcessor {
private final Project project;
private final Configuration sourceConfiguration;
private final SharedServiceManager serviceManager;
public ModProcessor(Project project, Configuration sourceConfiguration) {
public ModProcessor(Project project, Configuration sourceConfiguration, SharedServiceManager serviceManager) {
this.project = project;
this.sourceConfiguration = sourceConfiguration;
this.serviceManager = serviceManager;
}
public void processMods(List<ModDependency> remapList) throws IOException {
@@ -93,15 +96,15 @@ public class ModProcessor {
private void remapJars(List<ModDependency> remapList) throws IOException {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
final MappingConfiguration mappingConfiguration = extension.getMappingConfiguration();
Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles()
.stream().map(File::toPath).toArray(Path[]::new);
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
.withMappings(TinyRemapperHelper.create(mappingsProvider.getMappings(), fromM, toM, false))
.withMappings(TinyRemapperHelper.create(mappingConfiguration.getMappingsService(serviceManager).getMappingTree(), fromM, toM, false))
.renameInvalidLocals(false);
final KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(project);
final KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(serviceManager, project);
KotlinRemapperClassloader kotlinRemapperClassloader = null;
if (kotlinClasspathService != null) {

View File

@@ -32,25 +32,22 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
public final class LocalMavenHelper {
private final String group;
private final String name;
private final String version;
@Nullable
private final String baseClassifier;
private final Project project;
private final Path root;
LocalMavenHelper(String group, String name, String version, @Nullable String classifier, Project project) {
public LocalMavenHelper(String group, String name, String version, @Nullable String classifier, Path root) {
this.group = group;
this.name = name;
this.version = version;
this.baseClassifier = classifier;
this.project = project;
this.root = root;
}
public Path copyToMaven(Path artifact, @Nullable String classifier) throws IOException {
@@ -75,7 +72,7 @@ public final class LocalMavenHelper {
return String.format("%s:%s:%s", group, name, version);
}
private void savePom() {
public void savePom() {
try {
String pomTemplate;
@@ -94,13 +91,8 @@ public final class LocalMavenHelper {
}
}
private Path getRoot() {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
return extension.getFiles().getRemappedModCache().toPath();
}
private Path getDirectory() {
return getRoot().resolve("%s/%s/%s".formatted(group.replace(".", "/"), name, version));
return root.resolve("%s/%s/%s".formatted(group.replace(".", "/"), name, version));
}
private Path getPomPath() {

View File

@@ -69,7 +69,9 @@ public abstract sealed class ModDependency permits SplitModDependency, SimpleMod
public abstract void applyToProject(Project project);
protected LocalMavenHelper createMaven(String name) {
return new LocalMavenHelper(getRemappedGroup(), name, this.version, this.classifier, this.project);
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final Path root = extension.getFiles().getRemappedModCache().toPath();
return new LocalMavenHelper(getRemappedGroup(), name, this.version, this.classifier, root);
}
public ArtifactRef getInputArtifact() {

View File

@@ -26,6 +26,7 @@ package net.fabricmc.loom.configuration.processors;
import java.io.File;
@Deprecated()
public interface JarProcessor {
/**
* Returns a unique ID for this jar processor, containing all configuration details.

View File

@@ -1,129 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2020 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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hashing;
import com.google.common.io.CharSource;
import net.fabricmc.loom.util.ZipUtils;
public class JarProcessorManager {
private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
private static final String JAR_PROCESSOR_HASH_ATTRIBUTE = "Loom-Jar-Processor-Hash";
private final List<JarProcessor> jarProcessors;
public JarProcessorManager(List<JarProcessor> jarProcessors) {
this.jarProcessors = jarProcessors;
}
public void setupProcessors() {
jarProcessors.forEach(JarProcessor::setup);
}
public boolean active() {
return !jarProcessors.isEmpty();
}
public boolean isInvalid(File file) {
if (!file.exists()) {
return true;
}
String jarProcessorHash = getJarProcessorHash();
try (JarFile jar = new JarFile(file)) {
Manifest manifest = jar.getManifest();
if (manifest == null) {
return false;
}
Attributes attributes = manifest.getMainAttributes();
if (!jarProcessorHash.equals(attributes.getValue(JAR_PROCESSOR_HASH_ATTRIBUTE))) {
return true;
}
} catch (IOException e) {
throw new UncheckedIOException("Could not check jar manifest of " + file, e);
}
return false;
}
private String getJarProcessorHash() {
String jarProcessorIds = jarProcessors.stream()
.map(JarProcessor::getId)
.sorted()
.collect(Collectors.joining(";"));
try {
return CharSource.wrap(jarProcessorIds)
.asByteSource(StandardCharsets.UTF_8)
.hash(Hashing.sha256())
.toString();
} catch (IOException e) {
throw new UncheckedIOException("Could not hash jar processor IDs", e);
}
}
public void process(File file) {
for (JarProcessor jarProcessor : jarProcessors) {
jarProcessor.process(file);
}
try {
int count = ZipUtils.transform(file.toPath(), Map.of(MANIFEST_PATH, bytes -> {
Manifest manifest = new Manifest(new ByteArrayInputStream(bytes));
manifest.getMainAttributes().putValue(JAR_PROCESSOR_HASH_ATTRIBUTE, getJarProcessorHash());
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
return out.toByteArray();
}));
Preconditions.checkState(count > 0, "Did not add data to jar manifest in " + file);
} catch (IOException e) {
throw new UncheckedIOException("Could not add data to jar manifest in " + file, e);
}
}
public <T extends JarProcessor> T getByType(Class<T> tClass) {
//noinspection unchecked
return (T) jarProcessors.stream().filter(jarProcessor -> jarProcessor.getClass().equals(tClass)).findFirst().orElse(null);
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.configuration.processors;
import java.io.IOException;
import java.nio.file.Path;
import javax.inject.Inject;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
import net.fabricmc.loom.api.processor.ProcessorContext;
import net.fabricmc.loom.api.processor.SpecContext;
/**
* Wrapper around the deprecated API.
*/
public abstract class LegacyJarProcessorWrapper implements MinecraftJarProcessor<LegacyJarProcessorWrapper.Spec> {
private final JarProcessor delegate;
@Inject
public LegacyJarProcessorWrapper(JarProcessor delegate) {
this.delegate = delegate;
}
@Inject
public abstract Project getProject();
@Override
public String getName() {
return "legacy:" + delegate.getClass().getCanonicalName();
}
@Override
public @Nullable LegacyJarProcessorWrapper.Spec buildSpec(SpecContext context) {
delegate.setup();
return new Spec(delegate.getId());
}
@Override
public void processJar(Path jar, Spec spec, ProcessorContext context) throws IOException {
delegate.process(jar.toFile());
}
public record Spec(String cacheValue) implements MinecraftJarProcessor.Spec {
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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.configuration.processors;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.processor.MappingProcessorContext;
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
import net.fabricmc.loom.api.processor.ProcessorContext;
import net.fabricmc.loom.api.processor.SpecContext;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class MinecraftJarProcessorManager {
private static final String CACHE_VALUE_FILE_PATH = "META-INF/Loom-Jar-Processor-Cache";
private final List<ProcessorEntry<?>> jarProcessors;
private MinecraftJarProcessorManager(List<ProcessorEntry<?>> jarProcessors) {
this.jarProcessors = Collections.unmodifiableList(jarProcessors);
}
@Nullable
public static MinecraftJarProcessorManager create(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
List<MinecraftJarProcessor<?>> processors = new ArrayList<>(extension.getMinecraftJarProcessors().get());
for (JarProcessor legacyProcessor : extension.getGameJarProcessors().get()) {
processors.add(project.getObjects().newInstance(LegacyJarProcessorWrapper.class, legacyProcessor));
}
return MinecraftJarProcessorManager.create(processors, SpecContextImpl.create(project));
}
@Nullable
public static MinecraftJarProcessorManager create(List<MinecraftJarProcessor<?>> processors, SpecContext context) {
List<ProcessorEntry<?>> entries = new ArrayList<>();
for (MinecraftJarProcessor<?> processor : processors) {
MinecraftJarProcessor.Spec spec = processor.buildSpec(context);
if (spec != null) {
entries.add(new ProcessorEntry<>(processor, spec));
}
}
if (entries.isEmpty()) {
return null;
}
return new MinecraftJarProcessorManager(entries);
}
private String getCacheValue() {
return jarProcessors.stream()
.sorted(Comparator.comparing(ProcessorEntry::name))
.map(ProcessorEntry::cacheValue)
.collect(Collectors.joining("::"));
}
public boolean requiresProcessingJar(Path jar) {
Objects.requireNonNull(jar);
if (Files.notExists(jar)) {
return true;
}
byte[] existingCache;
try {
existingCache = ZipUtils.unpackNullable(jar, CACHE_VALUE_FILE_PATH);
} catch (IOException e) {
throw new UncheckedIOException("Failed to unpack jar: " + jar, e);
}
if (existingCache == null) {
return true;
}
final String existingCacheValue = new String(existingCache, StandardCharsets.UTF_8);
return !existingCacheValue.equals(getCacheValue());
}
public void processJar(Path jar, ProcessorContext context) throws IOException {
for (ProcessorEntry<?> entry : jarProcessors) {
try {
entry.processJar(jar, context);
} catch (IOException e) {
throw new IOException("Failed to process jar when running jar processor: %s".formatted(entry.name()), e);
}
}
ZipUtils.add(jar, CACHE_VALUE_FILE_PATH, getCacheValue());
}
public boolean processMappings(MemoryMappingTree mappings, MappingProcessorContext context) {
boolean transformed = false;
for (ProcessorEntry<?> entry : jarProcessors) {
if (entry.processMappings(mappings, context)) {
transformed = true;
}
}
return transformed;
}
record ProcessorEntry<S extends MinecraftJarProcessor.Spec>(S spec, MinecraftJarProcessor<S> processor, @Nullable MinecraftJarProcessor.MappingsProcessor<S> mappingsProcessor) {
@SuppressWarnings("unchecked")
ProcessorEntry(MinecraftJarProcessor<?> processor, MinecraftJarProcessor.Spec spec) {
this((S) Objects.requireNonNull(spec), (MinecraftJarProcessor<S>) processor, (MinecraftJarProcessor.MappingsProcessor<S>) processor.processMappings());
}
private void processJar(Path jar, ProcessorContext context) throws IOException {
processor().processJar(jar, spec, context);
}
private boolean processMappings(MemoryMappingTree mappings, MappingProcessorContext context) {
if (mappingsProcessor() == null) {
return false;
}
return mappingsProcessor().transform(mappings, spec, context);
}
private String name() {
return processor.getName();
}
private String cacheValue() {
return processor.getName() + ":" + spec.hashCode();
}
}
}

View File

@@ -25,66 +25,56 @@
package net.fabricmc.loom.configuration.processors;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import javax.inject.Inject;
import com.google.gson.JsonElement;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
import net.fabricmc.loom.api.processor.ProcessorContext;
import net.fabricmc.loom.api.processor.SpecContext;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class ModJavadocProcessor implements JarProcessor, GenerateSourcesTask.MappingsProcessor {
public abstract class ModJavadocProcessor implements MinecraftJarProcessor<ModJavadocProcessor.Spec> {
private static final Logger LOGGER = LoggerFactory.getLogger(ModJavadocProcessor.class);
private final List<ModJavadoc> javadocs;
private final String name;
private ModJavadocProcessor(List<ModJavadoc> javadocs) {
this.javadocs = javadocs;
@Inject
public ModJavadocProcessor(String name) {
this.name = name;
}
@Nullable
public static ModJavadocProcessor create(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final List<ModJavadoc> javadocs = new ArrayList<>();
@Override
public String getName() {
return name;
}
for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) {
final Set<File> artifacts = entry.getSourceConfiguration().get().resolve();
@Override
public @Nullable ModJavadocProcessor.Spec buildSpec(SpecContext context) {
List<ModJavadoc> javadocs = new ArrayList<>();
for (File artifact : artifacts) {
if (!ModUtils.isMod(artifact.toPath())) {
continue;
}
for (FabricModJson fabricModJson : context.allMods()) {
ModJavadoc javadoc = ModJavadoc.create(fabricModJson);
final ModJavadoc modJavadoc;
try {
modJavadoc = ModJavadoc.fromModJar(artifact.toPath());
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mod jar (%s)".formatted(artifact), e);
}
if (modJavadoc != null) {
javadocs.add(modJavadoc);
}
if (javadoc != null) {
javadocs.add(javadoc);
}
}
@@ -92,54 +82,49 @@ public final class ModJavadocProcessor implements JarProcessor, GenerateSourcesT
return null;
}
return new ModJavadocProcessor(javadocs);
return new Spec(Collections.unmodifiableList(javadocs));
}
public record Spec(List<ModJavadoc> javadocs) implements MinecraftJarProcessor.Spec {
}
@Override
public boolean transform(MemoryMappingTree mappings) {
for (ModJavadoc javadoc : javadocs) {
javadoc.apply(mappings);
}
return true;
public void processJar(Path jar, Spec spec, ProcessorContext context) {
// Nothing to do for the jar
}
@Override
public String getId() {
return "loom:interface_injection:" + javadocs.hashCode();
}
public @Nullable MappingsProcessor<Spec> processMappings() {
return (mappings, spec, context) -> {
for (ModJavadoc javadoc : spec.javadocs()) {
javadoc.apply(mappings);
}
@Override
public void setup() {
}
@Override
public void process(File file) {
// No need to actually process anything, we need to be a JarProcessor to ensure that the jar is cached correctly.
return true;
};
}
public record ModJavadoc(String modId, MemoryMappingTree mappingTree) {
@Nullable
public static ModJavadoc fromModJar(Path path) throws IOException {
JsonObject jsonObject = ModUtils.getFabricModJson(path);
public static ModJavadoc create(FabricModJson fabricModJson) {
final String modId = fabricModJson.getId();
final JsonElement customElement = fabricModJson.getCustom(Constants.CustomModJsonKeys.PROVIDED_JAVADOC);
if (jsonObject == null || !jsonObject.has("custom")) {
if (customElement == null) {
return null;
}
final String modId = jsonObject.get("id").getAsString();
final JsonObject custom = jsonObject.getAsJsonObject("custom");
if (!custom.has(Constants.CustomModJsonKeys.PROVIDED_JAVADOC)) {
return null;
}
final String javaDocPath = custom.getAsJsonPrimitive(Constants.CustomModJsonKeys.PROVIDED_JAVADOC).getAsString();
final byte[] data = ZipUtils.unpack(path, javaDocPath);
final String javaDocPath = customElement.getAsString();
final MemoryMappingTree mappings = new MemoryMappingTree();
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(data))) {
MappingReader.read(reader, mappings);
try {
final byte[] data = fabricModJson.getSource().read(javaDocPath);
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(data))) {
MappingReader.read(reader, mappings);
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to read javadoc from mod: " + modId, e);
}
if (!mappings.getSrcNamespace().equals(MappingsNamespace.INTERMEDIARY.toString())) {

View File

@@ -0,0 +1,75 @@
/*
* 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.configuration.processors;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.api.processor.ProcessorContext;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.tinyremapper.TinyRemapper;
public record ProcessorContextImpl(ConfigContext configContext, MinecraftJar minecraftJar) implements ProcessorContext {
@Override
public MinecraftJarConfiguration getJarConfiguration() {
return configContext.extension().getMinecraftJarConfiguration().get();
}
@Override
public boolean isMerged() {
return minecraftJar.isMerged();
}
@Override
public boolean includesClient() {
return minecraftJar.includesClient();
}
@Override
public boolean includesServer() {
return minecraftJar.includesServer();
}
@Override
public TinyRemapper createRemapper(MappingsNamespace from, MappingsNamespace to) {
try {
TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(configContext().project(), configContext().serviceManager(), from.toString(), to.toString());
tinyRemapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(configContext.project()));
for (Path minecraftJar : configContext.extension().getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
tinyRemapper.readClassPath(minecraftJar);
}
return tinyRemapper;
} catch (IOException e) {
throw new UncheckedIOException("Failed to create tiny remapper", e);
}
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.configuration.processors;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.processor.SpecContext;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
/**
* @param modDependencies External mods that are depended on
* @param localMods The main mod being built. In the future this may also include other mods.
*/
public record SpecContextImpl(List<FabricModJson> modDependencies, List<FabricModJson> localMods, List<FabricModJson> compileRuntimeMods) implements SpecContext {
public static SpecContextImpl create(Project project) {
return new SpecContextImpl(getDependentMods(project), getMods(project), getCompileRuntimeMods(project));
}
private static List<FabricModJson> getDependentMods(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
var mods = new ArrayList<FabricModJson>();
for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) {
final Set<File> artifacts = entry.getSourceConfiguration().get().resolve();
for (File artifact : artifacts) {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromZipNullable(artifact.toPath());
if (fabricModJson != null) {
mods.add(fabricModJson);
}
}
}
// TODO supporting projects here should magically allow TAWs and what not to work across project deps :)
return sorted(mods);
}
private static List<FabricModJson> getMods(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
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(sourceSets.toArray(SourceSet[]::new));
if (fabricModJson != null) {
return List.of(fabricModJson);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return Collections.emptyList();
}
private static List<FabricModJson> getCompileRuntimeMods(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final Function<RemapConfigurationSettings, Stream<Path>> resolve = settings ->
settings.getSourceConfiguration().get().resolve().stream()
.map(File::toPath);
final List<Path> runtimeEntries = extension.getRuntimeRemapConfigurations().stream()
.flatMap(resolve)
.toList();
return extension.getCompileRemapConfigurations().stream()
.flatMap(resolve)
.filter(runtimeEntries::contains) // Use the intersection of the two configurations.
.map(FabricModJsonFactory::createFromZipOptional)
.filter(Optional::isPresent)
.map(Optional::get)
.sorted(Comparator.comparing(FabricModJson::getId))
.toList();
}
// Sort to ensure stable caching
private static List<FabricModJson> sorted(List<FabricModJson> mods) {
return mods.stream().sorted(Comparator.comparing(FabricModJson::getId)).toList();
}
@Override
public List<FabricModJson> modDependenciesCompileRuntime() {
return compileRuntimeMods;
}
}

View File

@@ -38,6 +38,7 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingContext;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.util.download.DownloadBuilder;
import net.fabricmc.loom.util.service.ScopedSharedServiceManager;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class GradleMappingContext implements MappingContext {
@@ -66,7 +67,11 @@ public class GradleMappingContext implements MappingContext {
@Override
public Supplier<MemoryMappingTree> intermediaryTree() {
return () -> IntermediateMappingsService.getInstance(project, minecraftProvider()).getMemoryMappingTree();
return () -> {
try (var serviceManager = new ScopedSharedServiceManager()) {
return IntermediateMappingsService.getInstance(serviceManager, project, minecraftProvider()).getMemoryMappingTree();
}
};
}
@Override

View File

@@ -63,7 +63,7 @@ public abstract class IntermediaryMappingsProvider extends IntermediateMappingsP
.defaultCache()
.downloadPath(intermediaryJarPath);
MappingsProviderImpl.extractMappings(intermediaryJarPath, tinyMappings);
MappingConfiguration.extractMappings(intermediaryJarPath, tinyMappings);
}
@Override

View File

@@ -56,12 +56,12 @@ public final class IntermediateMappingsService implements SharedService {
this.intermediaryTiny = intermediaryTiny;
}
public static synchronized IntermediateMappingsService getInstance(Project project, MinecraftProvider minecraftProvider) {
public static synchronized IntermediateMappingsService getInstance(SharedServiceManager sharedServiceManager, Project project, MinecraftProvider minecraftProvider) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final IntermediateMappingsProvider intermediateProvider = extension.getIntermediateMappingsProvider();
final String id = "IntermediateMappingsService:%s:%s".formatted(intermediateProvider.getName(), intermediateProvider.getMinecraftVersion().get());
return SharedServiceManager.get(project).getOrCreateService(id, () -> create(intermediateProvider, minecraftProvider));
return sharedServiceManager.getOrCreateService(id, () -> create(intermediateProvider, minecraftProvider));
}
@VisibleForTesting

View File

@@ -38,9 +38,7 @@ import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import com.google.gson.JsonObject;
import org.apache.tools.ant.util.StringUtils;
import org.gradle.api.Project;
@@ -58,18 +56,15 @@ import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.stitch.Command;
import net.fabricmc.stitch.commands.CommandProposeFieldNames;
public class MappingsProviderImpl implements MappingsProvider, SharedService {
private static final Logger LOGGER = LoggerFactory.getLogger(MappingsProviderImpl.class);
public class MappingConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(MappingConfiguration.class);
private Supplier<MemoryMappingTree> mappingTree;
public final String mappingsIdentifier;
private final Path mappingsWorkingDir;
@@ -84,9 +79,7 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
private UnpickMetadata unpickMetadata;
private Map<String, String> signatureFixes;
private final Supplier<IntermediateMappingsService> intermediaryService;
private MappingsProviderImpl(String mappingsIdentifier, Path mappingsWorkingDir, Supplier<IntermediateMappingsService> intermediaryService) {
private MappingConfiguration(String mappingsIdentifier, Path mappingsWorkingDir) {
this.mappingsIdentifier = mappingsIdentifier;
this.mappingsWorkingDir = mappingsWorkingDir;
@@ -94,22 +87,9 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
this.tinyMappings = mappingsWorkingDir.resolve("mappings.tiny");
this.tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar");
this.unpickDefinitions = mappingsWorkingDir.resolve("mappings.unpick");
this.intermediaryService = intermediaryService;
}
public static synchronized MappingsProviderImpl getInstance(Project project, DependencyInfo dependency, MinecraftProvider minecraftProvider) {
return SharedServiceManager.get(project).getOrCreateService("MappingsProvider:%s:%s".formatted(dependency.getDepString(), minecraftProvider.minecraftVersion()), () -> {
Supplier<IntermediateMappingsService> intermediaryService = Suppliers.memoize(() -> IntermediateMappingsService.getInstance(project, minecraftProvider));
return create(dependency, minecraftProvider, intermediaryService);
});
}
public MemoryMappingTree getMappings() throws IOException {
return Objects.requireNonNull(mappingTree, "Cannot get mappings before they have been read").get();
}
private static MappingsProviderImpl create(DependencyInfo dependency, MinecraftProvider minecraftProvider, Supplier<IntermediateMappingsService> intermediaryService) {
public static MappingConfiguration create(Project project, SharedServiceManager serviceManager, DependencyInfo dependency, MinecraftProvider minecraftProvider) {
final String version = dependency.getResolvedVersion();
final Path inputJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve mappings: " + dependency)).toPath();
final String mappingsName = StringUtils.removeSuffix(dependency.getDependency().getGroup() + "." + dependency.getDependency().getName(), "-unmerged");
@@ -124,10 +104,10 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
final String mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, jarInfo.v2()), minecraftProvider.minecraftVersion());
final Path workingDir = minecraftProvider.dir(mappingsIdentifier).toPath();
var mappingProvider = new MappingsProviderImpl(mappingsIdentifier, workingDir, intermediaryService);
var mappingProvider = new MappingConfiguration(mappingsIdentifier, workingDir);
try {
mappingProvider.setup(minecraftProvider, inputJar);
mappingProvider.setup(project, serviceManager, minecraftProvider, inputJar);
} catch (IOException e) {
cleanWorkingDirectory(workingDir);
throw new UncheckedIOException("Failed to setup mappings: " + dependency.getDepString(), e);
@@ -136,13 +116,17 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
return mappingProvider;
}
private void setup(MinecraftProvider minecraftProvider, Path inputJar) throws IOException {
public TinyMappingsService getMappingsService(SharedServiceManager serviceManager) {
return TinyMappingsService.create(serviceManager, Objects.requireNonNull(tinyMappings));
}
private void setup(Project project, SharedServiceManager serviceManager, MinecraftProvider minecraftProvider, Path inputJar) throws IOException {
if (minecraftProvider.refreshDeps()) {
cleanWorkingDirectory(mappingsWorkingDir);
}
if (Files.notExists(tinyMappings) || minecraftProvider.refreshDeps()) {
storeMappings(minecraftProvider, inputJar);
storeMappings(project, serviceManager, minecraftProvider, inputJar);
} else {
try (FileSystem fileSystem = FileSystems.newFileSystem(inputJar, (ClassLoader) null)) {
extractExtras(fileSystem);
@@ -153,8 +137,6 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
Files.deleteIfExists(tinyMappingsJar);
ZipUtils.add(tinyMappingsJar, "mappings/mappings.tiny", Files.readAllBytes(tinyMappings));
}
mappingTree = Suppliers.memoize(this::readMappings);
}
public void applyToProject(Project project, DependencyInfo dependency) {
@@ -182,7 +164,7 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
return isV2 ? "-v2" : "";
}
private void storeMappings(MinecraftProvider minecraftProvider, Path inputJar) throws IOException {
private void storeMappings(Project project, SharedServiceManager serviceManager, MinecraftProvider minecraftProvider, Path inputJar) throws IOException {
LOGGER.info(":extracting " + inputJar.getFileName());
try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(inputJar)) {
@@ -192,7 +174,9 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
if (areMappingsV2(baseTinyMappings)) {
// These are unmerged v2 mappings
MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, intermediaryService.get());
IntermediateMappingsService intermediateMappingsService = IntermediateMappingsService.getInstance(serviceManager, project, minecraftProvider);
MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, intermediateMappingsService);
} else {
final List<Path> minecraftJars = minecraftProvider.getMinecraftJars();
@@ -207,16 +191,6 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
}
}
private MemoryMappingTree readMappings() {
try {
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingReader.read(tinyMappings, mappingTree);
return mappingTree;
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings", e);
}
}
private static boolean areMappingsV2(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path)) {
return MappingReader.detectFormat(reader) == MappingFormat.TINY_2;
@@ -326,16 +300,10 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
}
}
@Override
public Path mappingsWorkingDir() {
return mappingsWorkingDir;
}
@Override
public File intermediaryTinyFile() {
return intermediaryService.get().getIntermediaryTiny().toFile();
}
private static String createMappingsIdentifier(String mappingsName, String version, String classifier, String minecraftVersion) {
// mappingsName . mcVersion . version classifier
// Example: net.fabricmc.yarn . 1_16_5 . 1.16.5+build.5 -v2
@@ -365,9 +333,4 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
public record UnpickMetadata(String unpickGroup, String unpickVersion) {
}
@Override
public void close() throws IOException {
mappingTree = null;
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.configuration.providers.mappings;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class TinyMappingsService implements SharedService {
private final MemoryMappingTree mappingTree;
public TinyMappingsService(Path tinyMappings) {
try {
this.mappingTree = new MemoryMappingTree();
MappingReader.read(tinyMappings, mappingTree);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings", e);
}
}
public static synchronized TinyMappingsService create(SharedServiceManager serviceManager, Path tinyMappings) {
return serviceManager.getOrCreateService("TinyMappingsService:" + tinyMappings.toAbsolutePath(), () -> new TinyMappingsService(tinyMappings));
}
public MemoryMappingTree getMappingTree() {
return mappingTree;
}
}

View File

@@ -31,15 +31,14 @@ import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import org.gradle.api.Project;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.stitch.merge.JarMerger;
public final class MergedMinecraftProvider extends MinecraftProvider {
private Path minecraftMergedJar;
public MergedMinecraftProvider(Project project) {
super(project);
public MergedMinecraftProvider(ConfigContext configContext) {
super(configContext);
}
@Override

View File

@@ -0,0 +1,113 @@
/*
* 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.configuration.providers.minecraft;
import java.io.File;
import java.nio.file.Path;
import java.util.Objects;
public abstract sealed class MinecraftJar permits MinecraftJar.Merged, MinecraftJar.Common, MinecraftJar.ServerOnly, MinecraftJar.ClientOnly {
private final Path path;
private final boolean merged, client, server;
private final String name;
protected MinecraftJar(Path path, boolean merged, boolean client, boolean server, String name) {
this.path = Objects.requireNonNull(path);
this.merged = merged;
this.client = client;
this.server = server;
this.name = name;
}
public Path getPath() {
return path;
}
public File toFile() {
return getPath().toFile();
}
public boolean isMerged() {
return merged;
}
public boolean includesClient() {
return client;
}
public boolean includesServer() {
return server;
}
public String getName() {
return name;
}
public abstract MinecraftJar forPath(Path path);
public static final class Merged extends MinecraftJar {
public Merged(Path path) {
super(path, true, true, true, "merged");
}
@Override
public MinecraftJar forPath(Path path) {
return new Merged(path);
}
}
public static final class Common extends MinecraftJar {
public Common(Path path) {
super(path, false, false, true, "common");
}
@Override
public MinecraftJar forPath(Path path) {
return new Common(path);
}
}
public static final class ServerOnly extends MinecraftJar {
public ServerOnly(Path path) {
super(path, false, false, true, "serverOnly");
}
@Override
public MinecraftJar forPath(Path path) {
return new ServerOnly(path);
}
}
public static final class ClientOnly extends MinecraftJar {
public ClientOnly(Path path) {
super(path, false, true, false, "clientOnly");
}
@Override
public MinecraftJar forPath(Path path) {
return new ClientOnly(path);
}
}
}

View File

@@ -28,12 +28,11 @@ import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.gradle.api.Project;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.decompile.DecompileConfiguration;
import net.fabricmc.loom.configuration.decompile.SingleJarDecompileConfiguration;
import net.fabricmc.loom.configuration.decompile.SplitDecompileConfiguration;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
@@ -73,47 +72,47 @@ public enum MinecraftJarConfiguration {
List.of("client", "server")
);
private final Function<Project, MinecraftProvider> minecraftProviderFunction;
private final BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>> intermediaryMinecraftProviderBiFunction;
private final BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>> namedMinecraftProviderBiFunction;
private final BiFunction<NamedMinecraftProvider<?>, JarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> processedNamedMinecraftProviderBiFunction;
private final BiFunction<Project, MappedMinecraftProvider, DecompileConfiguration<?>> decompileConfigurationBiFunction;
private final Function<ConfigContext, MinecraftProvider> minecraftProviderFunction;
private final BiFunction<ConfigContext, MinecraftProvider, IntermediaryMinecraftProvider<?>> intermediaryMinecraftProviderBiFunction;
private final BiFunction<ConfigContext, MinecraftProvider, NamedMinecraftProvider<?>> namedMinecraftProviderBiFunction;
private final BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> processedNamedMinecraftProviderBiFunction;
private final BiFunction<ConfigContext, MappedMinecraftProvider, DecompileConfiguration<?>> decompileConfigurationBiFunction;
private final List<String> supportedEnvironments;
@SuppressWarnings("unchecked") // Just a bit of a generic mess :)
<M extends MinecraftProvider, P extends NamedMinecraftProvider<M>, Q extends MappedMinecraftProvider> MinecraftJarConfiguration(
Function<Project, M> minecraftProviderFunction,
BiFunction<Project, M, IntermediaryMinecraftProvider<M>> intermediaryMinecraftProviderBiFunction,
BiFunction<Project, M, P> namedMinecraftProviderBiFunction,
BiFunction<P, JarProcessorManager, ProcessedNamedMinecraftProvider<M, P>> processedNamedMinecraftProviderBiFunction,
BiFunction<Project, Q, DecompileConfiguration<?>> decompileConfigurationBiFunction,
Function<ConfigContext, M> minecraftProviderFunction,
BiFunction<ConfigContext, M, IntermediaryMinecraftProvider<M>> intermediaryMinecraftProviderBiFunction,
BiFunction<ConfigContext, M, P> namedMinecraftProviderBiFunction,
BiFunction<P, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<M, P>> processedNamedMinecraftProviderBiFunction,
BiFunction<ConfigContext, Q, DecompileConfiguration<?>> decompileConfigurationBiFunction,
List<String> supportedEnvironments
) {
this.minecraftProviderFunction = (Function<Project, MinecraftProvider>) minecraftProviderFunction;
this.intermediaryMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>>) (Object) intermediaryMinecraftProviderBiFunction;
this.namedMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>>) namedMinecraftProviderBiFunction;
this.processedNamedMinecraftProviderBiFunction = (BiFunction<NamedMinecraftProvider<?>, JarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>>) (Object) processedNamedMinecraftProviderBiFunction;
this.decompileConfigurationBiFunction = (BiFunction<Project, MappedMinecraftProvider, DecompileConfiguration<?>>) decompileConfigurationBiFunction;
this.minecraftProviderFunction = (Function<ConfigContext, MinecraftProvider>) minecraftProviderFunction;
this.intermediaryMinecraftProviderBiFunction = (BiFunction<ConfigContext, MinecraftProvider, IntermediaryMinecraftProvider<?>>) (Object) intermediaryMinecraftProviderBiFunction;
this.namedMinecraftProviderBiFunction = (BiFunction<ConfigContext, MinecraftProvider, NamedMinecraftProvider<?>>) namedMinecraftProviderBiFunction;
this.processedNamedMinecraftProviderBiFunction = (BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>>) (Object) processedNamedMinecraftProviderBiFunction;
this.decompileConfigurationBiFunction = (BiFunction<ConfigContext, MappedMinecraftProvider, DecompileConfiguration<?>>) decompileConfigurationBiFunction;
this.supportedEnvironments = supportedEnvironments;
}
public Function<Project, MinecraftProvider> getMinecraftProviderFunction() {
public Function<ConfigContext, MinecraftProvider> getMinecraftProviderFunction() {
return minecraftProviderFunction;
}
public BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>> getIntermediaryMinecraftProviderBiFunction() {
public BiFunction<ConfigContext, MinecraftProvider, IntermediaryMinecraftProvider<?>> getIntermediaryMinecraftProviderBiFunction() {
return intermediaryMinecraftProviderBiFunction;
}
public BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>> getNamedMinecraftProviderBiFunction() {
public BiFunction<ConfigContext, MinecraftProvider, NamedMinecraftProvider<?>> getNamedMinecraftProviderBiFunction() {
return namedMinecraftProviderBiFunction;
}
public BiFunction<NamedMinecraftProvider<?>, JarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> getProcessedNamedMinecraftProviderBiFunction() {
public BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> getProcessedNamedMinecraftProviderBiFunction() {
return processedNamedMinecraftProviderBiFunction;
}
public BiFunction<Project, MappedMinecraftProvider, DecompileConfiguration<?>> getDecompileConfigurationBiFunction() {
public BiFunction<ConfigContext, MappedMinecraftProvider, DecompileConfiguration<?>> getDecompileConfigurationBiFunction() {
return decompileConfigurationBiFunction;
}

View File

@@ -38,6 +38,7 @@ import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.DependencyInfo;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
import net.fabricmc.loom.util.Constants;
@@ -67,8 +68,8 @@ public abstract class MinecraftProvider {
private final Project project;
public MinecraftProvider(Project project) {
this.project = project;
public MinecraftProvider(ConfigContext configContext) {
this.project = configContext.project();
}
protected boolean provideClient() {

View File

@@ -220,8 +220,6 @@ public abstract sealed class MinecraftSourceSets permits MinecraftSourceSets.Sin
// The client only sources to the combined sources jar.
jar.from(clientOnlySourceSet.getAllSource());
});
extension.getInterfaceInjection().getInterfaceInjectionSourceSets().add(clientOnlySourceSet);
}
@Override

View File

@@ -34,9 +34,10 @@ import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.api.TrClass;
@@ -55,24 +56,24 @@ public record SignatureFixerApplyVisitor(Map<String, String> signatureFixes) imp
};
}
public static Map<String, String> getRemappedSignatures(boolean toIntermediary, MappingsProviderImpl mappingsProvider, Project project, String targetNamespace) throws IOException {
if (mappingsProvider.getSignatureFixes() == null) {
public static Map<String, String> getRemappedSignatures(boolean toIntermediary, MappingConfiguration mappingConfiguration, Project project, SharedServiceManager serviceManager, String targetNamespace) throws IOException {
if (mappingConfiguration.getSignatureFixes() == null) {
// No fixes
return Collections.emptyMap();
}
if (toIntermediary) {
// No need to remap, as these are already intermediary
return mappingsProvider.getSignatureFixes();
return mappingConfiguration.getSignatureFixes();
}
// Remap the sig fixes from intermediary to the target namespace
final Map<String, String> remapped = new HashMap<>();
final TinyRemapper sigTinyRemapper = TinyRemapperHelper.getTinyRemapper(project, MappingsNamespace.INTERMEDIARY.toString(), targetNamespace);
final TinyRemapper sigTinyRemapper = TinyRemapperHelper.getTinyRemapper(project, serviceManager, MappingsNamespace.INTERMEDIARY.toString(), targetNamespace);
final Remapper sigAsmRemapper = sigTinyRemapper.getEnvironment().getRemapper();
// Remap the class names and the signatures using a new tiny remapper instance.
for (Map.Entry<String, String> entry : mappingsProvider.getSignatureFixes().entrySet()) {
for (Map.Entry<String, String> entry : mappingConfiguration.getSignatureFixes().entrySet()) {
remapped.put(
sigAsmRemapper.map(entry.getKey()),
sigAsmRemapper.mapSignature(entry.getValue(), false)

View File

@@ -0,0 +1,43 @@
/*
* 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.configuration.providers.minecraft;
import java.nio.file.Path;
import java.util.function.Function;
public enum SingleJarEnvType {
CLIENT(MinecraftJar.ClientOnly::new),
SERVER(MinecraftJar.ServerOnly::new);
private final Function<Path, MinecraftJar> jarFunction;
SingleJarEnvType(Function<Path, MinecraftJar> jarFunction) {
this.jarFunction = jarFunction;
}
public Function<Path, MinecraftJar> getJar() {
return jarFunction;
}
}

View File

@@ -28,8 +28,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.gradle.api.Project;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
import net.fabricmc.tinyremapper.NonClassCopyMode;
import net.fabricmc.tinyremapper.OutputConsumerPath;
@@ -40,24 +39,24 @@ public final class SingleJarMinecraftProvider extends MinecraftProvider {
private Path minecraftEnvOnlyJar;
private SingleJarMinecraftProvider(Project project, Environment environment) {
super(project);
private SingleJarMinecraftProvider(ConfigContext configContext, Environment environment) {
super(configContext);
this.environment = environment;
}
public static SingleJarMinecraftProvider server(Project project) {
return new SingleJarMinecraftProvider(project, new Server());
public static SingleJarMinecraftProvider server(ConfigContext configContext) {
return new SingleJarMinecraftProvider(configContext, new Server());
}
public static SingleJarMinecraftProvider client(Project project) {
return new SingleJarMinecraftProvider(project, new Client());
public static SingleJarMinecraftProvider client(ConfigContext configContext) {
return new SingleJarMinecraftProvider(configContext, new Client());
}
@Override
protected void initFiles() {
super.initFiles();
minecraftEnvOnlyJar = path("minecraft-%s-only.jar".formatted(environment.name()));
minecraftEnvOnlyJar = path("minecraft-%s-only.jar".formatted(environment.type()));
}
@Override
@@ -92,7 +91,7 @@ public final class SingleJarMinecraftProvider extends MinecraftProvider {
}
} catch (Exception e) {
Files.deleteIfExists(minecraftEnvOnlyJar);
throw new RuntimeException("Failed to process %s only jar".formatted(environment.name()), e);
throw new RuntimeException("Failed to process %s only jar".formatted(environment.type()), e);
} finally {
if (remapper != null) {
remapper.finish();
@@ -115,15 +114,15 @@ public final class SingleJarMinecraftProvider extends MinecraftProvider {
}
private interface Environment {
String name();
SingleJarEnvType type();
Path getInputJar(SingleJarMinecraftProvider provider) throws Exception;
}
private static final class Server implements Environment {
@Override
public String name() {
return "server";
public SingleJarEnvType type() {
return SingleJarEnvType.SERVER;
}
@Override
@@ -141,8 +140,8 @@ public final class SingleJarMinecraftProvider extends MinecraftProvider {
private static final class Client implements Environment {
@Override
public String name() {
return "client";
public SingleJarEnvType type() {
return SingleJarEnvType.CLIENT;
}
@Override

View File

@@ -28,16 +28,15 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.gradle.api.Project;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
public final class SplitMinecraftProvider extends MinecraftProvider {
private Path minecraftClientOnlyJar;
private Path minecraftCommonJar;
public SplitMinecraftProvider(Project project) {
super(project);
public SplitMinecraftProvider(ConfigContext configContext) {
super(configContext);
}
@Override

View File

@@ -24,34 +24,41 @@
package net.fabricmc.loom.configuration.providers.minecraft.mapped;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.configuration.providers.minecraft.SignatureFixerApplyVisitor;
import net.fabricmc.loom.extension.LoomFiles;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvider> implements MappedMinecraftProvider.ProviderImpl {
protected final M minecraftProvider;
private final Project project;
protected final ConfigContext configContext;
protected final LoomGradleExtension extension;
public AbstractMappedMinecraftProvider(Project project, M minecraftProvider) {
this.project = project;
public AbstractMappedMinecraftProvider(ConfigContext configContext, M minecraftProvider) {
this.configContext = configContext;
this.minecraftProvider = minecraftProvider;
this.extension = LoomGradleExtension.get(project);
this.extension = configContext.extension();
}
public abstract MappingsNamespace getTargetNamespace();
@@ -90,24 +97,53 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
}
}
protected abstract Path getDirectory();
@Override
public Path getJar(String name) {
return getDirectory().resolve(getName(name) + ".jar");
return getMavenHelper(name).getOutputFile(null);
}
public enum MavenScope {
// Output files will be stored per project
LOCAL(LoomFiles::getLocalMinecraftRepo),
// Output files will be stored globally
GLOBAL(LoomFiles::getGlobalMinecraftRepo);
private final Function<LoomFiles, File> fileFunction;
MavenScope(Function<LoomFiles, File> fileFunction) {
this.fileFunction = fileFunction;
}
public Path getRoot(LoomGradleExtension extension) {
return fileFunction.apply(extension.getFiles()).toPath();
}
}
public abstract MavenScope getMavenScope();
public LocalMavenHelper getMavenHelper(String name) {
return new LocalMavenHelper("net.minecraft", getName(name), getVersion(), null, getMavenScope().getRoot(extension));
}
protected String getName(String name) {
return "minecraft-%s-%s".formatted(name, getTargetNamespace().toString());
if (getTargetNamespace() != MappingsNamespace.NAMED) {
name = getTargetNamespace().name().toLowerCase(Locale.ROOT) + "-" + name;
}
return "minecraft-" + name;
}
protected String getVersion() {
return "%s-%s".formatted(extension.getMinecraftProvider().minecraftVersion(), extension.getMappingConfiguration().mappingsIdentifier());
}
protected String getDependencyNotation(String name) {
return "net.minecraft:%s:%s/%s".formatted(getName(name), extension.getMinecraftProvider().minecraftVersion(), extension.getMappingsProvider().mappingsIdentifier());
return "net.minecraft:%s:%s".formatted(getName(name), getVersion());
}
private boolean areOutputsValid(List<RemappedJars> remappedJars) {
for (RemappedJars remappedJar : remappedJars) {
if (!Files.exists(remappedJar.outputJar())) {
if (!getMavenHelper(remappedJar.name()).exists(null)) {
return false;
}
}
@@ -124,21 +160,21 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
}
private void remapJar(RemappedJars remappedJars) throws IOException {
final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
final MappingConfiguration mappingConfiguration = extension.getMappingConfiguration();
final String fromM = remappedJars.sourceNamespace().toString();
final String toM = getTargetNamespace().toString();
Files.deleteIfExists(remappedJars.outputJar());
Files.deleteIfExists(remappedJars.outputJarPath());
final Map<String, String> remappedSignatures = SignatureFixerApplyVisitor.getRemappedSignatures(getTargetNamespace() == MappingsNamespace.INTERMEDIARY, mappingsProvider, project, toM);
TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(project, fromM, toM, true, (builder) -> {
final Map<String, String> remappedSignatures = SignatureFixerApplyVisitor.getRemappedSignatures(getTargetNamespace() == MappingsNamespace.INTERMEDIARY, mappingConfiguration, getProject(), configContext.serviceManager(), toM);
TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(getProject(), configContext.serviceManager(), fromM, toM, true, (builder) -> {
builder.extraPostApplyVisitor(new SignatureFixerApplyVisitor(remappedSignatures));
configureRemapper(remappedJars, builder);
});
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(remappedJars.outputJar()).build()) {
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(remappedJars.outputJarPath()).build()) {
outputConsumer.addNonClassFiles(remappedJars.inputJar());
remapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project));
remapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(getProject()));
for (Path path : remappedJars.remapClasspath()) {
remapper.readClassPath(path);
@@ -147,10 +183,12 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
remapper.readInputs(remappedJars.inputJar());
remapper.apply(outputConsumer);
} catch (Exception e) {
throw new RuntimeException("Failed to remap JAR " + remappedJars.inputJar() + " with mappings from " + mappingsProvider.tinyMappings, e);
throw new RuntimeException("Failed to remap JAR " + remappedJars.inputJar() + " with mappings from " + mappingConfiguration.tinyMappings, e);
} finally {
remapper.finish();
}
getMavenHelper(remappedJars.name()).savePom();
}
protected void configureRemapper(RemappedJars remappedJars, TinyRemapper.Builder tinyRemapperBuilder) {
@@ -158,18 +196,29 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
private void cleanOutputs(List<RemappedJars> remappedJars) throws IOException {
for (RemappedJars remappedJar : remappedJars) {
Files.deleteIfExists(remappedJar.outputJar());
Files.deleteIfExists(remappedJar.outputJarPath());
}
}
public ConfigContext getConfigContext() {
return configContext;
}
public Project getProject() {
return project;
return getConfigContext().project();
}
public M getMinecraftProvider() {
return minecraftProvider;
}
public record RemappedJars(Path inputJar, Path outputJar, MappingsNamespace sourceNamespace, Path... remapClasspath) {
public record RemappedJars(Path inputJar, MinecraftJar outputJar, MappingsNamespace sourceNamespace, Path... remapClasspath) {
public Path outputJarPath() {
return outputJar().getPath();
}
public String name() {
return outputJar().getName();
}
}
}

View File

@@ -24,27 +24,21 @@
package net.fabricmc.loom.configuration.providers.minecraft.mapped;
import java.nio.file.Path;
import java.util.List;
import org.gradle.api.Project;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
import net.fabricmc.loom.util.SidedClassVisitor;
import net.fabricmc.tinyremapper.TinyRemapper;
public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.SingleJarImpl, IntermediaryMinecraftProvider.SplitImpl {
public IntermediaryMinecraftProvider(Project project, M minecraftProvider) {
super(project, minecraftProvider);
}
@Override
protected Path getDirectory() {
return extension.getMinecraftProvider().workingDir().toPath();
public IntermediaryMinecraftProvider(ConfigContext configContext, M minecraftProvider) {
super(configContext, minecraftProvider);
}
@Override
@@ -52,9 +46,14 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
return MappingsNamespace.INTERMEDIARY;
}
@Override
public MavenScope getMavenScope() {
return MavenScope.GLOBAL;
}
public static final class MergedImpl extends IntermediaryMinecraftProvider<MergedMinecraftProvider> implements Merged {
public MergedImpl(Project project, MergedMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
public MergedImpl(ConfigContext configContext, MergedMinecraftProvider minecraftProvider) {
super(configContext, minecraftProvider);
}
@Override
@@ -66,8 +65,8 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
}
public static final class SplitImpl extends IntermediaryMinecraftProvider<SplitMinecraftProvider> implements Split {
public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
public SplitImpl(ConfigContext configContext, SplitMinecraftProvider minecraftProvider) {
super(configContext, minecraftProvider);
}
@Override
@@ -87,19 +86,19 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
}
public static final class SingleJarImpl extends IntermediaryMinecraftProvider<SingleJarMinecraftProvider> implements SingleJar {
private final String env;
private final SingleJarEnvType env;
private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, String env) {
super(project, minecraftProvider);
private SingleJarImpl(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider, SingleJarEnvType env) {
super(configContext, minecraftProvider);
this.env = env;
}
public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "server");
public static SingleJarImpl server(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(configContext, minecraftProvider, SingleJarEnvType.SERVER);
}
public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "client");
public static SingleJarImpl client(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(configContext, minecraftProvider, SingleJarEnvType.CLIENT);
}
@Override
@@ -110,7 +109,7 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
}
@Override
public String env() {
public SingleJarEnvType env() {
return env;
}
}

View File

@@ -27,8 +27,15 @@ package net.fabricmc.loom.configuration.providers.minecraft.mapped;
import java.nio.file.Path;
import java.util.List;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType;
public interface MappedMinecraftProvider {
List<Path> getMinecraftJars();
default List<Path> getMinecraftJarPaths() {
return getMinecraftJars().stream().map(MinecraftJar::getPath).toList();
}
List<MinecraftJar> getMinecraftJars();
interface ProviderImpl extends MappedMinecraftProvider {
Path getJar(String name);
@@ -37,12 +44,12 @@ public interface MappedMinecraftProvider {
interface Merged extends ProviderImpl {
String MERGED = "merged";
default Path getMergedJar() {
return getJar(MERGED);
default MinecraftJar getMergedJar() {
return new MinecraftJar.Merged(getJar(MERGED));
}
@Override
default List<Path> getMinecraftJars() {
default List<MinecraftJar> getMinecraftJars() {
return List.of(getMergedJar());
}
}
@@ -51,33 +58,33 @@ public interface MappedMinecraftProvider {
String COMMON = "common";
String CLIENT_ONLY = "clientOnly";
default Path getCommonJar() {
return getJar(COMMON);
default MinecraftJar getCommonJar() {
return new MinecraftJar.Common(getJar(COMMON));
}
default Path getClientOnlyJar() {
return getJar(CLIENT_ONLY);
default MinecraftJar getClientOnlyJar() {
return new MinecraftJar.ClientOnly(getJar(CLIENT_ONLY));
}
@Override
default List<Path> getMinecraftJars() {
default List<MinecraftJar> getMinecraftJars() {
return List.of(getCommonJar(), getClientOnlyJar());
}
}
interface SingleJar extends ProviderImpl {
String env();
SingleJarEnvType env();
default String envName() {
return "%sOnly".formatted(env());
}
default Path getEnvOnlyJar() {
return getJar(envName());
default MinecraftJar getEnvOnlyJar() {
return env().getJar().apply(getJar(envName()));
}
@Override
default List<Path> getMinecraftJars() {
default List<MinecraftJar> getMinecraftJars() {
return List.of(getEnvOnlyJar());
}
}

View File

@@ -24,27 +24,21 @@
package net.fabricmc.loom.configuration.providers.minecraft.mapped;
import java.nio.file.Path;
import java.util.List;
import org.gradle.api.Project;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
import net.fabricmc.loom.util.SidedClassVisitor;
import net.fabricmc.tinyremapper.TinyRemapper;
public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> {
public NamedMinecraftProvider(Project project, M minecraftProvider) {
super(project, minecraftProvider);
}
@Override
protected Path getDirectory() {
return extension.getMappingsProvider().mappingsWorkingDir();
public NamedMinecraftProvider(ConfigContext configContext, M minecraftProvider) {
super(configContext, minecraftProvider);
}
@Override
@@ -52,9 +46,14 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
return MappingsNamespace.NAMED;
}
@Override
public MavenScope getMavenScope() {
return MavenScope.GLOBAL;
}
public static final class MergedImpl extends NamedMinecraftProvider<MergedMinecraftProvider> implements Merged {
public MergedImpl(Project project, MergedMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
public MergedImpl(ConfigContext configContext, MergedMinecraftProvider minecraftProvider) {
super(configContext, minecraftProvider);
}
@Override
@@ -71,8 +70,8 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
}
public static final class SplitImpl extends NamedMinecraftProvider<SplitMinecraftProvider> implements Split {
public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
public SplitImpl(ConfigContext configContext, SplitMinecraftProvider minecraftProvider) {
super(configContext, minecraftProvider);
}
@Override
@@ -97,19 +96,19 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
}
public static final class SingleJarImpl extends NamedMinecraftProvider<SingleJarMinecraftProvider> implements SingleJar {
private final String env;
private final SingleJarEnvType env;
private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, String env) {
super(project, minecraftProvider);
private SingleJarImpl(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider, SingleJarEnvType env) {
super(configContext, minecraftProvider);
this.env = env;
}
public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "server");
public static SingleJarImpl server(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(configContext, minecraftProvider, SingleJarEnvType.SERVER);
}
public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "client");
public static SingleJarImpl client(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(configContext, minecraftProvider, SingleJarEnvType.CLIENT);
}
@Override
@@ -125,7 +124,7 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
}
@Override
public String env() {
public SingleJarEnvType env() {
return env;
}
}

View File

@@ -25,83 +25,89 @@
package net.fabricmc.loom.configuration.providers.minecraft.mapped;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Objects;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import org.gradle.api.Project;
import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper;
import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
import net.fabricmc.loom.configuration.processors.ProcessorContextImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvider, P extends NamedMinecraftProvider<M>> extends NamedMinecraftProvider<M> {
private final P parentMinecraftProvider;
private final JarProcessorManager jarProcessorManager;
private final String projectMappedName;
private final Path projectMappedDir;
private final MinecraftJarProcessorManager jarProcessorManager;
public ProcessedNamedMinecraftProvider(P parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
super(parentMinecraftProvide.getProject(), parentMinecraftProvide.getMinecraftProvider());
public ProcessedNamedMinecraftProvider(P parentMinecraftProvide, MinecraftJarProcessorManager jarProcessorManager) {
super(parentMinecraftProvide.getConfigContext(), parentMinecraftProvide.getMinecraftProvider());
this.parentMinecraftProvider = parentMinecraftProvide;
this.jarProcessorManager = jarProcessorManager;
this.projectMappedName = "minecraft-project-%s-".formatted(getProject().getPath().replace(':', '@'));
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
this.projectMappedDir = extension.getFiles().getRootProjectPersistentCache().toPath()
.resolve(getMinecraftProvider().minecraftVersion())
.resolve(extension.getMappingsProvider().mappingsIdentifier());
this.jarProcessorManager = Objects.requireNonNull(jarProcessorManager);
}
@Override
public void provide(boolean applyDependencies) throws Exception {
parentMinecraftProvider.provide(false);
final List<Path> inputJars = parentMinecraftProvider.getMinecraftJars();
boolean requiresProcessing = extension.refreshDeps() || inputJars.stream()
.map(this::getProcessedPath)
.map(Path::toFile)
.anyMatch(jarProcessorManager::isInvalid);
boolean requiresProcessing = parentMinecraftProvider.getMinecraftJarPaths().stream()
.anyMatch(jarProcessorManager::requiresProcessingJar);
if (requiresProcessing) {
try {
Files.createDirectories(projectMappedDir);
} catch (IOException e) {
throw new UncheckedIOException("Failed to create project mapped dir", e);
}
for (Path inputJar : inputJars) {
final Path outputJar = getProcessedPath(inputJar);
deleteSimilarJars(outputJar);
Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING);
jarProcessorManager.process(outputJar.toFile());
}
processJars();
}
if (applyDependencies) {
final List<String> dependencyTargets = parentMinecraftProvider.getDependencyTargets();
if (dependencyTargets.isEmpty()) {
return;
}
MinecraftSourceSets.get(getProject()).applyDependencies(
(configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)),
dependencyTargets
);
applyDependencies();
}
}
@Override
public MavenScope getMavenScope() {
return MavenScope.LOCAL;
}
private void processJars() throws IOException {
for (MinecraftJar minecraftJar : parentMinecraftProvider.getMinecraftJars()) {
final MinecraftJar outputJar = getProcessedPath(minecraftJar);
deleteSimilarJars(outputJar.getPath());
final LocalMavenHelper mavenHelper = getMavenHelper(minecraftJar.getName());
final Path outputPath = mavenHelper.copyToMaven(minecraftJar.getPath(), null);
jarProcessorManager.processJar(outputPath, new ProcessorContextImpl(configContext, minecraftJar));
}
}
private void applyDependencies() {
final List<String> dependencyTargets = parentMinecraftProvider.getDependencyTargets();
if (dependencyTargets.isEmpty()) {
return;
}
MinecraftSourceSets.get(getProject()).applyDependencies(
(configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)),
dependencyTargets
);
}
private void deleteSimilarJars(Path jar) throws IOException {
Files.deleteIfExists(jar);
final Path parent = jar.getParent();
for (Path path : Files.list(jar.getParent()).filter(Files::isRegularFile)
if (Files.notExists(parent)) {
return;
}
for (Path path : Files.list(parent).filter(Files::isRegularFile)
.filter(path -> path.getFileName().startsWith(jar.getFileName().toString().replace(".jar", ""))).toList()) {
Files.deleteIfExists(path);
}
@@ -109,7 +115,14 @@ public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvide
@Override
protected String getName(String name) {
return "%s%s-%s".formatted(projectMappedName, name, getTargetNamespace().toString());
final Project project = getProject();
if (project.getRootProject() == project) {
return "minecraft-%s-project-root".formatted(name);
}
final String projectPath = project.getPath().replace(':', '@');
return "minecraft-%s-project-%s".formatted(name, projectPath);
}
@Override
@@ -124,7 +137,7 @@ public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvide
}
@Override
public List<Path> getMinecraftJars() {
public List<MinecraftJar> getMinecraftJars() {
return getParentMinecraftProvider().getMinecraftJars().stream()
.map(this::getProcessedPath)
.toList();
@@ -134,60 +147,61 @@ public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvide
return parentMinecraftProvider;
}
public Path getProcessedPath(Path input) {
return projectMappedDir.resolve(input.getFileName().toString().replace("minecraft-", projectMappedName));
public MinecraftJar getProcessedPath(MinecraftJar minecraftJar) {
final Path path = getMavenHelper(minecraftJar.getName()).getOutputFile(null);
return minecraftJar.forPath(path);
}
public static final class MergedImpl extends ProcessedNamedMinecraftProvider<MergedMinecraftProvider, NamedMinecraftProvider.MergedImpl> implements Merged {
public MergedImpl(NamedMinecraftProvider.MergedImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
public MergedImpl(NamedMinecraftProvider.MergedImpl parentMinecraftProvide, MinecraftJarProcessorManager jarProcessorManager) {
super(parentMinecraftProvide, jarProcessorManager);
}
@Override
public Path getMergedJar() {
public MinecraftJar getMergedJar() {
return getProcessedPath(getParentMinecraftProvider().getMergedJar());
}
}
public static final class SplitImpl extends ProcessedNamedMinecraftProvider<SplitMinecraftProvider, NamedMinecraftProvider.SplitImpl> implements Split {
public SplitImpl(NamedMinecraftProvider.SplitImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
public SplitImpl(NamedMinecraftProvider.SplitImpl parentMinecraftProvide, MinecraftJarProcessorManager jarProcessorManager) {
super(parentMinecraftProvide, jarProcessorManager);
}
@Override
public Path getCommonJar() {
public MinecraftJar getCommonJar() {
return getProcessedPath(getParentMinecraftProvider().getCommonJar());
}
@Override
public Path getClientOnlyJar() {
public MinecraftJar getClientOnlyJar() {
return getProcessedPath(getParentMinecraftProvider().getClientOnlyJar());
}
}
public static final class SingleJarImpl extends ProcessedNamedMinecraftProvider<SingleJarMinecraftProvider, NamedMinecraftProvider.SingleJarImpl> implements SingleJar {
private final String env;
private final SingleJarEnvType env;
private SingleJarImpl(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager, String env) {
private SingleJarImpl(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, MinecraftJarProcessorManager jarProcessorManager, SingleJarEnvType env) {
super(parentMinecraftProvide, jarProcessorManager);
this.env = env;
}
public static ProcessedNamedMinecraftProvider.SingleJarImpl server(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, "server");
public static ProcessedNamedMinecraftProvider.SingleJarImpl server(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, MinecraftJarProcessorManager jarProcessorManager) {
return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, SingleJarEnvType.SERVER);
}
public static ProcessedNamedMinecraftProvider.SingleJarImpl client(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, "client");
public static ProcessedNamedMinecraftProvider.SingleJarImpl client(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, MinecraftJarProcessorManager jarProcessorManager) {
return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, SingleJarEnvType.CLIENT);
}
@Override
public Path getEnvOnlyJar() {
public MinecraftJar getEnvOnlyJar() {
return getProcessedPath(getParentMinecraftProvider().getEnvOnlyJar());
}
@Override
public String env() {
public SingleJarEnvType env() {
return env;
}
}

View File

@@ -48,4 +48,6 @@ public interface LoomFiles {
File getDevLauncherConfig();
File getUnpickLoggingConfigFile();
File getRemapClasspathFile();
File getGlobalMinecraftRepo();
File getLocalMinecraftRepo();
}

View File

@@ -97,4 +97,14 @@ public abstract class LoomFilesBaseImpl implements LoomFiles {
public File getRemapClasspathFile() {
return new File(getProjectPersistentCache(), "remapClasspath.txt");
}
@Override
public File getGlobalMinecraftRepo() {
return new File(getUserCache(), "minecraftMaven");
}
@Override
public File getLocalMinecraftRepo() {
return new File(getRootProjectPersistentCache(), "minecraftMaven");
}
}

View File

@@ -47,6 +47,7 @@ import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
import net.fabricmc.loom.configuration.RemapConfigurations;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.processors.JarProcessor;
@@ -65,6 +66,7 @@ import net.fabricmc.loom.util.gradle.SourceSetHelper;
*/
public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionAPI {
protected final DeprecationHelper deprecationHelper;
@Deprecated()
protected final ListProperty<JarProcessor> jarProcessors;
protected final ConfigurableFileCollection log4jConfigs;
protected final RegularFileProperty accessWidener;
@@ -85,6 +87,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
private final NamedDomainObjectContainer<DecompilerOptions> decompilers;
private final NamedDomainObjectContainer<ModSettings> mods;
private final NamedDomainObjectList<RemapConfigurationSettings> remapConfigurations;
private final ListProperty<MinecraftJarProcessor<?>> minecraftJarProcessors;
// A common mistake with layered mappings is to call the wrong `officialMojangMappings` method, use this to keep track of when we are building a layered mapping spec.
protected final ThreadLocal<Boolean> layeredSpecBuilderScope = ThreadLocal.withInitial(() -> false);
@@ -116,6 +119,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.decompilers = project.getObjects().domainObjectContainer(DecompilerOptions.class);
this.mods = project.getObjects().domainObjectContainer(ModSettings.class);
this.remapConfigurations = project.getObjects().namedDomainObjectList(RemapConfigurationSettings.class);
//noinspection unchecked
this.minecraftJarProcessors = (ListProperty<MinecraftJarProcessor<?>>) (Object) project.getObjects().listProperty(MinecraftJarProcessor.class);
this.minecraftJarProcessors.finalizeValueOnRead();
this.minecraftJarConfiguration = project.getObjects().property(MinecraftJarConfiguration.class).convention(MinecraftJarConfiguration.MERGED);
this.minecraftJarConfiguration.finalizeValueOnRead();
@@ -130,6 +136,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.splitModDependencies.finalizeValueOnRead();
this.interfaceInjectionExtension = project.getObjects().newInstance(InterfaceInjectionExtensionAPI.class);
this.interfaceInjectionExtension.getIsEnabled().convention(true);
this.splitEnvironmentalSourceSet = project.getObjects().property(Boolean.class).convention(false);
this.splitEnvironmentalSourceSet.finalizeValueOnRead();
@@ -169,6 +176,16 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return jarProcessors;
}
@Override
public ListProperty<MinecraftJarProcessor<?>> getMinecraftJarProcessors() {
return minecraftJarProcessors;
}
@Override
public void addMinecraftJarProcessor(Class<? extends MinecraftJarProcessor<?>> clazz, Object... parameters) {
getMinecraftJarProcessors().add(getProject().getObjects().newInstance(clazz, parameters));
}
@Override
public Dependency officialMojangMappings() {
if (layeredSpecBuilderScope.get()) {
@@ -259,7 +276,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
@Override
public File getMappingsFile() {
return LoomGradleExtension.get(getProject()).getMappingsProvider().tinyMappings.toFile();
return LoomGradleExtension.get(getProject()).getMappingConfiguration().tinyMappings.toFile();
}
@Override

View File

@@ -43,9 +43,8 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.configuration.LoomDependencyManager;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
@@ -63,9 +62,8 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
private final List<AccessWidenerFile> transitiveAccessWideners = new ArrayList<>();
private LoomDependencyManager dependencyManager;
private JarProcessorManager jarProcessorManager;
private MinecraftProvider minecraftProvider;
private MappingsProviderImpl mappingsProvider;
private MappingConfiguration mappingConfiguration;
private NamedMinecraftProvider<?> namedMinecraftProvider;
private IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider;
private InstallerData installerData;
@@ -115,16 +113,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
return Objects.requireNonNull(dependencyManager, "Cannot get LoomDependencyManager before it has been setup");
}
@Override
public void setJarProcessorManager(JarProcessorManager jarProcessorManager) {
this.jarProcessorManager = jarProcessorManager;
}
@Override
public JarProcessorManager getJarProcessorManager() {
return Objects.requireNonNull(jarProcessorManager, "Cannot get JarProcessorManager before it has been setup");
}
@Override
public MinecraftProvider getMinecraftProvider() {
return Objects.requireNonNull(minecraftProvider, "Cannot get MinecraftProvider before it has been setup");
@@ -136,13 +124,13 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
}
@Override
public MappingsProviderImpl getMappingsProvider() {
return Objects.requireNonNull(mappingsProvider, "Cannot get MappingsProvider before it has been setup");
public MappingConfiguration getMappingConfiguration() {
return Objects.requireNonNull(mappingConfiguration, "Cannot get MappingsProvider before it has been setup");
}
@Override
public void setMappingsProvider(MappingsProviderImpl mappingsProvider) {
this.mappingsProvider = mappingsProvider;
public void setMappingConfiguration(MappingConfiguration mappingConfiguration) {
this.mappingConfiguration = mappingConfiguration;
}
@Override

View File

@@ -45,10 +45,12 @@ import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.build.event.BuildEventsListenerRegistry;
import org.gradle.jvm.tasks.Jar;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
@@ -89,15 +91,23 @@ public abstract class AbstractRemapJarTask extends Jar {
@Inject
protected abstract WorkerExecutor getWorkerExecutor();
@Inject
protected abstract BuildEventsListenerRegistry getBuildEventsListenerRegistry();
@Input
public abstract Property<Boolean> getIncludesClientOnlyClasses();
private final Provider<JarManifestService> jarManifestServiceProvider;
@Inject
public AbstractRemapJarTask() {
getSourceNamespace().convention(MappingsNamespace.NAMED.toString()).finalizeValueOnRead();
getTargetNamespace().convention(MappingsNamespace.INTERMEDIARY.toString()).finalizeValueOnRead();
getRemapperIsolation().convention(false).finalizeValueOnRead();
getIncludesClientOnlyClasses().convention(false).finalizeValueOnRead();
jarManifestServiceProvider = JarManifestService.get(getProject());
usesService(jarManifestServiceProvider);
}
public final <P extends AbstractRemapParams> void submitWork(Class<? extends AbstractRemapAction<P>> workAction, Action<P> action) {
@@ -113,7 +123,7 @@ public abstract class AbstractRemapJarTask extends Jar {
params.getArchivePreserveFileTimestamps().set(isPreserveFileTimestamps());
params.getArchiveReproducibleFileOrder().set(isReproducibleFileOrder());
params.getJarManifestService().set(JarManifestService.get(getProject()));
params.getJarManifestService().set(jarManifestServiceProvider);
if (getIncludesClientOnlyClasses().get()) {
final List<String> clientOnlyEntries = getClientOnlyEntries();

View File

@@ -64,9 +64,9 @@ import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.api.processor.MappingProcessorContext;
import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMappingsProcessor;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.processors.ModJavadocProcessor;
import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
import net.fabricmc.loom.decompilers.LineNumberRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
@@ -318,7 +318,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
private Path getMappings() {
Path inputMappings = getExtension().getMappingsProvider().tinyMappings;
Path inputMappings = getExtension().getMappingConfiguration().tinyMappings;
MemoryMappingTree mappingTree = new MemoryMappingTree();
@@ -334,14 +334,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
mappingsProcessors.add(new TransitiveAccessWidenerMappingsProcessor(getProject()));
}
if (getExtension().getInterfaceInjection().isEnabled()) {
mappingsProcessors.add(new InterfaceInjectionProcessor(getProject()));
}
MinecraftJarProcessorManager minecraftJarProcessorManager = MinecraftJarProcessorManager.create(getProject());
final ModJavadocProcessor javadocProcessor = ModJavadocProcessor.create(getProject());
if (javadocProcessor != null) {
mappingsProcessors.add(javadocProcessor);
if (minecraftJarProcessorManager != null) {
mappingsProcessors.add(mappings -> minecraftJarProcessorManager.processMappings(mappings, new MappingProcessorContextImpl()));
}
if (mappingsProcessors.isEmpty()) {
@@ -392,4 +388,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
throw new RuntimeException(e);
}
}
private static class MappingProcessorContextImpl implements MappingProcessorContext {
}
}

View File

@@ -52,9 +52,10 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.service.ScopedSharedServiceManager;
import net.fabricmc.lorenztiny.TinyMappingsJoiner;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
@@ -105,10 +106,10 @@ public abstract class MigrateMappingsTask extends AbstractLoomTask {
Files.createDirectories(outputDir);
File mappings = loadMappings();
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
MappingConfiguration mappingConfiguration = extension.getMappingConfiguration();
try {
MemoryMappingTree currentMappings = mappingsProvider.getMappings();
try (var serviceManager = new ScopedSharedServiceManager()) {
MemoryMappingTree currentMappings = mappingConfiguration.getMappingsService(serviceManager).getMappingTree();
MemoryMappingTree targetMappings = getMappings(mappings);
migrateMappings(project, extension, inputDir, outputDir, currentMappings, targetMappings);
project.getLogger().lifecycle(":remapped project written to " + outputDir.toAbsolutePath());

View File

@@ -78,7 +78,7 @@ public abstract class PrepareJarRemapTask extends AbstractLoomTask {
final WorkQueue workQueue = getWorkerExecutor().noIsolation();
workQueue.submit(ReadInputsAction.class, params -> {
params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), remapJarTask.getTinyRemapperService()));
params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(remapJarTask.getTinyRemapperService()));
params.getInputFile().set(getInputFile());
});
}

View File

@@ -46,11 +46,13 @@ import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,7 +60,6 @@ import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerRemapper;
import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.build.MixinRefmapHelper;
import net.fabricmc.loom.build.nesting.IncludedJarFactory;
import net.fabricmc.loom.build.nesting.JarNester;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
@@ -67,10 +68,12 @@ import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.TinyRemapperService;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ExceptionUtil;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.SidedClassVisitor;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
import net.fabricmc.loom.util.service.BuildSharedServiceManager;
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
@@ -82,11 +85,18 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
@Input
public abstract Property<Boolean> getAddNestedDependencies();
private Supplier<TinyRemapperService> tinyRemapperService = Suppliers.memoize(() -> TinyRemapperService.getOrCreate(this));
@Input
@ApiStatus.Internal
public abstract Property<Boolean> getUseMixinAP();
private final Provider<BuildSharedServiceManager> serviceManagerProvider;
private final Supplier<TinyRemapperService> tinyRemapperService;
@Inject
public RemapJarTask() {
super();
serviceManagerProvider = BuildSharedServiceManager.createForTask(this, getBuildEventsListenerRegistry());
tinyRemapperService = Suppliers.memoize(() -> TinyRemapperService.getOrCreate(serviceManagerProvider.get().get(), this));
getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
getAddNestedDependencies().convention(true).finalizeValueOnRead();
@@ -94,6 +104,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE);
getNestedJars().from(new IncludedJarFactory(getProject()).getNestedJars(includeConfiguration));
getUseMixinAP().set(LoomGradleExtension.get(getProject()).getMixin().getUseLegacyMixinAp());
setupPreparationTask();
}
@@ -115,20 +127,18 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
@TaskAction
public void run() {
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
submitWork(RemapAction.class, params -> {
if (getAddNestedDependencies().get()) {
params.getNestedJars().from(getNestedJars());
}
params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), tinyRemapperService.get()));
params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(tinyRemapperService.get()));
params.getRemapClasspath().from(getClasspath());
final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get();
params.getUseMixinExtension().set(!legacyMixin);
final boolean mixinAp = getUseMixinAP().get();
params.getUseMixinExtension().set(!mixinAp);
if (legacyMixin) {
if (mixinAp) {
setupLegacyMixinRefmapRemapping(params);
}
});
@@ -138,14 +148,13 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
final MixinExtension mixinExtension = extension.getMixin();
final JsonObject fabricModJson = ModUtils.getFabricModJson(getInputFile().getAsFile().get().toPath());
final FabricModJson fabricModJson = FabricModJsonFactory.createFromZipNullable(getInputFile().getAsFile().get().toPath());
if (fabricModJson == null) {
getProject().getLogger().warn("Could not find fabric.mod.json file in: " + getInputFile().getAsFile().get().getName());
return;
}
final Collection<String> allMixinConfigs = MixinRefmapHelper.getMixinConfigurationFiles(fabricModJson);
final Collection<String> allMixinConfigs = fabricModJson.getMixinConfigurations();
for (SourceSet sourceSet : mixinExtension.getMixinSourceSets()) {
MixinExtension.MixinInformationContainer container = Objects.requireNonNull(

View File

@@ -32,6 +32,7 @@ import javax.inject.Inject;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.slf4j.Logger;
@@ -39,12 +40,16 @@ import org.slf4j.LoggerFactory;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.task.service.SourceRemapperService;
import net.fabricmc.loom.util.service.BuildSharedServiceManager;
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
private final Provider<BuildSharedServiceManager> serviceManagerProvider;
@Inject
public RemapSourcesJarTask() {
super();
serviceManagerProvider = BuildSharedServiceManager.createForTask(this, getBuildEventsListenerRegistry());
getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
}
@@ -52,7 +57,7 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
@TaskAction
public void run() {
submitWork(RemapSourcesAction.class, params -> {
params.getSourcesRemapperServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), SourceRemapperService.create(this)));
params.getSourcesRemapperServiceUuid().set(UnsafeWorkQueueHelper.create(SourceRemapperService.create(serviceManagerProvider.get().get(), this)));
});
}

View File

@@ -105,8 +105,8 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
MappedMinecraftProvider.Split split = (MappedMinecraftProvider.Split) getExtension().getNamedMinecraftProvider();
return switch (env) {
case "client" -> split.getClientOnlyJar().toAbsolutePath().toString();
case "common" -> split.getCommonJar().toAbsolutePath().toString();
case "client" -> split.getClientOnlyJar().getPath().toAbsolutePath().toString();
case "common" -> split.getCommonJar().getPath().toAbsolutePath().toString();
default -> throw new UnsupportedOperationException();
};
}

View File

@@ -40,6 +40,7 @@ import org.gradle.util.GradleVersion;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.tinyremapper.TinyRemapper;
@@ -63,10 +64,10 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
params.getGradleVersion().set(GradleVersion.current().getVersion());
params.getLoomVersion().set(LoomGradlePlugin.LOOM_VERSION);
params.getMCEVersion().set(Constants.Dependencies.Versions.MIXIN_COMPILE_EXTENSIONS);
params.getMinecraftVersion().set(extension.getMinecraftProvider().minecraftVersion());
params.getMinecraftVersion().set(project.provider(() -> extension.getMinecraftProvider().minecraftVersion()));
params.getTinyRemapperVersion().set(tinyRemapperVersion.orElse("unknown"));
params.getFabricLoaderVersion().set(getLoaderVersion(project).orElse("unknown"));
params.getMixinVersion().set(getMixinVersion(project).orElse(new MixinVersion("unknown", "unknown")));
params.getFabricLoaderVersion().set(project.provider(() -> Optional.ofNullable(extension.getInstallerData()).map(InstallerData::version).orElse("unknown")));
params.getMixinVersion().set(getMixinVersion(project));
});
});
}
@@ -98,31 +99,23 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
}
}
private static Optional<String> getLoaderVersion(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.getInstallerData() == null) {
project.getLogger().warn("Could not determine fabric loader version for jar manifest");
return Optional.empty();
}
return Optional.of(extension.getInstallerData().version());
}
private record MixinVersion(String group, String version) implements Serializable { }
private static Optional<MixinVersion> getMixinVersion(Project project) {
// Not super ideal that this uses the mod compile classpath, should prob look into making this not a thing at somepoint
Optional<Dependency> dependency = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES)
.getDependencies()
.stream()
.filter(dep -> "sponge-mixin".equals(dep.getName()))
.findFirst();
private static Provider<MixinVersion> getMixinVersion(Project project) {
return project.getConfigurations().named(Constants.Configurations.LOADER_DEPENDENCIES).map(configuration -> {
// Not super ideal that this uses the mod compile classpath, should prob look into making this not a thing at somepoint
Optional<Dependency> dependency = configuration
.getDependencies()
.stream()
.filter(dep -> "sponge-mixin".equals(dep.getName()))
.findFirst();
if (dependency.isEmpty()) {
project.getLogger().warn("Could not determine Mixin version for jar manifest");
}
if (dependency.isEmpty()) {
project.getLogger().warn("Could not determine Mixin version for jar manifest");
}
return dependency.map(d -> new MixinVersion(d.getGroup(), d.getVersion()));
return dependency.map(d -> new MixinVersion(d.getGroup(), d.getVersion()))
.orElse(new MixinVersion("unknown", "unknown"));
});
}
}

View File

@@ -31,7 +31,7 @@ import java.nio.file.Path;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
@@ -42,21 +42,17 @@ import net.fabricmc.tinyremapper.IMappingProvider;
public final class MappingsService implements SharedService {
private record Options(Path mappingsFile, String from, String to, boolean remapLocals) { }
public static MappingsService create(Project project, String name, Path mappingsFile, String from, String to, boolean remapLocals) {
return create(SharedServiceManager.get(project), name, mappingsFile, from, to, remapLocals);
}
public static synchronized MappingsService create(SharedServiceManager sharedServiceManager, String name, Path mappingsFile, String from, String to, boolean remapLocals) {
final Options options = new Options(mappingsFile, from, to, remapLocals);
final String id = name + options.hashCode();
return sharedServiceManager.getOrCreateService(id, () -> new MappingsService(options));
}
public static MappingsService createDefault(Project project, String from, String to) {
final MappingsProviderImpl mappingsProvider = LoomGradleExtension.get(project).getMappingsProvider();
public static MappingsService createDefault(Project project, SharedServiceManager serviceManager, String from, String to) {
final MappingConfiguration mappingConfiguration = LoomGradleExtension.get(project).getMappingConfiguration();
final String name = mappingsProvider.getBuildServiceName("mappingsProvider", from, to);
return MappingsService.create(project, name, mappingsProvider.tinyMappings, from, to, false);
final String name = mappingConfiguration.getBuildServiceName("mappingsProvider", from, to);
return MappingsService.create(serviceManager, name, mappingConfiguration.tinyMappings, from, to, false);
}
private final Options options;

View File

@@ -1,70 +0,0 @@
/*
* 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.task.service;
import java.io.File;
import java.util.HashSet;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.tinyremapper.IMappingProvider;
public final class MixinMappingsService implements SharedService {
private final SharedServiceManager sharedServiceManager;
private final HashSet<File> mixinMappings = new HashSet<>();
private MixinMappingsService(SharedServiceManager sharedServiceManager) {
this.sharedServiceManager = sharedServiceManager;
}
public static File getMixinMappingFile(Project project, SourceSet sourceSet) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
File mixinMapping = new File(extension.getFiles().getProjectBuildCache(), "mixin-map-" + extension.getMappingsProvider().mappingsIdentifier() + "." + sourceSet.getName() + ".tiny");
getService(SharedServiceManager.get(project), extension.getMappingsProvider()).mixinMappings.add(mixinMapping);
return mixinMapping;
}
static synchronized MixinMappingsService getService(SharedServiceManager sharedServiceManager, MappingsProviderImpl mappingsProvider) {
return sharedServiceManager.getOrCreateService("MixinMappings-" + mappingsProvider.mappingsIdentifier(), () -> new MixinMappingsService(sharedServiceManager));
}
IMappingProvider getMappingProvider(String from, String to) {
return out -> {
for (File mixinMapping : mixinMappings) {
if (!mixinMapping.exists()) continue;
MappingsService service = MappingsService.create(sharedServiceManager, mixinMapping.getAbsolutePath(), mixinMapping.toPath(), from, to, false);
service.getMappingsProvider().load(out);
}
};
}
}

View File

@@ -52,16 +52,15 @@ import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.lorenztiny.TinyMappingsReader;
public final class SourceRemapperService implements SharedService {
public static synchronized SourceRemapperService create(RemapSourcesJarTask task) {
public static synchronized SourceRemapperService create(SharedServiceManager serviceManager, RemapSourcesJarTask task) {
final Project project = task.getProject();
final String to = task.getTargetNamespace().get();
final String from = task.getSourceNamespace().get();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
final String id = extension.getMappingsProvider().getBuildServiceName("sourceremapper", from, to);
final String id = extension.getMappingConfiguration().getBuildServiceName("sourceremapper", from, to);
return sharedServiceManager.getOrCreateService(id, () ->
new SourceRemapperService(MappingsService.createDefault(project, from, to), task.getClasspath()
return serviceManager.getOrCreateService(id, () ->
new SourceRemapperService(MappingsService.createDefault(project, serviceManager, from, to), task.getClasspath()
));
}

View File

@@ -37,10 +37,15 @@ import java.util.Objects;
import java.util.StringJoiner;
import org.gradle.api.Project;
import org.gradle.api.invocation.Gradle;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.build.mixin.AnnotationProcessorInvoker;
import net.fabricmc.loom.task.AbstractRemapJarTask;
import net.fabricmc.loom.util.gradle.GradleUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
import net.fabricmc.loom.util.kotlin.KotlinClasspath;
import net.fabricmc.loom.util.kotlin.KotlinClasspathService;
import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader;
@@ -51,18 +56,17 @@ import net.fabricmc.tinyremapper.InputTag;
import net.fabricmc.tinyremapper.TinyRemapper;
public class TinyRemapperService implements SharedService {
public static synchronized TinyRemapperService getOrCreate(AbstractRemapJarTask remapJarTask) {
public static synchronized TinyRemapperService getOrCreate(SharedServiceManager serviceManager, AbstractRemapJarTask remapJarTask) {
final Project project = remapJarTask.getProject();
final String to = remapJarTask.getTargetNamespace().get();
final String from = remapJarTask.getSourceNamespace().get();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get();
final @Nullable KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(project);
final @Nullable KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(serviceManager, project);
// Generates an id that is used to share the remapper across projects. This tasks in the remap jar task name to handle custom remap jar tasks separately.
final var joiner = new StringJoiner(":");
joiner.add(extension.getMappingsProvider().getBuildServiceName("remapJarService", from, to));
joiner.add(extension.getMappingConfiguration().getBuildServiceName("remapJarService", from, to));
joiner.add(remapJarTask.getName());
if (kotlinClasspathService != null) {
@@ -75,12 +79,12 @@ public class TinyRemapperService implements SharedService {
final String id = joiner.toString();
TinyRemapperService service = sharedServiceManager.getOrCreateService(id, () -> {
TinyRemapperService service = serviceManager.getOrCreateService(id, () -> {
List<IMappingProvider> mappings = new ArrayList<>();
mappings.add(MappingsService.createDefault(project, from, to).getMappingsProvider());
mappings.add(MappingsService.createDefault(project, serviceManager, from, to).getMappingsProvider());
if (legacyMixin) {
mappings.add(MixinMappingsService.getService(SharedServiceManager.get(project), extension.getMappingsProvider()).getMappingProvider(from, to));
mappings.add(gradleMixinMappingProvider(serviceManager, project.getGradle(), extension.getMappingConfiguration().mappingsIdentifier, from, to));
}
return new TinyRemapperService(mappings, !legacyMixin, kotlinClasspathService);
@@ -91,6 +95,29 @@ public class TinyRemapperService implements SharedService {
return service;
}
// Add all of the mixin mappings from all loom projects.
private static IMappingProvider gradleMixinMappingProvider(SharedServiceManager serviceManager, Gradle gradle, String mappingId, String from, String to) {
return out -> GradleUtils.allLoomProjects(gradle, project -> {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
if (!mappingId.equals(extension.getMappingConfiguration().mappingsIdentifier)) {
// Only find mixin mappings that are from other projects with the same mapping id.
return;
}
for (SourceSet sourceSet : SourceSetHelper.getSourceSets(project)) {
final File mixinMappings = AnnotationProcessorInvoker.getMixinMappingsForSourceSet(project, sourceSet);
if (!mixinMappings.exists()) {
continue;
}
MappingsService service = MappingsService.create(serviceManager, mixinMappings.getAbsolutePath(), mixinMappings.toPath(), from, to, false);
service.getMappingsProvider().load(out);
}
});
}
private TinyRemapper tinyRemapper;
@Nullable
private KotlinRemapperClassloader kotlinRemapperClassloader;

View File

@@ -31,8 +31,8 @@ import org.gradle.api.logging.LogLevel;
import org.gradle.api.logging.configuration.WarningMode;
public interface DeprecationHelper {
default void replaceWithInLoom0_12(String currentName, String newName) {
toBeRemovedIn(currentName, newName, "Loom 0.12");
default void replaceWithInLoom2_0(String currentName, String newName) {
toBeRemovedIn(currentName, newName, "Loom 2.0");
}
default void toBeRemovedIn(String currentName, String newName, String removalVersion) {
@@ -49,13 +49,6 @@ public interface DeprecationHelper {
public ProjectBased(Project project) {
this.project = project;
project.getGradle().buildFinished(buildResult -> {
if (usingDeprecatedApi.get()) {
project.getLogger().lifecycle("Deprecated Loom APIs were used in this build, making it incompatible with future versions of Loom. "
+ "Use Gradle warning modes to control the verbosity of the warnings.");
}
});
}
@Override

View File

@@ -45,19 +45,22 @@ import org.slf4j.Logger;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.lorenztiny.TinyMappingsReader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class SourceRemapper {
private final Project project;
private final SharedServiceManager serviceManager;
private final boolean toNamed;
private final List<Consumer<ProgressLogger>> remapTasks = new ArrayList<>();
private Mercury mercury;
public SourceRemapper(Project project, boolean toNamed) {
public SourceRemapper(Project project, SharedServiceManager serviceManager, boolean toNamed) {
this.project = project;
this.serviceManager = serviceManager;
this.toNamed = toNamed;
}
@@ -158,11 +161,11 @@ public class SourceRemapper {
}
LoomGradleExtension extension = LoomGradleExtension.get(project);
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
MappingConfiguration mappingConfiguration = extension.getMappingConfiguration();
MappingSet mappings = extension.getOrCreateSrcMappingCache(toNamed ? 1 : 0, () -> {
try {
MemoryMappingTree m = mappingsProvider.getMappings();
MemoryMappingTree m = mappingConfiguration.getMappingsService(serviceManager).getMappingTree();
project.getLogger().info(":loading " + (toNamed ? "intermediary -> named" : "named -> intermediary") + " source mappings");
return new TinyMappingsReader(m, toNamed ? MappingsNamespace.INTERMEDIARY.toString() : MappingsNamespace.NAMED.toString(), toNamed ? MappingsNamespace.NAMED.toString() : MappingsNamespace.INTERMEDIARY.toString()).read();
} catch (Exception e) {

View File

@@ -36,6 +36,7 @@ import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
@@ -60,13 +61,13 @@ public final class TinyRemapperHelper {
private TinyRemapperHelper() {
}
public static TinyRemapper getTinyRemapper(Project project, String fromM, String toM) throws IOException {
return getTinyRemapper(project, fromM, toM, false, (builder) -> { });
public static TinyRemapper getTinyRemapper(Project project, SharedServiceManager serviceManager, String fromM, String toM) throws IOException {
return getTinyRemapper(project, serviceManager, fromM, toM, false, (builder) -> { });
}
public static TinyRemapper getTinyRemapper(Project project, String fromM, String toM, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException {
public static TinyRemapper getTinyRemapper(Project project, SharedServiceManager serviceManager, String fromM, String toM, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException {
LoomGradleExtension extension = LoomGradleExtension.get(project);
MemoryMappingTree mappingTree = extension.getMappingsProvider().getMappings();
MemoryMappingTree mappingTree = extension.getMappingConfiguration().getMappingsService(serviceManager).getMappingTree();
if (fixRecords && !mappingTree.getSrcNamespace().equals(fromM)) {
throw new IllegalStateException("Mappings src namespace must match remap src namespace");

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2018-2022 FabricMC
* 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
@@ -22,40 +22,40 @@
* SOFTWARE.
*/
package net.fabricmc.loom.build;
package net.fabricmc.loom.util.fmj;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readString;
import com.google.gson.JsonArray;
import java.util.List;
import java.util.Objects;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class MixinRefmapHelper {
private MixinRefmapHelper() { }
public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJsonV1, FabricModJsonV2 {
protected final JsonObject jsonObject;
private final FabricModJsonSource source;
private static final String FABRIC_MOD_JSON = "fabric.mod.json";
protected FabricModJson(JsonObject jsonObject, FabricModJsonSource source) {
this.jsonObject = Objects.requireNonNull(jsonObject);
this.source = Objects.requireNonNull(source);
}
@NotNull
public static Collection<String> getMixinConfigurationFiles(JsonObject fabricModJson) {
JsonArray mixins = fabricModJson.getAsJsonArray("mixins");
public abstract int getVersion();
if (mixins == null) {
return Collections.emptyList();
}
public String getId() {
return readString(jsonObject, "id");
}
return StreamSupport.stream(mixins.spliterator(), false)
.map(e -> {
if (e instanceof JsonPrimitive str) {
return str.getAsString();
} else if (e instanceof JsonObject obj) {
return obj.get("config").getAsString();
} else {
throw new RuntimeException("Incorrect fabric.mod.json format");
}
}).collect(Collectors.toSet());
@Nullable
public abstract JsonElement getCustom(String key);
public abstract List<String> getMixinConfigurations();
public abstract List<String> getClassTweakers(ModEnvironment modEnvironment);
public final FabricModJsonSource getSource() {
return source;
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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.fmj;
import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readInt;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import com.google.gson.JsonObject;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
public final class FabricModJsonFactory {
private static final String FABRIC_MOD_JSON = "fabric.mod.json";
private FabricModJsonFactory() {
}
@VisibleForTesting
public static FabricModJson create(JsonObject jsonObject, FabricModJsonSource source) {
int schemaVersion = 0;
if (jsonObject.has("schemaVersion")) {
// V0 had no schemaVersion key.
schemaVersion = readInt(jsonObject, "schemaVersion");
}
return switch (schemaVersion) {
case 0 -> new FabricModJsonV0(jsonObject, source);
case 1 -> new FabricModJsonV1(jsonObject, source);
case 2 -> new FabricModJsonV2(jsonObject, source);
default -> throw new UnsupportedOperationException(String.format("This version of fabric-loom doesn't support the newer fabric.mod.json schema version of (%s) Please update fabric-loom to be able to read this.", schemaVersion));
};
}
public static FabricModJson createFromZip(Path zipPath) {
try {
return create(ZipUtils.unpackGson(zipPath, FABRIC_MOD_JSON, JsonObject.class), new FabricModJsonSource.ZipSource(zipPath));
} catch (IOException e) {
throw new UncheckedIOException("Failed to read fabric.mod.json file in zip: " + zipPath, e);
}
}
@Nullable
public static FabricModJson createFromZipNullable(Path zipPath) {
JsonObject jsonObject;
try {
jsonObject = ZipUtils.unpackGsonNullable(zipPath, FABRIC_MOD_JSON, JsonObject.class);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read zip: " + zipPath, e);
}
if (jsonObject == null) {
return null;
}
return create(jsonObject, new FabricModJsonSource.ZipSource(zipPath));
}
public static Optional<FabricModJson> createFromZipOptional(Path zipPath) {
return Optional.ofNullable(createFromZipNullable(zipPath));
}
public static FabricModJson createFromDirectory(Path directory) throws IOException {
final Path path = directory.resolve(FABRIC_MOD_JSON);
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.DirectorySource(directory));
}
}
@Nullable
public static FabricModJson createFromSourceSetsNullable(SourceSet... sourceSets) throws IOException {
final File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, sourceSets);
if (file == null) {
return null;
}
try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.SourceSetSource(sourceSets));
}
}
public static boolean isModJar(File file) {
return isModJar(file.toPath());
}
public static boolean isModJar(Path input) {
return ZipUtils.contains(input, FABRIC_MOD_JSON);
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.fmj;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
/**
* A mod may be a zip, directory or Gradle {@link SourceSet}
* This abstraction allows easily reading a contained file from the mod.
*/
public interface FabricModJsonSource {
byte[] read(String path) throws IOException;
record ZipSource(Path zipPath) implements FabricModJsonSource {
@Override
public byte[] read(String path) throws IOException {
return ZipUtils.unpack(zipPath, path);
}
}
record DirectorySource(Path directoryPath) implements FabricModJsonSource {
@Override
public byte[] read(String path) throws IOException {
return Files.readAllBytes(directoryPath.resolve(path));
}
}
record SourceSetSource(SourceSet... sourceSets) implements FabricModJsonSource {
@Override
public byte[] read(String path) throws IOException {
final File file = SourceSetHelper.findFirstFileInResource(path, sourceSets);
if (file == null) {
throw new FileNotFoundException("Could not find: " + path);
}
return Files.readAllBytes(file.toPath());
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.fmj;
import java.util.Locale;
import java.util.function.Predicate;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
final class FabricModJsonUtils {
private FabricModJsonUtils() {
}
public static String readString(JsonObject jsonObject, String key) {
final JsonElement element = getElement(jsonObject, key);
ensurePrimitive(element, JsonPrimitive::isString, key);
return element.getAsString();
}
public static int readInt(JsonObject jsonObject, String key) {
final JsonElement element = getElement(jsonObject, key);
ensurePrimitive(element, JsonPrimitive::isNumber, key);
return element.getAsInt();
}
private static JsonElement getElement(JsonObject jsonObject, String key) {
final JsonElement element = jsonObject.get(key);
if (element == null) {
throw new ParseException("Unable to find json element for key (%s)", key);
}
return element;
}
private static void ensurePrimitive(JsonElement jsonElement, Predicate<JsonPrimitive> predicate, String key) {
if (!jsonElement.isJsonPrimitive() || !predicate.test(jsonElement.getAsJsonPrimitive())) {
throw new ParseException("Unexpected primitive type for key (%s)", key);
}
}
static class ParseException extends RuntimeException {
ParseException(String message, Object... args) {
super(String.format(Locale.ROOT, message, args));
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.fmj;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.jetbrains.annotations.Nullable;
@Deprecated
public final class FabricModJsonV0 extends FabricModJson {
FabricModJsonV0(JsonObject jsonObject, FabricModJsonSource source) {
super(jsonObject, source);
}
@Override
public int getVersion() {
return 0;
}
@Override
@Nullable
public JsonElement getCustom(String key) {
return null;
}
@Override
public List<String> getMixinConfigurations() {
final JsonObject mixinsObject = jsonObject.getAsJsonObject("mixins");
if (mixinsObject == null) {
return Collections.emptyList();
}
final List<String> mixins = new ArrayList<>();
for (String key : mixinsObject.keySet()) {
final JsonElement jsonElement = mixinsObject.get(key);
if (jsonElement instanceof JsonArray jsonArray) {
for (JsonElement arrayElement : jsonArray) {
if (arrayElement instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isString()) {
mixins.add(jsonPrimitive.getAsString());
} else {
throw new FabricModJsonUtils.ParseException("Expected entries in mixin %s to be an array of strings", key);
}
}
} else if (jsonElement instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isString()) {
mixins.add(jsonPrimitive.getAsString());
} else {
throw new FabricModJsonUtils.ParseException("Expected mixin %s to be a string or an array of strings", key);
}
}
return Collections.unmodifiableList(mixins);
}
@Override
public List<String> getClassTweakers(ModEnvironment modEnvironment) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.fmj;
import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readString;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.jetbrains.annotations.Nullable;
public final class FabricModJsonV1 extends FabricModJson {
FabricModJsonV1(JsonObject jsonObject, FabricModJsonSource source) {
super(jsonObject, source);
}
@Override
public int getVersion() {
return 1;
}
@Override
@Nullable
public JsonElement getCustom(String key) {
return getCustom(jsonObject, key);
}
static JsonElement getCustom(JsonObject jsonObject, String key) {
if (!jsonObject.has("custom")) {
return null;
}
final JsonObject custom = jsonObject.getAsJsonObject("custom");
if (!custom.has(key)) {
return null;
}
return custom.get(key);
}
@Override
public List<String> getMixinConfigurations() {
final JsonArray mixinArray = jsonObject.getAsJsonArray("mixins");
if (mixinArray == null) {
return Collections.emptyList();
}
return StreamSupport.stream(mixinArray.spliterator(), false)
.map(FabricModJsonV1::readMixinElement)
.collect(Collectors.toList());
}
private static String readMixinElement(JsonElement jsonElement) {
if (jsonElement instanceof JsonPrimitive str) {
return str.getAsString();
} else if (jsonElement instanceof JsonObject obj) {
return obj.get("config").getAsString();
} else {
throw new FabricModJsonUtils.ParseException("Expected mixin element to be an object or string");
}
}
@Override
public List<String> getClassTweakers(ModEnvironment modEnvironment) {
if (!jsonObject.has("accessWidener")) {
return Collections.emptyList();
}
return List.of(readString(jsonObject, "accessWidener"));
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.fmj;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Experimental
public final class FabricModJsonV2 extends FabricModJson {
FabricModJsonV2(JsonObject jsonObject, FabricModJsonSource source) {
super(jsonObject, source);
}
@Override
public int getVersion() {
return 2;
}
@Override
@Nullable
public JsonElement getCustom(String key) {
return FabricModJsonV1.getCustom(jsonObject, key);
}
@Override
public List<String> getMixinConfigurations() {
if (!jsonObject.has("mixins")) {
return Collections.emptyList();
}
return getConditionalConfigs(jsonObject.get("mixins"), ModEnvironment.UNIVERSAL);
}
@Override
public List<String> getClassTweakers(ModEnvironment modEnvironment) {
if (!jsonObject.has("classTweakers")) {
return Collections.emptyList();
}
return getConditionalConfigs(jsonObject.get("classTweakers"), modEnvironment);
}
private List<String> getConditionalConfigs(JsonElement jsonElement, ModEnvironment modEnvironment) {
final List<String> values = new ArrayList<>();
if (jsonElement instanceof JsonArray jsonArray) {
for (JsonElement arrayElement : jsonArray) {
final String value = readConditionalConfig(arrayElement, modEnvironment);
if (value != null) {
values.add(value);
}
}
} else if (jsonElement instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isString()) {
final String value = readConditionalConfig(jsonPrimitive, modEnvironment);
if (value != null) {
values.add(value);
}
} else {
throw new FabricModJsonUtils.ParseException("Must be a string or array of strings");
}
return values;
}
@Nullable
private String readConditionalConfig(JsonElement jsonElement, ModEnvironment modEnvironment) {
if (jsonElement instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isString()) {
return jsonElement.getAsString();
} else if (jsonElement instanceof JsonObject jsonObject) {
final String config = FabricModJsonUtils.readString(jsonObject, "config");
if (!validForEnvironment(jsonObject, modEnvironment)) {
return null;
}
return config;
} else {
throw new FabricModJsonUtils.ParseException("Must be a string or an object");
}
}
private boolean validForEnvironment(JsonObject jsonObject, ModEnvironment modEnvironment) {
if (!jsonObject.has("environment")) {
// Default enabled for all envs.
return true;
}
if (!(jsonObject.get("environment") instanceof JsonPrimitive jsonPrimitive) || !jsonPrimitive.isString()) {
throw new FabricModJsonUtils.ParseException("Environment must be a string");
}
final String environment = jsonPrimitive.getAsString();
return switch (environment) {
case "*" -> true;
case "client" -> modEnvironment.isClient();
case "server" -> modEnvironment.isServer();
default -> throw new FabricModJsonUtils.ParseException("Invalid environment type: " + environment);
};
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.fmj;
public enum ModEnvironment {
UNIVERSAL(true, true),
CLIENT(true, false),
SERVER(false, true);
private final boolean client;
private final boolean server;
ModEnvironment(boolean client, boolean server) {
this.client = client;
this.server = server;
}
public boolean isClient() {
return client;
}
public boolean isServer() {
return server;
}
}

View File

@@ -24,7 +24,10 @@
package net.fabricmc.loom.util.gradle;
import java.util.function.Consumer;
import org.gradle.api.Project;
import org.gradle.api.invocation.Gradle;
public final class GradleUtils {
private GradleUtils() {
@@ -41,4 +44,12 @@ public final class GradleUtils {
afterEvaluate.run();
});
}
public static void allLoomProjects(Gradle gradle, Consumer<Project> consumer) {
gradle.allprojects(project -> {
if (project.getPluginManager().hasPlugin("fabric-loom")) {
consumer.accept(project);
}
});
}
}

View File

@@ -32,6 +32,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.xml.xpath.XPath;
@@ -42,6 +43,7 @@ import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.SourceSetOutput;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.Nullable;
@@ -59,20 +61,23 @@ public final class SourceSetHelper {
private SourceSetHelper() {
}
public static SourceSetContainer getSourceSets(Project project) {
final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class);
return javaExtension.getSourceSets();
}
/**
* 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()
return getSourceSets(project).stream()
.anyMatch(test -> test == sourceSet); // Ensure we have an identical reference
}
public static SourceSet getSourceSetByName(String name, Project project) {
final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class);
return javaExtension.getSourceSets().getByName(name);
return getSourceSets(project).getByName(name);
}
public static SourceSet getMainSourceSet(Project project) {
@@ -80,8 +85,7 @@ public final class SourceSetHelper {
}
public static SourceSet createSourceSet(String name, Project project) {
final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class);
return javaExtension.getSourceSets().create(name);
return getSourceSets(project).create(name);
}
/**
@@ -226,4 +230,32 @@ public final class SourceSetHelper {
return Collections.singletonList(new File(binDir, reference.sourceSet().getName()));
}
@Nullable
public static File findFileInResource(SourceSet sourceSet, String path) {
Objects.requireNonNull(sourceSet);
Objects.requireNonNull(path);
try {
return sourceSet.getResources()
.matching(patternFilterable -> patternFilterable.include(path))
.getSingleFile();
} catch (IllegalStateException e) {
// File not found
return null;
}
}
@Nullable
public static File findFirstFileInResource(String path, SourceSet... sourceSets) {
for (SourceSet sourceSet : sourceSets) {
File file = findFileInResource(sourceSet, path);
if (file != null) {
return file;
}
}
return null;
}
}

View File

@@ -39,17 +39,16 @@ import net.fabricmc.loom.util.service.SharedServiceManager;
public record KotlinClasspathService(Set<URL> classpath, String version) implements KotlinClasspath, SharedService {
@Nullable
public static KotlinClasspathService getOrCreateIfRequired(Project project) {
public static KotlinClasspathService getOrCreateIfRequired(SharedServiceManager sharedServiceManager, Project project) {
if (!KotlinPluginUtils.hasKotlinPlugin(project)) {
return null;
}
return getOrCreate(project, KotlinPluginUtils.getKotlinPluginVersion(project), KotlinPluginUtils.getKotlinMetadataVersion());
return getOrCreate(sharedServiceManager, project, KotlinPluginUtils.getKotlinPluginVersion(project), KotlinPluginUtils.getKotlinMetadataVersion());
}
public static synchronized KotlinClasspathService getOrCreate(Project project, String kotlinVersion, String kotlinMetadataVersion) {
public static synchronized KotlinClasspathService getOrCreate(SharedServiceManager sharedServiceManager, Project project, String kotlinVersion, String kotlinMetadataVersion) {
final String id = "kotlinclasspath:%s:%s".formatted(kotlinVersion, kotlinMetadataVersion);
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
return sharedServiceManager.getOrCreateService(id, () -> create(project, kotlinVersion, kotlinMetadataVersion));
}

View File

@@ -0,0 +1,94 @@
/*
* 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.service;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import org.gradle.api.Task;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.gradle.build.event.BuildEventsListenerRegistry;
import org.gradle.tooling.events.OperationCompletionListener;
import org.gradle.tooling.events.task.TaskOperationDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class BuildSharedServiceManager implements BuildService<BuildServiceParameters.None> {
private static final Logger LOGGER = LoggerFactory.getLogger(BuildSharedServiceManager.class);
private static final String NAME = "loom:sharedServiceManager";
private SharedServiceManager sharedServiceManager = new BuildSharedServiceManagerImpl();
private final AtomicInteger refCount = new AtomicInteger(0);
public static Provider<BuildSharedServiceManager> createForTask(Task task, BuildEventsListenerRegistry buildEventsListenerRegistry) {
Provider<BuildSharedServiceManager> provider = task.getProject().getGradle().getSharedServices().registerIfAbsent(NAME, BuildSharedServiceManager.class, spec -> {
});
task.usesService(provider);
final BuildSharedServiceManager serviceManager = provider.get();
buildEventsListenerRegistry.onTaskCompletion(registerTaskCompletion(task, serviceManager::onFinish));
int count = serviceManager.refCount.incrementAndGet();
LOGGER.debug("Creating shared service manager provider for task: {} count: {}", task.getName(), count);
return provider;
}
public BuildSharedServiceManager() {
LOGGER.debug("New BuildSharedServiceManager instance");
}
public SharedServiceManager get() {
LOGGER.debug("Shared build service get");
return Objects.requireNonNull(sharedServiceManager);
}
private void onFinish() {
int count = refCount.decrementAndGet();
LOGGER.debug("Build service finish. count: {}", count);
if (count == 0) {
sharedServiceManager.onFinish();
sharedServiceManager = null;
} else if (count < 0) {
throw new IllegalStateException();
}
}
private static Provider<OperationCompletionListener> registerTaskCompletion(Task task, Runnable runnable) {
return task.getProject().provider(() -> event -> {
if (event.getDescriptor() instanceof TaskOperationDescriptor taskDescriptor) {
if (taskDescriptor.getTaskPath().equals(task.getPath())) {
runnable.run();
}
}
});
}
private static final class BuildSharedServiceManagerImpl extends SharedServiceManager {
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.service;
public final class ScopedSharedServiceManager extends SharedServiceManager implements AutoCloseable {
public ScopedSharedServiceManager() {
}
@Override
public void close() {
onFinish();
}
}

View File

@@ -26,38 +26,26 @@ package net.fabricmc.loom.util.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.gradle.BuildResult;
import org.gradle.api.Project;
import org.gradle.api.invocation.Gradle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A simple manager for {@link SharedService} to be used across gradle (sub) projects.
* This is a basic replacement for gradle's build service api.
*/
public final class SharedServiceManager {
private static final Map<Gradle, SharedServiceManager> SERVICE_FACTORY_MAP = new ConcurrentHashMap<>();
private final Gradle gradle;
private final Map<String, SharedService> sharedServiceMap = new ConcurrentHashMap<>();
public abstract class SharedServiceManager {
private static final Logger LOGGER = LoggerFactory.getLogger(BuildSharedServiceManager.class);
private final Map<String, SharedService> sharedServiceMap = new HashMap<>();
private boolean shutdown = false;
private SharedServiceManager(Gradle gradle) {
this.gradle = gradle;
this.gradle.buildFinished(this::onFinish);
}
public static SharedServiceManager get(Project project) {
return get(project.getGradle());
}
public static SharedServiceManager get(Gradle gradle) {
return SERVICE_FACTORY_MAP.computeIfAbsent(gradle, SharedServiceManager::new);
SharedServiceManager() {
LOGGER.info("Creating new SharedServiceManager({})", hashCode());
}
public <S extends SharedService> S getOrCreateService(String id, Supplier<S> function) {
@@ -70,6 +58,7 @@ public final class SharedServiceManager {
S sharedService = (S) sharedServiceMap.get(id);
if (sharedService == null) {
LOGGER.debug("Creating service for {}", id);
sharedService = function.get();
sharedServiceMap.put(id, sharedService);
}
@@ -78,12 +67,12 @@ public final class SharedServiceManager {
}
}
private void onFinish(BuildResult buildResult) {
protected void onFinish() {
synchronized (sharedServiceMap) {
shutdown = true;
}
SERVICE_FACTORY_MAP.remove(gradle);
LOGGER.info("Closing SharedServiceManager({})", hashCode());
final List<IOException> exceptionList = new ArrayList<>();
@@ -97,14 +86,14 @@ public final class SharedServiceManager {
sharedServiceMap.clear();
// This is required to ensure that mercury releases all of the file handles.
System.gc();
if (!exceptionList.isEmpty()) {
// Done to try and close all the services.
RuntimeException exception = new RuntimeException("Failed to close all shared services");
exceptionList.forEach(exception::addSuppressed);
throw exception;
}
// This is required to ensure that mercury releases all of the file handles.
System.gc();
}
}

View File

@@ -28,7 +28,6 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
// Massive hack to work around WorkerExecutor.noIsolation() doing isolation checks.
@@ -38,12 +37,10 @@ public final class UnsafeWorkQueueHelper {
private UnsafeWorkQueueHelper() {
}
public static String create(Project project, SharedService service) {
public static String create(SharedService service) {
final String uuid = UUID.randomUUID().toString();
SERVICE_MAP.put(uuid, service);
// Ensure we don't make a mess if things go wrong.
project.getGradle().buildFinished(buildResult -> SERVICE_MAP.remove(uuid));
return uuid;
}

View File

@@ -27,7 +27,7 @@ package net.fabricmc.loom.test
import org.gradle.util.GradleVersion
class LoomTestConstants {
private final static String NIGHTLY_VERSION = "8.0-20221001011953+0000"
private final static String NIGHTLY_VERSION = "8.0-20221022221212+0000"
private final static boolean NIGHTLY_EXISTS = nightlyExists(NIGHTLY_VERSION)
public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion()

View File

@@ -43,7 +43,7 @@ class FabricAPIBenchmark implements GradleProjectTestTrait {
allowExistingRepo: true,
repo: "https://github.com/FabricMC/fabric.git",
commit: "71b634e5b7845296b11be3fa6545f4fbfacc017f",
commit: "5f243a8b7849eac4b30cd876a22a127797a1c406",
patch: "fabric_api"
)

View File

@@ -0,0 +1,105 @@
/*
* 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.test.unit.fmj
import com.google.gson.Gson
import com.google.gson.JsonObject
import net.fabricmc.loom.util.Constants
import net.fabricmc.loom.util.fmj.FabricModJsonFactory
import net.fabricmc.loom.util.fmj.FabricModJsonSource
import net.fabricmc.loom.util.fmj.ModEnvironment
import org.intellij.lang.annotations.Language
import spock.lang.Specification
class FabricModJsonV0Test extends Specification {
// I think this is the old v0 format ¯\_(ツ)_/¯
@Language("json")
static String JSON = """
{
"id": "example-mod-id",
"name": "Example mod name for testing",
"version": "1.0.0",
"side": "universal",
"initializers": [
],
"mixins": {
"client": "mixins.client.json",
"common": [
"mixins.common.json"
]
}
}
"""
static JsonObject JSON_OBJECT = new Gson().fromJson(JSON, JsonObject.class)
def "version"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.version == 0
}
def "id"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.id == "example-mod-id"
}
def "mixins"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.mixinConfigurations == ["mixins.client.json", "mixins.common.json"]
}
// Not supported in this version
def "injected interfaces"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
def jsonObject = fmj.getCustom(Constants.CustomModJsonKeys.INJECTED_INTERFACE)
then:
jsonObject == null
}
// Not supported in this version
def "class tweaker"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.getClassTweakers(ModEnvironment.UNIVERSAL) == []
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.test.unit.fmj
import com.google.gson.Gson
import com.google.gson.JsonObject
import net.fabricmc.loom.util.Constants
import net.fabricmc.loom.util.fmj.FabricModJsonFactory
import net.fabricmc.loom.util.fmj.FabricModJsonSource
import net.fabricmc.loom.util.fmj.ModEnvironment
import org.intellij.lang.annotations.Language
import spock.lang.Specification
class FabricModJsonV1Test extends Specification {
@Language("json")
static String JSON = """
{
"schemaVersion": 1,
"id": "example-mod-id",
"name": "Example mod name for testing",
"version": "1.0.0",
"environment": "client",
"license": "Apache-2.0",
"mixins": [
{
"config": "test.client.mixins.json",
"environment": "client"
},
"test.mixins.json"
],
"accessWidener" : "modid.accesswidener",
"custom": {
"loom:injected_interfaces": {
"net/minecraft/class_123": ["net/test/TestClass"]
}
}
}
"""
static JsonObject JSON_OBJECT = new Gson().fromJson(JSON, JsonObject.class)
def "version"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.version == 1
}
def "id"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.id == "example-mod-id"
}
def "mixins"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.mixinConfigurations == ["test.client.mixins.json", "test.mixins.json"]
}
def "injected interfaces"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
def jsonObject = fmj.getCustom(Constants.CustomModJsonKeys.INJECTED_INTERFACE)
then:
jsonObject instanceof JsonObject
jsonObject.has("net/minecraft/class_123")
}
def "access widener"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.getClassTweakers(ModEnvironment.SERVER) == ["modid.accesswidener"]
}
}

View File

@@ -0,0 +1,142 @@
/*
* 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.test.unit.fmj
import com.google.gson.Gson
import com.google.gson.JsonObject
import net.fabricmc.loom.util.Constants
import net.fabricmc.loom.util.fmj.FabricModJsonFactory
import net.fabricmc.loom.util.fmj.FabricModJsonSource
import net.fabricmc.loom.util.fmj.ModEnvironment
import org.intellij.lang.annotations.Language
import spock.lang.Specification
class FabricModJsonV2Test extends Specification {
@Language("json")
static String JSON = """
{
"schemaVersion": 2,
"id": "example-mod-id",
"name": "Example mod name for testing",
"version": "1.0.0",
"environment": "client",
"license": "Apache-2.0",
"mixins": [
{
"config": "test.client.mixins.json",
"environment": "client"
},
{
"config": "test.server.mixins.json",
"environment": "server"
},
"test.mixins.json"
],
"classTweakers": [
{
"config": "client.ct",
"environment": "client"
},
{
"config": "server.ct",
"environment": "server"
},
"universal.ct"
],
"custom": {
"loom:injected_interfaces": {
"net/minecraft/class_123": ["net/test/TestClass"]
}
}
}
"""
static JsonObject JSON_OBJECT = new Gson().fromJson(JSON, JsonObject.class)
def "version"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.version == 2
}
def "id"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.id == "example-mod-id"
}
def "mixins"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.mixinConfigurations == ["test.client.mixins.json", "test.server.mixins.json", "test.mixins.json"]
}
def "injected interfaces"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
def jsonObject = fmj.getCustom(Constants.CustomModJsonKeys.INJECTED_INTERFACE)
then:
jsonObject instanceof JsonObject
jsonObject.has("net/minecraft/class_123")
}
def "universal class tweakers"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.getClassTweakers(ModEnvironment.UNIVERSAL) == ["client.ct", "server.ct", "universal.ct"]
}
def "client class tweakers"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.getClassTweakers(ModEnvironment.CLIENT) == ["client.ct", "universal.ct"]
}
def "server class tweakers"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.getClassTweakers(ModEnvironment.SERVER) == ["server.ct", "universal.ct"]
}
}