mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-04-02 21:47:42 -05:00
Make McpExecutor and GenerateForgePatchedSourcesTask config cache-compatible
- Added ForgeToolService to run tools in tasks - Added AccessTransformerService to simplify applying ATs and to make the tool config cache-compatible - Split McpExecutor into an execution stage (McpE) and a setup stage (McpExecutorBuilder) - McpExecutor is now a service - All StepLogic implementations are now services
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package dev.architectury.loom.forge.tool;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import dev.architectury.loom.forge.UserdevConfig;
|
||||
import dev.architectury.loom.util.TempFiles;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
||||
import net.fabricmc.loom.util.DependencyDownloader;
|
||||
import net.fabricmc.loom.util.FileSystemUtil;
|
||||
import net.fabricmc.loom.util.LoomVersions;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
/**
|
||||
* A service that executes the access transformer tool.
|
||||
* The tool information and the AT files are specified in the options.
|
||||
*/
|
||||
public final class AccessTransformerService extends Service<AccessTransformerService.Options> {
|
||||
public static final ServiceType<Options, AccessTransformerService> TYPE = new ServiceType<>(Options.class, AccessTransformerService.class);
|
||||
|
||||
public interface Options extends Service.Options {
|
||||
@InputFiles
|
||||
ConfigurableFileCollection getAccessTransformers();
|
||||
|
||||
@Input
|
||||
Property<String> getMainClass();
|
||||
|
||||
@Classpath
|
||||
ConfigurableFileCollection getClasspath();
|
||||
|
||||
@Nested
|
||||
Property<ForgeToolService.Options> getToolServiceOptions();
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(Project project, Object atFiles) {
|
||||
return TYPE.create(project, options -> {
|
||||
LoomVersions accessTransformer = chooseAccessTransformer(project);
|
||||
String mainClass = accessTransformer.equals(LoomVersions.ACCESS_TRANSFORMERS_NEO)
|
||||
? "net.neoforged.accesstransformer.cli.TransformerProcessor"
|
||||
: "net.minecraftforge.accesstransformer.TransformerProcessor";
|
||||
FileCollection classpath = new DependencyDownloader(project)
|
||||
.add(accessTransformer.mavenNotation())
|
||||
.add(LoomVersions.ASM.mavenNotation())
|
||||
.platform(LoomVersions.ACCESS_TRANSFORMERS_LOG4J_BOM.mavenNotation())
|
||||
.download();
|
||||
|
||||
options.getMainClass().set(mainClass);
|
||||
options.getAccessTransformers().from(atFiles);
|
||||
options.getClasspath().from(classpath);
|
||||
options.getToolServiceOptions().set(ForgeToolService.createOptions(project));
|
||||
});
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptionsForLoaderAts(Project project, TempFiles tempFiles) {
|
||||
final Provider<List<String>> atFiles = project.provider(() -> {
|
||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
Path userdevJar = extension.getForgeUserdevProvider().getUserdevJar().toPath();
|
||||
return extractAccessTransformers(userdevJar, extension.getForgeUserdevProvider().getConfig().ats(), tempFiles);
|
||||
});
|
||||
return createOptions(project, atFiles);
|
||||
}
|
||||
|
||||
private static List<String> extractAccessTransformers(Path jar, UserdevConfig.AccessTransformerLocation location, TempFiles tempFiles) throws IOException {
|
||||
final List<String> extracted = new ArrayList<>();
|
||||
|
||||
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar)) {
|
||||
for (Path atFile : getAccessTransformerPaths(fs, location)) {
|
||||
byte[] atBytes;
|
||||
|
||||
try {
|
||||
atBytes = Files.readAllBytes(atFile);
|
||||
} catch (NoSuchFileException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Path tmpFile = tempFiles.file("at-conf", ".cfg");
|
||||
Files.write(tmpFile, atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
extracted.add(tmpFile.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
|
||||
return extracted;
|
||||
}
|
||||
|
||||
private static List<Path> getAccessTransformerPaths(FileSystemUtil.Delegate fs, UserdevConfig.AccessTransformerLocation location) throws IOException {
|
||||
return location.visitIo(directory -> {
|
||||
Path dirPath = fs.getPath(directory);
|
||||
|
||||
try (Stream<Path> paths = Files.list(dirPath)) {
|
||||
return paths.toList();
|
||||
}
|
||||
}, paths -> paths.stream().map(fs::getPath).toList());
|
||||
}
|
||||
|
||||
public AccessTransformerService(Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
private static LoomVersions chooseAccessTransformer(Project project) {
|
||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
boolean serverBundleMetadataPresent = extension.getMinecraftProvider().getServerBundleMetadata() != null;
|
||||
|
||||
if (!serverBundleMetadataPresent) {
|
||||
return LoomVersions.ACCESS_TRANSFORMERS;
|
||||
} else if (extension.isNeoForge()) {
|
||||
MinecraftVersionMeta.JavaVersion javaVersion = extension.getMinecraftProvider().getVersionInfo().javaVersion();
|
||||
|
||||
if (javaVersion != null && javaVersion.majorVersion() >= 21) {
|
||||
return LoomVersions.ACCESS_TRANSFORMERS_NEO;
|
||||
}
|
||||
}
|
||||
|
||||
return LoomVersions.ACCESS_TRANSFORMERS_NEW;
|
||||
}
|
||||
|
||||
public void execute(Path input, Path output) throws IOException {
|
||||
final List<String> args = new ArrayList<>();
|
||||
args.add("--inJar");
|
||||
args.add(input.toAbsolutePath().toString());
|
||||
args.add("--outJar");
|
||||
args.add(output.toAbsolutePath().toString());
|
||||
|
||||
for (File atFile : getOptions().getAccessTransformers().getFiles()) {
|
||||
args.add("--atFile");
|
||||
args.add(atFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
final ForgeToolService toolService = getServiceFactory().get(getOptions().getToolServiceOptions());
|
||||
toolService.exec(spec -> {
|
||||
spec.getMainClass().set(getOptions().getMainClass());
|
||||
spec.setArgs(args);
|
||||
spec.setClasspath(getOptions().getClasspath());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,10 @@ import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.process.ExecOperations;
|
||||
import org.gradle.process.ExecResult;
|
||||
import org.gradle.process.JavaExecSpec;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
@@ -42,6 +44,13 @@ public abstract class ForgeToolExecutor {
|
||||
settings.getExecutable().set(JavaExecutableFetcher.getJavaToolchainExecutable(project));
|
||||
settings.getShowVerboseStdout().set(shouldShowVerboseStdout(project));
|
||||
settings.getShowVerboseStderr().set(shouldShowVerboseStderr(project));
|
||||
|
||||
// call this to ensure the fields aren't null
|
||||
settings.getProgramArgs();
|
||||
settings.getJvmArgs();
|
||||
settings.getMainClass();
|
||||
settings.getExecClasspath();
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -66,39 +75,46 @@ public abstract class ForgeToolExecutor {
|
||||
}
|
||||
|
||||
public static ExecResult exec(ExecOperations execOperations, Settings settings) {
|
||||
return execOperations.javaexec(spec -> {
|
||||
final @Nullable String executable = settings.getExecutable().getOrNull();
|
||||
if (executable != null) spec.setExecutable(executable);
|
||||
spec.getMainClass().set(settings.getMainClass());
|
||||
spec.setArgs(settings.getProgramArgs().get());
|
||||
spec.setJvmArgs(settings.getJvmArgs().get());
|
||||
spec.setClasspath(settings.getExecClasspath());
|
||||
return execOperations.javaexec(spec -> applyToSpec(settings, spec));
|
||||
}
|
||||
|
||||
if (settings.getShowVerboseStdout().get()) {
|
||||
spec.setStandardOutput(System.out);
|
||||
} else {
|
||||
spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM);
|
||||
}
|
||||
static void applyToSpec(Settings settings, JavaExecSpec spec) {
|
||||
final @Nullable String executable = settings.getExecutable().getOrNull();
|
||||
if (executable != null) spec.setExecutable(executable);
|
||||
final @Nullable String mainClass = settings.getMainClass().getOrNull();
|
||||
if (mainClass != null) spec.getMainClass().set(mainClass);
|
||||
spec.setArgs(settings.getProgramArgs().get());
|
||||
spec.setJvmArgs(settings.getJvmArgs().get());
|
||||
spec.setClasspath(settings.getExecClasspath());
|
||||
|
||||
if (settings.getShowVerboseStderr().get()) {
|
||||
spec.setErrorOutput(System.err);
|
||||
} else {
|
||||
spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM);
|
||||
}
|
||||
});
|
||||
if (settings.getShowVerboseStdout().get()) {
|
||||
spec.setStandardOutput(System.out);
|
||||
} else {
|
||||
spec.setStandardOutput(NullOutputStream.INSTANCE);
|
||||
}
|
||||
|
||||
if (settings.getShowVerboseStderr().get()) {
|
||||
spec.setErrorOutput(System.err);
|
||||
} else {
|
||||
spec.setErrorOutput(NullOutputStream.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Settings {
|
||||
@Input
|
||||
@Optional
|
||||
Property<String> getExecutable();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
ListProperty<String> getProgramArgs();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
ListProperty<String> getJvmArgs();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
Property<String> getMainClass();
|
||||
|
||||
@Classpath
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package dev.architectury.loom.forge.tool;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.process.ExecOperations;
|
||||
import org.gradle.process.ExecResult;
|
||||
import org.gradle.process.JavaExecSpec;
|
||||
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
/**
|
||||
* A service that can execute Forge tools in tasks.
|
||||
*/
|
||||
public final class ForgeToolService extends Service<ForgeToolService.Options> {
|
||||
public static final ServiceType<Options, ForgeToolService> TYPE = new ServiceType<>(Options.class, ForgeToolService.class);
|
||||
|
||||
public interface Options extends Service.Options {
|
||||
/**
|
||||
* The default settings from {@link ForgeToolExecutor}.
|
||||
* It contains the verbosity and JVM toolchain options that are dependent on the project state.
|
||||
*/
|
||||
@Nested
|
||||
Property<ForgeToolExecutor.Settings> getBaseSettings();
|
||||
|
||||
@Inject
|
||||
ExecOperations getExecOperations();
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(Project project) {
|
||||
return TYPE.create(project, options -> {
|
||||
options.getBaseSettings().set(ForgeToolExecutor.getDefaultSettings(project));
|
||||
});
|
||||
}
|
||||
|
||||
public ForgeToolService(Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the tool specified in the spec.
|
||||
*
|
||||
* @param configurator an action that configures the spec
|
||||
* @return the execution result
|
||||
*/
|
||||
public ExecResult exec(Action<? super JavaExecSpec> configurator) {
|
||||
return getOptions().getExecOperations().javaexec(spec -> {
|
||||
ForgeToolExecutor.applyToSpec(getOptions().getBaseSettings().get(), spec);
|
||||
configurator.execute(spec);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -215,4 +215,6 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
|
||||
default Path getPlatformMappingFile() {
|
||||
return getMappingConfiguration().getPlatformMappingFile(this);
|
||||
}
|
||||
|
||||
boolean manualRefreshDeps();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022-2023 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -42,10 +42,9 @@ import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.MoreFiles;
|
||||
import dev.architectury.at.AccessTransformSet;
|
||||
import dev.architectury.at.io.AccessTransformFormats;
|
||||
import dev.architectury.loom.forge.tool.ForgeToolValueSource;
|
||||
import dev.architectury.loom.forge.tool.AccessTransformerService;
|
||||
import dev.architectury.loom.util.TempFiles;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.logging.Logger;
|
||||
import org.gradle.api.logging.Logging;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -58,10 +57,10 @@ import net.fabricmc.loom.api.processor.SpecContext;
|
||||
import net.fabricmc.loom.build.IntermediaryNamespaces;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.DependencyDownloader;
|
||||
import net.fabricmc.loom.util.ExceptionUtil;
|
||||
import net.fabricmc.loom.util.LoomVersions;
|
||||
import net.fabricmc.loom.util.fmj.FabricModJson;
|
||||
import net.fabricmc.loom.util.service.ScopedServiceFactory;
|
||||
|
||||
public class AccessTransformerJarProcessor implements MinecraftJarProcessor<AccessTransformerJarProcessor.Spec> {
|
||||
private static final Logger LOGGER = Logging.getLogger(AccessTransformerJarProcessor.class);
|
||||
@@ -114,16 +113,14 @@ public class AccessTransformerJarProcessor implements MinecraftJarProcessor<Acce
|
||||
|
||||
@Override
|
||||
public void processJar(Path jar, Spec spec, ProcessorContext context) throws IOException {
|
||||
try (var tempFiles = new TempFiles()) {
|
||||
try (var tempFiles = new TempFiles(); var serviceFactory = new ScopedServiceFactory()) {
|
||||
LOGGER.lifecycle(":applying project access transformers");
|
||||
final Path tempInput = tempFiles.file("input", ".jar");
|
||||
Files.copy(jar, tempInput, StandardCopyOption.REPLACE_EXISTING);
|
||||
final Path atPath = mergeAndRemapAccessTransformers(context, spec.accessTransformers(), tempFiles);
|
||||
|
||||
executeAt(project, tempInput, jar, args -> {
|
||||
args.add("--atFile");
|
||||
args.add(atPath.toAbsolutePath().toString());
|
||||
});
|
||||
final AccessTransformerService service = serviceFactory.get(AccessTransformerService.createOptions(project, atPath.toAbsolutePath()));
|
||||
service.execute(tempInput, jar);
|
||||
} catch (IOException e) {
|
||||
throw ExceptionUtil.createDescriptiveWrapper(UncheckedIOException::new, "Could not access transform " + jar.toAbsolutePath(), e);
|
||||
}
|
||||
@@ -158,30 +155,6 @@ public class AccessTransformerJarProcessor implements MinecraftJarProcessor<Acce
|
||||
return name;
|
||||
}
|
||||
|
||||
public static void executeAt(Project project, Path input, Path output, AccessTransformerConfiguration configuration) throws IOException {
|
||||
LoomVersions accessTransformer = chooseAccessTransformer(project);
|
||||
String mainClass = accessTransformer == LoomVersions.ACCESS_TRANSFORMERS_NEO ? "net.neoforged.accesstransformer.cli.TransformerProcessor"
|
||||
: "net.minecraftforge.accesstransformer.TransformerProcessor";
|
||||
FileCollection classpath = new DependencyDownloader(project)
|
||||
.add(accessTransformer.mavenNotation())
|
||||
.add(LoomVersions.ASM.mavenNotation())
|
||||
.platform(LoomVersions.ACCESS_TRANSFORMERS_LOG4J_BOM.mavenNotation())
|
||||
.download();
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("--inJar");
|
||||
args.add(input.toAbsolutePath().toString());
|
||||
args.add("--outJar");
|
||||
args.add(output.toAbsolutePath().toString());
|
||||
|
||||
configuration.apply(args);
|
||||
|
||||
ForgeToolValueSource.exec(project, spec -> {
|
||||
spec.getMainClass().set(mainClass);
|
||||
spec.setArgs(args);
|
||||
spec.setClasspath(classpath);
|
||||
});
|
||||
}
|
||||
|
||||
private static LoomVersions chooseAccessTransformer(Project project) {
|
||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
boolean serverBundleMetadataPresent = extension.getMinecraftProvider().getServerBundleMetadata() != null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -24,12 +24,14 @@
|
||||
|
||||
package net.fabricmc.loom.configuration.providers.forge;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
|
||||
/**
|
||||
* A string or a variable in a Forge configuration file, or an MCPConfig step or function.
|
||||
*/
|
||||
public sealed interface ConfigValue {
|
||||
public sealed interface ConfigValue extends Serializable {
|
||||
/**
|
||||
* The variable that refers to the current MCP step's output path.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2020-2024 FabricMC
|
||||
* Copyright (c) 2020-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -32,7 +32,6 @@ import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
@@ -54,6 +53,7 @@ import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import de.oceanlabs.mcp.mcinjector.adaptors.ParameterAnnotationFixer;
|
||||
import dev.architectury.loom.forge.UserdevConfig;
|
||||
import dev.architectury.loom.forge.tool.AccessTransformerService;
|
||||
import dev.architectury.loom.forge.tool.ForgeToolValueSource;
|
||||
import dev.architectury.loom.neoforge.SidedJarIndexGenerator;
|
||||
import dev.architectury.loom.util.MappingOption;
|
||||
@@ -71,9 +71,9 @@ import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.build.IntermediaryNamespaces;
|
||||
import net.fabricmc.loom.configuration.accesstransformer.AccessTransformerJarProcessor;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigProvider;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpExecutor;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpExecutorBuilder;
|
||||
import net.fabricmc.loom.configuration.providers.forge.minecraft.ForgeMinecraftProvider;
|
||||
import net.fabricmc.loom.configuration.providers.mappings.TinyMappingsService;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||
@@ -84,6 +84,7 @@ import net.fabricmc.loom.util.ThreadingUtils;
|
||||
import net.fabricmc.loom.util.TinyRemapperHelper;
|
||||
import net.fabricmc.loom.util.ZipUtils;
|
||||
import net.fabricmc.loom.util.function.FsPathConsumer;
|
||||
import net.fabricmc.loom.util.service.ScopedServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.srg.CoreModClassRemapper;
|
||||
import net.fabricmc.loom.util.srg.InnerClassRemapper;
|
||||
@@ -189,9 +190,11 @@ public class MinecraftPatchedProvider {
|
||||
if (Files.notExists(minecraftIntermediateJar)) {
|
||||
this.dirty = true;
|
||||
|
||||
try (var tempFiles = new TempFiles()) {
|
||||
McpExecutor executor = createMcpExecutor(tempFiles.directory("loom-mcp"));
|
||||
Path output = executor.enqueue("rename").execute();
|
||||
try (var tempFiles = new TempFiles(); var serviceFactory = new ScopedServiceFactory()) {
|
||||
McpExecutorBuilder builder = createMcpExecutor(tempFiles.directory("loom-mcp"));
|
||||
builder.enqueue("rename");
|
||||
McpExecutor executor = serviceFactory.get(builder.build());
|
||||
Path output = executor.execute();
|
||||
Files.copy(output, minecraftIntermediateJar);
|
||||
}
|
||||
}
|
||||
@@ -403,60 +406,15 @@ public class MinecraftPatchedProvider {
|
||||
private void accessTransformForge() throws IOException {
|
||||
Path input = minecraftPatchedIntermediateJar;
|
||||
Path target = minecraftPatchedIntermediateAtJar;
|
||||
accessTransform(project, input, target);
|
||||
}
|
||||
|
||||
public static void accessTransform(Project project, Path input, Path target) throws IOException {
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
|
||||
project.getLogger().lifecycle(":access transforming minecraft");
|
||||
|
||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
Path userdevJar = extension.getForgeUserdevProvider().getUserdevJar().toPath();
|
||||
Files.deleteIfExists(target);
|
||||
|
||||
try (var tempFiles = new TempFiles()) {
|
||||
AccessTransformerJarProcessor.executeAt(project, input, target, args -> {
|
||||
for (String atFile : extractAccessTransformers(userdevJar, extension.getForgeUserdevProvider().getConfig().ats(), tempFiles)) {
|
||||
args.add("--atFile");
|
||||
args.add(atFile);
|
||||
}
|
||||
});
|
||||
logger.lifecycle(":access transforming minecraft");
|
||||
try (var tempFiles = new TempFiles(); var serviceFactory = new ScopedServiceFactory()) {
|
||||
AccessTransformerService service = serviceFactory.get(AccessTransformerService.createOptionsForLoaderAts(project, tempFiles));
|
||||
Files.deleteIfExists(target);
|
||||
service.execute(input, target);
|
||||
}
|
||||
|
||||
project.getLogger().lifecycle(":access transformed minecraft in " + stopwatch.stop());
|
||||
}
|
||||
|
||||
private static List<String> extractAccessTransformers(Path jar, UserdevConfig.AccessTransformerLocation location, TempFiles tempFiles) throws IOException {
|
||||
final List<String> extracted = new ArrayList<>();
|
||||
|
||||
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar)) {
|
||||
for (Path atFile : getAccessTransformerPaths(fs, location)) {
|
||||
byte[] atBytes;
|
||||
|
||||
try {
|
||||
atBytes = Files.readAllBytes(atFile);
|
||||
} catch (NoSuchFileException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Path tmpFile = tempFiles.file("at-conf", ".cfg");
|
||||
Files.write(tmpFile, atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
extracted.add(tmpFile.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
|
||||
return extracted;
|
||||
}
|
||||
|
||||
private static List<Path> getAccessTransformerPaths(FileSystemUtil.Delegate fs, UserdevConfig.AccessTransformerLocation location) throws IOException {
|
||||
return location.visitIo(directory -> {
|
||||
Path dirPath = fs.getPath(directory);
|
||||
|
||||
try (Stream<Path> paths = Files.list(dirPath)) {
|
||||
return paths.toList();
|
||||
}
|
||||
}, paths -> paths.stream().map(fs::getPath).toList());
|
||||
logger.lifecycle(":access transformed minecraft in " + stopwatch.stop());
|
||||
}
|
||||
|
||||
private void remapPatchedJar(ServiceFactory serviceFactory) throws Exception {
|
||||
@@ -669,9 +627,9 @@ public class MinecraftPatchedProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public McpExecutor createMcpExecutor(Path cache) {
|
||||
public McpExecutorBuilder createMcpExecutor(Path cache) {
|
||||
McpConfigProvider provider = getExtension().getMcpConfigProvider();
|
||||
return new McpExecutor(project, minecraftProvider, cache, provider, type.mcpId);
|
||||
return new McpExecutorBuilder(project, minecraftProvider, cache, provider, type.mcpId);
|
||||
}
|
||||
|
||||
public Path getMinecraftIntermediateJar() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022-2023 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -25,6 +25,7 @@
|
||||
package net.fabricmc.loom.configuration.providers.forge.mcpconfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
@@ -45,13 +46,13 @@ import net.fabricmc.loom.util.function.CollectionUtil;
|
||||
* @param jvmArgs the JVM arguments
|
||||
* @param repo the Maven repository to download the dependency from, or {@code null} if not specified
|
||||
*/
|
||||
public record McpConfigFunction(String version, List<ConfigValue> args, List<ConfigValue> jvmArgs, @Nullable String repo) {
|
||||
public record McpConfigFunction(String version, List<ConfigValue> args, List<ConfigValue> jvmArgs, @Nullable String repo) implements Serializable {
|
||||
private static final String VERSION_KEY = "version";
|
||||
private static final String ARGS_KEY = "args";
|
||||
private static final String JVM_ARGS_KEY = "jvmargs";
|
||||
private static final String REPO_KEY = "repo";
|
||||
|
||||
public Path download(StepLogic.ExecutionContext executionContext) throws IOException {
|
||||
public Path download(StepLogic.SetupContext executionContext) throws IOException {
|
||||
if (repo != null) {
|
||||
return executionContext.downloadFile(getDownloadUrl());
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
package net.fabricmc.loom.configuration.providers.forge.mcpconfig;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -31,7 +32,7 @@ import com.google.gson.JsonObject;
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.forge.ConfigValue;
|
||||
|
||||
public record McpConfigStep(String type, String name, Map<String, ConfigValue> config) {
|
||||
public record McpConfigStep(String type, String name, Map<String, ConfigValue> config) implements Serializable {
|
||||
private static final String TYPE_KEY = "type";
|
||||
private static final String NAME_KEY = "name";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022-2023 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -24,141 +24,101 @@
|
||||
|
||||
package net.fabricmc.loom.configuration.providers.forge.mcpconfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.architectury.loom.forge.tool.ForgeToolExecutor;
|
||||
import dev.architectury.loom.forge.tool.ForgeToolValueSource;
|
||||
import dev.architectury.loom.forge.tool.ForgeToolService;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.logging.LogLevel;
|
||||
import org.gradle.api.logging.Logger;
|
||||
import org.gradle.api.logging.Logging;
|
||||
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.Internal;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.process.JavaExecSpec;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.configuration.providers.forge.ConfigValue;
|
||||
import net.fabricmc.loom.configuration.providers.forge.ForgeProvider;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.ConstantLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.DownloadManifestFileLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.FunctionLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.InjectLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.ListLibrariesLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.NoOpLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.PatchLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.StepLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.StripLogic;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.download.Download;
|
||||
import net.fabricmc.loom.util.download.DownloadBuilder;
|
||||
import net.fabricmc.loom.util.function.CollectionUtil;
|
||||
import net.fabricmc.loom.util.gradle.GradleUtils;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
public final class McpExecutor {
|
||||
/**
|
||||
* Executes MCPConfig and NeoForm configs to build Minecraft jars on those platforms.
|
||||
*/
|
||||
public final class McpExecutor extends Service<McpExecutor.Options> {
|
||||
public static final ServiceType<Options, McpExecutor> TYPE = new ServiceType<>(Options.class, McpExecutor.class);
|
||||
|
||||
private static final Logger LOGGER = Logging.getLogger(McpExecutor.class);
|
||||
private static final LogLevel STEP_LOG_LEVEL = LogLevel.LIFECYCLE;
|
||||
private final Project project;
|
||||
private final MinecraftProvider minecraftProvider;
|
||||
private final Path cache;
|
||||
private final List<McpConfigStep> steps;
|
||||
private final DependencySet dependencySet;
|
||||
private final Map<String, McpConfigFunction> functions;
|
||||
private final Map<String, String> config = new HashMap<>();
|
||||
private final Map<String, String> config;
|
||||
private final Map<String, String> extraConfig = new HashMap<>();
|
||||
private @Nullable StepLogic.Provider stepLogicProvider = null;
|
||||
|
||||
public McpExecutor(Project project, MinecraftProvider minecraftProvider, Path cache, McpConfigProvider provider, String environment) {
|
||||
this.project = project;
|
||||
this.minecraftProvider = minecraftProvider;
|
||||
this.cache = cache;
|
||||
this.steps = provider.getData().steps().get(environment);
|
||||
this.functions = provider.getData().functions();
|
||||
this.dependencySet = new DependencySet(this.steps);
|
||||
this.dependencySet.skip(step -> getStepLogic(step.name(), step.type()) instanceof NoOpLogic);
|
||||
this.dependencySet.setIgnoreDependenciesFilter(step -> getStepLogic(step.name(), step.type()).hasNoContext());
|
||||
public interface Options extends Service.Options {
|
||||
// Steps
|
||||
|
||||
checkMinecraftVersion(provider);
|
||||
addDefaultFiles(provider, environment);
|
||||
/**
|
||||
* The service options for the step logics of the requested steps.
|
||||
*/
|
||||
@Nested
|
||||
MapProperty<String, Service.Options> getStepLogicOptions();
|
||||
|
||||
/**
|
||||
* The requested steps.
|
||||
*/
|
||||
@Input
|
||||
ListProperty<McpConfigStep> getStepsToExecute();
|
||||
|
||||
// Config data
|
||||
|
||||
/**
|
||||
* Mappings extracted from {@code data.mappings} in the MCPConfig JSON.
|
||||
*/
|
||||
@InputFile
|
||||
RegularFileProperty getMappings();
|
||||
|
||||
/**
|
||||
* The initial config from the data files.
|
||||
*/
|
||||
@Input
|
||||
MapProperty<String, String> getInitialConfig();
|
||||
|
||||
// Download settings
|
||||
@Input
|
||||
Property<Boolean> getOffline();
|
||||
|
||||
@Input
|
||||
Property<Boolean> getManualRefreshDeps();
|
||||
|
||||
// Services
|
||||
@Nested
|
||||
Property<ForgeToolService.Options> getToolServiceOptions();
|
||||
|
||||
@Internal
|
||||
DirectoryProperty getCache();
|
||||
}
|
||||
|
||||
private void checkMinecraftVersion(McpConfigProvider provider) {
|
||||
final String expected = provider.getData().version();
|
||||
final String actual = minecraftProvider.minecraftVersion();
|
||||
|
||||
if (!expected.equals(actual)) {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
final ForgeProvider forgeProvider = extension.getForgeProvider();
|
||||
final String message = "%s %s is not for Minecraft %s (expected: %s)."
|
||||
.formatted(
|
||||
extension.getPlatform().get().displayName(),
|
||||
forgeProvider.getVersion().getCombined(),
|
||||
actual,
|
||||
expected
|
||||
);
|
||||
|
||||
if (GradleUtils.getBooleanProperty(project, Constants.Properties.ALLOW_MISMATCHED_PLATFORM_VERSION)) {
|
||||
project.getLogger().warn(message);
|
||||
} else {
|
||||
final String fullMessage = "%s\nYou can suppress this error by adding '%s = true' to gradle.properties."
|
||||
.formatted(message, Constants.Properties.ALLOW_MISMATCHED_PLATFORM_VERSION);
|
||||
throw new UnsupportedOperationException(fullMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addDefaultFiles(McpConfigProvider provider, String environment) {
|
||||
for (Map.Entry<String, JsonElement> entry : provider.getData().data().entrySet()) {
|
||||
if (entry.getValue().isJsonPrimitive()) {
|
||||
addDefaultFile(provider, entry.getKey(), entry.getValue().getAsString());
|
||||
} else if (entry.getValue().isJsonObject()) {
|
||||
JsonObject json = entry.getValue().getAsJsonObject();
|
||||
|
||||
if (json.has(environment) && json.get(environment).isJsonPrimitive()) {
|
||||
addDefaultFile(provider, entry.getKey(), json.getAsJsonPrimitive(environment).getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addDefaultFile(McpConfigProvider provider, String key, String value) {
|
||||
Path path = provider.getUnpackedZip().resolve(value).toAbsolutePath();
|
||||
|
||||
if (!path.startsWith(provider.getUnpackedZip().toAbsolutePath())) {
|
||||
// This is probably not what we're looking for since it falls outside the directory.
|
||||
return;
|
||||
} else if (Files.notExists(path)) {
|
||||
// Not a real file, let's continue.
|
||||
return;
|
||||
}
|
||||
|
||||
addConfig(key, path.toString());
|
||||
}
|
||||
|
||||
public void addConfig(String key, String value) {
|
||||
config.put(key, value);
|
||||
}
|
||||
|
||||
private Path getDownloadCache() throws IOException {
|
||||
Path downloadCache = cache.resolve("downloads");
|
||||
Files.createDirectories(downloadCache);
|
||||
return downloadCache;
|
||||
public McpExecutor(Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
this.config = new HashMap<>(options.getInitialConfig().get());
|
||||
this.cache = options.getCache().get().getAsFile().toPath();
|
||||
}
|
||||
|
||||
private Path getStepCache(String step) {
|
||||
@@ -195,96 +155,34 @@ public final class McpExecutor {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues a step and its dependencies to be executed.
|
||||
*
|
||||
* @param step the name of the step
|
||||
* @return this executor
|
||||
*/
|
||||
public McpExecutor enqueue(String step) {
|
||||
dependencySet.add(step);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all queued steps and their dependencies.
|
||||
*
|
||||
* @return the output file of the last executed step
|
||||
*/
|
||||
public Path execute() throws IOException {
|
||||
SortedSet<String> stepNames = dependencySet.buildExecutionSet();
|
||||
dependencySet.clear();
|
||||
List<McpConfigStep> toExecute = new ArrayList<>();
|
||||
|
||||
for (String stepName : stepNames) {
|
||||
McpConfigStep step = CollectionUtil.find(steps, s -> s.name().equals(stepName))
|
||||
.orElseThrow(() -> new NoSuchElementException("Step '" + stepName + "' not found in MCP config"));
|
||||
toExecute.add(step);
|
||||
}
|
||||
|
||||
return executeSteps(toExecute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the specified steps.
|
||||
*
|
||||
* @param steps the steps to execute
|
||||
* @return the output file of the last executed step
|
||||
*/
|
||||
public Path executeSteps(List<McpConfigStep> steps) throws IOException {
|
||||
extraConfig.clear();
|
||||
|
||||
List<McpConfigStep> steps = getOptions().getStepsToExecute().get();
|
||||
int totalSteps = steps.size();
|
||||
int currentStepIndex = 0;
|
||||
|
||||
project.getLogger().log(STEP_LOG_LEVEL, ":executing {} MCP steps", totalSteps);
|
||||
LOGGER.log(STEP_LOG_LEVEL, ":executing {} MCP steps", totalSteps);
|
||||
|
||||
for (McpConfigStep currentStep : steps) {
|
||||
currentStepIndex++;
|
||||
StepLogic stepLogic = getStepLogic(currentStep.name(), currentStep.type());
|
||||
project.getLogger().log(STEP_LOG_LEVEL, ":step {}/{} - {}", currentStepIndex, totalSteps, stepLogic.getDisplayName(currentStep.name()));
|
||||
StepLogic<?> stepLogic = getStepLogic(currentStep.name());
|
||||
LOGGER.log(STEP_LOG_LEVEL, ":step {}/{} - {}", currentStepIndex, totalSteps, stepLogic.getDisplayName(currentStep.name()));
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
stepLogic.execute(new ExecutionContextImpl(currentStep));
|
||||
project.getLogger().log(STEP_LOG_LEVEL, ":{} done in {}", currentStep.name(), stopwatch.stop());
|
||||
LOGGER.log(STEP_LOG_LEVEL, ":{} done in {}", currentStep.name(), stopwatch.stop());
|
||||
}
|
||||
|
||||
return Path.of(extraConfig.get(ConfigValue.OUTPUT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom step logic provider of this executor.
|
||||
*
|
||||
* @param stepLogicProvider the provider, or null to disable
|
||||
*/
|
||||
public void setStepLogicProvider(@Nullable StepLogic.Provider stepLogicProvider) {
|
||||
this.stepLogicProvider = stepLogicProvider;
|
||||
}
|
||||
|
||||
private StepLogic getStepLogic(String name, String type) {
|
||||
if (stepLogicProvider != null) {
|
||||
final @Nullable StepLogic custom = stepLogicProvider.getStepLogic(name, type).orElse(null);
|
||||
if (custom != null) return custom;
|
||||
}
|
||||
|
||||
return switch (type) {
|
||||
case "downloadManifest", "downloadJson" -> new NoOpLogic();
|
||||
case "downloadClient" -> new ConstantLogic(() -> minecraftProvider.getMinecraftClientJar().toPath());
|
||||
case "downloadServer" -> new ConstantLogic(() -> minecraftProvider.getMinecraftServerJar().toPath());
|
||||
case "strip" -> new StripLogic();
|
||||
case "listLibraries" -> new ListLibrariesLogic();
|
||||
case "downloadClientMappings" -> new DownloadManifestFileLogic(minecraftProvider.getVersionInfo().download("client_mappings"));
|
||||
case "downloadServerMappings" -> new DownloadManifestFileLogic(minecraftProvider.getVersionInfo().download("server_mappings"));
|
||||
case "inject" -> new InjectLogic();
|
||||
case "patch" -> new PatchLogic();
|
||||
default -> {
|
||||
if (functions.containsKey(type)) {
|
||||
yield new FunctionLogic(functions.get(type));
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("MCP config step type: " + type);
|
||||
}
|
||||
};
|
||||
private StepLogic<?> getStepLogic(String name) {
|
||||
final Provider<Service.Options> options = getOptions().getStepLogicOptions().getting(name);
|
||||
return (StepLogic<?>) getServiceFactory().get(options);
|
||||
}
|
||||
|
||||
private class ExecutionContextImpl implements StepLogic.ExecutionContext {
|
||||
@@ -296,7 +194,7 @@ public final class McpExecutor {
|
||||
|
||||
@Override
|
||||
public Logger logger() {
|
||||
return project.getLogger();
|
||||
return LOGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -319,7 +217,7 @@ public final class McpExecutor {
|
||||
|
||||
@Override
|
||||
public Path mappings() {
|
||||
return LoomGradleExtension.get(project).getMcpConfigProvider().getMappings();
|
||||
return getOptions().getMappings().get().getAsFile().toPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -327,55 +225,31 @@ public final class McpExecutor {
|
||||
return McpExecutor.this.resolve(step, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path downloadFile(String url) throws IOException {
|
||||
Path path = getDownloadCache().resolve(Hashing.sha256().hashString(url, StandardCharsets.UTF_8).toString().substring(0, 24));
|
||||
redirectAwareDownload(url, path);
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path downloadDependency(String notation) {
|
||||
final Dependency dependency = project.getDependencies().create(notation);
|
||||
final Configuration configuration = project.getConfigurations().detachedConfiguration(dependency);
|
||||
configuration.setTransitive(false);
|
||||
return configuration.getSingleFile().toPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadBuilder downloadBuilder(String url) {
|
||||
return LoomGradleExtension.get(project).download(url);
|
||||
}
|
||||
DownloadBuilder builder;
|
||||
|
||||
// Some of these files linked to the old Forge maven, let's follow the redirects to the new one.
|
||||
private static void redirectAwareDownload(String urlString, Path path) throws IOException {
|
||||
URL url = new URL(urlString);
|
||||
|
||||
if (url.getProtocol().equals("http")) {
|
||||
url = new URL("https", url.getHost(), url.getPort(), url.getFile());
|
||||
try {
|
||||
builder = Download.create(url);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("Failed to create downloader for: " + e);
|
||||
}
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM || connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP) {
|
||||
redirectAwareDownload(connection.getHeaderField("Location"), path);
|
||||
} else {
|
||||
try (InputStream in = connection.getInputStream()) {
|
||||
Files.copy(in, path);
|
||||
}
|
||||
if (getOptions().getOffline().get()) {
|
||||
builder.offline();
|
||||
}
|
||||
|
||||
if (getOptions().getManualRefreshDeps().get()) {
|
||||
builder.forceDownload();
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void javaexec(Action<? super ForgeToolExecutor.Settings> configurator) {
|
||||
ForgeToolValueSource.exec(project, configurator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<File> getMinecraftLibraries() {
|
||||
// (1.2) minecraftRuntimeLibraries contains the compile-time libraries as well.
|
||||
return project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_RUNTIME_LIBRARIES).resolve();
|
||||
public void javaexec(Action<? super JavaExecSpec> configurator) {
|
||||
final ForgeToolService toolService = getServiceFactory().get(getOptions().getToolServiceOptions());
|
||||
toolService.exec(configurator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.configuration.providers.forge.mcpconfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.architectury.loom.forge.tool.ForgeToolService;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.configuration.providers.forge.ForgeProvider;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.ConstantLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.DownloadManifestFileLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.FunctionLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.InjectLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.ListLibrariesLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.NoOpLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.PatchLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.StepLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.StripLogic;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.function.CollectionUtil;
|
||||
import net.fabricmc.loom.util.gradle.GradleUtils;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
|
||||
/**
|
||||
* Builds an {@link McpExecutor}'s {@linkplain McpExecutor.Options options} from the project state
|
||||
* and enqueued steps.
|
||||
*/
|
||||
public final class McpExecutorBuilder {
|
||||
private final Project project;
|
||||
private final MinecraftProvider minecraftProvider;
|
||||
private final Path cache;
|
||||
private final List<McpConfigStep> steps;
|
||||
private final DependencySet dependencySet;
|
||||
private final Map<String, McpConfigFunction> functions;
|
||||
private final Map<String, String> config = new HashMap<>();
|
||||
private final StepLogic.SetupContext setupContext = new SetupContextImpl();
|
||||
private @Nullable StepLogic.StepLogicProvider stepLogicProvider = null;
|
||||
|
||||
public McpExecutorBuilder(Project project, MinecraftProvider minecraftProvider, Path cache, McpConfigProvider provider, String environment) {
|
||||
this.project = project;
|
||||
this.minecraftProvider = minecraftProvider;
|
||||
this.cache = cache;
|
||||
this.steps = provider.getData().steps().get(environment);
|
||||
this.functions = provider.getData().functions();
|
||||
this.dependencySet = new DependencySet(this.steps);
|
||||
this.dependencySet.skip(step -> isNoOp(step.type()));
|
||||
|
||||
checkMinecraftVersion(provider);
|
||||
addDefaultFiles(provider, environment);
|
||||
}
|
||||
|
||||
private void checkMinecraftVersion(McpConfigProvider provider) {
|
||||
final String expected = provider.getData().version();
|
||||
final String actual = minecraftProvider.minecraftVersion();
|
||||
|
||||
if (!expected.equals(actual)) {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
final ForgeProvider forgeProvider = extension.getForgeProvider();
|
||||
final String message = "%s %s is not for Minecraft %s (expected: %s)."
|
||||
.formatted(
|
||||
extension.getPlatform().get().displayName(),
|
||||
forgeProvider.getVersion().getCombined(),
|
||||
actual,
|
||||
expected
|
||||
);
|
||||
|
||||
if (GradleUtils.getBooleanProperty(project, Constants.Properties.ALLOW_MISMATCHED_PLATFORM_VERSION)) {
|
||||
project.getLogger().warn(message);
|
||||
} else {
|
||||
final String fullMessage = "%s\nYou can suppress this error by adding '%s = true' to gradle.properties."
|
||||
.formatted(message, Constants.Properties.ALLOW_MISMATCHED_PLATFORM_VERSION);
|
||||
throw new UnsupportedOperationException(fullMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addDefaultFiles(McpConfigProvider provider, String environment) {
|
||||
for (Map.Entry<String, JsonElement> entry : provider.getData().data().entrySet()) {
|
||||
if (entry.getValue().isJsonPrimitive()) {
|
||||
addDefaultFile(provider, entry.getKey(), entry.getValue().getAsString());
|
||||
} else if (entry.getValue().isJsonObject()) {
|
||||
JsonObject json = entry.getValue().getAsJsonObject();
|
||||
|
||||
if (json.has(environment) && json.get(environment).isJsonPrimitive()) {
|
||||
addDefaultFile(provider, entry.getKey(), json.getAsJsonPrimitive(environment).getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addDefaultFile(McpConfigProvider provider, String key, String value) {
|
||||
Path path = provider.getUnpackedZip().resolve(value).toAbsolutePath();
|
||||
|
||||
if (!path.startsWith(provider.getUnpackedZip().toAbsolutePath())) {
|
||||
// This is probably not what we're looking for since it falls outside the directory.
|
||||
return;
|
||||
} else if (Files.notExists(path)) {
|
||||
// Not a real file, let's continue.
|
||||
return;
|
||||
}
|
||||
|
||||
addConfig(key, path.toString());
|
||||
}
|
||||
|
||||
public void addConfig(String key, String value) {
|
||||
config.put(key, value);
|
||||
}
|
||||
|
||||
private Path getDownloadCache() throws IOException {
|
||||
Path downloadCache = cache.resolve("downloads");
|
||||
Files.createDirectories(downloadCache);
|
||||
return downloadCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues a step and its dependencies to be executed.
|
||||
*
|
||||
* @param step the name of the step
|
||||
* @return this builder
|
||||
*/
|
||||
public McpExecutorBuilder enqueue(String step) {
|
||||
dependencySet.add(step);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds options for an executor that runs all queued steps and their dependencies.
|
||||
*
|
||||
* @return the options
|
||||
*/
|
||||
public Provider<McpExecutor.Options> build() throws IOException {
|
||||
SortedSet<String> stepNames = dependencySet.buildExecutionSet();
|
||||
dependencySet.clear();
|
||||
List<McpConfigStep> toExecute = new ArrayList<>();
|
||||
|
||||
for (String stepName : stepNames) {
|
||||
McpConfigStep step = CollectionUtil.find(steps, s -> s.name().equals(stepName))
|
||||
.orElseThrow(() -> new NoSuchElementException("Step '" + stepName + "' not found in MCP config"));
|
||||
toExecute.add(step);
|
||||
}
|
||||
|
||||
return McpExecutor.TYPE.create(project, options -> {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
|
||||
for (McpConfigStep step : toExecute) {
|
||||
options.getStepLogicOptions().put(step.name(), getStepLogic(step.name(), step.type()));
|
||||
}
|
||||
|
||||
options.getStepsToExecute().set(toExecute);
|
||||
options.getMappings().set(extension.getMcpConfigProvider().getMappings().toFile());
|
||||
options.getInitialConfig().set(config);
|
||||
options.getOffline().set(project.getGradle().getStartParameter().isOffline());
|
||||
options.getManualRefreshDeps().set(extension.manualRefreshDeps());
|
||||
options.getToolServiceOptions().set(ForgeToolService.createOptions(project));
|
||||
options.getCache().set(cache.toFile());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom step logic provider of this executor.
|
||||
*
|
||||
* @param stepLogicProvider the provider, or null to disable
|
||||
*/
|
||||
public void setStepLogicProvider(@Nullable StepLogic.StepLogicProvider stepLogicProvider) {
|
||||
this.stepLogicProvider = stepLogicProvider;
|
||||
}
|
||||
|
||||
private boolean isNoOp(String stepType) {
|
||||
return "downloadManifest".equals(stepType) || "downloadJson".equals(stepType);
|
||||
}
|
||||
|
||||
private Provider<? extends Service.Options> getStepLogic(String name, String type) {
|
||||
if (stepLogicProvider != null) {
|
||||
final @Nullable Provider<? extends Service.Options> custom = stepLogicProvider.getStepLogic(setupContext, name, type);
|
||||
if (custom != null) return custom;
|
||||
}
|
||||
|
||||
return switch (type) {
|
||||
case "downloadManifest", "downloadJson" -> NoOpLogic.createOptions(setupContext);
|
||||
case "downloadClient" -> ConstantLogic.createOptions(setupContext, () -> minecraftProvider.getMinecraftClientJar().toPath());
|
||||
case "downloadServer" -> ConstantLogic.createOptions(setupContext, () -> minecraftProvider.getMinecraftServerJar().toPath());
|
||||
case "strip" -> StripLogic.createOptions(setupContext);
|
||||
case "listLibraries" -> ListLibrariesLogic.createOptions(setupContext);
|
||||
case "downloadClientMappings" -> DownloadManifestFileLogic.createOptions(setupContext, minecraftProvider.getVersionInfo().download("client_mappings"));
|
||||
case "downloadServerMappings" -> DownloadManifestFileLogic.createOptions(setupContext, minecraftProvider.getVersionInfo().download("server_mappings"));
|
||||
case "inject" -> InjectLogic.createOptions(setupContext);
|
||||
case "patch" -> PatchLogic.createOptions(setupContext);
|
||||
default -> {
|
||||
if (functions.containsKey(type)) {
|
||||
yield FunctionLogic.createOptions(setupContext, functions.get(type));
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("MCP config step type: " + type);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class SetupContextImpl implements StepLogic.SetupContext {
|
||||
@Override
|
||||
public Project project() {
|
||||
return project;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path downloadFile(String url) throws IOException {
|
||||
Path path = getDownloadCache().resolve(Hashing.sha256().hashString(url, StandardCharsets.UTF_8).toString().substring(0, 24));
|
||||
|
||||
// If the file is already downloaded, we don't need to do anything.
|
||||
if (Files.exists(path)) return path;
|
||||
|
||||
redirectAwareDownload(url, path);
|
||||
return path;
|
||||
}
|
||||
|
||||
// Some of these files linked to the old Forge maven, let's follow the redirects to the new one.
|
||||
private static void redirectAwareDownload(String urlString, Path path) throws IOException {
|
||||
URL url = new URL(urlString);
|
||||
|
||||
if (url.getProtocol().equals("http")) {
|
||||
url = new URL("https", url.getHost(), url.getPort(), url.getFile());
|
||||
}
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM || connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP) {
|
||||
redirectAwareDownload(connection.getHeaderField("Location"), path);
|
||||
} else {
|
||||
try (InputStream in = connection.getInputStream()) {
|
||||
Files.copy(in, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path downloadDependency(String notation) {
|
||||
final Dependency dependency = project.getDependencies().create(notation);
|
||||
final Configuration configuration = project.getConfigurations().detachedConfiguration(dependency);
|
||||
configuration.setTransitive(false);
|
||||
return configuration.getSingleFile().toPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Provider<FileCollection> getMinecraftLibraries() {
|
||||
return project().provider(Suppliers.memoize(() -> {
|
||||
project.getLogger().lifecycle(":downloading minecraft libraries, this may take a while...");
|
||||
// (1.2) minecraftRuntimeLibraries contains the compile-time libraries as well.
|
||||
final Set<File> files = project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_RUNTIME_LIBRARIES).resolve();
|
||||
return project.files(files);
|
||||
})::get);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -28,19 +28,38 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Input;
|
||||
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
/**
|
||||
* A no-op step logic that is used for steps automatically executed by Loom earlier.
|
||||
* This one returns a file.
|
||||
*/
|
||||
public final class ConstantLogic implements StepLogic {
|
||||
private final Supplier<Path> path;
|
||||
public final class ConstantLogic extends StepLogic<ConstantLogic.Options> {
|
||||
public static final ServiceType<Options, ConstantLogic> TYPE = new ServiceType<>(Options.class, ConstantLogic.class);
|
||||
|
||||
public ConstantLogic(Supplier<Path> path) {
|
||||
this.path = path;
|
||||
public interface Options extends Service.Options {
|
||||
@Input
|
||||
Property<String> getFile();
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(SetupContext context, Supplier<Path> path) {
|
||||
return TYPE.create(context.project(), options -> {
|
||||
options.getFile().set(context.project().provider(() -> path.get().toAbsolutePath().toString()));
|
||||
});
|
||||
}
|
||||
|
||||
public ConstantLogic(Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ExecutionContext context) throws IOException {
|
||||
context.setOutput(path.get());
|
||||
context.setOutput(Path.of(getOptions().getFile().get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -26,20 +26,37 @@ package net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Input;
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
/**
|
||||
* Downloads a file from the Minecraft version metadata.
|
||||
*/
|
||||
public final class DownloadManifestFileLogic implements StepLogic {
|
||||
private final MinecraftVersionMeta.Download download;
|
||||
public final class DownloadManifestFileLogic extends StepLogic<DownloadManifestFileLogic.Options> {
|
||||
public static final ServiceType<Options, DownloadManifestFileLogic> TYPE = new ServiceType<>(Options.class, DownloadManifestFileLogic.class);
|
||||
|
||||
public DownloadManifestFileLogic(MinecraftVersionMeta.Download download) {
|
||||
this.download = download;
|
||||
public interface Options extends Service.Options {
|
||||
@Input
|
||||
Property<MinecraftVersionMeta.Download> getDownload();
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(SetupContext context, MinecraftVersionMeta.Download download) {
|
||||
return TYPE.create(context.project(), options -> options.getDownload().set(download));
|
||||
}
|
||||
|
||||
public DownloadManifestFileLogic(Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ExecutionContext context) throws IOException {
|
||||
MinecraftVersionMeta.Download download = getOptions().getDownload().get();
|
||||
context.downloadBuilder(download.url())
|
||||
.sha1(download.sha1())
|
||||
.downloadPath(context.setOutput("output"));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022-2023 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -24,21 +24,54 @@
|
||||
|
||||
package net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
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 net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigFunction;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
/**
|
||||
* Runs a Forge tool configured by a {@linkplain McpConfigFunction function}.
|
||||
*/
|
||||
public final class FunctionLogic implements StepLogic {
|
||||
private final McpConfigFunction function;
|
||||
public final class FunctionLogic extends StepLogic<FunctionLogic.Options> {
|
||||
public static final ServiceType<Options, FunctionLogic> TYPE = new ServiceType<>(Options.class, FunctionLogic.class);
|
||||
|
||||
public FunctionLogic(McpConfigFunction function) {
|
||||
this.function = function;
|
||||
public interface Options extends Service.Options {
|
||||
@Input
|
||||
Property<McpConfigFunction> getFunction();
|
||||
|
||||
@InputFile
|
||||
RegularFileProperty getToolJar();
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(SetupContext context, McpConfigFunction function) {
|
||||
return TYPE.create(context.project(), options -> {
|
||||
options.getFunction().set(function);
|
||||
final Provider<File> jar = context.project().provider(Suppliers.memoize(() -> {
|
||||
try {
|
||||
return function.download(context).toFile();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
})::get);
|
||||
options.getToolJar().set(context.project().getLayout().file(jar));
|
||||
});
|
||||
}
|
||||
|
||||
public FunctionLogic(Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,13 +80,15 @@ public final class FunctionLogic implements StepLogic {
|
||||
// The other tools seem to work with the name containing .jar anyway.
|
||||
// Technically, FG supports an "outputExtension" config value for steps, but it's not used in practice.
|
||||
context.setOutput("output.jar");
|
||||
Path jar = function.download(context);
|
||||
|
||||
McpConfigFunction function = getOptions().getFunction().get();
|
||||
File jar = getOptions().getToolJar().get().getAsFile();
|
||||
String mainClass;
|
||||
|
||||
try (JarFile jarFile = new JarFile(jar.toFile())) {
|
||||
try (JarFile jarFile = new JarFile(jar)) {
|
||||
mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Could not determine main class for " + jar.toAbsolutePath(), e);
|
||||
throw new IOException("Could not determine main class for " + jar.getAbsolutePath(), e);
|
||||
}
|
||||
|
||||
context.javaexec(spec -> {
|
||||
@@ -66,6 +101,6 @@ public final class FunctionLogic implements StepLogic {
|
||||
|
||||
@Override
|
||||
public String getDisplayName(String stepName) {
|
||||
return stepName + " with " + function.version();
|
||||
return stepName + " with " + getOptions().getFunction().get().version();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022-2023 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -32,10 +32,28 @@ import java.nio.file.StandardCopyOption;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.gradle.api.provider.Provider;
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.forge.ConfigValue;
|
||||
import net.fabricmc.loom.util.FileSystemUtil;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
public final class InjectLogic extends StepLogic<InjectLogic.Options> {
|
||||
public static final ServiceType<Options, InjectLogic> TYPE = new ServiceType<>(Options.class, InjectLogic.class);
|
||||
|
||||
public interface Options extends Service.Options {
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(SetupContext context) {
|
||||
return TYPE.create(context.project(), options -> { });
|
||||
}
|
||||
|
||||
public InjectLogic(Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
public final class InjectLogic implements StepLogic {
|
||||
@Override
|
||||
public void execute(ExecutionContext context) throws IOException {
|
||||
Path injectedFiles = Path.of(context.resolve(new ConfigValue.Variable("inject")));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -29,16 +29,39 @@ import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
/**
|
||||
* Lists the Minecraft libraries into the output file.
|
||||
*/
|
||||
public final class ListLibrariesLogic implements StepLogic {
|
||||
public final class ListLibrariesLogic extends StepLogic<ListLibrariesLogic.Options> {
|
||||
public static final ServiceType<Options, ListLibrariesLogic> TYPE = new ServiceType<>(Options.class, ListLibrariesLogic.class);
|
||||
|
||||
public interface Options extends Service.Options {
|
||||
@InputFiles
|
||||
ConfigurableFileCollection getMinecraftLibraries();
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(SetupContext context) {
|
||||
return TYPE.create(context.project(), options -> {
|
||||
options.getMinecraftLibraries().from(context.getMinecraftLibraries());
|
||||
});
|
||||
}
|
||||
|
||||
public ListLibrariesLogic(Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ExecutionContext context) throws IOException {
|
||||
context.logger().lifecycle(":downloading minecraft libraries, this may take a while...");
|
||||
|
||||
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(context.setOutput("libraries.txt")))) {
|
||||
for (File lib : context.getMinecraftLibraries()) {
|
||||
for (File lib : getOptions().getMinecraftLibraries()) {
|
||||
writer.println("-e=" + lib.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -26,10 +26,29 @@ package net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.gradle.api.provider.Provider;
|
||||
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
/**
|
||||
* A no-op step logic that is used for steps automatically executed by Loom earlier.
|
||||
*/
|
||||
public final class NoOpLogic implements StepLogic {
|
||||
public final class NoOpLogic extends StepLogic<NoOpLogic.Options> {
|
||||
public static final ServiceType<Options, NoOpLogic> TYPE = new ServiceType<>(Options.class, NoOpLogic.class);
|
||||
|
||||
public interface Options extends Service.Options {
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(SetupContext context) {
|
||||
return TYPE.create(context.project(), options -> { });
|
||||
}
|
||||
|
||||
public NoOpLogic(Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ExecutionContext context) throws IOException {
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -32,10 +32,27 @@ import codechicken.diffpatch.cli.PatchOperation;
|
||||
import codechicken.diffpatch.util.LoggingOutputStream;
|
||||
import codechicken.diffpatch.util.PatchMode;
|
||||
import org.gradle.api.logging.LogLevel;
|
||||
import org.gradle.api.provider.Provider;
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.forge.ConfigValue;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
public final class PatchLogic extends StepLogic<PatchLogic.Options> {
|
||||
public static final ServiceType<Options, PatchLogic> TYPE = new ServiceType<>(PatchLogic.Options.class, PatchLogic.class);
|
||||
|
||||
public interface Options extends Service.Options {
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(SetupContext context) {
|
||||
return TYPE.create(context.project(), options -> { });
|
||||
}
|
||||
|
||||
public PatchLogic(PatchLogic.Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
public final class PatchLogic implements StepLogic {
|
||||
@Override
|
||||
public void execute(ExecutionContext context) throws IOException {
|
||||
Path input = Path.of(context.resolve(new ConfigValue.Variable("input")));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022-2023 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -24,36 +24,39 @@
|
||||
|
||||
package net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import dev.architectury.loom.forge.tool.ForgeToolExecutor;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.logging.Logger;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.process.JavaExecSpec;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.forge.ConfigValue;
|
||||
import net.fabricmc.loom.util.download.DownloadBuilder;
|
||||
import net.fabricmc.loom.util.function.CollectionUtil;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
|
||||
/**
|
||||
* The logic for executing a step. This corresponds to the {@code type} key in the step JSON format.
|
||||
*/
|
||||
public interface StepLogic {
|
||||
void execute(ExecutionContext context) throws IOException;
|
||||
public abstract class StepLogic<O extends Service.Options> extends Service<O> {
|
||||
public StepLogic(O options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
default String getDisplayName(String stepName) {
|
||||
public abstract void execute(ExecutionContext context) throws IOException;
|
||||
|
||||
public String getDisplayName(String stepName) {
|
||||
return stepName;
|
||||
}
|
||||
|
||||
default boolean hasNoContext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
interface ExecutionContext {
|
||||
public interface ExecutionContext {
|
||||
Logger logger();
|
||||
Path setOutput(String fileName) throws IOException;
|
||||
Path setOutput(Path output);
|
||||
@@ -61,19 +64,23 @@ public interface StepLogic {
|
||||
/** Mappings extracted from {@code data.mappings} in the MCPConfig JSON. */
|
||||
Path mappings();
|
||||
String resolve(ConfigValue value);
|
||||
Path downloadFile(String url) throws IOException;
|
||||
Path downloadDependency(String notation);
|
||||
DownloadBuilder downloadBuilder(String url);
|
||||
void javaexec(Action<? super ForgeToolExecutor.Settings> configurator);
|
||||
Set<File> getMinecraftLibraries();
|
||||
void javaexec(Action<? super JavaExecSpec> configurator);
|
||||
|
||||
default List<String> resolve(List<ConfigValue> configValues) {
|
||||
return CollectionUtil.map(configValues, this::resolve);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SetupContext {
|
||||
Project project();
|
||||
Path downloadFile(String url) throws IOException;
|
||||
Path downloadDependency(String notation);
|
||||
Provider<FileCollection> getMinecraftLibraries();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Provider {
|
||||
Optional<StepLogic> getStepLogic(String name, String type);
|
||||
public interface StepLogicProvider {
|
||||
@Nullable Provider<? extends Service.Options> getStepLogic(SetupContext context, String name, String type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -33,14 +33,32 @@ import java.nio.file.StandardCopyOption;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.gradle.api.provider.Provider;
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.forge.ConfigValue;
|
||||
import net.fabricmc.loom.util.FileSystemUtil;
|
||||
import net.fabricmc.loom.util.ThreadingUtils;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceType;
|
||||
|
||||
/**
|
||||
* Strips certain classes from the jar.
|
||||
*/
|
||||
public final class StripLogic implements StepLogic {
|
||||
public final class StripLogic extends StepLogic<StripLogic.Options> {
|
||||
public static final ServiceType<Options, StripLogic> TYPE = new ServiceType<>(StripLogic.Options.class, StripLogic.class);
|
||||
|
||||
public interface Options extends Service.Options {
|
||||
}
|
||||
|
||||
public static Provider<Options> createOptions(SetupContext context) {
|
||||
return TYPE.create(context.project(), options -> { });
|
||||
}
|
||||
|
||||
public StripLogic(StripLogic.Options options, ServiceFactory serviceFactory) {
|
||||
super(options, serviceFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ExecutionContext context) throws IOException {
|
||||
Set<String> filter = Files.readAllLines(context.mappings(), StandardCharsets.UTF_8).stream()
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
package net.fabricmc.loom.configuration.providers.minecraft;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -163,7 +164,7 @@ public record MinecraftVersionMeta(
|
||||
}
|
||||
}
|
||||
|
||||
public record Download(String path, String sha1, long size, String url) {
|
||||
public record Download(String path, String sha1, long size, String url) implements Serializable {
|
||||
public File relativeFile(File baseDirectory) {
|
||||
Objects.requireNonNull(path(), "Cannot get relative file from a null path");
|
||||
return new File(baseDirectory, path());
|
||||
|
||||
@@ -298,7 +298,8 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
|
||||
return builder;
|
||||
}
|
||||
|
||||
private boolean manualRefreshDeps() {
|
||||
@Override
|
||||
public boolean manualRefreshDeps() {
|
||||
return project.getGradle().getStartParameter().isRefreshDependencies() || Boolean.getBoolean("loom.refresh");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022-2023 FabricMC
|
||||
* Copyright (c) 2022-2025 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -25,12 +25,12 @@
|
||||
package net.fabricmc.loom.task;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import codechicken.diffpatch.cli.CliOperation;
|
||||
import codechicken.diffpatch.cli.PatchOperation;
|
||||
@@ -38,32 +38,36 @@ import codechicken.diffpatch.util.LoggingOutputStream;
|
||||
import codechicken.diffpatch.util.PatchMode;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import dev.architectury.loom.forge.ForgeSourcesService;
|
||||
import dev.architectury.loom.forge.tool.ForgeToolValueSource;
|
||||
import dev.architectury.loom.forge.tool.AccessTransformerService;
|
||||
import dev.architectury.loom.forge.tool.ForgeToolService;
|
||||
import dev.architectury.loom.forge.tool.ForgeTools;
|
||||
import dev.architectury.loom.util.TempFiles;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.logging.LogLevel;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
|
||||
import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider;
|
||||
import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpExecutor;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpExecutorBuilder;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.ConstantLogic;
|
||||
import net.fabricmc.loom.task.service.MappingsService;
|
||||
import net.fabricmc.loom.task.service.SourceRemapperService;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.DependencyDownloader;
|
||||
import net.fabricmc.loom.util.FileSystemUtil;
|
||||
import net.fabricmc.loom.util.SourceRemapper;
|
||||
import net.fabricmc.loom.util.service.ScopedServiceFactory;
|
||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||
|
||||
// TODO: NeoForge support
|
||||
// TODO: Config cache support
|
||||
public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
|
||||
/**
|
||||
* The SRG Minecraft file produced by the MCP executor.
|
||||
@@ -83,35 +87,112 @@ public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getOutputJar();
|
||||
|
||||
@OutputFile
|
||||
protected abstract RegularFileProperty getSideAnnotationStrippedMinecraftJar();
|
||||
|
||||
@Nested
|
||||
public abstract Property<ForgeSourcesService.Options> getForgeSourcesOptions();
|
||||
protected abstract Property<ForgeSourcesService.Options> getForgeSourcesOptions();
|
||||
|
||||
@Nested
|
||||
protected abstract Property<McpExecutor.Options> getMcpExecutorOptions();
|
||||
|
||||
@Nested
|
||||
protected abstract Property<AccessTransformerService.Options> getAccessTransformerOptions();
|
||||
|
||||
@Nested
|
||||
protected abstract Property<ForgeToolService.Options> getToolServiceOptions();
|
||||
|
||||
@Nested
|
||||
protected abstract Property<SourceRemapperService.Options> getSourceRemapperOptions();
|
||||
|
||||
@Nested
|
||||
protected abstract Property<SasOptions> getSasOptions();
|
||||
|
||||
@Input
|
||||
protected abstract Property<String> getPatchPathInZip();
|
||||
|
||||
@Input
|
||||
protected abstract Property<String> getPatchesOriginalPrefix();
|
||||
|
||||
@Input
|
||||
protected abstract Property<String> getPatchesModifiedPrefix();
|
||||
|
||||
@Internal
|
||||
protected abstract Property<TempFiles> getTempFiles();
|
||||
|
||||
public GenerateForgePatchedSourcesTask() {
|
||||
getOutputs().upToDateWhen((o) -> false);
|
||||
getOutputJar().fileProvider(getProject().provider(() -> GenerateSourcesTask.getJarFileWithSuffix(getRuntimeJar(), "-sources.jar")));
|
||||
getForgeSourcesOptions().convention(ForgeSourcesService.createOptions(getProject()));
|
||||
|
||||
final TempFiles tempFiles = new TempFiles();
|
||||
getTempFiles().value(tempFiles).finalizeValue();
|
||||
final Path cache;
|
||||
|
||||
try {
|
||||
cache = tempFiles.directory("mcp-cache");
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
|
||||
getSideAnnotationStrippedMinecraftJar().set(cache.resolve("side-annotation-stripped.jar").toFile());
|
||||
getMcpExecutorOptions().convention(getProject().provider(() -> {
|
||||
MinecraftPatchedProvider patchedProvider = MinecraftPatchedProvider.get(getProject());
|
||||
McpExecutorBuilder mcp = patchedProvider.createMcpExecutor(cache);
|
||||
mcp.setStepLogicProvider((setupContext, name, type) -> {
|
||||
if (name.equals("rename")) {
|
||||
return ConstantLogic.createOptions(setupContext, () -> getSideAnnotationStrippedMinecraftJar().get().getAsFile().toPath());
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
mcp.enqueue("decompile");
|
||||
mcp.enqueue("patch");
|
||||
|
||||
try {
|
||||
return mcp.build();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}).flatMap(o -> o));
|
||||
getAccessTransformerOptions().convention(AccessTransformerService.createOptionsForLoaderAts(getProject(), tempFiles));
|
||||
getToolServiceOptions().convention(ForgeToolService.createOptions(getProject()));
|
||||
|
||||
final SasOptions sasOptions = getProject().getObjects().newInstance(SasOptions.class);
|
||||
sasOptions.getUserdevJar().set(getExtension().getForgeUserdevProvider().getUserdevJar());
|
||||
sasOptions.getSass().set(getExtension().getForgeUserdevProvider().getConfig().sass());
|
||||
sasOptions.getClasspath().from(DependencyDownloader.download(getProject(), ForgeTools.SIDE_STRIPPER, false, false));
|
||||
getSasOptions().set(sasOptions);
|
||||
|
||||
getPatchPathInZip().set(getExtension().getForgeUserdevProvider().getConfig().patches());
|
||||
getPatchesOriginalPrefix().set(getExtension().getForgeUserdevProvider().getConfig().patchesOriginalPrefix().orElseThrow());
|
||||
getPatchesModifiedPrefix().set(getExtension().getForgeUserdevProvider().getConfig().patchesModifiedPrefix().orElseThrow());
|
||||
|
||||
getSourceRemapperOptions().set(SourceRemapperService.TYPE.create(getProject(), sro -> {
|
||||
sro.getMappings().set(MappingsService.createOptionsWithProjectMappings(
|
||||
getProject(),
|
||||
getProject().provider(() -> "srg"),
|
||||
getProject().provider(() -> "named")
|
||||
));
|
||||
sro.getJavaCompileRelease().set(SourceRemapperService.getJavaCompileRelease(getProject()));
|
||||
sro.getClasspath().from(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
|
||||
}));
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void run() throws IOException {
|
||||
// Check that the jar is not processed
|
||||
final @Nullable MinecraftJarProcessorManager jarProcessorManager = MinecraftJarProcessorManager.create(getProject());
|
||||
|
||||
if (jarProcessorManager != null) {
|
||||
throw new UnsupportedOperationException("Cannot run Forge's patched decompilation with a processed Minecraft jar");
|
||||
}
|
||||
|
||||
try (var tempFiles = new TempFiles(); var serviceFactory = new ScopedServiceFactory()) {
|
||||
try (var tempFiles = getTempFiles().get(); var serviceFactory = new ScopedServiceFactory()) {
|
||||
Path cache = tempFiles.directory("loom-decompilation");
|
||||
|
||||
// Transform game jar before decompiling
|
||||
Path accessTransformed = cache.resolve("access-transformed.jar");
|
||||
MinecraftPatchedProvider.accessTransform(getProject(), getInputJar().get().getAsFile().toPath(), accessTransformed);
|
||||
Path sideAnnotationStripped = cache.resolve("side-annotation-stripped.jar");
|
||||
stripSideAnnotations(accessTransformed, sideAnnotationStripped);
|
||||
AccessTransformerService atService = serviceFactory.get(getAccessTransformerOptions());
|
||||
atService.execute(getInputJar().get().getAsFile().toPath(), accessTransformed);
|
||||
Path sideAnnotationStripped = getSideAnnotationStrippedMinecraftJar().get().getAsFile().toPath();
|
||||
stripSideAnnotations(accessTransformed, sideAnnotationStripped, serviceFactory);
|
||||
|
||||
// Step 1: decompile and patch with MCP patches
|
||||
Path rawDecompiled = decompileAndPatch(cache, sideAnnotationStripped);
|
||||
Path rawDecompiled = decompileAndPatch(serviceFactory);
|
||||
// Step 2: patch with Forge patches
|
||||
getLogger().lifecycle(":applying Forge patches");
|
||||
Path patched = sourcePatch(cache, rawDecompiled);
|
||||
@@ -123,40 +204,26 @@ public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
|
||||
}
|
||||
}
|
||||
|
||||
private Path decompileAndPatch(Path cache, Path gameJar) throws IOException {
|
||||
Path mcpCache = cache.resolve("mcp");
|
||||
Files.createDirectory(mcpCache);
|
||||
|
||||
MinecraftPatchedProvider patchedProvider = MinecraftPatchedProvider.get(getProject());
|
||||
McpExecutor mcp = patchedProvider.createMcpExecutor(mcpCache);
|
||||
mcp.setStepLogicProvider((name, type) -> {
|
||||
if (name.equals("rename")) {
|
||||
return Optional.of(new ConstantLogic(() -> gameJar));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
});
|
||||
mcp.enqueue("decompile");
|
||||
mcp.enqueue("patch");
|
||||
return mcp.execute();
|
||||
private Path decompileAndPatch(ScopedServiceFactory serviceFactory) throws IOException {
|
||||
final McpExecutor executor = serviceFactory.get(getMcpExecutorOptions());
|
||||
return executor.execute();
|
||||
}
|
||||
|
||||
private Path sourcePatch(Path cache, Path rawDecompiled) throws IOException {
|
||||
ForgeUserdevProvider userdev = getExtension().getForgeUserdevProvider();
|
||||
String patchPathInZip = userdev.getConfig().patches();
|
||||
String patchPathInZip = getPatchPathInZip().get();
|
||||
Path output = cache.resolve("patched.jar");
|
||||
Path rejects = cache.resolve("rejects");
|
||||
|
||||
CliOperation.Result<PatchOperation.PatchesSummary> result = PatchOperation.builder()
|
||||
.logTo(new LoggingOutputStream(getLogger(), LogLevel.INFO))
|
||||
.basePath(rawDecompiled)
|
||||
.patchesPath(userdev.getUserdevJar().toPath())
|
||||
.patchesPath(getSasOptions().get().getUserdevJar().get().getAsFile().toPath())
|
||||
.patchesPrefix(patchPathInZip)
|
||||
.outputPath(output)
|
||||
.mode(PatchMode.ACCESS)
|
||||
.rejectsPath(rejects)
|
||||
.aPrefix(userdev.getConfig().patchesOriginalPrefix().orElseThrow())
|
||||
.bPrefix(userdev.getConfig().patchesModifiedPrefix().orElseThrow())
|
||||
.aPrefix(getPatchesOriginalPrefix().get())
|
||||
.bPrefix(getPatchesModifiedPrefix().get())
|
||||
.build()
|
||||
.operate();
|
||||
|
||||
@@ -167,23 +234,20 @@ public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
|
||||
return output;
|
||||
}
|
||||
|
||||
private void remap(Path input, ServiceFactory serviceFactory) {
|
||||
SourceRemapper remapper = new SourceRemapper(getProject(), serviceFactory, "srg", "named");
|
||||
remapper.scheduleRemapSources(input.toFile(), getOutputJar().get().getAsFile(), false, true, () -> {
|
||||
});
|
||||
remapper.remapAll();
|
||||
private void remap(Path input, ServiceFactory serviceFactory) throws IOException {
|
||||
final SourceRemapperService remapperService = serviceFactory.get(getSourceRemapperOptions());
|
||||
remapperService.remapSourcesJar(input, getOutputJar().get().getAsFile().toPath());
|
||||
}
|
||||
|
||||
private void stripSideAnnotations(Path input, Path output) throws IOException {
|
||||
private void stripSideAnnotations(Path input, Path output, ServiceFactory serviceFactory) throws IOException {
|
||||
final Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
getLogger().lifecycle(":stripping side annotations");
|
||||
|
||||
try (var tempFiles = new TempFiles()) {
|
||||
final ForgeUserdevProvider userdevProvider = getExtension().getForgeUserdevProvider();
|
||||
final List<String> sass = userdevProvider.getConfig().sass();
|
||||
final List<String> sass = getSasOptions().get().getSass().get();
|
||||
final List<Path> sasPaths = new ArrayList<>();
|
||||
|
||||
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(userdevProvider.getUserdevJar(), false)) {
|
||||
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(getSasOptions().get().getUserdevJar().get().getAsFile(), false)) {
|
||||
for (String sasPath : sass) {
|
||||
try {
|
||||
final Path from = fs.getPath(sasPath);
|
||||
@@ -196,10 +260,9 @@ public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
|
||||
}
|
||||
}
|
||||
|
||||
final FileCollection classpath = DependencyDownloader.download(getProject(), ForgeTools.SIDE_STRIPPER, false, true);
|
||||
|
||||
ForgeToolValueSource.exec(getProject(), spec -> {
|
||||
spec.setClasspath(classpath);
|
||||
final ForgeToolService toolService = serviceFactory.get(getToolServiceOptions());
|
||||
toolService.exec(spec -> {
|
||||
spec.setClasspath(getSasOptions().get().getClasspath());
|
||||
spec.args(
|
||||
"--strip",
|
||||
"--input", input.toAbsolutePath().toString(),
|
||||
@@ -214,4 +277,15 @@ public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
|
||||
|
||||
getLogger().lifecycle(":side annotations stripped in " + stopwatch.stop());
|
||||
}
|
||||
|
||||
public interface SasOptions {
|
||||
@InputFile
|
||||
RegularFileProperty getUserdevJar();
|
||||
|
||||
@Input
|
||||
ListProperty<String> getSass();
|
||||
|
||||
@Classpath
|
||||
ConfigurableFileCollection getClasspath();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user