mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-30 21:05:58 -05:00
Merge remote-tracking branch 'upstream/exp/1.4' into exp/1.4
# Conflicts: # .gitignore # build.gradle # settings.gradle # src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java # src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java # src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java # src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java # src/main/java/net/fabricmc/loom/task/service/JarManifestService.java # src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java # src/main/java/net/fabricmc/loom/util/Constants.java
This commit is contained in:
@@ -99,7 +99,7 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
|
||||
|
||||
// Setup extensions
|
||||
project.getExtensions().create(LoomGradleExtensionAPI.class, "loom", LoomGradleExtensionImpl.class, project, LoomFiles.create(project));
|
||||
project.getExtensions().create("fabricApi", FabricApiExtension.class, project);
|
||||
project.getExtensions().create("fabricApi", FabricApiExtension.class);
|
||||
|
||||
for (Class<? extends Runnable> jobClass : SETUP_JOBS) {
|
||||
project.getObjects().newInstance(jobClass).run();
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
import org.gradle.api.publish.maven.MavenPublication;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -133,6 +134,8 @@ public interface LoomGradleExtensionAPI {
|
||||
|
||||
Property<String> getCustomMinecraftManifest();
|
||||
|
||||
SetProperty<String> getKnownIndyBsms();
|
||||
|
||||
/**
|
||||
* Disables the deprecated POM generation for a publication.
|
||||
* This is useful if you want to suppress deprecation warnings when you're not using software components.
|
||||
|
||||
@@ -49,6 +49,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
||||
import net.fabricmc.loom.extension.MixinExtension;
|
||||
import net.fabricmc.loom.task.PrepareJarRemapTask;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.LoomVersions;
|
||||
|
||||
/**
|
||||
* Normally javac invokes annotation processors, but when the scala or kapt plugin are installed they will want to invoke
|
||||
@@ -150,7 +151,7 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
|
||||
|
||||
// Add Mixin and mixin extensions (fabric-mixin-compile-extensions pulls mixin itself too)
|
||||
project.getDependencies().add(processorConfig.getName(),
|
||||
Constants.Dependencies.MIXIN_COMPILE_EXTENSIONS + Constants.Dependencies.Versions.MIXIN_COMPILE_EXTENSIONS);
|
||||
LoomVersions.MIXIN_COMPILE_EXTENSIONS.mavenNotation());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,11 @@ package net.fabricmc.loom.configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
@@ -41,25 +43,29 @@ import org.w3c.dom.NodeList;
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.util.download.DownloadException;
|
||||
|
||||
public class FabricApiExtension {
|
||||
private final Project project;
|
||||
|
||||
public FabricApiExtension(Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
public abstract class FabricApiExtension {
|
||||
@Inject
|
||||
public abstract Project getProject();
|
||||
|
||||
private static final HashMap<String, Map<String, String>> moduleVersionCache = new HashMap<>();
|
||||
private static final HashMap<String, Map<String, String>> deprecatedModuleVersionCache = new HashMap<>();
|
||||
|
||||
public Dependency module(String moduleName, String fabricApiVersion) {
|
||||
return project.getDependencies()
|
||||
return getProject().getDependencies()
|
||||
.create(getDependencyNotation(moduleName, fabricApiVersion));
|
||||
}
|
||||
|
||||
public String moduleVersion(String moduleName, String fabricApiVersion) {
|
||||
String moduleVersion = moduleVersionCache
|
||||
.computeIfAbsent(fabricApiVersion, this::populateModuleVersionMap)
|
||||
.computeIfAbsent(fabricApiVersion, this::getApiModuleVersions)
|
||||
.get(moduleName);
|
||||
|
||||
if (moduleVersion == null) {
|
||||
moduleVersion = deprecatedModuleVersionCache
|
||||
.computeIfAbsent(fabricApiVersion, this::getDeprecatedApiModuleVersions)
|
||||
.get(moduleName);
|
||||
}
|
||||
|
||||
if (moduleVersion == null) {
|
||||
throw new RuntimeException("Failed to find module version for module: " + moduleName);
|
||||
}
|
||||
@@ -71,9 +77,24 @@ public class FabricApiExtension {
|
||||
return String.format("net.fabricmc.fabric-api:%s:%s", moduleName, moduleVersion(moduleName, fabricApiVersion));
|
||||
}
|
||||
|
||||
private Map<String, String> populateModuleVersionMap(String fabricApiVersion) {
|
||||
File pomFile = getApiMavenPom(fabricApiVersion);
|
||||
private Map<String, String> getApiModuleVersions(String fabricApiVersion) {
|
||||
try {
|
||||
return populateModuleVersionMap(getApiMavenPom(fabricApiVersion));
|
||||
} catch (PomNotFoundException e) {
|
||||
throw new RuntimeException("Could not find fabric-api version: " + fabricApiVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> getDeprecatedApiModuleVersions(String fabricApiVersion) {
|
||||
try {
|
||||
return populateModuleVersionMap(getDeprecatedApiMavenPom(fabricApiVersion));
|
||||
} catch (PomNotFoundException e) {
|
||||
// Not all fabric-api versions have deprecated modules, return an empty map to cache this fact.
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> populateModuleVersionMap(File pomFile) {
|
||||
try {
|
||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
||||
@@ -101,27 +122,36 @@ public class FabricApiExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private File getApiMavenPom(String fabricApiVersion) {
|
||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
private File getApiMavenPom(String fabricApiVersion) throws PomNotFoundException {
|
||||
return getPom("fabric-api", fabricApiVersion);
|
||||
}
|
||||
|
||||
File mavenPom = new File(extension.getFiles().getUserCache(), "fabric-api/" + fabricApiVersion + ".pom");
|
||||
private File getDeprecatedApiMavenPom(String fabricApiVersion) throws PomNotFoundException {
|
||||
return getPom("fabric-api-deprecated", fabricApiVersion);
|
||||
}
|
||||
|
||||
if (project.getGradle().getStartParameter().isOffline()) {
|
||||
if (!mavenPom.exists()) {
|
||||
throw new RuntimeException("Cannot retrieve fabric-api pom due to being offline");
|
||||
}
|
||||
|
||||
return mavenPom;
|
||||
}
|
||||
private File getPom(String name, String version) throws PomNotFoundException {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||
final var mavenPom = new File(extension.getFiles().getUserCache(), "fabric-api/%s-%s.pom".formatted(name, version));
|
||||
|
||||
try {
|
||||
extension.download(String.format("https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/%1$s/fabric-api-%1$s.pom", fabricApiVersion))
|
||||
extension.download(String.format("https://maven.fabricmc.net/net/fabricmc/fabric-api/%2$s/%1$s/%2$s-%1$s.pom", version, name))
|
||||
.defaultCache()
|
||||
.downloadPath(mavenPom.toPath());
|
||||
} catch (DownloadException e) {
|
||||
throw new UncheckedIOException("Failed to download maven info for " + fabricApiVersion, e);
|
||||
if (e.getStatusCode() == 404) {
|
||||
throw new PomNotFoundException(e);
|
||||
}
|
||||
|
||||
throw new UncheckedIOException("Failed to download maven info to " + mavenPom.getName(), e);
|
||||
}
|
||||
|
||||
return mavenPom;
|
||||
}
|
||||
|
||||
private static class PomNotFoundException extends Exception {
|
||||
PomNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,16 @@
|
||||
|
||||
package net.fabricmc.loom.configuration;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ExternalModuleDependency;
|
||||
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.LoomRepositoryPlugin;
|
||||
@@ -36,6 +41,8 @@ import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
|
||||
public record InstallerData(String version, JsonObject installerJson) {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(InstallerData.class);
|
||||
|
||||
public void applyToProject(Project project) {
|
||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
|
||||
@@ -45,35 +52,61 @@ public record InstallerData(String version, JsonObject installerJson) {
|
||||
|
||||
extension.setInstallerData(this);
|
||||
|
||||
JsonObject libraries = installerJson.get("libraries").getAsJsonObject();
|
||||
Configuration loaderDepsConfig = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES);
|
||||
Configuration apDepsConfig = project.getConfigurations().getByName("annotationProcessor");
|
||||
final JsonObject libraries = installerJson.get("libraries").getAsJsonObject();
|
||||
|
||||
libraries.get("common").getAsJsonArray().forEach(jsonElement -> {
|
||||
String name = jsonElement.getAsJsonObject().get("name").getAsString();
|
||||
project.getLogger().debug("Adding dependency ({}) from installer JSON", name);
|
||||
applyDependendencies(libraries.get("common").getAsJsonArray(), project);
|
||||
|
||||
// Apply development dependencies if they exist.
|
||||
if (libraries.has("development")) {
|
||||
applyDependendencies(libraries.get("development").getAsJsonArray(), project);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyDependendencies(JsonArray jsonArray, Project project) {
|
||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
Configuration loaderDepsConfig = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES);
|
||||
Configuration annotationProcessor = project.getConfigurations().getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME);
|
||||
|
||||
for (JsonElement jsonElement : jsonArray) {
|
||||
final JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
final String name = jsonObject.get("name").getAsString();
|
||||
|
||||
LOGGER.debug("Adding dependency ({}) from installer JSON", name);
|
||||
|
||||
ExternalModuleDependency modDep = (ExternalModuleDependency) project.getDependencies().create(name);
|
||||
modDep.setTransitive(false);
|
||||
modDep.setTransitive(false); // Match the launcher in not being transitive
|
||||
loaderDepsConfig.getDependencies().add(modDep);
|
||||
|
||||
// TODO: work around until https://github.com/FabricMC/Mixin/pull/60 and https://github.com/FabricMC/fabric-mixin-compile-extensions/issues/14 is fixed.
|
||||
// Work around https://github.com/FabricMC/Mixin/pull/60 and https://github.com/FabricMC/fabric-mixin-compile-extensions/issues/14.
|
||||
if (!IdeaUtils.isIdeaSync() && extension.getMixin().getUseLegacyMixinAp().get()) {
|
||||
apDepsConfig.getDependencies().add(modDep);
|
||||
annotationProcessor.getDependencies().add(modDep);
|
||||
}
|
||||
|
||||
// If user choose to use dependencyResolutionManagement, then they should declare
|
||||
// these repositories manually in the settings file.
|
||||
if (jsonElement.getAsJsonObject().has("url") && !project.getGradle().getPlugins().hasPlugin(LoomRepositoryPlugin.class)) {
|
||||
String url = jsonElement.getAsJsonObject().get("url").getAsString();
|
||||
long count = project.getRepositories().stream().filter(artifactRepository -> artifactRepository instanceof MavenArtifactRepository)
|
||||
.map(artifactRepository -> (MavenArtifactRepository) artifactRepository)
|
||||
.filter(mavenArtifactRepository -> mavenArtifactRepository.getUrl().toString().equalsIgnoreCase(url)).count();
|
||||
|
||||
if (count == 0) {
|
||||
project.getRepositories().maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(jsonElement.getAsJsonObject().get("url").getAsString()));
|
||||
}
|
||||
if (project.getGradle().getPlugins().hasPlugin(LoomRepositoryPlugin.class)) {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
|
||||
addRepository(jsonObject, project);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRepository(JsonObject jsonObject, Project project) {
|
||||
if (!jsonObject.has("url")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String url = jsonObject.get("url").getAsString();
|
||||
final boolean isPresent = project.getRepositories().stream()
|
||||
.filter(artifactRepository -> artifactRepository instanceof MavenArtifactRepository)
|
||||
.map(artifactRepository -> (MavenArtifactRepository) artifactRepository)
|
||||
.anyMatch(mavenArtifactRepository -> mavenArtifactRepository.getUrl().toString().equalsIgnoreCase(url));
|
||||
|
||||
if (isPresent) {
|
||||
return;
|
||||
}
|
||||
|
||||
project.getRepositories().maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(jsonObject.get("url").getAsString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.gradle.api.plugins.JavaPlugin;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.LoomVersions;
|
||||
import net.fabricmc.loom.util.gradle.GradleUtils;
|
||||
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
||||
|
||||
@@ -106,10 +107,10 @@ public abstract class LoomConfigurations implements Runnable {
|
||||
extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_RUNTIME_LIBRARIES);
|
||||
|
||||
// Add the dev time dependencies
|
||||
getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR);
|
||||
getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER);
|
||||
getDependencies().add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS);
|
||||
getDependencies().add(JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME, Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS);
|
||||
getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, LoomVersions.DEV_LAUNCH_INJECTOR.mavenNotation());
|
||||
getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, LoomVersions.TERMINAL_CONSOLE_APPENDER.mavenNotation());
|
||||
getDependencies().add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation());
|
||||
getDependencies().add(JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME, LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation());
|
||||
|
||||
GradleUtils.afterSuccessfulEvaluation(getProject(), () -> {
|
||||
if (extension.shouldGenerateSrgTiny()) {
|
||||
|
||||
@@ -170,9 +170,10 @@ public class ModProcessor {
|
||||
MemoryMappingTree mappings = mappingConfiguration.getMappingsService(serviceManager, srg).getMappingTree();
|
||||
LoggerFilter.replaceSystemOut();
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
|
||||
.withKnownIndyBsm(extension.getKnownIndyBsms().get())
|
||||
.withMappings(TinyRemapperHelper.create(mappingConfiguration.getMappingsService(serviceManager).getMappingTree(), fromM, toM, false))
|
||||
.logger(project.getLogger()::lifecycle)
|
||||
.logUnknownInvokeDynamic(false)
|
||||
.withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false))
|
||||
.renameInvalidLocals(false)
|
||||
.extraAnalyzeVisitor(AccessWidenerAnalyzeVisitorProvider.createFromMods(fromM, remapList, extension.getPlatform().get()));
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.function.Predicate;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.library.Library;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryContext;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessor;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.LoomVersions;
|
||||
import net.fabricmc.loom.util.Platform;
|
||||
|
||||
public class LoomNativeSupportLibraryProcessor extends LibraryProcessor {
|
||||
@@ -56,7 +56,7 @@ public class LoomNativeSupportLibraryProcessor extends LibraryProcessor {
|
||||
|
||||
@Override
|
||||
public Predicate<Library> apply(Consumer<Library> dependencyConsumer) {
|
||||
dependencyConsumer.accept(Library.fromMaven(Constants.Dependencies.NATIVE_SUPPORT + Constants.Dependencies.Versions.NATIVE_SUPPORT_VERSION, Library.Target.LOCAL_MOD));
|
||||
dependencyConsumer.accept(Library.fromMaven(LoomVersions.NATIVE_SUPPORT.mavenNotation(), Library.Target.LOCAL_MOD));
|
||||
return ALLOW_ALL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2018-2020 FabricMC
|
||||
* Copyright (c) 2018-2023 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,14 +24,27 @@
|
||||
|
||||
package net.fabricmc.loom.decompilers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.NamedDomainObjectProvider;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
|
||||
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
|
||||
import net.fabricmc.loom.decompilers.cfr.LoomCFRDecompiler;
|
||||
import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler;
|
||||
import net.fabricmc.loom.decompilers.vineflower.VineflowerDecompiler;
|
||||
import net.fabricmc.loom.util.LoomVersions;
|
||||
import net.fabricmc.loom.util.ZipUtils;
|
||||
|
||||
public abstract class DecompilerConfiguration implements Runnable {
|
||||
@Inject
|
||||
@@ -39,11 +52,118 @@ public abstract class DecompilerConfiguration implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
registerDecompiler(getProject(), "fernFlower", FabricFernFlowerDecompiler.class);
|
||||
registerDecompiler(getProject(), "cfr", LoomCFRDecompiler.class);
|
||||
var fernflowerConfiguration = createConfiguration("fernflower", LoomVersions.FERNFLOWER);
|
||||
var cfrConfiguration = createConfiguration("cfr", LoomVersions.CFR);
|
||||
var vineflowerConfiguration = createConfiguration("vineflower", LoomVersions.VINEFLOWER);
|
||||
|
||||
registerDecompiler(getProject(), "fernFlower", BuiltinFernflower.class, fernflowerConfiguration);
|
||||
registerDecompiler(getProject(), "cfr", BuiltinCfr.class, cfrConfiguration);
|
||||
registerDecompiler(getProject(), "vineflower", BuiltinVineflower.class, vineflowerConfiguration);
|
||||
}
|
||||
|
||||
private void registerDecompiler(Project project, String name, Class<? extends LoomDecompiler> decompilerClass) {
|
||||
LoomGradleExtension.get(project).getDecompilerOptions().register(name, options -> options.getDecompilerClassName().set(decompilerClass.getName()));
|
||||
private NamedDomainObjectProvider<Configuration> createConfiguration(String name, LoomVersions version) {
|
||||
final String configurationName = name + "DecompilerClasspath";
|
||||
NamedDomainObjectProvider<Configuration> configuration = getProject().getConfigurations().register(configurationName);
|
||||
getProject().getDependencies().add(configurationName, version.mavenNotation());
|
||||
return configuration;
|
||||
}
|
||||
|
||||
private void registerDecompiler(Project project, String name, Class<? extends LoomDecompiler> decompilerClass, NamedDomainObjectProvider<Configuration> configuration) {
|
||||
LoomGradleExtension.get(project).getDecompilerOptions().register(name, options -> {
|
||||
options.getDecompilerClassName().set(decompilerClass.getName());
|
||||
options.getClasspath().from(configuration);
|
||||
});
|
||||
}
|
||||
|
||||
// We need to wrap the internal API with the public API.
|
||||
// This is needed as the sourceset containing fabric's decompilers do not have access to loom classes.
|
||||
private abstract static sealed class BuiltinDecompiler implements LoomDecompiler permits BuiltinFernflower, BuiltinCfr, BuiltinVineflower {
|
||||
private final LoomInternalDecompiler internalDecompiler;
|
||||
|
||||
BuiltinDecompiler(LoomInternalDecompiler internalDecompiler) {
|
||||
this.internalDecompiler = internalDecompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
|
||||
final Logger slf4jLogger = LoggerFactory.getLogger(internalDecompiler.getClass());
|
||||
|
||||
final var logger = new LoomInternalDecompiler.Logger() {
|
||||
@Override
|
||||
public void accept(String data) throws IOException {
|
||||
metaData.logger().accept(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg) {
|
||||
slf4jLogger.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
internalDecompiler.decompile(new LoomInternalDecompiler.Context() {
|
||||
@Override
|
||||
public Path compiledJar() {
|
||||
return compiledJar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path sourcesDestination() {
|
||||
return sourcesDestination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path linemapDestination() {
|
||||
return linemapDestination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int numberOfThreads() {
|
||||
return metaData.numberOfThreads();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path javaDocs() {
|
||||
return metaData.javaDocs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Path> libraries() {
|
||||
return metaData.libraries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoomInternalDecompiler.Logger logger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> options() {
|
||||
return metaData.options();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] unpackZip(Path zip, String path) throws IOException {
|
||||
return ZipUtils.unpack(zip, path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BuiltinFernflower extends BuiltinDecompiler {
|
||||
public BuiltinFernflower() {
|
||||
super(new FabricFernFlowerDecompiler());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BuiltinCfr extends BuiltinDecompiler {
|
||||
public BuiltinCfr() {
|
||||
super(new LoomCFRDecompiler());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BuiltinVineflower extends BuiltinDecompiler {
|
||||
public BuiltinVineflower() {
|
||||
super(new VineflowerDecompiler());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2021 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.decompilers.cfr;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.benf.cfr.reader.bytecode.analysis.types.JavaRefTypeInstance;
|
||||
import org.benf.cfr.reader.bytecode.analysis.types.JavaTypeInstance;
|
||||
import org.benf.cfr.reader.bytecode.analysis.types.MethodPrototype;
|
||||
import org.benf.cfr.reader.entities.AccessFlag;
|
||||
import org.benf.cfr.reader.entities.ClassFile;
|
||||
import org.benf.cfr.reader.entities.ClassFileField;
|
||||
import org.benf.cfr.reader.entities.Field;
|
||||
import org.benf.cfr.reader.mapping.NullMapping;
|
||||
import org.benf.cfr.reader.util.output.DelegatingDumper;
|
||||
import org.benf.cfr.reader.util.output.Dumper;
|
||||
|
||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||
import net.fabricmc.mappingio.MappingReader;
|
||||
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
|
||||
import net.fabricmc.mappingio.tree.MappingTree;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
public class CFRObfuscationMapping extends NullMapping {
|
||||
private final MappingTree mappingTree;
|
||||
|
||||
public CFRObfuscationMapping(Path mappings) {
|
||||
mappingTree = readMappings(mappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dumper wrap(Dumper d) {
|
||||
return new JavadocProvidingDumper(d);
|
||||
}
|
||||
|
||||
private static MappingTree readMappings(Path input) {
|
||||
try (BufferedReader reader = Files.newBufferedReader(input)) {
|
||||
MemoryMappingTree mappingTree = new MemoryMappingTree();
|
||||
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, MappingsNamespace.NAMED.toString());
|
||||
MappingReader.read(reader, nsSwitch);
|
||||
|
||||
return mappingTree;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to read mappings", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class JavadocProvidingDumper extends DelegatingDumper {
|
||||
JavadocProvidingDumper(Dumper delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dumper dumpClassDoc(JavaTypeInstance owner) {
|
||||
MappingTree.ClassMapping mapping = getClassMapping(owner);
|
||||
|
||||
if (mapping == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
List<String> recordComponentDocs = new LinkedList<>();
|
||||
|
||||
if (isRecord(owner)) {
|
||||
ClassFile classFile = ((JavaRefTypeInstance) owner).getClassFile();
|
||||
|
||||
for (ClassFileField field : classFile.getFields()) {
|
||||
if (field.getField().testAccessFlag(AccessFlag.ACC_STATIC)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MappingTree.FieldMapping fieldMapping = mapping.getField(field.getFieldName(), field.getField().getDescriptor());
|
||||
|
||||
if (fieldMapping == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String comment = fieldMapping.getComment();
|
||||
|
||||
if (comment != null) {
|
||||
recordComponentDocs.add(String.format("@param %s %s", fieldMapping.getSrcName(), comment));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String comment = mapping.getComment();
|
||||
|
||||
if (comment != null || !recordComponentDocs.isEmpty()) {
|
||||
print("/**").newln();
|
||||
|
||||
if (comment != null) {
|
||||
for (String line : comment.split("\\R")) {
|
||||
print(" * ").print(line).newln();
|
||||
}
|
||||
|
||||
if (!recordComponentDocs.isEmpty()) {
|
||||
print(" * ").newln();
|
||||
}
|
||||
}
|
||||
|
||||
if (comment != null && !recordComponentDocs.isEmpty()) {
|
||||
print(" * ");
|
||||
}
|
||||
|
||||
for (String componentDoc : recordComponentDocs) {
|
||||
print(" * ").print(componentDoc).newln();
|
||||
}
|
||||
|
||||
print(" */").newln();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dumper dumpMethodDoc(MethodPrototype method) {
|
||||
MappingTree.ClassMapping classMapping = getClassMapping(method.getOwner());
|
||||
|
||||
if (classMapping == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
List<String> lines = new ArrayList<>();
|
||||
MappingTree.MethodMapping mapping = classMapping.getMethod(method.getName(), method.getOriginalDescriptor());
|
||||
|
||||
if (mapping != null) {
|
||||
String comment = mapping.getComment();
|
||||
|
||||
if (comment != null) {
|
||||
lines.addAll(Arrays.asList(comment.split("\\R")));
|
||||
}
|
||||
|
||||
final Collection<? extends MappingTree.MethodArgMapping> methodArgs = mapping.getArgs();
|
||||
final List<String> params = new ArrayList<>();
|
||||
|
||||
for (MappingTree.MethodArgMapping arg : methodArgs) {
|
||||
String argComment = arg.getComment();
|
||||
|
||||
if (argComment != null) {
|
||||
params.addAll(Arrays.asList(("@param " + arg.getSrcName() + " " + argComment).split("\\R")));
|
||||
}
|
||||
}
|
||||
|
||||
// Add a blank line between params and the comment.
|
||||
if (!lines.isEmpty() && !params.isEmpty()) {
|
||||
lines.add("");
|
||||
}
|
||||
|
||||
lines.addAll(params);
|
||||
}
|
||||
|
||||
if (!lines.isEmpty()) {
|
||||
print("/**").newln();
|
||||
|
||||
for (String line : lines) {
|
||||
print(" * ").print(line).newln();
|
||||
}
|
||||
|
||||
print(" */").newln();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dumper dumpFieldDoc(Field field, JavaTypeInstance owner) {
|
||||
// None static fields in records are handled in the class javadoc.
|
||||
if (isRecord(owner) && !isStatic(field)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
MappingTree.ClassMapping classMapping = getClassMapping(owner);
|
||||
|
||||
if (classMapping == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
MappingTree.FieldMapping fieldMapping = classMapping.getField(field.getFieldName(), field.getDescriptor());
|
||||
|
||||
if (fieldMapping != null) {
|
||||
dumpComment(fieldMapping.getComment());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private MappingTree.ClassMapping getClassMapping(JavaTypeInstance type) {
|
||||
String qualifiedName = type.getRawName().replace('.', '/');
|
||||
return mappingTree.getClass(qualifiedName);
|
||||
}
|
||||
|
||||
private boolean isRecord(JavaTypeInstance javaTypeInstance) {
|
||||
if (javaTypeInstance instanceof JavaRefTypeInstance) {
|
||||
ClassFile classFile = ((JavaRefTypeInstance) javaTypeInstance).getClassFile();
|
||||
return classFile.getClassSignature().getSuperClass().getRawName().equals("java.lang.Record");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isStatic(Field field) {
|
||||
return field.testAccessFlag(AccessFlag.ACC_STATIC);
|
||||
}
|
||||
|
||||
private void dumpComment(String comment) {
|
||||
if (comment == null || comment.isBlank()) {
|
||||
return;
|
||||
}
|
||||
|
||||
print("/**").newln();
|
||||
|
||||
for (String line : comment.split("\n")) {
|
||||
print(" * ").print(line).newln();
|
||||
}
|
||||
|
||||
print(" */").newln();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2021 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.decompilers.cfr;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import org.benf.cfr.reader.api.OutputSinkFactory;
|
||||
import org.benf.cfr.reader.api.SinkReturns;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.util.IOStringConsumer;
|
||||
|
||||
public class CFRSinkFactory implements OutputSinkFactory {
|
||||
private static final Logger ERROR_LOGGER = LoggerFactory.getLogger(CFRSinkFactory.class);
|
||||
|
||||
private final JarOutputStream outputStream;
|
||||
private final IOStringConsumer logger;
|
||||
private final Set<String> addedDirectories = new HashSet<>();
|
||||
private final Map<String, Map<Integer, Integer>> lineMap = new TreeMap<>();
|
||||
|
||||
public CFRSinkFactory(JarOutputStream outputStream, IOStringConsumer logger) {
|
||||
this.outputStream = outputStream;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SinkClass> getSupportedSinks(SinkType sinkType, Collection<SinkClass> available) {
|
||||
return switch (sinkType) {
|
||||
case JAVA -> Collections.singletonList(SinkClass.DECOMPILED);
|
||||
case LINENUMBER -> Collections.singletonList(SinkClass.LINE_NUMBER_MAPPING);
|
||||
default -> Collections.emptyList();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Sink<T> getSink(SinkType sinkType, SinkClass sinkClass) {
|
||||
return switch (sinkType) {
|
||||
case JAVA -> (Sink<T>) decompiledSink();
|
||||
case LINENUMBER -> (Sink<T>) lineNumberMappingSink();
|
||||
case EXCEPTION -> (e) -> ERROR_LOGGER.error((String) e);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private Sink<SinkReturns.Decompiled> decompiledSink() {
|
||||
return sinkable -> {
|
||||
String filename = sinkable.getPackageName().replace('.', '/');
|
||||
if (!filename.isEmpty()) filename += "/";
|
||||
filename += sinkable.getClassName() + ".java";
|
||||
|
||||
byte[] data = sinkable.getJava().getBytes(Charsets.UTF_8);
|
||||
|
||||
writeToJar(filename, data);
|
||||
};
|
||||
}
|
||||
|
||||
private Sink<SinkReturns.LineNumberMapping> lineNumberMappingSink() {
|
||||
return sinkable -> {
|
||||
final String className = sinkable.getClassName();
|
||||
final NavigableMap<Integer, Integer> classFileMappings = sinkable.getClassFileMappings();
|
||||
final NavigableMap<Integer, Integer> mappings = sinkable.getMappings();
|
||||
|
||||
if (classFileMappings == null || mappings == null) return;
|
||||
|
||||
for (Map.Entry<Integer, Integer> entry : mappings.entrySet()) {
|
||||
// New line number
|
||||
Integer dstLineNumber = entry.getValue();
|
||||
|
||||
// Line mapping in the original jar
|
||||
Integer srcLineNumber = classFileMappings.get(entry.getKey());
|
||||
|
||||
if (srcLineNumber == null || dstLineNumber == null) continue;
|
||||
|
||||
lineMap.computeIfAbsent(className, (c) -> new TreeMap<>()).put(srcLineNumber, dstLineNumber);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private synchronized void writeToJar(String filename, byte[] data) {
|
||||
String[] path = filename.split("/");
|
||||
String pathPart = "";
|
||||
|
||||
for (int i = 0; i < path.length - 1; i++) {
|
||||
pathPart += path[i] + "/";
|
||||
|
||||
if (addedDirectories.add(pathPart)) {
|
||||
JarEntry entry = new JarEntry(pathPart);
|
||||
entry.setTime(new Date().getTime());
|
||||
|
||||
try {
|
||||
outputStream.putNextEntry(entry);
|
||||
outputStream.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JarEntry entry = new JarEntry(filename);
|
||||
entry.setTime(new Date().getTime());
|
||||
entry.setSize(data.length);
|
||||
|
||||
try {
|
||||
logger.accept("Writing: " + filename);
|
||||
outputStream.putNextEntry(entry);
|
||||
outputStream.write(data);
|
||||
outputStream.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Map<Integer, Integer>> getLineMap() {
|
||||
return Collections.unmodifiableMap(lineMap);
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2021 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.decompilers.cfr;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.benf.cfr.reader.Driver;
|
||||
import org.benf.cfr.reader.state.ClassFileSourceImpl;
|
||||
import org.benf.cfr.reader.state.DCCommonState;
|
||||
import org.benf.cfr.reader.util.AnalysisType;
|
||||
import org.benf.cfr.reader.util.getopt.Options;
|
||||
import org.benf.cfr.reader.util.getopt.OptionsImpl;
|
||||
import org.benf.cfr.reader.util.output.SinkDumperFactory;
|
||||
|
||||
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
|
||||
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
|
||||
|
||||
public final class LoomCFRDecompiler implements LoomDecompiler {
|
||||
private static final Map<String, String> DECOMPILE_OPTIONS = Map.of(
|
||||
"renameillegalidents", "true",
|
||||
"trackbytecodeloc", "true",
|
||||
"comments", "false"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
|
||||
final String path = compiledJar.toAbsolutePath().toString();
|
||||
final Map<String, String> allOptions = new HashMap<>(DECOMPILE_OPTIONS);
|
||||
allOptions.putAll(metaData.options());
|
||||
|
||||
final Options options = OptionsImpl.getFactory().create(allOptions);
|
||||
|
||||
ClassFileSourceImpl classFileSource = new ClassFileSourceImpl(options);
|
||||
|
||||
for (Path library : metaData.libraries()) {
|
||||
classFileSource.addJarContent(library.toAbsolutePath().toString(), AnalysisType.JAR);
|
||||
}
|
||||
|
||||
classFileSource.informAnalysisRelativePathDetail(null, null);
|
||||
|
||||
DCCommonState state = new DCCommonState(options, classFileSource);
|
||||
|
||||
if (metaData.javaDocs() != null) {
|
||||
state = new DCCommonState(state, new CFRObfuscationMapping(metaData.javaDocs()));
|
||||
}
|
||||
|
||||
final Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
||||
|
||||
Map<String, Map<Integer, Integer>> lineMap;
|
||||
|
||||
try (JarOutputStream outputStream = new JarOutputStream(Files.newOutputStream(sourcesDestination), manifest)) {
|
||||
CFRSinkFactory cfrSinkFactory = new CFRSinkFactory(outputStream, metaData.logger());
|
||||
SinkDumperFactory dumperFactory = new SinkDumperFactory(cfrSinkFactory, options);
|
||||
|
||||
Driver.doJar(state, path, AnalysisType.JAR, dumperFactory);
|
||||
|
||||
lineMap = cfrSinkFactory.getLineMap();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to decompile", e);
|
||||
}
|
||||
|
||||
writeLineMap(linemapDestination, lineMap);
|
||||
}
|
||||
|
||||
private void writeLineMap(Path output, Map<String, Map<Integer, Integer>> lineMap) {
|
||||
try (Writer writer = Files.newBufferedWriter(output, StandardCharsets.UTF_8)) {
|
||||
for (Map.Entry<String, Map<Integer, Integer>> classEntry : lineMap.entrySet()) {
|
||||
final String name = classEntry.getKey().replace(".", "/");
|
||||
|
||||
final Map<Integer, Integer> mapping = classEntry.getValue();
|
||||
|
||||
int maxLine = 0;
|
||||
int maxLineDest = 0;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (Map.Entry<Integer, Integer> mappingEntry : mapping.entrySet()) {
|
||||
final int src = mappingEntry.getKey();
|
||||
final int dst = mappingEntry.getValue();
|
||||
|
||||
maxLine = Math.max(maxLine, src);
|
||||
maxLineDest = Math.max(maxLineDest, dst);
|
||||
|
||||
builder.append("\t").append(src).append("\t").append(dst).append("\n");
|
||||
}
|
||||
|
||||
writer.write(String.format(Locale.ENGLISH, "%s\t%d\t%d\n", name, maxLine, maxLineDest));
|
||||
writer.write(builder.toString());
|
||||
writer.write("\n");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to write line map", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2019-2021 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.decompilers.fernflower;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.Fernflower;
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
|
||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
|
||||
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
|
||||
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
|
||||
|
||||
public final class FabricFernFlowerDecompiler implements LoomDecompiler {
|
||||
@Override
|
||||
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
|
||||
final Map<String, Object> options = new HashMap<>(
|
||||
Map.of(
|
||||
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1",
|
||||
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1",
|
||||
IFernflowerPreferences.REMOVE_SYNTHETIC, "1",
|
||||
IFernflowerPreferences.LOG_LEVEL, "trace",
|
||||
IFernflowerPreferences.THREADS, String.valueOf(metaData.numberOfThreads()),
|
||||
IFernflowerPreferences.INDENT_STRING, "\t",
|
||||
IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(metaData.javaDocs().toFile())
|
||||
)
|
||||
);
|
||||
|
||||
options.putAll(metaData.options());
|
||||
|
||||
IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile);
|
||||
Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, new FernflowerLogger(metaData.logger()));
|
||||
|
||||
for (Path library : metaData.libraries()) {
|
||||
ff.addLibrary(library.toFile());
|
||||
}
|
||||
|
||||
ff.addSource(compiledJar.toFile());
|
||||
|
||||
try {
|
||||
ff.decompileContext();
|
||||
} finally {
|
||||
ff.clearContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2021 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.decompilers.fernflower;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
|
||||
|
||||
import net.fabricmc.loom.util.IOStringConsumer;
|
||||
|
||||
public class FernflowerLogger extends IFernflowerLogger {
|
||||
private final IOStringConsumer logger;
|
||||
|
||||
public FernflowerLogger(IOStringConsumer logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMessage(String message, Severity severity) {
|
||||
if (message.contains("Inconsistent inner class entries for")) return;
|
||||
if (message.contains("Inconsistent generic signature in method")) return;
|
||||
System.err.println(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMessage(String message, Severity severity, Throwable t) {
|
||||
writeMessage(message, severity);
|
||||
}
|
||||
|
||||
private void write(String data) {
|
||||
try {
|
||||
logger.accept(data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to log", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startReadingClass(String className) {
|
||||
write("Decompiling " + className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startClass(String className) {
|
||||
write("Decompiling " + className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startWriteClass(String className) {
|
||||
// Nope
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMethod(String methodName) {
|
||||
// Nope
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endMethod() {
|
||||
// Nope
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2019-2020 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.decompilers.fernflower;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.DecompilerContext;
|
||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricResultSaver;
|
||||
|
||||
/**
|
||||
* Created by covers1624 on 18/02/19.
|
||||
*/
|
||||
public class ThreadSafeResultSaver implements IResultSaver, IFabricResultSaver {
|
||||
private final Supplier<File> output;
|
||||
private final Supplier<File> lineMapFile;
|
||||
|
||||
public Map<String, ZipOutputStream> outputStreams = new HashMap<>();
|
||||
public Map<String, ExecutorService> saveExecutors = new HashMap<>();
|
||||
public PrintWriter lineMapWriter;
|
||||
|
||||
public ThreadSafeResultSaver(Supplier<File> output, Supplier<File> lineMapFile) {
|
||||
this.output = output;
|
||||
this.lineMapFile = lineMapFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createArchive(String path, String archiveName, Manifest manifest) {
|
||||
String key = path + "/" + archiveName;
|
||||
File file = output.get();
|
||||
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
ZipOutputStream zos = manifest == null ? new ZipOutputStream(fos) : new JarOutputStream(fos, manifest);
|
||||
outputStreams.put(key, zos);
|
||||
saveExecutors.put(key, Executors.newSingleThreadExecutor());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to create archive: " + file, e);
|
||||
}
|
||||
|
||||
if (lineMapFile.get() != null) {
|
||||
try {
|
||||
lineMapWriter = new PrintWriter(new FileWriter(lineMapFile.get()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to create line mapping file: " + lineMapFile.get(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) {
|
||||
this.saveClassEntry(path, archiveName, qualifiedName, entryName, content, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content, int[] mapping) {
|
||||
String key = path + "/" + archiveName;
|
||||
ExecutorService executor = saveExecutors.get(key);
|
||||
executor.submit(() -> {
|
||||
ZipOutputStream zos = outputStreams.get(key);
|
||||
|
||||
try {
|
||||
zos.putNextEntry(new ZipEntry(entryName));
|
||||
|
||||
if (content != null) {
|
||||
zos.write(content.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
DecompilerContext.getLogger().writeMessage("Cannot write entry " + entryName, e);
|
||||
}
|
||||
|
||||
if (mapping != null && lineMapWriter != null) {
|
||||
int maxLine = 0;
|
||||
int maxLineDest = 0;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < mapping.length; i += 2) {
|
||||
maxLine = Math.max(maxLine, mapping[i]);
|
||||
maxLineDest = Math.max(maxLineDest, mapping[i + 1]);
|
||||
builder.append("\t").append(mapping[i]).append("\t").append(mapping[i + 1]).append("\n");
|
||||
}
|
||||
|
||||
lineMapWriter.println(qualifiedName + "\t" + maxLine + "\t" + maxLineDest);
|
||||
lineMapWriter.println(builder.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeArchive(String path, String archiveName) {
|
||||
String key = path + "/" + archiveName;
|
||||
ExecutorService executor = saveExecutors.get(key);
|
||||
Future<?> closeFuture = executor.submit(() -> {
|
||||
ZipOutputStream zos = outputStreams.get(key);
|
||||
|
||||
try {
|
||||
zos.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to close zip. " + key, e);
|
||||
}
|
||||
});
|
||||
executor.shutdown();
|
||||
|
||||
try {
|
||||
closeFuture.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
outputStreams.remove(key);
|
||||
saveExecutors.remove(key);
|
||||
|
||||
if (lineMapWriter != null) {
|
||||
lineMapWriter.flush();
|
||||
lineMapWriter.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveFolder(String path) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFile(String source, String path, String entryName) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveDirEntry(String path, String archiveName, String entryName) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyEntry(String source, String path, String archiveName, String entry) {
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2019-2021 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.decompilers.fernflower;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.java.decompiler.struct.StructClass;
|
||||
import org.jetbrains.java.decompiler.struct.StructField;
|
||||
import org.jetbrains.java.decompiler.struct.StructMethod;
|
||||
import org.jetbrains.java.decompiler.struct.StructRecordComponent;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
|
||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||
import net.fabricmc.mappingio.MappingReader;
|
||||
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
|
||||
import net.fabricmc.mappingio.tree.MappingTree;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
public class TinyJavadocProvider implements IFabricJavadocProvider {
|
||||
private final MappingTree mappingTree;
|
||||
|
||||
public TinyJavadocProvider(File tinyFile) {
|
||||
mappingTree = readMappings(tinyFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassDoc(StructClass structClass) {
|
||||
MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
|
||||
|
||||
if (classMapping == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isRecord(structClass)) {
|
||||
return classMapping.getComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the record component docs here.
|
||||
*
|
||||
* Record components are mapped via the field name, thus take the docs from the fields and display them on then class.
|
||||
*/
|
||||
List<String> parts = new ArrayList<>();
|
||||
|
||||
if (classMapping.getComment() != null) {
|
||||
parts.add(classMapping.getComment());
|
||||
}
|
||||
|
||||
boolean addedParam = false;
|
||||
|
||||
for (StructRecordComponent component : structClass.getRecordComponents()) {
|
||||
// The component will always match the field name and descriptor
|
||||
MappingTree.FieldMapping fieldMapping = classMapping.getField(component.getName(), component.getDescriptor());
|
||||
|
||||
if (fieldMapping == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String comment = fieldMapping.getComment();
|
||||
|
||||
if (comment != null) {
|
||||
if (!addedParam && classMapping.getComment() != null) {
|
||||
//Add a blank line before components when the class has a comment
|
||||
parts.add("");
|
||||
addedParam = true;
|
||||
}
|
||||
|
||||
parts.add(String.format("@param %s %s", fieldMapping.getName(MappingsNamespace.NAMED.toString()), comment));
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.join("\n", parts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFieldDoc(StructClass structClass, StructField structField) {
|
||||
// None static fields in records are handled in the class javadoc.
|
||||
if (isRecord(structClass) && !isStatic(structField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
|
||||
|
||||
if (classMapping == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MappingTree.FieldMapping fieldMapping = classMapping.getField(structField.getName(), structField.getDescriptor());
|
||||
|
||||
return fieldMapping != null ? fieldMapping.getComment() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodDoc(StructClass structClass, StructMethod structMethod) {
|
||||
MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
|
||||
|
||||
if (classMapping == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MappingTree.MethodMapping methodMapping = classMapping.getMethod(structMethod.getName(), structMethod.getDescriptor());
|
||||
|
||||
if (methodMapping != null) {
|
||||
List<String> parts = new ArrayList<>();
|
||||
|
||||
if (methodMapping.getComment() != null) {
|
||||
parts.add(methodMapping.getComment());
|
||||
}
|
||||
|
||||
boolean addedParam = false;
|
||||
|
||||
for (MappingTree.MethodArgMapping argMapping : methodMapping.getArgs()) {
|
||||
String comment = argMapping.getComment();
|
||||
|
||||
if (comment != null) {
|
||||
if (!addedParam && methodMapping.getComment() != null) {
|
||||
//Add a blank line before params when the method has a comment
|
||||
parts.add("");
|
||||
addedParam = true;
|
||||
}
|
||||
|
||||
parts.add(String.format("@param %s %s", argMapping.getName(MappingsNamespace.NAMED.toString()), comment));
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.join("\n", parts);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static MappingTree readMappings(File input) {
|
||||
try (BufferedReader reader = Files.newBufferedReader(input.toPath())) {
|
||||
MemoryMappingTree mappingTree = new MemoryMappingTree();
|
||||
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, MappingsNamespace.NAMED.toString());
|
||||
MappingReader.read(reader, nsSwitch);
|
||||
|
||||
return mappingTree;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to read mappings", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isRecord(StructClass structClass) {
|
||||
return (structClass.getAccessFlags() & Opcodes.ACC_RECORD) != 0;
|
||||
}
|
||||
|
||||
public static boolean isStatic(StructField structField) {
|
||||
return (structField.getAccessFlags() & Opcodes.ACC_STATIC) != 0;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
@@ -43,6 +44,7 @@ import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
import org.gradle.api.publish.maven.MavenPublication;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
|
||||
@@ -85,6 +87,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
|
||||
protected final ConfigurableFileCollection log4jConfigs;
|
||||
protected final RegularFileProperty accessWidener;
|
||||
protected final Property<String> customManifest;
|
||||
protected final SetProperty<String> knownIndyBsms;
|
||||
protected final Property<Boolean> transitiveAccessWideners;
|
||||
protected final Property<Boolean> modProvidedJavadoc;
|
||||
protected final Property<String> intermediary;
|
||||
@@ -121,6 +124,12 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
|
||||
this.log4jConfigs = project.files(directories.getDefaultLog4jConfigFile());
|
||||
this.accessWidener = project.getObjects().fileProperty();
|
||||
this.customManifest = project.getObjects().property(String.class);
|
||||
this.knownIndyBsms = project.getObjects().setProperty(String.class).convention(Set.of(
|
||||
"java/lang/invoke/StringConcatFactory",
|
||||
"java/lang/runtime/ObjectMethods",
|
||||
"org/codehaus/groovy/vmplugin/v8/IndyInterface"
|
||||
));
|
||||
this.knownIndyBsms.finalizeValueOnRead();
|
||||
this.transitiveAccessWideners = project.getObjects().property(Boolean.class)
|
||||
.convention(true);
|
||||
this.transitiveAccessWideners.finalizeValueOnRead();
|
||||
@@ -275,6 +284,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
|
||||
return customManifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetProperty<String> getKnownIndyBsms() {
|
||||
return knownIndyBsms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModVersion() {
|
||||
return versionParser.getModVersion();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2016-2022 FabricMC
|
||||
* Copyright (c) 2023 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,23 +22,16 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.decompilers.fernflower;
|
||||
package net.fabricmc.loom.kotlin.remapping;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import kotlin.Metadata;
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata;
|
||||
|
||||
import org.jetbrains.java.decompiler.util.InterpreterUtil;
|
||||
|
||||
import net.fabricmc.loom.util.ZipUtils;
|
||||
|
||||
public class FernFlowerUtils {
|
||||
public static byte[] getBytecode(String externalPath, String internalPath) throws IOException {
|
||||
File file = new File(externalPath);
|
||||
|
||||
if (internalPath == null) {
|
||||
return InterpreterUtil.getBytes(file);
|
||||
} else {
|
||||
return ZipUtils.unpack(file.toPath(), internalPath);
|
||||
}
|
||||
/**
|
||||
* Similar story to JvmExtensionWrapper, lets abuse the fact that Java can call "internal" Kotlin APIs without reflection :).
|
||||
*/
|
||||
public record KotlinClassMetadataWrapper(KotlinClassMetadata metadata) {
|
||||
public Metadata getAnnotationData() {
|
||||
return metadata.getAnnotationData$kotlinx_metadata_jvm();
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -53,6 +54,7 @@ import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.bundling.ZipEntryCompression;
|
||||
import org.gradle.build.event.BuildEventsListenerRegistry;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.gradle.workers.WorkAction;
|
||||
@@ -74,6 +76,7 @@ public abstract class AbstractRemapJarTask extends Jar {
|
||||
public static final String MANIFEST_NAMESPACE_KEY = "Fabric-Mapping-Namespace";
|
||||
public static final String MANIFEST_SPLIT_ENV_KEY = "Fabric-Loom-Split-Environment";
|
||||
public static final String MANIFEST_CLIENT_ENTRIES_KEY = "Fabric-Loom-Client-Only-Entries";
|
||||
public static final String MANIFEST_JAR_TYPE_KEY = "Fabric-Jar-Type";
|
||||
public static final Attributes.Name MANIFEST_SPLIT_ENV_NAME = new Attributes.Name(MANIFEST_SPLIT_ENV_KEY);
|
||||
public static final Attributes.Name MANIFEST_CLIENT_ENTRIES_NAME = new Attributes.Name(MANIFEST_CLIENT_ENTRIES_KEY);
|
||||
|
||||
@@ -111,6 +114,11 @@ public abstract class AbstractRemapJarTask extends Jar {
|
||||
@Optional
|
||||
public abstract Property<String> getClientOnlySourceSetName();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
@ApiStatus.Internal
|
||||
public abstract Property<String> getJarType();
|
||||
|
||||
private final Provider<JarManifestService> jarManifestServiceProvider;
|
||||
|
||||
@Inject
|
||||
@@ -119,6 +127,7 @@ public abstract class AbstractRemapJarTask extends Jar {
|
||||
getTargetNamespace().convention(IntermediaryNamespaces.intermediary(getProject())).finalizeValueOnRead();
|
||||
getRemapperIsolation().convention(true).finalizeValueOnRead();
|
||||
getIncludesClientOnlyClasses().convention(false).finalizeValueOnRead();
|
||||
getJarType().finalizeValueOnRead();
|
||||
|
||||
jarManifestServiceProvider = JarManifestService.get(getProject());
|
||||
usesService(jarManifestServiceProvider);
|
||||
@@ -138,14 +147,20 @@ public abstract class AbstractRemapJarTask extends Jar {
|
||||
params.getArchiveReproducibleFileOrder().set(isReproducibleFileOrder());
|
||||
|
||||
params.getJarManifestService().set(jarManifestServiceProvider);
|
||||
params.getEntryCompression().set(getEntryCompression());
|
||||
|
||||
if (getIncludesClientOnlyClasses().get()) {
|
||||
final List<String> clientOnlyEntries = new ArrayList<>(getClientOnlyEntries(getClientSourceSet()));
|
||||
clientOnlyEntries.addAll(getAdditionalClientOnlyEntries().get());
|
||||
Collections.sort(clientOnlyEntries);
|
||||
applyClientOnlyManifestAttributes(params, clientOnlyEntries);
|
||||
params.getClientOnlyEntries().set(clientOnlyEntries.stream().filter(s -> s.endsWith(".class")).toList());
|
||||
}
|
||||
|
||||
if (getJarType().isPresent()) {
|
||||
params.getManifestAttributes().put(MANIFEST_JAR_TYPE_KEY, getJarType().get());
|
||||
}
|
||||
|
||||
action.execute(params);
|
||||
});
|
||||
}
|
||||
@@ -161,6 +176,7 @@ public abstract class AbstractRemapJarTask extends Jar {
|
||||
|
||||
Property<Boolean> getArchivePreserveFileTimestamps();
|
||||
Property<Boolean> getArchiveReproducibleFileOrder();
|
||||
Property<ZipEntryCompression> getEntryCompression();
|
||||
|
||||
Property<JarManifestService> getJarManifestService();
|
||||
MapProperty<String, String> getManifestAttributes();
|
||||
@@ -203,9 +219,10 @@ public abstract class AbstractRemapJarTask extends Jar {
|
||||
protected void rewriteJar() throws IOException {
|
||||
final boolean isReproducibleFileOrder = getParameters().getArchiveReproducibleFileOrder().get();
|
||||
final boolean isPreserveFileTimestamps = getParameters().getArchivePreserveFileTimestamps().get();
|
||||
final ZipEntryCompression compression = getParameters().getEntryCompression().get();
|
||||
|
||||
if (isReproducibleFileOrder || !isPreserveFileTimestamps) {
|
||||
ZipReprocessorUtil.reprocessZip(outputFile.toFile(), isReproducibleFileOrder, isPreserveFileTimestamps);
|
||||
if (isReproducibleFileOrder || !isPreserveFileTimestamps || compression != ZipEntryCompression.DEFLATED) {
|
||||
ZipReprocessorUtil.reprocessZip(outputFile.toFile(), isReproducibleFileOrder, isPreserveFileTimestamps, compression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
||||
// Make outputs reproducible by default
|
||||
setReproducibleFileOrder(true);
|
||||
setPreserveFileTimestamps(false);
|
||||
|
||||
getJarType().set("classes");
|
||||
}
|
||||
|
||||
private void setupPreparationTask() {
|
||||
|
||||
@@ -51,6 +51,7 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
|
||||
serviceManagerProvider = BuildSharedServiceManager.createForTask(this, getBuildEventsListenerRegistry());
|
||||
|
||||
getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
|
||||
getJarType().set("sources");
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
|
||||
@@ -43,6 +43,7 @@ import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.LoomGradlePlugin;
|
||||
import net.fabricmc.loom.configuration.InstallerData;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.LoomVersions;
|
||||
|
||||
public abstract class JarManifestService implements BuildService<JarManifestService.Params> {
|
||||
interface Params extends BuildServiceParameters {
|
||||
@@ -63,7 +64,7 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
|
||||
|
||||
params.getGradleVersion().set(GradleVersion.current().getVersion());
|
||||
params.getLoomVersion().set(LoomGradlePlugin.LOOM_VERSION);
|
||||
params.getMCEVersion().set(Constants.Dependencies.Versions.MIXIN_COMPILE_EXTENSIONS);
|
||||
params.getMCEVersion().set(LoomVersions.MIXIN_COMPILE_EXTENSIONS.version());
|
||||
params.getMinecraftVersion().set(project.provider(() -> extension.getMinecraftProvider().minecraftVersion()));
|
||||
params.getTinyRemapperVersion().set(tinyRemapperVersion.orElse("unknown"));
|
||||
params.getFabricLoaderVersion().set(project.provider(() -> Optional.ofNullable(extension.getInstallerData()).map(InstallerData::version).orElse("unknown")));
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import dev.architectury.tinyremapper.IMappingProvider;
|
||||
@@ -78,6 +79,8 @@ public class TinyRemapperService implements SharedService {
|
||||
joiner.add(project.getPath());
|
||||
}
|
||||
|
||||
extension.getKnownIndyBsms().get().stream().sorted().forEach(joiner::add);
|
||||
|
||||
if (extension.isForge()) {
|
||||
joiner.add("forge");
|
||||
}
|
||||
@@ -92,7 +95,7 @@ public class TinyRemapperService implements SharedService {
|
||||
mappings.add(gradleMixinMappingProvider(serviceManager, project.getGradle(), extension.getMappingConfiguration().mappingsIdentifier, from, to));
|
||||
}
|
||||
|
||||
return new TinyRemapperService(mappings, !legacyMixin, kotlinClasspathService);
|
||||
return new TinyRemapperService(mappings, !legacyMixin, kotlinClasspathService, extension.getKnownIndyBsms().get());
|
||||
});
|
||||
|
||||
service.readClasspath(remapJarTask.getClasspath().getFiles().stream().map(File::toPath).filter(Files::exists).toList());
|
||||
@@ -132,8 +135,8 @@ public class TinyRemapperService implements SharedService {
|
||||
// Set to true once remapping has started, once set no inputs can be read.
|
||||
private boolean isRemapping = false;
|
||||
|
||||
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension, @Nullable KotlinClasspath kotlinClasspath) {
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper();
|
||||
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension, @Nullable KotlinClasspath kotlinClasspath, Set<String> knownIndyBsms) {
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper().withKnownIndyBsm(knownIndyBsms);
|
||||
|
||||
for (IMappingProvider provider : mappings) {
|
||||
builder.withMappings(provider);
|
||||
|
||||
@@ -108,46 +108,6 @@ public class Constants {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants related to dependencies.
|
||||
*/
|
||||
public static final class Dependencies {
|
||||
public static final String MIXIN_COMPILE_EXTENSIONS = "net.fabricmc:fabric-mixin-compile-extensions:";
|
||||
public static final String DEV_LAUNCH_INJECTOR = "net.fabricmc:dev-launch-injector:";
|
||||
public static final String TERMINAL_CONSOLE_APPENDER = "net.minecrell:terminalconsoleappender:";
|
||||
public static final String JETBRAINS_ANNOTATIONS = "org.jetbrains:annotations:";
|
||||
public static final String NATIVE_SUPPORT = "net.fabricmc:fabric-loom-native-support:";
|
||||
public static final String JAVAX_ANNOTATIONS = "com.google.code.findbugs:jsr305:"; // I hate that I have to add these.
|
||||
public static final String FORGE_RUNTIME = "dev.architectury:architectury-loom-runtime:";
|
||||
public static final String ACCESS_TRANSFORMERS = "net.minecraftforge:accesstransformers:";
|
||||
public static final String UNPROTECT = "io.github.juuxel:unprotect:";
|
||||
// Used to upgrade the ASM version for the AT tool.
|
||||
public static final String ASM = "org.ow2.asm:asm:";
|
||||
|
||||
private Dependencies() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants for versions of dependencies.
|
||||
*/
|
||||
public static final class Versions {
|
||||
public static final String MIXIN_COMPILE_EXTENSIONS = "0.6.0";
|
||||
public static final String DEV_LAUNCH_INJECTOR = "0.2.1+build.8";
|
||||
public static final String TERMINAL_CONSOLE_APPENDER = "1.2.0";
|
||||
public static final String JETBRAINS_ANNOTATIONS = "24.0.1";
|
||||
public static final String NATIVE_SUPPORT_VERSION = "1.0.1";
|
||||
public static final String JAVAX_ANNOTATIONS = "3.0.2";
|
||||
public static final String FORGE_RUNTIME = "1.1.8";
|
||||
public static final String ACCESS_TRANSFORMERS = "3.0.1";
|
||||
public static final String ACCESS_TRANSFORMERS_NEW = "8.0.5";
|
||||
public static final String UNPROTECT = "1.2.0";
|
||||
public static final String ASM = "9.3";
|
||||
|
||||
private Versions() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class MixinArguments {
|
||||
public static final String IN_MAP_FILE_NAMED_INTERMEDIARY = "inMapFileNamedIntermediary";
|
||||
public static final String OUT_MAP_FILE_NAMED_INTERMEDIARY = "outMapFileNamedIntermediary";
|
||||
|
||||
@@ -206,7 +206,7 @@ public class SourceRemapper {
|
||||
}
|
||||
|
||||
Set<File> files = project.getConfigurations()
|
||||
.detachedConfiguration(project.getDependencies().create(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS))
|
||||
.detachedConfiguration(project.getDependencies().create(LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation()))
|
||||
.resolve();
|
||||
|
||||
for (File file : files) {
|
||||
|
||||
@@ -90,6 +90,7 @@ public final class TinyRemapperHelper {
|
||||
.rebuildSourceFilenames(true)
|
||||
.invalidLvNamePattern(MC_LV_PATTERN)
|
||||
.inferNameFromSameLvIndex(true)
|
||||
.withKnownIndyBsm(extension.getKnownIndyBsms().get())
|
||||
.extraPreApplyVisitor((cls, next) -> {
|
||||
if (fixRecords && !cls.isRecord() && "java/lang/Record".equals(cls.getSuperName())) {
|
||||
return new RecordComponentFixVisitor(next, mappingTree, intermediaryNsId);
|
||||
|
||||
@@ -30,7 +30,6 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.GregorianCalendar;
|
||||
@@ -38,12 +37,10 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class ZipReprocessorUtil {
|
||||
/**
|
||||
* See {@link org.gradle.api.internal.file.archive.ZipCopyAction} about this.
|
||||
*/
|
||||
private static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = new GregorianCalendar(1980, Calendar.FEBRUARY, 1, 0, 0, 0).getTimeInMillis();
|
||||
import org.gradle.api.tasks.bundling.ZipEntryCompression;
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
|
||||
public class ZipReprocessorUtil {
|
||||
private ZipReprocessorUtil() { }
|
||||
|
||||
private static final String MANIFEST_LOCATION = "META-INF/MANIFEST.MF";
|
||||
@@ -92,6 +89,10 @@ public class ZipReprocessorUtil {
|
||||
}
|
||||
|
||||
public static void reprocessZip(File file, boolean reproducibleFileOrder, boolean preserveFileTimestamps) throws IOException {
|
||||
reprocessZip(file, reproducibleFileOrder, preserveFileTimestamps, ZipEntryCompression.DEFLATED);
|
||||
}
|
||||
|
||||
public static void reprocessZip(File file, boolean reproducibleFileOrder, boolean preserveFileTimestamps, ZipEntryCompression zipEntryCompression) throws IOException {
|
||||
if (!reproducibleFileOrder && preserveFileTimestamps) {
|
||||
return;
|
||||
}
|
||||
@@ -111,6 +112,8 @@ public class ZipReprocessorUtil {
|
||||
final var outZip = new ByteArrayOutputStream(entries.length);
|
||||
|
||||
try (var zipOutputStream = new ZipOutputStream(outZip)) {
|
||||
zipOutputStream.setMethod(zipOutputStreamCompressionMethod(zipEntryCompression));
|
||||
|
||||
for (ZipEntry entry : entries) {
|
||||
ZipEntry newEntry = entry;
|
||||
|
||||
@@ -119,6 +122,7 @@ public class ZipReprocessorUtil {
|
||||
setConstantFileTime(newEntry);
|
||||
}
|
||||
|
||||
newEntry.setMethod(zipEntryCompressionMethod(zipEntryCompression));
|
||||
copyZipEntry(zipOutputStream, newEntry, zipFile.getInputStream(entry));
|
||||
}
|
||||
}
|
||||
@@ -173,8 +177,23 @@ public class ZipReprocessorUtil {
|
||||
}
|
||||
|
||||
private static void setConstantFileTime(ZipEntry entry) {
|
||||
entry.setTime(ZipReprocessorUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES);
|
||||
entry.setLastModifiedTime(FileTime.fromMillis(ZipReprocessorUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES));
|
||||
entry.setLastAccessTime(FileTime.fromMillis(ZipReprocessorUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES));
|
||||
// See https://github.com/openjdk/jdk/blob/master/test/jdk/java/util/zip/ZipFile/ZipEntryTimeBounds.java
|
||||
entry.setTime(new GregorianCalendar(1980, Calendar.JANUARY, 1, 0, 0, 0).getTimeInMillis());
|
||||
}
|
||||
|
||||
@MagicConstant(valuesFromClass = ZipOutputStream.class)
|
||||
private static int zipOutputStreamCompressionMethod(ZipEntryCompression compression) {
|
||||
return switch (compression) {
|
||||
case STORED -> ZipOutputStream.STORED;
|
||||
case DEFLATED -> ZipOutputStream.DEFLATED;
|
||||
};
|
||||
}
|
||||
|
||||
@MagicConstant(valuesFromClass = ZipEntry.class)
|
||||
private static int zipEntryCompressionMethod(ZipEntryCompression compression) {
|
||||
return switch (compression) {
|
||||
case STORED -> ZipEntry.STORED;
|
||||
case DEFLATED -> ZipEntry.DEFLATED;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +62,11 @@ import net.fabricmc.loom.util.Checksum;
|
||||
public final class Download {
|
||||
private static final String E_TAG = "ETag";
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Download.class);
|
||||
private static final Duration TIMEOUT = Duration.ofMinutes(1);
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
||||
.followRedirects(HttpClient.Redirect.ALWAYS)
|
||||
.proxy(ProxySelector.getDefault())
|
||||
.connectTimeout(TIMEOUT)
|
||||
.build();
|
||||
|
||||
public static DownloadBuilder create(String url) throws URISyntaxException {
|
||||
@@ -93,17 +95,20 @@ public final class Download {
|
||||
this.downloadAttempt = downloadAttempt;
|
||||
}
|
||||
|
||||
private HttpRequest getRequest() {
|
||||
private HttpRequest.Builder requestBuilder() {
|
||||
return HttpRequest.newBuilder(url)
|
||||
.timeout(TIMEOUT)
|
||||
.version(httpVersion)
|
||||
.GET()
|
||||
.GET();
|
||||
}
|
||||
|
||||
private HttpRequest getRequest() {
|
||||
return requestBuilder()
|
||||
.build();
|
||||
}
|
||||
|
||||
private HttpRequest getETagRequest(String etag) {
|
||||
return HttpRequest.newBuilder(url)
|
||||
.version(httpVersion)
|
||||
.GET()
|
||||
return requestBuilder()
|
||||
.header("If-None-Match", etag)
|
||||
.build();
|
||||
}
|
||||
@@ -129,7 +134,7 @@ public final class Download {
|
||||
|
||||
if (!successful) {
|
||||
progressListener.onEnd();
|
||||
throw error("HTTP request to (%s) returned unsuccessful status (%d)", url, statusCode);
|
||||
throw statusError("HTTP request to (%s) returned unsuccessful status".formatted(url) + "(%d)", statusCode);
|
||||
}
|
||||
|
||||
try (InputStream inputStream = decodeOutput(response)) {
|
||||
@@ -190,47 +195,12 @@ public final class Download {
|
||||
return;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
try {
|
||||
Files.deleteIfExists(output);
|
||||
} catch (IOException e) {
|
||||
throw error(e, "Failed to delete existing file");
|
||||
}
|
||||
|
||||
final long length = Long.parseLong(response.headers().firstValue("Content-Length").orElse("-1"));
|
||||
AtomicLong totalBytes = new AtomicLong(0);
|
||||
|
||||
try (OutputStream outputStream = Files.newOutputStream(output, StandardOpenOption.CREATE_NEW)) {
|
||||
copyWithCallback(decodeOutput(response), outputStream, value -> {
|
||||
if (length < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressListener.onProgress(totalBytes.addAndGet(value), length);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
throw error(e, "Failed to decode and write download output");
|
||||
}
|
||||
|
||||
if (Files.notExists(output)) {
|
||||
throw error("No file was downloaded");
|
||||
}
|
||||
|
||||
if (length > 0) {
|
||||
try {
|
||||
final long actualLength = Files.size(output);
|
||||
|
||||
if (actualLength != length) {
|
||||
throw error("Unexpected file length of %d bytes, expected %d bytes".formatted(actualLength, length));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw error(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw error("HTTP request returned unsuccessful status (%d)", statusCode);
|
||||
if (!success) {
|
||||
throw statusError("HTTP request returned unsuccessful status (%d)", statusCode);
|
||||
}
|
||||
|
||||
downloadToPath(output, response);
|
||||
|
||||
if (useEtag) {
|
||||
final HttpHeaders headers = response.headers();
|
||||
final String responseETag = headers.firstValue(E_TAG.toLowerCase(Locale.ROOT)).orElse(null);
|
||||
@@ -260,6 +230,58 @@ public final class Download {
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadToPath(Path output, HttpResponse<InputStream> response) throws DownloadException {
|
||||
// Download the file initially to a .part file
|
||||
final Path partFile = getPartFile(output);
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(output);
|
||||
Files.deleteIfExists(partFile);
|
||||
} catch (IOException e) {
|
||||
throw error(e, "Failed to delete existing file");
|
||||
}
|
||||
|
||||
final long length = Long.parseLong(response.headers().firstValue("Content-Length").orElse("-1"));
|
||||
AtomicLong totalBytes = new AtomicLong(0);
|
||||
|
||||
try (OutputStream outputStream = Files.newOutputStream(partFile, StandardOpenOption.CREATE_NEW)) {
|
||||
copyWithCallback(decodeOutput(response), outputStream, value -> {
|
||||
if (length < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressListener.onProgress(totalBytes.addAndGet(value), length);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
throw error(e, "Failed to decode and write download output");
|
||||
}
|
||||
|
||||
if (Files.notExists(partFile)) {
|
||||
throw error("No file was downloaded");
|
||||
}
|
||||
|
||||
if (length > 0) {
|
||||
try {
|
||||
final long actualLength = Files.size(partFile);
|
||||
|
||||
if (actualLength != length) {
|
||||
throw error("Unexpected file length of %d bytes, expected %d bytes".formatted(actualLength, length));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw error(e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Once the file has been fully read, create a hard link to the destination file.
|
||||
// And then remove the temporary file, this ensures that the output file only exists in fully populated state.
|
||||
Files.createLink(output, partFile);
|
||||
Files.delete(partFile);
|
||||
} catch (IOException e) {
|
||||
throw error(e, "Failed to complete download");
|
||||
}
|
||||
}
|
||||
|
||||
private void copyWithCallback(InputStream is, OutputStream os, IntConsumer consumer) throws IOException {
|
||||
byte[] buffer = new byte[1024];
|
||||
int length;
|
||||
@@ -389,6 +411,18 @@ public final class Download {
|
||||
} catch (IOException ignored) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(getLockFile(output));
|
||||
} catch (IOException ignored) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(getPartFile(output));
|
||||
} catch (IOException ignored) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
// A faster exists check
|
||||
@@ -405,6 +439,10 @@ public final class Download {
|
||||
return output.resolveSibling(output.getFileName() + ".lock");
|
||||
}
|
||||
|
||||
private Path getPartFile(Path output) {
|
||||
return output.resolveSibling(output.getFileName() + ".part");
|
||||
}
|
||||
|
||||
private boolean getAndResetLock(Path output) throws DownloadException {
|
||||
final Path lock = getLockFile(output);
|
||||
final boolean exists = exists(lock);
|
||||
@@ -430,6 +468,10 @@ public final class Download {
|
||||
}
|
||||
}
|
||||
|
||||
private DownloadException statusError(String message, int statusCode) {
|
||||
return new DownloadException(String.format(Locale.ENGLISH, message, statusCode), statusCode);
|
||||
}
|
||||
|
||||
private DownloadException error(String message, Object... args) {
|
||||
return new DownloadException(String.format(Locale.ENGLISH, message, args));
|
||||
}
|
||||
|
||||
@@ -158,6 +158,11 @@ public class DownloadBuilder {
|
||||
|
||||
return supplier.get(build(i));
|
||||
} catch (DownloadException e) {
|
||||
if (e.getStatusCode() == 404) {
|
||||
// Don't retry on 404's
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (i == maxRetries) {
|
||||
throw new DownloadException(String.format(Locale.ENGLISH, "Failed download after %d attempts", maxRetries), e);
|
||||
}
|
||||
|
||||
@@ -27,15 +27,32 @@ package net.fabricmc.loom.util.download;
|
||||
import java.io.IOException;
|
||||
|
||||
public class DownloadException extends IOException {
|
||||
private final int statusCode;
|
||||
|
||||
public DownloadException(String message) {
|
||||
super(message);
|
||||
statusCode = -1;
|
||||
}
|
||||
|
||||
public DownloadException(String message, int statusCode) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public DownloadException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
statusCode = cause instanceof DownloadException downloadException ? downloadException.getStatusCode() : -1;
|
||||
}
|
||||
|
||||
public DownloadException(Throwable cause) {
|
||||
super(cause);
|
||||
statusCode = cause instanceof DownloadException downloadException ? downloadException.getStatusCode() : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return -1 when the status code is unknown.
|
||||
*/
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user