mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Merge remote-tracking branch 'FabricMC/exp/1.6' into exp/1.6
# Conflicts: # gradle/libs.versions.toml # src/main/java/net/fabricmc/loom/LoomGradleExtension.java # src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java # src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java # src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java # src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java # src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java # src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java # src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java # src/main/java/net/fabricmc/loom/extension/LoomFiles.java # src/main/java/net/fabricmc/loom/extension/LoomFilesBaseImpl.java # src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java # src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java # src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java # src/main/java/net/fabricmc/loom/task/RemapJarTask.java # src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java
This commit is contained in:
@@ -143,12 +143,13 @@ dependencies {
|
|||||||
implementation libs.fabric.tiny.remapper
|
implementation libs.fabric.tiny.remapper
|
||||||
implementation libs.fabric.access.widener
|
implementation libs.fabric.access.widener
|
||||||
implementation libs.fabric.mapping.io
|
implementation libs.fabric.mapping.io
|
||||||
|
|
||||||
implementation (libs.fabric.lorenz.tiny) {
|
implementation (libs.fabric.lorenz.tiny) {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
implementation "dev.architectury:refmap-remapper:1.0.5"
|
implementation "dev.architectury:refmap-remapper:1.0.5"
|
||||||
|
|
||||||
|
implementation libs.fabric.loom.nativelib
|
||||||
|
|
||||||
// decompilers
|
// decompilers
|
||||||
fernflowerCompileOnly runtimeLibs.fernflower
|
fernflowerCompileOnly runtimeLibs.fernflower
|
||||||
fernflowerCompileOnly libs.fabric.mapping.io
|
fernflowerCompileOnly libs.fabric.mapping.io
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ mapping-io = "0.5.1"
|
|||||||
lorenz-tiny = "4.0.2"
|
lorenz-tiny = "4.0.2"
|
||||||
mercury = "0.1.4.17"
|
mercury = "0.1.4.17"
|
||||||
kotlinx-metadata = "0.9.0"
|
kotlinx-metadata = "0.9.0"
|
||||||
|
loom-native = "0.1.0"
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
spotless = "6.25.0"
|
spotless = "6.25.0"
|
||||||
@@ -48,6 +49,7 @@ fabric-access-widener = { module = "net.fabricmc:access-widener", version.ref =
|
|||||||
fabric-mapping-io = { module = "net.fabricmc:mapping-io", version.ref = "mapping-io" }
|
fabric-mapping-io = { module = "net.fabricmc:mapping-io", version.ref = "mapping-io" }
|
||||||
fabric-lorenz-tiny = { module = "net.fabricmc:lorenz-tiny", version.ref = "lorenz-tiny" }
|
fabric-lorenz-tiny = { module = "net.fabricmc:lorenz-tiny", version.ref = "lorenz-tiny" }
|
||||||
fabric-mercury = { module = "dev.architectury:mercury", version.ref = "mercury" }
|
fabric-mercury = { module = "dev.architectury:mercury", version.ref = "mercury" }
|
||||||
|
fabric-loom-nativelib = { module = "net.fabricmc:fabric-loom-native", version.ref = "loom-native" }
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
|
|||||||
return switch (mappingsNamespace) {
|
return switch (mappingsNamespace) {
|
||||||
case NAMED -> getNamedMinecraftProvider().getMinecraftJarPaths();
|
case NAMED -> getNamedMinecraftProvider().getMinecraftJarPaths();
|
||||||
case INTERMEDIARY -> getIntermediaryMinecraftProvider().getMinecraftJarPaths();
|
case INTERMEDIARY -> getIntermediaryMinecraftProvider().getMinecraftJarPaths();
|
||||||
case OFFICIAL -> getMinecraftProvider().getMinecraftJars();
|
case OFFICIAL, CLIENT_OFFICIAL, SERVER_OFFICIAL -> getMinecraftProvider().getMinecraftJars();
|
||||||
case SRG -> {
|
case SRG -> {
|
||||||
ModPlatform.assertPlatform(this, ModPlatform.FORGE, () -> "SRG jars are only available on Forge.");
|
ModPlatform.assertPlatform(this, ModPlatform.FORGE, () -> "SRG jars are only available on Forge.");
|
||||||
yield getSrgMinecraftProvider().getMinecraftJarPaths();
|
yield getSrgMinecraftProvider().getMinecraftJarPaths();
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.gradle.api.NamedDomainObjectContainer;
|
|||||||
import org.gradle.api.NamedDomainObjectList;
|
import org.gradle.api.NamedDomainObjectList;
|
||||||
import org.gradle.api.artifacts.Dependency;
|
import org.gradle.api.artifacts.Dependency;
|
||||||
import org.gradle.api.file.ConfigurableFileCollection;
|
import org.gradle.api.file.ConfigurableFileCollection;
|
||||||
|
import org.gradle.api.file.FileCollection;
|
||||||
import org.gradle.api.file.RegularFileProperty;
|
import org.gradle.api.file.RegularFileProperty;
|
||||||
import org.gradle.api.provider.ListProperty;
|
import org.gradle.api.provider.ListProperty;
|
||||||
import org.gradle.api.provider.Property;
|
import org.gradle.api.provider.Property;
|
||||||
@@ -206,7 +207,8 @@ public interface LoomGradleExtensionAPI {
|
|||||||
*/
|
*/
|
||||||
Property<String> getIntermediaryUrl();
|
Property<String> getIntermediaryUrl();
|
||||||
|
|
||||||
Property<MinecraftJarConfiguration> getMinecraftJarConfiguration();
|
@ApiStatus.Experimental
|
||||||
|
Property<MinecraftJarConfiguration<?, ?, ?>> getMinecraftJarConfiguration();
|
||||||
|
|
||||||
default void serverOnlyMinecraftJar() {
|
default void serverOnlyMinecraftJar() {
|
||||||
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SERVER_ONLY);
|
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SERVER_ONLY);
|
||||||
@@ -235,6 +237,11 @@ public interface LoomGradleExtensionAPI {
|
|||||||
*/
|
*/
|
||||||
Provider<String> getMinecraftVersion();
|
Provider<String> getMinecraftVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A lazily evaluated {@link FileCollection} containing the named minecraft jars.
|
||||||
|
*/
|
||||||
|
FileCollection getNamedMinecraftJars();
|
||||||
|
|
||||||
// ===================
|
// ===================
|
||||||
// Architectury Loom
|
// Architectury Loom
|
||||||
// ===================
|
// ===================
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ public abstract class IntermediateMappingsProvider implements Named {
|
|||||||
|
|
||||||
public abstract Property<Function<String, DownloadBuilder>> getDownloader();
|
public abstract Property<Function<String, DownloadBuilder>> getDownloader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true if the minecraft version is pre 1.3.
|
||||||
|
* When true the expected src namespace is intermediary, and the expected dst namespaces are clientOfficial and/or serverOfficial
|
||||||
|
* When false the expected src namespace is named and the expected dst namespace is intermediary
|
||||||
|
*/
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public abstract Property<Boolean> getIsLegacyMinecraft();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate or download a tinyv2 mapping file with intermediary and named namespaces.
|
* Generate or download a tinyv2 mapping file with intermediary and named namespaces.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ public enum MappingsNamespace {
|
|||||||
*/
|
*/
|
||||||
OFFICIAL,
|
OFFICIAL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Official names for the Minecraft client jar, usually obfuscated.
|
||||||
|
* This namespace is used for versions <1.3, where the client and server jars are obfuscated differently.
|
||||||
|
*/
|
||||||
|
CLIENT_OFFICIAL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Official names for the Minecraft server jar, usually obfuscated.
|
||||||
|
* This namespace is used for versions <1.3, where the client and server jars are obfuscated differently.
|
||||||
|
*/
|
||||||
|
SERVER_OFFICIAL,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Intermediary mappings have been generated to provide a stable set of names across minecraft versions.
|
* Intermediary mappings have been generated to provide a stable set of names across minecraft versions.
|
||||||
*
|
*
|
||||||
@@ -76,6 +88,8 @@ public enum MappingsNamespace {
|
|||||||
public static @Nullable MappingsNamespace of(String namespace) {
|
public static @Nullable MappingsNamespace of(String namespace) {
|
||||||
return switch (namespace) {
|
return switch (namespace) {
|
||||||
case "official" -> OFFICIAL;
|
case "official" -> OFFICIAL;
|
||||||
|
case "clientOfficial" -> CLIENT_OFFICIAL;
|
||||||
|
case "serverOfficial" -> SERVER_OFFICIAL;
|
||||||
case "intermediary" -> INTERMEDIARY;
|
case "intermediary" -> INTERMEDIARY;
|
||||||
case "srg" -> SRG;
|
case "srg" -> SRG;
|
||||||
case "mojang" -> MOJANG;
|
case "mojang" -> MOJANG;
|
||||||
@@ -86,6 +100,10 @@ public enum MappingsNamespace {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name().toLowerCase(Locale.ROOT);
|
return switch (this) {
|
||||||
|
case CLIENT_OFFICIAL -> "clientOfficial";
|
||||||
|
case SERVER_OFFICIAL -> "serverOfficial";
|
||||||
|
default -> name().toLowerCase(Locale.ROOT);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
@@ -55,6 +57,8 @@ import org.gradle.api.provider.Provider;
|
|||||||
import org.gradle.api.tasks.TaskDependency;
|
import org.gradle.api.tasks.TaskDependency;
|
||||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
|
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.LoomGradlePlugin;
|
import net.fabricmc.loom.LoomGradlePlugin;
|
||||||
@@ -65,6 +69,9 @@ import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
|
|||||||
|
|
||||||
public final class IncludedJarFactory {
|
public final class IncludedJarFactory {
|
||||||
private final Project project;
|
private final Project project;
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(IncludedJarFactory.class);
|
||||||
|
private static final String SEMVER_REGEX = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$";
|
||||||
|
private static final Pattern SEMVER_PATTERN = Pattern.compile(SEMVER_REGEX);
|
||||||
|
|
||||||
public IncludedJarFactory(Project project) {
|
public IncludedJarFactory(Project project) {
|
||||||
this.project = project;
|
this.project = project;
|
||||||
@@ -217,7 +224,8 @@ public final class IncludedJarFactory {
|
|||||||
jsonObject.addProperty("schemaVersion", 1);
|
jsonObject.addProperty("schemaVersion", 1);
|
||||||
|
|
||||||
jsonObject.addProperty("id", modId);
|
jsonObject.addProperty("id", modId);
|
||||||
jsonObject.addProperty("version", metadata.version());
|
String version = getVersion(metadata);
|
||||||
|
jsonObject.addProperty("version", version);
|
||||||
jsonObject.addProperty("name", metadata.name());
|
jsonObject.addProperty("name", metadata.name());
|
||||||
|
|
||||||
JsonObject custom = new JsonObject();
|
JsonObject custom = new JsonObject();
|
||||||
@@ -236,6 +244,35 @@ public final class IncludedJarFactory {
|
|||||||
return "_" + classifier;
|
return "_" + classifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return group + ":" + name + ":" + version + classifier();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getVersion(Metadata metadata) {
|
||||||
|
String version = metadata.version();
|
||||||
|
|
||||||
|
if (validSemVer(version)) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version.endsWith(".Final") || version.endsWith(".final")) {
|
||||||
|
String trimmedVersion = version.substring(0, version.length() - 6);
|
||||||
|
|
||||||
|
if (validSemVer(trimmedVersion)) {
|
||||||
|
return trimmedVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.warn("({}) is not valid semver for dependency {}", version, metadata);
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean validSemVer(String version) {
|
||||||
|
Matcher matcher = SEMVER_PATTERN.matcher(version);
|
||||||
|
return matcher.find();
|
||||||
}
|
}
|
||||||
|
|
||||||
public record NestedFile(Metadata metadata, File file) implements Serializable { }
|
public record NestedFile(Metadata metadata, File file) implements Serializable { }
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -42,7 +40,6 @@ import javax.inject.Inject;
|
|||||||
import org.gradle.api.GradleException;
|
import org.gradle.api.GradleException;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.file.FileCollection;
|
import org.gradle.api.file.FileCollection;
|
||||||
import org.gradle.api.logging.LogLevel;
|
|
||||||
import org.gradle.api.logging.Logger;
|
import org.gradle.api.logging.Logger;
|
||||||
import org.gradle.api.logging.Logging;
|
import org.gradle.api.logging.Logging;
|
||||||
import org.gradle.api.plugins.JavaPlugin;
|
import org.gradle.api.plugins.JavaPlugin;
|
||||||
@@ -78,6 +75,7 @@ import net.fabricmc.loom.configuration.providers.forge.minecraft.ForgeMinecraftP
|
|||||||
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
|
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMetadataProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.mapped.AbstractMappedMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.mapped.AbstractMappedMinecraftProvider;
|
||||||
@@ -88,7 +86,9 @@ import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftPr
|
|||||||
import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper;
|
import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper;
|
||||||
import net.fabricmc.loom.extension.MixinExtension;
|
import net.fabricmc.loom.extension.MixinExtension;
|
||||||
import net.fabricmc.loom.util.Checksum;
|
import net.fabricmc.loom.util.Checksum;
|
||||||
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.ExceptionUtil;
|
import net.fabricmc.loom.util.ExceptionUtil;
|
||||||
|
import net.fabricmc.loom.util.ProcessUtil;
|
||||||
import net.fabricmc.loom.util.gradle.GradleUtils;
|
import net.fabricmc.loom.util.gradle.GradleUtils;
|
||||||
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
||||||
import net.fabricmc.loom.util.service.ScopedSharedServiceManager;
|
import net.fabricmc.loom.util.service.ScopedSharedServiceManager;
|
||||||
@@ -127,6 +127,7 @@ public abstract class CompileConfiguration implements Runnable {
|
|||||||
try {
|
try {
|
||||||
setupMinecraft(configContext);
|
setupMinecraft(configContext);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
ExceptionUtil.printFileLocks(e, getProject());
|
||||||
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to setup Minecraft", e);
|
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to setup Minecraft", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,15 +197,21 @@ public abstract class CompileConfiguration implements Runnable {
|
|||||||
private synchronized void setupMinecraft(ConfigContext configContext) throws Exception {
|
private synchronized void setupMinecraft(ConfigContext configContext) throws Exception {
|
||||||
final Project project = configContext.project();
|
final Project project = configContext.project();
|
||||||
final LoomGradleExtension extension = configContext.extension();
|
final LoomGradleExtension extension = configContext.extension();
|
||||||
final MinecraftJarConfiguration jarConfiguration = extension.getMinecraftJarConfiguration().get();
|
|
||||||
|
|
||||||
// Provide the vanilla mc jars -- TODO share across getProject()s.
|
final MinecraftMetadataProvider metadataProvider = MinecraftMetadataProvider.create(configContext);
|
||||||
final MinecraftProvider minecraftProvider = jarConfiguration.getMinecraftProviderFunction().apply(configContext);
|
|
||||||
|
var jarConfiguration = extension.getMinecraftJarConfiguration().get();
|
||||||
|
|
||||||
|
if (jarConfiguration == MinecraftJarConfiguration.MERGED && !metadataProvider.getVersionMeta().isVersionOrNewer(Constants.RELEASE_TIME_1_3)) {
|
||||||
|
jarConfiguration = MinecraftJarConfiguration.LEGACY_MERGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the vanilla mc jars
|
||||||
|
final MinecraftProvider minecraftProvider = jarConfiguration.createMinecraftProvider(metadataProvider, configContext);
|
||||||
|
|
||||||
if (extension.isForgeLike() && !(minecraftProvider instanceof ForgeMinecraftProvider)) {
|
if (extension.isForgeLike() && !(minecraftProvider instanceof ForgeMinecraftProvider)) {
|
||||||
throw new UnsupportedOperationException("Using Forge with split jars is not supported!");
|
throw new UnsupportedOperationException("Using Forge with split jars is not supported!");
|
||||||
}
|
}
|
||||||
|
|
||||||
extension.setMinecraftProvider(minecraftProvider);
|
extension.setMinecraftProvider(minecraftProvider);
|
||||||
minecraftProvider.provide();
|
minecraftProvider.provide();
|
||||||
|
|
||||||
@@ -236,15 +243,15 @@ public abstract class CompileConfiguration implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Provide the remapped mc jars
|
// Provide the remapped mc jars
|
||||||
final IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider = jarConfiguration.getIntermediaryMinecraftProviderBiFunction().apply(project, minecraftProvider);
|
final IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider = jarConfiguration.createIntermediaryMinecraftProvider(project);
|
||||||
NamedMinecraftProvider<?> namedMinecraftProvider = jarConfiguration.getNamedMinecraftProviderBiFunction().apply(project, minecraftProvider);
|
NamedMinecraftProvider<?> namedMinecraftProvider = jarConfiguration.createNamedMinecraftProvider(project);
|
||||||
|
|
||||||
registerGameProcessors(configContext);
|
registerGameProcessors(configContext);
|
||||||
MinecraftJarProcessorManager minecraftJarProcessorManager = MinecraftJarProcessorManager.create(getProject());
|
MinecraftJarProcessorManager minecraftJarProcessorManager = MinecraftJarProcessorManager.create(getProject());
|
||||||
|
|
||||||
if (minecraftJarProcessorManager != null) {
|
if (minecraftJarProcessorManager != null) {
|
||||||
// Wrap the named MC provider for one that will provide the processed jars
|
// Wrap the named MC provider for one that will provide the processed jars
|
||||||
namedMinecraftProvider = jarConfiguration.getProcessedNamedMinecraftProviderBiFunction().apply(namedMinecraftProvider, minecraftJarProcessorManager);
|
namedMinecraftProvider = jarConfiguration.createProcessedNamedMinecraftProvider(namedMinecraftProvider, minecraftJarProcessorManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var provideContext = new AbstractMappedMinecraftProvider.ProvideContext(true, extension.refreshDeps(), configContext);
|
final var provideContext = new AbstractMappedMinecraftProvider.ProvideContext(true, extension.refreshDeps(), configContext);
|
||||||
@@ -327,8 +334,7 @@ public abstract class CompileConfiguration implements Runnable {
|
|||||||
final LoomGradleExtension extension = configContext.extension();
|
final LoomGradleExtension extension = configContext.extension();
|
||||||
|
|
||||||
extension.getMinecraftJarConfiguration().get()
|
extension.getMinecraftJarConfiguration().get()
|
||||||
.getDecompileConfigurationBiFunction()
|
.createDecompileConfiguration(getProject())
|
||||||
.apply(configContext.project(), extension.getNamedMinecraftProvider())
|
|
||||||
.afterEvaluation();
|
.afterEvaluation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +409,8 @@ public abstract class CompileConfiguration implements Runnable {
|
|||||||
Files.deleteIfExists(lockFile.file);
|
Files.deleteIfExists(lockFile.file);
|
||||||
abrupt = true;
|
abrupt = true;
|
||||||
} else {
|
} else {
|
||||||
logger.lifecycle(printWithParents(handle.get()));
|
ProcessUtil processUtil = ProcessUtil.create(getProject());
|
||||||
|
logger.lifecycle(processUtil.printWithParents(handle.get()));
|
||||||
logger.lifecycle("Waiting for lock to be released...");
|
logger.lifecycle("Waiting for lock to be released...");
|
||||||
long sleptMs = 0;
|
long sleptMs = 0;
|
||||||
|
|
||||||
@@ -441,69 +448,6 @@ public abstract class CompileConfiguration implements Runnable {
|
|||||||
return abrupt ? LockResult.ACQUIRED_PREVIOUS_OWNER_MISSING : LockResult.ACQUIRED_CLEAN;
|
return abrupt ? LockResult.ACQUIRED_PREVIOUS_OWNER_MISSING : LockResult.ACQUIRED_CLEAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String printWithParents(ProcessHandle processHandle) {
|
|
||||||
var output = new StringBuilder();
|
|
||||||
|
|
||||||
List<ProcessHandle> chain = getParentChain(null, processHandle);
|
|
||||||
|
|
||||||
for (int i = 0; i < chain.size(); i++) {
|
|
||||||
ProcessHandle handle = chain.get(i);
|
|
||||||
|
|
||||||
output.append("\t".repeat(i));
|
|
||||||
|
|
||||||
if (i != 0) {
|
|
||||||
output.append("└─ ");
|
|
||||||
}
|
|
||||||
|
|
||||||
output.append(getInfoString(handle));
|
|
||||||
|
|
||||||
if (i < chain.size() - 1) {
|
|
||||||
output.append('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getInfoString(ProcessHandle handle) {
|
|
||||||
return "(%s) pid %s '%s%s'%s".formatted(
|
|
||||||
handle.info().user().orElse("unknown user"),
|
|
||||||
handle.pid(),
|
|
||||||
handle.info().command().orElse("unknown command"),
|
|
||||||
handle.info().arguments().map(arr -> {
|
|
||||||
if (getProject().getGradle().getStartParameter().getLogLevel() != LogLevel.INFO
|
|
||||||
&& getProject().getGradle().getStartParameter().getLogLevel() != LogLevel.DEBUG) {
|
|
||||||
return " (run with --info or --debug to show arguments, may reveal sensitive info)";
|
|
||||||
}
|
|
||||||
|
|
||||||
String join = String.join(" ", arr);
|
|
||||||
|
|
||||||
if (join.isBlank()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return " " + join;
|
|
||||||
}).orElse(" (unknown arguments)"),
|
|
||||||
handle.info().startInstant().map(instant -> " started at " + instant).orElse("")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ProcessHandle> getParentChain(List<ProcessHandle> collectTo, ProcessHandle processHandle) {
|
|
||||||
if (collectTo == null) {
|
|
||||||
collectTo = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<ProcessHandle> parent = processHandle.parent();
|
|
||||||
|
|
||||||
if (parent.isPresent()) {
|
|
||||||
getParentChain(collectTo, parent.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
collectTo.add(processHandle);
|
|
||||||
|
|
||||||
return collectTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releaseLock() {
|
private void releaseLock() {
|
||||||
final Path lock = getLockFile().file;
|
final Path lock = getLockFile().file;
|
||||||
|
|
||||||
|
|||||||
@@ -181,8 +181,8 @@ public abstract class FabricApiExtension {
|
|||||||
|
|
||||||
if (settings.getCreateRunConfiguration().get()) {
|
if (settings.getCreateRunConfiguration().get()) {
|
||||||
extension.getRunConfigs().create("datagen", run -> {
|
extension.getRunConfigs().create("datagen", run -> {
|
||||||
run.setConfigName("Data Generation");
|
|
||||||
run.inherit(extension.getRunConfigs().getByName("server"));
|
run.inherit(extension.getRunConfigs().getByName("server"));
|
||||||
|
run.setConfigName("Data Generation");
|
||||||
|
|
||||||
run.property("fabric-api.datagen");
|
run.property("fabric-api.datagen");
|
||||||
run.property("fabric-api.datagen.output-dir", outputDirectory.getAbsolutePath());
|
run.property("fabric-api.datagen.output-dir", outputDirectory.getAbsolutePath());
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration<Mapp
|
|||||||
// Decompiler will be passed to the constructor of GenerateSourcesTask
|
// Decompiler will be passed to the constructor of GenerateSourcesTask
|
||||||
project.getTasks().register(taskName, GenerateSourcesTask.class, options).configure(task -> {
|
project.getTasks().register(taskName, GenerateSourcesTask.class, options).configure(task -> {
|
||||||
task.getInputJarName().set(minecraftJar.getName());
|
task.getInputJarName().set(minecraftJar.getName());
|
||||||
task.getOutputJar().fileValue(GenerateSourcesTask.getMappedJarFileWithSuffix("-sources.jar", minecraftJar.getPath()));
|
task.getOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", minecraftJar.getPath()));
|
||||||
|
|
||||||
task.dependsOn(project.getTasks().named("validateAccessWidener"));
|
task.dependsOn(project.getTasks().named("validateAccessWidener"));
|
||||||
task.setDescription("Decompile minecraft using %s.".formatted(decompilerName));
|
task.setDescription("Decompile minecraft using %s.".formatted(decompilerName));
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration<Ma
|
|||||||
|
|
||||||
final TaskProvider<Task> commonDecompileTask = createDecompileTasks("Common", task -> {
|
final TaskProvider<Task> commonDecompileTask = createDecompileTasks("Common", task -> {
|
||||||
task.getInputJarName().set(commonJar.getName());
|
task.getInputJarName().set(commonJar.getName());
|
||||||
task.getOutputJar().fileValue(GenerateSourcesTask.getMappedJarFileWithSuffix("-sources.jar", commonJar.getPath()));
|
task.getOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", commonJar.getPath()));
|
||||||
|
|
||||||
if (mappingConfiguration.hasUnpickDefinitions()) {
|
if (mappingConfiguration.hasUnpickDefinitions()) {
|
||||||
File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar");
|
File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar");
|
||||||
@@ -65,7 +65,7 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration<Ma
|
|||||||
|
|
||||||
final TaskProvider<Task> clientOnlyDecompileTask = createDecompileTasks("ClientOnly", task -> {
|
final TaskProvider<Task> clientOnlyDecompileTask = createDecompileTasks("ClientOnly", task -> {
|
||||||
task.getInputJarName().set(clientOnlyJar.getName());
|
task.getInputJarName().set(clientOnlyJar.getName());
|
||||||
task.getOutputJar().fileValue(GenerateSourcesTask.getMappedJarFileWithSuffix("-sources.jar", clientOnlyJar.getPath()));
|
task.getOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", clientOnlyJar.getPath()));
|
||||||
|
|
||||||
if (mappingConfiguration.hasUnpickDefinitions()) {
|
if (mappingConfiguration.hasUnpickDefinitions()) {
|
||||||
File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar");
|
File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar");
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import java.util.stream.Collectors;
|
|||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import org.gradle.api.JavaVersion;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.artifacts.ModuleVersionIdentifier;
|
import org.gradle.api.artifacts.ModuleVersionIdentifier;
|
||||||
import org.gradle.api.artifacts.ResolvedArtifact;
|
import org.gradle.api.artifacts.ResolvedArtifact;
|
||||||
@@ -59,6 +60,7 @@ import net.fabricmc.loom.configuration.InstallerData;
|
|||||||
import net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask;
|
import net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask;
|
||||||
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
|
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
|
||||||
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryContext;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.gradle.SourceSetReference;
|
import net.fabricmc.loom.util.gradle.SourceSetReference;
|
||||||
|
|
||||||
@@ -138,6 +140,12 @@ public class RunConfig {
|
|||||||
public static RunConfig runConfig(Project project, RunConfigSettings settings) {
|
public static RunConfig runConfig(Project project, RunConfigSettings settings) {
|
||||||
settings.evaluateNow();
|
settings.evaluateNow();
|
||||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
|
LibraryContext context = new LibraryContext(extension.getMinecraftProvider().getVersionInfo(), JavaVersion.current());
|
||||||
|
|
||||||
|
if (settings.getEnvironment().equals("client") && context.usesLWJGL3()) {
|
||||||
|
settings.startFirstThread();
|
||||||
|
}
|
||||||
|
|
||||||
String name = settings.getName();
|
String name = settings.getName();
|
||||||
|
|
||||||
String configName = settings.getConfigName();
|
String configName = settings.getConfigName();
|
||||||
|
|||||||
@@ -357,7 +357,6 @@ public class RunConfigSettings implements Named {
|
|||||||
* Configure run config with the default client options.
|
* Configure run config with the default client options.
|
||||||
*/
|
*/
|
||||||
public void client() {
|
public void client() {
|
||||||
startFirstThread();
|
|
||||||
environment("client");
|
environment("client");
|
||||||
defaultMainClass(Constants.Knot.KNOT_CLIENT);
|
defaultMainClass(Constants.Knot.KNOT_CLIENT);
|
||||||
|
|
||||||
|
|||||||
@@ -116,8 +116,7 @@ record DownloadSourcesHook(Project project, Task task) {
|
|||||||
private String getGenSourcesTaskName(MinecraftJar.Type jarType) {
|
private String getGenSourcesTaskName(MinecraftJar.Type jarType) {
|
||||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
return extension.getMinecraftJarConfiguration().get()
|
return extension.getMinecraftJarConfiguration().get()
|
||||||
.getDecompileConfigurationBiFunction()
|
.createDecompileConfiguration(project)
|
||||||
.apply(project, extension.getNamedMinecraftProvider())
|
|
||||||
.getTaskName(jarType);
|
.getTaskName(jarType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,12 +55,14 @@ import net.fabricmc.loom.LoomGradleExtension;
|
|||||||
import net.fabricmc.loom.configuration.ide.RunConfig;
|
import net.fabricmc.loom.configuration.ide.RunConfig;
|
||||||
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
|
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
|
||||||
import net.fabricmc.loom.task.AbstractLoomTask;
|
import net.fabricmc.loom.task.AbstractLoomTask;
|
||||||
|
import net.fabricmc.loom.util.Constants;
|
||||||
|
|
||||||
public abstract class IdeaSyncTask extends AbstractLoomTask {
|
public abstract class IdeaSyncTask extends AbstractLoomTask {
|
||||||
@Inject
|
@Inject
|
||||||
public IdeaSyncTask() {
|
public IdeaSyncTask() {
|
||||||
// Always re-run this task.
|
// Always re-run this task.
|
||||||
getOutputs().upToDateWhen(element -> false);
|
getOutputs().upToDateWhen(element -> false);
|
||||||
|
setGroup(Constants.TaskGroup.IDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
|
|||||||
@@ -50,10 +50,12 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
|||||||
|
|
||||||
public final class IntermediateMappingsService implements SharedService {
|
public final class IntermediateMappingsService implements SharedService {
|
||||||
private final Path intermediaryTiny;
|
private final Path intermediaryTiny;
|
||||||
|
private final String expectedSrcNs;
|
||||||
private final Supplier<MemoryMappingTree> memoryMappingTree = Suppliers.memoize(this::createMemoryMappingTree);
|
private final Supplier<MemoryMappingTree> memoryMappingTree = Suppliers.memoize(this::createMemoryMappingTree);
|
||||||
|
|
||||||
private IntermediateMappingsService(Path intermediaryTiny) {
|
private IntermediateMappingsService(Path intermediaryTiny, String expectedSrcNs) {
|
||||||
this.intermediaryTiny = intermediaryTiny;
|
this.intermediaryTiny = intermediaryTiny;
|
||||||
|
this.expectedSrcNs = expectedSrcNs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized IntermediateMappingsService getInstance(SharedServiceManager sharedServiceManager, Project project, MinecraftProvider minecraftProvider) {
|
public static synchronized IntermediateMappingsService getInstance(SharedServiceManager sharedServiceManager, Project project, MinecraftProvider minecraftProvider) {
|
||||||
@@ -84,7 +86,13 @@ public final class IntermediateMappingsService implements SharedService {
|
|||||||
throw new UncheckedIOException("Failed to provide intermediate mappings", e);
|
throw new UncheckedIOException("Failed to provide intermediate mappings", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new IntermediateMappingsService(intermediaryTiny);
|
// When merging legacy versions there will be multiple named namespaces, so use intermediary as the common src ns
|
||||||
|
// Newer versions will use intermediary as the src ns
|
||||||
|
final String expectedSrcNs = minecraftProvider.isLegacyVersion()
|
||||||
|
? MappingsNamespace.INTERMEDIARY.toString() // <1.3
|
||||||
|
: MappingsNamespace.OFFICIAL.toString(); // >=1.3
|
||||||
|
|
||||||
|
return new IntermediateMappingsService(intermediaryTiny, expectedSrcNs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MemoryMappingTree createMemoryMappingTree() {
|
private MemoryMappingTree createMemoryMappingTree() {
|
||||||
@@ -100,6 +108,10 @@ public final class IntermediateMappingsService implements SharedService {
|
|||||||
throw new UncheckedIOException("Failed to read intermediary mappings", e);
|
throw new UncheckedIOException("Failed to read intermediary mappings", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!expectedSrcNs.equals(tree.getSrcNamespace())) {
|
||||||
|
throw new RuntimeException("Invalid intermediate mappings: expected source namespace '" + expectedSrcNs + "' but found '" + tree.getSrcNamespace() + "\'");
|
||||||
|
}
|
||||||
|
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ public class MappingConfiguration {
|
|||||||
// These are unmerged v2 mappings
|
// These are unmerged v2 mappings
|
||||||
IntermediateMappingsService intermediateMappingsService = IntermediateMappingsService.getInstance(serviceManager, project, minecraftProvider);
|
IntermediateMappingsService intermediateMappingsService = IntermediateMappingsService.getInstance(serviceManager, project, minecraftProvider);
|
||||||
|
|
||||||
MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, intermediateMappingsService);
|
MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, minecraftProvider, intermediateMappingsService);
|
||||||
} else {
|
} else {
|
||||||
if (LoomGradleExtension.get(project).isForgeLike()) {
|
if (LoomGradleExtension.get(project).isForgeLike()) {
|
||||||
// (2022-09-11) This is due to ordering issues.
|
// (2022-09-11) This is due to ordering issues.
|
||||||
|
|||||||
@@ -37,11 +37,12 @@ import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
|
|||||||
* A bit of a hack, creates an empty intermediary mapping file to be used for mc versions without any intermediate mappings.
|
* A bit of a hack, creates an empty intermediary mapping file to be used for mc versions without any intermediate mappings.
|
||||||
*/
|
*/
|
||||||
public abstract class NoOpIntermediateMappingsProvider extends IntermediateMappingsProvider {
|
public abstract class NoOpIntermediateMappingsProvider extends IntermediateMappingsProvider {
|
||||||
private static final String HEADER = "tiny\t2\t0\tofficial\tintermediary";
|
private static final String HEADER_OFFICIAL_MERGED = "tiny\t2\t0\tofficial\tintermediary";
|
||||||
|
private static final String HEADER_OFFICIAL_LEGACY_MERGED = "tiny\t2\t0\tintermediary\tclientOfficial\tserverOfficial\t";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void provide(Path tinyMappings) throws IOException {
|
public void provide(Path tinyMappings) throws IOException {
|
||||||
Files.writeString(tinyMappings, HEADER, StandardCharsets.UTF_8);
|
Files.writeString(tinyMappings, getIsLegacyMinecraft().get() ? HEADER_OFFICIAL_LEGACY_MERGED : HEADER_OFFICIAL_MERGED, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public record SignatureFixesLayerImpl(Path mappingsFile) implements MappingLayer
|
|||||||
public Map<String, String> getSignatureFixes() {
|
public Map<String, String> getSignatureFixes() {
|
||||||
try {
|
try {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return ZipUtils.unpackJackson(mappingsFile(), SIGNATURE_FIXES_PATH, Map.class);
|
return ZipUtils.unpackJson(mappingsFile(), SIGNATURE_FIXES_PATH, Map.class);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("Failed to extract signature fixes", e);
|
throw new RuntimeException("Failed to extract signature fixes", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,6 @@ public record ParchmentMappingLayer(Path parchmentFile, boolean removePrefix) im
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ParchmentTreeV1 getParchmentData() throws IOException {
|
private ParchmentTreeV1 getParchmentData() throws IOException {
|
||||||
return ZipUtils.unpackJackson(parchmentFile, PARCHMENT_DATA_FILE_NAME, ParchmentTreeV1.class);
|
return ZipUtils.unpackJson(parchmentFile, PARCHMENT_DATA_FILE_NAME, ParchmentTreeV1.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService;
|
import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||||
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
|
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
|
||||||
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
|
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
|
||||||
import net.fabricmc.mappingio.format.tiny.Tiny2FileReader;
|
import net.fabricmc.mappingio.format.tiny.Tiny2FileReader;
|
||||||
@@ -49,10 +50,20 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
|||||||
public final class MappingsMerger {
|
public final class MappingsMerger {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(MappingsMerger.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(MappingsMerger.class);
|
||||||
|
|
||||||
public static void mergeAndSaveMappings(Path from, Path out, IntermediateMappingsService intermediateMappingsService) throws IOException {
|
public static void mergeAndSaveMappings(Path from, Path out, MinecraftProvider minecraftProvider, IntermediateMappingsService intermediateMappingsService) throws IOException {
|
||||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||||
LOGGER.info(":merging mappings");
|
LOGGER.info(":merging mappings");
|
||||||
|
|
||||||
|
if (minecraftProvider.isLegacyVersion()) {
|
||||||
|
legacyMergeAndSaveMappings(from, out, intermediateMappingsService);
|
||||||
|
} else {
|
||||||
|
mergeAndSaveMappings(from, out, intermediateMappingsService);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info(":merged mappings in " + stopwatch.stop());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void mergeAndSaveMappings(Path from, Path out, IntermediateMappingsService intermediateMappingsService) throws IOException {
|
||||||
MemoryMappingTree intermediaryTree = new MemoryMappingTree();
|
MemoryMappingTree intermediaryTree = new MemoryMappingTree();
|
||||||
intermediateMappingsService.getMemoryMappingTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString()));
|
intermediateMappingsService.getMemoryMappingTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString()));
|
||||||
|
|
||||||
@@ -70,8 +81,27 @@ public final class MappingsMerger {
|
|||||||
try (var writer = new Tiny2FileWriter(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) {
|
try (var writer = new Tiny2FileWriter(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) {
|
||||||
officialTree.accept(writer);
|
officialTree.accept(writer);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.info(":merged mappings in " + stopwatch.stop());
|
private static void legacyMergeAndSaveMappings(Path from, Path out, IntermediateMappingsService intermediateMappingsService) throws IOException {
|
||||||
|
MemoryMappingTree intermediaryTree = new MemoryMappingTree();
|
||||||
|
intermediateMappingsService.getMemoryMappingTree().accept(intermediaryTree);
|
||||||
|
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) {
|
||||||
|
Tiny2FileReader.read(reader, intermediaryTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryMappingTree officialTree = new MemoryMappingTree();
|
||||||
|
MappingNsCompleter nsCompleter = new MappingNsCompleter(officialTree, Map.of(MappingsNamespace.CLIENT_OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.SERVER_OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString()));
|
||||||
|
intermediaryTree.accept(nsCompleter);
|
||||||
|
|
||||||
|
// versions this old strip inner class attributes
|
||||||
|
// from the obfuscated jars anyway
|
||||||
|
//inheritMappedNamesOfEnclosingClasses(officialTree);
|
||||||
|
|
||||||
|
try (var writer = new Tiny2FileWriter(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) {
|
||||||
|
officialTree.accept(writer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.configuration.providers.minecraft;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
|
import net.fabricmc.loom.configuration.ConfigContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minecraft versions prior to 1.3 obfuscate the server and client jars differently.
|
||||||
|
* The obfuscated jars must be provided separately, and can be merged after remapping.
|
||||||
|
*/
|
||||||
|
public final class LegacyMergedMinecraftProvider extends MinecraftProvider {
|
||||||
|
private final SingleJarMinecraftProvider.Server serverMinecraftProvider;
|
||||||
|
private final SingleJarMinecraftProvider.Client clientMinecraftProvider;
|
||||||
|
|
||||||
|
public LegacyMergedMinecraftProvider(MinecraftMetadataProvider metadataProvider, ConfigContext configContext) {
|
||||||
|
super(metadataProvider, configContext);
|
||||||
|
serverMinecraftProvider = SingleJarMinecraftProvider.server(metadataProvider, configContext);
|
||||||
|
clientMinecraftProvider = SingleJarMinecraftProvider.client(metadataProvider, configContext);
|
||||||
|
|
||||||
|
if (!isLegacyVersion()) {
|
||||||
|
throw new RuntimeException("something has gone wrong - legacy-merged jar configuration selected but Minecraft " + metadataProvider.getMinecraftVersion() + " allows merging the obfuscated jars - the merged jar configuration should have been selected!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleJarMinecraftProvider.Server getServerMinecraftProvider() {
|
||||||
|
return serverMinecraftProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleJarMinecraftProvider.Client getClientMinecraftProvider() {
|
||||||
|
return clientMinecraftProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void provide() throws Exception {
|
||||||
|
if (!serverMinecraftProvider.provideServer() || !clientMinecraftProvider.provideClient()) {
|
||||||
|
throw new UnsupportedOperationException("This version does not provide both the client and server jars - please select the client-only or server-only jar configuration!");
|
||||||
|
}
|
||||||
|
|
||||||
|
serverMinecraftProvider.provide();
|
||||||
|
clientMinecraftProvider.provide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Path> getMinecraftJars() {
|
||||||
|
return List.of(
|
||||||
|
serverMinecraftProvider.getMinecraftEnvOnlyJar(),
|
||||||
|
clientMinecraftProvider.getMinecraftEnvOnlyJar()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public MappingsNamespace getOfficialNamespace() {
|
||||||
|
// Legacy merged providers do not have a single namespace as they delegate to the single jar providers
|
||||||
|
throw new UnsupportedOperationException("Cannot query the official namespace for legacy-merged minecraft providers");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,13 +31,22 @@ import java.nio.file.Path;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.configuration.ConfigContext;
|
import net.fabricmc.loom.configuration.ConfigContext;
|
||||||
|
|
||||||
public class MergedMinecraftProvider extends MinecraftProvider {
|
public class MergedMinecraftProvider extends MinecraftProvider {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(MergedMinecraftProvider.class);
|
||||||
private Path minecraftMergedJar;
|
private Path minecraftMergedJar;
|
||||||
|
|
||||||
public MergedMinecraftProvider(ConfigContext configContext) {
|
public MergedMinecraftProvider(MinecraftMetadataProvider metadataProvider, ConfigContext configContext) {
|
||||||
super(configContext);
|
super(metadataProvider, configContext);
|
||||||
|
|
||||||
|
if (isLegacyVersion()) {
|
||||||
|
throw new RuntimeException("something has gone wrong - merged jar configuration selected but Minecraft " + metadataProvider.getMinecraftVersion() + " does not allow merging the obfuscated jars - the legacy-merged jar configuration should have been selected!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -51,12 +60,17 @@ public class MergedMinecraftProvider extends MinecraftProvider {
|
|||||||
return List.of(minecraftMergedJar);
|
return List.of(minecraftMergedJar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MappingsNamespace getOfficialNamespace() {
|
||||||
|
return MappingsNamespace.OFFICIAL;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void provide() throws Exception {
|
public void provide() throws Exception {
|
||||||
super.provide();
|
super.provide();
|
||||||
|
|
||||||
if (!getVersionInfo().isVersionOrNewer("2012-07-25T22:00:00+00:00" /* 1.3 release date */)) {
|
if (!provideServer() || !provideClient()) {
|
||||||
throw new UnsupportedOperationException("Minecraft versions 1.2.5 and older cannot be merged. Please use `loom { server/clientOnlyMinecraftJar() }`");
|
throw new UnsupportedOperationException("This version does not provide both the client and server jars - please select the client-only or server-only jar configuration!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Files.exists(minecraftMergedJar) || getExtension().refreshDeps()) {
|
if (!Files.exists(minecraftMergedJar) || getExtension().refreshDeps()) {
|
||||||
@@ -74,18 +88,24 @@ public class MergedMinecraftProvider extends MinecraftProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void mergeJars() throws IOException {
|
protected void mergeJars() throws IOException {
|
||||||
getLogger().info(":merging jars");
|
File minecraftClientJar = getMinecraftClientJar();
|
||||||
|
File minecraftServerJar = getMinecraftServerJar();
|
||||||
File jarToMerge = getMinecraftServerJar();
|
|
||||||
|
|
||||||
if (getServerBundleMetadata() != null) {
|
if (getServerBundleMetadata() != null) {
|
||||||
extractBundledServerJar();
|
extractBundledServerJar();
|
||||||
jarToMerge = getMinecraftExtractedServerJar();
|
minecraftServerJar = getMinecraftExtractedServerJar();
|
||||||
}
|
}
|
||||||
|
|
||||||
Objects.requireNonNull(jarToMerge, "Cannot merge null input jar?");
|
mergeJars(minecraftClientJar, minecraftServerJar, minecraftMergedJar.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
try (var jarMerger = new MinecraftJarMerger(getMinecraftClientJar(), jarToMerge, minecraftMergedJar.toFile())) {
|
public static void mergeJars(File clientJar, File serverJar, File mergedJar) throws IOException {
|
||||||
|
LOGGER.info(":merging jars");
|
||||||
|
|
||||||
|
Objects.requireNonNull(clientJar, "Cannot merge null client jar?");
|
||||||
|
Objects.requireNonNull(serverJar, "Cannot merge null server jar?");
|
||||||
|
|
||||||
|
try (var jarMerger = new MinecraftJarMerger(clientJar, serverJar, mergedJar)) {
|
||||||
jarMerger.enableSyntheticParamsOffset();
|
jarMerger.enableSyntheticParamsOffset();
|
||||||
jarMerger.merge();
|
jarMerger.merge();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,6 +257,8 @@ public class MinecraftClassMerger {
|
|||||||
int j = 0;
|
int j = 0;
|
||||||
|
|
||||||
while (i < first.size() || j < second.size()) {
|
while (i < first.size() || j < second.size()) {
|
||||||
|
int saved = i + j;
|
||||||
|
|
||||||
while (i < first.size() && j < second.size()
|
while (i < first.size() && j < second.size()
|
||||||
&& first.get(i).equals(second.get(j))) {
|
&& first.get(i).equals(second.get(j))) {
|
||||||
out.add(first.get(i));
|
out.add(first.get(i));
|
||||||
@@ -273,6 +275,20 @@ public class MinecraftClassMerger {
|
|||||||
out.add(second.get(j));
|
out.add(second.get(j));
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the order is scrambled, it's not possible to merge
|
||||||
|
// the lists while preserving the order from both sides
|
||||||
|
if (i + j == saved) {
|
||||||
|
for (; i < first.size(); i++) {
|
||||||
|
out.add(first.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; j < second.size(); j++) {
|
||||||
|
if (!first.contains(second.get(j))) {
|
||||||
|
out.add(second.get(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -25,11 +25,10 @@
|
|||||||
package net.fabricmc.loom.configuration.providers.minecraft;
|
package net.fabricmc.loom.configuration.providers.minecraft;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.configuration.ConfigContext;
|
import net.fabricmc.loom.configuration.ConfigContext;
|
||||||
import net.fabricmc.loom.configuration.decompile.DecompileConfiguration;
|
import net.fabricmc.loom.configuration.decompile.DecompileConfiguration;
|
||||||
import net.fabricmc.loom.configuration.decompile.SingleJarDecompileConfiguration;
|
import net.fabricmc.loom.configuration.decompile.SingleJarDecompileConfiguration;
|
||||||
@@ -43,96 +42,110 @@ import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraft
|
|||||||
import net.fabricmc.loom.configuration.providers.minecraft.mapped.ProcessedNamedMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.mapped.ProcessedNamedMinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider;
|
||||||
|
|
||||||
public enum MinecraftJarConfiguration {
|
public record MinecraftJarConfiguration<
|
||||||
MERGED(
|
M extends MinecraftProvider,
|
||||||
ForgeMinecraftProvider::createMerged,
|
N extends NamedMinecraftProvider<M>,
|
||||||
IntermediaryMinecraftProvider.MergedImpl::new,
|
Q extends MappedMinecraftProvider>(
|
||||||
NamedMinecraftProvider.MergedImpl::new,
|
MinecraftProviderFactory<M> minecraftProviderFactory,
|
||||||
SrgMinecraftProvider.MergedImpl::new,
|
IntermediaryMinecraftProviderFactory<M> intermediaryMinecraftProviderFactory,
|
||||||
MojangMappedMinecraftProvider.MergedImpl::new,
|
NamedMinecraftProviderFactory<M> namedMinecraftProviderFactory,
|
||||||
ProcessedNamedMinecraftProvider.MergedImpl::new,
|
ProcessedNamedMinecraftProviderFactory<M, N> processedNamedMinecraftProviderFactory,
|
||||||
SingleJarDecompileConfiguration::new,
|
DecompileConfigurationFactory<Q> decompileConfigurationFactory,
|
||||||
List.of("client", "server")
|
List<String> supportedEnvironments) {
|
||||||
),
|
public static final MinecraftJarConfiguration<
|
||||||
SERVER_ONLY(
|
MergedMinecraftProvider,
|
||||||
ForgeMinecraftProvider::createServerOnly,
|
NamedMinecraftProvider.MergedImpl,
|
||||||
IntermediaryMinecraftProvider.SingleJarImpl::server,
|
MappedMinecraftProvider> MERGED = new MinecraftJarConfiguration<>(
|
||||||
NamedMinecraftProvider.SingleJarImpl::server,
|
ForgeMinecraftProvider::createMerged,
|
||||||
SrgMinecraftProvider.SingleJarImpl::server,
|
IntermediaryMinecraftProvider.MergedImpl::new,
|
||||||
MojangMappedMinecraftProvider.SingleJarImpl::server,
|
NamedMinecraftProvider.MergedImpl::new,
|
||||||
ProcessedNamedMinecraftProvider.SingleJarImpl::server,
|
SrgMinecraftProvider.MergedImpl::new,
|
||||||
SingleJarDecompileConfiguration::new,
|
MojangMappedMinecraftProvider.MergedImpl::new,
|
||||||
List.of("server")
|
ProcessedNamedMinecraftProvider.MergedImpl::new,
|
||||||
),
|
SingleJarDecompileConfiguration::new,
|
||||||
CLIENT_ONLY(
|
List.of("client", "server")
|
||||||
ForgeMinecraftProvider::createClientOnly,
|
);
|
||||||
IntermediaryMinecraftProvider.SingleJarImpl::client,
|
public static final MinecraftJarConfiguration<
|
||||||
NamedMinecraftProvider.SingleJarImpl::client,
|
LegacyMergedMinecraftProvider,
|
||||||
SrgMinecraftProvider.SingleJarImpl::client,
|
NamedMinecraftProvider.LegacyMergedImpl,
|
||||||
MojangMappedMinecraftProvider.SingleJarImpl::client,
|
MappedMinecraftProvider> LEGACY_MERGED = new MinecraftJarConfiguration<>(
|
||||||
ProcessedNamedMinecraftProvider.SingleJarImpl::client,
|
LegacyMergedMinecraftProvider::new,
|
||||||
SingleJarDecompileConfiguration::new,
|
IntermediaryMinecraftProvider.LegacyMergedImpl::new,
|
||||||
List.of("client")
|
NamedMinecraftProvider.LegacyMergedImpl::new,
|
||||||
),
|
ProcessedNamedMinecraftProvider.LegacyMergedImpl::new,
|
||||||
SPLIT(
|
SingleJarDecompileConfiguration::new,
|
||||||
SplitMinecraftProvider::new,
|
List.of("client", "server")
|
||||||
IntermediaryMinecraftProvider.SplitImpl::new,
|
);
|
||||||
NamedMinecraftProvider.SplitImpl::new,
|
public static final MinecraftJarConfiguration<
|
||||||
SrgMinecraftProvider.SplitImpl::new,
|
SingleJarMinecraftProvider,
|
||||||
MojangMappedMinecraftProvider.SplitImpl::new,
|
NamedMinecraftProvider.SingleJarImpl,
|
||||||
ProcessedNamedMinecraftProvider.SplitImpl::new,
|
MappedMinecraftProvider> SERVER_ONLY = new MinecraftJarConfiguration<>(
|
||||||
SplitDecompileConfiguration::new,
|
ForgeMinecraftProvider::createServerOnly,
|
||||||
List.of("client", "server")
|
IntermediaryMinecraftProvider.SingleJarImpl::server,
|
||||||
);
|
NamedMinecraftProvider.SingleJarImpl::server,
|
||||||
|
SrgMinecraftProvider.SingleJarImpl::server,
|
||||||
|
MojangMappedMinecraftProvider.SingleJarImpl::server,
|
||||||
|
ProcessedNamedMinecraftProvider.SingleJarImpl::server,
|
||||||
|
SingleJarDecompileConfiguration::new,
|
||||||
|
List.of("server")
|
||||||
|
);
|
||||||
|
public static final MinecraftJarConfiguration<
|
||||||
|
SingleJarMinecraftProvider,
|
||||||
|
NamedMinecraftProvider.SingleJarImpl,
|
||||||
|
MappedMinecraftProvider> CLIENT_ONLY = new MinecraftJarConfiguration<>(
|
||||||
|
ForgeMinecraftProvider::createClientOnly,
|
||||||
|
IntermediaryMinecraftProvider.SingleJarImpl::client,
|
||||||
|
NamedMinecraftProvider.SingleJarImpl::client,
|
||||||
|
SrgMinecraftProvider.SingleJarImpl::client,
|
||||||
|
MojangMappedMinecraftProvider.SingleJarImpl::client,
|
||||||
|
ProcessedNamedMinecraftProvider.SingleJarImpl::client,
|
||||||
|
SingleJarDecompileConfiguration::new,
|
||||||
|
List.of("client")
|
||||||
|
);
|
||||||
|
public static final MinecraftJarConfiguration<
|
||||||
|
SplitMinecraftProvider,
|
||||||
|
NamedMinecraftProvider.SplitImpl,
|
||||||
|
MappedMinecraftProvider.Split> SPLIT = new MinecraftJarConfiguration<>(
|
||||||
|
SplitMinecraftProvider::new,
|
||||||
|
IntermediaryMinecraftProvider.SplitImpl::new,
|
||||||
|
NamedMinecraftProvider.SplitImpl::new,
|
||||||
|
SrgMinecraftProvider.SplitImpl::new,
|
||||||
|
MojangMappedMinecraftProvider.SplitImpl::new,
|
||||||
|
ProcessedNamedMinecraftProvider.SplitImpl::new,
|
||||||
|
SplitDecompileConfiguration::new,
|
||||||
|
List.of("client", "server")
|
||||||
|
);
|
||||||
|
|
||||||
private final Function<ConfigContext, MinecraftProvider> minecraftProviderFunction;
|
public MinecraftProvider createMinecraftProvider(MinecraftMetadataProvider metadataProvider, ConfigContext context) {
|
||||||
private final BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>> intermediaryMinecraftProviderBiFunction;
|
return minecraftProviderFactory.create(metadataProvider, context);
|
||||||
private final BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>> namedMinecraftProviderBiFunction;
|
|
||||||
private final BiFunction<Project, MinecraftProvider, SrgMinecraftProvider<?>> srgMinecraftProviderBiFunction;
|
|
||||||
private final BiFunction<Project, MinecraftProvider, MojangMappedMinecraftProvider<?>> mojangMappedMinecraftProviderBiFunction;
|
|
||||||
private final BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> processedNamedMinecraftProviderBiFunction;
|
|
||||||
private final BiFunction<Project, MappedMinecraftProvider, DecompileConfiguration<?>> decompileConfigurationBiFunction;
|
|
||||||
private final List<String> supportedEnvironments;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") // Just a bit of a generic mess :)
|
|
||||||
<M extends MinecraftProvider, P extends NamedMinecraftProvider<M>, Q extends MappedMinecraftProvider> MinecraftJarConfiguration(
|
|
||||||
Function<ConfigContext, M> minecraftProviderFunction,
|
|
||||||
BiFunction<Project, M, IntermediaryMinecraftProvider<M>> intermediaryMinecraftProviderBiFunction,
|
|
||||||
BiFunction<Project, M, P> namedMinecraftProviderBiFunction,
|
|
||||||
BiFunction<Project, M, SrgMinecraftProvider<M>> srgMinecraftProviderBiFunction,
|
|
||||||
BiFunction<Project, M, MojangMappedMinecraftProvider<M>> mojangMappedMinecraftProviderBiFunction,
|
|
||||||
BiFunction<P, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<M, P>> processedNamedMinecraftProviderBiFunction,
|
|
||||||
BiFunction<Project, Q, DecompileConfiguration<?>> decompileConfigurationBiFunction,
|
|
||||||
List<String> supportedEnvironments
|
|
||||||
) {
|
|
||||||
this.minecraftProviderFunction = (Function<ConfigContext, MinecraftProvider>) minecraftProviderFunction;
|
|
||||||
this.intermediaryMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>>) (Object) intermediaryMinecraftProviderBiFunction;
|
|
||||||
this.namedMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>>) namedMinecraftProviderBiFunction;
|
|
||||||
this.srgMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, SrgMinecraftProvider<?>>) (Object) srgMinecraftProviderBiFunction;
|
|
||||||
this.mojangMappedMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, MojangMappedMinecraftProvider<?>>) (Object) mojangMappedMinecraftProviderBiFunction;
|
|
||||||
this.processedNamedMinecraftProviderBiFunction = (BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>>) (Object) processedNamedMinecraftProviderBiFunction;
|
|
||||||
this.decompileConfigurationBiFunction = (BiFunction<Project, MappedMinecraftProvider, DecompileConfiguration<?>>) decompileConfigurationBiFunction;
|
|
||||||
this.supportedEnvironments = supportedEnvironments;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Function<ConfigContext, MinecraftProvider> getMinecraftProviderFunction() {
|
public IntermediaryMinecraftProvider<M> createIntermediaryMinecraftProvider(Project project) {
|
||||||
return minecraftProviderFunction;
|
return intermediaryMinecraftProviderFactory.create(project, getMinecraftProvider(project));
|
||||||
}
|
}
|
||||||
|
|
||||||
public BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>> getIntermediaryMinecraftProviderBiFunction() {
|
public NamedMinecraftProvider<M> createNamedMinecraftProvider(Project project) {
|
||||||
return intermediaryMinecraftProviderBiFunction;
|
return namedMinecraftProviderFactory.create(project, getMinecraftProvider(project));
|
||||||
}
|
}
|
||||||
|
|
||||||
public BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>> getNamedMinecraftProviderBiFunction() {
|
public ProcessedNamedMinecraftProvider<M, N> createProcessedNamedMinecraftProvider(NamedMinecraftProvider<?> namedMinecraftProvider, MinecraftJarProcessorManager jarProcessorManager) {
|
||||||
return namedMinecraftProviderBiFunction;
|
return processedNamedMinecraftProviderFactory.create((N) namedMinecraftProvider, jarProcessorManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> getProcessedNamedMinecraftProviderBiFunction() {
|
public DecompileConfiguration<Q> createDecompileConfiguration(Project project) {
|
||||||
return processedNamedMinecraftProviderBiFunction;
|
return decompileConfigurationFactory.create(project, getMappedMinecraftProvider(project));
|
||||||
}
|
}
|
||||||
|
|
||||||
public BiFunction<Project, MappedMinecraftProvider, DecompileConfiguration<?>> getDecompileConfigurationBiFunction() {
|
private M getMinecraftProvider(Project project) {
|
||||||
return decompileConfigurationBiFunction;
|
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
|
//noinspection unchecked
|
||||||
|
return (M) extension.getMinecraftProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Q getMappedMinecraftProvider(Project project) {
|
||||||
|
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
|
//noinspection unchecked
|
||||||
|
return (Q) extension.getNamedMinecraftProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BiFunction<Project, MinecraftProvider, SrgMinecraftProvider<?>> getSrgMinecraftProviderBiFunction() {
|
public BiFunction<Project, MinecraftProvider, SrgMinecraftProvider<?>> getSrgMinecraftProviderBiFunction() {
|
||||||
@@ -146,4 +159,33 @@ public enum MinecraftJarConfiguration {
|
|||||||
public List<String> getSupportedEnvironments() {
|
public List<String> getSupportedEnvironments() {
|
||||||
return supportedEnvironments;
|
return supportedEnvironments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Factory interfaces:
|
||||||
|
private interface MinecraftProviderFactory<M extends MinecraftProvider> {
|
||||||
|
M create(MinecraftMetadataProvider metadataProvider, ConfigContext configContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface IntermediaryMinecraftProviderFactory<M extends MinecraftProvider> {
|
||||||
|
IntermediaryMinecraftProvider<M> create(Project project, M minecraftProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface NamedMinecraftProviderFactory<M extends MinecraftProvider> {
|
||||||
|
NamedMinecraftProvider<M> create(Project project, M minecraftProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface SrgMinecraftProviderFactory<M extends MinecraftProvider> {
|
||||||
|
SrgMinecraftProvider<M> create(Project project, M minecraftProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface MojangMappedMinecraftProviderFactory<M extends MinecraftProvider> {
|
||||||
|
MojangMappedMinecraftProvider<M> create(Project project, M minecraftProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ProcessedNamedMinecraftProviderFactory<M extends MinecraftProvider, N extends NamedMinecraftProvider<M>> {
|
||||||
|
ProcessedNamedMinecraftProvider<M, N> create(N namedMinecraftProvider, MinecraftJarProcessorManager jarProcessorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface DecompileConfigurationFactory<M extends MappedMinecraftProvider> {
|
||||||
|
DecompileConfiguration<M> create(Project project, M minecraftProvider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ public class MinecraftJarMerger implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Files.createDirectories(output.toPath().getParent());
|
||||||
|
|
||||||
this.inputClient = (inputClientFs = FileSystemUtil.getJarFileSystem(inputClient, false)).get().getPath("/");
|
this.inputClient = (inputClientFs = FileSystemUtil.getJarFileSystem(inputClient, false)).get().getPath("/");
|
||||||
this.inputServer = (inputServerFs = FileSystemUtil.getJarFileSystem(inputServer, false)).get().getPath("/");
|
this.inputServer = (inputServerFs = FileSystemUtil.getJarFileSystem(inputServer, false)).get().getPath("/");
|
||||||
this.outputFs = FileSystemUtil.getJarFileSystem(output, true);
|
this.outputFs = FileSystemUtil.getJarFileSystem(output, true);
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ public class MinecraftLibraryProvider {
|
|||||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
final MinecraftJarConfiguration jarConfiguration = extension.getMinecraftJarConfiguration().get();
|
final MinecraftJarConfiguration jarConfiguration = extension.getMinecraftJarConfiguration().get();
|
||||||
|
|
||||||
final boolean provideClient = jarConfiguration.getSupportedEnvironments().contains("client");
|
final boolean provideClient = jarConfiguration.supportedEnvironments().contains("client");
|
||||||
final boolean provideServer = jarConfiguration.getSupportedEnvironments().contains("server");
|
final boolean provideServer = jarConfiguration.supportedEnvironments().contains("server");
|
||||||
assert provideClient || provideServer;
|
assert provideClient || provideServer;
|
||||||
|
|
||||||
if (provideClient) {
|
if (provideClient) {
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.LoomGradlePlugin;
|
import net.fabricmc.loom.LoomGradlePlugin;
|
||||||
|
import net.fabricmc.loom.configuration.ConfigContext;
|
||||||
|
import net.fabricmc.loom.configuration.DependencyInfo;
|
||||||
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.MirrorUtil;
|
import net.fabricmc.loom.util.MirrorUtil;
|
||||||
import net.fabricmc.loom.util.download.DownloadBuilder;
|
import net.fabricmc.loom.util.download.DownloadBuilder;
|
||||||
|
|
||||||
@@ -45,11 +48,34 @@ public final class MinecraftMetadataProvider {
|
|||||||
private ManifestVersion.Versions versionEntry;
|
private ManifestVersion.Versions versionEntry;
|
||||||
private MinecraftVersionMeta versionMeta;
|
private MinecraftVersionMeta versionMeta;
|
||||||
|
|
||||||
public MinecraftMetadataProvider(Options options, Function<String, DownloadBuilder> download) {
|
private MinecraftMetadataProvider(Options options, Function<String, DownloadBuilder> download) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.download = download;
|
this.download = download;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MinecraftMetadataProvider create(ConfigContext configContext) {
|
||||||
|
final String minecraftVersion = resolveMinecraftVersion(configContext.project());
|
||||||
|
final Path workingDir = MinecraftProvider.minecraftWorkingDirectory(configContext.project(), minecraftVersion).toPath();
|
||||||
|
|
||||||
|
return new MinecraftMetadataProvider(
|
||||||
|
MinecraftMetadataProvider.Options.create(
|
||||||
|
minecraftVersion,
|
||||||
|
configContext.project(),
|
||||||
|
workingDir.resolve("minecraft-info.json")
|
||||||
|
),
|
||||||
|
configContext.extension()::download
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String resolveMinecraftVersion(Project project) {
|
||||||
|
final DependencyInfo dependency = DependencyInfo.create(project, Constants.Configurations.MINECRAFT);
|
||||||
|
return dependency.getDependency().getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMinecraftVersion() {
|
||||||
|
return options.minecraftVersion();
|
||||||
|
}
|
||||||
|
|
||||||
public MinecraftVersionMeta getVersionMeta() {
|
public MinecraftVersionMeta getVersionMeta() {
|
||||||
try {
|
try {
|
||||||
if (versionEntry == null) {
|
if (versionEntry == null) {
|
||||||
|
|||||||
@@ -31,13 +31,15 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import org.gradle.api.JavaVersion;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.logging.Logger;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.configuration.ConfigContext;
|
import net.fabricmc.loom.configuration.ConfigContext;
|
||||||
import net.fabricmc.loom.configuration.DependencyInfo;
|
|
||||||
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.download.DownloadExecutor;
|
import net.fabricmc.loom.util.download.DownloadExecutor;
|
||||||
@@ -45,10 +47,10 @@ import net.fabricmc.loom.util.download.GradleDownloadProgressListener;
|
|||||||
import net.fabricmc.loom.util.gradle.ProgressGroup;
|
import net.fabricmc.loom.util.gradle.ProgressGroup;
|
||||||
|
|
||||||
public abstract class MinecraftProvider {
|
public abstract class MinecraftProvider {
|
||||||
private String minecraftVersion;
|
private static final Logger LOGGER = LoggerFactory.getLogger(MinecraftProvider.class);
|
||||||
private MinecraftMetadataProvider metadataProvider;
|
|
||||||
|
private final MinecraftMetadataProvider metadataProvider;
|
||||||
|
|
||||||
private File workingDir;
|
|
||||||
private File minecraftClientJar;
|
private File minecraftClientJar;
|
||||||
// Note this will be the boostrap jar starting with 21w39a
|
// Note this will be the boostrap jar starting with 21w39a
|
||||||
private File minecraftServerJar;
|
private File minecraftServerJar;
|
||||||
@@ -58,10 +60,11 @@ public abstract class MinecraftProvider {
|
|||||||
private BundleMetadata serverBundleMetadata;
|
private BundleMetadata serverBundleMetadata;
|
||||||
private String jarPrefix = "";
|
private String jarPrefix = "";
|
||||||
|
|
||||||
private final Project project;
|
private final ConfigContext configContext;
|
||||||
|
|
||||||
public MinecraftProvider(ConfigContext configContext) {
|
public MinecraftProvider(MinecraftMetadataProvider metadataProvider, ConfigContext configContext) {
|
||||||
this.project = configContext.project();
|
this.metadataProvider = metadataProvider;
|
||||||
|
this.configContext = configContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean provideClient() {
|
protected boolean provideClient() {
|
||||||
@@ -73,23 +76,22 @@ public abstract class MinecraftProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void provide() throws Exception {
|
public void provide() throws Exception {
|
||||||
final DependencyInfo dependency = DependencyInfo.create(getProject(), Constants.Configurations.MINECRAFT);
|
|
||||||
minecraftVersion = dependency.getDependency().getVersion();
|
|
||||||
|
|
||||||
if (getExtension().shouldGenerateSrgTiny() && !getExtension().isForgeLike()) {
|
if (getExtension().shouldGenerateSrgTiny() && !getExtension().isForgeLike()) {
|
||||||
getProject().getDependencies().add(Constants.Configurations.SRG, "de.oceanlabs.mcp:mcp_config:" + minecraftVersion);
|
getProject().getDependencies().add(Constants.Configurations.SRG, "de.oceanlabs.mcp:mcp_config:" + minecraftVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
initFiles();
|
initFiles();
|
||||||
|
|
||||||
metadataProvider = new MinecraftMetadataProvider(
|
final MinecraftVersionMeta.JavaVersion javaVersion = getVersionInfo().javaVersion();
|
||||||
MinecraftMetadataProvider.Options.create(
|
|
||||||
minecraftVersion,
|
if (javaVersion != null) {
|
||||||
getProject(),
|
final int requiredMajorJavaVersion = getVersionInfo().javaVersion().majorVersion();
|
||||||
file("minecraft-info.json").toPath()
|
final JavaVersion requiredJavaVersion = JavaVersion.toVersion(requiredMajorJavaVersion);
|
||||||
),
|
|
||||||
getExtension()::download
|
if (!JavaVersion.current().isCompatibleWith(requiredJavaVersion)) {
|
||||||
);
|
throw new IllegalStateException("Minecraft " + minecraftVersion() + " requires Java " + requiredJavaVersion + " but Gradle is using " + JavaVersion.current());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
downloadJars();
|
downloadJars();
|
||||||
|
|
||||||
@@ -97,14 +99,11 @@ public abstract class MinecraftProvider {
|
|||||||
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
|
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
final MinecraftLibraryProvider libraryProvider = new MinecraftLibraryProvider(this, project);
|
final MinecraftLibraryProvider libraryProvider = new MinecraftLibraryProvider(this, configContext.project());
|
||||||
libraryProvider.provide();
|
libraryProvider.provide();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initFiles() {
|
protected void initFiles() {
|
||||||
workingDir = new File(getExtension().getFiles().getUserCache(), minecraftVersion);
|
|
||||||
workingDir.mkdirs();
|
|
||||||
|
|
||||||
if (provideClient()) {
|
if (provideClient()) {
|
||||||
minecraftClientJar = file("minecraft-client.jar");
|
minecraftClientJar = file("minecraft-client.jar");
|
||||||
}
|
}
|
||||||
@@ -140,17 +139,17 @@ public abstract class MinecraftProvider {
|
|||||||
Preconditions.checkArgument(provideServer(), "Not configured to provide server jar");
|
Preconditions.checkArgument(provideServer(), "Not configured to provide server jar");
|
||||||
Objects.requireNonNull(getServerBundleMetadata(), "Cannot bundled mc jar from none bundled server jar");
|
Objects.requireNonNull(getServerBundleMetadata(), "Cannot bundled mc jar from none bundled server jar");
|
||||||
|
|
||||||
getLogger().info(":Extracting server jar from bootstrap");
|
LOGGER.info(":Extracting server jar from bootstrap");
|
||||||
|
|
||||||
if (getServerBundleMetadata().versions().size() != 1) {
|
if (getServerBundleMetadata().versions().size() != 1) {
|
||||||
throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(getServerBundleMetadata().versions().size()));
|
throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(getServerBundleMetadata().versions().size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
getServerBundleMetadata().versions().get(0).unpackEntry(minecraftServerJar.toPath(), getMinecraftExtractedServerJar().toPath(), project);
|
getServerBundleMetadata().versions().get(0).unpackEntry(minecraftServerJar.toPath(), getMinecraftExtractedServerJar().toPath(), configContext.project());
|
||||||
}
|
}
|
||||||
|
|
||||||
public File workingDir() {
|
public File workingDir() {
|
||||||
return workingDir;
|
return minecraftWorkingDirectory(configContext.project(), minecraftVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
public File dir(String path) {
|
public File dir(String path) {
|
||||||
@@ -186,13 +185,20 @@ public abstract class MinecraftProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String minecraftVersion() {
|
public String minecraftVersion() {
|
||||||
return minecraftVersion;
|
return Objects.requireNonNull(metadataProvider, "Metadata provider not setup").getMinecraftVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinecraftVersionMeta getVersionInfo() {
|
public MinecraftVersionMeta getVersionInfo() {
|
||||||
return Objects.requireNonNull(metadataProvider, "Metadata provider not setup").getVersionMeta();
|
return Objects.requireNonNull(metadataProvider, "Metadata provider not setup").getVersionMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the minecraft version is older than 1.3.
|
||||||
|
*/
|
||||||
|
public boolean isLegacyVersion() {
|
||||||
|
return !getVersionInfo().isVersionOrNewer(Constants.RELEASE_TIME_1_3);
|
||||||
|
}
|
||||||
|
|
||||||
public String getJarPrefix() {
|
public String getJarPrefix() {
|
||||||
return jarPrefix;
|
return jarPrefix;
|
||||||
}
|
}
|
||||||
@@ -206,21 +212,26 @@ public abstract class MinecraftProvider {
|
|||||||
return serverBundleMetadata;
|
return serverBundleMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Logger getLogger() {
|
|
||||||
return getProject().getLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract List<Path> getMinecraftJars();
|
public abstract List<Path> getMinecraftJars();
|
||||||
|
|
||||||
|
public abstract MappingsNamespace getOfficialNamespace();
|
||||||
|
|
||||||
protected Project getProject() {
|
protected Project getProject() {
|
||||||
return project;
|
return configContext.project();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LoomGradleExtension getExtension() {
|
protected LoomGradleExtension getExtension() {
|
||||||
return LoomGradleExtension.get(getProject());
|
return configContext.extension();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean refreshDeps() {
|
public boolean refreshDeps() {
|
||||||
return getExtension().refreshDeps();
|
return getExtension().refreshDeps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static File minecraftWorkingDirectory(Project project, String version) {
|
||||||
|
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
|
File workingDir = new File(extension.getFiles().getUserCache(), version);
|
||||||
|
workingDir.mkdirs();
|
||||||
|
return workingDir;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ public record MinecraftVersionMeta(
|
|||||||
int minimumLauncherVersion,
|
int minimumLauncherVersion,
|
||||||
String releaseTime,
|
String releaseTime,
|
||||||
String time,
|
String time,
|
||||||
String type
|
String type,
|
||||||
|
@Nullable JavaVersion javaVersion
|
||||||
) {
|
) {
|
||||||
private static Map<Platform.OperatingSystem, String> OS_NAMES = Map.of(
|
private static Map<Platform.OperatingSystem, String> OS_NAMES = Map.of(
|
||||||
Platform.OperatingSystem.WINDOWS, "windows",
|
Platform.OperatingSystem.WINDOWS, "windows",
|
||||||
@@ -168,4 +169,7 @@ public record MinecraftVersionMeta(
|
|||||||
return new File(baseDirectory, path());
|
return new File(baseDirectory, path());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record JavaVersion(String component, int majorVersion) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,35 +28,45 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.configuration.ConfigContext;
|
import net.fabricmc.loom.configuration.ConfigContext;
|
||||||
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
||||||
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.tinyremapper.NonClassCopyMode;
|
import net.fabricmc.tinyremapper.NonClassCopyMode;
|
||||||
import net.fabricmc.tinyremapper.OutputConsumerPath;
|
import net.fabricmc.tinyremapper.OutputConsumerPath;
|
||||||
import net.fabricmc.tinyremapper.TinyRemapper;
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
|
|
||||||
public class SingleJarMinecraftProvider extends MinecraftProvider {
|
public abstract sealed class SingleJarMinecraftProvider extends MinecraftProvider permits SingleJarMinecraftProvider.Server, SingleJarMinecraftProvider.Client {
|
||||||
private final Environment environment;
|
private final MappingsNamespace officialNamespace;
|
||||||
|
|
||||||
private Path minecraftEnvOnlyJar;
|
private Path minecraftEnvOnlyJar;
|
||||||
|
|
||||||
protected SingleJarMinecraftProvider(ConfigContext configContext, Environment environment) {
|
protected SingleJarMinecraftProvider(MinecraftMetadataProvider metadataProvider, ConfigContext configContext, MappingsNamespace officialNamespace) {
|
||||||
super(configContext);
|
super(metadataProvider, configContext);
|
||||||
this.environment = environment;
|
this.officialNamespace = officialNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SingleJarMinecraftProvider server(ConfigContext configContext) {
|
public static SingleJarMinecraftProvider.Server server(MinecraftMetadataProvider metadataProvider, ConfigContext configContext) {
|
||||||
return new SingleJarMinecraftProvider(configContext, new Server());
|
return new SingleJarMinecraftProvider.Server(metadataProvider, configContext, getOfficialNamespace(metadataProvider, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SingleJarMinecraftProvider client(ConfigContext configContext) {
|
public static SingleJarMinecraftProvider.Client client(MinecraftMetadataProvider metadataProvider, ConfigContext configContext) {
|
||||||
return new SingleJarMinecraftProvider(configContext, new Client());
|
return new SingleJarMinecraftProvider.Client(metadataProvider, configContext, getOfficialNamespace(metadataProvider, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MappingsNamespace getOfficialNamespace(MinecraftMetadataProvider metadataProvider, boolean server) {
|
||||||
|
// Versions before 1.3 don't have a common namespace, so use side specific namespaces.
|
||||||
|
if (!metadataProvider.getVersionMeta().isVersionOrNewer(Constants.RELEASE_TIME_1_3)) {
|
||||||
|
return server ? MappingsNamespace.SERVER_OFFICIAL : MappingsNamespace.CLIENT_OFFICIAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MappingsNamespace.OFFICIAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initFiles() {
|
protected void initFiles() {
|
||||||
super.initFiles();
|
super.initFiles();
|
||||||
|
|
||||||
minecraftEnvOnlyJar = path("minecraft-%s-only.jar".formatted(environment.type()));
|
minecraftEnvOnlyJar = path("minecraft-%s-only.jar".formatted(type()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -69,7 +79,7 @@ public class SingleJarMinecraftProvider extends MinecraftProvider {
|
|||||||
super.provide();
|
super.provide();
|
||||||
|
|
||||||
// Server only JARs are supported on any version, client only JARs are pretty much useless after 1.3.
|
// Server only JARs are supported on any version, client only JARs are pretty much useless after 1.3.
|
||||||
if (provideClient() && getVersionInfo().isVersionOrNewer("2012-07-25T22:00:00+00:00" /* 1.3 release date */)) {
|
if (provideClient() && !isLegacyVersion()) {
|
||||||
getProject().getLogger().warn("Using `clientOnlyMinecraftJar()` is not recommended for Minecraft versions 1.3 or newer.");
|
getProject().getLogger().warn("Using `clientOnlyMinecraftJar()` is not recommended for Minecraft versions 1.3 or newer.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +93,7 @@ public class SingleJarMinecraftProvider extends MinecraftProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Path inputJar = environment.getInputJar(this);
|
final Path inputJar = getInputJar(this);
|
||||||
|
|
||||||
TinyRemapper remapper = null;
|
TinyRemapper remapper = null;
|
||||||
|
|
||||||
@@ -100,7 +110,7 @@ public class SingleJarMinecraftProvider extends MinecraftProvider {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Files.deleteIfExists(minecraftEnvOnlyJar);
|
Files.deleteIfExists(minecraftEnvOnlyJar);
|
||||||
throw new RuntimeException("Failed to process %s only jar".formatted(environment.type()), e);
|
throw new RuntimeException("Failed to process %s only jar".formatted(type()), e);
|
||||||
} finally {
|
} finally {
|
||||||
if (remapper != null) {
|
if (remapper != null) {
|
||||||
remapper.finish();
|
remapper.finish();
|
||||||
@@ -108,27 +118,24 @@ public class SingleJarMinecraftProvider extends MinecraftProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean provideClient() {
|
|
||||||
return environment instanceof Client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean provideServer() {
|
|
||||||
return environment instanceof Server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getMinecraftEnvOnlyJar() {
|
public Path getMinecraftEnvOnlyJar() {
|
||||||
return minecraftEnvOnlyJar;
|
return minecraftEnvOnlyJar;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected interface Environment {
|
@Override
|
||||||
SingleJarEnvType type();
|
public MappingsNamespace getOfficialNamespace() {
|
||||||
|
return officialNamespace;
|
||||||
Path getInputJar(SingleJarMinecraftProvider provider) throws Exception;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Server implements Environment {
|
abstract SingleJarEnvType type();
|
||||||
|
|
||||||
|
abstract Path getInputJar(SingleJarMinecraftProvider provider) throws Exception;
|
||||||
|
|
||||||
|
public static final class Server extends SingleJarMinecraftProvider {
|
||||||
|
private Server(MinecraftMetadataProvider metadataProvider, ConfigContext configContext, MappingsNamespace officialNamespace) {
|
||||||
|
super(metadataProvider, configContext, officialNamespace);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SingleJarEnvType type() {
|
public SingleJarEnvType type() {
|
||||||
return SingleJarEnvType.SERVER;
|
return SingleJarEnvType.SERVER;
|
||||||
@@ -145,9 +152,23 @@ public class SingleJarMinecraftProvider extends MinecraftProvider {
|
|||||||
provider.extractBundledServerJar();
|
provider.extractBundledServerJar();
|
||||||
return provider.getMinecraftExtractedServerJar().toPath();
|
return provider.getMinecraftExtractedServerJar().toPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean provideServer() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean provideClient() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Client implements Environment {
|
public static final class Client extends SingleJarMinecraftProvider {
|
||||||
|
private Client(MinecraftMetadataProvider metadataProvider, ConfigContext configContext, MappingsNamespace officialNamespace) {
|
||||||
|
super(metadataProvider, configContext, officialNamespace);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SingleJarEnvType type() {
|
public SingleJarEnvType type() {
|
||||||
return SingleJarEnvType.CLIENT;
|
return SingleJarEnvType.CLIENT;
|
||||||
@@ -157,5 +178,15 @@ public class SingleJarMinecraftProvider extends MinecraftProvider {
|
|||||||
public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception {
|
public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception {
|
||||||
return provider.getMinecraftClientJar().toPath();
|
return provider.getMinecraftClientJar().toPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean provideServer() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean provideClient() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.configuration.ConfigContext;
|
import net.fabricmc.loom.configuration.ConfigContext;
|
||||||
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
||||||
|
|
||||||
@@ -35,8 +36,8 @@ public final class SplitMinecraftProvider extends MinecraftProvider {
|
|||||||
private Path minecraftClientOnlyJar;
|
private Path minecraftClientOnlyJar;
|
||||||
private Path minecraftCommonJar;
|
private Path minecraftCommonJar;
|
||||||
|
|
||||||
public SplitMinecraftProvider(ConfigContext configContext) {
|
public SplitMinecraftProvider(MinecraftMetadataProvider metadataProvider, ConfigContext configContext) {
|
||||||
super(configContext);
|
super(metadataProvider, configContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -52,6 +53,11 @@ public final class SplitMinecraftProvider extends MinecraftProvider {
|
|||||||
return List.of(minecraftClientOnlyJar, minecraftCommonJar);
|
return List.of(minecraftClientOnlyJar, minecraftCommonJar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MappingsNamespace getOfficialNamespace() {
|
||||||
|
return MappingsNamespace.OFFICIAL;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void provide() throws Exception {
|
public void provide() throws Exception {
|
||||||
super.provide();
|
super.provide();
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import net.fabricmc.loom.configuration.providers.mappings.TinyMappingsService;
|
|||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.SignatureFixerApplyVisitor;
|
import net.fabricmc.loom.configuration.providers.minecraft.SignatureFixerApplyVisitor;
|
||||||
import net.fabricmc.loom.extension.LoomFiles;
|
import net.fabricmc.loom.extension.LoomFiles;
|
||||||
import net.fabricmc.loom.util.SidedClassVisitor;
|
import net.fabricmc.loom.util.SidedClassVisitor;
|
||||||
@@ -205,7 +206,10 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
|
|||||||
|
|
||||||
final Set<String> classNames = extension.isForgeLike() ? InnerClassRemapper.readClassNames(remappedJars.inputJar()) : Set.of();
|
final Set<String> classNames = extension.isForgeLike() ? InnerClassRemapper.readClassNames(remappedJars.inputJar()) : Set.of();
|
||||||
final Map<String, String> remappedSignatures = SignatureFixerApplyVisitor.getRemappedSignatures(getTargetNamespace() == MappingsNamespace.INTERMEDIARY, mappingConfiguration, getProject(), configContext.serviceManager(), toM);
|
final Map<String, String> remappedSignatures = SignatureFixerApplyVisitor.getRemappedSignatures(getTargetNamespace() == MappingsNamespace.INTERMEDIARY, mappingConfiguration, getProject(), configContext.serviceManager(), toM);
|
||||||
TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(getProject(), configContext.serviceManager(), fromM, toM, true, (builder) -> {
|
final MinecraftVersionMeta.JavaVersion javaVersion = minecraftProvider.getVersionInfo().javaVersion();
|
||||||
|
final boolean fixRecords = javaVersion != null && javaVersion.majorVersion() >= 16;
|
||||||
|
|
||||||
|
TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(getProject(), configContext.serviceManager(), fromM, toM, fixRecords, (builder) -> {
|
||||||
builder.extraPostApplyVisitor(new SignatureFixerApplyVisitor(remappedSignatures));
|
builder.extraPostApplyVisitor(new SignatureFixerApplyVisitor(remappedSignatures));
|
||||||
configureRemapper(remappedJars, builder);
|
configureRemapper(remappedJars, builder);
|
||||||
}, classNames);
|
}, classNames);
|
||||||
|
|||||||
@@ -29,14 +29,16 @@ import java.util.List;
|
|||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
|
|
||||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.LegacyMergedMinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType;
|
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
|
||||||
import net.fabricmc.tinyremapper.TinyRemapper;
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
|
|
||||||
public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.SingleJarImpl, IntermediaryMinecraftProvider.SplitImpl {
|
public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.LegacyMergedImpl, IntermediaryMinecraftProvider.SingleJarImpl, IntermediaryMinecraftProvider.SplitImpl {
|
||||||
public IntermediaryMinecraftProvider(Project project, M minecraftProvider) {
|
public IntermediaryMinecraftProvider(Project project, M minecraftProvider) {
|
||||||
super(project, minecraftProvider);
|
super(project, minecraftProvider);
|
||||||
}
|
}
|
||||||
@@ -59,11 +61,49 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
|
|||||||
@Override
|
@Override
|
||||||
public List<RemappedJars> getRemappedJars() {
|
public List<RemappedJars> getRemappedJars() {
|
||||||
return List.of(
|
return List.of(
|
||||||
new RemappedJars(minecraftProvider.getMergedJar(), getMergedJar(), MappingsNamespace.OFFICIAL)
|
new RemappedJars(minecraftProvider.getMergedJar(), getMergedJar(), minecraftProvider.getOfficialNamespace())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class LegacyMergedImpl extends IntermediaryMinecraftProvider<LegacyMergedMinecraftProvider> implements Merged {
|
||||||
|
private final SingleJarImpl server;
|
||||||
|
private final SingleJarImpl client;
|
||||||
|
|
||||||
|
public LegacyMergedImpl(Project project, LegacyMergedMinecraftProvider minecraftProvider) {
|
||||||
|
super(project, minecraftProvider);
|
||||||
|
server = new SingleJarImpl(project, minecraftProvider.getServerMinecraftProvider(), SingleJarEnvType.SERVER);
|
||||||
|
client = new SingleJarImpl(project, minecraftProvider.getClientMinecraftProvider(), SingleJarEnvType.CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MinecraftJar> provide(ProvideContext context) throws Exception {
|
||||||
|
// Map the client and server jars separately
|
||||||
|
server.provide(context);
|
||||||
|
client.provide(context);
|
||||||
|
|
||||||
|
// then merge them
|
||||||
|
MergedMinecraftProvider.mergeJars(
|
||||||
|
client.getEnvOnlyJar().toFile(),
|
||||||
|
server.getEnvOnlyJar().toFile(),
|
||||||
|
getMergedJar().toFile()
|
||||||
|
);
|
||||||
|
|
||||||
|
return List.of(getMergedJar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RemappedJars> getRemappedJars() {
|
||||||
|
// The delegate providers will handle the remapping
|
||||||
|
throw new UnsupportedOperationException("LegacyMergedImpl does not support getRemappedJars");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MinecraftJar.Type> getDependencyTypes() {
|
||||||
|
return List.of(MinecraftJar.Type.MERGED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final class SplitImpl extends IntermediaryMinecraftProvider<SplitMinecraftProvider> implements Split {
|
public static final class SplitImpl extends IntermediaryMinecraftProvider<SplitMinecraftProvider> implements Split {
|
||||||
public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) {
|
public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) {
|
||||||
super(project, minecraftProvider);
|
super(project, minecraftProvider);
|
||||||
@@ -72,8 +112,8 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
|
|||||||
@Override
|
@Override
|
||||||
public List<RemappedJars> getRemappedJars() {
|
public List<RemappedJars> getRemappedJars() {
|
||||||
return List.of(
|
return List.of(
|
||||||
new RemappedJars(minecraftProvider.getMinecraftCommonJar(), getCommonJar(), MappingsNamespace.OFFICIAL),
|
new RemappedJars(minecraftProvider.getMinecraftCommonJar(), getCommonJar(), minecraftProvider.getOfficialNamespace()),
|
||||||
new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar(), getClientOnlyJar(), MappingsNamespace.OFFICIAL, minecraftProvider.getMinecraftCommonJar())
|
new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar(), getClientOnlyJar(), minecraftProvider.getOfficialNamespace(), minecraftProvider.getMinecraftCommonJar())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +142,7 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
|
|||||||
@Override
|
@Override
|
||||||
public List<RemappedJars> getRemappedJars() {
|
public List<RemappedJars> getRemappedJars() {
|
||||||
return List.of(
|
return List.of(
|
||||||
new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), MappingsNamespace.OFFICIAL)
|
new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), minecraftProvider.getOfficialNamespace())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,11 @@ import java.util.List;
|
|||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
|
|
||||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.LegacyMergedMinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType;
|
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
|
||||||
@@ -60,7 +62,7 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
|
|||||||
@Override
|
@Override
|
||||||
public List<RemappedJars> getRemappedJars() {
|
public List<RemappedJars> getRemappedJars() {
|
||||||
return List.of(
|
return List.of(
|
||||||
new RemappedJars(minecraftProvider.getMergedJar(), getMergedJar(), MappingsNamespace.OFFICIAL)
|
new RemappedJars(minecraftProvider.getMergedJar(), getMergedJar(), minecraftProvider.getOfficialNamespace())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +72,55 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class LegacyMergedImpl extends NamedMinecraftProvider<LegacyMergedMinecraftProvider> implements Merged {
|
||||||
|
private final SingleJarImpl server;
|
||||||
|
private final SingleJarImpl client;
|
||||||
|
|
||||||
|
public LegacyMergedImpl(Project project, LegacyMergedMinecraftProvider minecraftProvider) {
|
||||||
|
super(project, minecraftProvider);
|
||||||
|
server = new SingleJarImpl(project, minecraftProvider.getServerMinecraftProvider(), SingleJarEnvType.SERVER);
|
||||||
|
client = new SingleJarImpl(project, minecraftProvider.getClientMinecraftProvider(), SingleJarEnvType.CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MinecraftJar> provide(ProvideContext context) throws Exception {
|
||||||
|
final ProvideContext childContext = context.withApplyDependencies(false);
|
||||||
|
|
||||||
|
// Map the client and server jars separately
|
||||||
|
server.provide(childContext);
|
||||||
|
client.provide(childContext);
|
||||||
|
|
||||||
|
// then merge them
|
||||||
|
MergedMinecraftProvider.mergeJars(
|
||||||
|
client.getEnvOnlyJar().toFile(),
|
||||||
|
server.getEnvOnlyJar().toFile(),
|
||||||
|
getMergedJar().toFile()
|
||||||
|
);
|
||||||
|
|
||||||
|
getMavenHelper(MinecraftJar.Type.MERGED).savePom();
|
||||||
|
|
||||||
|
if (context.applyDependencies()) {
|
||||||
|
MinecraftSourceSets.get(getProject()).applyDependencies(
|
||||||
|
(configuration, type) -> getProject().getDependencies().add(configuration, getDependencyNotation(type)),
|
||||||
|
getDependencyTypes()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of(getMergedJar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RemappedJars> getRemappedJars() {
|
||||||
|
// The delegate providers will handle the remapping
|
||||||
|
throw new UnsupportedOperationException("LegacyMergedImpl does not support getRemappedJars");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MinecraftJar.Type> getDependencyTypes() {
|
||||||
|
return List.of(MinecraftJar.Type.MERGED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final class SplitImpl extends NamedMinecraftProvider<SplitMinecraftProvider> implements Split {
|
public static final class SplitImpl extends NamedMinecraftProvider<SplitMinecraftProvider> implements Split {
|
||||||
public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) {
|
public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) {
|
||||||
super(project, minecraftProvider);
|
super(project, minecraftProvider);
|
||||||
@@ -78,8 +129,8 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
|
|||||||
@Override
|
@Override
|
||||||
public List<RemappedJars> getRemappedJars() {
|
public List<RemappedJars> getRemappedJars() {
|
||||||
return List.of(
|
return List.of(
|
||||||
new RemappedJars(minecraftProvider.getMinecraftCommonJar(), getCommonJar(), MappingsNamespace.OFFICIAL),
|
new RemappedJars(minecraftProvider.getMinecraftCommonJar(), getCommonJar(), minecraftProvider.getOfficialNamespace()),
|
||||||
new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar(), getClientOnlyJar(), MappingsNamespace.OFFICIAL, minecraftProvider.getMinecraftCommonJar())
|
new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar(), getClientOnlyJar(), minecraftProvider.getOfficialNamespace(), minecraftProvider.getMinecraftCommonJar())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +164,7 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
|
|||||||
@Override
|
@Override
|
||||||
public List<RemappedJars> getRemappedJars() {
|
public List<RemappedJars> getRemappedJars() {
|
||||||
return List.of(
|
return List.of(
|
||||||
new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), MappingsNamespace.OFFICIAL)
|
new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), minecraftProvider.getOfficialNamespace())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import net.fabricmc.loom.configuration.ConfigContext;
|
|||||||
import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper;
|
import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper;
|
||||||
import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
|
import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
|
||||||
import net.fabricmc.loom.configuration.processors.ProcessorContextImpl;
|
import net.fabricmc.loom.configuration.processors.ProcessorContextImpl;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.LegacyMergedMinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||||
@@ -178,6 +179,17 @@ public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvide
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class LegacyMergedImpl extends ProcessedNamedMinecraftProvider<LegacyMergedMinecraftProvider, NamedMinecraftProvider.LegacyMergedImpl> implements Merged {
|
||||||
|
public LegacyMergedImpl(NamedMinecraftProvider.LegacyMergedImpl parentMinecraftProvider, MinecraftJarProcessorManager jarProcessorManager) {
|
||||||
|
super(parentMinecraftProvider, jarProcessorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MinecraftJar getMergedJar() {
|
||||||
|
return getProcessedJar(getParentMinecraftProvider().getMergedJar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final class SplitImpl extends ProcessedNamedMinecraftProvider<SplitMinecraftProvider, NamedMinecraftProvider.SplitImpl> implements Split {
|
public static final class SplitImpl extends ProcessedNamedMinecraftProvider<SplitMinecraftProvider, NamedMinecraftProvider.SplitImpl> implements Split {
|
||||||
public SplitImpl(NamedMinecraftProvider.SplitImpl parentMinecraftProvide, MinecraftJarProcessorManager jarProcessorManager) {
|
public SplitImpl(NamedMinecraftProvider.SplitImpl parentMinecraftProvide, MinecraftJarProcessorManager jarProcessorManager) {
|
||||||
super(parentMinecraftProvide, jarProcessorManager);
|
super(parentMinecraftProvide, jarProcessorManager);
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public record ClassLineNumbers(Map<String, ClassLineNumbers.Entry> lineMap) {
|
||||||
|
public ClassLineNumbers {
|
||||||
|
Objects.requireNonNull(lineMap, "lineMap");
|
||||||
|
|
||||||
|
if (lineMap.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("lineMap is empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClassLineNumbers readMappings(Path lineMappingsPath) {
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(lineMappingsPath)) {
|
||||||
|
return readMappings(reader);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException("Exception reading LineMappings file.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClassLineNumbers readMappings(BufferedReader reader) {
|
||||||
|
var lineMap = new HashMap<String, ClassLineNumbers.Entry>();
|
||||||
|
|
||||||
|
String line = null;
|
||||||
|
int lineNumber = 0;
|
||||||
|
|
||||||
|
record CurrentClass(String className, int maxLine, int maxLineDest) {
|
||||||
|
void putEntry(Map<String, ClassLineNumbers.Entry> entries, Map<Integer, Integer> mappings) {
|
||||||
|
var entry = new ClassLineNumbers.Entry(className(), maxLine(), maxLineDest(), Collections.unmodifiableMap(mappings));
|
||||||
|
|
||||||
|
final ClassLineNumbers.Entry previous = entries.put(className(), entry);
|
||||||
|
|
||||||
|
if (previous != null) {
|
||||||
|
throw new IllegalStateException("Duplicate class line mappings for " + className());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentClass currentClass = null;
|
||||||
|
Map<Integer, Integer> currentMappings = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String[] segments = line.trim().split("\t");
|
||||||
|
|
||||||
|
if (line.charAt(0) != '\t') {
|
||||||
|
if (currentClass != null) {
|
||||||
|
currentClass.putEntry(lineMap, currentMappings);
|
||||||
|
currentMappings = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentClass = new CurrentClass(segments[0], Integer.parseInt(segments[1]), Integer.parseInt(segments[2]));
|
||||||
|
} else {
|
||||||
|
Objects.requireNonNull(currentClass, "No class line mappings found for line " + lineNumber);
|
||||||
|
currentMappings.put(Integer.parseInt(segments[0]), Integer.parseInt(segments[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
lineNumber++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(format("Exception reading mapping line @{0}: {1}", lineNumber, line), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert currentClass != null;
|
||||||
|
currentClass.putEntry(lineMap, currentMappings);
|
||||||
|
|
||||||
|
return new ClassLineNumbers(Collections.unmodifiableMap(lineMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(Writer writer) throws IOException {
|
||||||
|
for (Map.Entry<String, ClassLineNumbers.Entry> entry : lineMap.entrySet()) {
|
||||||
|
entry.getValue().write(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge two ClassLineNumbers together, throwing an exception if there are any duplicate class line mappings.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static ClassLineNumbers merge(@Nullable ClassLineNumbers a, @Nullable ClassLineNumbers b) {
|
||||||
|
if (a == null) {
|
||||||
|
return b;
|
||||||
|
} else if (b == null) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lineMap = new HashMap<>(a.lineMap());
|
||||||
|
|
||||||
|
for (Map.Entry<String, Entry> entry : b.lineMap().entrySet()) {
|
||||||
|
lineMap.merge(entry.getKey(), entry.getValue(), (v1, v2) -> {
|
||||||
|
throw new IllegalStateException("Duplicate class line mappings for " + entry.getKey());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClassLineNumbers(Collections.unmodifiableMap(lineMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Entry(String className, int maxLine, int maxLineDest, Map<Integer, Integer> lineMap) {
|
||||||
|
public void write(Writer writer) throws IOException {
|
||||||
|
writer.write(className);
|
||||||
|
writer.write('\t');
|
||||||
|
writer.write(Integer.toString(maxLine));
|
||||||
|
writer.write('\t');
|
||||||
|
writer.write(Integer.toString(maxLineDest));
|
||||||
|
writer.write('\n');
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, Integer> lineEntry : lineMap.entrySet()) {
|
||||||
|
writer.write('\t');
|
||||||
|
writer.write(Integer.toString(lineEntry.getKey()));
|
||||||
|
writer.write('\t');
|
||||||
|
writer.write(Integer.toString(lineEntry.getValue()));
|
||||||
|
writer.write('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,93 +24,48 @@
|
|||||||
|
|
||||||
package net.fabricmc.loom.decompilers;
|
package net.fabricmc.loom.decompilers;
|
||||||
|
|
||||||
import static java.text.MessageFormat.format;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.FileVisitResult;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.SimpleFileVisitor;
|
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.util.HashSet;
|
||||||
import java.util.HashMap;
|
import java.util.Set;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.objectweb.asm.ClassReader;
|
import org.objectweb.asm.ClassReader;
|
||||||
import org.objectweb.asm.ClassVisitor;
|
import org.objectweb.asm.ClassVisitor;
|
||||||
import org.objectweb.asm.ClassWriter;
|
import org.objectweb.asm.ClassWriter;
|
||||||
import org.objectweb.asm.Label;
|
import org.objectweb.asm.Label;
|
||||||
import org.objectweb.asm.MethodVisitor;
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.AsyncZipProcessor;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.IOStringConsumer;
|
|
||||||
|
|
||||||
/**
|
public record LineNumberRemapper(ClassLineNumbers lineNumbers) {
|
||||||
* Created by covers1624 on 18/02/19.
|
private static final Logger LOGGER = LoggerFactory.getLogger(LineNumberRemapper.class);
|
||||||
*/
|
|
||||||
public class LineNumberRemapper {
|
|
||||||
private final Map<String, RClass> lineMap = new HashMap<>();
|
|
||||||
|
|
||||||
public void readMappings(File lineMappings) {
|
public void process(Path input, Path output) throws IOException {
|
||||||
try (BufferedReader reader = new BufferedReader(new FileReader(lineMappings))) {
|
AsyncZipProcessor.processEntries(input, output, new AsyncZipProcessor() {
|
||||||
RClass clazz = null;
|
private final Set<Path> createdParents = new HashSet<>();
|
||||||
String line = null;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
if (line.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] segs = line.trim().split("\t");
|
|
||||||
|
|
||||||
if (line.charAt(0) != '\t') {
|
|
||||||
clazz = lineMap.computeIfAbsent(segs[0], RClass::new);
|
|
||||||
clazz.maxLine = Integer.parseInt(segs[1]);
|
|
||||||
clazz.maxLineDest = Integer.parseInt(segs[2]);
|
|
||||||
} else {
|
|
||||||
clazz.lineMap.put(Integer.parseInt(segs[0]), Integer.parseInt(segs[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(format("Exception reading mapping line @{0}: {1}", i, line), e);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Exception reading LineMappings file.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void process(IOStringConsumer logger, Path input, Path output) throws IOException {
|
|
||||||
Files.walkFileTree(input, new SimpleFileVisitor<>() {
|
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
public void processEntryAsync(Path file, Path dst) throws IOException {
|
||||||
String rel = input.relativize(file).toString();
|
|
||||||
Path dst = output.resolve(rel);
|
|
||||||
Path parent = dst.getParent();
|
Path parent = dst.getParent();
|
||||||
|
|
||||||
if (parent != null) {
|
synchronized (createdParents) {
|
||||||
Files.createDirectories(parent);
|
if (parent != null && createdParents.add(parent)) {
|
||||||
|
Files.createDirectories(parent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String fName = file.getFileName().toString();
|
String fileName = file.getFileName().toString();
|
||||||
|
|
||||||
if (fName.endsWith(".class")) {
|
if (fileName.endsWith(".class")) {
|
||||||
if (Files.exists(dst)) {
|
String idx = fileName.substring(0, fileName.length() - 6);
|
||||||
Files.delete(dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
String idx = rel.substring(0, rel.length() - 6);
|
LOGGER.debug("Remapping line numbers for class: " + idx);
|
||||||
|
|
||||||
if (logger != null) {
|
|
||||||
logger.accept("Remapping " + idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
int dollarPos = idx.indexOf('$'); //This makes the assumption that only Java classes are to be remapped.
|
int dollarPos = idx.indexOf('$'); //This makes the assumption that only Java classes are to be remapped.
|
||||||
|
|
||||||
@@ -118,30 +73,29 @@ public class LineNumberRemapper {
|
|||||||
idx = idx.substring(0, dollarPos);
|
idx = idx.substring(0, dollarPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lineMap.containsKey(idx)) {
|
if (lineNumbers.lineMap().containsKey(idx)) {
|
||||||
try (InputStream is = Files.newInputStream(file)) {
|
try (InputStream is = Files.newInputStream(file)) {
|
||||||
ClassReader reader = new ClassReader(is);
|
ClassReader reader = new ClassReader(is);
|
||||||
ClassWriter writer = new ClassWriter(0);
|
ClassWriter writer = new ClassWriter(0);
|
||||||
|
|
||||||
reader.accept(new LineNumberVisitor(Constants.ASM_VERSION, writer, lineMap.get(idx)), 0);
|
reader.accept(new LineNumberVisitor(Constants.ASM_VERSION, writer, lineNumbers.lineMap().get(idx)), 0);
|
||||||
Files.write(dst, writer.toByteArray());
|
Files.write(dst, writer.toByteArray());
|
||||||
return FileVisitResult.CONTINUE;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Files.copy(file, dst, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(file, dst, StandardCopyOption.REPLACE_EXISTING);
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LineNumberVisitor extends ClassVisitor {
|
private static class LineNumberVisitor extends ClassVisitor {
|
||||||
private final RClass rClass;
|
private final ClassLineNumbers.Entry lineNumbers;
|
||||||
|
|
||||||
LineNumberVisitor(int api, ClassVisitor classVisitor, RClass rClass) {
|
LineNumberVisitor(int api, ClassVisitor classVisitor, ClassLineNumbers.Entry lineNumbers) {
|
||||||
super(api, classVisitor);
|
super(api, classVisitor);
|
||||||
this.rClass = rClass;
|
this.lineNumbers = lineNumbers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -153,30 +107,19 @@ public class LineNumberRemapper {
|
|||||||
|
|
||||||
if (tLine <= 0) {
|
if (tLine <= 0) {
|
||||||
super.visitLineNumber(line, start);
|
super.visitLineNumber(line, start);
|
||||||
} else if (tLine >= rClass.maxLine) {
|
} else if (tLine >= lineNumbers.maxLine()) {
|
||||||
super.visitLineNumber(rClass.maxLineDest, start);
|
super.visitLineNumber(lineNumbers.maxLineDest(), start);
|
||||||
} else {
|
} else {
|
||||||
Integer matchedLine = null;
|
Integer matchedLine = null;
|
||||||
|
|
||||||
while (tLine <= rClass.maxLine && ((matchedLine = rClass.lineMap.get(tLine)) == null)) {
|
while (tLine <= lineNumbers.maxLine() && ((matchedLine = lineNumbers.lineMap().get(tLine)) == null)) {
|
||||||
tLine++;
|
tLine++;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.visitLineNumber(matchedLine != null ? matchedLine : rClass.maxLineDest, start);
|
super.visitLineNumber(matchedLine != null ? matchedLine : lineNumbers.maxLineDest(), start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RClass {
|
|
||||||
private final String name;
|
|
||||||
private int maxLine;
|
|
||||||
private int maxLineDest;
|
|
||||||
private final Map<Integer, Integer> lineMap = new HashMap<>();
|
|
||||||
|
|
||||||
private RClass(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
209
src/main/java/net/fabricmc/loom/decompilers/cache/CachedData.java
vendored
Normal file
209
src/main/java/net/fabricmc/loom/decompilers/cache/CachedData.java
vendored
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 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.cache;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.decompilers.ClassLineNumbers;
|
||||||
|
|
||||||
|
// Serialised data for a class entry in the cache
|
||||||
|
// Uses the RIFF format, allows for appending the line numbers to the end of the file
|
||||||
|
// Stores the source code and line numbers for the class
|
||||||
|
public record CachedData(String className, String sources, @Nullable ClassLineNumbers.Entry lineNumbers) {
|
||||||
|
public static final CachedFileStore.EntrySerializer<CachedData> SERIALIZER = new EntrySerializer();
|
||||||
|
|
||||||
|
private static final String HEADER_ID = "LOOM";
|
||||||
|
private static final String NAME_ID = "NAME";
|
||||||
|
private static final String SOURCES_ID = "SRC ";
|
||||||
|
private static final String LINE_NUMBERS_ID = "LNUM";
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(CachedData.class);
|
||||||
|
|
||||||
|
public CachedData {
|
||||||
|
Objects.requireNonNull(className, "className");
|
||||||
|
Objects.requireNonNull(sources, "sources");
|
||||||
|
|
||||||
|
if (lineNumbers != null) {
|
||||||
|
if (!className.equals(lineNumbers.className())) {
|
||||||
|
throw new IllegalArgumentException("Class name does not match line numbers class name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(FileChannel fileChannel) {
|
||||||
|
try (var c = new RiffChunk(HEADER_ID, fileChannel)) {
|
||||||
|
writeClassname(fileChannel);
|
||||||
|
writeSource(fileChannel);
|
||||||
|
|
||||||
|
if (lineNumbers != null) {
|
||||||
|
writeLineNumbers(fileChannel);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to write cached data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeClassname(FileChannel fileChannel) throws IOException {
|
||||||
|
try (var c = new RiffChunk(NAME_ID, fileChannel)) {
|
||||||
|
fileChannel.write(ByteBuffer.wrap(className.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeSource(FileChannel fileChannel) throws IOException {
|
||||||
|
try (var c = new RiffChunk(SOURCES_ID, fileChannel)) {
|
||||||
|
fileChannel.write(ByteBuffer.wrap(sources.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLineNumbers(FileChannel fileChannel) throws IOException {
|
||||||
|
Objects.requireNonNull(lineNumbers);
|
||||||
|
|
||||||
|
try (var c = new RiffChunk(LINE_NUMBERS_ID, fileChannel);
|
||||||
|
StringWriter stringWriter = new StringWriter()) {
|
||||||
|
lineNumbers.write(stringWriter);
|
||||||
|
fileChannel.write(ByteBuffer.wrap(stringWriter.toString().getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CachedData read(InputStream inputStream) throws IOException {
|
||||||
|
// Read and validate the RIFF header
|
||||||
|
final String header = readHeader(inputStream);
|
||||||
|
|
||||||
|
if (!header.equals(HEADER_ID)) {
|
||||||
|
throw new IOException("Invalid RIFF header: " + header + ", expected " + HEADER_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the data length
|
||||||
|
int length = readInt(inputStream);
|
||||||
|
|
||||||
|
String className = null;
|
||||||
|
String sources = null;
|
||||||
|
ClassLineNumbers.Entry lineNumbers = null;
|
||||||
|
|
||||||
|
while (inputStream.available() > 0) {
|
||||||
|
String chunkHeader = readHeader(inputStream);
|
||||||
|
int chunkLength = readInt(inputStream);
|
||||||
|
byte[] chunkData = readBytes(inputStream, chunkLength);
|
||||||
|
|
||||||
|
switch (chunkHeader) {
|
||||||
|
case NAME_ID -> {
|
||||||
|
if (className != null) {
|
||||||
|
throw new IOException("Duplicate name chunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
className = new String(chunkData, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
case SOURCES_ID -> {
|
||||||
|
if (sources != null) {
|
||||||
|
throw new IOException("Duplicate sources chunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
sources = new String(chunkData, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
case LINE_NUMBERS_ID -> {
|
||||||
|
if (lineNumbers != null) {
|
||||||
|
throw new IOException("Duplicate line numbers chunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (var br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(chunkData), StandardCharsets.UTF_8))) {
|
||||||
|
ClassLineNumbers classLineNumbers = ClassLineNumbers.readMappings(br);
|
||||||
|
|
||||||
|
if (classLineNumbers.lineMap().size() != 1) {
|
||||||
|
throw new IOException("Expected exactly one class line numbers entry got " + classLineNumbers.lineMap().size() + " entries");
|
||||||
|
}
|
||||||
|
|
||||||
|
lineNumbers = classLineNumbers.lineMap().values().iterator().next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
// Skip unknown chunk
|
||||||
|
LOGGER.warn("Skipping unknown chunk: {} of size {}", chunkHeader, chunkLength);
|
||||||
|
inputStream.skip(chunkLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sources == null) {
|
||||||
|
throw new IOException("Missing sources");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CachedData(className, sources, lineNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readHeader(InputStream inputStream) throws IOException {
|
||||||
|
byte[] header = readBytes(inputStream, 4);
|
||||||
|
return new String(header, StandardCharsets.US_ASCII);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readInt(InputStream inputStream) throws IOException {
|
||||||
|
byte[] bytes = readBytes(inputStream, 4);
|
||||||
|
return ByteBuffer.wrap(bytes).getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readBytes(InputStream inputStream, int length) throws IOException {
|
||||||
|
byte[] bytes = new byte[length];
|
||||||
|
|
||||||
|
int read = inputStream.read(bytes);
|
||||||
|
|
||||||
|
if (read != length) {
|
||||||
|
throw new IOException("Failed to read bytes expected " + length + " bytes but got " + read + " bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class EntrySerializer implements CachedFileStore.EntrySerializer<CachedData> {
|
||||||
|
@Override
|
||||||
|
public CachedData read(Path path) throws IOException {
|
||||||
|
try (var inputStream = new BufferedInputStream(Files.newInputStream(path))) {
|
||||||
|
return CachedData.read(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(CachedData entry, Path path) throws IOException {
|
||||||
|
try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
|
||||||
|
entry.write(fileChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main/java/net/fabricmc/loom/decompilers/cache/CachedFileStore.java
vendored
Normal file
42
src/main/java/net/fabricmc/loom/decompilers/cache/CachedFileStore.java
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 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.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public interface CachedFileStore<T> {
|
||||||
|
@Nullable T getEntry(String key) throws IOException;
|
||||||
|
|
||||||
|
void putEntry(String key, T entry) throws IOException;
|
||||||
|
|
||||||
|
interface EntrySerializer<T> {
|
||||||
|
T read(Path path) throws IOException;
|
||||||
|
|
||||||
|
void write(T entry, Path path) throws IOException;
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/main/java/net/fabricmc/loom/decompilers/cache/CachedFileStoreImpl.java
vendored
Normal file
141
src/main/java/net/fabricmc/loom/decompilers/cache/CachedFileStoreImpl.java
vendored
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 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.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.FileTime;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public record CachedFileStoreImpl<T>(Path root, EntrySerializer<T> entrySerializer, CacheRules cacheRules) implements CachedFileStore<T> {
|
||||||
|
public CachedFileStoreImpl {
|
||||||
|
Objects.requireNonNull(root, "root");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable T getEntry(String key) throws IOException {
|
||||||
|
Path path = resolve(key);
|
||||||
|
|
||||||
|
if (Files.notExists(path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last modified, so recently used files stay in the cache
|
||||||
|
Files.setLastModifiedTime(path, FileTime.from(Instant.now()));
|
||||||
|
return entrySerializer.read(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putEntry(String key, T data) throws IOException {
|
||||||
|
Path path = resolve(key);
|
||||||
|
Files.createDirectories(path.getParent());
|
||||||
|
entrySerializer.write(data, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path resolve(String key) {
|
||||||
|
return root.resolve(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void prune() throws IOException {
|
||||||
|
// Sorted oldest -> newest
|
||||||
|
List<PathEntry> entries = new ArrayList<>();
|
||||||
|
|
||||||
|
// Iterate over all the files in the cache, and store them into the sorted list.
|
||||||
|
try (Stream<Path> walk = Files.walk(root)) {
|
||||||
|
Iterator<Path> iterator = walk.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
final Path entry = iterator.next();
|
||||||
|
|
||||||
|
if (!Files.isRegularFile(entry)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertSorted(entries, new PathEntry(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the oldest files to get under the max file limit
|
||||||
|
if (entries.size() > cacheRules.maxFiles) {
|
||||||
|
for (int i = 0; i < cacheRules.maxFiles; i++) {
|
||||||
|
PathEntry toRemove = entries.remove(0);
|
||||||
|
Files.delete(toRemove.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Instant maxAge = Instant.now().minus(cacheRules().maxAge());
|
||||||
|
Iterator<PathEntry> iterator = entries.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
final PathEntry entry = iterator.next();
|
||||||
|
|
||||||
|
if (entry.lastModified().toInstant().isAfter(maxAge)) {
|
||||||
|
// File is not longer than the max age
|
||||||
|
// As this is a sorted list we don't need to keep checking
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all files over the max age
|
||||||
|
iterator.remove();
|
||||||
|
Files.delete(entry.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertSorted(List<PathEntry> list, PathEntry entry) {
|
||||||
|
int index = Collections.binarySearch(list, entry, Comparator.comparing(PathEntry::lastModified));
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
index = -index - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(index, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rules for the cache.
|
||||||
|
*
|
||||||
|
* @param maxFiles The maximum number of files in the cache
|
||||||
|
* @param maxAge The maximum age of a file in the cache
|
||||||
|
*/
|
||||||
|
public record CacheRules(long maxFiles, Duration maxAge) {
|
||||||
|
}
|
||||||
|
|
||||||
|
record PathEntry(Path path, FileTime lastModified) {
|
||||||
|
PathEntry(Path path) throws IOException {
|
||||||
|
this(path, Files.getLastModifiedTime(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
268
src/main/java/net/fabricmc/loom/decompilers/cache/CachedJarProcessor.java
vendored
Normal file
268
src/main/java/net/fabricmc/loom/decompilers/cache/CachedJarProcessor.java
vendored
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 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.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.decompilers.ClassLineNumbers;
|
||||||
|
import net.fabricmc.loom.util.FileSystemUtil;
|
||||||
|
|
||||||
|
public record CachedJarProcessor(CachedFileStore<CachedData> fileStore, String baseHash) {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(CachedJarProcessor.class);
|
||||||
|
|
||||||
|
public WorkRequest prepareJob(Path inputJar) throws IOException {
|
||||||
|
boolean isIncomplete = false;
|
||||||
|
boolean hasSomeExisting = false;
|
||||||
|
|
||||||
|
Path incompleteJar = Files.createTempFile("loom-cache-incomplete", ".jar");
|
||||||
|
Path existingJar = Files.createTempFile("loom-cache-existing", ".jar");
|
||||||
|
|
||||||
|
// We must delete the empty files, so they can be created as a zip
|
||||||
|
Files.delete(incompleteJar);
|
||||||
|
Files.delete(existingJar);
|
||||||
|
|
||||||
|
// Sources name -> hash
|
||||||
|
Map<String, String> outputNameMap = new HashMap<>();
|
||||||
|
Map<String, ClassLineNumbers.Entry> lineNumbersMap = new HashMap<>();
|
||||||
|
|
||||||
|
int hits = 0;
|
||||||
|
int misses = 0;
|
||||||
|
|
||||||
|
try (FileSystemUtil.Delegate inputFs = FileSystemUtil.getJarFileSystem(inputJar, false);
|
||||||
|
FileSystemUtil.Delegate incompleteFs = FileSystemUtil.getJarFileSystem(incompleteJar, true);
|
||||||
|
FileSystemUtil.Delegate existingFs = FileSystemUtil.getJarFileSystem(existingJar, true)) {
|
||||||
|
final List<ClassEntry> inputClasses = JarWalker.findClasses(inputFs);
|
||||||
|
|
||||||
|
for (ClassEntry entry : inputClasses) {
|
||||||
|
String outputFileName = entry.sourcesFileName();
|
||||||
|
String fullHash = baseHash + "/" + entry.hash(inputFs.getRoot());
|
||||||
|
|
||||||
|
final CachedData entryData = fileStore.getEntry(fullHash);
|
||||||
|
|
||||||
|
if (entryData == null) {
|
||||||
|
// Cached entry was not found, so copy the input to the incomplete jar to be processed
|
||||||
|
entry.copyTo(inputFs.getRoot(), incompleteFs.getRoot());
|
||||||
|
isIncomplete = true;
|
||||||
|
outputNameMap.put(outputFileName, fullHash);
|
||||||
|
|
||||||
|
LOGGER.debug("Cached entry ({}) not found, going to process {}", fullHash, outputFileName);
|
||||||
|
misses++;
|
||||||
|
} else {
|
||||||
|
final Path outputPath = existingFs.getPath(outputFileName);
|
||||||
|
Files.createDirectories(outputPath.getParent());
|
||||||
|
Files.writeString(outputPath, entryData.sources());
|
||||||
|
lineNumbersMap.put(entryData.className(), entryData.lineNumbers());
|
||||||
|
hasSomeExisting = true;
|
||||||
|
|
||||||
|
LOGGER.debug("Cached entry ({}) found: {}", fullHash, outputFileName);
|
||||||
|
hits++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A jar file that will be created by the work action, containing the newly processed items.
|
||||||
|
Path outputJar = Files.createTempFile("loom-cache-output", ".jar");
|
||||||
|
Files.delete(outputJar);
|
||||||
|
|
||||||
|
final ClassLineNumbers lineNumbers = lineNumbersMap.isEmpty() ? null : new ClassLineNumbers(Collections.unmodifiableMap(lineNumbersMap));
|
||||||
|
final var stats = new CacheStats(hits, misses);
|
||||||
|
|
||||||
|
if (isIncomplete && !hasSomeExisting) {
|
||||||
|
// The cache contained nothing of use, fully process the input jar
|
||||||
|
Files.delete(incompleteJar);
|
||||||
|
Files.delete(existingJar);
|
||||||
|
|
||||||
|
LOGGER.info("No cached entries found, going to process the whole jar");
|
||||||
|
return new FullWorkJob(inputJar, outputJar, outputNameMap)
|
||||||
|
.asRequest(stats, lineNumbers);
|
||||||
|
} else if (isIncomplete) {
|
||||||
|
// The cache did not contain everything so we have some work to do
|
||||||
|
LOGGER.info("Some cached entries found, using partial work job");
|
||||||
|
return new PartialWorkJob(incompleteJar, existingJar, outputJar, outputNameMap)
|
||||||
|
.asRequest(stats, lineNumbers);
|
||||||
|
} else {
|
||||||
|
// The cached contained everything we need, so the existing jar is the output
|
||||||
|
LOGGER.info("All cached entries found, using completed work job");
|
||||||
|
Files.delete(incompleteJar);
|
||||||
|
return new CompletedWorkJob(existingJar)
|
||||||
|
.asRequest(stats, lineNumbers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void completeJob(Path output, WorkJob workJob, ClassLineNumbers lineNumbers) throws IOException {
|
||||||
|
if (workJob instanceof CompletedWorkJob completedWorkJob) {
|
||||||
|
// Fully complete, nothing new to cache
|
||||||
|
Files.move(completedWorkJob.completed(), output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work has been done, we need to cache the newly processed items
|
||||||
|
if (workJob instanceof WorkToDoJob workToDoJob) {
|
||||||
|
// Sources name -> hash
|
||||||
|
Map<String, String> outputNameMap = workToDoJob.outputNameMap();
|
||||||
|
|
||||||
|
try (FileSystemUtil.Delegate outputFs = FileSystemUtil.getJarFileSystem(workToDoJob.output(), false);
|
||||||
|
Stream<Path> walk = Files.walk(outputFs.getRoot())) {
|
||||||
|
Iterator<Path> iterator = walk.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
final Path fsPath = iterator.next();
|
||||||
|
|
||||||
|
if (fsPath.startsWith("/META-INF/")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.isRegularFile(fsPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String hash = outputNameMap.get(fsPath.toString().substring(outputFs.getRoot().toString().length()));
|
||||||
|
|
||||||
|
if (hash == null) {
|
||||||
|
throw new IllegalStateException("Unexpected output: " + fsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim the leading / and the .java extension
|
||||||
|
final String className = fsPath.toString().substring(1, fsPath.toString().length() - ".java".length());
|
||||||
|
final String sources = Files.readString(fsPath);
|
||||||
|
|
||||||
|
ClassLineNumbers.Entry lineMapEntry = null;
|
||||||
|
|
||||||
|
if (lineNumbers != null) {
|
||||||
|
lineMapEntry = lineNumbers.lineMap().get(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
final var cachedData = new CachedData(className, sources, lineMapEntry);
|
||||||
|
fileStore.putEntry(hash, cachedData);
|
||||||
|
|
||||||
|
LOGGER.debug("Saving processed entry ({}) to cache: {}", hash, fsPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workJob instanceof PartialWorkJob partialWorkJob) {
|
||||||
|
// Copy all the existing items to the output jar
|
||||||
|
try (FileSystemUtil.Delegate outputFs = FileSystemUtil.getJarFileSystem(partialWorkJob.output(), false);
|
||||||
|
FileSystemUtil.Delegate existingFs = FileSystemUtil.getJarFileSystem(partialWorkJob.existing(), false);
|
||||||
|
Stream<Path> walk = Files.walk(existingFs.getRoot())) {
|
||||||
|
Iterator<Path> iterator = walk.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Path existingPath = iterator.next();
|
||||||
|
|
||||||
|
if (!Files.isRegularFile(existingPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Path outputPath = outputFs.getRoot().resolve(existingPath.toString());
|
||||||
|
|
||||||
|
LOGGER.debug("Copying existing entry to output: {}", existingPath);
|
||||||
|
Files.createDirectories(outputPath.getParent());
|
||||||
|
Files.copy(existingPath, outputPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.delete(partialWorkJob.existing());
|
||||||
|
Files.move(partialWorkJob.output(), output);
|
||||||
|
} else if (workJob instanceof FullWorkJob fullWorkJob) {
|
||||||
|
// Nothing to merge, just use the output jar
|
||||||
|
Files.move(fullWorkJob.output, output);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record WorkRequest(WorkJob job, CacheStats stats, @Nullable ClassLineNumbers lineNumbers) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CacheStats(int hits, int misses) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed interface WorkJob permits CompletedWorkJob, WorkToDoJob {
|
||||||
|
default WorkRequest asRequest(CacheStats stats, @Nullable ClassLineNumbers lineNumbers) {
|
||||||
|
return new WorkRequest(this, stats, lineNumbers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed interface WorkToDoJob extends WorkJob permits PartialWorkJob, FullWorkJob {
|
||||||
|
/**
|
||||||
|
* A path to jar file containing all the classes to be processed.
|
||||||
|
*/
|
||||||
|
Path incomplete();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A jar file to be written to during processing
|
||||||
|
*/
|
||||||
|
Path output();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A map of sources name to hash
|
||||||
|
*/
|
||||||
|
Map<String, String> outputNameMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No work to be done, all restored from cache.
|
||||||
|
*
|
||||||
|
* @param completed
|
||||||
|
*/
|
||||||
|
public record CompletedWorkJob(Path completed) implements WorkJob {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some work needs to be done.
|
||||||
|
*
|
||||||
|
* @param incomplete A path to jar file containing all the classes to be processed
|
||||||
|
* @param existing A path pointing to a jar containing existing classes that have previously been processed
|
||||||
|
* @param output A path to a temporary jar where work output should be written to
|
||||||
|
* @param outputNameMap A map of sources name to hash
|
||||||
|
*/
|
||||||
|
public record PartialWorkJob(Path incomplete, Path existing, Path output, Map<String, String> outputNameMap) implements WorkToDoJob {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The full jar must be processed.
|
||||||
|
*
|
||||||
|
* @param incomplete A path to jar file containing all the classes to be processed
|
||||||
|
* @param output A path to a temporary jar where work output should be written to
|
||||||
|
* @param outputNameMap A map of sources name to hash
|
||||||
|
*/
|
||||||
|
public record FullWorkJob(Path incomplete, Path output, Map<String, String> outputNameMap) implements WorkToDoJob {
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java
vendored
Normal file
75
src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 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.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.Checksum;
|
||||||
|
|
||||||
|
public record ClassEntry(String parentClass, List<String> innerClasses) {
|
||||||
|
/**
|
||||||
|
* Copy the class and its inner classes to the target root.
|
||||||
|
* @param sourceRoot The root of the source jar
|
||||||
|
* @param targetRoot The root of the target jar
|
||||||
|
*
|
||||||
|
* @throws IOException If an error occurs while copying the files
|
||||||
|
*/
|
||||||
|
public void copyTo(Path sourceRoot, Path targetRoot) throws IOException {
|
||||||
|
Path targetPath = targetRoot.resolve(parentClass);
|
||||||
|
Files.createDirectories(targetPath.getParent());
|
||||||
|
Files.copy(sourceRoot.resolve(parentClass), targetPath);
|
||||||
|
|
||||||
|
for (String innerClass : innerClasses) {
|
||||||
|
Files.copy(sourceRoot.resolve(innerClass), targetRoot.resolve(innerClass));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash the class and its inner classes using sha256.
|
||||||
|
* @param root The root of the jar
|
||||||
|
* @return The hash of the class and its inner classes
|
||||||
|
*
|
||||||
|
* @throws IOException If an error occurs while hashing the files
|
||||||
|
*/
|
||||||
|
public String hash(Path root) throws IOException {
|
||||||
|
StringJoiner joiner = new StringJoiner(",");
|
||||||
|
|
||||||
|
joiner.add(Checksum.sha256Hex(Files.readAllBytes(root.resolve(parentClass))));
|
||||||
|
|
||||||
|
for (String innerClass : innerClasses) {
|
||||||
|
joiner.add(Checksum.sha256Hex(Files.readAllBytes(root.resolve(innerClass))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Checksum.sha256Hex(joiner.toString().getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String sourcesFileName() {
|
||||||
|
return parentClass.replace(".class", ".java");
|
||||||
|
}
|
||||||
|
}
|
||||||
108
src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java
vendored
Normal file
108
src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 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.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.FileSystemUtil;
|
||||||
|
|
||||||
|
public final class JarWalker {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JarWalker.class);
|
||||||
|
|
||||||
|
private JarWalker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ClassEntry> findClasses(Path jar) throws IOException {
|
||||||
|
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar)) {
|
||||||
|
return findClasses(fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ClassEntry> findClasses(FileSystemUtil.Delegate fs) throws IOException {
|
||||||
|
List<String> outerClasses = new ArrayList<>();
|
||||||
|
Map<String, List<String>> innerClasses = new HashMap<>();
|
||||||
|
|
||||||
|
// Iterate over all the classes in the jar, and store them into the sorted list.
|
||||||
|
try (Stream<Path> walk = Files.walk(fs.getRoot())) {
|
||||||
|
Iterator<Path> iterator = walk.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
final Path entry = iterator.next();
|
||||||
|
|
||||||
|
if (!Files.isRegularFile(entry)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String fileName = entry.toString().substring(fs.getRoot().toString().length());
|
||||||
|
|
||||||
|
if (!fileName.endsWith(".class")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isInnerClass = fileName.contains("$");
|
||||||
|
|
||||||
|
if (isInnerClass) {
|
||||||
|
String outerClassName = fileName.substring(0, fileName.indexOf('$')) + ".class";
|
||||||
|
innerClasses.computeIfAbsent(outerClassName, k -> new ArrayList<>()).add(fileName);
|
||||||
|
} else {
|
||||||
|
outerClasses.add(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Found {} outer classes and {} inner classes", outerClasses.size(), innerClasses.size());
|
||||||
|
|
||||||
|
Collections.sort(outerClasses);
|
||||||
|
|
||||||
|
List<ClassEntry> classEntries = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String outerClass : outerClasses) {
|
||||||
|
List<String> innerClasList = innerClasses.get(outerClass);
|
||||||
|
|
||||||
|
if (innerClasList == null) {
|
||||||
|
innerClasList = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
Collections.sort(innerClasList);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassEntry classEntry = new ClassEntry(outerClass, Collections.unmodifiableList(innerClasList));
|
||||||
|
classEntries.add(classEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(classEntries);
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/main/java/net/fabricmc/loom/decompilers/cache/RiffChunk.java
vendored
Normal file
69
src/main/java/net/fabricmc/loom/decompilers/cache/RiffChunk.java
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 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.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a RIFF chunk to a file channel
|
||||||
|
*
|
||||||
|
* <p>Works by writing the chunk header and then reserving space for the chunk size.
|
||||||
|
* The chunk size is then written after the chunk data has been written.
|
||||||
|
*/
|
||||||
|
public class RiffChunk implements AutoCloseable {
|
||||||
|
private final long position;
|
||||||
|
private final FileChannel fileChannel;
|
||||||
|
|
||||||
|
public RiffChunk(String id, FileChannel fileChannel) throws IOException {
|
||||||
|
if (id.length() != 4) {
|
||||||
|
throw new IllegalArgumentException("ID must be 4 characters long");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the chunk header and reserve space for the chunk size
|
||||||
|
fileChannel.write(ByteBuffer.wrap(id.getBytes(StandardCharsets.US_ASCII)));
|
||||||
|
this.position = fileChannel.position();
|
||||||
|
fileChannel.write(ByteBuffer.allocate(4));
|
||||||
|
|
||||||
|
// Store the position and file channel for later use
|
||||||
|
this.fileChannel = fileChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
long endPosition = fileChannel.position();
|
||||||
|
long chunkSize = endPosition - position - 4;
|
||||||
|
|
||||||
|
if (chunkSize > Integer.MAX_VALUE) {
|
||||||
|
throw new IOException("Chunk size is too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
fileChannel.position(position);
|
||||||
|
fileChannel.write(ByteBuffer.allocate(Integer.BYTES).putInt((int) (chunkSize)).flip());
|
||||||
|
fileChannel.position(endPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,5 +50,6 @@ public interface LoomFiles {
|
|||||||
File getRemapClasspathFile();
|
File getRemapClasspathFile();
|
||||||
File getGlobalMinecraftRepo();
|
File getGlobalMinecraftRepo();
|
||||||
File getLocalMinecraftRepo();
|
File getLocalMinecraftRepo();
|
||||||
|
File getDecompileCache(String version);
|
||||||
File getForgeDependencyRepo();
|
File getForgeDependencyRepo();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,11 @@ public abstract class LoomFilesBaseImpl implements LoomFiles {
|
|||||||
return new File(getRootProjectPersistentCache(), "minecraftMaven");
|
return new File(getRootProjectPersistentCache(), "minecraftMaven");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getDecompileCache(String version) {
|
||||||
|
return new File(getUserCache(), "decompile/" + version + ".zip");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getForgeDependencyRepo() {
|
public File getForgeDependencyRepo() {
|
||||||
return new File(getUserCache(), "forge/transformed-dependencies-v1");
|
return new File(getUserCache(), "forge/transformed-dependencies-v1");
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import org.gradle.api.Project;
|
|||||||
import org.gradle.api.UncheckedIOException;
|
import org.gradle.api.UncheckedIOException;
|
||||||
import org.gradle.api.artifacts.Dependency;
|
import org.gradle.api.artifacts.Dependency;
|
||||||
import org.gradle.api.file.ConfigurableFileCollection;
|
import org.gradle.api.file.ConfigurableFileCollection;
|
||||||
|
import org.gradle.api.file.FileCollection;
|
||||||
import org.gradle.api.file.RegularFileProperty;
|
import org.gradle.api.file.RegularFileProperty;
|
||||||
import org.gradle.api.model.ObjectFactory;
|
import org.gradle.api.model.ObjectFactory;
|
||||||
import org.gradle.api.provider.ListProperty;
|
import org.gradle.api.provider.ListProperty;
|
||||||
@@ -63,6 +64,7 @@ import net.fabricmc.loom.api.NeoForgeExtensionAPI;
|
|||||||
import net.fabricmc.loom.api.RemapConfigurationSettings;
|
import net.fabricmc.loom.api.RemapConfigurationSettings;
|
||||||
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
|
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
|
||||||
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
|
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
|
||||||
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
|
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
|
||||||
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
|
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
|
||||||
import net.fabricmc.loom.api.remapping.RemapperExtension;
|
import net.fabricmc.loom.api.remapping.RemapperExtension;
|
||||||
@@ -103,7 +105,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
|
|||||||
protected final Property<IntermediateMappingsProvider> intermediateMappingsProvider;
|
protected final Property<IntermediateMappingsProvider> intermediateMappingsProvider;
|
||||||
private final Property<Boolean> runtimeOnlyLog4j;
|
private final Property<Boolean> runtimeOnlyLog4j;
|
||||||
private final Property<Boolean> splitModDependencies;
|
private final Property<Boolean> splitModDependencies;
|
||||||
private final Property<MinecraftJarConfiguration> minecraftJarConfiguration;
|
private final Property<MinecraftJarConfiguration<?, ?, ?>> minecraftJarConfiguration;
|
||||||
private final Property<Boolean> splitEnvironmentalSourceSet;
|
private final Property<Boolean> splitEnvironmentalSourceSet;
|
||||||
private final InterfaceInjectionExtensionAPI interfaceInjectionExtension;
|
private final InterfaceInjectionExtensionAPI interfaceInjectionExtension;
|
||||||
|
|
||||||
@@ -165,7 +167,8 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
|
|||||||
this.minecraftJarProcessors = (ListProperty<MinecraftJarProcessor<?>>) (Object) project.getObjects().listProperty(MinecraftJarProcessor.class);
|
this.minecraftJarProcessors = (ListProperty<MinecraftJarProcessor<?>>) (Object) project.getObjects().listProperty(MinecraftJarProcessor.class);
|
||||||
this.minecraftJarProcessors.finalizeValueOnRead();
|
this.minecraftJarProcessors.finalizeValueOnRead();
|
||||||
|
|
||||||
this.minecraftJarConfiguration = project.getObjects().property(MinecraftJarConfiguration.class).convention(MinecraftJarConfiguration.MERGED);
|
//noinspection unchecked
|
||||||
|
this.minecraftJarConfiguration = project.getObjects().property((Class<MinecraftJarConfiguration<?, ?, ?>>) (Class<?>) MinecraftJarConfiguration.class).convention(MinecraftJarConfiguration.MERGED);
|
||||||
this.minecraftJarConfiguration.finalizeValueOnRead();
|
this.minecraftJarConfiguration.finalizeValueOnRead();
|
||||||
|
|
||||||
this.accessWidener.finalizeValueOnRead();
|
this.accessWidener.finalizeValueOnRead();
|
||||||
@@ -384,7 +387,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Property<MinecraftJarConfiguration> getMinecraftJarConfiguration() {
|
public Property<MinecraftJarConfiguration<?, ?, ?>> getMinecraftJarConfiguration() {
|
||||||
return minecraftJarConfiguration;
|
return minecraftJarConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,6 +480,13 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
|
|||||||
return getProject().provider(() -> LoomGradleExtension.get(getProject()).getMinecraftProvider().minecraftVersion());
|
return getProject().provider(() -> LoomGradleExtension.get(getProject()).getMinecraftProvider().minecraftVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileCollection getNamedMinecraftJars() {
|
||||||
|
final ConfigurableFileCollection jars = getProject().getObjects().fileCollection();
|
||||||
|
jars.from(getProject().provider(() -> LoomGradleExtension.get(getProject()).getMinecraftJars(MappingsNamespace.NAMED)));
|
||||||
|
return jars;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void silentMojangMappingsLicense() {
|
public void silentMojangMappingsLicense() {
|
||||||
this.silentMojangMappingsLicense = true;
|
this.silentMojangMappingsLicense = true;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import net.fabricmc.loom.configuration.providers.forge.ForgeRunsProvider;
|
|||||||
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider;
|
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
|
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
||||||
|
import net.fabricmc.loom.configuration.providers.mappings.NoOpIntermediateMappingsProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessorManager;
|
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessorManager;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
|
||||||
@@ -186,6 +187,11 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
|
|||||||
this.intermediaryMinecraftProvider = intermediaryMinecraftProvider;
|
this.intermediaryMinecraftProvider = intermediaryMinecraftProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noIntermediateMappings() {
|
||||||
|
setIntermediateMappingsProvider(NoOpIntermediateMappingsProvider.class, p -> { });
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SrgMinecraftProvider<?> getSrgMinecraftProvider() {
|
public SrgMinecraftProvider<?> getSrgMinecraftProvider() {
|
||||||
return Objects.requireNonNull(srgMinecraftProvider, "Cannot get SrgMinecraftProvider before it has been setup");
|
return Objects.requireNonNull(srgMinecraftProvider, "Cannot get SrgMinecraftProvider before it has been setup");
|
||||||
@@ -312,6 +318,9 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
|
|||||||
|
|
||||||
provider.getDownloader().set(this::download);
|
provider.getDownloader().set(this::download);
|
||||||
provider.getDownloader().disallowChanges();
|
provider.getDownloader().disallowChanges();
|
||||||
|
|
||||||
|
provider.getIsLegacyMinecraft().set(getProject().provider(() -> getMinecraftProvider().isLegacyVersion()));
|
||||||
|
provider.getIsLegacyMinecraft().disallowChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,271 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
* 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.kotlin.remapping;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import kotlinx.metadata.KmAnnotation;
|
|
||||||
import kotlinx.metadata.KmProperty;
|
|
||||||
import kotlinx.metadata.internal.extensions.KmClassExtension;
|
|
||||||
import kotlinx.metadata.internal.extensions.KmConstructorExtension;
|
|
||||||
import kotlinx.metadata.internal.extensions.KmFunctionExtension;
|
|
||||||
import kotlinx.metadata.internal.extensions.KmPackageExtension;
|
|
||||||
import kotlinx.metadata.internal.extensions.KmPropertyExtension;
|
|
||||||
import kotlinx.metadata.internal.extensions.KmTypeExtension;
|
|
||||||
import kotlinx.metadata.internal.extensions.KmTypeParameterExtension;
|
|
||||||
import kotlinx.metadata.jvm.JvmFieldSignature;
|
|
||||||
import kotlinx.metadata.jvm.JvmMethodSignature;
|
|
||||||
import kotlinx.metadata.jvm.internal.JvmClassExtension;
|
|
||||||
import kotlinx.metadata.jvm.internal.JvmConstructorExtension;
|
|
||||||
import kotlinx.metadata.jvm.internal.JvmFunctionExtension;
|
|
||||||
import kotlinx.metadata.jvm.internal.JvmPackageExtension;
|
|
||||||
import kotlinx.metadata.jvm.internal.JvmPropertyExtension;
|
|
||||||
import kotlinx.metadata.jvm.internal.JvmTypeExtension;
|
|
||||||
import kotlinx.metadata.jvm.internal.JvmTypeParameterExtension;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is a fun meme. All of these kotlin classes are marked as "internal" so Kotlin code cannot compile against them.
|
|
||||||
* However, luckily for us the Java compiler has no idea about this, so they can compile against it :D
|
|
||||||
*
|
|
||||||
* This file contains Java wrappers around Kotlin classes, to used by Kotlin.
|
|
||||||
*/
|
|
||||||
public interface JvmExtensionWrapper {
|
|
||||||
record Class(JvmClassExtension extension) implements JvmExtensionWrapper {
|
|
||||||
@Nullable
|
|
||||||
public static Class get(KmClassExtension classExtension) {
|
|
||||||
if (classExtension instanceof JvmClassExtension jvmClassExtension) {
|
|
||||||
return new Class(jvmClassExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<KmProperty> getLocalDelegatedProperties() {
|
|
||||||
return extension.getLocalDelegatedProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getModuleName() {
|
|
||||||
return extension.getModuleName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setModuleName(@Nullable String name) {
|
|
||||||
extension.setModuleName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getAnonymousObjectOriginName() {
|
|
||||||
return extension.getAnonymousObjectOriginName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAnonymousObjectOriginName(@Nullable String name) {
|
|
||||||
extension.setAnonymousObjectOriginName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getJvmFlags() {
|
|
||||||
return extension.getJvmFlags();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJvmFlags(int flags) {
|
|
||||||
extension.setJvmFlags(flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record Package(JvmPackageExtension extension) {
|
|
||||||
@Nullable
|
|
||||||
public static Package get(KmPackageExtension packageExtension) {
|
|
||||||
if (packageExtension instanceof JvmPackageExtension jvmPackageExtension) {
|
|
||||||
return new Package(jvmPackageExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<KmProperty> getLocalDelegatedProperties() {
|
|
||||||
return extension.getLocalDelegatedProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getModuleName() {
|
|
||||||
return extension.getModuleName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setModuleName(@Nullable String name) {
|
|
||||||
extension.setModuleName(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record Function(JvmFunctionExtension extension) {
|
|
||||||
@Nullable
|
|
||||||
public static Function get(KmFunctionExtension functionExtension) {
|
|
||||||
if (functionExtension instanceof JvmFunctionExtension jvmFunctionExtension) {
|
|
||||||
return new Function(jvmFunctionExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public JvmMethodSignature getSignature() {
|
|
||||||
return extension.getSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSignature(@Nullable JvmMethodSignature signature) {
|
|
||||||
extension.setSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getLambdaClassOriginName() {
|
|
||||||
return extension.getLambdaClassOriginName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLambdaClassOriginName(@Nullable String name) {
|
|
||||||
extension.setLambdaClassOriginName(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record Property(JvmPropertyExtension extension) {
|
|
||||||
@Nullable
|
|
||||||
public static Property get(KmPropertyExtension propertyExtension) {
|
|
||||||
if (propertyExtension instanceof JvmPropertyExtension jvmPropertyExtension) {
|
|
||||||
return new Property(jvmPropertyExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getJvmFlags() {
|
|
||||||
return extension.getJvmFlags();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJvmFlags(int flags) {
|
|
||||||
extension.setJvmFlags(flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public JvmFieldSignature getFieldSignature() {
|
|
||||||
return extension.getFieldSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFieldSignature(@Nullable JvmFieldSignature signature) {
|
|
||||||
extension.setFieldSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public JvmMethodSignature getGetterSignature() {
|
|
||||||
return extension.getGetterSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGetterSignature(@Nullable JvmMethodSignature signature) {
|
|
||||||
extension.setGetterSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public JvmMethodSignature getSetterSignature() {
|
|
||||||
return extension.getSetterSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSetterSignature(@Nullable JvmMethodSignature signature) {
|
|
||||||
extension.setSetterSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public JvmMethodSignature getSyntheticMethodForAnnotations() {
|
|
||||||
return extension.getSyntheticMethodForAnnotations();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSyntheticMethodForAnnotations(@Nullable JvmMethodSignature signature) {
|
|
||||||
extension.setSyntheticMethodForAnnotations(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public JvmMethodSignature getSyntheticMethodForDelegate() {
|
|
||||||
return extension.getSyntheticMethodForDelegate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSyntheticMethodForDelegate(@Nullable JvmMethodSignature signature) {
|
|
||||||
extension.setSyntheticMethodForDelegate(signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record Constructor(JvmConstructorExtension extension) {
|
|
||||||
@Nullable
|
|
||||||
public static Constructor get(KmConstructorExtension constructorExtension) {
|
|
||||||
if (constructorExtension instanceof JvmConstructorExtension jvmConstructorExtension) {
|
|
||||||
return new Constructor(jvmConstructorExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public JvmMethodSignature getSignature() {
|
|
||||||
return extension.getSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSignature(@Nullable JvmMethodSignature signature) {
|
|
||||||
extension.setSignature(signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record TypeParameter(JvmTypeParameterExtension extension) {
|
|
||||||
@Nullable
|
|
||||||
public static TypeParameter get(KmTypeParameterExtension typeParameterExtension) {
|
|
||||||
if (typeParameterExtension instanceof JvmTypeParameterExtension jvmTypeParameterExtension) {
|
|
||||||
return new TypeParameter(jvmTypeParameterExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<KmAnnotation> getAnnotations() {
|
|
||||||
return extension.getAnnotations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record Type(JvmTypeExtension extension) {
|
|
||||||
@Nullable
|
|
||||||
public static Type get(KmTypeExtension typeExtension) {
|
|
||||||
if (typeExtension instanceof JvmTypeExtension jvmTypeExtension) {
|
|
||||||
return new Type(jvmTypeExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRaw() {
|
|
||||||
return extension.isRaw();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRaw(boolean raw) {
|
|
||||||
extension.setRaw(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<KmAnnotation> getAnnotations() {
|
|
||||||
return extension.getAnnotations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,6 +40,7 @@ import org.gradle.api.Project;
|
|||||||
import org.gradle.api.file.ConfigurableFileCollection;
|
import org.gradle.api.file.ConfigurableFileCollection;
|
||||||
import org.gradle.api.file.FileCollection;
|
import org.gradle.api.file.FileCollection;
|
||||||
import org.gradle.api.provider.Property;
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.provider.Provider;
|
||||||
import org.gradle.api.services.ServiceReference;
|
import org.gradle.api.services.ServiceReference;
|
||||||
import org.gradle.api.specs.Spec;
|
import org.gradle.api.specs.Spec;
|
||||||
import org.gradle.api.tasks.JavaExec;
|
import org.gradle.api.tasks.JavaExec;
|
||||||
@@ -52,7 +53,7 @@ import net.fabricmc.loom.util.gradle.SyncTaskBuildService;
|
|||||||
public abstract class AbstractRunTask extends JavaExec {
|
public abstract class AbstractRunTask extends JavaExec {
|
||||||
private static final CharsetEncoder ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder();
|
private static final CharsetEncoder ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder();
|
||||||
|
|
||||||
private final RunConfig config;
|
private final Provider<RunConfig> config;
|
||||||
// We control the classpath, as we use a ArgFile to pass it over the command line: https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#commandlineargfile
|
// We control the classpath, as we use a ArgFile to pass it over the command line: https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#commandlineargfile
|
||||||
private final ConfigurableFileCollection classpath = getProject().getObjects().fileCollection();
|
private final ConfigurableFileCollection classpath = getProject().getObjects().fileCollection();
|
||||||
|
|
||||||
@@ -63,13 +64,12 @@ public abstract class AbstractRunTask extends JavaExec {
|
|||||||
public AbstractRunTask(Function<Project, RunConfig> configProvider) {
|
public AbstractRunTask(Function<Project, RunConfig> configProvider) {
|
||||||
super();
|
super();
|
||||||
setGroup(Constants.TaskGroup.FABRIC);
|
setGroup(Constants.TaskGroup.FABRIC);
|
||||||
this.config = configProvider.apply(getProject());
|
|
||||||
|
|
||||||
setClasspath(config.sourceSet.getRuntimeClasspath().filter(File::exists).filter(new LibraryFilter()));
|
this.config = getProject().provider(() -> configProvider.apply(getProject()));
|
||||||
|
|
||||||
args(config.programArgs);
|
|
||||||
getMainClass().set(config.mainClass);
|
|
||||||
|
|
||||||
|
classpath.from(config.map(runConfig -> runConfig.sourceSet.getRuntimeClasspath().filter(File::exists).filter(new LibraryFilter())));
|
||||||
|
getArgumentProviders().add(() -> config.get().programArgs);
|
||||||
|
getMainClass().set(config.map(runConfig -> runConfig.mainClass));
|
||||||
getJvmArguments().addAll(getProject().provider(this::getGameJvmArgs));
|
getJvmArguments().addAll(getProject().provider(this::getGameJvmArgs));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,8 +100,8 @@ public abstract class AbstractRunTask extends JavaExec {
|
|||||||
super.setClasspath(classpath);
|
super.setClasspath(classpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
setWorkingDir(new File(getProject().getProjectDir(), config.runDir));
|
setWorkingDir(new File(getProject().getProjectDir(), config.get().runDir));
|
||||||
environment(config.environmentVariables);
|
environment(config.get().environmentVariables);
|
||||||
|
|
||||||
super.exec();
|
super.exec();
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ public abstract class AbstractRunTask extends JavaExec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args.addAll(config.vmArgs);
|
args.addAll(config.get().vmArgs);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,11 +204,11 @@ public abstract class AbstractRunTask extends JavaExec {
|
|||||||
@Override
|
@Override
|
||||||
public boolean isSatisfiedBy(File element) {
|
public boolean isSatisfiedBy(File element) {
|
||||||
if (excludedLibraryPaths == null) {
|
if (excludedLibraryPaths == null) {
|
||||||
excludedLibraryPaths = config.getExcludedLibraryPaths(getProject());
|
excludedLibraryPaths = config.get().getExcludedLibraryPaths(getProject());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (excludedLibraryPaths.contains(element.getAbsolutePath())) {
|
if (excludedLibraryPaths.contains(element.getAbsolutePath())) {
|
||||||
getProject().getLogger().debug("Excluding library {} from {} run config", element.getName(), config.configName);
|
getProject().getLogger().debug("Excluding library {} from {} run config", element.getName(), config.get().configName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package net.fabricmc.loom.task;
|
package net.fabricmc.loom.task;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -36,11 +37,15 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.StringJoiner;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -54,9 +59,11 @@ import org.gradle.api.services.ServiceReference;
|
|||||||
import org.gradle.api.tasks.Input;
|
import org.gradle.api.tasks.Input;
|
||||||
import org.gradle.api.tasks.InputFile;
|
import org.gradle.api.tasks.InputFile;
|
||||||
import org.gradle.api.tasks.InputFiles;
|
import org.gradle.api.tasks.InputFiles;
|
||||||
|
import org.gradle.api.tasks.Internal;
|
||||||
import org.gradle.api.tasks.Optional;
|
import org.gradle.api.tasks.Optional;
|
||||||
import org.gradle.api.tasks.OutputFile;
|
import org.gradle.api.tasks.OutputFile;
|
||||||
import org.gradle.api.tasks.TaskAction;
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
import org.gradle.api.tasks.options.Option;
|
||||||
import org.gradle.process.ExecOperations;
|
import org.gradle.process.ExecOperations;
|
||||||
import org.gradle.process.ExecResult;
|
import org.gradle.process.ExecResult;
|
||||||
import org.gradle.work.DisableCachingByDefault;
|
import org.gradle.work.DisableCachingByDefault;
|
||||||
@@ -65,8 +72,12 @@ import org.gradle.workers.WorkParameters;
|
|||||||
import org.gradle.workers.WorkQueue;
|
import org.gradle.workers.WorkQueue;
|
||||||
import org.gradle.workers.WorkerExecutor;
|
import org.gradle.workers.WorkerExecutor;
|
||||||
import org.gradle.workers.internal.WorkerDaemonClientsManager;
|
import org.gradle.workers.internal.WorkerDaemonClientsManager;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
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.DecompilationMetadata;
|
||||||
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
|
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
|
||||||
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
|
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
|
||||||
@@ -77,9 +88,14 @@ import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
|
|||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.mapped.AbstractMappedMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.mapped.AbstractMappedMinecraftProvider;
|
||||||
import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper;
|
import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper;
|
||||||
|
import net.fabricmc.loom.decompilers.ClassLineNumbers;
|
||||||
import net.fabricmc.loom.decompilers.LineNumberRemapper;
|
import net.fabricmc.loom.decompilers.LineNumberRemapper;
|
||||||
|
import net.fabricmc.loom.decompilers.cache.CachedData;
|
||||||
|
import net.fabricmc.loom.decompilers.cache.CachedFileStoreImpl;
|
||||||
|
import net.fabricmc.loom.decompilers.cache.CachedJarProcessor;
|
||||||
import net.fabricmc.loom.decompilers.linemap.LineMapClassFilter;
|
import net.fabricmc.loom.decompilers.linemap.LineMapClassFilter;
|
||||||
import net.fabricmc.loom.decompilers.linemap.LineMapVisitor;
|
import net.fabricmc.loom.decompilers.linemap.LineMapVisitor;
|
||||||
|
import net.fabricmc.loom.util.Checksum;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.ExceptionUtil;
|
import net.fabricmc.loom.util.ExceptionUtil;
|
||||||
import net.fabricmc.loom.util.FileSystemUtil;
|
import net.fabricmc.loom.util.FileSystemUtil;
|
||||||
@@ -99,6 +115,8 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
|||||||
|
|
||||||
@DisableCachingByDefault
|
@DisableCachingByDefault
|
||||||
public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(GenerateSourcesTask.class);
|
||||||
|
private static final String CACHE_VERSION = "v1";
|
||||||
private final DecompilerOptions decompilerOptions;
|
private final DecompilerOptions decompilerOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,10 +144,25 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
@Optional
|
@Optional
|
||||||
public abstract ConfigurableFileCollection getUnpickClasspath();
|
public abstract ConfigurableFileCollection getUnpickClasspath();
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
@Optional
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public abstract ConfigurableFileCollection getUnpickRuntimeClasspath();
|
||||||
|
|
||||||
@OutputFile
|
@OutputFile
|
||||||
@Optional
|
@Optional
|
||||||
public abstract RegularFileProperty getUnpickOutputJar();
|
public abstract RegularFileProperty getUnpickOutputJar();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Option(option = "use-cache", description = "Use the decompile cache")
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public abstract Property<Boolean> getUseCache();
|
||||||
|
|
||||||
|
// Internal outputs
|
||||||
|
@ApiStatus.Internal
|
||||||
|
@Internal
|
||||||
|
protected abstract RegularFileProperty getDecompileCacheFile();
|
||||||
|
|
||||||
// Injects
|
// Injects
|
||||||
@Inject
|
@Inject
|
||||||
public abstract WorkerExecutor getWorkerExecutor();
|
public abstract WorkerExecutor getWorkerExecutor();
|
||||||
@@ -151,6 +184,12 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
getOutputs().upToDateWhen((o) -> false);
|
getOutputs().upToDateWhen((o) -> false);
|
||||||
getClasspath().from(decompilerOptions.getClasspath()).finalizeValueOnRead();
|
getClasspath().from(decompilerOptions.getClasspath()).finalizeValueOnRead();
|
||||||
dependsOn(decompilerOptions.getClasspath().getBuiltBy());
|
dependsOn(decompilerOptions.getClasspath().getBuiltBy());
|
||||||
|
|
||||||
|
LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||||
|
getDecompileCacheFile().set(extension.getFiles().getDecompileCache(CACHE_VERSION));
|
||||||
|
getUnpickRuntimeClasspath().from(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH));
|
||||||
|
|
||||||
|
getUseCache().convention(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
@@ -161,21 +200,197 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements.");
|
throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!getUseCache().get()) {
|
||||||
|
try (var timer = new Timer("Decompiled sources")) {
|
||||||
|
runWithoutCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Using decompile cache.");
|
||||||
|
|
||||||
|
try (var timer = new Timer("Decompiled sources with cache")) {
|
||||||
|
final Path cacheFile = getDecompileCacheFile().getAsFile().get().toPath();
|
||||||
|
|
||||||
|
// TODO ensure we have a lock on this file to prevent multiple tasks from running at the same time
|
||||||
|
// TODO handle being unable to read the cache file
|
||||||
|
Files.createDirectories(cacheFile.getParent());
|
||||||
|
|
||||||
|
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) {
|
||||||
|
runWithCache(fs.getRoot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runWithCache(Path cacheRoot) throws IOException {
|
||||||
final MinecraftJar minecraftJar = rebuildInputJar();
|
final MinecraftJar minecraftJar = rebuildInputJar();
|
||||||
// Input jar is the jar to decompile, this may be unpicked.
|
final var cacheRules = new CachedFileStoreImpl.CacheRules(50_000, Duration.ofDays(90));
|
||||||
|
final var decompileCache = new CachedFileStoreImpl<>(cacheRoot, CachedData.SERIALIZER, cacheRules);
|
||||||
|
final String cacheKey = getCacheKey();
|
||||||
|
final CachedJarProcessor cachedJarProcessor = new CachedJarProcessor(decompileCache, cacheKey);
|
||||||
|
final CachedJarProcessor.WorkRequest workRequest;
|
||||||
|
|
||||||
|
LOGGER.info("Decompile cache key: {}", cacheKey);
|
||||||
|
|
||||||
|
try (var timer = new Timer("Prepare job")) {
|
||||||
|
workRequest = cachedJarProcessor.prepareJob(minecraftJar.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
final CachedJarProcessor.WorkJob job = workRequest.job();
|
||||||
|
final CachedJarProcessor.CacheStats cacheStats = workRequest.stats();
|
||||||
|
|
||||||
|
getProject().getLogger().lifecycle("Decompile cache stats: {} hits, {} misses", cacheStats.hits(), cacheStats.misses());
|
||||||
|
|
||||||
|
ClassLineNumbers outputLineNumbers = null;
|
||||||
|
|
||||||
|
if (job instanceof CachedJarProcessor.WorkToDoJob workToDoJob) {
|
||||||
|
Path inputJar = workToDoJob.incomplete();
|
||||||
|
@Nullable Path existing = (job instanceof CachedJarProcessor.PartialWorkJob partialWorkJob) ? partialWorkJob.existing() : null;
|
||||||
|
|
||||||
|
if (getUnpickDefinitions().isPresent()) {
|
||||||
|
try (var timer = new Timer("Unpick")) {
|
||||||
|
inputJar = unpickJar(inputJar, existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (var timer = new Timer("Decompile")) {
|
||||||
|
outputLineNumbers = runDecompileJob(inputJar, workToDoJob.output(), existing);
|
||||||
|
outputLineNumbers = filterForgeLineNumbers(outputLineNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Files.notExists(workToDoJob.output())) {
|
||||||
|
throw new RuntimeException("Failed to decompile sources");
|
||||||
|
}
|
||||||
|
} else if (job instanceof CachedJarProcessor.CompletedWorkJob completedWorkJob) {
|
||||||
|
// Nothing to do :)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The final output sources jar
|
||||||
|
final Path sourcesJar = getOutputJar().get().getAsFile().toPath();
|
||||||
|
Files.deleteIfExists(sourcesJar);
|
||||||
|
|
||||||
|
try (var timer = new Timer("Complete job")) {
|
||||||
|
cachedJarProcessor.completeJob(sourcesJar, job, outputLineNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the minecraft jar used at runtime.
|
||||||
|
final Path classesJar = minecraftJar.getPath();
|
||||||
|
|
||||||
|
// Remap the line numbers with the new and existing numbers
|
||||||
|
final ClassLineNumbers existingLinenumbers = workRequest.lineNumbers();
|
||||||
|
final ClassLineNumbers lineNumbers = ClassLineNumbers.merge(existingLinenumbers, outputLineNumbers);
|
||||||
|
|
||||||
|
if (lineNumbers == null) {
|
||||||
|
LOGGER.info("No line numbers to remap, skipping remapping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path tempJar = Files.createTempFile("loom", "linenumber-remap.jar");
|
||||||
|
Files.delete(tempJar);
|
||||||
|
|
||||||
|
try (var timer = new Timer("Remap line numbers")) {
|
||||||
|
remapLineNumbers(lineNumbers, classesJar, tempJar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.move(tempJar, classesJar, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
|
try (var timer = new Timer("Prune cache")) {
|
||||||
|
decompileCache.prune();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runWithoutCache() throws IOException {
|
||||||
|
final MinecraftJar minecraftJar = rebuildInputJar();
|
||||||
|
|
||||||
Path inputJar = minecraftJar.getPath();
|
Path inputJar = minecraftJar.getPath();
|
||||||
// Runtime jar is the jar used to run the game
|
// The final output sources jar
|
||||||
final Path runtimeJar = inputJar;
|
final Path sourcesJar = getOutputJar().get().getAsFile().toPath();
|
||||||
|
|
||||||
if (getUnpickDefinitions().isPresent()) {
|
if (getUnpickDefinitions().isPresent()) {
|
||||||
inputJar = unpickJar(inputJar);
|
try (var timer = new Timer("Unpick")) {
|
||||||
|
inputJar = unpickJar(inputJar, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClassLineNumbers lineNumbers;
|
||||||
|
|
||||||
|
try (var timer = new Timer("Decompile")) {
|
||||||
|
lineNumbers = runDecompileJob(inputJar, sourcesJar, null);
|
||||||
|
lineNumbers = filterForgeLineNumbers(lineNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Files.notExists(sourcesJar)) {
|
||||||
|
throw new RuntimeException("Failed to decompile sources");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineNumbers == null) {
|
||||||
|
LOGGER.info("No line numbers to remap, skipping remapping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the minecraft jar used at runtime.
|
||||||
|
final Path classesJar = minecraftJar.getPath();
|
||||||
|
final Path tempJar = Files.createTempFile("loom", "linenumber-remap.jar");
|
||||||
|
Files.delete(tempJar);
|
||||||
|
|
||||||
|
try (var timer = new Timer("Remap line numbers")) {
|
||||||
|
remapLineNumbers(lineNumbers, classesJar, tempJar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.move(tempJar, classesJar, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCacheKey() {
|
||||||
|
var sj = new StringJoiner(",");
|
||||||
|
sj.add(getDecompilerCheckKey());
|
||||||
|
sj.add(getUnpickCacheKey());
|
||||||
|
|
||||||
|
LOGGER.info("Decompile cache data: {}", sj);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Checksum.sha256Hex(sj.toString().getBytes(StandardCharsets.UTF_8));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDecompilerCheckKey() {
|
||||||
|
var sj = new StringJoiner(",");
|
||||||
|
sj.add(decompilerOptions.getDecompilerClassName().get());
|
||||||
|
sj.add(fileCollectionHash(decompilerOptions.getClasspath()));
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : decompilerOptions.getOptions().get().entrySet()) {
|
||||||
|
sj.add(entry.getKey() + "=" + entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUnpickCacheKey() {
|
||||||
|
if (!getUnpickDefinitions().isPresent()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var sj = new StringJoiner(",");
|
||||||
|
sj.add(fileHash(getUnpickDefinitions().getAsFile().get()));
|
||||||
|
sj.add(fileCollectionHash(getUnpickConstantJar()));
|
||||||
|
sj.add(fileCollectionHash(getUnpickRuntimeClasspath()));
|
||||||
|
|
||||||
|
return sj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ClassLineNumbers runDecompileJob(Path inputJar, Path outputJar, @Nullable Path existingJar) throws IOException {
|
||||||
|
final Platform platform = Platform.CURRENT;
|
||||||
|
final Path lineMapFile = File.createTempFile("loom", "linemap").toPath();
|
||||||
|
Files.delete(lineMapFile);
|
||||||
|
|
||||||
if (!platform.supportsUnixDomainSockets()) {
|
if (!platform.supportsUnixDomainSockets()) {
|
||||||
getProject().getLogger().warn("Decompile worker logging disabled as Unix Domain Sockets is not supported on your operating system.");
|
getProject().getLogger().warn("Decompile worker logging disabled as Unix Domain Sockets is not supported on your operating system.");
|
||||||
|
|
||||||
doWork(null, inputJar, runtimeJar);
|
doWork(null, inputJar, outputJar, lineMapFile, existingJar);
|
||||||
return;
|
return readLineNumbers(lineMapFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the IPC path to get the log output back from the forked JVM
|
// Set up the IPC path to get the log output back from the forked JVM
|
||||||
@@ -184,7 +399,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
|
|
||||||
try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompilerOptions.getName(), "Decompiling minecraft sources");
|
try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompilerOptions.getName(), "Decompiling minecraft sources");
|
||||||
IPCServer logReceiver = new IPCServer(ipcPath, loggerConsumer)) {
|
IPCServer logReceiver = new IPCServer(ipcPath, loggerConsumer)) {
|
||||||
doWork(logReceiver, inputJar, runtimeJar);
|
doWork(logReceiver, inputJar, outputJar, lineMapFile, existingJar);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new RuntimeException("Failed to shutdown log receiver", e);
|
throw new RuntimeException("Failed to shutdown log receiver", e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -197,6 +412,28 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
ForgeSourcesRemapper.addForgeSources(getProject(), serviceManager, getOutputJar().get().getAsFile().toPath());
|
ForgeSourcesRemapper.addForgeSources(getProject(), serviceManager, getOutputJar().get().getAsFile().toPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return readLineNumbers(lineMapFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ClassLineNumbers filterForgeLineNumbers(@Nullable ClassLineNumbers lineNumbers) {
|
||||||
|
if (lineNumbers == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getParameters().getForge().get()) {
|
||||||
|
try {
|
||||||
|
// Remove Forge and NeoForge classes from linemap
|
||||||
|
// TODO: We should instead not decompile Forge's classes at all
|
||||||
|
LineMapVisitor.process(linemap, next -> new LineMapClassFilter(next, name -> {
|
||||||
|
// Skip both Forge and NeoForge classes.
|
||||||
|
return !name.startsWith("net/minecraftforge/") && !name.startsWith("net/neoforged/");
|
||||||
|
}));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException("Failed to process linemap", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-run the named minecraft provider to give us a fresh jar to decompile.
|
// Re-run the named minecraft provider to give us a fresh jar to decompile.
|
||||||
@@ -209,7 +446,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
final var provideContext = new AbstractMappedMinecraftProvider.ProvideContext(false, true, configContext);
|
final var provideContext = new AbstractMappedMinecraftProvider.ProvideContext(false, true, configContext);
|
||||||
minecraftJars = getExtension().getNamedMinecraftProvider().provide(provideContext);
|
minecraftJars = getExtension().getNamedMinecraftProvider().provide(provideContext);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to rebuild input jars", e);
|
ExceptionUtil.printFileLocks(e, getProject());
|
||||||
|
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to rebuild input jars", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (MinecraftJar minecraftJar : minecraftJars) {
|
for (MinecraftJar minecraftJar : minecraftJars) {
|
||||||
@@ -224,13 +462,13 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path unpickJar(Path inputJar) {
|
private Path unpickJar(Path inputJar, @Nullable Path existingJar) {
|
||||||
final Path outputJar = getUnpickOutputJar().get().getAsFile().toPath();
|
final Path outputJar = getUnpickOutputJar().get().getAsFile().toPath();
|
||||||
final List<String> args = getUnpickArgs(inputJar, outputJar);
|
final List<String> args = getUnpickArgs(inputJar, outputJar, existingJar);
|
||||||
|
|
||||||
ExecResult result = getExecOperations().javaexec(spec -> {
|
ExecResult result = getExecOperations().javaexec(spec -> {
|
||||||
spec.getMainClass().set("daomephsta.unpick.cli.Main");
|
spec.getMainClass().set("daomephsta.unpick.cli.Main");
|
||||||
spec.classpath(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH));
|
spec.classpath(getUnpickRuntimeClasspath());
|
||||||
spec.args(args);
|
spec.args(args);
|
||||||
spec.systemProperty("java.util.logging.config.file", writeUnpickLogConfig().getAbsolutePath());
|
spec.systemProperty("java.util.logging.config.file", writeUnpickLogConfig().getAbsolutePath());
|
||||||
});
|
});
|
||||||
@@ -240,7 +478,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
return outputJar;
|
return outputJar;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getUnpickArgs(Path inputJar, Path outputJar) {
|
private List<String> getUnpickArgs(Path inputJar, Path outputJar, @Nullable Path existingJar) {
|
||||||
var fileArgs = new ArrayList<File>();
|
var fileArgs = new ArrayList<File>();
|
||||||
|
|
||||||
fileArgs.add(inputJar.toFile());
|
fileArgs.add(inputJar.toFile());
|
||||||
@@ -257,6 +495,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
fileArgs.add(file);
|
fileArgs.add(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existingJar != null) {
|
||||||
|
fileArgs.add(existingJar.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
return fileArgs.stream()
|
return fileArgs.stream()
|
||||||
.map(File::getAbsolutePath)
|
.map(File::getAbsolutePath)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -275,25 +517,36 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
return unpickLoggingConfigFile;
|
return unpickLoggingConfigFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doWork(@Nullable IPCServer ipcServer, Path inputJar, Path runtimeJar) {
|
private void remapLineNumbers(ClassLineNumbers lineNumbers, Path inputJar, Path outputJar) throws IOException {
|
||||||
|
Objects.requireNonNull(lineNumbers, "lineNumbers");
|
||||||
|
final var remapper = new LineNumberRemapper(lineNumbers);
|
||||||
|
remapper.process(inputJar, outputJar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doWork(@Nullable IPCServer ipcServer, Path inputJar, Path outputJar, Path linemapFile, @Nullable Path existingJar) {
|
||||||
final String jvmMarkerValue = UUID.randomUUID().toString();
|
final String jvmMarkerValue = UUID.randomUUID().toString();
|
||||||
final WorkQueue workQueue = createWorkQueue(jvmMarkerValue);
|
final WorkQueue workQueue = createWorkQueue(jvmMarkerValue);
|
||||||
|
|
||||||
|
ConfigurableFileCollection classpath = getProject().files();
|
||||||
|
classpath.from(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
|
||||||
|
|
||||||
|
if (existingJar != null) {
|
||||||
|
classpath.from(existingJar);
|
||||||
|
}
|
||||||
|
|
||||||
workQueue.submit(DecompileAction.class, params -> {
|
workQueue.submit(DecompileAction.class, params -> {
|
||||||
params.getDecompilerOptions().set(decompilerOptions.toDto());
|
params.getDecompilerOptions().set(decompilerOptions.toDto());
|
||||||
|
|
||||||
params.getInputJar().set(inputJar.toFile());
|
params.getInputJar().set(inputJar.toFile());
|
||||||
params.getRuntimeJar().set(runtimeJar.toFile());
|
params.getOutputJar().set(outputJar.toFile());
|
||||||
params.getSourcesDestinationJar().set(getOutputJar());
|
params.getLinemapFile().set(linemapFile.toFile());
|
||||||
params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap", runtimeJar));
|
|
||||||
params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar", runtimeJar));
|
|
||||||
params.getMappings().set(getMappings().toFile());
|
params.getMappings().set(getMappings().toFile());
|
||||||
|
|
||||||
if (ipcServer != null) {
|
if (ipcServer != null) {
|
||||||
params.getIPCPath().set(ipcServer.getPath().toFile());
|
params.getIPCPath().set(ipcServer.getPath().toFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
params.getClassPath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
|
params.getClassPath().setFrom(classpath);
|
||||||
|
|
||||||
// Architectury
|
// Architectury
|
||||||
params.getForge().set(getExtension().isForgeLike());
|
params.getForge().set(getExtension().isForgeLike());
|
||||||
@@ -338,10 +591,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
Property<DecompilerOptions.Dto> getDecompilerOptions();
|
Property<DecompilerOptions.Dto> getDecompilerOptions();
|
||||||
|
|
||||||
RegularFileProperty getInputJar();
|
RegularFileProperty getInputJar();
|
||||||
RegularFileProperty getRuntimeJar();
|
RegularFileProperty getOutputJar();
|
||||||
RegularFileProperty getSourcesDestinationJar();
|
RegularFileProperty getLinemapFile();
|
||||||
RegularFileProperty getLinemap();
|
|
||||||
RegularFileProperty getLinemapJar();
|
|
||||||
RegularFileProperty getMappings();
|
RegularFileProperty getMappings();
|
||||||
|
|
||||||
RegularFileProperty getIPCPath();
|
RegularFileProperty getIPCPath();
|
||||||
@@ -372,10 +623,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
|
|
||||||
private void doDecompile(IOStringConsumer logger) {
|
private void doDecompile(IOStringConsumer logger) {
|
||||||
final Path inputJar = getParameters().getInputJar().get().getAsFile().toPath();
|
final Path inputJar = getParameters().getInputJar().get().getAsFile().toPath();
|
||||||
final Path sourcesDestinationJar = getParameters().getSourcesDestinationJar().get().getAsFile().toPath();
|
final Path linemap = getParameters().getLinemapFile().get().getAsFile().toPath();
|
||||||
final Path linemap = getParameters().getLinemap().get().getAsFile().toPath();
|
final Path outputJar = getParameters().getOutputJar().get().getAsFile().toPath();
|
||||||
final Path linemapJar = getParameters().getLinemapJar().get().getAsFile().toPath();
|
|
||||||
final Path runtimeJar = getParameters().getRuntimeJar().get().getAsFile().toPath();
|
|
||||||
|
|
||||||
final DecompilerOptions.Dto decompilerOptions = getParameters().getDecompilerOptions().get();
|
final DecompilerOptions.Dto decompilerOptions = getParameters().getDecompilerOptions().get();
|
||||||
|
|
||||||
@@ -391,7 +640,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
throw new RuntimeException("Failed to create decompiler", e);
|
throw new RuntimeException("Failed to create decompiler", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
DecompilationMetadata metadata = new DecompilationMetadata(
|
final var metadata = new DecompilationMetadata(
|
||||||
decompilerOptions.maxThreads(),
|
decompilerOptions.maxThreads(),
|
||||||
getParameters().getMappings().get().getAsFile().toPath(),
|
getParameters().getMappings().get().getAsFile().toPath(),
|
||||||
getLibraries(),
|
getLibraries(),
|
||||||
@@ -401,7 +650,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
|
|
||||||
decompiler.decompile(
|
decompiler.decompile(
|
||||||
inputJar,
|
inputJar,
|
||||||
sourcesDestinationJar,
|
outputJar,
|
||||||
linemap,
|
linemap,
|
||||||
metadata
|
metadata
|
||||||
);
|
);
|
||||||
@@ -412,41 +661,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException("Failed to close loggers", e);
|
throw new UncheckedIOException("Failed to close loggers", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Files.exists(linemap)) {
|
|
||||||
if (getParameters().getForge().get()) {
|
|
||||||
try {
|
|
||||||
// Remove Forge and NeoForge classes from linemap
|
|
||||||
// TODO: We should instead not decompile Forge's classes at all
|
|
||||||
LineMapVisitor.process(linemap, next -> new LineMapClassFilter(next, name -> {
|
|
||||||
// Skip both Forge and NeoForge classes.
|
|
||||||
return !name.startsWith("net/minecraftforge/") && !name.startsWith("net/neoforged/");
|
|
||||||
}));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Failed to process linemap", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Line map the actually jar used to run the game, not the one used to decompile
|
|
||||||
remapLineNumbers(metadata.logger(), runtimeJar, linemap, linemapJar);
|
|
||||||
|
|
||||||
Files.copy(linemapJar, runtimeJar, StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
Files.delete(linemapJar);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Failed to remap line numbers", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void remapLineNumbers(IOStringConsumer logger, Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException {
|
|
||||||
LineNumberRemapper remapper = new LineNumberRemapper();
|
|
||||||
remapper.readMappings(linemap.toFile());
|
|
||||||
|
|
||||||
try (FileSystemUtil.Delegate inFs = FileSystemUtil.getJarFileSystem(oldCompiledJar.toFile(), true);
|
|
||||||
FileSystemUtil.Delegate outFs = FileSystemUtil.getJarFileSystem(linemappedJarDestination.toFile(), true)) {
|
|
||||||
remapper.process(logger, inFs.get().getPath("/"), outFs.get().getPath("/"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<Path> getLibraries() {
|
private Collection<Path> getLibraries() {
|
||||||
@@ -462,16 +676,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
return getMappedJarFileWithSuffix(suffix, runtimeJar.get().getAsFile().toPath());
|
return getMappedJarFileWithSuffix(suffix, runtimeJar.get().getAsFile().toPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getMappedJarFileWithSuffix(String suffix, Path runtimeJar) {
|
|
||||||
final String path = runtimeJar.toFile().getAbsolutePath();
|
|
||||||
|
|
||||||
if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) {
|
|
||||||
throw new RuntimeException("Invalid mapped JAR path: " + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new File(path.substring(0, path.length() - 4) + suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path getMappings() {
|
private Path getMappings() {
|
||||||
Path inputMappings = getExtension().getPlatformMappingFile();
|
Path inputMappings = getExtension().getPlatformMappingFile();
|
||||||
|
|
||||||
@@ -530,8 +734,25 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
return outputMappings;
|
return outputMappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface MappingsProcessor {
|
public static File getJarFileWithSuffix(String suffix, Path runtimeJar) {
|
||||||
boolean transform(MemoryMappingTree mappings);
|
final String path = runtimeJar.toFile().getAbsolutePath();
|
||||||
|
|
||||||
|
if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) {
|
||||||
|
throw new RuntimeException("Invalid mapped JAR path: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new File(path.substring(0, path.length() - 4) + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static ClassLineNumbers readLineNumbers(Path linemapFile) throws IOException {
|
||||||
|
if (Files.notExists(linemapFile)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(linemapFile, StandardCharsets.UTF_8)) {
|
||||||
|
return ClassLineNumbers.readMappings(reader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) {
|
private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) {
|
||||||
@@ -544,4 +765,43 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String fileHash(File file) {
|
||||||
|
try {
|
||||||
|
return Checksum.sha256Hex(Files.readAllBytes(file.toPath()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fileCollectionHash(FileCollection files) {
|
||||||
|
var sj = new StringJoiner(",");
|
||||||
|
|
||||||
|
files.getFiles()
|
||||||
|
.stream()
|
||||||
|
.sorted(Comparator.comparing(File::getAbsolutePath))
|
||||||
|
.map(GenerateSourcesTask::fileHash)
|
||||||
|
.forEach(sj::add);
|
||||||
|
|
||||||
|
return sj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MappingsProcessor {
|
||||||
|
boolean transform(MemoryMappingTree mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Timer implements AutoCloseable {
|
||||||
|
private final String name;
|
||||||
|
private final long start;
|
||||||
|
|
||||||
|
Timer(String name) {
|
||||||
|
this.name = name;
|
||||||
|
this.start = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
getProject().getLogger().info("{} took {}ms", name, System.currentTimeMillis() - start);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,13 +164,18 @@ public abstract class LoomTasks implements Runnable {
|
|||||||
|
|
||||||
// Remove the client or server run config when not required. Done by name to not remove any possible custom run configs
|
// Remove the client or server run config when not required. Done by name to not remove any possible custom run configs
|
||||||
GradleUtils.afterSuccessfulEvaluation(getProject(), () -> {
|
GradleUtils.afterSuccessfulEvaluation(getProject(), () -> {
|
||||||
String taskName = switch (extension.getMinecraftJarConfiguration().get()) {
|
String taskName;
|
||||||
case SERVER_ONLY -> "client";
|
|
||||||
case CLIENT_ONLY -> "server";
|
|
||||||
default -> null;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (taskName == null) {
|
boolean serverOnly = extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SERVER_ONLY;
|
||||||
|
boolean clientOnly = extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.CLIENT_ONLY;
|
||||||
|
|
||||||
|
if (serverOnly) {
|
||||||
|
// Server only, remove the client run config
|
||||||
|
taskName = "client";
|
||||||
|
} else if (clientOnly) {
|
||||||
|
// Client only, remove the server run config
|
||||||
|
taskName = "server";
|
||||||
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,6 @@ public abstract class PrepareJarRemapTask extends AbstractLoomTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void prepare(TinyRemapperService tinyRemapperService, Path inputFile) {
|
static void prepare(TinyRemapperService tinyRemapperService, Path inputFile) {
|
||||||
tinyRemapperService.getTinyRemapperForInputs().readInputs(tinyRemapperService.getOrCreateTag(inputFile), inputFile);
|
tinyRemapperService.getTinyRemapperForInputs().readInputsAsync(tinyRemapperService.getOrCreateTag(inputFile), inputFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ import net.fabricmc.loom.util.SidedClassVisitor;
|
|||||||
import net.fabricmc.loom.util.ZipUtils;
|
import net.fabricmc.loom.util.ZipUtils;
|
||||||
import net.fabricmc.loom.util.fmj.FabricModJson;
|
import net.fabricmc.loom.util.fmj.FabricModJson;
|
||||||
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
|
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
|
||||||
|
import net.fabricmc.loom.util.fmj.FabricModJsonUtils;
|
||||||
import net.fabricmc.loom.util.service.BuildSharedServiceManager;
|
import net.fabricmc.loom.util.service.BuildSharedServiceManager;
|
||||||
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
|
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
|
||||||
import net.fabricmc.tinyremapper.OutputConsumerPath;
|
import net.fabricmc.tinyremapper.OutputConsumerPath;
|
||||||
@@ -101,6 +102,14 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
|||||||
@Input
|
@Input
|
||||||
public abstract Property<Boolean> getAddNestedDependencies();
|
public abstract Property<Boolean> getAddNestedDependencies();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to optimize the fabric.mod.json file, by default this is false.
|
||||||
|
*
|
||||||
|
* <p>The schemaVersion entry will be placed first in the json file
|
||||||
|
*/
|
||||||
|
@Input
|
||||||
|
public abstract Property<Boolean> getOptimizeFabricModJson();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the jar paths to the access wideners that will be converted to ATs for Forge runtime.
|
* Gets the jar paths to the access wideners that will be converted to ATs for Forge runtime.
|
||||||
* If you specify multiple files, they will be merged into one.
|
* If you specify multiple files, they will be merged into one.
|
||||||
@@ -145,6 +154,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
|||||||
final ConfigurationContainer configurations = getProject().getConfigurations();
|
final ConfigurationContainer configurations = getProject().getConfigurations();
|
||||||
getClasspath().from(configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
|
getClasspath().from(configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
|
||||||
getAddNestedDependencies().convention(true).finalizeValueOnRead();
|
getAddNestedDependencies().convention(true).finalizeValueOnRead();
|
||||||
|
getOptimizeFabricModJson().convention(false).finalizeValueOnRead();
|
||||||
getReadMixinConfigsFromManifest().convention(LoomGradleExtension.get(getProject()).isForgeLike()).finalizeValueOnRead();
|
getReadMixinConfigsFromManifest().convention(LoomGradleExtension.get(getProject()).isForgeLike()).finalizeValueOnRead();
|
||||||
getInjectAccessWidener().convention(false);
|
getInjectAccessWidener().convention(false);
|
||||||
|
|
||||||
@@ -236,6 +246,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
|||||||
if (!getAtAccessWideners().get().isEmpty()) {
|
if (!getAtAccessWideners().get().isEmpty()) {
|
||||||
params.getMappingBuildServiceUuid().set(UnsafeWorkQueueHelper.create(MappingsService.createDefault(getProject(), serviceManagerProvider.get().get(), getSourceNamespace().get(), getTargetNamespace().get())));
|
params.getMappingBuildServiceUuid().set(UnsafeWorkQueueHelper.create(MappingsService.createDefault(getProject(), serviceManagerProvider.get().get(), getSourceNamespace().get(), getTargetNamespace().get())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
params.getOptimizeFmj().set(getOptimizeFabricModJson().get());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,6 +305,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
|||||||
|
|
||||||
Property<Boolean> getUseMixinExtension();
|
Property<Boolean> getUseMixinExtension();
|
||||||
Property<Boolean> getMultiProjectOptimisation();
|
Property<Boolean> getMultiProjectOptimisation();
|
||||||
|
Property<Boolean> getOptimizeFmj();
|
||||||
|
|
||||||
record RefmapData(List<String> mixinConfigs, String refmapName) implements Serializable { }
|
record RefmapData(List<String> mixinConfigs, String refmapName) implements Serializable { }
|
||||||
ListProperty<RefmapData> getMixinData();
|
ListProperty<RefmapData> getMixinData();
|
||||||
@@ -348,6 +361,10 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
|||||||
|
|
||||||
rewriteJar();
|
rewriteJar();
|
||||||
|
|
||||||
|
if (getParameters().getOptimizeFmj().get()) {
|
||||||
|
optimizeFMJ();
|
||||||
|
}
|
||||||
|
|
||||||
if (tinyRemapperService != null && !getParameters().getMultiProjectOptimisation().get()) {
|
if (tinyRemapperService != null && !getParameters().getMultiProjectOptimisation().get()) {
|
||||||
tinyRemapperService.close();
|
tinyRemapperService.close();
|
||||||
}
|
}
|
||||||
@@ -482,6 +499,14 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void optimizeFMJ() throws IOException {
|
||||||
|
if (!ZipUtils.contains(outputFile, FabricModJsonFactory.FABRIC_MOD_JSON)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZipUtils.transformJson(JsonObject.class, outputFile, FabricModJsonFactory.FABRIC_MOD_JSON, FabricModJsonUtils::optimizeFmj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
88
src/main/java/net/fabricmc/loom/util/AsyncZipProcessor.java
Normal file
88
src/main/java/net/fabricmc/loom/util/AsyncZipProcessor.java
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileVisitResult;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.SimpleFileVisitor;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public interface AsyncZipProcessor {
|
||||||
|
static void processEntries(Path inputZip, Path outputZip, AsyncZipProcessor processor) throws IOException {
|
||||||
|
try (FileSystemUtil.Delegate inFs = FileSystemUtil.getJarFileSystem(inputZip, false);
|
||||||
|
FileSystemUtil.Delegate outFs = FileSystemUtil.getJarFileSystem(outputZip, true)) {
|
||||||
|
final Path inRoot = inFs.get().getPath("/");
|
||||||
|
final Path outRoot = outFs.get().getPath("/");
|
||||||
|
|
||||||
|
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||||
|
final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||||
|
|
||||||
|
Files.walkFileTree(inRoot, new SimpleFileVisitor<>() {
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path inputFile, BasicFileAttributes attrs) throws IOException {
|
||||||
|
final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
final String rel = inRoot.relativize(inputFile).toString();
|
||||||
|
final Path outputFile = outRoot.resolve(rel);
|
||||||
|
processor.processEntryAsync(inputFile, outputFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new CompletionException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, executor);
|
||||||
|
|
||||||
|
futures.add(future);
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for all futures to complete
|
||||||
|
for (CompletableFuture<Void> future : futures) {
|
||||||
|
try {
|
||||||
|
future.join();
|
||||||
|
} catch (CompletionException e) {
|
||||||
|
if (e.getCause() instanceof IOException ioe) {
|
||||||
|
throw ioe;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Failed to process zip", e.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processEntryAsync(Path inputEntry, Path outputEntry) throws IOException;
|
||||||
|
}
|
||||||
@@ -68,6 +68,11 @@ public class Checksum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String sha256Hex(byte[] input) throws IOException {
|
||||||
|
HashCode hash = ByteSource.wrap(input).hash(Hashing.sha256());
|
||||||
|
return Checksum.toHex(hash.asBytes());
|
||||||
|
}
|
||||||
|
|
||||||
public static String sha1Hex(Path path) throws IOException {
|
public static String sha1Hex(Path path) throws IOException {
|
||||||
HashCode hash = Files.asByteSource(path.toFile()).hash(Hashing.sha1());
|
HashCode hash = Files.asByteSource(path.toFile()).hash(Hashing.sha1());
|
||||||
return toHex(hash.asBytes());
|
return toHex(hash.asBytes());
|
||||||
@@ -102,6 +107,7 @@ public class Checksum {
|
|||||||
|
|
||||||
public static String projectHash(Project project) {
|
public static String projectHash(Project project) {
|
||||||
String str = project.getProjectDir().getAbsolutePath() + ":" + project.getPath();
|
String str = project.getProjectDir().getAbsolutePath() + ":" + project.getPath();
|
||||||
return toHex(str.getBytes(StandardCharsets.UTF_8)).substring(0, 16);
|
String hex = sha1Hex(str.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return hex.substring(hex.length() - 16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public class Constants {
|
|||||||
public static final String FABRIC_REPOSITORY = "https://maven.fabricmc.net/";
|
public static final String FABRIC_REPOSITORY = "https://maven.fabricmc.net/";
|
||||||
|
|
||||||
public static final int ASM_VERSION = Opcodes.ASM9;
|
public static final int ASM_VERSION = Opcodes.ASM9;
|
||||||
|
public static final String RELEASE_TIME_1_3 = "2012-07-25T22:00:00+00:00";
|
||||||
|
|
||||||
private Constants() {
|
private Constants() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 FabricMC
|
* Copyright (c) 2022-2024 FabricMC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -24,8 +24,17 @@
|
|||||||
|
|
||||||
package net.fabricmc.loom.util;
|
package net.fabricmc.loom.util;
|
||||||
|
|
||||||
|
import java.nio.file.FileSystemException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.nativeplatform.LoomNativePlatform;
|
||||||
|
|
||||||
public final class ExceptionUtil {
|
public final class ExceptionUtil {
|
||||||
/**
|
/**
|
||||||
* Creates a descriptive user-facing wrapper exception for an underlying cause.
|
* Creates a descriptive user-facing wrapper exception for an underlying cause.
|
||||||
@@ -44,4 +53,40 @@ public final class ExceptionUtil {
|
|||||||
String descriptiveMessage = "%s, %s: %s".formatted(message, cause.getClass().getName(), cause.getMessage());
|
String descriptiveMessage = "%s, %s: %s".formatted(message, cause.getClass().getName(), cause.getMessage());
|
||||||
return constructor.apply(descriptiveMessage, cause);
|
return constructor.apply(descriptiveMessage, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void printFileLocks(Throwable e, Project project) {
|
||||||
|
Throwable cause = e;
|
||||||
|
|
||||||
|
while (cause != null) {
|
||||||
|
if (cause instanceof FileSystemException fse) {
|
||||||
|
printFileLocks(fse.getFile(), project);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cause = cause.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printFileLocks(String filename, Project project) {
|
||||||
|
final Path path = Paths.get(filename);
|
||||||
|
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<ProcessHandle> processes = LoomNativePlatform.getProcessesWithLockOn(path);
|
||||||
|
|
||||||
|
if (processes.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProcessUtil processUtil = ProcessUtil.create(project);
|
||||||
|
|
||||||
|
final String noun = processes.size() == 1 ? "process has" : "processes have";
|
||||||
|
project.getLogger().error("The following {} a lock on the file '{}':", noun, path);
|
||||||
|
|
||||||
|
for (ProcessHandle process : processes) {
|
||||||
|
project.getLogger().error(processUtil.printWithParents(process));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ public final class FileSystemUtil {
|
|||||||
return get().getPath(path, more);
|
return get().getPath(path, more);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Path getRoot() {
|
||||||
|
return get().getPath("/");
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] readAllBytes(String path) throws IOException {
|
public byte[] readAllBytes(String path) throws IOException {
|
||||||
Path fsPath = getPath(path);
|
Path fsPath = getPath(path);
|
||||||
|
|
||||||
|
|||||||
109
src/main/java/net/fabricmc/loom/util/ProcessUtil.java
Normal file
109
src/main/java/net/fabricmc/loom/util/ProcessUtil.java
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.api.logging.LogLevel;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.nativeplatform.LoomNativePlatform;
|
||||||
|
|
||||||
|
public record ProcessUtil(LogLevel logLevel) {
|
||||||
|
private static final String EXPLORER_COMMAND = "C:\\Windows\\explorer.exe";
|
||||||
|
|
||||||
|
public static ProcessUtil create(Project project) {
|
||||||
|
return new ProcessUtil(project.getGradle().getStartParameter().getLogLevel());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String printWithParents(ProcessHandle handle) {
|
||||||
|
String result = printWithParents(handle, 0).trim();
|
||||||
|
|
||||||
|
if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) {
|
||||||
|
return "Run with --info or --debug to show arguments, may reveal sensitive info\n" + result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String printWithParents(ProcessHandle handle, int depth) {
|
||||||
|
var lines = new ArrayList<String>();
|
||||||
|
getWindowTitles(handle).ifPresent(titles -> lines.add("title: " + titles));
|
||||||
|
lines.add("pid: " + handle.pid());
|
||||||
|
handle.info().command().ifPresent(command -> lines.add("command: " + command));
|
||||||
|
getProcessArguments(handle).ifPresent(arguments -> lines.add("arguments: " + arguments));
|
||||||
|
handle.info().startInstant().ifPresent(instant -> lines.add("started at: " + instant));
|
||||||
|
handle.info().user().ifPresent(user -> lines.add("user: " + user));
|
||||||
|
handle.parent().ifPresent(parent -> lines.add("parent:\n" + printWithParents(parent, depth + 1)));
|
||||||
|
|
||||||
|
StringBuilder sj = new StringBuilder();
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
sj.append("\t".repeat(depth)).append("- ").append(line).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return sj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<String> getProcessArguments(ProcessHandle handle) {
|
||||||
|
if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle.info().arguments().map(arr -> {
|
||||||
|
String join = String.join(" ", arr);
|
||||||
|
|
||||||
|
if (join.isBlank()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return " " + join;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<String> getWindowTitles(ProcessHandle processHandle) {
|
||||||
|
if (processHandle.info().command().orElse("").equals(EXPLORER_COMMAND)) {
|
||||||
|
// Explorer is a single process, so the window titles are not useful
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> titles = LoomNativePlatform.getWindowTitlesForPid(processHandle.pid());
|
||||||
|
|
||||||
|
if (titles.isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
final StringJoiner joiner = new StringJoiner(", ");
|
||||||
|
|
||||||
|
for (String title : titles) {
|
||||||
|
joiner.add("'" + title + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(joiner.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@ public final class TinyRemapperHelper {
|
|||||||
MemoryMappingTree mappingTree = extension.getMappingConfiguration().getMappingsService(serviceManager, mappingOption).getMappingTree();
|
MemoryMappingTree mappingTree = extension.getMappingConfiguration().getMappingsService(serviceManager, mappingOption).getMappingTree();
|
||||||
|
|
||||||
if (fixRecords && !mappingTree.getSrcNamespace().equals(fromM)) {
|
if (fixRecords && !mappingTree.getSrcNamespace().equals(fromM)) {
|
||||||
throw new IllegalStateException("Mappings src namespace must match remap src namespace");
|
throw new IllegalStateException("Mappings src namespace must match remap src namespace, expected " + fromM + " but got " + mappingTree.getSrcNamespace());
|
||||||
}
|
}
|
||||||
|
|
||||||
int intermediaryNsId = mappingTree.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString());
|
int intermediaryNsId = mappingTree.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString());
|
||||||
|
|||||||
@@ -79,13 +79,13 @@ public class ZipUtils {
|
|||||||
|
|
||||||
public static void unpackAll(Path zip, Path output) throws IOException {
|
public static void unpackAll(Path zip, Path output) throws IOException {
|
||||||
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, false);
|
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, false);
|
||||||
Stream<Path> walk = Files.walk(fs.get().getPath("/"))) {
|
Stream<Path> walk = Files.walk(fs.getRoot())) {
|
||||||
Iterator<Path> iterator = walk.iterator();
|
Iterator<Path> iterator = walk.iterator();
|
||||||
|
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
Path fsPath = iterator.next();
|
Path fsPath = iterator.next();
|
||||||
if (!Files.isRegularFile(fsPath)) continue;
|
if (!Files.isRegularFile(fsPath)) continue;
|
||||||
Path dstPath = output.resolve(fs.get().getPath("/").relativize(fsPath).toString());
|
Path dstPath = output.resolve(fs.getRoot().relativize(fsPath).toString());
|
||||||
Path dstPathParent = dstPath.getParent();
|
Path dstPathParent = dstPath.getParent();
|
||||||
if (dstPathParent != null) Files.createDirectories(dstPathParent);
|
if (dstPathParent != null) Files.createDirectories(dstPathParent);
|
||||||
Files.copy(fsPath, dstPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
|
Files.copy(fsPath, dstPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
|
||||||
@@ -121,7 +121,7 @@ public class ZipUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T unpackJackson(Path zip, String path, Class<T> clazz) throws IOException {
|
public static <T> T unpackJson(Path zip, String path, Class<T> clazz) throws IOException {
|
||||||
final byte[] bytes = unpack(zip, path);
|
final byte[] bytes = unpack(zip, path);
|
||||||
return LoomGradlePlugin.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), clazz);
|
return LoomGradlePlugin.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), clazz);
|
||||||
}
|
}
|
||||||
@@ -215,6 +215,14 @@ public class ZipUtils {
|
|||||||
s -> LoomGradlePlugin.GSON.toJson(s, typeOfT).getBytes(StandardCharsets.UTF_8));
|
s -> LoomGradlePlugin.GSON.toJson(s, typeOfT).getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> void transformJson(Class<T> typeOfT, Path zip, String path, UnsafeUnaryOperator<T> transformer) throws IOException {
|
||||||
|
int transformed = transformJson(typeOfT, zip, Map.of(path, transformer));
|
||||||
|
|
||||||
|
if (transformed != 1) {
|
||||||
|
throw new IOException("Failed to transform " + path + " in " + zip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static int transform(Path zip, Collection<Pair<String, UnsafeUnaryOperator<byte[]>>> transforms) throws IOException {
|
public static int transform(Path zip, Collection<Pair<String, UnsafeUnaryOperator<byte[]>>> transforms) throws IOException {
|
||||||
return transform(zip, transforms.stream());
|
return transform(zip, transforms.stream());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,14 @@ import java.nio.file.Path;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
import dev.architectury.loom.metadata.ModMetadataFile;
|
import dev.architectury.loom.metadata.ModMetadataFile;
|
||||||
import dev.architectury.loom.metadata.ModMetadataFiles;
|
import dev.architectury.loom.metadata.ModMetadataFiles;
|
||||||
import org.gradle.api.tasks.SourceSet;
|
import org.gradle.api.tasks.SourceSet;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.annotations.VisibleForTesting;
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradlePlugin;
|
import net.fabricmc.loom.LoomGradlePlugin;
|
||||||
import net.fabricmc.loom.util.FileSystemUtil;
|
import net.fabricmc.loom.util.FileSystemUtil;
|
||||||
@@ -49,7 +52,9 @@ import net.fabricmc.loom.util.ZipUtils;
|
|||||||
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
||||||
|
|
||||||
public final class FabricModJsonFactory {
|
public final class FabricModJsonFactory {
|
||||||
private static final String FABRIC_MOD_JSON = "fabric.mod.json";
|
public static final String FABRIC_MOD_JSON = "fabric.mod.json";
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(FabricModJsonFactory.class);
|
||||||
|
|
||||||
private FabricModJsonFactory() {
|
private FabricModJsonFactory() {
|
||||||
}
|
}
|
||||||
@@ -158,6 +163,11 @@ public final class FabricModJsonFactory {
|
|||||||
|
|
||||||
try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
|
try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
|
||||||
return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.SourceSetSource(sourceSets));
|
return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.SourceSetSource(sourceSets));
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
LOGGER.warn("Failed to parse fabric.mod.json: {}", file.getAbsolutePath());
|
||||||
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException("Failed to read " + file.getAbsolutePath(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,14 @@
|
|||||||
package net.fabricmc.loom.util.fmj;
|
package net.fabricmc.loom.util.fmj;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonPrimitive;
|
import com.google.gson.JsonPrimitive;
|
||||||
|
|
||||||
final class FabricModJsonUtils {
|
public final class FabricModJsonUtils {
|
||||||
private FabricModJsonUtils() {
|
private FabricModJsonUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +50,30 @@ final class FabricModJsonUtils {
|
|||||||
return element.getAsInt();
|
return element.getAsInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that the schemaVersion json entry, is first in the json file
|
||||||
|
// This exercises an optimisation here: https://github.com/FabricMC/fabric-loader/blob/d69cb72d26497e3f387cf46f9b24340b402a4644/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java#L62
|
||||||
|
public static JsonObject optimizeFmj(JsonObject json) {
|
||||||
|
if (!json.has("schemaVersion")) {
|
||||||
|
// No schemaVersion, something will explode later?!
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new json object with the schemaVersion first
|
||||||
|
var out = new JsonObject();
|
||||||
|
out.add("schemaVersion", json.get("schemaVersion"));
|
||||||
|
|
||||||
|
for (Map.Entry<String, JsonElement> entry : json.entrySet()) {
|
||||||
|
if (entry.getKey().equals("schemaVersion")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all other entries
|
||||||
|
out.add(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
private static JsonElement getElement(JsonObject jsonObject, String key) {
|
private static JsonElement getElement(JsonObject jsonObject, String key) {
|
||||||
final JsonElement element = jsonObject.get(key);
|
final JsonElement element = jsonObject.get(key);
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import java.util.List;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradlePlugin;
|
import net.fabricmc.loom.LoomGradlePlugin;
|
||||||
import net.fabricmc.loom.kotlin.remapping.JvmExtensionWrapper;
|
|
||||||
import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtensionImpl;
|
import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtensionImpl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,8 +61,7 @@ public class KotlinRemapperClassloader extends URLClassLoader {
|
|||||||
public static KotlinRemapperClassloader create(KotlinClasspath classpathProvider) {
|
public static KotlinRemapperClassloader create(KotlinClasspath classpathProvider) {
|
||||||
// Include the libraries that are not on the kotlin classpath.
|
// Include the libraries that are not on the kotlin classpath.
|
||||||
final Stream<URL> loomUrls = getClassUrls(
|
final Stream<URL> loomUrls = getClassUrls(
|
||||||
KotlinMetadataTinyRemapperExtensionImpl.class, // Loom (Kotlin)
|
KotlinMetadataTinyRemapperExtensionImpl.class // Loom (Kotlin)
|
||||||
JvmExtensionWrapper.class // Loom (Java)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final URL[] urls = Stream.concat(
|
final URL[] urls = Stream.concat(
|
||||||
|
|||||||
@@ -40,18 +40,17 @@ import kotlinx.metadata.KmTypeAlias
|
|||||||
import kotlinx.metadata.KmTypeParameter
|
import kotlinx.metadata.KmTypeParameter
|
||||||
import kotlinx.metadata.KmTypeProjection
|
import kotlinx.metadata.KmTypeProjection
|
||||||
import kotlinx.metadata.KmValueParameter
|
import kotlinx.metadata.KmValueParameter
|
||||||
import kotlinx.metadata.internal.extensions.KmClassExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmConstructorExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmFunctionExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmPackageExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmPropertyExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmTypeAliasExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmTypeExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmTypeParameterExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmValueParameterExtension
|
|
||||||
import kotlinx.metadata.isLocalClassName
|
import kotlinx.metadata.isLocalClassName
|
||||||
import kotlinx.metadata.jvm.JvmFieldSignature
|
import kotlinx.metadata.jvm.JvmFieldSignature
|
||||||
import kotlinx.metadata.jvm.JvmMethodSignature
|
import kotlinx.metadata.jvm.JvmMethodSignature
|
||||||
|
import kotlinx.metadata.jvm.annotations
|
||||||
|
import kotlinx.metadata.jvm.fieldSignature
|
||||||
|
import kotlinx.metadata.jvm.getterSignature
|
||||||
|
import kotlinx.metadata.jvm.localDelegatedProperties
|
||||||
|
import kotlinx.metadata.jvm.setterSignature
|
||||||
|
import kotlinx.metadata.jvm.signature
|
||||||
|
import kotlinx.metadata.jvm.syntheticMethodForAnnotations
|
||||||
|
import kotlinx.metadata.jvm.syntheticMethodForDelegate
|
||||||
import kotlinx.metadata.jvm.toJvmInternalName
|
import kotlinx.metadata.jvm.toJvmInternalName
|
||||||
import org.objectweb.asm.commons.Remapper
|
import org.objectweb.asm.commons.Remapper
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ class KotlinClassRemapper(private val remapper: Remapper) {
|
|||||||
clazz.nestedClasses.replaceAll(this::remap)
|
clazz.nestedClasses.replaceAll(this::remap)
|
||||||
clazz.sealedSubclasses.replaceAll(this::remap)
|
clazz.sealedSubclasses.replaceAll(this::remap)
|
||||||
clazz.contextReceiverTypes.replaceAll(this::remap)
|
clazz.contextReceiverTypes.replaceAll(this::remap)
|
||||||
clazz.getExtensions().replaceAll(this::remap)
|
clazz.localDelegatedProperties.replaceAll(this::remap)
|
||||||
return clazz
|
return clazz
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +80,7 @@ class KotlinClassRemapper(private val remapper: Remapper) {
|
|||||||
pkg.functions.replaceAll(this::remap)
|
pkg.functions.replaceAll(this::remap)
|
||||||
pkg.properties.replaceAll(this::remap)
|
pkg.properties.replaceAll(this::remap)
|
||||||
pkg.typeAliases.replaceAll(this::remap)
|
pkg.typeAliases.replaceAll(this::remap)
|
||||||
pkg.getExtensions().replaceAll(this::remap)
|
pkg.localDelegatedProperties.replaceAll(this::remap)
|
||||||
return pkg
|
return pkg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +106,7 @@ class KotlinClassRemapper(private val remapper: Remapper) {
|
|||||||
type.abbreviatedType = type.abbreviatedType?.let { remap(it) }
|
type.abbreviatedType = type.abbreviatedType?.let { remap(it) }
|
||||||
type.outerType = type.outerType?.let { remap(it) }
|
type.outerType = type.outerType?.let { remap(it) }
|
||||||
type.flexibleTypeUpperBound = type.flexibleTypeUpperBound?.let { remap(it) }
|
type.flexibleTypeUpperBound = type.flexibleTypeUpperBound?.let { remap(it) }
|
||||||
type.getExtensions().replaceAll(this::remap)
|
type.annotations.replaceAll(this::remap)
|
||||||
return type
|
return type
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +116,7 @@ class KotlinClassRemapper(private val remapper: Remapper) {
|
|||||||
function.contextReceiverTypes.replaceAll(this::remap)
|
function.contextReceiverTypes.replaceAll(this::remap)
|
||||||
function.valueParameters.replaceAll(this::remap)
|
function.valueParameters.replaceAll(this::remap)
|
||||||
function.returnType = remap(function.returnType)
|
function.returnType = remap(function.returnType)
|
||||||
function.getExtensions().replaceAll(this::remap)
|
function.signature = function.signature?.let { remap(it) }
|
||||||
return function
|
return function
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +126,11 @@ class KotlinClassRemapper(private val remapper: Remapper) {
|
|||||||
property.contextReceiverTypes.replaceAll(this::remap)
|
property.contextReceiverTypes.replaceAll(this::remap)
|
||||||
property.setterParameter = property.setterParameter?.let { remap(it) }
|
property.setterParameter = property.setterParameter?.let { remap(it) }
|
||||||
property.returnType = remap(property.returnType)
|
property.returnType = remap(property.returnType)
|
||||||
property.getExtensions().replaceAll(this::remap)
|
property.fieldSignature = property.fieldSignature?.let { remap(it) }
|
||||||
|
property.getterSignature = property.getterSignature?.let { remap(it) }
|
||||||
|
property.setterSignature = property.setterSignature?.let { remap(it) }
|
||||||
|
property.syntheticMethodForAnnotations = property.syntheticMethodForAnnotations?.let { remap(it) }
|
||||||
|
property.syntheticMethodForDelegate = property.syntheticMethodForDelegate?.let { remap(it) }
|
||||||
return property
|
return property
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,19 +139,18 @@ class KotlinClassRemapper(private val remapper: Remapper) {
|
|||||||
typeAlias.underlyingType = remap(typeAlias.underlyingType)
|
typeAlias.underlyingType = remap(typeAlias.underlyingType)
|
||||||
typeAlias.expandedType = remap(typeAlias.expandedType)
|
typeAlias.expandedType = remap(typeAlias.expandedType)
|
||||||
typeAlias.annotations.replaceAll(this::remap)
|
typeAlias.annotations.replaceAll(this::remap)
|
||||||
typeAlias.getExtensions().replaceAll(this::remap)
|
|
||||||
return typeAlias
|
return typeAlias
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun remap(constructor: KmConstructor): KmConstructor {
|
private fun remap(constructor: KmConstructor): KmConstructor {
|
||||||
constructor.valueParameters.replaceAll(this::remap)
|
constructor.valueParameters.replaceAll(this::remap)
|
||||||
constructor.getExtensions().replaceAll(this::remap)
|
constructor.signature = constructor.signature?.let { remap(it) }
|
||||||
return constructor
|
return constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun remap(typeParameter: KmTypeParameter): KmTypeParameter {
|
private fun remap(typeParameter: KmTypeParameter): KmTypeParameter {
|
||||||
typeParameter.upperBounds.replaceAll(this::remap)
|
typeParameter.upperBounds.replaceAll(this::remap)
|
||||||
typeParameter.getExtensions().replaceAll(this::remap)
|
typeParameter.annotations.replaceAll(this::remap)
|
||||||
return typeParameter
|
return typeParameter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +165,6 @@ class KotlinClassRemapper(private val remapper: Remapper) {
|
|||||||
private fun remap(valueParameter: KmValueParameter): KmValueParameter {
|
private fun remap(valueParameter: KmValueParameter): KmValueParameter {
|
||||||
valueParameter.type = remap(valueParameter.type)
|
valueParameter.type = remap(valueParameter.type)
|
||||||
valueParameter.varargElementType = valueParameter.varargElementType?.let { remap(it) }
|
valueParameter.varargElementType = valueParameter.varargElementType?.let { remap(it) }
|
||||||
valueParameter.getExtensions().replaceAll(this::remap)
|
|
||||||
return valueParameter
|
return valueParameter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,76 +172,6 @@ class KotlinClassRemapper(private val remapper: Remapper) {
|
|||||||
return KmAnnotation(remap(annotation.className), annotation.arguments)
|
return KmAnnotation(remap(annotation.className), annotation.arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun remap(classExtension: KmClassExtension): KmClassExtension {
|
|
||||||
JvmExtensionWrapper.Class.get(classExtension)?.let {
|
|
||||||
it.localDelegatedProperties.replaceAll(this::remap)
|
|
||||||
return it.extension
|
|
||||||
}
|
|
||||||
|
|
||||||
return classExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remap(packageExtension: KmPackageExtension): KmPackageExtension {
|
|
||||||
JvmExtensionWrapper.Package.get(packageExtension)?.let {
|
|
||||||
it.localDelegatedProperties.replaceAll(this::remap)
|
|
||||||
return it.extension
|
|
||||||
}
|
|
||||||
|
|
||||||
return packageExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remap(typeExtension: KmTypeExtension): KmTypeExtension {
|
|
||||||
JvmExtensionWrapper.Type.get(typeExtension)?.let {
|
|
||||||
it.annotations.replaceAll(this::remap)
|
|
||||||
return it.extension
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remap(functionExtension: KmFunctionExtension): KmFunctionExtension {
|
|
||||||
JvmExtensionWrapper.Function.get(functionExtension)?.let {
|
|
||||||
it.signature = it.signature?.let { sig -> remap(sig) }
|
|
||||||
return it.extension
|
|
||||||
}
|
|
||||||
|
|
||||||
return functionExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remap(propertyExtension: KmPropertyExtension): KmPropertyExtension {
|
|
||||||
JvmExtensionWrapper.Property.get(propertyExtension)?.let {
|
|
||||||
it.fieldSignature = it.fieldSignature?.let { sig -> remap(sig) }
|
|
||||||
it.getterSignature = it.getterSignature?.let { sig -> remap(sig) }
|
|
||||||
it.setterSignature = it.setterSignature?.let { sig -> remap(sig) }
|
|
||||||
it.syntheticMethodForAnnotations = it.syntheticMethodForAnnotations?.let { sig -> remap(sig) }
|
|
||||||
it.syntheticMethodForDelegate = it.syntheticMethodForDelegate?.let { sig -> remap(sig) }
|
|
||||||
return it.extension
|
|
||||||
}
|
|
||||||
|
|
||||||
return propertyExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remap(typeAliasExtension: KmTypeAliasExtension): KmTypeAliasExtension {
|
|
||||||
return typeAliasExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remap(typeParameterExtension: KmTypeParameterExtension): KmTypeParameterExtension {
|
|
||||||
return typeParameterExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remap(valueParameterExtension: KmValueParameterExtension): KmValueParameterExtension {
|
|
||||||
return valueParameterExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remap(constructorExtension: KmConstructorExtension): KmConstructorExtension {
|
|
||||||
JvmExtensionWrapper.Constructor.get(constructorExtension)?.let {
|
|
||||||
it.signature = it.signature?.let { sig -> remap(sig) }
|
|
||||||
return it.extension
|
|
||||||
}
|
|
||||||
|
|
||||||
return constructorExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remap(signature: JvmMethodSignature): JvmMethodSignature {
|
private fun remap(signature: JvmMethodSignature): JvmMethodSignature {
|
||||||
return JvmMethodSignature(signature.name, remapper.mapMethodDesc(signature.descriptor))
|
return JvmMethodSignature(signature.name, remapper.mapMethodDesc(signature.descriptor))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@file:Suppress("UNCHECKED_CAST")
|
|
||||||
|
|
||||||
package net.fabricmc.loom.kotlin.remapping
|
|
||||||
|
|
||||||
import kotlinx.metadata.KmClass
|
|
||||||
import kotlinx.metadata.KmConstructor
|
|
||||||
import kotlinx.metadata.KmFunction
|
|
||||||
import kotlinx.metadata.KmPackage
|
|
||||||
import kotlinx.metadata.KmProperty
|
|
||||||
import kotlinx.metadata.KmType
|
|
||||||
import kotlinx.metadata.KmTypeAlias
|
|
||||||
import kotlinx.metadata.KmTypeParameter
|
|
||||||
import kotlinx.metadata.KmValueParameter
|
|
||||||
import kotlinx.metadata.internal.extensions.KmClassExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmConstructorExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmFunctionExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmPackageExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmPropertyExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmTypeAliasExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmTypeExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmTypeParameterExtension
|
|
||||||
import kotlinx.metadata.internal.extensions.KmValueParameterExtension
|
|
||||||
import java.lang.reflect.Field
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
val KM_CLASS_EXTENSIONS = getField(KmClass::class)
|
|
||||||
val KM_PACKAGE_EXTENSIONS = getField(KmPackage::class)
|
|
||||||
val KM_TYPE_EXTENSIONS = getField(KmType::class)
|
|
||||||
val KM_FUNCTION_EXTENSIONS = getField(KmFunction::class)
|
|
||||||
val KM_PROPERTY_EXTENSIONS = getField(KmProperty::class)
|
|
||||||
val KM_TYPE_ALIAS_EXTENSIONS = getField(KmTypeAlias::class)
|
|
||||||
val KM_TYPE_PARAMETER_EXTENSIONS = getField(KmTypeParameter::class)
|
|
||||||
val KM_VALUE_PARAMETER_EXTENSIONS = getField(KmValueParameter::class)
|
|
||||||
val KM_CONSTRUCTOR_EXTENSIONS = getField(KmConstructor::class)
|
|
||||||
|
|
||||||
fun KmClass.getExtensions(): MutableList<KmClassExtension> {
|
|
||||||
return KM_CLASS_EXTENSIONS.get(this) as MutableList<KmClassExtension>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KmPackage.getExtensions(): MutableList<KmPackageExtension> {
|
|
||||||
return KM_PACKAGE_EXTENSIONS.get(this) as MutableList<KmPackageExtension>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KmType.getExtensions(): MutableList<KmTypeExtension> {
|
|
||||||
return KM_TYPE_EXTENSIONS.get(this) as MutableList<KmTypeExtension>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KmFunction.getExtensions(): MutableList<KmFunctionExtension> {
|
|
||||||
return KM_FUNCTION_EXTENSIONS.get(this) as MutableList<KmFunctionExtension>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KmProperty.getExtensions(): MutableList<KmPropertyExtension> {
|
|
||||||
return KM_PROPERTY_EXTENSIONS.get(this) as MutableList<KmPropertyExtension>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KmTypeAlias.getExtensions(): MutableList<KmTypeAliasExtension> {
|
|
||||||
return KM_TYPE_ALIAS_EXTENSIONS.get(this) as MutableList<KmTypeAliasExtension>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KmTypeParameter.getExtensions(): MutableList<KmTypeParameterExtension> {
|
|
||||||
return KM_TYPE_PARAMETER_EXTENSIONS.get(this) as MutableList<KmTypeParameterExtension>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KmValueParameter.getExtensions(): MutableList<KmValueParameterExtension> {
|
|
||||||
return KM_VALUE_PARAMETER_EXTENSIONS.get(this) as MutableList<KmValueParameterExtension>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KmConstructor.getExtensions(): MutableList<KmConstructorExtension> {
|
|
||||||
return KM_CONSTRUCTOR_EXTENSIONS.get(this) as MutableList<KmConstructorExtension>
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getField(clazz: KClass<*>): Field {
|
|
||||||
val field = clazz.java.getDeclaredField("extensions")
|
|
||||||
field.isAccessible = true
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
@@ -57,7 +57,11 @@ class KotlinMetadataRemappingClassVisitor(private val remapper: Remapper, next:
|
|||||||
var result: AnnotationVisitor? = super.visitAnnotation(descriptor, visible)
|
var result: AnnotationVisitor? = super.visitAnnotation(descriptor, visible)
|
||||||
|
|
||||||
if (descriptor == ANNOTATION_DESCRIPTOR && result != null) {
|
if (descriptor == ANNOTATION_DESCRIPTOR && result != null) {
|
||||||
result = KotlinClassMetadataRemappingAnnotationVisitor(remapper, result, className)
|
try {
|
||||||
|
result = KotlinClassMetadataRemappingAnnotationVisitor(remapper, result, className)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Failed to remap Kotlin metadata annotation in class $className", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -88,12 +88,15 @@ class DataGenerationTest extends Specification implements GradleProjectTestTrait
|
|||||||
|
|
||||||
modDatagenImplementation fabricApi.module("fabric-data-generation-api-v1", "0.90.0+1.20.2")
|
modDatagenImplementation fabricApi.module("fabric-data-generation-api-v1", "0.90.0+1.20.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println("%%" + loom.runs.datagen.configName + "%%")
|
||||||
'''
|
'''
|
||||||
when:
|
when:
|
||||||
def result = gradle.run(task: "runDatagen")
|
def result = gradle.run(task: "runDatagen")
|
||||||
|
|
||||||
then:
|
then:
|
||||||
result.task(":runDatagen").outcome == SUCCESS
|
result.task(":runDatagen").outcome == SUCCESS
|
||||||
|
result.output.contains("%%Data Generation%%")
|
||||||
|
|
||||||
where:
|
where:
|
||||||
version << STANDARD_TEST_VERSIONS
|
version << STANDARD_TEST_VERSIONS
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class DebugLineNumbersTest extends Specification implements GradleProjectTestTra
|
|||||||
'''
|
'''
|
||||||
when:
|
when:
|
||||||
// First generate sources
|
// First generate sources
|
||||||
def genSources = gradle.run(task: "genSources")
|
def genSources = gradle.run(task: "genSources", args: ["--info"])
|
||||||
genSources.task(":genSources").outcome == SUCCESS
|
genSources.task(":genSources").outcome == SUCCESS
|
||||||
|
|
||||||
// Print out the source of the file
|
// Print out the source of the file
|
||||||
|
|||||||
@@ -74,4 +74,36 @@ class DecompileTest extends Specification implements GradleProjectTestTrait {
|
|||||||
where:
|
where:
|
||||||
version << STANDARD_TEST_VERSIONS
|
version << STANDARD_TEST_VERSIONS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "decompile cache"() {
|
||||||
|
setup:
|
||||||
|
def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE, gradleHomeDir: File.createTempDir())
|
||||||
|
gradle.buildSrc("decompile")
|
||||||
|
gradle.buildGradle << '''
|
||||||
|
dependencies {
|
||||||
|
minecraft "com.mojang:minecraft:1.20.4"
|
||||||
|
mappings "net.fabricmc:yarn:1.20.4+build.3:v2"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = gradle.run(tasks: ["genSourcesWithVineflower"], args: ["--use-cache", "--info"])
|
||||||
|
|
||||||
|
// Add fabric API to the project, this introduces some transitive access wideners
|
||||||
|
gradle.buildGradle << '''
|
||||||
|
dependencies {
|
||||||
|
modImplementation "net.fabricmc.fabric-api:fabric-api:0.96.4+1.20.4"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
def result2 = gradle.run(tasks: ["genSourcesWithVineflower"], args: ["--use-cache", "--info"])
|
||||||
|
|
||||||
|
// And run again, with no changes
|
||||||
|
def result3 = gradle.run(tasks: ["genSourcesWithVineflower"], args: ["--use-cache", "--info"])
|
||||||
|
|
||||||
|
then:
|
||||||
|
result.task(":genSourcesWithVineflower").outcome == SUCCESS
|
||||||
|
result2.task(":genSourcesWithVineflower").outcome == SUCCESS
|
||||||
|
result3.task(":genSourcesWithVineflower").outcome == SUCCESS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
|
|
||||||
package net.fabricmc.loom.test.integration
|
package net.fabricmc.loom.test.integration
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
@@ -118,4 +120,31 @@ class LegacyProjectTest extends Specification implements GradleProjectTestTrait
|
|||||||
'b1.8.1' | _
|
'b1.8.1' | _
|
||||||
'a1.2.5' | _
|
'a1.2.5' | _
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Unroll
|
||||||
|
def "Legacy merged"() {
|
||||||
|
setup:
|
||||||
|
def mappings = Path.of("src/test/resources/mappings/1.2.5-intermediary.tiny.zip").toAbsolutePath()
|
||||||
|
def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE)
|
||||||
|
|
||||||
|
gradle.buildGradle << """
|
||||||
|
dependencies {
|
||||||
|
minecraft "com.mojang:minecraft:1.2.5"
|
||||||
|
mappings loom.layered() {
|
||||||
|
// No names
|
||||||
|
}
|
||||||
|
|
||||||
|
modImplementation "net.fabricmc:fabric-loader:0.15.7"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
gradle.buildSrc("legacyMergedIntermediary")
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = gradle.run(task: "build", args: [
|
||||||
|
"-Ploom.test.legacyMergedIntermediary.mappingPath=${mappings}"
|
||||||
|
])
|
||||||
|
|
||||||
|
then:
|
||||||
|
result.task(":build").outcome == SUCCESS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.test.integration
|
||||||
|
|
||||||
|
import spock.lang.Specification
|
||||||
|
import spock.lang.Unroll
|
||||||
|
|
||||||
|
import net.fabricmc.loom.build.nesting.IncludedJarFactory
|
||||||
|
import net.fabricmc.loom.test.util.GradleProjectTestTrait
|
||||||
|
|
||||||
|
class SemVerParsingTest extends Specification implements GradleProjectTestTrait {
|
||||||
|
@Unroll
|
||||||
|
def "test valid Semantic Versioning strings"() {
|
||||||
|
given:
|
||||||
|
IncludedJarFactory includedJarFactory = new IncludedJarFactory(null)
|
||||||
|
|
||||||
|
expect:
|
||||||
|
includedJarFactory.validSemVer(version) == true
|
||||||
|
|
||||||
|
where:
|
||||||
|
version | _
|
||||||
|
"1.0.0" | _
|
||||||
|
"2.5.3" | _
|
||||||
|
"3.0.0-beta.2" | _
|
||||||
|
"4.2.1-alpha+001" | _
|
||||||
|
"5.0.0-rc.1+build.1" | _
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unroll
|
||||||
|
def "test non-Semantic Versioning strings"() {
|
||||||
|
given:
|
||||||
|
IncludedJarFactory includedJarFactory = new IncludedJarFactory(null)
|
||||||
|
|
||||||
|
expect:
|
||||||
|
includedJarFactory.validSemVer(version) == false
|
||||||
|
|
||||||
|
where:
|
||||||
|
version | _
|
||||||
|
"1.0" | _
|
||||||
|
"3.0.0.Beta1-120922-126" | _
|
||||||
|
"3.0.2.Final" | _
|
||||||
|
"4.2.1.4.RELEASE" | _
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unroll
|
||||||
|
def "test '.Final' suffixed SemVer"() {
|
||||||
|
given:
|
||||||
|
IncludedJarFactory includedJarFactory = new IncludedJarFactory(null)
|
||||||
|
|
||||||
|
expect:
|
||||||
|
includedJarFactory.getVersion(metadata) == expectedVersion
|
||||||
|
|
||||||
|
where:
|
||||||
|
metadata | expectedVersion
|
||||||
|
new IncludedJarFactory.Metadata("group", "name", "1.0.0.Final", null) | "1.0.0"
|
||||||
|
new IncludedJarFactory.Metadata("group", "name", "2.5.3.final", null) | "2.5.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,8 @@ package net.fabricmc.loom.test.integration.buildSrc.decompile
|
|||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
import com.google.common.io.Files
|
||||||
|
|
||||||
import net.fabricmc.loom.api.decompilers.DecompilationMetadata
|
import net.fabricmc.loom.api.decompilers.DecompilationMetadata
|
||||||
import net.fabricmc.loom.api.decompilers.LoomDecompiler
|
import net.fabricmc.loom.api.decompilers.LoomDecompiler
|
||||||
|
|
||||||
@@ -33,5 +35,6 @@ class CustomDecompiler implements LoomDecompiler {
|
|||||||
@Override
|
@Override
|
||||||
void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
|
void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
|
||||||
println("Running custom decompiler")
|
println("Running custom decompiler")
|
||||||
|
Files.touch(sourcesDestination.toFile())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.test.integration.buildSrc.legacyMergedIntermediary
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
|
||||||
|
import net.fabricmc.loom.api.LoomGradleExtensionAPI
|
||||||
|
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider
|
||||||
|
import net.fabricmc.loom.util.ZipUtils
|
||||||
|
|
||||||
|
class TestPlugin implements Plugin<Project> {
|
||||||
|
@Override
|
||||||
|
void apply(Project project) {
|
||||||
|
LoomGradleExtensionAPI extension = project.getExtensions().getByName("loom")
|
||||||
|
extension.setIntermediateMappingsProvider(LegacyIntermediaryProvider.class) {
|
||||||
|
mappingPath.set(project.property("loom.test.legacyMergedIntermediary.mappingPath"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract static class LegacyIntermediaryProvider extends IntermediateMappingsProvider {
|
||||||
|
final String name = "legacyMerged"
|
||||||
|
|
||||||
|
abstract Property<String> getMappingPath();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void provide(Path tinyMappings) throws IOException {
|
||||||
|
if (getMinecraftVersion().get() != "1.2.5") {
|
||||||
|
throw new IllegalStateException("This plugin only supports Minecraft 1.2.5")
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data = ZipUtils.unpack(Paths.get(getMappingPath().get()), "1.2.5-intermediary.tiny")
|
||||||
|
Files.write(tinyMappings, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.test.unit
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import net.fabricmc.loom.test.util.ZipTestUtils
|
||||||
|
import net.fabricmc.loom.util.AsyncZipProcessor
|
||||||
|
import net.fabricmc.loom.util.ZipUtils
|
||||||
|
|
||||||
|
class AsyncZipProcessorTest extends Specification {
|
||||||
|
def "process async"() {
|
||||||
|
given:
|
||||||
|
def inputZip = ZipTestUtils.createZip(createEntries())
|
||||||
|
def outputZip = ZipTestUtils.createZip(Collections.emptyMap())
|
||||||
|
Files.delete(outputZip)
|
||||||
|
|
||||||
|
when:
|
||||||
|
// Process the input zip asynchronously, converting all entries to uppercase
|
||||||
|
AsyncZipProcessor.processEntries(inputZip, outputZip) { Path inputEntry, Path outputEntry ->
|
||||||
|
def str = Files.readString(inputEntry)
|
||||||
|
Files.writeString(outputEntry, str.toUpperCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
then:
|
||||||
|
ZipUtils.unpack(outputZip, "file1.txt") == "FILE1".bytes
|
||||||
|
ZipUtils.unpack(outputZip, "file500.txt") == "FILE500".bytes
|
||||||
|
ZipUtils.unpack(outputZip, "file800.txt") == "FILE800".bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
def "re throws"() {
|
||||||
|
given:
|
||||||
|
def inputZip = ZipTestUtils.createZip(createEntries())
|
||||||
|
def outputZip = ZipTestUtils.createZip(Collections.emptyMap())
|
||||||
|
Files.delete(outputZip)
|
||||||
|
|
||||||
|
when:
|
||||||
|
AsyncZipProcessor.processEntries(inputZip, outputZip) { Path inputEntry, Path outputEntry ->
|
||||||
|
throw new IOException("Test exception")
|
||||||
|
}
|
||||||
|
|
||||||
|
then:
|
||||||
|
thrown(IOException)
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> createEntries(int count = 10000) {
|
||||||
|
Map<String, String> entries = [:]
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
entries.put("file" + i + ".txt", "file$i")
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.test.unit
|
||||||
|
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import net.fabricmc.loom.decompilers.ClassLineNumbers
|
||||||
|
|
||||||
|
class ClassLineNumbersTest extends Specification {
|
||||||
|
def "read linemap"() {
|
||||||
|
when:
|
||||||
|
def reader = new BufferedReader(new StringReader(LINE_MAP))
|
||||||
|
def lineNumbers = ClassLineNumbers.readMappings(reader)
|
||||||
|
def lineMap = lineNumbers.lineMap()
|
||||||
|
|
||||||
|
then:
|
||||||
|
lineMap.size() == 2
|
||||||
|
lineMap["net/minecraft/server/dedicated/ServerPropertiesHandler"].lineMap().size() == 39
|
||||||
|
lineMap["net/minecraft/server/dedicated/ServerPropertiesHandler"].maxLine() == 203
|
||||||
|
lineMap["net/minecraft/server/dedicated/ServerPropertiesHandler"].maxLineDest() == 187
|
||||||
|
|
||||||
|
lineMap["net/minecraft/server/dedicated/ServerPropertiesLoader"].lineMap().size() == 6
|
||||||
|
lineMap["net/minecraft/server/dedicated/ServerPropertiesLoader"].maxLine() == 25
|
||||||
|
lineMap["net/minecraft/server/dedicated/ServerPropertiesLoader"].maxLineDest() == 30
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String LINE_MAP = """
|
||||||
|
net/minecraft/server/dedicated/ServerPropertiesHandler\t203\t187
|
||||||
|
\t48\t187
|
||||||
|
\t91\t92
|
||||||
|
\t96\t97
|
||||||
|
\t110\t108
|
||||||
|
\t112\t109
|
||||||
|
\t113\t110
|
||||||
|
\t115\t111
|
||||||
|
\t116\t112
|
||||||
|
\t118\t113
|
||||||
|
\t119\t113
|
||||||
|
\t120\t113
|
||||||
|
\t122\t114
|
||||||
|
\t130\t115
|
||||||
|
\t147\t129
|
||||||
|
\t149\t131
|
||||||
|
\t151\t133
|
||||||
|
\t154\t136
|
||||||
|
\t158\t141
|
||||||
|
\t159\t142
|
||||||
|
\t163\t144
|
||||||
|
\t164\t145
|
||||||
|
\t165\t146
|
||||||
|
\t166\t147
|
||||||
|
\t168\t149
|
||||||
|
\t169\t150
|
||||||
|
\t170\t151
|
||||||
|
\t172\t153
|
||||||
|
\t175\t155
|
||||||
|
\t176\t156
|
||||||
|
\t177\t157
|
||||||
|
\t178\t158
|
||||||
|
\t181\t160
|
||||||
|
\t186\t165
|
||||||
|
\t187\t166
|
||||||
|
\t192\t171
|
||||||
|
\t194\t173
|
||||||
|
\t195\t174
|
||||||
|
\t197\t176
|
||||||
|
\t203\t182
|
||||||
|
|
||||||
|
net/minecraft/server/dedicated/ServerPropertiesLoader\t25\t30
|
||||||
|
\t11\t15
|
||||||
|
\t12\t16
|
||||||
|
\t16\t20
|
||||||
|
\t20\t24
|
||||||
|
\t24\t28
|
||||||
|
\t25\t30
|
||||||
|
"""
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.test.unit
|
||||||
|
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import net.fabricmc.loom.decompilers.cache.JarWalker
|
||||||
|
import net.fabricmc.loom.test.util.ZipTestUtils
|
||||||
|
import net.fabricmc.loom.util.FileSystemUtil
|
||||||
|
|
||||||
|
class JarWalkerTest extends Specification {
|
||||||
|
def "find classes in jar"() {
|
||||||
|
given:
|
||||||
|
def jar = ZipTestUtils.createZip([
|
||||||
|
"net/fabricmc/Test.class": "",
|
||||||
|
"net/fabricmc/other/Test.class": "",
|
||||||
|
"net/fabricmc/other/Test\$Inner.class": "",
|
||||||
|
"net/fabricmc/other/Test\$1.class": "",
|
||||||
|
])
|
||||||
|
when:
|
||||||
|
def entries = JarWalker.findClasses(jar)
|
||||||
|
then:
|
||||||
|
entries.size() == 2
|
||||||
|
|
||||||
|
entries[0].parentClass() == "net/fabricmc/Test.class"
|
||||||
|
entries[0].sourcesFileName() == "net/fabricmc/Test.java"
|
||||||
|
entries[0].innerClasses().size() == 0
|
||||||
|
|
||||||
|
entries[1].parentClass() == "net/fabricmc/other/Test.class"
|
||||||
|
entries[1].sourcesFileName() == "net/fabricmc/other/Test.java"
|
||||||
|
entries[1].innerClasses().size() == 2
|
||||||
|
entries[1].innerClasses()[0] == "net/fabricmc/other/Test\$1.class"
|
||||||
|
entries[1].innerClasses()[1] == "net/fabricmc/other/Test\$Inner.class"
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Hash Classes"() {
|
||||||
|
given:
|
||||||
|
def jar = ZipTestUtils.createZip(zipEntries)
|
||||||
|
when:
|
||||||
|
def entries = JarWalker.findClasses(jar)
|
||||||
|
def hash = FileSystemUtil.getJarFileSystem(jar).withCloseable { fs ->
|
||||||
|
return entries[0].hash(fs.root)
|
||||||
|
}
|
||||||
|
then:
|
||||||
|
entries.size() == 1
|
||||||
|
hash == expectedHash
|
||||||
|
where:
|
||||||
|
expectedHash | zipEntries
|
||||||
|
"2339de144d8a4a1198adf8142b6d3421ec0baacea13c9ade42a93071b6d62e43" | [
|
||||||
|
"net/fabricmc/Test.class": "abc123",
|
||||||
|
]
|
||||||
|
"1053cfadf4e371ec89ff5b58d9b3bdb80373f3179e804b2e241171223709f4d1" | [
|
||||||
|
"net/fabricmc/other/Test.class": "Hello",
|
||||||
|
"net/fabricmc/other/Test\$Inner.class": "World",
|
||||||
|
"net/fabricmc/other/Test\$Inner\$2.class": "123",
|
||||||
|
"net/fabricmc/other/Test\$1.class": "test",
|
||||||
|
]
|
||||||
|
"f30b705f3a921b60103a4ee9951aff59b6db87cc289ba24563743d753acff433" | [
|
||||||
|
"net/fabricmc/other/Test.class": "Hello",
|
||||||
|
"net/fabricmc/other/Test\$Inner.class": "World",
|
||||||
|
"net/fabricmc/other/Test\$Inner\$2.class": "abc123",
|
||||||
|
"net/fabricmc/other/Test\$1.class": "test",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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.test.unit
|
||||||
|
|
||||||
|
import org.gradle.api.logging.LogLevel
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.ProcessUtil
|
||||||
|
|
||||||
|
class ProcessUtilTest extends Specification {
|
||||||
|
def "print process info"() {
|
||||||
|
when:
|
||||||
|
def output = new ProcessUtil(LogLevel.DEBUG).printWithParents(ProcessHandle.current())
|
||||||
|
|
||||||
|
then:
|
||||||
|
// Just a simple check to see if the output is not empty
|
||||||
|
!output.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets
|
|||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
import net.fabricmc.loom.util.Checksum
|
import net.fabricmc.loom.util.Checksum
|
||||||
@@ -188,4 +189,28 @@ class ZipUtilsTest extends Specification {
|
|||||||
"Etc/GMT-6" | _
|
"Etc/GMT-6" | _
|
||||||
"Etc/GMT+9" | _
|
"Etc/GMT+9" | _
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "transform json"() {
|
||||||
|
given:
|
||||||
|
def dir = File.createTempDir()
|
||||||
|
def zip = File.createTempFile("loom-zip-test", ".zip").toPath()
|
||||||
|
new File(dir, "test.json").text = """
|
||||||
|
{
|
||||||
|
"test": "This is a test of transforming"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
ZipUtils.pack(dir.toPath(), zip)
|
||||||
|
|
||||||
|
when:
|
||||||
|
ZipUtils.transformJson(JsonObject.class, zip, "test.json") { json ->
|
||||||
|
def test = json.get("test").getAsString()
|
||||||
|
json.addProperty("test", test.toUpperCase())
|
||||||
|
json
|
||||||
|
}
|
||||||
|
|
||||||
|
def transformed = ZipUtils.unpackJson(zip, "test.json", JsonObject.class)
|
||||||
|
|
||||||
|
then:
|
||||||
|
transformed.get("test").asString == "THIS IS A TEST OF TRANSFORMING"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/test/groovy/net/fabricmc/loom/test/unit/cache/CachedDataTest.groovy
vendored
Normal file
62
src/test/groovy/net/fabricmc/loom/test/unit/cache/CachedDataTest.groovy
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.test.unit.cache
|
||||||
|
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardOpenOption
|
||||||
|
|
||||||
|
import spock.lang.Specification
|
||||||
|
import spock.lang.TempDir
|
||||||
|
|
||||||
|
import net.fabricmc.loom.decompilers.ClassLineNumbers
|
||||||
|
import net.fabricmc.loom.decompilers.cache.CachedData
|
||||||
|
|
||||||
|
class CachedDataTest extends Specification {
|
||||||
|
@TempDir
|
||||||
|
Path testPath
|
||||||
|
|
||||||
|
// Simple test to check if the CachedData class can be written and read from a file
|
||||||
|
def "Read + Write CachedData"() {
|
||||||
|
given:
|
||||||
|
def lineNumberEntry = new ClassLineNumbers.Entry("net/test/TestClass", 1, 2, [1: 2, 4: 7])
|
||||||
|
def cachedData = new CachedData("net/test/TestClass", "Example sources", lineNumberEntry)
|
||||||
|
def path = testPath.resolve("cachedData.bin")
|
||||||
|
when:
|
||||||
|
// Write the cachedData to a file
|
||||||
|
FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE).withCloseable {
|
||||||
|
cachedData.write(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And read it back
|
||||||
|
def readCachedData = Files.newInputStream(path).withCloseable {
|
||||||
|
return CachedData.read(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
then:
|
||||||
|
cachedData == readCachedData
|
||||||
|
}
|
||||||
|
}
|
||||||
132
src/test/groovy/net/fabricmc/loom/test/unit/cache/CachedFileStoreTest.groovy
vendored
Normal file
132
src/test/groovy/net/fabricmc/loom/test/unit/cache/CachedFileStoreTest.groovy
vendored
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.test.unit.cache
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.attribute.FileTime
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
import spock.lang.Specification
|
||||||
|
import spock.lang.TempDir
|
||||||
|
|
||||||
|
import net.fabricmc.loom.decompilers.cache.CachedFileStore
|
||||||
|
import net.fabricmc.loom.decompilers.cache.CachedFileStoreImpl
|
||||||
|
import net.fabricmc.loom.util.FileSystemUtil
|
||||||
|
|
||||||
|
class CachedFileStoreTest extends Specification {
|
||||||
|
@TempDir
|
||||||
|
Path testPath
|
||||||
|
|
||||||
|
FileSystemUtil.Delegate zipDelegate
|
||||||
|
Path root
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
zipDelegate = FileSystemUtil.getJarFileSystem(testPath.resolve("cache.zip"), true)
|
||||||
|
root = zipDelegate.get().getPath("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup() {
|
||||||
|
zipDelegate.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
def "putEntry"() {
|
||||||
|
given:
|
||||||
|
def cacheRules = new CachedFileStoreImpl.CacheRules(100, Duration.ofDays(7))
|
||||||
|
def store = new CachedFileStoreImpl(root, BYTE_ARRAY_SERIALIZER, cacheRules)
|
||||||
|
when:
|
||||||
|
store.putEntry("abc", "Hello world".bytes)
|
||||||
|
then:
|
||||||
|
Files.exists(root.resolve("abc"))
|
||||||
|
}
|
||||||
|
|
||||||
|
def "getEntry"() {
|
||||||
|
given:
|
||||||
|
def cacheRules = new CachedFileStoreImpl.CacheRules(100, Duration.ofDays(7))
|
||||||
|
def store = new CachedFileStoreImpl(root, BYTE_ARRAY_SERIALIZER, cacheRules)
|
||||||
|
when:
|
||||||
|
store.putEntry("abc", "Hello world".bytes)
|
||||||
|
def entry = store.getEntry("abc")
|
||||||
|
def unknownEntry = store.getEntry("123")
|
||||||
|
then:
|
||||||
|
entry == "Hello world".bytes
|
||||||
|
unknownEntry == null
|
||||||
|
}
|
||||||
|
|
||||||
|
def "pruneManyFiles"() {
|
||||||
|
given:
|
||||||
|
def cacheRules = new CachedFileStoreImpl.CacheRules(250, Duration.ofDays(7))
|
||||||
|
def store = new CachedFileStoreImpl(root, BYTE_ARRAY_SERIALIZER, cacheRules)
|
||||||
|
when:
|
||||||
|
|
||||||
|
for (i in 0..<500) {
|
||||||
|
def key = "test_" + i
|
||||||
|
store.putEntry(key, "Hello world".bytes)
|
||||||
|
// Higher files are older and should be removed.
|
||||||
|
Files.setLastModifiedTime(root.resolve(key), FileTime.from(Instant.now().minusSeconds(i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
store.prune()
|
||||||
|
|
||||||
|
then:
|
||||||
|
Files.exists(root.resolve("test_0"))
|
||||||
|
Files.exists(root.resolve("test_100"))
|
||||||
|
Files.notExists(root.resolve("test_300"))
|
||||||
|
}
|
||||||
|
|
||||||
|
def "pruneOldFiles"() {
|
||||||
|
given:
|
||||||
|
def cacheRules = new CachedFileStoreImpl.CacheRules(1000, Duration.ofSeconds(250))
|
||||||
|
def store = new CachedFileStoreImpl(root, BYTE_ARRAY_SERIALIZER, cacheRules)
|
||||||
|
when:
|
||||||
|
|
||||||
|
for (i in 0..<500) {
|
||||||
|
def key = "test_" + i
|
||||||
|
store.putEntry(key, "Hello world".bytes)
|
||||||
|
// Higher files are older and should be removed.
|
||||||
|
Files.setLastModifiedTime(root.resolve(key), FileTime.from(Instant.now().minusSeconds(i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
store.prune()
|
||||||
|
|
||||||
|
then:
|
||||||
|
Files.exists(root.resolve("test_0"))
|
||||||
|
Files.exists(root.resolve("test_100"))
|
||||||
|
Files.notExists(root.resolve("test_300"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CachedFileStore.EntrySerializer<byte[]> BYTE_ARRAY_SERIALIZER = new CachedFileStore.EntrySerializer<byte[]>() {
|
||||||
|
@Override
|
||||||
|
byte[] read(Path path) throws IOException {
|
||||||
|
return Files.readAllBytes(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void write(byte[] entry, Path path) throws IOException {
|
||||||
|
Files.write(path, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
241
src/test/groovy/net/fabricmc/loom/test/unit/cache/CachedJarProcessorTest.groovy
vendored
Normal file
241
src/test/groovy/net/fabricmc/loom/test/unit/cache/CachedJarProcessorTest.groovy
vendored
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.test.unit.cache
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import net.fabricmc.loom.decompilers.ClassLineNumbers
|
||||||
|
import net.fabricmc.loom.decompilers.cache.CachedData
|
||||||
|
import net.fabricmc.loom.decompilers.cache.CachedFileStore
|
||||||
|
import net.fabricmc.loom.decompilers.cache.CachedJarProcessor
|
||||||
|
import net.fabricmc.loom.test.util.ZipTestUtils
|
||||||
|
import net.fabricmc.loom.util.ZipUtils
|
||||||
|
|
||||||
|
class CachedJarProcessorTest extends Specification {
|
||||||
|
static Map<String, String> jarEntries = [
|
||||||
|
"net/fabricmc/Example.class": "",
|
||||||
|
"net/fabricmc/other/Test.class": "",
|
||||||
|
"net/fabricmc/other/Test\$Inner.class": "",
|
||||||
|
"net/fabricmc/other/Test\$1.class": "",
|
||||||
|
]
|
||||||
|
|
||||||
|
static String ExampleHash = "abc123/cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f5a6adc36f8e6"
|
||||||
|
static String TestHash = "abc123/ecd40b16ec50b636a390cb8da716a22606965f14e526e3051144dd567f336bc5"
|
||||||
|
|
||||||
|
static CachedData ExampleCachedData = new CachedData("net/fabricmc/Example", "Example sources", lineNumber("net/fabricmc/Example"))
|
||||||
|
static CachedData TestCachedData = new CachedData("net/fabricmc/other/Test", "Test sources", lineNumber("net/fabricmc/other/Test"))
|
||||||
|
|
||||||
|
def "prepare full work job"() {
|
||||||
|
given:
|
||||||
|
def jar = ZipTestUtils.createZip(jarEntries)
|
||||||
|
def cache = Mock(CachedFileStore)
|
||||||
|
def processor = new CachedJarProcessor(cache, "abc123")
|
||||||
|
|
||||||
|
when:
|
||||||
|
def workRequest = processor.prepareJob(jar)
|
||||||
|
def workJob = workRequest.job() as CachedJarProcessor.FullWorkJob
|
||||||
|
|
||||||
|
then:
|
||||||
|
workRequest.lineNumbers() == null
|
||||||
|
workJob.outputNameMap().size() == 2
|
||||||
|
|
||||||
|
// Expect two calls looking for the existing entry in the cache
|
||||||
|
2 * cache.getEntry(_) >> null
|
||||||
|
|
||||||
|
0 * _ // Strict mock
|
||||||
|
}
|
||||||
|
|
||||||
|
def "prepare partial work job"() {
|
||||||
|
given:
|
||||||
|
def jar = ZipTestUtils.createZip(jarEntries)
|
||||||
|
def cache = Mock(CachedFileStore)
|
||||||
|
def processor = new CachedJarProcessor(cache, "abc123")
|
||||||
|
|
||||||
|
when:
|
||||||
|
def workRequest = processor.prepareJob(jar)
|
||||||
|
def workJob = workRequest.job() as CachedJarProcessor.PartialWorkJob
|
||||||
|
def lineMap = workRequest.lineNumbers().lineMap()
|
||||||
|
|
||||||
|
then:
|
||||||
|
lineMap.size() == 1
|
||||||
|
lineMap.get("net/fabricmc/Example") == ExampleCachedData.lineNumbers()
|
||||||
|
|
||||||
|
workJob.outputNameMap().size() == 1
|
||||||
|
ZipUtils.unpackNullable(workJob.existing(), "net/fabricmc/Example.java") == "Example sources".bytes
|
||||||
|
|
||||||
|
// Provide one cached entry
|
||||||
|
// And then one call not finding the entry in the cache
|
||||||
|
1 * cache.getEntry(ExampleHash) >> ExampleCachedData
|
||||||
|
1 * cache.getEntry(_) >> null
|
||||||
|
|
||||||
|
0 * _ // Strict mock
|
||||||
|
}
|
||||||
|
|
||||||
|
def "prepare completed work job"() {
|
||||||
|
given:
|
||||||
|
def jar = ZipTestUtils.createZip(jarEntries)
|
||||||
|
def cache = Mock(CachedFileStore)
|
||||||
|
def processor = new CachedJarProcessor(cache, "abc123")
|
||||||
|
|
||||||
|
when:
|
||||||
|
def workRequest = processor.prepareJob(jar)
|
||||||
|
def workJob = workRequest.job() as CachedJarProcessor.CompletedWorkJob
|
||||||
|
def lineMap = workRequest.lineNumbers().lineMap()
|
||||||
|
|
||||||
|
then:
|
||||||
|
lineMap.size() == 2
|
||||||
|
lineMap.get("net/fabricmc/Example") == ExampleCachedData.lineNumbers()
|
||||||
|
lineMap.get("net/fabricmc/other/Test") == TestCachedData.lineNumbers()
|
||||||
|
|
||||||
|
workJob.completed() != null
|
||||||
|
ZipUtils.unpackNullable(workJob.completed(), "net/fabricmc/Example.java") == "Example sources".bytes
|
||||||
|
ZipUtils.unpackNullable(workJob.completed(), "net/fabricmc/other/Test.java") == "Test sources".bytes
|
||||||
|
|
||||||
|
// Provide one cached entry
|
||||||
|
// And then two calls not finding the entry in the cache
|
||||||
|
1 * cache.getEntry(ExampleHash) >> ExampleCachedData
|
||||||
|
1 * cache.getEntry(TestHash) >> TestCachedData
|
||||||
|
|
||||||
|
0 * _ // Strict mock
|
||||||
|
}
|
||||||
|
|
||||||
|
def "complete full work job"() {
|
||||||
|
given:
|
||||||
|
def jar = ZipTestUtils.createZip(jarEntries)
|
||||||
|
def cache = Mock(CachedFileStore)
|
||||||
|
def processor = new CachedJarProcessor(cache, "abc123")
|
||||||
|
|
||||||
|
when:
|
||||||
|
def workRequest = processor.prepareJob(jar)
|
||||||
|
def workJob = workRequest.job() as CachedJarProcessor.FullWorkJob
|
||||||
|
|
||||||
|
// Do the work, such as decompiling.
|
||||||
|
ZipUtils.add(workJob.output(), "net/fabricmc/Example.java", "Example sources")
|
||||||
|
ZipUtils.add(workJob.output(), "net/fabricmc/other/Test.java", "Test sources")
|
||||||
|
|
||||||
|
def outputJar = Files.createTempFile("loom-test-output", ".jar")
|
||||||
|
Files.delete(outputJar)
|
||||||
|
|
||||||
|
ClassLineNumbers lineNumbers = lineNumbers([
|
||||||
|
"net/fabricmc/Example",
|
||||||
|
"net/fabricmc/other/Test"
|
||||||
|
])
|
||||||
|
processor.completeJob(outputJar, workJob, lineNumbers)
|
||||||
|
|
||||||
|
then:
|
||||||
|
workJob.outputNameMap().size() == 2
|
||||||
|
|
||||||
|
ZipUtils.unpackNullable(outputJar, "net/fabricmc/Example.java") == "Example sources".bytes
|
||||||
|
ZipUtils.unpackNullable(outputJar, "net/fabricmc/other/Test.java") == "Test sources".bytes
|
||||||
|
|
||||||
|
// Expect two calls looking for the existing entry in the cache
|
||||||
|
1 * cache.getEntry(ExampleHash) >> null
|
||||||
|
1 * cache.getEntry(TestHash) >> null
|
||||||
|
|
||||||
|
// Expect the new work to be put into the cache
|
||||||
|
1 * cache.putEntry(ExampleHash, ExampleCachedData)
|
||||||
|
1 * cache.putEntry(TestHash, TestCachedData)
|
||||||
|
|
||||||
|
0 * _ // Strict mock
|
||||||
|
}
|
||||||
|
|
||||||
|
def "complete partial work job"() {
|
||||||
|
given:
|
||||||
|
def jar = ZipTestUtils.createZip(jarEntries)
|
||||||
|
def cache = Mock(CachedFileStore)
|
||||||
|
def processor = new CachedJarProcessor(cache, "abc123")
|
||||||
|
|
||||||
|
when:
|
||||||
|
def workRequest = processor.prepareJob(jar)
|
||||||
|
def workJob = workRequest.job() as CachedJarProcessor.PartialWorkJob
|
||||||
|
|
||||||
|
// Do the work
|
||||||
|
ZipUtils.add(workJob.output(), "net/fabricmc/other/Test.java", "Test sources")
|
||||||
|
|
||||||
|
def outputJar = Files.createTempFile("loom-test-output", ".jar")
|
||||||
|
Files.delete(outputJar)
|
||||||
|
|
||||||
|
ClassLineNumbers lineNumbers = lineNumbers([
|
||||||
|
"net/fabricmc/Example",
|
||||||
|
"net/fabricmc/other/Test"
|
||||||
|
])
|
||||||
|
processor.completeJob(outputJar, workJob, lineNumbers)
|
||||||
|
|
||||||
|
then:
|
||||||
|
workJob.outputNameMap().size() == 1
|
||||||
|
|
||||||
|
ZipUtils.unpackNullable(outputJar, "net/fabricmc/Example.java") == "Example sources".bytes
|
||||||
|
ZipUtils.unpackNullable(outputJar, "net/fabricmc/other/Test.java") == "Test sources".bytes
|
||||||
|
|
||||||
|
// The cache already contains sources for example, but not for test
|
||||||
|
1 * cache.getEntry(ExampleHash) >> ExampleCachedData
|
||||||
|
1 * cache.getEntry(TestHash) >> null
|
||||||
|
|
||||||
|
// Expect the new work to be put into the cache
|
||||||
|
1 * cache.putEntry(TestHash, TestCachedData)
|
||||||
|
|
||||||
|
0 * _ // Strict mock
|
||||||
|
}
|
||||||
|
|
||||||
|
def "complete completed work job"() {
|
||||||
|
given:
|
||||||
|
def jar = ZipTestUtils.createZip(jarEntries)
|
||||||
|
def cache = Mock(CachedFileStore)
|
||||||
|
def processor = new CachedJarProcessor(cache, "abc123")
|
||||||
|
|
||||||
|
when:
|
||||||
|
def workRequest = processor.prepareJob(jar)
|
||||||
|
def workJob = workRequest.job() as CachedJarProcessor.CompletedWorkJob
|
||||||
|
|
||||||
|
def outputJar = Files.createTempFile("loom-test-output", ".jar")
|
||||||
|
Files.delete(outputJar)
|
||||||
|
|
||||||
|
ClassLineNumbers lineNumbers = lineNumbers([
|
||||||
|
"net/fabricmc/Example",
|
||||||
|
"net/fabricmc/other/Test"
|
||||||
|
])
|
||||||
|
processor.completeJob(outputJar, workJob, lineNumbers)
|
||||||
|
|
||||||
|
then:
|
||||||
|
ZipUtils.unpackNullable(outputJar, "net/fabricmc/Example.java") == "Example sources".bytes
|
||||||
|
ZipUtils.unpackNullable(outputJar, "net/fabricmc/other/Test.java") == "Test sources".bytes
|
||||||
|
|
||||||
|
// The cache already contains sources for example, but not for test
|
||||||
|
1 * cache.getEntry(ExampleHash) >> ExampleCachedData
|
||||||
|
1 * cache.getEntry(TestHash) >> TestCachedData
|
||||||
|
|
||||||
|
0 * _ // Strict mock
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassLineNumbers lineNumbers(List<String> names) {
|
||||||
|
return new ClassLineNumbers(names.collectEntries { [it, lineNumber(it)] })
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassLineNumbers.Entry lineNumber(String name) {
|
||||||
|
return new ClassLineNumbers.Entry(name, 0, 0, [:])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 FabricMC
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.loom.test.unit.fmj
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import org.intellij.lang.annotations.Language
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.fmj.FabricModJsonUtils
|
||||||
|
|
||||||
|
class FabricModJsonUtilsTest extends Specification {
|
||||||
|
// Test that the schemaVersion is moved to the first position
|
||||||
|
def "optimize FMJ"() {
|
||||||
|
given:
|
||||||
|
// Matches LoomGradlePlugin
|
||||||
|
def gson = new GsonBuilder().setPrettyPrinting().create()
|
||||||
|
def json = gson.fromJson(INPUT_FMJ, JsonObject.class)
|
||||||
|
when:
|
||||||
|
def outputJson = FabricModJsonUtils.optimizeFmj(json)
|
||||||
|
def output = gson.toJson(outputJson)
|
||||||
|
then:
|
||||||
|
output == OUTPUT_FMJ
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// schemaVersion is not first
|
||||||
|
@Language("json")
|
||||||
|
static String INPUT_FMJ = """
|
||||||
|
{
|
||||||
|
"id": "modid",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"name": "Example mod",
|
||||||
|
"description": "This is an example description! Tell everyone what your mod is about!",
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"icon": "assets/modid/icon.png",
|
||||||
|
"environment": "*",
|
||||||
|
"entrypoints": {
|
||||||
|
"main": [
|
||||||
|
"com.example.ExampleMod"
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
"com.example.ExampleModClient"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"mixins": [
|
||||||
|
"modid.mixins.json",
|
||||||
|
{
|
||||||
|
"config": "modid.client.mixins.json",
|
||||||
|
"environment": "client"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": "\\u003e\\u003d0.15.0",
|
||||||
|
"minecraft": "~1.20.4",
|
||||||
|
"java": "\\u003e\\u003d17",
|
||||||
|
"fabric-api": "*"
|
||||||
|
},
|
||||||
|
"suggests": {
|
||||||
|
"another-mod": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
""".trim()
|
||||||
|
|
||||||
|
// schemaVersion is first, everything else is unchanged
|
||||||
|
@Language("json")
|
||||||
|
static String OUTPUT_FMJ = """
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "modid",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"name": "Example mod",
|
||||||
|
"description": "This is an example description! Tell everyone what your mod is about!",
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"icon": "assets/modid/icon.png",
|
||||||
|
"environment": "*",
|
||||||
|
"entrypoints": {
|
||||||
|
"main": [
|
||||||
|
"com.example.ExampleMod"
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
"com.example.ExampleModClient"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mixins": [
|
||||||
|
"modid.mixins.json",
|
||||||
|
{
|
||||||
|
"config": "modid.client.mixins.json",
|
||||||
|
"environment": "client"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": "\\u003e\\u003d0.15.0",
|
||||||
|
"minecraft": "~1.20.4",
|
||||||
|
"java": "\\u003e\\u003d17",
|
||||||
|
"fabric-api": "*"
|
||||||
|
},
|
||||||
|
"suggests": {
|
||||||
|
"another-mod": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
""".trim()
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ class IntermediaryMappingLayerTest extends LayeredMappingsSpecification {
|
|||||||
def "Read intermediary mappings" () {
|
def "Read intermediary mappings" () {
|
||||||
setup:
|
setup:
|
||||||
intermediaryUrl = INTERMEDIARY_1_17_URL
|
intermediaryUrl = INTERMEDIARY_1_17_URL
|
||||||
|
mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_17
|
||||||
when:
|
when:
|
||||||
def mappings = getSingleMapping(new IntermediaryMappingsSpec())
|
def mappings = getSingleMapping(new IntermediaryMappingsSpec())
|
||||||
def tiny = getTiny(mappings)
|
def tiny = getTiny(mappings)
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ interface LayeredMappingsTestConstants {
|
|||||||
client_mappings: new MinecraftVersionMeta.Download(null, "227d16f520848747a59bef6f490ae19dc290a804", 6431705, "https://launcher.mojang.com/v1/objects/227d16f520848747a59bef6f490ae19dc290a804/client.txt"),
|
client_mappings: new MinecraftVersionMeta.Download(null, "227d16f520848747a59bef6f490ae19dc290a804", 6431705, "https://launcher.mojang.com/v1/objects/227d16f520848747a59bef6f490ae19dc290a804/client.txt"),
|
||||||
server_mappings: new MinecraftVersionMeta.Download(null, "84d80036e14bc5c7894a4fad9dd9f367d3000334", 4948536, "https://launcher.mojang.com/v1/objects/84d80036e14bc5c7894a4fad9dd9f367d3000334/server.txt")
|
server_mappings: new MinecraftVersionMeta.Download(null, "84d80036e14bc5c7894a4fad9dd9f367d3000334", 4948536, "https://launcher.mojang.com/v1/objects/84d80036e14bc5c7894a4fad9dd9f367d3000334/server.txt")
|
||||||
]
|
]
|
||||||
public static final MinecraftVersionMeta VERSION_META_1_17 = new MinecraftVersionMeta(null, null, null, 0, DOWNLOADS_1_17, null, null, null, null, 0, null, null, null)
|
public static final MinecraftVersionMeta VERSION_META_1_17 = new MinecraftVersionMeta(null, null, null, 0, DOWNLOADS_1_17, null, null, null, null, 0, "2021-06-08T11:00:40+00:00", null, null, null)
|
||||||
|
|
||||||
public static final Map<String, MinecraftVersionMeta.Download> DOWNLOADS_1_16_5 = [
|
public static final Map<String, MinecraftVersionMeta.Download> DOWNLOADS_1_16_5 = [
|
||||||
client_mappings: new MinecraftVersionMeta.Download(null, "e3dfb0001e1079a1af72ee21517330edf52e6192", 5746047, "https://launcher.mojang.com/v1/objects/e3dfb0001e1079a1af72ee21517330edf52e6192/client.txt"),
|
client_mappings: new MinecraftVersionMeta.Download(null, "e3dfb0001e1079a1af72ee21517330edf52e6192", 5746047, "https://launcher.mojang.com/v1/objects/e3dfb0001e1079a1af72ee21517330edf52e6192/client.txt"),
|
||||||
server_mappings: new MinecraftVersionMeta.Download(null, "81d5c793695d8cde63afddb40dde88e3a88132ac", 4400926, "https://launcher.mojang.com/v1/objects/81d5c793695d8cde63afddb40dde88e3a88132ac/server.txt")
|
server_mappings: new MinecraftVersionMeta.Download(null, "81d5c793695d8cde63afddb40dde88e3a88132ac", 4400926, "https://launcher.mojang.com/v1/objects/81d5c793695d8cde63afddb40dde88e3a88132ac/server.txt")
|
||||||
]
|
]
|
||||||
public static final MinecraftVersionMeta VERSION_META_1_16_5 = new MinecraftVersionMeta(null, null, null, 0, DOWNLOADS_1_16_5, null, null, null, null, 0, null, null, null)
|
public static final MinecraftVersionMeta VERSION_META_1_16_5 = new MinecraftVersionMeta(null, null, null, 0, DOWNLOADS_1_16_5, null, null, null, null, 0, "2021-01-14T16:05:32+00:00", null, null, null)
|
||||||
|
|
||||||
public static final String PARCHMENT_NOTATION = "org.parchmentmc.data:parchment-1.16.5:20210608-SNAPSHOT@zip"
|
public static final String PARCHMENT_NOTATION = "org.parchmentmc.data:parchment-1.16.5:20210608-SNAPSHOT@zip"
|
||||||
public static final String PARCHMENT_URL = "https://maven.parchmentmc.net/org/parchmentmc/data/parchment-1.16.5/20210608-SNAPSHOT/parchment-1.16.5-20210608-SNAPSHOT.zip"
|
public static final String PARCHMENT_URL = "https://maven.parchmentmc.net/org/parchmentmc/data/parchment-1.16.5/20210608-SNAPSHOT/parchment-1.16.5-20210608-SNAPSHOT.zip"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ trait GradleProjectTestTrait {
|
|||||||
String gradleVersion = options.version as String ?: LoomTestConstants.DEFAULT_GRADLE
|
String gradleVersion = options.version as String ?: LoomTestConstants.DEFAULT_GRADLE
|
||||||
String warningMode = options.warningMode as String ?: "fail"
|
String warningMode = options.warningMode as String ?: "fail"
|
||||||
File projectDir = options.projectDir as File ?: options.sharedFiles ? sharedProjectDir : File.createTempDir()
|
File projectDir = options.projectDir as File ?: options.sharedFiles ? sharedProjectDir : File.createTempDir()
|
||||||
File gradleHomeDir = gradleHomeDir
|
File gradleHomeDir = options.gradleHomeDir as File ?: gradleHomeDir
|
||||||
|
|
||||||
setupProject(options, projectDir)
|
setupProject(options, projectDir)
|
||||||
|
|
||||||
|
|||||||
BIN
src/test/resources/mappings/1.2.5-intermediary.tiny.zip
Normal file
BIN
src/test/resources/mappings/1.2.5-intermediary.tiny.zip
Normal file
Binary file not shown.
2
src/test/resources/mappings/ATTRIBUTIONS.md
Normal file
2
src/test/resources/mappings/ATTRIBUTIONS.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
The file `1.2.5-intermediary.tiny` was taken from OrnitheMC's "[Calamus](https://github.com/OrnitheMC/calamus/blob/gen2/mappings/1.2.5.tiny)" intermediaries under the CC0 license.
|
||||||
|
The file was rewritten in Tiny V2 format, but the mappings are otherwise unmodified.
|
||||||
@@ -49,4 +49,8 @@ dependencies {
|
|||||||
|
|
||||||
base {
|
base {
|
||||||
archivesName = "fabric-example-mod"
|
archivesName = "fabric-example-mod"
|
||||||
|
}
|
||||||
|
|
||||||
|
runClient {
|
||||||
|
// Realise this task to ensure that the runConfig is lazily evaluated
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user