Merge remote-tracking branch 'upstream/dev/0.11' into dev/0.11.0

# Conflicts:
#	build.gradle
#	src/main/java/net/fabricmc/loom/LoomGradleExtension.java
#	src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
#	src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
#	src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
#	src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java
#	src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
#	src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java
#	src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java
#	src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java
#	src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java
#	src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java
#	src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java
#	src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.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/LoomTasks.java
#	src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java
#	src/main/java/net/fabricmc/loom/task/RemapJarTask.java
#	src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java
#	src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java
#	src/main/java/net/fabricmc/loom/task/service/MappingsService.java
#	src/main/java/net/fabricmc/loom/util/Checksum.java
#	src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
#	src/test/resources/projects/kotlin/build.gradle.kts
This commit is contained in:
Juuz
2022-01-18 17:43:44 +02:00
109 changed files with 5001 additions and 2151 deletions

View File

@@ -24,7 +24,7 @@
package net.fabricmc.loom;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Supplier;
@@ -36,9 +36,9 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.configuration.LoomDependencyManager;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
@@ -52,7 +52,9 @@ import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider;
import net.fabricmc.loom.configuration.providers.forge.PatchProvider;
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.extension.LoomFiles;
import net.fabricmc.loom.extension.MixinExtension;
@@ -90,21 +92,31 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
JarProcessorManager getJarProcessorManager();
default MinecraftProviderImpl getMinecraftProvider() {
return getDependencyManager().getProvider(MinecraftProviderImpl.class);
MinecraftProvider getMinecraftProvider();
void setMinecraftProvider(MinecraftProvider minecraftProvider);
MappingsProviderImpl getMappingsProvider();
void setMappingsProvider(MappingsProviderImpl mappingsProvider);
NamedMinecraftProvider<?> getNamedMinecraftProvider();
IntermediaryMinecraftProvider<?> getIntermediaryMinecraftProvider();
void setNamedMinecraftProvider(NamedMinecraftProvider<?> namedMinecraftProvider);
void setIntermediaryMinecraftProvider(IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider);
default List<Path> getMinecraftJars(MappingsNamespace mappingsNamespace) {
return switch (mappingsNamespace) {
case NAMED -> getNamedMinecraftProvider().getMinecraftJars();
case INTERMEDIARY -> getIntermediaryMinecraftProvider().getMinecraftJars();
case OFFICIAL -> getMinecraftProvider().getMinecraftJars();
};
}
default MappingsProviderImpl getMappingsProvider() {
return getDependencyManager().getProvider(isForge() ? FieldMigratedMappingsProvider.class : MappingsProviderImpl.class);
}
default MinecraftMappedProvider getMinecraftMappedProvider() {
return getMappingsProvider().mappedProvider;
}
File getMixinMappings(SourceSet sourceSet);
FileCollection getAllMixinMappings();
FileCollection getMinecraftJarsCollection(MappingsNamespace mappingsNamespace);
boolean isRootProject();

View File

@@ -28,7 +28,6 @@ import java.util.List;
import java.util.function.Consumer;
import org.gradle.api.Action;
import org.gradle.api.DomainObjectCollection;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.file.ConfigurableFileCollection;
@@ -39,13 +38,14 @@ import org.gradle.api.provider.Provider;
import org.gradle.api.publish.maven.MavenPublication;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.util.DeprecationHelper;
import net.fabricmc.loom.util.ModPlatform;
@@ -64,11 +64,9 @@ public interface LoomGradleExtensionAPI {
getShareRemapCaches().set(true);
}
DomainObjectCollection<LoomDecompiler> getGameDecompilers();
NamedDomainObjectContainer<DecompilerOptions> getDecompilerOptions();
default void addDecompiler(LoomDecompiler decompiler) {
getGameDecompilers().add(decompiler);
}
void decompilers(Action<NamedDomainObjectContainer<DecompilerOptions>> action);
ListProperty<JarProcessor> getGameJarProcessors();
@@ -150,6 +148,19 @@ public interface LoomGradleExtensionAPI {
*/
Property<Boolean> getEnableInterfaceInjection();
@ApiStatus.Experimental
Property<MinecraftJarConfiguration> getMinecraftJarConfiguration();
@ApiStatus.Experimental
default void serverOnlyMinecraftJar() {
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SERVER_ONLY);
}
@ApiStatus.Experimental
default void splitMinecraftJar() {
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SPLIT);
}
// ===================
// Architectury Loom
// ===================

View File

@@ -0,0 +1,81 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.api.decompilers;
import java.io.Serializable;
import java.util.Map;
import com.google.common.base.Preconditions;
import org.gradle.api.Named;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
public abstract class DecompilerOptions implements Named {
/**
* Class name for to the {@link LoomDecompiler}.
*/
public abstract Property<String> getDecompilerClassName();
/**
* Additional classpath entries for the decompiler jvm.
*/
public abstract ConfigurableFileCollection getClasspath();
/**
* Additional options to be passed to the decompiler.
*/
public abstract MapProperty<String, String> getOptions();
/**
* Memory used for forked JVM in megabytes.
*/
public abstract Property<Long> getMemory();
/**
* Maximum number of threads the decompiler is allowed to use.
*/
public abstract Property<Integer> getMaxThreads();
public DecompilerOptions() {
getDecompilerClassName().finalizeValueOnRead();
getClasspath().finalizeValueOnRead();
getOptions().finalizeValueOnRead();
getMemory().convention(4096L).finalizeValueOnRead();
getMaxThreads().convention(Runtime.getRuntime().availableProcessors()).finalizeValueOnRead();
}
// Done to work around weird issues with the workers, possibly https://github.com/gradle/gradle/issues/13422
public record Dto(String className, Map<String, String> options, int maxThreads) implements Serializable { }
public Dto toDto() {
Preconditions.checkArgument(getDecompilerClassName().isPresent(), "No decompiler classname specified for decompiler: " + getName());
return new Dto(
getDecompilerClassName().get(),
getOptions().get(),
getMaxThreads().get()
);
}
}

View File

@@ -26,13 +26,7 @@ package net.fabricmc.loom.api.decompilers;
import java.nio.file.Path;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
import org.jetbrains.annotations.Nullable;
public interface LoomDecompiler {
String name();
/**
* @param sourcesDestination Decompiled sources jar
* @param linemapDestination A byproduct of decompilation that lines up the compiled jar's line numbers with the decompiled
@@ -41,12 +35,4 @@ public interface LoomDecompiler {
* @param metaData Additional information that may or may not be needed while decompiling
*/
void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData);
/**
* Add additional classpath entries to the decompiler classpath, can return a configuration.
*/
@Nullable
default FileCollection getBootstrapClasspath(Project project) {
return null;
}
}

View File

@@ -25,13 +25,14 @@
package net.fabricmc.loom.api.mappings.layered;
import java.nio.file.Path;
import java.util.function.Supplier;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.logging.Logger;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.configuration.providers.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
@ApiStatus.Experimental /* Very Experimental and not cleanly separated from the impl atm */
public interface MappingContext {
@@ -39,7 +40,7 @@ public interface MappingContext {
Path resolveMavenDependency(String mavenNotation);
MappingsProvider mappingsProvider();
Supplier<MemoryMappingTree> intermediaryTree();
MinecraftProvider minecraftProvider();

View File

@@ -42,7 +42,7 @@ public interface LayeredMappingSpecBuilder {
LayeredMappingSpecBuilder addLayer(MappingsSpec<?> mappingSpec);
/**
* Add a layer that uses the official mappings provided by Mojang with the default options.
* Add a layer that uses the official mappings provided by Mojang with the default decompilerOptions.
*/
default LayeredMappingSpecBuilder officialMojangMappings() {
return officialMojangMappings(builder -> { });

View File

@@ -43,6 +43,7 @@ import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.MixinMappingsService;
import net.fabricmc.loom.util.Constants;
/**
@@ -86,7 +87,7 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
Path mappings = loom.isForge() ? loom.getMappingsProvider().mixinTinyMappingsWithSrg : loom.getMappingsProvider().tinyMappings;
Map<String, String> args = new HashMap<>() {{
put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, mappings.toFile().getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, loom.getMixinMappings(sourceSet).getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, MixinMappingsService.getMixinMappingFile(project, sourceSet).getCanonicalPath());
put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, refmapName));
put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary");
}};

View File

@@ -24,7 +24,6 @@
package net.fabricmc.loom.configuration;
import java.io.File;
import java.nio.charset.StandardCharsets;
import org.gradle.api.NamedDomainObjectProvider;
@@ -35,7 +34,6 @@ import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.AbstractCopyTask;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.javadoc.Javadoc;
@@ -43,8 +41,10 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.build.mixin.JavaApInvoker;
import net.fabricmc.loom.build.mixin.KaptApInvoker;
import net.fabricmc.loom.build.mixin.ScalaApInvoker;
import net.fabricmc.loom.configuration.providers.LaunchProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingsProvider;
import net.fabricmc.loom.configuration.providers.forge.ForgeProvider;
import net.fabricmc.loom.configuration.providers.forge.ForgeUniversalProvider;
@@ -53,9 +53,11 @@ import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider;
import net.fabricmc.loom.configuration.providers.forge.PatchProvider;
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.task.UnpickJarTask;
import net.fabricmc.loom.util.Constants;
public final class CompileConfiguration {
@@ -179,6 +181,11 @@ public final class CompileConfiguration {
extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, project);
extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, project);
// Add the dev time dependencies
project.getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR);
project.getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER);
project.getDependencies().add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS);
}
public static void configureCompile(Project p) {
@@ -191,11 +198,15 @@ public final class CompileConfiguration {
});
p.afterEvaluate(project -> {
try {
setupMinecraft(project);
} catch (Exception e) {
throw new RuntimeException("Failed to setup minecraft", e);
}
LoomDependencyManager dependencyManager = new LoomDependencyManager();
extension.setDependencyManager(dependencyManager);
dependencyManager.addProvider(new MinecraftProviderImpl(project));
if (extension.isForge()) {
dependencyManager.addProvider(new ForgeProvider(project));
dependencyManager.addProvider(new ForgeUserdevProvider(project));
@@ -211,9 +222,6 @@ public final class CompileConfiguration {
dependencyManager.addProvider(new ForgeUniversalProvider(project));
}
dependencyManager.addProvider(extension.isForge() ? new FieldMigratedMappingsProvider(project) : new MappingsProviderImpl(project));
dependencyManager.addProvider(new LaunchProvider(project));
dependencyManager.handleDependencies(project);
extension.getRemapArchives().finalizeValue();
@@ -246,6 +254,69 @@ public final class CompileConfiguration {
}
}
// This is not thread safe across projects synchronize it here just to be sure, might be possible to move this further down, but for now this will do.
private static synchronized void setupMinecraft(Project project) throws Exception {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final MinecraftJarConfiguration jarConfiguration = extension.getMinecraftJarConfiguration().get();
// Provide the vanilla mc jars -- TODO share across projects.
final MinecraftProvider minecraftProvider = jarConfiguration.getMinecraftProviderFunction().apply(project);
extension.setMinecraftProvider(minecraftProvider);
minecraftProvider.provide();
final DependencyInfo mappingsDep = DependencyInfo.create(project, Constants.Configurations.MAPPINGS);
final MappingsProviderImpl mappingsProvider = MappingsProviderImpl.getInstance(project, mappingsDep, minecraftProvider);
extension.setMappingsProvider(mappingsProvider);
mappingsProvider.applyToProject(project, mappingsDep);
// Provide the remapped mc jars
final IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider = jarConfiguration.getIntermediaryMinecraftProviderBiFunction().apply(project, minecraftProvider);
NamedMinecraftProvider<?> namedMinecraftProvider = jarConfiguration.getNamedMinecraftProviderBiFunction().apply(project, minecraftProvider);
final JarProcessorManager jarProcessorManager = createJarProcessorManager(project);
if (jarProcessorManager.active()) {
// Wrap the named MC provider for one that will provide the processed jars
namedMinecraftProvider = jarConfiguration.getProcessedNamedMinecraftProviderBiFunction().apply(namedMinecraftProvider, jarProcessorManager);
}
extension.setIntermediaryMinecraftProvider(intermediaryMinecraftProvider);
intermediaryMinecraftProvider.provide(true);
extension.setNamedMinecraftProvider(namedMinecraftProvider);
namedMinecraftProvider.provide(true);
}
private static JarProcessorManager createJarProcessorManager(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.getAccessWidenerPath().isPresent()) {
extension.getGameJarProcessors().add(new AccessWidenerJarProcessor(project));
}
if (extension.getEnableTransitiveAccessWideners().get()) {
TransitiveAccessWidenerJarProcessor transitiveAccessWidenerJarProcessor = new TransitiveAccessWidenerJarProcessor(project);
if (!transitiveAccessWidenerJarProcessor.isEmpty()) {
extension.getGameJarProcessors().add(transitiveAccessWidenerJarProcessor);
}
}
if (extension.getEnableInterfaceInjection().get()) {
InterfaceInjectionProcessor jarProcessor = new InterfaceInjectionProcessor(project);
if (!jarProcessor.isEmpty()) {
extension.getGameJarProcessors().add(jarProcessor);
}
}
JarProcessorManager processorManager = new JarProcessorManager(extension.getGameJarProcessors().get());
extension.setJarProcessorManager(processorManager);
processorManager.setupProcessors();
return processorManager;
}
private static void setupMixinAp(Project project, MixinExtension mixin) {
mixin.init();
@@ -270,39 +341,10 @@ public final class CompileConfiguration {
}
private static void configureDecompileTasks(Project project) {
final TaskContainer tasks = project.getTasks();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
File mappedJar = mappingsProvider.mappedProvider.getMappedJar();
if (mappingsProvider.hasUnpickDefinitions()) {
File outputJar = mappingsProvider.mappedProvider.getUnpickedJar();
tasks.register("unpickJar", UnpickJarTask.class, unpickJarTask -> {
unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
unpickJarTask.getInputJar().set(mappingsProvider.mappedProvider.getMappedJar());
unpickJarTask.getOutputJar().set(outputJar);
});
mappedJar = outputJar;
}
final File inputJar = mappedJar;
extension.getGameDecompilers().configureEach(decompiler -> {
String taskName = "genSourcesWith" + decompiler.name();
// Set the input jar for the task after evaluation has occurred.
tasks.named(taskName, GenerateSourcesTask.class).configure(task -> {
task.getInputJar().set(inputJar);
if (mappingsProvider.hasUnpickDefinitions()) {
task.dependsOn(tasks.named("unpickJar"));
}
});
});
extension.getMinecraftJarConfiguration().get().getDecompileConfigurationBiFunction()
.apply(project, extension.getNamedMinecraftProvider()).afterEvaluation();
}
private static void extendsFrom(String a, String b, Project project) {

View File

@@ -0,0 +1,140 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-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.configuration;
import java.io.File;
import java.util.Optional;
import java.util.Set;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.artifacts.SelfResolvingDependency;
public class DependencyInfo {
final Project project;
final Dependency dependency;
final Configuration sourceConfiguration;
private String resolvedVersion = null;
public static DependencyInfo create(Project project, String configuration) {
return create(project, project.getConfigurations().getByName(configuration));
}
public static DependencyInfo create(Project project, Configuration configuration) {
DependencySet dependencies = configuration.getDependencies();
if (dependencies.isEmpty()) {
throw new IllegalArgumentException(String.format("Configuration '%s' has no dependencies", configuration.getName()));
}
if (dependencies.size() != 1) {
throw new IllegalArgumentException(String.format("Configuration '%s' must only have 1 dependency", configuration.getName()));
}
return create(project, dependencies.iterator().next(), configuration);
}
public static DependencyInfo create(Project project, Dependency dependency, Configuration sourceConfiguration) {
if (dependency instanceof SelfResolvingDependency selfResolvingDependency) {
return new FileDependencyInfo(project, selfResolvingDependency, sourceConfiguration);
} else {
return new DependencyInfo(project, dependency, sourceConfiguration);
}
}
DependencyInfo(Project project, Dependency dependency, Configuration sourceConfiguration) {
this.project = project;
this.dependency = dependency;
this.sourceConfiguration = sourceConfiguration;
}
public Dependency getDependency() {
return dependency;
}
public String getResolvedVersion() {
if (resolvedVersion != null) {
return resolvedVersion;
}
for (ResolvedDependency rd : sourceConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies()) {
if (rd.getModuleGroup().equals(dependency.getGroup()) && rd.getModuleName().equals(dependency.getName())) {
resolvedVersion = rd.getModuleVersion();
return resolvedVersion;
}
}
resolvedVersion = dependency.getVersion();
return resolvedVersion;
}
public Configuration getSourceConfiguration() {
return sourceConfiguration;
}
public Set<File> resolve() {
if (dependency instanceof SelfResolvingDependency selfResolvingDependency) {
return selfResolvingDependency.resolve();
}
return sourceConfiguration.files(dependency);
}
public Optional<File> resolveFile() {
Set<File> files = resolve();
if (files.isEmpty()) {
return Optional.empty();
} else if (files.size() > 1) {
StringBuilder builder = new StringBuilder(this.toString());
builder.append(" resolves to more than one file:");
for (File f : files) {
builder.append("\n\t-").append(f.getAbsolutePath());
}
throw new RuntimeException(builder.toString());
} else {
return files.stream().findFirst();
}
}
@Override
public String toString() {
return getDepString();
}
public String getDepString() {
return dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion();
}
public String getResolvedDepString() {
return dependency.getGroup() + ":" + dependency.getName() + ":" + getResolvedVersion();
}
}

View File

@@ -1,294 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-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.configuration;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.google.common.collect.Iterables;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.commons.io.FilenameUtils;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.artifacts.SelfResolvingDependency;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.providers.MinecraftProvider;
import net.fabricmc.loom.extension.LoomFiles;
import net.fabricmc.loom.util.ZipUtils;
public abstract class DependencyProvider {
private LoomDependencyManager dependencyManager;
private final Project project;
private final LoomGradleExtension extension;
public DependencyProvider(Project project) {
this.project = project;
this.extension = LoomGradleExtension.get(project);
}
public abstract void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception;
public abstract String getTargetConfig();
public Dependency addDependency(Object object, String target) {
if (object instanceof File) {
object = project.files(object);
}
return project.getDependencies().add(target, object);
}
public void register(LoomDependencyManager dependencyManager) {
this.dependencyManager = dependencyManager;
}
public LoomDependencyManager getDependencyManager() {
return dependencyManager;
}
public Project getProject() {
return project;
}
public LoomGradleExtension getExtension() {
return extension;
}
public LoomFiles getDirectories() {
return getExtension().getFiles();
}
public MinecraftProvider getMinecraftProvider() {
return getExtension().getMinecraftProvider();
}
public boolean isRefreshDeps() {
return LoomGradlePlugin.refreshDeps;
}
public static class DependencyInfo {
final Project project;
final Dependency dependency;
final Configuration sourceConfiguration;
private String resolvedVersion = null;
public static DependencyInfo create(Project project, Dependency dependency, Configuration sourceConfiguration) {
if (dependency instanceof SelfResolvingDependency selfResolvingDependency) {
return new FileDependencyInfo(project, selfResolvingDependency, sourceConfiguration);
} else {
return new DependencyInfo(project, dependency, sourceConfiguration);
}
}
private DependencyInfo(Project project, Dependency dependency, Configuration sourceConfiguration) {
this.project = project;
this.dependency = dependency;
this.sourceConfiguration = sourceConfiguration;
}
public Dependency getDependency() {
return dependency;
}
public String getResolvedVersion() {
if (resolvedVersion != null) {
return resolvedVersion;
}
for (ResolvedDependency rd : sourceConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies()) {
if (rd.getModuleGroup().equals(dependency.getGroup()) && rd.getModuleName().equals(dependency.getName())) {
resolvedVersion = rd.getModuleVersion();
return resolvedVersion;
}
}
resolvedVersion = dependency.getVersion();
return resolvedVersion;
}
public Configuration getSourceConfiguration() {
return sourceConfiguration;
}
public Set<File> resolve() {
if (dependency instanceof SelfResolvingDependency selfResolvingDependency) {
return selfResolvingDependency.resolve();
}
return sourceConfiguration.files(dependency);
}
public Optional<File> resolveFile() {
Set<File> files = resolve();
if (files.isEmpty()) {
return Optional.empty();
} else if (files.size() > 1) {
StringBuilder builder = new StringBuilder(this.toString());
builder.append(" resolves to more than one file:");
for (File f : files) {
builder.append("\n\t-").append(f.getAbsolutePath());
}
throw new RuntimeException(builder.toString());
} else {
return files.stream().findFirst();
}
}
@Override
public String toString() {
return getDepString();
}
public String getDepString() {
return dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion();
}
public String getResolvedDepString() {
return dependency.getGroup() + ":" + dependency.getName() + ":" + getResolvedVersion();
}
}
public static class FileDependencyInfo extends DependencyInfo {
protected final Map<String, File> classifierToFile = new HashMap<>();
protected final Set<File> resolvedFiles;
protected final String group, name, version;
FileDependencyInfo(Project project, SelfResolvingDependency dependency, Configuration configuration) {
super(project, dependency, configuration);
Set<File> files = dependency.resolve();
this.resolvedFiles = files;
switch (files.size()) {
case 0 -> //Don't think Gradle would ever let you do this
throw new IllegalStateException("Empty dependency for " + configuration.getName());
case 1 -> //Single file dependency
classifierToFile.put("", Iterables.getOnlyElement(files));
default -> { //File collection, try work out the classifiers
List<File> sortedFiles = files.stream().sorted(Comparator.comparing(File::getName, Comparator.comparingInt(String::length))).collect(Collectors.toList());
//First element in sortedFiles is the one with the shortest name, we presume all the others are different classifier types of this
File shortest = sortedFiles.remove(0);
String shortestName = FilenameUtils.removeExtension(shortest.getName()); //name.jar -> name
for (File file : sortedFiles) {
if (!file.getName().startsWith(shortestName)) {
//If there is another file which doesn't start with the same name as the presumed classifier-less one we're out of our depth
throw new IllegalArgumentException("Unable to resolve classifiers for " + this + " (failed to sort " + files + ')');
}
}
//We appear to be right, therefore this is the normal dependency file we want
classifierToFile.put("", shortest);
int start = shortestName.length();
for (File file : sortedFiles) {
//Now we just have to work out what classifier type the other files are, this shouldn't even return an empty string
String classifier = FilenameUtils.removeExtension(file.getName()).substring(start);
//The classifier could well be separated with a dash (thing name.jar and name-sources.jar), we don't want that leading dash
if (classifierToFile.put(classifier.charAt(0) == '-' ? classifier.substring(1) : classifier, file) != null) {
throw new InvalidUserDataException("Duplicate classifiers for " + this + " (\"" + file.getName().substring(start) + "\" in " + files + ')');
}
}
}
}
if (dependency.getGroup() != null && dependency.getVersion() != null) {
group = dependency.getGroup();
name = dependency.getName();
version = dependency.getVersion();
} else {
group = "net.fabricmc.synthetic";
File root = classifierToFile.get(""); //We've built the classifierToFile map, now to try find a name and version for our dependency
byte[] modJson;
try {
if ("jar".equals(FilenameUtils.getExtension(root.getName())) && (modJson = ZipUtils.unpackNullable(root.toPath(), "fabric.mod.json")) != null) {
//It's a Fabric mod, see how much we can extract out
JsonObject json = new Gson().fromJson(new String(modJson, StandardCharsets.UTF_8), JsonObject.class);
if (json == null || !json.has("id") || !json.has("version")) {
throw new IllegalArgumentException("Invalid Fabric mod jar: " + root + " (malformed json: " + json + ')');
}
if (json.has("name")) { //Go for the name field if it's got one
name = json.get("name").getAsString();
} else {
name = json.get("id").getAsString();
}
version = json.get("version").getAsString();
} else {
//Not a Fabric mod, just have to make something up
name = FilenameUtils.removeExtension(root.getName());
version = "1.0";
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to read input file: " + root, e);
}
}
}
@Override
public String getResolvedVersion() {
return version;
}
@Override
public String getDepString() {
//Use our custom name and version with the dummy group rather than the null:unspecified:null it would otherwise return
return group + ':' + name + ':' + version;
}
@Override
public String getResolvedDepString() {
return getDepString();
}
@Override
public Set<File> resolve() {
return this.resolvedFiles;
}
}
}

View File

@@ -0,0 +1,149 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-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.configuration;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.collect.Iterables;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.commons.io.FilenameUtils;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.SelfResolvingDependency;
import net.fabricmc.loom.util.ZipUtils;
public class FileDependencyInfo extends DependencyInfo {
protected final Map<String, File> classifierToFile = new HashMap<>();
protected final Set<File> resolvedFiles;
protected final String group, name, version;
FileDependencyInfo(Project project, SelfResolvingDependency dependency, Configuration configuration) {
super(project, dependency, configuration);
Set<File> files = dependency.resolve();
this.resolvedFiles = files;
switch (files.size()) {
case 0 -> //Don't think Gradle would ever let you do this
throw new IllegalStateException("Empty dependency?");
case 1 -> //Single file dependency
classifierToFile.put("", Iterables.getOnlyElement(files));
default -> { //File collection, try work out the classifiers
List<File> sortedFiles = files.stream().sorted(Comparator.comparing(File::getName, Comparator.comparingInt(String::length))).collect(Collectors.toList());
//First element in sortedFiles is the one with the shortest name, we presume all the others are different classifier types of this
File shortest = sortedFiles.remove(0);
String shortestName = FilenameUtils.removeExtension(shortest.getName()); //name.jar -> name
for (File file : sortedFiles) {
if (!file.getName().startsWith(shortestName)) {
//If there is another file which doesn't start with the same name as the presumed classifier-less one we're out of our depth
throw new IllegalArgumentException("Unable to resolve classifiers for " + this + " (failed to sort " + files + ')');
}
}
//We appear to be right, therefore this is the normal dependency file we want
classifierToFile.put("", shortest);
int start = shortestName.length();
for (File file : sortedFiles) {
//Now we just have to work out what classifier type the other files are, this shouldn't even return an empty string
String classifier = FilenameUtils.removeExtension(file.getName()).substring(start);
//The classifier could well be separated with a dash (thing name.jar and name-sources.jar), we don't want that leading dash
if (classifierToFile.put(classifier.charAt(0) == '-' ? classifier.substring(1) : classifier, file) != null) {
throw new InvalidUserDataException("Duplicate classifiers for " + this + " (\"" + file.getName().substring(start) + "\" in " + files + ')');
}
}
}
}
if (dependency.getGroup() != null && dependency.getVersion() != null) {
group = dependency.getGroup();
name = dependency.getName();
version = dependency.getVersion();
} else {
group = "net.fabricmc.synthetic";
File root = classifierToFile.get(""); //We've built the classifierToFile map, now to try find a name and version for our dependency
byte[] modJson;
try {
if ("jar".equals(FilenameUtils.getExtension(root.getName())) && (modJson = ZipUtils.unpackNullable(root.toPath(), "fabric.mod.json")) != null) {
//It's a Fabric mod, see how much we can extract out
JsonObject json = new Gson().fromJson(new String(modJson, StandardCharsets.UTF_8), JsonObject.class);
if (json == null || !json.has("id") || !json.has("version")) {
throw new IllegalArgumentException("Invalid Fabric mod jar: " + root + " (malformed json: " + json + ')');
}
if (json.has("name")) { //Go for the name field if it's got one
name = json.get("name").getAsString();
} else {
name = json.get("id").getAsString();
}
version = json.get("version").getAsString();
} else {
//Not a Fabric mod, just have to make something up
name = FilenameUtils.removeExtension(root.getName());
version = "1.0";
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to read input file: " + root, e);
}
}
}
@Override
public String getResolvedVersion() {
return version;
}
@Override
public String getDepString() {
//Use our custom name and version with the dummy group rather than the null:unspecified:null it would otherwise return
return group + ':' + name + ':' + version;
}
@Override
public String getResolvedDepString() {
return getDepString();
}
@Override
public Set<File> resolve() {
return this.resolvedFiles;
}
}

View File

@@ -29,15 +29,12 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.ExternalModuleDependency;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
@@ -45,106 +42,17 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.LoomRepositoryPlugin;
import net.fabricmc.loom.build.ModCompileRemapper;
import net.fabricmc.loom.configuration.DependencyProvider.DependencyInfo;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.ZipUtils;
public class LoomDependencyManager {
private static class ProviderList {
private final String key;
private final List<DependencyProvider> providers = new ArrayList<>();
ProviderList(String key) {
this.key = key;
}
}
private final List<DependencyProvider> dependencyProviderList = new ArrayList<>();
public <T extends DependencyProvider> T addProvider(T provider) {
if (dependencyProviderList.contains(provider)) {
throw new RuntimeException("Provider is already registered");
}
if (getProvider(provider.getClass()) != null) {
throw new RuntimeException("Provider of this type is already registered");
}
provider.register(this);
dependencyProviderList.add(provider);
return provider;
}
public <T> T getProvider(Class<? extends T> clazz) {
for (DependencyProvider provider : dependencyProviderList) {
if (provider.getClass() == clazz) {
return (T) provider;
}
}
return null;
}
public void handleDependencies(Project project) {
List<Runnable> afterTasks = new ArrayList<>();
MappingsProviderImpl mappingsProvider = null;
project.getLogger().info(":setting up loom dependencies");
LoomGradleExtension extension = LoomGradleExtension.get(project);
Map<String, ProviderList> providerListMap = new HashMap<>();
List<ProviderList> targetProviders = new ArrayList<>();
for (DependencyProvider provider : dependencyProviderList) {
providerListMap.computeIfAbsent(provider.getTargetConfig(), (k) -> {
ProviderList list = new ProviderList(k);
targetProviders.add(list);
return list;
}).providers.add(provider);
if (provider instanceof MappingsProviderImpl) {
mappingsProvider = (MappingsProviderImpl) provider;
}
}
if (mappingsProvider == null) {
throw new RuntimeException("Could not find MappingsProvider instance!");
}
for (ProviderList list : targetProviders) {
Configuration configuration = project.getConfigurations().getByName(list.key);
DependencySet dependencies = configuration.getDependencies();
if (dependencies.isEmpty()) {
throw new IllegalArgumentException(String.format("No '%s' dependency was specified!", list.key));
}
if (dependencies.size() > 1) {
throw new IllegalArgumentException(String.format("Only one '%s' dependency should be specified, but %d were!",
list.key,
dependencies.size())
);
}
for (Dependency dependency : dependencies) {
for (DependencyProvider provider : list.providers) {
DependencyProvider.DependencyInfo info = DependencyInfo.create(project, dependency, configuration);
try {
provider.provide(info, afterTasks::add);
} catch (Exception e) {
throw new RuntimeException("Failed to provide " + dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion() + " : " + e.toString() + "\n\tEnsure minecraft is not open and try running with --refresh-dependencies. Use --stacktrace to see the full stacktrace.", e);
}
}
}
}
SourceRemapper sourceRemapper = new SourceRemapper(project, true);
String platformSuffix = extension.isForge() ? "_forge" : "";
String mappingsIdentifier = mappingsProvider.mappingsIdentifier() + platformSuffix;
if (extension.getInstallerData() == null && !extension.isForge()) {
//If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking
@@ -168,15 +76,18 @@ public class LoomDependencyManager {
}
}
if (extension.getInstallerData() == null) {
project.getLogger().warn("fabric-installer.json not found in classpath!");
}
}
SourceRemapper sourceRemapper = new SourceRemapper(project, true);
String platformSuffix = extension.isForge() ? "_forge" : "";
String mappingsIdentifier = extension.getMappingsProvider().mappingsIdentifier() + platformSuffix;
ModCompileRemapper.remapDependencies(project, mappingsIdentifier, extension, sourceRemapper);
sourceRemapper.remapAll();
if (extension.getInstallerData() == null) {
project.getLogger().warn("fabric-installer.json not found in classpath!");
}
for (Runnable runnable : afterTasks) {
runnable.run();
}

View File

@@ -30,7 +30,6 @@ import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Arrays;
import com.google.common.hash.Hashing;
import org.gradle.api.Project;
@@ -39,6 +38,7 @@ import net.fabricmc.accesswidener.AccessWidener;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.ZipUtils;
public class AccessWidenerJarProcessor implements JarProcessor {
@@ -57,7 +57,7 @@ public class AccessWidenerJarProcessor implements JarProcessor {
@Override
public String getId() {
return "loom:access_widener";
return "loom:access_widener:" + Checksum.toHex(inputHash);
}
@Override
@@ -91,21 +91,4 @@ public class AccessWidenerJarProcessor implements JarProcessor {
throw new UncheckedIOException("Failed to write aw jar hash", e);
}
}
@Override
public boolean isInvalid(File file) {
byte[] hash;
try {
hash = ZipUtils.unpackNullable(file.toPath(), HASH_FILENAME);
} catch (IOException e) {
return true;
}
if (hash == null) {
return true;
}
return !Arrays.equals(inputHash, hash); // TODO how do we know if the current jar as the correct access applied? save the hash of the input?
}
}

View File

@@ -174,7 +174,10 @@ public class TransitiveAccessWidenerJarProcessor implements JarProcessor {
TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(project, "intermediary", "named");
tinyRemapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project));
tinyRemapper.readClassPath(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath());
for (Path minecraftJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
tinyRemapper.readClassPath(minecraftJar);
}
return tinyRemapper;
} catch (IOException e) {
@@ -182,12 +185,6 @@ public class TransitiveAccessWidenerJarProcessor implements JarProcessor {
}
}
@Override
public boolean isInvalid(File file) {
// The hash is handled by getId()
return false;
}
private static class TransitiveDetectorVisitor implements AccessWidenerVisitor {
private boolean transitive = false;

View File

@@ -0,0 +1,59 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.decompile;
import java.io.File;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskProvider;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.task.UnpickJarTask;
public abstract class DecompileConfiguration<T extends MappedMinecraftProvider> {
protected final Project project;
protected final T minecraftProvider;
protected final LoomGradleExtension extension;
protected final MappingsProviderImpl mappingsProvider;
public DecompileConfiguration(Project project, T minecraftProvider) {
this.project = project;
this.minecraftProvider = minecraftProvider;
this.extension = LoomGradleExtension.get(project);
this.mappingsProvider = extension.getMappingsProvider();
}
public abstract void afterEvaluation();
protected final TaskProvider<UnpickJarTask> createUnpickJarTask(String name, File inputJar, File outputJar) {
return project.getTasks().register(name, UnpickJarTask.class, unpickJarTask -> {
unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
unpickJarTask.getInputJar().set(inputJar);
unpickJarTask.getOutputJar().set(outputJar);
});
}
}

View File

@@ -0,0 +1,86 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.decompile;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.util.Constants;
public class SingleJarDecompileConfiguration extends DecompileConfiguration<MappedMinecraftProvider> {
public SingleJarDecompileConfiguration(Project project, MappedMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public final void afterEvaluation() {
List<Path> minecraftJars = minecraftProvider.getMinecraftJars();
assert minecraftJars.size() == 1;
final File namedJar = minecraftJars.get(0).toFile();
File mappedJar = namedJar;
if (mappingsProvider.hasUnpickDefinitions()) {
File outputJar = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar");
createUnpickJarTask("unpickJar", namedJar, outputJar);
mappedJar = outputJar;
}
final File inputJar = mappedJar;
LoomGradleExtension.get(project).getDecompilerOptions().forEach(options -> {
final String decompilerName = options.getName().substring(0, 1).toUpperCase() + options.getName().substring(1);
String taskName = "genSourcesWith" + decompilerName;
// Decompiler will be passed to the constructor of GenerateSourcesTask
project.getTasks().register(taskName, GenerateSourcesTask.class, options).configure(task -> {
task.getInputJar().set(inputJar);
task.getRuntimeJar().set(namedJar);
task.dependsOn(project.getTasks().named("validateAccessWidener"));
task.setDescription("Decompile minecraft using %s.".formatted(decompilerName));
task.setGroup(Constants.TaskGroup.FABRIC);
if (mappingsProvider.hasUnpickDefinitions()) {
task.dependsOn(project.getTasks().named("unpickJar"));
}
});
});
project.getTasks().register("genSources", task -> {
task.setDescription("Decompile minecraft using the default decompiler.");
task.setGroup(Constants.TaskGroup.FABRIC);
task.dependsOn(project.getTasks().named("genSourcesWithCfr"));
});
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.configuration.decompile;
import java.io.File;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.task.UnpickJarTask;
import net.fabricmc.loom.util.Constants;
public final class SplitDecompileConfiguration extends DecompileConfiguration<MappedMinecraftProvider.Split> {
public SplitDecompileConfiguration(Project project, MappedMinecraftProvider.Split minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public void afterEvaluation() {
File commonJarToDecompile = minecraftProvider.getCommonJar().toFile();
File clientOnlyJarToDecompile = minecraftProvider.getClientOnlyJar().toFile();
TaskProvider<UnpickJarTask> unpickCommonJar = null;
TaskProvider<UnpickJarTask> unpickClientOnlyJar = null;
if (mappingsProvider.hasUnpickDefinitions()) {
commonJarToDecompile = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar");
clientOnlyJarToDecompile = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar");
unpickCommonJar = createUnpickJarTask("unpickCommonJar", minecraftProvider.getCommonJar().toFile(), commonJarToDecompile);
unpickClientOnlyJar = createUnpickJarTask("unpickClientOnlyJar", minecraftProvider.getClientOnlyJar().toFile(), clientOnlyJarToDecompile);
}
// Need to re-declare them as final to access them from the lambada
final File commonJar = commonJarToDecompile;
final File clientOnlyJar = clientOnlyJarToDecompile;
final TaskProvider<UnpickJarTask> unpickCommonJarTask = unpickCommonJar;
final TaskProvider<UnpickJarTask> unpickClientOnlyJarTask = unpickClientOnlyJar;
final TaskProvider<Task> commonDecompileTask = createDecompileTasks("Common", task -> {
task.getInputJar().set(commonJar);
task.getRuntimeJar().set(minecraftProvider.getCommonJar().toFile());
if (unpickCommonJarTask != null) {
task.dependsOn(unpickCommonJarTask);
}
});
final TaskProvider<Task> clientOnlyDecompileTask = createDecompileTasks("ClientOnly", task -> {
task.getInputJar().set(clientOnlyJar);
task.getRuntimeJar().set(minecraftProvider.getClientOnlyJar().toFile());
if (unpickCommonJarTask != null) {
task.dependsOn(unpickClientOnlyJarTask);
}
// Don't allow them to run at the same time.
task.mustRunAfter(commonDecompileTask);
});
project.getTasks().register("genSources", task -> {
task.setDescription("Decompile minecraft using the default decompiler.");
task.setGroup(Constants.TaskGroup.FABRIC);
task.dependsOn(commonDecompileTask);
task.dependsOn(clientOnlyDecompileTask);
});
}
private TaskProvider<Task> createDecompileTasks(String name, Action<GenerateSourcesTask> configureAction) {
extension.getDecompilerOptions().forEach(options -> {
final String decompilerName = options.getName().substring(0, 1).toUpperCase() + options.getName().substring(1);
final String taskName = "gen%sSourcesWith%s".formatted(name, decompilerName);
project.getTasks().register(taskName, GenerateSourcesTask.class, options).configure(task -> {
configureAction.execute(task);
task.dependsOn(project.getTasks().named("validateAccessWidener"));
task.setDescription("Decompile minecraft using %s.".formatted(decompilerName));
task.setGroup(Constants.TaskGroup.FABRIC);
});
});
return project.getTasks().register("gen%sSources".formatted(name), task -> {
task.setDescription("Decompile minecraft (%s) using the default decompiler.".formatted(name));
task.setGroup(Constants.TaskGroup.FABRIC);
task.dependsOn(project.getTasks().named("gen%sSourcesWithCfr".formatted(name)));
});
}
}

View File

@@ -30,10 +30,12 @@ import org.gradle.api.Project;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.execution.taskgraph.TaskExecutionGraphInternal;
import net.fabricmc.loom.task.LoomTasks;
public class IdeaConfiguration {
public static void setup(Project project) {
TaskProvider<IdeaSyncTask> ideaSyncTask = project.getTasks().register("ideaSyncTask", IdeaSyncTask.class, ideaSyncTask1 -> {
ideaSyncTask1.dependsOn(project.getTasks().named("downloadAssets"));
TaskProvider<IdeaSyncTask> ideaSyncTask = project.getTasks().register("ideaSyncTask", IdeaSyncTask.class, task -> {
task.dependsOn(LoomTasks.getIDELaunchConfigureTaskName(project));
});
if (!IdeaUtils.isIdeaSync()) {

View File

@@ -29,7 +29,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -38,6 +37,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.gson.Gson;
@@ -52,8 +52,10 @@ import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.TinyRemapperHelper;
@@ -82,6 +84,12 @@ public class InterfaceInjectionProcessor implements JarProcessor {
return injectedInterfaces.isEmpty();
}
@Override
public String getId() {
Preconditions.checkArgument(!isEmpty());
return "loom:interface_injection:" + Checksum.toHex(inputHash);
}
@Override
public void setup() {
}
@@ -147,23 +155,6 @@ public class InterfaceInjectionProcessor implements JarProcessor {
};
}
@Override
public boolean isInvalid(File file) {
byte[] hash;
try {
hash = ZipUtils.unpackNullable(file.toPath(), HASH_FILENAME);
} catch (IOException e) {
return true;
}
if (hash == null) {
return true;
}
return !Arrays.equals(inputHash, hash);
}
private List<InjectedInterface> getInjectedInterfaces() {
List<InjectedInterface> result = new ArrayList<>();
@@ -272,7 +263,10 @@ public class InterfaceInjectionProcessor implements JarProcessor {
try {
TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(project, "intermediary", "named");
tinyRemapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project));
tinyRemapper.readClassPath(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath());
for (Path minecraftJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
tinyRemapper.readClassPath(minecraftJar);
}
return tinyRemapper;
} catch (IOException e) {

View File

@@ -58,7 +58,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.LoggerFilter;
@@ -149,12 +149,11 @@ public class ModProcessor {
private void remapJars(List<ModDependencyInfo> remapList) throws IOException {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final MinecraftMappedProvider mappedProvider = extension.getMinecraftMappedProvider();
final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm");
String fromM = extension.isForge() ? MappingsNamespace.SRG.toString() : MappingsNamespace.INTERMEDIARY.toString();
String toM = MappingsNamespace.NAMED.toString();
Path intermediaryJar = extension.isForge() ? mappedProvider.getSrgJar().toPath() : mappedProvider.getIntermediaryJar().toPath();
Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles()
.stream().map(File::toPath).toArray(Path[]::new);
@@ -163,12 +162,21 @@ public class ModProcessor {
MemoryMappingTree mappings = (fromM.equals("srg") || toM.equals("srg")) && extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings();
LoggerFilter.replaceSystemOut();
final TinyRemapper remapper = TinyRemapper.newRemapper()
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
.logger(project.getLogger()::lifecycle)
.logUnknownInvokeDynamic(false)
.withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false))
.renameInvalidLocals(false)
.build();
.renameInvalidLocals(false);
if (useKotlinExtension) {
builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE);
}
final TinyRemapper remapper = builder.build();
for (Path minecraftJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
remapper.readClassPathAsync(minecraftJar);
}
remapper.readClassPathAsync(intermediaryJar);

View File

@@ -33,11 +33,9 @@ public interface JarProcessor {
* <p>If the jar processor implementation class supports creating multiple jar processors with different effects,
* the needed configuration should also be included in this ID. Example: {@code path.to.MyJarProcessor#someOption}.
*
* @return the ID of this jar processor
* @return the unique ID of this jar processor
*/
default String getId() {
return getClass().getName();
}
String getId();
void setup();
@@ -45,9 +43,4 @@ public interface JarProcessor {
* Currently this is a destructive process that replaces the existing jar.
*/
void process(File file);
/**
* Return true to make all jar processors run again, return false to use the existing results of jar processing.
*/
boolean isInvalid(File file);
}

View File

@@ -83,7 +83,7 @@ public class JarProcessorManager {
throw new UncheckedIOException("Could not check jar manifest of " + file, e);
}
return jarProcessors.stream().anyMatch(jarProcessor -> jarProcessor.isInvalid(file));
return false;
}
private String getJarProcessorHash() {

View File

@@ -1,98 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2020-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.configuration.processors;
import java.io.File;
import java.io.IOException;
import java.util.function.Consumer;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.util.Constants;
public class MinecraftProcessedProvider extends MinecraftMappedProvider {
public final String projectMappedClassifier;
private File projectMappedJar;
private final JarProcessorManager jarProcessorManager;
public MinecraftProcessedProvider(Project project, JarProcessorManager jarProcessorManager) {
super(project);
this.jarProcessorManager = jarProcessorManager;
this.projectMappedClassifier = "project-" + project.getPath().replace(':', '@')
+ "-mapped";
}
@Override
protected void addDependencies(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) {
boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty();
if (jarProcessorManager.isInvalid(projectMappedJar) || isRefreshDeps() || isForgeAtDirty) {
getProject().getLogger().info(":processing mapped jar");
invalidateJar();
try {
FileUtils.copyFile(super.getMappedJar(), projectMappedJar);
} catch (IOException e) {
throw new RuntimeException("Failed to copy source jar", e);
}
jarProcessorManager.process(projectMappedJar);
}
getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED,
getProject().getDependencies().module("net.minecraft:" + minecraftProvider.getJarPrefix() + "minecraft-" + projectMappedClassifier + ":" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()));
}
private void invalidateJar() {
if (projectMappedJar.exists()) {
getProject().getLogger().warn("Invalidating project jar");
try {
FileUtils.forceDelete(projectMappedJar);
} catch (IOException e) {
throw new RuntimeException("Failed to invalidate jar, try stopping gradle daemon or closing the game", e);
}
}
}
@Override
public void initFiles(MinecraftProviderImpl minecraftProvider, MappingsProviderImpl mappingsProvider) {
super.initFiles(minecraftProvider, mappingsProvider);
projectMappedJar = new File(getDirectories().getRootProjectPersistentCache(), getMinecraftProvider().minecraftVersion() + "/"
+ getExtension().getMappingsProvider().mappingsIdentifier() + "/" + minecraftProvider.getJarPrefix() + "minecraft-" + projectMappedClassifier + ".jar");
}
@Override
public File getMappedJar() {
return projectMappedJar;
}
}

View File

@@ -1,238 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2019-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import org.gradle.api.logging.configuration.ConsoleOutput;
import org.gradle.api.plugins.JavaPlugin;
import net.fabricmc.loom.configuration.DependencyProvider;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.PropertyUtil;
public class LaunchProvider extends DependencyProvider {
public LaunchProvider(Project project) {
super(project);
}
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws IOException {
final String nativesPath = getExtension().getFiles().getNativesDirectory(getProject()).getAbsolutePath();
final LaunchConfig launchConfig = new LaunchConfig()
.property("fabric.development", "true")
.property("fabric.remapClasspathFile", getRemapClasspathFile().getAbsolutePath())
.property("log4j.configurationFile", getAllLog4JConfigFiles())
.property("log4j2.formatMsgNoLookups", "true")
.property("client", "java.library.path", nativesPath)
.property("client", "org.lwjgl.librarypath", nativesPath);
if (!getExtension().isForge()) {
launchConfig
.argument("client", "--assetIndex")
.argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion()))
.argument("client", "--assetsDir")
.argument("client", new File(getDirectories().getUserCache(), "assets").getAbsolutePath());
}
if (getExtension().isForge()) {
launchConfig
// Should match YarnNamingService.PATH_TO_MAPPINGS in forge-runtime
.property("fabric.yarnWithSrg.path", getExtension().getMappingsProvider().tinyMappingsWithSrg.toAbsolutePath().toString())
.argument("data", "--all")
.argument("data", "--mod")
.argument("data", String.join(",", getExtension().getForge().getDataGenMods()))
.argument("data", "--output")
.argument("data", getProject().file("src/generated/resources").getAbsolutePath())
.property("mixin.env.remapRefMap", "true");
if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) {
launchConfig.property("mixin.forgeloom.inject.mappings.srg-named", getExtension().getMappingsProvider().mixinTinyMappingsWithSrg.toAbsolutePath().toString());
} else {
launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.toAbsolutePath().toString());
}
Set<String> mixinConfigs = PropertyUtil.getAndFinalize(getExtension().getForge().getMixinConfigs());
if (!mixinConfigs.isEmpty()) {
for (String config : mixinConfigs) {
launchConfig.argument("-mixin.config");
launchConfig.argument(config);
}
}
}
addDependency(Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES);
addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES);
addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME);
if (getExtension().isForge()) {
addDependency(Constants.Dependencies.FORGE_RUNTIME + Constants.Dependencies.Versions.FORGE_RUNTIME, Constants.Configurations.FORGE_EXTRA);
addDependency(Constants.Dependencies.JAVAX_ANNOTATIONS + Constants.Dependencies.Versions.JAVAX_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME);
}
for (LaunchProviderSettings settings : getExtension().getLaunchConfigs()) {
settings.evaluateNow();
for (String argument : settings.getArguments()) {
launchConfig.argument(settings.getName(), argument);
}
for (Map.Entry<String, String> property : settings.getProperties()) {
launchConfig.property(settings.getName(), property.getKey(), property.getValue());
}
}
final boolean plainConsole = getProject().getGradle().getStartParameter().getConsoleOutput() == ConsoleOutput.Plain;
final boolean ansiSupportedIDE = new File(getProject().getRootDir(), ".vscode").exists()
|| new File(getProject().getRootDir(), ".idea").exists()
|| (Arrays.stream(getProject().getRootDir().listFiles()).anyMatch(file -> file.getName().endsWith(".iws")));
//Enable ansi by default for idea and vscode when gradle is not ran with plain console.
if (ansiSupportedIDE && !plainConsole) {
launchConfig.property("fabric.log.disableAnsi", "false");
}
writeLog4jConfig();
FileUtils.writeStringToFile(getDirectories().getDevLauncherConfig(), launchConfig.asString(), StandardCharsets.UTF_8);
postPopulationScheduler.accept(this::writeRemapClassPath);
}
private File getLog4jConfigFile() {
return getDirectories().getDefaultLog4jConfigFile();
}
private String getAllLog4JConfigFiles() {
return getExtension().getLog4jConfigs().getFiles().stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(","));
}
private File getRemapClasspathFile() {
return new File(getDirectories().getDevLauncherConfig().getParentFile(), "remapClasspath.txt");
}
private void writeLog4jConfig() {
try (InputStream is = LaunchProvider.class.getClassLoader().getResourceAsStream("log4j2.fabric.xml")) {
Files.deleteIfExists(getLog4jConfigFile().toPath());
Files.copy(is, getLog4jConfigFile().toPath());
} catch (IOException e) {
throw new RuntimeException("Failed to generate log4j config", e);
}
}
private void writeRemapClassPath() {
List<String> inputConfigurations = new ArrayList<>();
inputConfigurations.add(Constants.Configurations.LOADER_DEPENDENCIES);
inputConfigurations.addAll(Constants.MOD_COMPILE_ENTRIES.stream().map(RemappedConfigurationEntry::sourceConfiguration).collect(Collectors.toList()));
List<File> remapClasspath = new ArrayList<>();
for (String inputConfiguration : inputConfigurations) {
remapClasspath.addAll(getProject().getConfigurations().getByName(inputConfiguration).getFiles());
}
remapClasspath.add(getExtension().getMinecraftMappedProvider().getIntermediaryJar());
if (getExtension().isForgeAndNotOfficial()) {
remapClasspath.add(getExtension().getMinecraftMappedProvider().getForgeIntermediaryJar());
}
String str = remapClasspath.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator));
try {
Files.writeString(getRemapClasspathFile().toPath(), str);
} catch (IOException e) {
throw new RuntimeException("Failed to generate remap classpath", e);
}
}
@Override
public String getTargetConfig() {
return Constants.Configurations.MINECRAFT_NAMED;
}
public static class LaunchConfig {
private final Map<String, List<String>> values = new HashMap<>();
public LaunchConfig property(String key, String value) {
return property("common", key, value);
}
public LaunchConfig property(String side, String key, String value) {
values.computeIfAbsent(side + "Properties", (s -> new ArrayList<>()))
.add(String.format("%s=%s", key, value));
return this;
}
public LaunchConfig argument(String value) {
return argument("common", value);
}
public LaunchConfig argument(String side, String value) {
values.computeIfAbsent(side + "Args", (s -> new ArrayList<>()))
.add(value);
return this;
}
public String asString() {
StringJoiner stringJoiner = new StringJoiner("\n");
for (Map.Entry<String, List<String>> entry : values.entrySet()) {
stringJoiner.add(entry.getKey());
for (String s : entry.getValue()) {
stringJoiner.add("\t" + s);
}
}
return stringJoiner.toString();
}
}
}

View File

@@ -26,6 +26,7 @@ package net.fabricmc.loom.configuration.providers.mappings;
import java.io.File;
import java.nio.file.Path;
import java.util.function.Supplier;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
@@ -35,7 +36,8 @@ import org.gradle.api.logging.Logger;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingContext;
import net.fabricmc.loom.configuration.providers.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class GradleMappingContext implements MappingContext {
private final Project project;
@@ -62,8 +64,8 @@ public class GradleMappingContext implements MappingContext {
}
@Override
public MappingsProvider mappingsProvider() {
return extension.getMappingsProvider();
public Supplier<MemoryMappingTree> intermediaryTree() {
return () -> IntermediaryService.getInstance(project, minecraftProvider()).getMemoryMappingTree();
}
@Override

View File

@@ -0,0 +1,118 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.mappings;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.net.UrlEscapers;
import org.gradle.api.Project;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.format.Tiny2Reader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class IntermediaryService implements SharedService {
private static final Logger LOGGER = LoggerFactory.getLogger(IntermediaryService.class);
private final Path intermediaryTiny;
private final Supplier<MemoryMappingTree> memoryMappingTree = Suppliers.memoize(this::createMemoryMappingTree);
private IntermediaryService(Path intermediaryTiny) {
this.intermediaryTiny = intermediaryTiny;
}
public static synchronized IntermediaryService getInstance(Project project, MinecraftProvider minecraftProvider) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(minecraftProvider.minecraftVersion());
final String intermediaryArtifactUrl = extension.getIntermediaryUrl(encodedMinecraftVersion);
return SharedServiceManager.get(project).getOrCreateService("IntermediaryService:" + intermediaryArtifactUrl,
() -> create(intermediaryArtifactUrl, minecraftProvider));
}
@VisibleForTesting
public static IntermediaryService create(String intermediaryUrl, MinecraftProvider minecraftProvider) {
final Path intermediaryTiny = minecraftProvider.file("intermediary-v2.tiny").toPath();
if (!Files.exists(intermediaryTiny) || LoomGradlePlugin.refreshDeps) {
// Download and extract intermediary
File intermediaryJar = minecraftProvider.file("intermediary-v2.jar");
try {
DownloadUtil.downloadIfChanged(new URL(intermediaryUrl), intermediaryJar, LOGGER);
MappingsProviderImpl.extractMappings(intermediaryJar.toPath(), intermediaryTiny);
} catch (IOException e) {
throw new UncheckedIOException("Failed to download and extract intermediary", e);
}
}
return new IntermediaryService(intermediaryTiny);
}
private MemoryMappingTree createMemoryMappingTree() {
final MemoryMappingTree tree = new MemoryMappingTree();
try {
MappingNsCompleter nsCompleter = new MappingNsCompleter(tree, Collections.singletonMap(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), true);
try (BufferedReader reader = Files.newBufferedReader(getIntermediaryTiny(), StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, nsCompleter);
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to read intermediary mappings", e);
}
return tree;
}
public MemoryMappingTree getMemoryMappingTree() {
return memoryMappingTree.get();
}
public Path getIntermediaryTiny() {
return Objects.requireNonNull(intermediaryTiny, "Intermediary mappings have not been setup");
}
}

View File

@@ -29,146 +29,134 @@ import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.function.Supplier;
import com.google.common.base.Stopwatch;
import com.google.common.net.UrlEscapers;
import com.google.common.base.Suppliers;
import com.google.gson.JsonObject;
import org.apache.tools.ant.util.StringUtils;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.DependencyProvider;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider;
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.configuration.DependencyInfo;
import net.fabricmc.loom.configuration.providers.mappings.tiny.MappingsMerger;
import net.fabricmc.loom.configuration.providers.mappings.tiny.TinyJarInfo;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.srg.MCPReader;
import net.fabricmc.loom.util.srg.SrgMerger;
import net.fabricmc.loom.util.srg.SrgNamedWriter;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.format.Tiny2Reader;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.stitch.Command;
import net.fabricmc.stitch.commands.CommandProposeFieldNames;
import net.fabricmc.stitch.commands.tinyv2.TinyFile;
import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer;
public class MappingsProviderImpl extends DependencyProvider implements MappingsProvider {
public MinecraftMappedProvider mappedProvider;
public MinecraftPatchedProvider patchedProvider;
public class MappingsProviderImpl implements MappingsProvider, SharedService {
private static final Logger LOGGER = LoggerFactory.getLogger(MappingsProviderImpl.class);
public String mappingsIdentifier;
private Supplier<MemoryMappingTree> mappingTree;
public final String mappingsIdentifier;
private Path mappingsWorkingDir;
private Path intermediaryTiny;
private boolean hasRefreshed = false;
private final Path mappingsWorkingDir;
// The mappings that gradle gives us
private Path baseTinyMappings;
private final Path baseTinyMappings;
// The mappings we use in practice
public Path tinyMappings;
public Path tinyMappingsJar;
public Path tinyMappingsWithSrg;
public Path mixinTinyMappingsWithSrg; // FORGE: The mixin mappings have srg names in intermediary.
public Path srgToNamedSrg; // FORGE: srg to named in srg file format
private Path unpickDefinitions;
public final Path tinyMappings;
public final Path tinyMappingsJar;
private final Path unpickDefinitions;
private boolean hasUnpickDefinitions;
private UnpickMetadata unpickMetadata;
private MemoryMappingTree mappingTree;
private MemoryMappingTree mappingTreeWithSrg;
private Map<String, String> signatureFixes;
public MappingsProviderImpl(Project project) {
super(project);
private final Supplier<IntermediaryService> intermediaryService;
private MappingsProviderImpl(String mappingsIdentifier, Path mappingsWorkingDir, Supplier<IntermediaryService> intermediaryService) {
this.mappingsIdentifier = mappingsIdentifier;
this.mappingsWorkingDir = mappingsWorkingDir;
this.baseTinyMappings = mappingsWorkingDir.resolve("mappings-base.tiny");
this.tinyMappings = mappingsWorkingDir.resolve("mappings.tiny");
this.tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar");
this.unpickDefinitions = mappingsWorkingDir.resolve("mappings.unpick");
this.intermediaryService = intermediaryService;
}
public static synchronized MappingsProviderImpl getInstance(Project project, DependencyInfo dependency, MinecraftProvider minecraftProvider) {
return SharedServiceManager.get(project).getOrCreateService("MappingsProvider:%s:%s".formatted(dependency.getDepString(), minecraftProvider.minecraftVersion()), () -> {
Supplier<IntermediaryService> intermediaryService = Suppliers.memoize(() -> IntermediaryService.getInstance(project, minecraftProvider));
return create(dependency, minecraftProvider, intermediaryService);
});
}
public MemoryMappingTree getMappings() throws IOException {
return Objects.requireNonNull(mappingTree, "Cannot get mappings before they have been read");
return Objects.requireNonNull(mappingTree, "Cannot get mappings before they have been read").get();
}
public MemoryMappingTree getMappingsWithSrg() throws IOException {
return Objects.requireNonNull(mappingTreeWithSrg, "Cannot get mappings before they have been read");
private static MappingsProviderImpl create(DependencyInfo dependency, MinecraftProvider minecraftProvider, Supplier<IntermediaryService> intermediaryService) {
final String version = dependency.getResolvedVersion();
final Path inputJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve mappings: " + dependency)).toPath();
final String mappingsName = StringUtils.removeSuffix(dependency.getDependency().getGroup() + "." + dependency.getDependency().getName(), "-unmerged");
final TinyJarInfo jarInfo = TinyJarInfo.get(inputJar);
jarInfo.minecraftVersionId().ifPresent(id -> {
if (!minecraftProvider.minecraftVersion().equals(id)) {
LOGGER.warn("The mappings (%s) were not build for minecraft version (%s) produce with caution.".formatted(dependency.getDepString(), minecraftProvider.minecraftVersion()));
}
});
final String mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, jarInfo.v2()), minecraftProvider.minecraftVersion());
final Path workingDir = minecraftProvider.dir(mappingsIdentifier).toPath();
var mappingProvider = new MappingsProviderImpl(mappingsIdentifier, workingDir, intermediaryService);
try {
mappingProvider.setup(minecraftProvider, inputJar);
} catch (IOException e) {
cleanWorkingDirectory(workingDir);
throw new UncheckedIOException("Failed to setup mappings: " + dependency.getDepString(), e);
}
return mappingProvider;
}
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
MinecraftProviderImpl minecraftProvider = getDependencyManager().getProvider(MinecraftProviderImpl.class);
getProject().getLogger().info(":setting up mappings (" + dependency.getDependency().getName() + " " + dependency.getResolvedVersion() + ")");
String version = dependency.getResolvedVersion();
File mappingsJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not find yarn mappings: " + dependency));
String mappingsName = StringUtils.removeSuffix(dependency.getDependency().getGroup() + "." + dependency.getDependency().getName(), "-unmerged");
boolean isV2 = isV2(dependency, mappingsJar);
this.mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, isV2));
initFiles();
private void setup(MinecraftProvider minecraftProvider, Path inputJar) throws IOException {
if (isRefreshDeps()) {
cleanWorkingDirectory(mappingsWorkingDir);
}
if (Files.notExists(tinyMappings) || isRefreshDeps()) {
storeMappings(getProject(), minecraftProvider, mappingsJar.toPath(), postPopulationScheduler);
storeMappings(minecraftProvider, inputJar);
} else {
try (FileSystem fileSystem = FileSystems.newFileSystem(mappingsJar.toPath(), (ClassLoader) null)) {
try (FileSystem fileSystem = FileSystems.newFileSystem(inputJar, (ClassLoader) null)) {
extractExtras(fileSystem);
}
}
if (getExtension().isForge()) {
patchedProvider = new MinecraftPatchedProvider(getProject());
patchedProvider.provide(dependency, postPopulationScheduler);
}
mappingTree = readMappings(tinyMappings);
manipulateMappings(mappingsJar.toPath());
if (getExtension().shouldGenerateSrgTiny()) {
if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) {
// Merge tiny mappings with srg
SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true);
}
mappingTreeWithSrg = readMappings(tinyMappingsWithSrg);
}
if (Files.notExists(tinyMappingsJar) || isRefreshDeps()) {
Files.deleteIfExists(tinyMappingsJar);
ZipUtils.add(tinyMappingsJar, "mappings/mappings.tiny", Files.readAllBytes(tinyMappings));
}
mappingTree = Suppliers.memoize(this::readMappings);
}
public void applyToProject(Project project, DependencyInfo dependency) {
if (hasUnpickDefinitions()) {
String notation = String.format("%s:%s:%s:constants",
dependency.getDependency().getGroup(),
@@ -176,95 +164,14 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
dependency.getDependency().getVersion()
);
getProject().getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation);
populateUnpickClasspath();
project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation);
populateUnpickClasspath(project);
}
if (getExtension().isForge()) {
if (!getExtension().shouldGenerateSrgTiny()) {
throw new IllegalStateException("We have to generate srg tiny in a forge environment!");
}
if (Files.notExists(mixinTinyMappingsWithSrg) || isRefreshDeps()) {
List<String> lines = new ArrayList<>(Files.readAllLines(tinyMappingsWithSrg));
lines.set(0, lines.get(0).replace("intermediary", "yraidemretni").replace("srg", "intermediary"));
Files.deleteIfExists(mixinTinyMappingsWithSrg);
Files.write(mixinTinyMappingsWithSrg, lines);
}
if (Files.notExists(srgToNamedSrg) || isRefreshDeps()) {
SrgNamedWriter.writeTo(getProject().getLogger(), srgToNamedSrg, getMappingsWithSrg(), "srg", "named");
}
}
addDependency(getProject().getDependencies().module("loom.resolved:mappings:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()), Constants.Configurations.MAPPINGS_FINAL);
LoomGradleExtension extension = getExtension();
if (extension.getAccessWidenerPath().isPresent()) {
extension.getGameJarProcessors().add(new AccessWidenerJarProcessor(getProject()));
}
if (extension.getEnableTransitiveAccessWideners().get()) {
TransitiveAccessWidenerJarProcessor transitiveAccessWidenerJarProcessor = new TransitiveAccessWidenerJarProcessor(getProject());
if (!transitiveAccessWidenerJarProcessor.isEmpty()) {
extension.getGameJarProcessors().add(transitiveAccessWidenerJarProcessor);
}
}
if (extension.getEnableInterfaceInjection().get()) {
InterfaceInjectionProcessor jarProcessor = new InterfaceInjectionProcessor(getProject());
if (!jarProcessor.isEmpty()) {
extension.getGameJarProcessors().add(jarProcessor);
}
}
extension.getAccessWidenerPath().finalizeValue();
extension.getGameJarProcessors().finalizeValue();
JarProcessorManager processorManager = new JarProcessorManager(extension.getGameJarProcessors().get());
extension.setJarProcessorManager(processorManager);
processorManager.setupProcessors();
if (extension.isForge()) {
patchedProvider.finishProvide();
}
if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) {
mappedProvider = new MinecraftProcessedProvider(getProject(), processorManager);
getProject().getLogger().info("Using project based jar storage");
} else {
mappedProvider = new MinecraftMappedProvider(getProject());
}
mappedProvider.initFiles(minecraftProvider, this);
mappedProvider.provide(dependency, postPopulationScheduler);
project.getDependencies().add(Constants.Configurations.MAPPINGS_FINAL, project.files(tinyMappingsJar.toFile()));
}
protected Path getRawSrgFile() throws IOException {
LoomGradleExtension extension = getExtension();
if (extension.getSrgProvider().isTsrgV2()) {
return extension.getSrgProvider().getMergedMojangTrimmed();
}
return extension.getSrgProvider().getSrg();
}
public Path getMojmapSrgFileIfPossible() {
try {
LoomGradleExtension extension = getExtension();
return SrgProvider.getMojmapTsrg2(getProject(), extension);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public void manipulateMappings(Path mappingsJar) throws IOException {
}
private String getMappingsClassifier(DependencyInfo dependency, boolean isV2) {
private static String getMappingsClassifier(DependencyInfo dependency, boolean isV2) {
String[] depStringSplit = dependency.getDepString().split(":");
if (depStringSplit.length >= 4) {
@@ -274,122 +181,46 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
return isV2 ? "-v2" : "";
}
private boolean isV2(DependencyInfo dependency, File mappingsJar) throws IOException {
String minecraftVersion = getMinecraftProvider().minecraftVersion();
private void storeMappings(MinecraftProvider minecraftProvider, Path inputJar) throws IOException {
LOGGER.info(":extracting " + inputJar.getFileName());
// Only do this for official yarn, there isn't really a way we can get the mc version for all mappings
if (dependency.getDependency().getGroup() != null && dependency.getDependency().getGroup().equals("net.fabricmc") && dependency.getDependency().getName().equals("yarn") && dependency.getDependency().getVersion() != null) {
String yarnVersion = dependency.getDependency().getVersion();
char separator = yarnVersion.contains("+build.") ? '+' : yarnVersion.contains("-") ? '-' : '.';
String yarnMinecraftVersion = yarnVersion.substring(0, yarnVersion.lastIndexOf(separator));
if (!yarnMinecraftVersion.equalsIgnoreCase(minecraftVersion)) {
getProject().getLogger().warn("Minecraft Version ({}) does not match yarn's minecraft version ({})", minecraftVersion, yarnMinecraftVersion);
}
// We can save reading the zip file + header by checking the file name
return mappingsJar.getName().endsWith("-v2.jar") || mappingsJar.getName().endsWith("-mergedv2.jar");
} else {
return doesJarContainV2Mappings(mappingsJar.toPath());
}
}
private void storeMappings(Project project, MinecraftProviderImpl minecraftProvider, Path yarnJar, Consumer<Runnable> postPopulationScheduler)
throws IOException {
project.getLogger().info(":extracting " + yarnJar.getFileName());
if (isMCP(yarnJar)) {
try {
readAndMergeMCP(yarnJar, postPopulationScheduler);
} catch (Exception e) {
throw new RuntimeException(e);
}
return;
}
try (FileSystem fileSystem = FileSystems.newFileSystem(yarnJar, (ClassLoader) null)) {
try (FileSystem fileSystem = FileSystems.newFileSystem(inputJar, (ClassLoader) null)) {
extractMappings(fileSystem, baseTinyMappings);
extractExtras(fileSystem);
}
if (areMappingsMergedV2(baseTinyMappings)) {
// Architectury Loom Patch
// If a merged tiny v2 mappings file is provided
// Skip merging, should save a lot of time
Files.copy(baseTinyMappings, tinyMappings, StandardCopyOption.REPLACE_EXISTING);
} else if (areMappingsV2(baseTinyMappings)) {
if (areMappingsV2(baseTinyMappings)) {
// These are unmerged v2 mappings
mergeAndSaveMappings(project, baseTinyMappings, tinyMappings);
MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, intermediaryService.get());
} else {
// These are merged v1 mappings
Files.deleteIfExists(tinyMappings);
project.getLogger().lifecycle(":populating field names");
suggestFieldNames(minecraftProvider, baseTinyMappings, tinyMappings);
}
}
private static MemoryMappingTree readMappings(Path file) throws IOException {
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingReader.read(file, mappingTree);
return mappingTree;
}
private void readAndMergeMCP(Path mcpJar, Consumer<Runnable> postPopulationScheduler) throws Exception {
Path intermediaryTinyPath = getIntermediaryTiny();
SrgProvider provider = getExtension().getSrgProvider();
if (provider == null) {
if (!getExtension().shouldGenerateSrgTiny()) {
Configuration srg = getProject().getConfigurations().maybeCreate(Constants.Configurations.SRG);
srg.setTransitive(false);
if (minecraftProvider instanceof MergedMinecraftProvider mergedMinecraftProvider) {
// These are merged v1 mappings
Files.deleteIfExists(tinyMappings);
LOGGER.info(":populating field names");
suggestFieldNames(mergedMinecraftProvider, baseTinyMappings, tinyMappings);
} else {
throw new UnsupportedOperationException("V1 mappings only support merged minecraft");
}
provider = new SrgProvider(getProject());
getProject().getDependencies().add(provider.getTargetConfig(), "de.oceanlabs.mcp:mcp_config:" + getMinecraftProvider().minecraftVersion());
Configuration configuration = getProject().getConfigurations().getByName(provider.getTargetConfig());
provider.provide(DependencyInfo.create(getProject(), configuration.getDependencies().iterator().next(), configuration), postPopulationScheduler);
}
Path srgPath = getRawSrgFile();
TinyFile file = new MCPReader(intermediaryTinyPath, srgPath).read(mcpJar);
TinyV2Writer.write(file, tinyMappings);
}
private boolean isMCP(Path path) throws IOException {
try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) {
return Files.exists(fs.getPath("fields.csv")) && Files.exists(fs.getPath("methods.csv"));
private MemoryMappingTree readMappings() {
try {
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingReader.read(tinyMappings, mappingTree);
return mappingTree;
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings", e);
}
}
private static boolean areMappingsV2(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path)) {
return MappingReader.detectFormat(reader) == MappingFormat.TINY_2;
} catch (NoSuchFileException e) {
// TODO: just check the mappings version when Parser supports V1 in readMetadata()
return false;
}
}
private static boolean areMappingsMergedV2(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path)) {
return MappingReader.detectFormat(reader) == MappingFormat.TINY_2 && MappingReader.getNamespaces(reader).containsAll(Arrays.asList("named", "intermediary", "official"));
} catch (NoSuchFileException e) {
return false;
}
}
private static boolean doesJarContainV2Mappings(Path path) throws IOException {
try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) {
try (BufferedReader reader = Files.newBufferedReader(fs.getPath("mappings", "mappings.tiny"))) {
return MappingReader.detectFormat(reader) == MappingFormat.TINY_2;
}
} catch (NoSuchFileException e) {
return false;
}
}
private static void extractMappings(Path jar, Path extractTo) throws IOException {
public static void extractMappings(Path jar, Path extractTo) throws IOException {
try (FileSystem unmergedIntermediaryFs = FileSystems.newFileSystem(jar, (ClassLoader) null)) {
extractMappings(unmergedIntermediaryFs, extractTo);
}
@@ -444,9 +275,9 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
);
}
private void populateUnpickClasspath() {
private void populateUnpickClasspath(Project project) {
String unpickCliName = "unpick-cli";
getProject().getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH,
project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH,
String.format("%s:%s:%s", unpickMetadata.unpickGroup, unpickCliName, unpickMetadata.unpickVersion)
);
@@ -459,83 +290,17 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
};
for (String asm : asmDeps) {
getProject().getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH,
project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH,
asm.formatted(Opcodes.class.getPackage().getImplementationVersion())
);
}
}
private void mergeAndSaveMappings(Project project, Path from, Path out) throws IOException {
Stopwatch stopwatch = Stopwatch.createStarted();
project.getLogger().info(":merging mappings");
MemoryMappingTree tree = new MemoryMappingTree();
MappingSourceNsSwitch sourceNsSwitch = new MappingSourceNsSwitch(tree, MappingsNamespace.OFFICIAL.toString());
readIntermediaryTree().accept(sourceNsSwitch);
try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, tree);
}
inheritMappedNamesOfEnclosingClasses(tree);
try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) {
tree.accept(writer);
}
project.getLogger().info(":merged mappings in " + stopwatch.stop());
}
/**
* Searches the mapping tree for inner classes with no mapped name, whose enclosing classes have mapped names.
* Currently, Yarn does not export mappings for these inner classes.
*/
private void inheritMappedNamesOfEnclosingClasses(MemoryMappingTree tree) {
int intermediaryIdx = tree.getNamespaceId("intermediary");
int namedIdx = tree.getNamespaceId("named");
// The tree does not have an index by intermediary names by default
tree.setIndexByDstNames(true);
for (MappingTree.ClassMapping classEntry : tree.getClasses()) {
String intermediaryName = classEntry.getDstName(intermediaryIdx);
String namedName = classEntry.getDstName(namedIdx);
if (intermediaryName.equals(namedName) && intermediaryName.contains("$")) {
String[] path = intermediaryName.split(Pattern.quote("$"));
int parts = path.length;
for (int i = parts - 2; i >= 0; i--) {
String currentPath = String.join("$", Arrays.copyOfRange(path, 0, i + 1));
String namedParentClass = tree.mapClassName(currentPath, intermediaryIdx, namedIdx);
if (!namedParentClass.equals(currentPath)) {
classEntry.setDstName(namedParentClass
+ "$" + String.join("$", Arrays.copyOfRange(path, i + 1, path.length)),
namedIdx);
break;
}
}
}
}
}
private MemoryMappingTree readIntermediaryTree() throws IOException {
MemoryMappingTree tree = new MemoryMappingTree();
MappingNsCompleter nsCompleter = new MappingNsCompleter(tree, Collections.singletonMap(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), true);
try (BufferedReader reader = Files.newBufferedReader(getIntermediaryTiny(), StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, nsCompleter);
}
return tree;
}
private void suggestFieldNames(MinecraftProviderImpl minecraftProvider, Path oldMappings, Path newMappings) {
private void suggestFieldNames(MergedMinecraftProvider minecraftProvider, Path oldMappings, Path newMappings) {
Command command = new CommandProposeFieldNames();
runCommand(command, minecraftProvider.getMergedJar().getAbsolutePath(),
oldMappings.toAbsolutePath().toString(),
newMappings.toAbsolutePath().toString());
runCommand(command, minecraftProvider.getMergedJar().toFile().getAbsolutePath(),
oldMappings.toAbsolutePath().toString(),
newMappings.toAbsolutePath().toString());
}
private void runCommand(Command command, String... args) {
@@ -546,22 +311,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
}
private void initFiles() {
mappingsWorkingDir = getMinecraftProvider().dir(mappingsIdentifier).toPath();
baseTinyMappings = mappingsWorkingDir.resolve("mappings-base.tiny");
tinyMappings = mappingsWorkingDir.resolve("mappings.tiny");
tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar");
unpickDefinitions = mappingsWorkingDir.resolve("mappings.unpick");
tinyMappingsWithSrg = mappingsWorkingDir.resolve("mappings-srg.tiny");
mixinTinyMappingsWithSrg = mappingsWorkingDir.resolve("mappings-mixin-srg.tiny");
srgToNamedSrg = mappingsWorkingDir.resolve("mappings-srg-named.srg");
if (isRefreshDeps()) {
cleanFiles();
}
}
public void cleanFiles() {
private static void cleanWorkingDirectory(Path mappingsWorkingDir) {
try {
if (Files.exists(mappingsWorkingDir)) {
Files.walkFileTree(mappingsWorkingDir, new DeletingFileVisitor());
@@ -573,39 +323,20 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
}
@Override
public String getTargetConfig() {
return Constants.Configurations.MAPPINGS;
}
public Path getIntermediaryTiny() throws IOException {
if (intermediaryTiny == null) {
intermediaryTiny = getMinecraftProvider().file("intermediary-v2.tiny").toPath();
if (!Files.exists(intermediaryTiny) || (isRefreshDeps() && !hasRefreshed)) {
hasRefreshed = true;
// Download and extract intermediary
String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(getMinecraftProvider().minecraftVersion());
String intermediaryArtifactUrl = getExtension().getIntermediaryUrl(encodedMinecraftVersion);
File intermediaryJar = getMinecraftProvider().file("intermediary-v2.jar");
DownloadUtil.downloadIfChanged(new URL(intermediaryArtifactUrl), intermediaryJar, getProject().getLogger());
extractMappings(intermediaryJar.toPath(), intermediaryTiny);
}
}
return intermediaryTiny;
}
@Override
public Path mappingsWorkingDir() {
return mappingsWorkingDir;
}
protected String createMappingsIdentifier(String mappingsName, String version, String classifier) {
@Override
public File intermediaryTinyFile() {
return intermediaryService.get().getIntermediaryTiny().toFile();
}
private static String createMappingsIdentifier(String mappingsName, String version, String classifier, String minecraftVersion) {
// mappingsName . mcVersion . version classifier
// Example: net.fabricmc.yarn . 1_16_5 . 1.16.5+build.5 -v2
return mappingsName + "." + getMinecraftProvider().minecraftVersion().replace(' ', '_').replace('.', '_').replace('-', '_') + "." + version + classifier;
return mappingsName + "." + minecraftVersion.replace(' ', '_').replace('.', '_').replace('-', '_') + "." + version + classifier;
}
public String mappingsIdentifier() {
@@ -625,19 +356,19 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
return signatureFixes;
}
@Override
public File intermediaryTinyFile() {
try {
return getIntermediaryTiny().toFile();
} catch (IOException e) {
throw new RuntimeException("Failed to get intermediary", e);
}
}
public String getBuildServiceName(String name, String from, String to) {
return "%s:%s:%s>%S".formatted(name, mappingsIdentifier(), from, to);
}
public record UnpickMetadata(String unpickGroup, String unpickVersion) {
}
protected static boolean isRefreshDeps() {
return LoomGradlePlugin.refreshDeps;
}
@Override
public void close() throws IOException {
mappingTree = null;
}
}

View File

@@ -24,11 +24,7 @@
package net.fabricmc.loom.configuration.providers.mappings.intermediary;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections;
import java.util.function.Supplier;
@@ -36,9 +32,9 @@ import net.fabricmc.loom.api.mappings.layered.MappingLayer;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.format.Tiny2Reader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public record IntermediaryMappingLayer(Supplier<File> tinyFile) implements MappingLayer {
public record IntermediaryMappingLayer(Supplier<MemoryMappingTree> memoryMappingTree) implements MappingLayer {
@Override
public MappingsNamespace getSourceNamespace() {
return MappingsNamespace.OFFICIAL;
@@ -49,8 +45,6 @@ public record IntermediaryMappingLayer(Supplier<File> tinyFile) implements Mappi
// Populate named with intermediary and add Add a "named" namespace
MappingNsCompleter nsCompleter = new MappingNsCompleter(mappingVisitor, Collections.singletonMap(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), true);
try (BufferedReader reader = Files.newBufferedReader(tinyFile().get().toPath(), StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, nsCompleter);
}
memoryMappingTree.get().accept(nsCompleter);
}
}

View File

@@ -30,6 +30,6 @@ import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec;
public record IntermediaryMappingsSpec() implements MappingsSpec<IntermediaryMappingLayer> {
@Override
public IntermediaryMappingLayer createLayer(MappingContext context) {
return new IntermediaryMappingLayer(context.mappingsProvider()::intermediaryTinyFile);
return new IntermediaryMappingLayer(context.intermediaryTree());
}
}

View File

@@ -0,0 +1,110 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.mappings.tiny;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.regex.Pattern;
import com.google.common.base.Stopwatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryService;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.Tiny2Reader;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class MappingsMerger {
private static final Logger LOGGER = LoggerFactory.getLogger(MappingsMerger.class);
public static void mergeAndSaveMappings(Path from, Path out, IntermediaryService intermediaryService) throws IOException {
Stopwatch stopwatch = Stopwatch.createStarted();
LOGGER.info(":merging mappings");
MemoryMappingTree intermediaryTree = new MemoryMappingTree();
intermediaryService.getMemoryMappingTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString()));
try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, intermediaryTree);
}
MemoryMappingTree officialTree = new MemoryMappingTree();
MappingNsCompleter nsCompleter = new MappingNsCompleter(officialTree, Map.of(MappingsNamespace.OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString()));
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nsCompleter, MappingsNamespace.OFFICIAL.toString());
intermediaryTree.accept(nsSwitch);
inheritMappedNamesOfEnclosingClasses(officialTree);
try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) {
officialTree.accept(writer);
}
LOGGER.info(":merged mappings in " + stopwatch.stop());
}
/**
* Searches the mapping tree for inner classes with no mapped name, whose enclosing classes have mapped names.
* Currently, Yarn does not export mappings for these inner classes.
*/
private static void inheritMappedNamesOfEnclosingClasses(MemoryMappingTree tree) {
int intermediaryIdx = tree.getNamespaceId("intermediary");
int namedIdx = tree.getNamespaceId("named");
// The tree does not have an index by intermediary names by default
tree.setIndexByDstNames(true);
for (MappingTree.ClassMapping classEntry : tree.getClasses()) {
String intermediaryName = classEntry.getDstName(intermediaryIdx);
String namedName = classEntry.getDstName(namedIdx);
if (intermediaryName.equals(namedName) && intermediaryName.contains("$")) {
String[] path = intermediaryName.split(Pattern.quote("$"));
int parts = path.length;
for (int i = parts - 2; i >= 0; i--) {
String currentPath = String.join("$", Arrays.copyOfRange(path, 0, i + 1));
String namedParentClass = tree.mapClassName(currentPath, intermediaryIdx, namedIdx);
if (!namedParentClass.equals(currentPath)) {
classEntry.setDstName(namedParentClass
+ "$" + String.join("$", Arrays.copyOfRange(path, i + 1, path.length)),
namedIdx);
break;
}
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.mappings.tiny;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.format.MappingFormat;
public record TinyJarInfo(boolean v2, Optional<String> minecraftVersionId) {
public static TinyJarInfo get(Path jar) {
try {
return new TinyJarInfo(doesJarContainV2Mappings(jar), Optional.empty());
} catch (IOException e) {
throw new UncheckedIOException("Failed to read tiny jar info", e);
}
}
private static boolean doesJarContainV2Mappings(Path path) throws IOException {
try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) {
try (BufferedReader reader = Files.newBufferedReader(fs.getPath("mappings", "mappings.tiny"))) {
return MappingReader.detectFormat(reader) == MappingFormat.TINY_2;
}
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.configuration.providers.minecraft;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import org.gradle.api.Project;
import net.fabricmc.loom.util.HashedDownloadUtil;
import net.fabricmc.stitch.merge.JarMerger;
public final class MergedMinecraftProvider extends MinecraftProvider {
private Path minecraftMergedJar;
public MergedMinecraftProvider(Project project) {
super(project);
}
@Override
protected void initFiles() {
super.initFiles();
minecraftMergedJar = path("minecraft-merged.jar");
}
@Override
public List<Path> getMinecraftJars() {
return List.of(minecraftMergedJar);
}
@Override
public void provide() throws Exception {
super.provide();
if (!Files.exists(minecraftMergedJar) || isRefreshDeps()) {
try {
mergeJars();
} catch (Throwable e) {
HashedDownloadUtil.delete(getMinecraftClientJar());
HashedDownloadUtil.delete(getMinecraftServerJar());
Files.deleteIfExists(minecraftMergedJar);
getProject().getLogger().error("Could not merge JARs! Deleting source JARs - please re-run the command and move on.", e);
throw e;
}
}
}
private void mergeJars() throws IOException {
getLogger().info(":merging jars");
File jarToMerge = getMinecraftServerJar();
if (getServerBundleMetadata() != null) {
extractBundledServerJar();
jarToMerge = getMinecraftExtractedServerJar();
}
Objects.requireNonNull(jarToMerge, "Cannot merge null input jar?");
try (JarMerger jarMerger = new JarMerger(getMinecraftClientJar(), jarToMerge, minecraftMergedJar.toFile())) {
jarMerger.enableSyntheticParamsOffset();
jarMerger.merge();
}
}
public Path getMergedJar() {
return minecraftMergedJar;
}
}

View File

@@ -0,0 +1,115 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.gradle.api.Project;
import net.fabricmc.loom.configuration.decompile.DecompileConfiguration;
import net.fabricmc.loom.configuration.decompile.SingleJarDecompileConfiguration;
import net.fabricmc.loom.configuration.decompile.SplitDecompileConfiguration;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.ProcessedNamedMinecraftProvider;
public enum MinecraftJarConfiguration {
MERGED(
MergedMinecraftProvider::new,
IntermediaryMinecraftProvider.MergedImpl::new,
NamedMinecraftProvider.MergedImpl::new,
ProcessedNamedMinecraftProvider.MergedImpl::new,
SingleJarDecompileConfiguration::new,
List.of("client", "server")
),
SERVER_ONLY(
ServerOnlyMinecraftProvider::new,
IntermediaryMinecraftProvider.ServerOnlyImpl::new,
NamedMinecraftProvider.ServerOnlyImpl::new,
ProcessedNamedMinecraftProvider.ServerOnlyImpl::new,
SingleJarDecompileConfiguration::new,
List.of("server")
),
SPLIT(
SplitMinecraftProvider::new,
IntermediaryMinecraftProvider.SplitImpl::new,
NamedMinecraftProvider.SplitImpl::new,
ProcessedNamedMinecraftProvider.SplitImpl::new,
SplitDecompileConfiguration::new,
List.of("client", "server")
);
private final Function<Project, MinecraftProvider> minecraftProviderFunction;
private final BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>> intermediaryMinecraftProviderBiFunction;
private final BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>> namedMinecraftProviderBiFunction;
private final BiFunction<NamedMinecraftProvider<?>, JarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> processedNamedMinecraftProviderBiFunction;
private final BiFunction<Project, MappedMinecraftProvider, DecompileConfiguration<?>> decompileConfigurationBiFunction;
private final List<String> supportedEnvironments;
@SuppressWarnings("unchecked") // Just a bit of a generic mess :)
<M extends MinecraftProvider, P extends NamedMinecraftProvider<M>, Q extends MappedMinecraftProvider> MinecraftJarConfiguration(
Function<Project, M> minecraftProviderFunction,
BiFunction<Project, M, IntermediaryMinecraftProvider<M>> intermediaryMinecraftProviderBiFunction,
BiFunction<Project, M, P> namedMinecraftProviderBiFunction,
BiFunction<P, JarProcessorManager, ProcessedNamedMinecraftProvider<M, P>> processedNamedMinecraftProviderBiFunction,
BiFunction<Project, Q, DecompileConfiguration<?>> decompileConfigurationBiFunction,
List<String> supportedEnvironments
) {
this.minecraftProviderFunction = (Function<Project, MinecraftProvider>) minecraftProviderFunction;
this.intermediaryMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>>) (Object) intermediaryMinecraftProviderBiFunction;
this.namedMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>>) namedMinecraftProviderBiFunction;
this.processedNamedMinecraftProviderBiFunction = (BiFunction<NamedMinecraftProvider<?>, JarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>>) (Object) processedNamedMinecraftProviderBiFunction;
this.decompileConfigurationBiFunction = (BiFunction<Project, MappedMinecraftProvider, DecompileConfiguration<?>>) decompileConfigurationBiFunction;
this.supportedEnvironments = supportedEnvironments;
}
public Function<Project, MinecraftProvider> getMinecraftProviderFunction() {
return minecraftProviderFunction;
}
public BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>> getIntermediaryMinecraftProviderBiFunction() {
return intermediaryMinecraftProviderBiFunction;
}
public BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>> getNamedMinecraftProviderBiFunction() {
return namedMinecraftProviderBiFunction;
}
public BiFunction<NamedMinecraftProvider<?>, JarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> getProcessedNamedMinecraftProviderBiFunction() {
return processedNamedMinecraftProviderBiFunction;
}
public BiFunction<Project, MappedMinecraftProvider, DecompileConfiguration<?>> getDecompileConfigurationBiFunction() {
return decompileConfigurationBiFunction;
}
public List<String> getSupportedEnvironments() {
return supportedEnvironments;
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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.configuration.providers.minecraft;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Stream;
import com.google.common.collect.Sets;
import net.fabricmc.loom.util.FileSystemUtil;
public class MinecraftJarSplitter implements AutoCloseable {
private final Path clientInputJar;
private final Path serverInputJar;
private EntryData entryData;
private Set<String> sharedEntries = new HashSet<>();
private Set<String> forcedClientEntries = new HashSet<>();
public MinecraftJarSplitter(Path clientInputJar, Path serverInputJar) {
this.clientInputJar = Objects.requireNonNull(clientInputJar);
this.serverInputJar = Objects.requireNonNull(serverInputJar);
}
public void split(Path clientOnlyOutputJar, Path commonOutputJar) throws IOException {
Objects.requireNonNull(clientOnlyOutputJar);
Objects.requireNonNull(commonOutputJar);
if (entryData == null) {
entryData = new EntryData(getJarEntries(clientInputJar), getJarEntries(serverInputJar));
}
// Not something we expect, will require 3 jars, server, client and common.
assert entryData.serverOnlyEntries.isEmpty();
copyEntriesToJar(entryData.commonEntries, serverInputJar, commonOutputJar);
copyEntriesToJar(entryData.clientOnlyEntries, clientInputJar, clientOnlyOutputJar);
}
public void sharedEntry(String path) {
this.sharedEntries.add(path);
}
public void forcedClientEntry(String path) {
this.forcedClientEntries.add(path);
}
private Set<String> getJarEntries(Path input) throws IOException {
Set<String> entries = Sets.newHashSet();
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(input);
Stream<Path> walk = Files.walk(fs.get().getPath("/"))) {
Iterator<Path> iterator = walk.iterator();
while (iterator.hasNext()) {
Path fsPath = iterator.next();
if (!Files.isRegularFile(fsPath)) {
continue;
}
String entryPath = fs.get().getPath("/").relativize(fsPath).toString();
if (entryPath.startsWith("META-INF/")) {
continue;
}
entries.add(entryPath);
}
}
return entries;
}
private void copyEntriesToJar(Set<String> entries, Path inputJar, Path outputJar) throws IOException {
Files.deleteIfExists(outputJar);
try (FileSystemUtil.Delegate inputFs = FileSystemUtil.getJarFileSystem(inputJar);
FileSystemUtil.Delegate outputFs = FileSystemUtil.getJarFileSystem(outputJar, true)) {
for (String entry : entries) {
Path inputPath = inputFs.get().getPath(entry);
Path outputPath = outputFs.get().getPath(entry);
assert Files.isRegularFile(inputPath);
Path outputPathParent = outputPath.getParent();
if (outputPathParent != null) {
Files.createDirectories(outputPathParent);
}
Files.copy(inputPath, outputPath, StandardCopyOption.COPY_ATTRIBUTES);
}
writeManifest(outputFs);
}
}
private void writeManifest(FileSystemUtil.Delegate outputFs) throws IOException {
final Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
Files.createDirectories(outputFs.get().getPath("META-INF"));
Files.write(outputFs.get().getPath("META-INF/MANIFEST.MF"), out.toByteArray());
}
@Override
public void close() throws Exception {
}
private final class EntryData {
private final Set<String> clientEntries;
private final Set<String> serverEntries;
private final Set<String> commonEntries;
private final Set<String> clientOnlyEntries;
private final Set<String> serverOnlyEntries;
private EntryData(Set<String> clientEntries, Set<String> serverEntries) {
this.clientEntries = clientEntries;
this.serverEntries = serverEntries;
this.commonEntries = Sets.newHashSet(clientEntries);
this.commonEntries.retainAll(serverEntries);
this.commonEntries.addAll(sharedEntries);
this.commonEntries.removeAll(forcedClientEntries);
this.clientOnlyEntries = Sets.newHashSet(clientEntries);
this.clientOnlyEntries.removeAll(serverEntries);
this.clientOnlyEntries.addAll(sharedEntries);
this.clientOnlyEntries.addAll(forcedClientEntries);
this.serverOnlyEntries = Sets.newHashSet(serverEntries);
this.serverOnlyEntries.removeAll(clientEntries);
}
}
}

View File

@@ -30,16 +30,17 @@ import java.util.regex.Pattern;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ExternalModuleDependency;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.util.Constants;
public class MinecraftLibraryProvider {
private static final Pattern NATIVES_PATTERN = Pattern.compile("^(?<group>.*)/(.*?)/(?<version>.*)/((?<name>.*?)-([0-9].*?)-)(?<classifier>.*).jar$");
public void provide(MinecraftProviderImpl minecraftProvider, Project project) {
MinecraftVersionMeta versionInfo = minecraftProvider.getVersionInfo();
BundleMetadata serverBundleMetadata = minecraftProvider.getServerBundleMetadata();
public void provide(MinecraftProvider minecraftProvider, Project project) {
final MinecraftJarConfiguration jarConfiguration = LoomGradleExtension.get(project).getMinecraftJarConfiguration().get();
final MinecraftVersionMeta versionInfo = minecraftProvider.getVersionInfo();
final BundleMetadata serverBundleMetadata = minecraftProvider.getServerBundleMetadata();
final boolean overrideLWJGL = LWJGLVersionOverride.overrideByDefault() || LWJGLVersionOverride.forceOverride(project) || Boolean.getBoolean("loom.test.lwjgloverride");
@@ -55,7 +56,7 @@ public class MinecraftLibraryProvider {
if (library.isValidForOS() && !library.hasNatives() && library.artifact() != null) {
if (serverBundleMetadata != null && isLibraryInBundle(serverBundleMetadata, library)) {
project.getDependencies().add(Constants.Configurations.MINECRAFT_SERVER_DEPENDENCIES, library.name());
} else {
} else if (jarConfiguration.getSupportedEnvironments().contains("client")) {
// Client only library, or legacy version
project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, library.name());
}

View File

@@ -1,389 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-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.configuration.providers.minecraft;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import com.google.common.base.Stopwatch;
import dev.architectury.tinyremapper.IMappingProvider;
import dev.architectury.tinyremapper.InputTag;
import dev.architectury.tinyremapper.NonClassCopyMode;
import dev.architectury.tinyremapper.OutputConsumerPath;
import dev.architectury.tinyremapper.TinyRemapper;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.tuple.Triple;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.DependencyProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.tr.OutputRemappingHandler;
import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.OperatingSystem;
import net.fabricmc.loom.util.ThreadingUtils;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.srg.AtRemapper;
import net.fabricmc.loom.util.srg.CoreModClassRemapper;
import net.fabricmc.loom.util.srg.InnerClassRemapper;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class MinecraftMappedProvider extends DependencyProvider {
private File inputJar;
private File inputForgeJar;
private File minecraftMappedJar;
private File minecraftIntermediaryJar;
private File minecraftSrgJar;
private File forgeMappedJar;
private File forgeIntermediaryJar;
private File forgeSrgJar;
protected MinecraftProviderImpl minecraftProvider;
public MinecraftMappedProvider(Project project) {
super(project);
}
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
if (Files.notExists(getExtension().getMappingsProvider().tinyMappings)) {
throw new RuntimeException("mappings file not found");
}
if (!inputJar.exists()) {
throw new RuntimeException("input merged jar not found");
}
boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty();
boolean needToRemap = false;
if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || (getExtension().isForge() && !getSrgJar().exists()) || isRefreshDeps() || isForgeAtDirty) {
needToRemap = true;
}
if (getExtension().isForgeAndNotOfficial() && (!getForgeMappedJar().exists() || !getForgeIntermediaryJar().exists() || !getForgeSrgJar().exists() || isRefreshDeps() || isForgeAtDirty)) {
needToRemap = true;
}
if (needToRemap) {
if (minecraftMappedJar.exists()) {
minecraftMappedJar.delete();
}
minecraftMappedJar.getParentFile().mkdirs();
if (minecraftIntermediaryJar.exists()) {
minecraftIntermediaryJar.delete();
}
if (getExtension().isForge() && minecraftSrgJar.exists()) {
minecraftSrgJar.delete();
}
if (getExtension().isForgeAndNotOfficial()) {
if (getForgeMappedJar().exists()) {
getForgeMappedJar().delete();
}
getForgeMappedJar().getParentFile().mkdirs();
getForgeIntermediaryJar().delete();
getForgeSrgJar().delete();
}
try {
TinyRemapper[] remapperArray = new TinyRemapper[] {null};
mapMinecraftJar(remapperArray);
remapperArray[0].finish();
} catch (Throwable t) {
// Cleanup some some things that may be in a bad state now
DownloadUtil.delete(minecraftMappedJar);
DownloadUtil.delete(minecraftIntermediaryJar);
getExtension().getMinecraftProvider().deleteFiles();
if (getExtension().isForge()) {
DownloadUtil.delete(minecraftSrgJar);
DownloadUtil.delete(forgeMappedJar);
DownloadUtil.delete(forgeSrgJar);
DownloadUtil.delete(forgeIntermediaryJar);
}
getExtension().getMappingsProvider().cleanFiles();
throw new RuntimeException("Failed to remap minecraft", t);
}
}
if (!minecraftMappedJar.exists()) {
throw new RuntimeException("mapped jar not found");
}
addDependencies(dependency, postPopulationScheduler);
if (getExtension().isForgeAndNotOfficial()) {
getProject().getDependencies().add(Constants.Configurations.FORGE_NAMED,
getProject().getDependencies().module("net.minecraftforge-loom:forge-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier() + "/forge"));
}
if (getExtension().isForge()) {
getProject().afterEvaluate(project -> {
if (!OperatingSystem.isCIBuild()) {
try {
ForgeSourcesRemapper.addBaseForgeSources(project, getExtension().isForgeAndOfficial());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
private byte[][] inputBytes(Path input) throws IOException {
List<byte[]> inputByteList = new ArrayList<>();
try (FileSystemUtil.Delegate inputFs = FileSystemUtil.getJarFileSystem(input, false)) {
ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter();
for (Path path : (Iterable<? extends Path>) Files.walk(inputFs.get().getPath("/"))::iterator) {
if (Files.isRegularFile(path)) {
if (path.getFileName().toString().endsWith(".class")) {
taskCompleter.add(() -> {
byte[] bytes = Files.readAllBytes(path);
synchronized (inputByteList) {
inputByteList.add(bytes);
}
});
}
}
}
taskCompleter.complete();
}
return inputByteList.toArray(new byte[0][0]);
}
private void assetsOut(Path input, @Nullable Path assetsOut) throws IOException {
if (assetsOut != null) {
try (OutputConsumerPath tmpAssetsPath = new OutputConsumerPath.Builder(assetsOut).assumeArchive(true).build()) {
if (getExtension().isForge()) {
tmpAssetsPath.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, null);
} else {
tmpAssetsPath.addNonClassFiles(input);
}
}
}
}
private void mapMinecraftJar(TinyRemapper[] remapperArray) throws Exception {
Path input = inputJar.toPath();
Path inputForge = inputForgeJar == null ? null : inputForgeJar.toPath();
Path outputMapped = minecraftMappedJar.toPath();
Path outputIntermediary = minecraftIntermediaryJar.toPath();
Path outputSrg = minecraftSrgJar == null ? null : minecraftSrgJar.toPath();
Path forgeOutputMapped = forgeMappedJar == null ? null : forgeMappedJar.toPath();
Path forgeOutputIntermediary = forgeIntermediaryJar == null ? null : forgeIntermediaryJar.toPath();
Path forgeOutputSrg = forgeSrgJar == null ? null : forgeSrgJar.toPath();
Path vanillaAssets = Files.createTempFile("assets", null);
Files.deleteIfExists(vanillaAssets);
vanillaAssets.toFile().deleteOnExit();
Path forgeAssets = Files.createTempFile("assets", null);
Files.deleteIfExists(forgeAssets);
forgeAssets.toFile().deleteOnExit();
Info vanilla = new Info(vanillaAssets, input, outputMapped, outputIntermediary, outputSrg);
Info forge = getExtension().isForgeAndNotOfficial() ? new Info(forgeAssets, inputForge, forgeOutputMapped, forgeOutputIntermediary, forgeOutputSrg) : null;
Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> pair = TinyRemapperHelper.getTinyRemapper(getProject(), true, builder -> { });
TinyRemapper remapper = remapperArray[0] = pair.getLeft();
assetsOut(input, vanillaAssets);
if (getExtension().isForgeAndNotOfficial()) {
assetsOut(inputForge, forgeAssets);
}
remap(remapper, pair.getMiddle(), pair.getRight(), vanilla, forge, MappingsNamespace.OFFICIAL.toString());
}
public static class Info {
Path assets;
Path input;
Path outputMapped;
Path outputIntermediary;
Path outputSrg;
public Info(Path assets, Path input, Path outputMapped, Path outputIntermediary, Path outputSrg) {
this.assets = assets;
this.input = input;
this.outputMapped = outputMapped;
this.outputIntermediary = outputIntermediary;
this.outputSrg = outputSrg;
}
}
public void remap(TinyRemapper remapper, Mutable<MemoryMappingTree> mappings, List<TinyRemapper.ApplyVisitorProvider> postApply, Info vanilla, @Nullable Info forge, String fromM) throws IOException {
Set<String> classNames = getExtension().isForge() ? InnerClassRemapper.readClassNames(vanilla.input) : null;
for (String toM : getExtension().isForge() ? Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.SRG.toString(), MappingsNamespace.NAMED.toString()) : Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.NAMED.toString())) {
Path output = MappingsNamespace.NAMED.toString().equals(toM) ? vanilla.outputMapped : MappingsNamespace.SRG.toString().equals(toM) ? vanilla.outputSrg : vanilla.outputIntermediary;
Path outputForge = forge == null ? null : MappingsNamespace.NAMED.toString().equals(toM) ? forge.outputMapped : MappingsNamespace.SRG.toString().equals(toM) ? forge.outputSrg : forge.outputIntermediary;
InputTag vanillaTag = remapper.createInputTag();
InputTag forgeTag = remapper.createInputTag();
Stopwatch stopwatch = Stopwatch.createStarted();
getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")");
remapper.readInputs(vanillaTag, vanilla.input);
if (forge != null) {
remapper.readInputs(forgeTag, forge.input);
}
remapper.replaceMappings(getMappings(classNames, fromM, toM, mappings));
if (!MappingsNamespace.INTERMEDIARY.toString().equals(toM)) mappings.setValue(null);
postApply.clear();
postApply.add(new SignatureFixerApplyVisitor(SignatureFixerApplyVisitor.getRemappedSignatures(MappingsNamespace.INTERMEDIARY.toString().equals(toM), getExtension().getMappingsProvider(), getProject(), toM)));
OutputRemappingHandler.remap(remapper, vanilla.assets, output, null, vanillaTag);
if (forge != null) {
OutputRemappingHandler.remap(remapper, forge.assets, outputForge, null, forgeTag);
}
getProject().getLogger().lifecycle(":remapped minecraft (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch);
remapper.removeInput();
mappings.setValue(null);
if (getExtension().isForge() && !"srg".equals(toM)) {
getProject().getLogger().info(":running minecraft finalising tasks");
MemoryMappingTree yarnWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg();
AtRemapper.remap(getProject().getLogger(), output, yarnWithSrg);
CoreModClassRemapper.remapJar(output, yarnWithSrg, getProject().getLogger());
}
}
}
public Set<IMappingProvider> getMappings(@Nullable Set<String> fromClassNames, String fromM, String toM, Mutable<MemoryMappingTree> mappings) throws IOException {
Set<IMappingProvider> providers = new HashSet<>();
mappings.setValue(getExtension().isForge() ? getExtension().getMappingsProvider().getMappingsWithSrg() : getExtension().getMappingsProvider().getMappings());
providers.add(TinyRemapperHelper.create(mappings.getValue(), fromM, toM, true));
if (getExtension().isForge()) {
if (fromClassNames != null) {
providers.add(InnerClassRemapper.of(fromClassNames, getExtension().getMappingsProvider().getMappingsWithSrg(), fromM, toM));
}
} else {
providers.add(out -> TinyRemapperHelper.JSR_TO_JETBRAINS.forEach(out::acceptClass));
}
return providers;
}
protected void addDependencies(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) {
getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED,
getProject().getDependencies().module("net.minecraft:" + minecraftProvider.getJarPrefix() + "minecraft-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()));
}
public void initFiles(MinecraftProviderImpl minecraftProvider, MappingsProviderImpl mappingsProvider) {
this.minecraftProvider = minecraftProvider;
minecraftIntermediaryJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-intermediary.jar");
minecraftSrgJar = !getExtension().isForge() ? null : new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-srg.jar");
minecraftMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), minecraftProvider.getJarPrefix() + "minecraft-mapped.jar");
inputJar = getExtension().isForge() ? mappingsProvider.patchedProvider.getMergedJar() : minecraftProvider.getMergedJar();
if (getExtension().isForgeAndNotOfficial()) {
inputForgeJar = mappingsProvider.patchedProvider.getForgeMergedJar();
forgeIntermediaryJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-intermediary.jar");
forgeSrgJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-srg.jar");
forgeMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-mapped.jar");
} else {
inputForgeJar = null;
forgeIntermediaryJar = null;
forgeSrgJar = null;
forgeMappedJar = null;
}
}
protected File getJarDirectory(File parentDirectory, String type) {
return new File(parentDirectory, getJarVersionString(type));
}
protected String getJarVersionString(String type) {
return String.format("%s-%s%s", type, getExtension().getMappingsProvider().mappingsIdentifier(), minecraftProvider.getJarPrefix());
}
public File getIntermediaryJar() {
return minecraftIntermediaryJar;
}
public File getSrgJar() {
return minecraftSrgJar;
}
public File getMappedJar() {
return minecraftMappedJar;
}
public final File getBaseMappedJar() {
return minecraftMappedJar;
}
public File getForgeIntermediaryJar() {
return forgeIntermediaryJar;
}
public File getForgeSrgJar() {
return forgeSrgJar;
}
public File getForgeMappedJar() {
return forgeMappedJar;
}
public File getUnpickedJar() {
return new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar");
}
@Override
public String getTargetConfig() {
return Constants.Configurations.MINECRAFT_NAMED;
}
}

View File

@@ -22,35 +22,34 @@
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers;
package net.fabricmc.loom.configuration.providers.minecraft;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import com.google.common.base.Stopwatch;
import com.google.common.io.Files;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.DependencyProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ManifestVersion;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftLibraryProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import net.fabricmc.loom.configuration.DependencyInfo;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.HashedDownloadUtil;
import net.fabricmc.loom.util.MirrorUtil;
import net.fabricmc.stitch.merge.JarMerger;
public class MinecraftProviderImpl extends DependencyProvider implements MinecraftProvider {
public abstract class MinecraftProvider {
private String minecraftVersion;
private MinecraftVersionMeta versionInfo;
@@ -58,30 +57,26 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
private File workingDir;
private File minecraftJson;
public File minecraftClientJar;
private File minecraftClientJar;
// Note this will be the boostrap jar starting with 21w39a
public File minecraftServerJar;
private File minecraftServerJar;
// The extracted server jar from the boostrap, only exists in >=21w39a
public File minecraftExtractedServerJar;
private File minecraftExtractedServerJar;
@Nullable
public BundleMetadata serverBundleMetadata;
private File minecraftMergedJar;
private BundleMetadata serverBundleMetadata;
private File versionManifestJson;
private File experimentalVersionsJson;
private String jarPrefix = "";
public MinecraftProviderImpl(Project project) {
super(project);
private final Project project;
public MinecraftProvider(Project project) {
this.project = project;
}
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
public void provide() throws Exception {
final DependencyInfo dependency = DependencyInfo.create(getProject(), Constants.Configurations.MINECRAFT);
minecraftVersion = dependency.getDependency().getVersion();
if (getExtension().shouldGenerateSrgTiny() && !getExtension().isForge()) {
addDependency("de.oceanlabs.mcp:mcp_config:" + minecraftVersion, Constants.Configurations.SRG);
}
boolean offline = getProject().getGradle().getStartParameter().isOffline();
initFiles();
@@ -95,9 +90,6 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
if (offline) {
if (minecraftClientJar.exists() && minecraftServerJar.exists()) {
getProject().getLogger().debug("Found client and server jars, presuming up-to-date");
} else if (minecraftMergedJar.exists()) {
//Strictly we don't need the split jars if the merged one exists, let's try go on
getProject().getLogger().warn("Missing game jar but merged jar present, things might end badly");
} else {
throw new GradleException("Missing jar(s); Client: " + minecraftClientJar.exists() + ", Server: " + minecraftServerJar.exists());
}
@@ -109,39 +101,17 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
libraryProvider = new MinecraftLibraryProvider();
libraryProvider.provide(this, getProject());
if (!minecraftMergedJar.exists() || isRefreshDeps()) {
try {
mergeJars(getProject().getLogger());
} catch (Throwable e) {
HashedDownloadUtil.delete(minecraftClientJar);
HashedDownloadUtil.delete(minecraftServerJar);
minecraftMergedJar.delete();
getProject().getLogger().error("Could not merge JARs! Deleting source JARs - please re-run the command and move on.", e);
throw e;
}
}
}
private void initFiles() {
workingDir = new File(getDirectories().getUserCache(), minecraftVersion);
protected void initFiles() {
workingDir = new File(getExtension().getFiles().getUserCache(), minecraftVersion);
workingDir.mkdirs();
minecraftJson = file("minecraft-info.json");
minecraftClientJar = file("minecraft-client.jar");
minecraftServerJar = file("minecraft-server.jar");
minecraftExtractedServerJar = file("minecraft-extracted_server.jar");
minecraftMergedJar = file("minecraft-merged.jar");
versionManifestJson = new File(getDirectories().getUserCache(), "version_manifest.json");
experimentalVersionsJson = new File(getDirectories().getUserCache(), "experimental_version_manifest.json");
}
public void deleteFiles() {
DownloadUtil.delete(minecraftClientJar);
DownloadUtil.delete(minecraftServerJar);
DownloadUtil.delete(minecraftMergedJar);
DownloadUtil.delete(versionManifestJson);
DownloadUtil.delete(experimentalVersionsJson);
versionManifestJson = new File(getExtension().getFiles().getUserCache(), "version_manifest.json");
experimentalVersionsJson = new File(getExtension().getFiles().getUserCache(), "experimental_version_manifest.json");
}
private void downloadMcJson(boolean offline) throws IOException {
@@ -268,58 +238,55 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
HashedDownloadUtil.downloadIfInvalid(new URL(server.url()), minecraftServerJar, server.sha1(), logger, false);
}
private void mergeJars(Logger logger) throws IOException {
logger.info(":merging jars");
Stopwatch stopwatch = Stopwatch.createStarted();
protected final void extractBundledServerJar() throws IOException {
Objects.requireNonNull(getServerBundleMetadata(), "Cannot bundled mc jar from none bundled server jar");
File jarToMerge = minecraftServerJar;
getLogger().info(":Extracting server jar from bootstrap");
if (serverBundleMetadata != null) {
logger.info(":Extracting server jar from bootstrap");
if (serverBundleMetadata.versions().size() != 1) {
throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(serverBundleMetadata.versions().size()));
}
serverBundleMetadata.versions().get(0).unpackEntry(minecraftServerJar.toPath(), minecraftExtractedServerJar.toPath());
jarToMerge = minecraftExtractedServerJar;
if (getServerBundleMetadata().versions().size() != 1) {
throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(getServerBundleMetadata().versions().size()));
}
try (JarMerger jarMerger = new JarMerger(minecraftClientJar, jarToMerge, minecraftMergedJar)) {
jarMerger.enableSyntheticParamsOffset();
jarMerger.merge();
}
logger.info(":merged jars in " + stopwatch);
getServerBundleMetadata().versions().get(0).unpackEntry(minecraftServerJar.toPath(), getMinecraftExtractedServerJar().toPath());
}
public File getMergedJar() {
return minecraftMergedJar;
}
@Override
public File workingDir() {
return workingDir;
}
@Override
public File dir(String path) {
File dir = file(path);
dir.mkdirs();
return dir;
}
@Override
public File file(String path) {
return new File(workingDir(), path);
}
@Override
public Path path(String path) {
return file(path).toPath();
}
public File getMinecraftClientJar() {
return minecraftClientJar;
}
// May be null on older versions
@Nullable
public File getMinecraftExtractedServerJar() {
return minecraftExtractedServerJar;
}
// This may be the server bundler jar on newer versions prob not what you want.
public File getMinecraftServerJar() {
return minecraftServerJar;
}
public String minecraftVersion() {
return minecraftVersion;
}
@Override
public MinecraftVersionMeta getVersionInfo() {
return versionInfo;
}
@@ -328,15 +295,6 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
return libraryProvider;
}
public String getJarPrefix() {
return jarPrefix;
}
public void setJarPrefix(String jarSuffix) {
this.jarPrefix = jarSuffix;
}
@Override
public String getTargetConfig() {
return Constants.Configurations.MINECRAFT;
}
@@ -345,4 +303,22 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
public BundleMetadata getServerBundleMetadata() {
return serverBundleMetadata;
}
protected Logger getLogger() {
return getProject().getLogger();
}
public abstract List<Path> getMinecraftJars();
protected Project getProject() {
return project;
}
protected LoomGradleExtension getExtension() {
return LoomGradleExtension.get(getProject());
}
protected boolean isRefreshDeps() {
return LoomGradlePlugin.refreshDeps;
}
}

View File

@@ -0,0 +1,102 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.gradle.api.Project;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
import net.fabricmc.tinyremapper.NonClassCopyMode;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
public final class ServerOnlyMinecraftProvider extends MinecraftProvider {
private Path minecraftServerOnlyJar;
public ServerOnlyMinecraftProvider(Project project) {
super(project);
}
@Override
protected void initFiles() {
super.initFiles();
minecraftServerOnlyJar = path("minecraft-server-only.jar");
}
@Override
public List<Path> getMinecraftJars() {
return List.of(minecraftServerOnlyJar);
}
@Override
public void provide() throws Exception {
super.provide();
boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftServerOnlyJar);
if (!requiresRefresh) {
return;
}
BundleMetadata serverBundleMetadata = getServerBundleMetadata();
if (serverBundleMetadata == null) {
throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar support server only configuration, please use a merged jar setup for this version of minecraft");
}
extractBundledServerJar();
final Path serverJar = getMinecraftExtractedServerJar().toPath();
TinyRemapper remapper = null;
try {
remapper = TinyRemapper.newRemapper().build();
Files.deleteIfExists(minecraftServerOnlyJar);
// Pass through tiny remapper to fix the meta-inf
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(minecraftServerOnlyJar).build()) {
outputConsumer.addNonClassFiles(serverJar, NonClassCopyMode.FIX_META_INF, remapper);
remapper.readInputs(serverJar);
remapper.apply(outputConsumer);
}
} catch (Exception e) {
Files.deleteIfExists(minecraftServerOnlyJar);
throw new RuntimeException("Failed to process server only jar", e);
} finally {
if (remapper != null) {
remapper.finish();
}
}
}
public Path getMinecraftServerOnlyJar() {
return minecraftServerOnlyJar;
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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.configuration.providers.minecraft;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.gradle.api.Project;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
public final class SplitMinecraftProvider extends MinecraftProvider {
private Path minecraftClientOnlyJar;
private Path minecraftCommonJar;
public SplitMinecraftProvider(Project project) {
super(project);
}
@Override
protected void initFiles() {
super.initFiles();
minecraftClientOnlyJar = path("minecraft-client-only.jar");
minecraftCommonJar = path("minecraft-common.jar");
}
@Override
public List<Path> getMinecraftJars() {
return List.of(minecraftClientOnlyJar, minecraftCommonJar);
}
@Override
public void provide() throws Exception {
super.provide();
boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftClientOnlyJar) || Files.notExists(minecraftCommonJar);
if (!requiresRefresh) {
return;
}
BundleMetadata serverBundleMetadata = getServerBundleMetadata();
if (serverBundleMetadata == null) {
throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar can be split, please use a merged jar setup for this version of minecraft");
}
extractBundledServerJar();
final Path clientJar = getMinecraftClientJar().toPath();
final Path serverJar = getMinecraftExtractedServerJar().toPath();
try (MinecraftJarSplitter jarSplitter = new MinecraftJarSplitter(clientJar, serverJar)) {
// Required for loader to compute the version info also useful to have in both jars.
jarSplitter.sharedEntry("version.json");
jarSplitter.forcedClientEntry("assets/.mcassetsroot");
jarSplitter.split(minecraftClientOnlyJar, minecraftCommonJar);
} catch (Exception e) {
Files.deleteIfExists(minecraftClientOnlyJar);
Files.deleteIfExists(minecraftCommonJar);
throw new RuntimeException("Failed to split minecraft", e);
}
}
public Path getMinecraftClientOnlyJar() {
return minecraftClientOnlyJar;
}
public Path getMinecraftCommonJar() {
return minecraftCommonJar;
}
}

View File

@@ -43,13 +43,13 @@ import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import net.fabricmc.loom.util.MirrorUtil;
import net.fabricmc.loom.util.HashedDownloadUtil;
public class MinecraftAssetsProvider {
public static void provide(MinecraftProviderImpl minecraftProvider, Project project) throws IOException {
public static void provide(MinecraftProvider minecraftProvider, Project project) throws IOException {
LoomGradleExtension extension = LoomGradleExtension.get(project);
boolean offline = project.getGradle().getStartParameter().isOffline();

View File

@@ -0,0 +1,166 @@
/*
* 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.configuration.providers.minecraft.mapped;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SignatureFixerApplyVisitor;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvider> implements MappedMinecraftProvider.ProviderImpl {
protected final M minecraftProvider;
private final Project project;
protected final LoomGradleExtension extension;
public AbstractMappedMinecraftProvider(Project project, M minecraftProvider) {
this.project = project;
this.minecraftProvider = minecraftProvider;
this.extension = LoomGradleExtension.get(project);
}
public abstract MappingsNamespace getTargetNamespace();
public abstract List<RemappedJars> getRemappedJars();
protected void applyDependencies(BiConsumer<String, String> consumer) {
// Override if needed
}
public void provide(boolean applyDependencies) throws Exception {
final List<RemappedJars> remappedJars = getRemappedJars();
assert !remappedJars.isEmpty();
if (!areOutputsValid(remappedJars) || LoomGradlePlugin.refreshDeps) {
try {
remapInputs(remappedJars);
} catch (Throwable t) {
cleanOutputs(remappedJars);
throw new RuntimeException("Failed to remap minecraft", t);
}
}
if (applyDependencies) {
applyDependencies((configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)));
}
}
protected abstract Path getDirectory();
@Override
public Path getJar(String name) {
return getDirectory().resolve(getName(name) + ".jar");
}
protected String getName(String name) {
return "minecraft-%s-%s".formatted(name, getTargetNamespace().toString());
}
protected String getDependencyNotation(String name) {
return "net.minecraft:%s:%s/%s".formatted(getName(name), extension.getMinecraftProvider().minecraftVersion(), extension.getMappingsProvider().mappingsIdentifier());
}
private boolean areOutputsValid(List<RemappedJars> remappedJars) {
for (RemappedJars remappedJar : remappedJars) {
if (!Files.exists(remappedJar.outputJar())) {
return false;
}
}
return true;
}
private void remapInputs(List<RemappedJars> remappedJars) throws IOException {
cleanOutputs(remappedJars);
for (RemappedJars remappedJar : remappedJars) {
remapJar(remappedJar);
}
}
private void remapJar(RemappedJars remappedJars) throws IOException {
final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
final String fromM = remappedJars.sourceNamespace().toString();
final String toM = getTargetNamespace().toString();
Files.deleteIfExists(remappedJars.outputJar());
final Map<String, String> remappedSignatures = SignatureFixerApplyVisitor.getRemappedSignatures(getTargetNamespace() == MappingsNamespace.INTERMEDIARY, mappingsProvider, project, toM);
TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(project, fromM, toM, true, (builder) -> {
builder.extraPostApplyVisitor(new SignatureFixerApplyVisitor(remappedSignatures));
configureRemapper(remappedJars, builder);
});
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(remappedJars.outputJar()).build()) {
outputConsumer.addNonClassFiles(remappedJars.inputJar());
remapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project));
for (Path path : remappedJars.remapClasspath()) {
remapper.readClassPath(path);
}
remapper.readInputs(remappedJars.inputJar());
remapper.apply(outputConsumer);
} catch (Exception e) {
throw new RuntimeException("Failed to remap JAR " + remappedJars.inputJar() + " with mappings from " + mappingsProvider.tinyMappings, e);
} finally {
remapper.finish();
}
}
protected void configureRemapper(RemappedJars remappedJars, TinyRemapper.Builder tinyRemapperBuilder) {
}
private void cleanOutputs(List<RemappedJars> remappedJars) throws IOException {
for (RemappedJars remappedJar : remappedJars) {
Files.deleteIfExists(remappedJar.outputJar());
}
}
public Project getProject() {
return project;
}
public M getMinecraftProvider() {
return minecraftProvider;
}
public record RemappedJars(Path inputJar, Path outputJar, MappingsNamespace sourceNamespace, Path... remapClasspath) {
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.configuration.providers.minecraft.mapped;
import java.nio.file.Path;
import java.util.List;
import org.gradle.api.Project;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
import net.fabricmc.loom.util.SidedClassVisitor;
import net.fabricmc.tinyremapper.TinyRemapper;
public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.ServerOnlyImpl, IntermediaryMinecraftProvider.SplitImpl {
public IntermediaryMinecraftProvider(Project project, M minecraftProvider) {
super(project, minecraftProvider);
}
@Override
protected Path getDirectory() {
return extension.getMinecraftProvider().workingDir().toPath();
}
@Override
public final MappingsNamespace getTargetNamespace() {
return MappingsNamespace.INTERMEDIARY;
}
public static final class MergedImpl extends IntermediaryMinecraftProvider<MergedMinecraftProvider> implements Merged {
public MergedImpl(Project project, MergedMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMergedJar(), getMergedJar(), MappingsNamespace.OFFICIAL)
);
}
}
public static final class SplitImpl extends IntermediaryMinecraftProvider<SplitMinecraftProvider> implements Split {
public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftCommonJar(), getCommonJar(), MappingsNamespace.OFFICIAL),
new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar(), getClientOnlyJar(), MappingsNamespace.OFFICIAL, minecraftProvider.getMinecraftCommonJar())
);
}
@Override
protected void configureRemapper(RemappedJars remappedJars, TinyRemapper.Builder tinyRemapperBuilder) {
if (remappedJars.outputJar().equals(getClientOnlyJar())) {
tinyRemapperBuilder.extraPostApplyVisitor(SidedClassVisitor.CLIENT);
}
}
}
public static final class ServerOnlyImpl extends IntermediaryMinecraftProvider<ServerOnlyMinecraftProvider> implements ServerOnly {
public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL)
);
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.configuration.providers.minecraft.mapped;
import java.nio.file.Path;
import java.util.List;
public interface MappedMinecraftProvider {
List<Path> getMinecraftJars();
interface ProviderImpl extends MappedMinecraftProvider {
Path getJar(String name);
}
interface Merged extends ProviderImpl {
String MERGED = "merged";
default Path getMergedJar() {
return getJar(MERGED);
}
@Override
default List<Path> getMinecraftJars() {
return List.of(getMergedJar());
}
}
interface Split extends ProviderImpl {
String COMMON = "common";
String CLIENT_ONLY = "clientOnly";
default Path getCommonJar() {
return getJar(COMMON);
}
default Path getClientOnlyJar() {
return getJar(CLIENT_ONLY);
}
@Override
default List<Path> getMinecraftJars() {
return List.of(getCommonJar(), getClientOnlyJar());
}
}
interface ServerOnly extends ProviderImpl {
String SERVER_ONLY = "serverOnly";
default Path getServerOnlyJar() {
return getJar(SERVER_ONLY);
}
@Override
default List<Path> getMinecraftJars() {
return List.of(getServerOnlyJar());
}
}
}

View File

@@ -0,0 +1,119 @@
/*
* 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.configuration.providers.minecraft.mapped;
import java.nio.file.Path;
import java.util.List;
import java.util.function.BiConsumer;
import org.gradle.api.Project;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.SidedClassVisitor;
import net.fabricmc.tinyremapper.TinyRemapper;
public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> {
public NamedMinecraftProvider(Project project, M minecraftProvider) {
super(project, minecraftProvider);
}
@Override
protected Path getDirectory() {
return extension.getMappingsProvider().mappingsWorkingDir();
}
@Override
public final MappingsNamespace getTargetNamespace() {
return MappingsNamespace.NAMED;
}
public static final class MergedImpl extends NamedMinecraftProvider<MergedMinecraftProvider> implements Merged {
public MergedImpl(Project project, MergedMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMergedJar(), getMergedJar(), MappingsNamespace.OFFICIAL)
);
}
@Override
protected void applyDependencies(BiConsumer<String, String> consumer) {
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, MERGED);
}
}
public static final class SplitImpl extends NamedMinecraftProvider<SplitMinecraftProvider> implements Split {
public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftCommonJar(), getCommonJar(), MappingsNamespace.OFFICIAL),
new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar(), getClientOnlyJar(), MappingsNamespace.OFFICIAL, minecraftProvider.getMinecraftCommonJar())
);
}
@Override
protected void configureRemapper(RemappedJars remappedJars, TinyRemapper.Builder tinyRemapperBuilder) {
if (remappedJars.outputJar().equals(getClientOnlyJar())) {
tinyRemapperBuilder.extraPostApplyVisitor(SidedClassVisitor.CLIENT);
}
}
@Override
protected void applyDependencies(BiConsumer<String, String> consumer) {
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, COMMON);
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, CLIENT_ONLY);
}
}
public static final class ServerOnlyImpl extends NamedMinecraftProvider<ServerOnlyMinecraftProvider> implements ServerOnly {
public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL)
);
}
@Override
protected void applyDependencies(BiConsumer<String, String> consumer) {
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, SERVER_ONLY);
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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.configuration.providers.minecraft.mapped;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvider, P extends NamedMinecraftProvider<M>> extends NamedMinecraftProvider<M> {
private final P parentMinecraftProvider;
private final JarProcessorManager jarProcessorManager;
private final String projectMappedName;
private final Path projectMappedDir;
public ProcessedNamedMinecraftProvider(P parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
super(parentMinecraftProvide.getProject(), parentMinecraftProvide.getMinecraftProvider());
this.parentMinecraftProvider = parentMinecraftProvide;
this.jarProcessorManager = jarProcessorManager;
this.projectMappedName = "minecraft-project-%s-".formatted(getProject().getPath().replace(':', '@'));
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
this.projectMappedDir = extension.getFiles().getRootProjectPersistentCache().toPath()
.resolve(getMinecraftProvider().minecraftVersion())
.resolve(extension.getMappingsProvider().mappingsIdentifier());
}
@Override
public void provide(boolean applyDependencies) throws Exception {
parentMinecraftProvider.provide(false);
final List<Path> inputJars = parentMinecraftProvider.getMinecraftJars();
boolean requiresProcessing = LoomGradlePlugin.refreshDeps || inputJars.stream()
.map(this::getProcessedPath)
.map(Path::toFile)
.anyMatch(jarProcessorManager::isInvalid);
if (requiresProcessing) {
try {
Files.createDirectories(projectMappedDir);
} catch (IOException e) {
throw new UncheckedIOException("Failed to create project mapped dir", e);
}
for (Path inputJar : inputJars) {
final Path outputJar = getProcessedPath(inputJar);
deleteSimilarJars(outputJar);
Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING);
jarProcessorManager.process(outputJar.toFile());
}
}
if (applyDependencies) {
parentMinecraftProvider.applyDependencies((configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)));
}
}
private void deleteSimilarJars(Path jar) throws IOException {
Files.deleteIfExists(jar);
for (Path path : Files.list(jar.getParent()).filter(Files::isRegularFile)
.filter(path -> path.getFileName().startsWith(jar.getFileName().toString().replace(".jar", ""))).toList()) {
Files.deleteIfExists(path);
}
}
@Override
protected String getName(String name) {
return "%s%s-%s".formatted(projectMappedName, name, getTargetNamespace().toString());
}
@Override
public Path getJar(String name) {
// Something has gone wrong if this gets called.
throw new UnsupportedOperationException();
}
@Override
public List<RemappedJars> getRemappedJars() {
throw new UnsupportedOperationException();
}
@Override
public List<Path> getMinecraftJars() {
return getParentMinecraftProvider().getMinecraftJars().stream()
.map(this::getProcessedPath)
.toList();
}
public P getParentMinecraftProvider() {
return parentMinecraftProvider;
}
public Path getProcessedPath(Path input) {
return projectMappedDir.resolve(input.getFileName().toString().replace("minecraft-", projectMappedName));
}
public static final class MergedImpl extends ProcessedNamedMinecraftProvider<MergedMinecraftProvider, NamedMinecraftProvider.MergedImpl> implements Merged {
public MergedImpl(NamedMinecraftProvider.MergedImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
super(parentMinecraftProvide, jarProcessorManager);
}
@Override
public Path getMergedJar() {
return getProcessedPath(getParentMinecraftProvider().getMergedJar());
}
}
public static final class SplitImpl extends ProcessedNamedMinecraftProvider<SplitMinecraftProvider, NamedMinecraftProvider.SplitImpl> implements Split {
public SplitImpl(NamedMinecraftProvider.SplitImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
super(parentMinecraftProvide, jarProcessorManager);
}
@Override
public Path getCommonJar() {
return getProcessedPath(getParentMinecraftProvider().getCommonJar());
}
@Override
public Path getClientOnlyJar() {
return getProcessedPath(getParentMinecraftProvider().getClientOnlyJar());
}
}
public static final class ServerOnlyImpl extends ProcessedNamedMinecraftProvider<ServerOnlyMinecraftProvider, NamedMinecraftProvider.ServerOnlyImpl> implements ServerOnly {
public ServerOnlyImpl(NamedMinecraftProvider.ServerOnlyImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
super(parentMinecraftProvide, jarProcessorManager);
}
@Override
public Path getServerOnlyJar() {
return getProcessedPath(getParentMinecraftProvider().getServerOnlyJar());
}
}
}

View File

@@ -27,6 +27,7 @@ package net.fabricmc.loom.decompilers;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.decompilers.cfr.LoomCFRDecompiler;
import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler;
@@ -35,8 +36,11 @@ public final class DecompilerConfiguration {
}
public static void setup(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
extension.getGameDecompilers().add(new FabricFernFlowerDecompiler());
extension.getGameDecompilers().add(new LoomCFRDecompiler());
registerDecompiler(project, "fernFlower", FabricFernFlowerDecompiler.class);
registerDecompiler(project, "cfr", LoomCFRDecompiler.class);
}
private static void registerDecompiler(Project project, String name, Class<? extends LoomDecompiler> decompilerClass) {
LoomGradleExtension.get(project).getDecompilerOptions().register(name, options -> options.getDecompilerClassName().set(decompilerClass.getName()));
}
}

View File

@@ -30,8 +30,6 @@ import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.Attributes;
@@ -48,20 +46,14 @@ import org.benf.cfr.reader.util.output.SinkDumperFactory;
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.decompilers.LineNumberRemapper;
public class LoomCFRDecompiler implements LoomDecompiler {
public final class LoomCFRDecompiler implements LoomDecompiler {
private static final Map<String, String> DECOMPILE_OPTIONS = Map.of(
"renameillegalidents", "true",
"trackbytecodeloc", "true",
"comments", "false"
);
@Override
public String name() {
return "Cfr";
}
@Override
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
final String path = compiledJar.toAbsolutePath().toString();
@@ -132,21 +124,4 @@ public class LoomCFRDecompiler implements LoomDecompiler {
throw new UncheckedIOException("Failed to write line map", e);
}
}
// A test main class to make it quicker/easier to debug with minimal jars
public static void main(String[] args) throws IOException {
LoomCFRDecompiler decompiler = new LoomCFRDecompiler();
Path lineMap = Paths.get("linemap.txt");
decompiler.decompile(Paths.get("input.jar"),
Paths.get("output-sources.jar"),
lineMap,
new DecompilationMetadata(4, null, Collections.emptyList(), null, Collections.emptyMap())
);
LineNumberRemapper lineNumberRemapper = new LineNumberRemapper();
lineNumberRemapper.readMappings(lineMap.toFile());
lineNumberRemapper.process(null, Paths.get("input.jar"), Paths.get("output.jar"));
}
}

View File

@@ -37,11 +37,6 @@ import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
public final class FabricFernFlowerDecompiler implements LoomDecompiler {
@Override
public String name() {
return "FernFlower";
}
@Override
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
final Map<String, Object> options = new HashMap<>(

View File

@@ -47,4 +47,5 @@ public interface LoomFiles {
File getDefaultLog4jConfigFile();
File getDevLauncherConfig();
File getUnpickLoggingConfigFile();
File getRemapClasspathFile();
}

View File

@@ -92,4 +92,9 @@ public abstract class LoomFilesBaseImpl implements LoomFiles {
public File getUnpickLoggingConfigFile() {
return new File(getProjectPersistentCache(), "unpick-logging.properties");
}
@Override
public File getRemapClasspathFile() {
return new File(getProjectPersistentCache(), "remapClasspath.txt");
}
}

View File

@@ -33,7 +33,6 @@ import java.util.function.Consumer;
import com.google.common.base.Suppliers;
import org.gradle.api.Action;
import org.gradle.api.DomainObjectCollection;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
@@ -47,7 +46,7 @@ import org.gradle.api.publish.maven.MavenPublication;
import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.api.MixinExtensionAPI;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.configuration.ide.RunConfig;
@@ -59,6 +58,7 @@ import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.util.DeprecationHelper;
import net.fabricmc.loom.util.ModPlatform;
@@ -70,7 +70,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
private static final String PLATFORM_PROPERTY = "loom.platform";
protected final DeprecationHelper deprecationHelper;
protected final DomainObjectCollection<LoomDecompiler> decompilers;
protected final ListProperty<JarProcessor> jarProcessors;
protected final ConfigurableFileCollection log4jConfigs;
protected final RegularFileProperty accessWidener;
@@ -81,10 +80,12 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
protected final Property<Boolean> transitiveAccessWideners;
protected final Property<String> intermediary;
protected final Property<Boolean> enableInterfaceInjection;
private final Property<MinecraftJarConfiguration> minecraftJarConfiguration;
private final ModVersionParser versionParser;
private NamedDomainObjectContainer<RunConfigSettings> runConfigs;
private final NamedDomainObjectContainer<RunConfigSettings> runConfigs;
private final NamedDomainObjectContainer<DecompilerOptions> decompilers;
// ===================
// Architectury Loom
@@ -98,9 +99,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
private NamedDomainObjectContainer<LaunchProviderSettings> launchConfigs;
protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) {
this.runConfigs = project.container(RunConfigSettings.class,
baseName -> new RunConfigSettings(project, baseName));
this.decompilers = project.getObjects().domainObjectSet(LoomDecompiler.class);
this.jarProcessors = project.getObjects().listProperty(JarProcessor.class)
.empty();
this.log4jConfigs = project.files(directories.getDefaultLog4jConfigFile());
@@ -124,6 +122,16 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.versionParser = new ModVersionParser(project);
this.deprecationHelper = new DeprecationHelper.ProjectBased(project);
this.runConfigs = project.container(RunConfigSettings.class,
baseName -> new RunConfigSettings(project, baseName));
this.decompilers = project.getObjects().domainObjectContainer(DecompilerOptions.class);
this.minecraftJarConfiguration = project.getObjects().property(MinecraftJarConfiguration.class).convention(MinecraftJarConfiguration.MERGED);
this.minecraftJarConfiguration.finalizeValueOnRead();
this.accessWidener.finalizeValueOnRead();
this.getGameJarProcessors().finalizeValueOnRead();
this.platform = project.provider(Suppliers.memoize(() -> {
Object platformProperty = project.findProperty(PLATFORM_PROPERTY);
@@ -161,10 +169,15 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
}
@Override
public DomainObjectCollection<LoomDecompiler> getGameDecompilers() {
public NamedDomainObjectContainer<DecompilerOptions> getDecompilerOptions() {
return decompilers;
}
@Override
public void decompilers(Action<NamedDomainObjectContainer<DecompilerOptions>> action) {
action.execute(decompilers);
}
@Override
public ListProperty<JarProcessor> getGameJarProcessors() {
return jarProcessors;
@@ -244,6 +257,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication);
}
@Override
public Property<MinecraftJarConfiguration> getMinecraftJarConfiguration() {
return minecraftJarConfiguration;
}
@Override
public void silentMojangMappingsLicense() {
this.silentMojangMappingsLicense = true;

View File

@@ -24,7 +24,7 @@
package net.fabricmc.loom.extension;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -41,14 +41,18 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.configuration.LoomDependencyManager;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.util.ModPlatform;
import net.fabricmc.loom.util.function.LazyBool;
@@ -59,7 +63,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
private final ConfigurableFileCollection unmappedMods;
private final Supplier<ForgeExtensionAPI> forgeExtension;
private final ConfigurableFileCollection mixinMappings;
private final MappingSet[] srcMappingCache = new MappingSet[2];
private final Mercury[] srcMercuryCache = new Mercury[2];
private final Map<String, NamedDomainObjectProvider<Configuration>> lazyConfigurations = new HashMap<>();
@@ -67,6 +70,10 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
private LoomDependencyManager dependencyManager;
private JarProcessorManager jarProcessorManager;
private MinecraftProvider minecraftProvider;
private MappingsProviderImpl mappingsProvider;
private NamedMinecraftProvider<?> namedMinecraftProvider;
private IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider;
private InstallerData installerData;
// +-------------------+
@@ -80,7 +87,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
this.project = project;
// Initiate with newInstance to allow gradle to decorate our extension
this.mixinApExtension = project.getObjects().newInstance(MixinExtensionImpl.class, project);
this.mixinMappings = project.getObjects().fileCollection();
this.loomFiles = files;
this.unmappedMods = project.files();
this.forgeExtension = Suppliers.memoize(() -> isForge() ? project.getObjects().newInstance(ForgeExtensionImpl.class, project, this) : null);
@@ -97,18 +103,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
return loomFiles;
}
@Override
public synchronized File getMixinMappings(SourceSet sourceSet) {
File mixinMapping = new File(getFiles().getProjectBuildCache(), "mixin-map-" + getMappingsProvider().mappingsIdentifier() + "." + sourceSet.getName() + ".tiny");
mixinMappings.from(getProject().files(mixinMapping));
return mixinMapping;
}
@Override
public FileCollection getAllMixinMappings() {
return mixinMappings.filter(File::exists);
}
@Override
public void setDependencyManager(LoomDependencyManager dependencyManager) {
this.dependencyManager = dependencyManager;
@@ -129,6 +123,55 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
return Objects.requireNonNull(jarProcessorManager, "Cannot get JarProcessorManager before it has been setup");
}
@Override
public MinecraftProvider getMinecraftProvider() {
return Objects.requireNonNull(minecraftProvider, "Cannot get MinecraftProvider before it has been setup");
}
@Override
public void setMinecraftProvider(MinecraftProvider minecraftProvider) {
this.minecraftProvider = minecraftProvider;
}
@Override
public MappingsProviderImpl getMappingsProvider() {
return Objects.requireNonNull(mappingsProvider, "Cannot get MappingsProvider before it has been setup");
}
@Override
public void setMappingsProvider(MappingsProviderImpl mappingsProvider) {
this.mappingsProvider = mappingsProvider;
}
@Override
public NamedMinecraftProvider<?> getNamedMinecraftProvider() {
return Objects.requireNonNull(namedMinecraftProvider, "Cannot get NamedMinecraftProvider before it has been setup");
}
@Override
public IntermediaryMinecraftProvider<?> getIntermediaryMinecraftProvider() {
return Objects.requireNonNull(intermediaryMinecraftProvider, "Cannot get IntermediaryMinecraftProvider before it has been setup");
}
@Override
public void setNamedMinecraftProvider(NamedMinecraftProvider<?> namedMinecraftProvider) {
this.namedMinecraftProvider = namedMinecraftProvider;
}
@Override
public void setIntermediaryMinecraftProvider(IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider) {
this.intermediaryMinecraftProvider = intermediaryMinecraftProvider;
}
@Override
public FileCollection getMinecraftJarsCollection(MappingsNamespace mappingsNamespace) {
return getProject().files(
getProject().provider(() ->
getProject().files(getMinecraftJars(mappingsNamespace).stream().map(Path::toFile).toList())
)
);
}
@Override
public MappingSet getOrCreateSrcMappingCache(int id, Supplier<MappingSet> factory) {
if (id < 0 || id >= srcMappingCache.length) return factory.get();

View File

@@ -43,13 +43,9 @@ import javax.inject.Inject;
import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
@@ -58,12 +54,11 @@ import org.gradle.workers.WorkerExecutor;
import org.gradle.workers.internal.WorkerDaemonClientsManager;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMappingsProcessor;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.decompilers.LineNumberRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
@@ -76,22 +71,19 @@ import net.fabricmc.loom.util.ipc.IPCClient;
import net.fabricmc.loom.util.ipc.IPCServer;
public abstract class GenerateSourcesTask extends AbstractLoomTask {
public final LoomDecompiler decompiler;
private final DecompilerOptions decompilerOptions;
/**
* The jar to decompile, can be the unpick jar.
*/
@InputFile
public abstract RegularFileProperty getInputJar();
/**
* Max memory for forked JVM in megabytes.
* The jar used at runtime.
*/
@Input
public abstract Property<Long> getMaxMemory();
@Input
public abstract MapProperty<String, String> getOptions();
@InputFiles
public abstract ConfigurableFileCollection getClasspath();
@InputFile
public abstract RegularFileProperty getRuntimeJar();
@Inject
public abstract WorkerExecutor getWorkerExecutor();
@@ -100,21 +92,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
public abstract WorkerDaemonClientsManager getWorkerDaemonClientsManager();
@Inject
public GenerateSourcesTask(LoomDecompiler decompiler) {
this.decompiler = decompiler;
Objects.requireNonNull(getDecompilerConstructor(this.decompiler.getClass().getCanonicalName()),
"%s must have a no args constructor".formatted(this.decompiler.getClass().getCanonicalName()));
FileCollection decompilerClasspath = decompiler.getBootstrapClasspath(getProject());
if (decompilerClasspath != null) {
getClasspath().from(decompilerClasspath);
}
public GenerateSourcesTask(DecompilerOptions decompilerOptions) {
this.decompilerOptions = decompilerOptions;
getOutputs().upToDateWhen((o) -> false);
getMaxMemory().convention(4096L).finalizeValueOnRead();
getOptions().finalizeValueOnRead();
}
@TaskAction
@@ -134,9 +115,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
final Path ipcPath = Files.createTempFile("loom", "ipc");
Files.deleteIfExists(ipcPath);
try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompiler.name(), "Decompiling minecraft sources");
try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompilerOptions.getName(), "Decompiling minecraft sources");
IPCServer logReceiver = new IPCServer(ipcPath, loggerConsumer)) {
doWork(ipcPath);
doWork(logReceiver);
} catch (InterruptedException e) {
throw new RuntimeException("Failed to shutdown log receiver", e);
} finally {
@@ -144,24 +125,22 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
}
private void doWork(@Nullable Path ipcPath) {
private void doWork(@Nullable IPCServer ipcServer) {
final String jvmMarkerValue = UUID.randomUUID().toString();
final WorkQueue workQueue = createWorkQueue(jvmMarkerValue);
workQueue.submit(DecompileAction.class, params -> {
params.getDecompilerClass().set(decompiler.getClass().getCanonicalName());
params.getOptions().set(getOptions());
params.getDecompilerOptions().set(decompilerOptions.toDto());
params.getInputJar().set(getInputJar());
params.getRuntimeJar().set(getExtension().getMappingsProvider().mappedProvider.getMappedJar());
params.getRuntimeJar().set(getRuntimeJar());
params.getSourcesDestinationJar().set(getMappedJarFileWithSuffix("-sources.jar"));
params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap"));
params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar"));
params.getMappings().set(getMappings(getProject(), getExtension()).toFile());
if (ipcPath != null) {
params.getIPCPath().set(ipcPath.toFile());
if (ipcServer != null) {
params.getIPCPath().set(ipcServer.getPath().toFile());
}
params.getClassPath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES));
@@ -170,10 +149,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
try {
workQueue.await();
} finally {
if (useProcessIsolation()) {
if (ipcServer != null) {
boolean stopped = WorkerDaemonClientsManagerHelper.stopIdleJVM(getWorkerDaemonClientsManager(), jvmMarkerValue);
if (!stopped) {
if (!stopped && ipcServer.hasReceivedMessage()) {
throw new RuntimeException("Failed to stop decompile worker JVM");
}
}
@@ -187,9 +166,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
return getWorkerExecutor().processIsolation(spec -> {
spec.forkOptions(forkOptions -> {
forkOptions.setMaxHeapSize("%dm".formatted(getMaxMemory().get()));
forkOptions.setMaxHeapSize("%dm".formatted(decompilerOptions.getMemory().get()));
forkOptions.systemProperty(WorkerDaemonClientsManagerHelper.MARKER_PROP, jvmMarkerValue);
forkOptions.bootstrapClasspath(getClasspath());
forkOptions.bootstrapClasspath(decompilerOptions.getClasspath());
});
});
}
@@ -200,9 +179,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
public interface DecompileParams extends WorkParameters {
Property<String> getDecompilerClass();
MapProperty<String, String> getOptions();
Property<DecompilerOptions.Dto> getDecompilerOptions();
RegularFileProperty getInputJar();
RegularFileProperty getRuntimeJar();
@@ -241,20 +218,26 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
final Path linemapJar = getParameters().getLinemapJar().get().getAsFile().toPath();
final Path runtimeJar = getParameters().getRuntimeJar().get().getAsFile().toPath();
final DecompilerOptions.Dto decompilerOptions = getParameters().getDecompilerOptions().get();
final LoomDecompiler decompiler;
try {
decompiler = getDecompilerConstructor(getParameters().getDecompilerClass().get()).newInstance();
final String className = decompilerOptions.className();
final Constructor<LoomDecompiler> decompilerConstructor = getDecompilerConstructor(className);
Objects.requireNonNull(decompilerConstructor, "%s must have a no args constructor".formatted(className));
decompiler = decompilerConstructor.newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Failed to create decompiler", e);
}
DecompilationMetadata metadata = new DecompilationMetadata(
Runtime.getRuntime().availableProcessors(),
decompilerOptions.maxThreads(),
getParameters().getMappings().get().getAsFile().toPath(),
getLibraries(),
logger,
getParameters().getOptions().get()
decompilerOptions.options()
);
decompiler.decompile(
@@ -304,18 +287,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
private File getMappedJarFileWithSuffix(String suffix) {
return getMappedJarFileWithSuffix(getProject(), suffix);
}
public static File getMappedJarFileWithSuffix(Project project, String suffix) {
return getMappedJarFileWithSuffix(project, suffix, false);
}
public static File getMappedJarFileWithSuffix(Project project, String suffix, boolean forgeJar) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
File mappedJar = forgeJar ? mappingsProvider.mappedProvider.getForgeMappedJar() : mappingsProvider.mappedProvider.getMappedJar();
String path = mappedJar.getAbsolutePath();
String path = getRuntimeJar().get().getAsFile().getAbsolutePath();
if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) {
throw new RuntimeException("Invalid mapped JAR path: " + path);

View File

@@ -26,11 +26,17 @@ package net.fabricmc.loom.task;
import com.google.common.base.Preconditions;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.task.launch.GenerateDLIConfigTask;
import net.fabricmc.loom.task.launch.GenerateLog4jConfigTask;
import net.fabricmc.loom.task.launch.GenerateRemapClasspathTask;
import net.fabricmc.loom.util.Constants;
public final class LoomTasks {
@@ -47,11 +53,39 @@ public final class LoomTasks {
RemapTaskConfiguration.setupRemap(project);
TaskProvider<ExtractNativesTask> extractNatives = tasks.register("extractNatives", ExtractNativesTask.class);
tasks.register("extractNatives", ExtractNativesTask.class, t -> {
t.setDescription("Extracts the minecraft platform specific natives.");
});
tasks.register("downloadAssets", DownloadAssetsTask.class, t -> {
t.dependsOn(extractNatives);
t.setDescription("Downloads required assets for Fabric.");
});
tasks.register("generateDLIConfig", GenerateDLIConfigTask.class, t -> {
t.setDescription("Generate the DevLaunchInjector config file");
});
tasks.register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> {
t.setDescription("Generate the log4j config file");
});
tasks.register("generateRemapClasspath", GenerateRemapClasspathTask.class, t -> {
t.setDescription("Generate the remap classpath file");
});
tasks.register("configureLaunch", task -> {
task.dependsOn(tasks.named("generateDLIConfig"));
task.dependsOn(tasks.named("generateLog4jConfig"));
task.dependsOn(tasks.named("generateRemapClasspath"));
task.setDescription("Setup the required files to launch Minecraft");
task.setGroup(Constants.TaskGroup.FABRIC);
});
tasks.register("configureClientLaunch", task -> {
task.dependsOn(tasks.named("extractNatives"));
task.dependsOn(tasks.named("downloadAssets"));
task.dependsOn(tasks.named("configureLaunch"));
task.setDescription("Setup the required files to launch the Minecraft client");
task.setGroup(Constants.TaskGroup.FABRIC);
});
TaskProvider<ValidateAccessWidenerTask> validateAccessWidener = tasks.register("validateAccessWidener", ValidateAccessWidenerTask.class, t -> {
t.setDescription("Validate all the rules in the access widener against the Minecraft jar");
@@ -62,20 +96,18 @@ public final class LoomTasks {
registerIDETasks(tasks);
registerRunTasks(tasks, project);
registerLaunchSettings(project);
registerDecompileTasks(tasks, project);
}
private static void registerIDETasks(TaskContainer tasks) {
tasks.register("genIdeaWorkspace", GenIdeaProjectTask.class, t -> {
t.setDescription("Generates an IntelliJ IDEA workspace from this project.");
t.dependsOn("idea", "downloadAssets");
t.dependsOn("idea", getIDELaunchConfigureTaskName(t.getProject()));
t.setGroup(Constants.TaskGroup.IDE);
});
tasks.register("genEclipseRuns", GenEclipseRunsTask.class, t -> {
t.setDescription("Generates Eclipse run configurations for this project.");
t.dependsOn("downloadAssets");
t.dependsOn(getIDELaunchConfigureTaskName(t.getProject()));
t.setGroup(Constants.TaskGroup.IDE);
});
@@ -86,7 +118,7 @@ public final class LoomTasks {
tasks.register("vscode", GenVsCodeProjectTask.class, t -> {
t.setDescription("Generates VSCode launch configurations.");
t.dependsOn("downloadAssets");
t.dependsOn(getIDELaunchConfigureTaskName(t.getProject()));
t.setGroup(Constants.TaskGroup.IDE);
});
}
@@ -103,52 +135,25 @@ public final class LoomTasks {
tasks.register(taskName, RunGameTask.class, config).configure(t -> {
t.setDescription("Starts the '" + config.getConfigName() + "' run configuration");
if (config.getEnvironment().equals("client")) {
t.dependsOn("downloadAssets");
}
t.dependsOn(config.getEnvironment().equals("client") ? "configureClientLaunch" : "configureLaunch");
});
});
extension.getRunConfigs().create("client", RunConfigSettings::client);
extension.getRunConfigs().create("server", RunConfigSettings::server);
// Remove the client run config when server only. Done by name to not remove any possible custom run configs
project.afterEvaluate(p -> {
if (extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SERVER_ONLY) {
extension.getRunConfigs().removeIf(settings -> settings.getName().equals("client"));
}
});
}
private static void registerLaunchSettings(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
Preconditions.checkArgument(extension.getLaunchConfigs().size() == 0, "Launch configurations must not be registered before loom");
extension.getLaunchConfigs().create("client");
extension.getLaunchConfigs().create("server");
if (extension.isForge()) {
extension.getLaunchConfigs().create("data");
}
}
private static void registerDecompileTasks(TaskContainer tasks, Project project) {
LoomGradleExtension.get(project).getGameDecompilers().configureEach(decompiler -> {
String taskName = "genSourcesWith" + decompiler.name();
// Decompiler will be passed to the constructor of GenerateSourcesTask
tasks.register(taskName, GenerateSourcesTask.class, decompiler).configure(task -> {
task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name()));
task.setGroup(Constants.TaskGroup.FABRIC);
task.dependsOn(tasks.named("validateAccessWidener"));
});
});
LoomGradleExtension.get(project).getArchGameDecompilers().configureEach(decompiler -> {
String taskName = "genSourcesWith" + decompiler.name();
// Decompiler will be passed to the constructor of ArchitecturyGenerateSourcesTask
tasks.register(taskName, ArchitecturyGenerateSourcesTask.class, decompiler).configure(task -> {
task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name()));
task.setGroup(Constants.TaskGroup.FABRIC);
});
});
tasks.register("genSources", task -> {
task.setDescription("Decompile minecraft using the default decompiler.");
task.setGroup(Constants.TaskGroup.FABRIC);
task.dependsOn(project.getTasks().named("genSourcesWithCfr"));
public static Provider<Task> getIDELaunchConfigureTaskName(Project project) {
return project.provider(() -> {
final MinecraftJarConfiguration jarConfiguration = LoomGradleExtension.get(project).getMinecraftJarConfiguration().get();
final String name = jarConfiguration == MinecraftJarConfiguration.SERVER_ONLY ? "configureLaunch" : "configureClientLaunch";
return project.getTasks().getByName(name);
});
}
}

View File

@@ -51,7 +51,6 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.lorenztiny.TinyMappingsJoiner;
import net.fabricmc.mappingio.MappingReader;
@@ -101,7 +100,7 @@ public class MigrateMappingsTask extends AbstractLoomTask {
try {
MemoryMappingTree currentMappings = mappingsProvider.getMappings();
MemoryMappingTree targetMappings = getMappings(mappings);
migrateMappings(project, extension, extension.getMinecraftMappedProvider(), inputDir, outputDir, currentMappings, targetMappings);
migrateMappings(project, extension, extension, inputDir, outputDir, currentMappings, targetMappings);
project.getLogger().lifecycle(":remapped project written to " + outputDir.toAbsolutePath());
} catch (IOException e) {
throw new IllegalArgumentException("Error while loading mappings", e);
@@ -157,8 +156,8 @@ public class MigrateMappingsTask extends AbstractLoomTask {
return mappingTree;
}
private static void migrateMappings(Project project, LoomGradleExtension extension, MinecraftMappedProvider minecraftMappedProvider,
Path inputDir, Path outputDir, MemoryMappingTree currentMappings, MemoryMappingTree targetMappings
private static void migrateMappings(Project project, LoomGradleExtension extension,
Path inputDir, Path outputDir, MemoryMappingTree currentMappings, MemoryMappingTree targetMappings
) throws IOException {
project.getLogger().info(":joining mappings");
@@ -174,8 +173,13 @@ public class MigrateMappingsTask extends AbstractLoomTask {
final JavaVersion javaVersion = project.getExtensions().getByType(JavaPluginExtension.class).getSourceCompatibility();
mercury.setSourceCompatibility(javaVersion.toString());
mercury.getClassPath().add(minecraftMappedProvider.getMappedJar().toPath());
mercury.getClassPath().add(minecraftMappedProvider.getIntermediaryJar().toPath());
for (Path intermediaryJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
mercury.getClassPath().add(intermediaryJar);
}
for (Path intermediaryJar : extension.getMinecraftJars(MappingsNamespace.NAMED)) {
mercury.getClassPath().add(intermediaryJar);
}
if (extension.isForge()) {
mercury.getClassPath().add(minecraftMappedProvider.getSrgJar().toPath());

View File

@@ -0,0 +1,106 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task;
import java.nio.file.Path;
import javax.inject.Inject;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
import net.fabricmc.loom.task.service.TinyRemapperService;
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
import net.fabricmc.tinyremapper.TinyRemapper;
/**
* The prepare remap task runs before all other jar remap tasks, should be used to setup tiny remapper.
*/
public abstract class PrepareJarRemapTask extends AbstractLoomTask {
private final RemapJarTask remapJarTask;
@InputFile
public abstract RegularFileProperty getInputFile();
@Inject
public PrepareJarRemapTask(RemapJarTask remapJarTask) {
this.remapJarTask = remapJarTask;
getInputFile().set(remapJarTask.getInputFile());
// TODO can this be up-to-date when the main task is up-to date?
getOutputs().upToDateWhen((o) -> false);
getProject().getGradle().allprojects(project -> {
project.getTasks().configureEach(task -> {
if (task instanceof PrepareJarRemapTask otherTask) {
if (otherTask == this) return;
// Ensure that all other prepare tasks inputs have completed
dependsOn(otherTask.getInputs());
mustRunAfter(otherTask.getInputs());
}
});
});
}
@Inject
protected abstract WorkerExecutor getWorkerExecutor();
@TaskAction
public void run() {
final WorkQueue workQueue = getWorkerExecutor().noIsolation();
workQueue.submit(ReadInputsAction.class, params -> {
params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), remapJarTask.getTinyRemapperService()));
params.getInputFile().set(getInputFile());
});
}
public interface ReadInputsParams extends WorkParameters {
Property<String> getTinyRemapperBuildServiceUuid();
RegularFileProperty getInputFile();
}
public abstract static class ReadInputsAction implements WorkAction<ReadInputsParams> {
private final TinyRemapperService tinyRemapperService;
public ReadInputsAction() {
this.tinyRemapperService = UnsafeWorkQueueHelper.get(getParameters().getTinyRemapperBuildServiceUuid(), TinyRemapperService.class);
}
@Override
public void execute() {
final TinyRemapper tinyRemapper = tinyRemapperService.getTinyRemapperForInputs();
final Path inputFile = getParameters().getInputFile().getAsFile().get().toPath();
tinyRemapper.readInputsAsync(tinyRemapperService.getOrCreateTag(inputFile), inputFile);
}
}
}

View File

@@ -27,7 +27,6 @@ package net.fabricmc.loom.task;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
@@ -41,6 +40,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.Set;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
@@ -48,6 +48,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.gson.JsonObject;
import dev.architectury.tinyremapper.InputTag;
import dev.architectury.tinyremapper.OutputConsumerPath;
@@ -66,9 +67,9 @@ import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.objectweb.asm.commons.Remapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -76,20 +77,22 @@ import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerRemapper;
import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.build.MixinRefmapHelper;
import net.fabricmc.loom.build.nesting.IncludedJarFactory;
import net.fabricmc.loom.build.nesting.JarNester;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.JarManifestService;
import net.fabricmc.loom.task.service.MappingsService;
import net.fabricmc.loom.task.service.TinyRemapperService;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.LfWriter;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.aw2at.Aw2At;
import net.fabricmc.lorenztiny.TinyMappingsReader;
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
public abstract class RemapJarTask extends AbstractRemapJarTask {
private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
@@ -111,6 +114,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
@Input
public abstract SetProperty<String> getAtAccessWideners();
private Supplier<TinyRemapperService> tinyRemapperService = Suppliers.memoize(() -> TinyRemapperService.getOrCreate(this));
@Inject
public RemapJarTask() {
super();
@@ -120,6 +125,25 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE);
getNestedJars().from(new IncludedJarFactory(getProject()).getNestedJars(includeConfiguration));
setupPreparationTask();
}
private void setupPreparationTask() {
PrepareJarRemapTask prepareJarTask = getProject().getTasks().create("prepare" + getName().substring(0, 1).toUpperCase() + getName().substring(1), PrepareJarRemapTask.class, this);
dependsOn(prepareJarTask);
mustRunAfter(prepareJarTask);
getProject().getGradle().allprojects(project -> {
project.getTasks().configureEach(task -> {
if (task instanceof PrepareJarRemapTask otherTask) {
// Ensure that all remap jars run after all prepare tasks
dependsOn(otherTask);
mustRunAfter(otherTask);
}
});
});
}
@TaskAction
@@ -132,14 +156,13 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
}
params.getJarManifestService().set(JarManifestService.get(getProject()));
params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), tinyRemapperService.get()));
params.getRemapClasspath().from(getClasspath());
params.getMappings().add(MappingsService.createDefault(getProject(), getSourceNamespace().get(), getTargetNamespace().get()));
final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get();
params.getUseMixinExtension().set(!legacyMixin);
if (legacyMixin) {
params.getMixinMappings().from(extension.getAllMixinMappings());
setupLegacyMixinRefmapRemapping(params);
} else if (extension.isForge()) {
throw new RuntimeException("Forge must have useLegacyMixinAp enabled");
@@ -209,9 +232,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
public interface RemapParams extends AbstractRemapParams {
ConfigurableFileCollection getNestedJars();
ConfigurableFileCollection getRemapClasspath();
ConfigurableFileCollection getMixinMappings();
ListProperty<Provider<MappingsService>> getMappings();
Property<Boolean> getForge();
@@ -223,19 +243,25 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
ListProperty<RefmapData> getMixinData();
Property<JarManifestService> getJarManifestService();
Property<String> getTinyRemapperBuildServiceUuid();
}
public abstract static class RemapAction extends AbstractRemapAction<RemapParams> {
private static final Logger LOGGER = LoggerFactory.getLogger(RemapAction.class);
private final TinyRemapperService tinyRemapperService;
private TinyRemapper tinyRemapper;
public RemapAction() {
this.tinyRemapperService = UnsafeWorkQueueHelper.get(getParameters().getTinyRemapperBuildServiceUuid(), TinyRemapperService.class);
}
@Override
public void execute() {
try {
LOGGER.info("Remapping {} to {}", inputFile, outputFile);
tinyRemapper = createTinyRemapper();
tinyRemapper = tinyRemapperService.getTinyRemapperForRemapping();
remap();
remapAccessWidener();
@@ -248,9 +274,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
rewriteJar();
tinyRemapper.finish();
tinyRemapper = null;
LOGGER.debug("Finished remapping {}", inputFile);
} catch (Exception e) {
try {
@@ -264,13 +287,9 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
}
private void remap() throws IOException {
final InputTag inputTag = tinyRemapper.createInputTag();
tinyRemapper.readInputsAsync(inputTag, inputFile);
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(outputFile).build()) {
outputConsumer.addNonClassFiles(inputFile);
tinyRemapper.apply(outputConsumer, inputTag);
tinyRemapper.apply(outputConsumer, tinyRemapperService.getOrCreateTag(inputFile));
}
}
@@ -281,7 +300,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
return;
}
byte[] remapped = remapAccessWidener(accessWidenerFile.content(), tinyRemapper.getEnvironment().getRemapper(), MappingsNamespace.INTERMEDIARY.toString());
byte[] remapped = remapAccessWidener(accessWidenerFile.content());
// Finally, replace the output with the remaped aw
ZipUtils.replace(outputFile, accessWidenerFile.path(), remapped);
@@ -343,15 +362,15 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
}
}
private static byte[] remapAccessWidener(byte[] input, Remapper asmRemapper, String targetNamespace) {
private byte[] remapAccessWidener(byte[] input) {
int version = AccessWidenerReader.readVersion(input);
AccessWidenerWriter writer = new AccessWidenerWriter(version);
AccessWidenerRemapper remapper = new AccessWidenerRemapper(
writer,
asmRemapper,
MappingsNamespace.NAMED.toString(),
targetNamespace
tinyRemapper.getEnvironment().getRemapper(),
getParameters().getSourceNamespace().get(),
getParameters().getTargetNamespace().get()
);
AccessWidenerReader reader = new AccessWidenerReader(remapper);
reader.read(input);
@@ -400,30 +419,10 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
})));
}
}
}
private TinyRemapper createTinyRemapper() {
TinyRemapper.Builder builder = TinyRemapper.newRemapper();
for (Provider<MappingsService> provider : getParameters().getMappings().get()) {
builder.withMappings(provider.get().getMappingsProvider());
}
for (File mixinMapping : getParameters().getMixinMappings()) {
builder.withMappings(TinyUtils.createTinyMappingProvider(mixinMapping.toPath(), getParameters().getSourceNamespace().get(), getParameters().getTargetNamespace().get()));
}
if (getParameters().getUseMixinExtension().get()) {
builder.extension(new dev.architectury.tinyremapper.extension.mixin.MixinExtension());
}
TinyRemapper remapper = builder.build();
// Apply classpath
for (File file : getParameters().getRemapClasspath()) {
remapper.readClassPathAsync(file.toPath());
}
return remapper;
}
@Internal
public TinyRemapperService getTinyRemapperService() {
return tinyRemapperService.get();
}
}

View File

@@ -35,8 +35,8 @@ import org.gradle.api.tasks.TaskAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.task.service.MappingsService;
import net.fabricmc.loom.task.service.SourceRemapperService;
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
@Inject
@@ -49,12 +49,12 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
@TaskAction
public void run() {
submitWork(RemapSourcesAction.class, params -> {
params.getSourcesRemapperService().set(SourceRemapperService.create(getProject(), MappingsService.createDefault(getProject(), getSourceNamespace().get(), getTargetNamespace().get()), getClasspath()));
params.getSourcesRemapperServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), SourceRemapperService.create(this)));
});
}
public interface RemapSourcesParams extends AbstractRemapParams {
Property<SourceRemapperService> getSourcesRemapperService();
Property<String> getSourcesRemapperServiceUuid();
}
public abstract static class RemapSourcesAction extends AbstractRemapAction<RemapSourcesParams> {
@@ -65,7 +65,7 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
public RemapSourcesAction() {
super();
sourceRemapperService = getParameters().getSourcesRemapperService().get();
sourceRemapperService = UnsafeWorkQueueHelper.get(getParameters().getSourcesRemapperServiceUuid(), SourceRemapperService.class);
}
@Override

View File

@@ -54,8 +54,8 @@ public class RemapTaskConfiguration {
return;
}
// Register the default remap jar task
TaskProvider<RemapJarTask> remapJarTaskProvider = tasks.register(REMAP_JAR_TASK_NAME, RemapJarTask.class, task -> {
// Register the default remap jar task - must not be lazy to ensure that the prepare tasks get setup for other projects to depend on.
RemapJarTask remapJarTask = tasks.create(REMAP_JAR_TASK_NAME, RemapJarTask.class, task -> {
final AbstractArchiveTask jarTask = tasks.named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class).get();
// Basic task setup
@@ -76,7 +76,7 @@ public class RemapTaskConfiguration {
task.getDestinationDirectory().set(new File(project.getBuildDir(), "devlibs"));
});
tasks.named(BasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(remapJarTaskProvider));
tasks.named(BasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(remapJarTask));
trySetupSourceRemapping(project);
@@ -84,8 +84,8 @@ public class RemapTaskConfiguration {
// Remove -dev jars from the default jar task
for (String configurationName : new String[] { JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME }) {
Configuration configuration = project.getConfigurations().getByName(configurationName);
final Task jarTask = project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME);
configuration.getArtifacts().removeIf(artifact -> {
Task jarTask = project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME);
// if the artifact is a -dev jar and "builtBy jar"
return "dev".equals(artifact.getClassifier()) && artifact.getBuildDependencies().getDependencies(null).contains(jarTask);
});

View File

@@ -28,6 +28,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.inject.Inject;
@@ -40,7 +41,7 @@ import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.OutputFile;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.LaunchProvider;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.extension.LoomFiles;
import net.fabricmc.loom.util.Constants;
@@ -76,7 +77,9 @@ public abstract class UnpickJarTask extends JavaExec {
fileArg(getConstantJar().getSingleFile());
// Classpath
fileArg(getExtension().getMinecraftMappedProvider().getMappedJar());
for (Path minecraftJar : getExtension().getMinecraftJars(MappingsNamespace.NAMED)) {
fileArg(minecraftJar.toFile());
}
for (File file : getUnpickClasspath()) {
fileArg(file);
@@ -89,7 +92,7 @@ public abstract class UnpickJarTask extends JavaExec {
}
private void writeUnpickLogConfig() {
try (InputStream is = LaunchProvider.class.getClassLoader().getResourceAsStream("unpick-logging.properties")) {
try (InputStream is = UnpickJarTask.class.getClassLoader().getResourceAsStream("unpick-logging.properties")) {
Files.deleteIfExists(getDirectories().getUnpickLoggingConfigFile().toPath());
Files.copy(is, getDirectories().getUnpickLoggingConfigFile().toPath());
} catch (IOException e) {

View File

@@ -25,6 +25,7 @@
package net.fabricmc.loom.task;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
@@ -32,11 +33,11 @@ import java.nio.file.Files;
import javax.inject.Inject;
import dev.architectury.tinyremapper.TinyRemapper;
import dev.architectury.tinyremapper.api.TrEnvironment;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
@@ -44,21 +45,24 @@ import net.fabricmc.accesswidener.AccessWidenerFormatException;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerVisitor;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.api.TrEnvironment;
public abstract class ValidateAccessWidenerTask extends DefaultTask {
@SkipWhenEmpty
@InputFile
public abstract RegularFileProperty getAccessWidener();
@InputFile
public abstract RegularFileProperty getTargetJar();
@InputFiles
public abstract ConfigurableFileCollection getTargetJars();
@Inject
public ValidateAccessWidenerTask() {
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
getAccessWidener().convention(extension.getAccessWidenerPath()).finalizeValueOnRead();
getTargetJar().convention(getProject().getObjects().fileProperty().fileValue(extension.getMinecraftMappedProvider().getMappedJar())).finalizeValueOnRead();
getTargetJars().from(extension.getMinecraftJarsCollection(MappingsNamespace.NAMED));
// Ignore outputs for up-to-date checks as there aren't any (so only inputs are checked)
getOutputs().upToDateWhen(task -> true);
@@ -67,7 +71,10 @@ public abstract class ValidateAccessWidenerTask extends DefaultTask {
@TaskAction
public void run() {
final TinyRemapper tinyRemapper = TinyRemapper.newRemapper().build();
tinyRemapper.readClassPath(getTargetJar().get().getAsFile().toPath());
for (File file : getTargetJars().getFiles()) {
tinyRemapper.readClassPath(file.toPath());
}
final AccessWidenerValidator validator = new AccessWidenerValidator(tinyRemapper.getEnvironment());
final AccessWidenerReader accessWidenerReader = new AccessWidenerReader(validator);

View File

@@ -0,0 +1,119 @@
/*
* 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.task.launch;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.gradle.api.logging.configuration.ConsoleOutput;
import org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.task.AbstractLoomTask;
public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
@TaskAction
public void run() throws IOException {
final String nativesPath = getExtension().getFiles().getNativesDirectory(getProject()).getAbsolutePath();
final LaunchConfig launchConfig = new LaunchConfig()
.property("fabric.development", "true")
.property("fabric.remapClasspathFile", getExtension().getFiles().getRemapClasspathFile().getAbsolutePath())
.property("log4j.configurationFile", getAllLog4JConfigFiles())
.property("log4j2.formatMsgNoLookups", "true")
.property("client", "java.library.path", nativesPath)
.property("client", "org.lwjgl.librarypath", nativesPath)
.argument("client", "--assetIndex")
.argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion()))
.argument("client", "--assetsDir")
.argument("client", new File(getExtension().getFiles().getUserCache(), "assets").getAbsolutePath());
final boolean plainConsole = getProject().getGradle().getStartParameter().getConsoleOutput() == ConsoleOutput.Plain;
final boolean ansiSupportedIDE = new File(getProject().getRootDir(), ".vscode").exists()
|| new File(getProject().getRootDir(), ".idea").exists()
|| (Arrays.stream(getProject().getRootDir().listFiles()).anyMatch(file -> file.getName().endsWith(".iws")));
//Enable ansi by default for idea and vscode when gradle is not ran with plain console.
if (ansiSupportedIDE && !plainConsole) {
launchConfig.property("fabric.log.disableAnsi", "false");
}
FileUtils.writeStringToFile(getExtension().getFiles().getDevLauncherConfig(), launchConfig.asString(), StandardCharsets.UTF_8);
}
private String getAllLog4JConfigFiles() {
return getExtension().getLog4jConfigs().getFiles().stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(","));
}
public static class LaunchConfig {
private final Map<String, List<String>> values = new HashMap<>();
public LaunchConfig property(String key, String value) {
return property("common", key, value);
}
public LaunchConfig property(String side, String key, String value) {
values.computeIfAbsent(side + "Properties", (s -> new ArrayList<>()))
.add(String.format("%s=%s", key, value));
return this;
}
public LaunchConfig argument(String value) {
return argument("common", value);
}
public LaunchConfig argument(String side, String value) {
values.computeIfAbsent(side + "Args", (s -> new ArrayList<>()))
.add(value);
return this;
}
public String asString() {
StringJoiner stringJoiner = new StringJoiner("\n");
for (Map.Entry<String, List<String>> entry : values.entrySet()) {
stringJoiner.add(entry.getKey());
for (String s : entry.getValue()) {
stringJoiner.add("\t" + s);
}
}
return stringJoiner.toString();
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.task.launch;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.task.AbstractLoomTask;
public abstract class GenerateLog4jConfigTask extends AbstractLoomTask {
@TaskAction
public void run() {
Path outputFile = getExtension().getFiles().getDefaultLog4jConfigFile().toPath();
try (InputStream is = GenerateLog4jConfigTask.class.getClassLoader().getResourceAsStream("log4j2.fabric.xml")) {
Files.deleteIfExists(outputFile);
Files.copy(is, outputFile);
} catch (IOException e) {
throw new RuntimeException("Failed to generate log4j config", e);
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.task.launch;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.task.AbstractLoomTask;
import net.fabricmc.loom.util.Constants;
public abstract class GenerateRemapClasspathTask extends AbstractLoomTask {
@TaskAction
public void run() {
List<String> inputConfigurations = new ArrayList<>();
inputConfigurations.add(Constants.Configurations.LOADER_DEPENDENCIES);
inputConfigurations.addAll(Constants.MOD_COMPILE_ENTRIES.stream().map(RemappedConfigurationEntry::sourceConfiguration).toList());
List<File> remapClasspath = new ArrayList<>();
for (String inputConfiguration : inputConfigurations) {
remapClasspath.addAll(getProject().getConfigurations().getByName(inputConfiguration).getFiles());
}
for (Path minecraftJar : getExtension().getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
remapClasspath.add(minecraftJar.toFile());
}
String str = remapClasspath.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator));
try {
Files.writeString(getExtension().getFiles().getRemapClasspathFile().toPath(), str);
} catch (IOException e) {
throw new RuntimeException("Failed to generate remap classpath", e);
}
}
}

View File

@@ -53,7 +53,7 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
Property<MixinVersion> getMixinVersion();
}
public static Provider<JarManifestService> get(Project project) {
public static synchronized Provider<JarManifestService> get(Project project) {
return project.getGradle().getSharedServices().registerIfAbsent("LoomJarManifestService:" + project.getName(), JarManifestService.class, spec -> {
spec.parameters(params -> {
LoomGradleExtension extension = LoomGradleExtension.get(project);

View File

@@ -24,49 +24,45 @@
package net.fabricmc.loom.task.service;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import dev.architectury.tinyremapper.IMappingProvider;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public abstract class MappingsService implements BuildService<MappingsService.Params>, AutoCloseable {
interface Params extends BuildServiceParameters {
RegularFileProperty getMappingsFile();
public final class MappingsService implements SharedService {
private record Options(Path mappingsFile, String from, String to, boolean remapLocals) { }
Property<String> getFromNamespace();
Property<String> getToNamespace();
Property<Boolean> getRemapLocals();
public static MappingsService create(Project project, String name, Path mappingsFile, String from, String to, boolean remapLocals) {
return create(SharedServiceManager.get(project), name, mappingsFile, from, to, remapLocals);
}
public static synchronized Provider<MappingsService> create(Project project, String name, File mappingsFile, String from, String to, boolean remapLocals) {
return project.getGradle().getSharedServices().registerIfAbsent(name, MappingsService.class, spec -> {
spec.parameters(params -> {
params.getMappingsFile().set(mappingsFile);
params.getFromNamespace().set(from);
params.getToNamespace().set(to);
params.getRemapLocals().set(remapLocals);
});
});
public static synchronized MappingsService create(SharedServiceManager sharedServiceManager, String name, Path mappingsFile, String from, String to, boolean remapLocals) {
final Options options = new Options(mappingsFile, from, to, remapLocals);
final String id = name + options.hashCode();
return sharedServiceManager.getOrCreateService(id, () -> new MappingsService(options));
}
public static Provider<MappingsService> createDefault(Project project, String from, String to) {
public static MappingsService createDefault(Project project, String from, String to) {
final MappingsProviderImpl mappingsProvider = LoomGradleExtension.get(project).getMappingsProvider();
final String name = mappingsProvider.getBuildServiceName("mappingsProvider", from, to);
return MappingsService.create(project, name, (from.equals("srg") || to.equals("srg")) && LoomGradleExtension.get(project).shouldGenerateSrgTiny() ? mappingsProvider.tinyMappingsWithSrg.toFile() : mappingsProvider.tinyMappings.toFile(), from, to, false);
return MappingsService.create(project, name, (from.equals("srg") || to.equals("srg")) && LoomGradleExtension.get(project).shouldGenerateSrgTiny() ? mappingsProvider.tinyMappingsWithSrg.toFile() : mappingsProvider.tinyMappings, from, to, false);
}
private final Options options;
public MappingsService(Options options) {
this.options = options;
}
private IMappingProvider mappingProvider = null;
@@ -76,13 +72,13 @@ public abstract class MappingsService implements BuildService<MappingsService.Pa
if (mappingProvider == null) {
try {
mappingProvider = TinyRemapperHelper.create(
getParameters().getMappingsFile().get().getAsFile().toPath(),
getParameters().getFromNamespace().get(),
getParameters().getToNamespace().get(),
getParameters().getRemapLocals().get()
options.mappingsFile(),
options.from(),
options.to(),
options.remapLocals()
);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings from: " + getParameters().getMappingsFile().get().getAsFile().getAbsolutePath(), e);
throw new UncheckedIOException("Failed to read mappings from: " + options.mappingsFile(), e);
}
}
@@ -94,9 +90,9 @@ public abstract class MappingsService implements BuildService<MappingsService.Pa
memoryMappingTree = new MemoryMappingTree();
try {
MappingReader.read(getParameters().getMappingsFile().get().getAsFile().toPath(), memoryMappingTree);
MappingReader.read(options.mappingsFile(), memoryMappingTree);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings from: " + getParameters().getMappingsFile().get().getAsFile().getAbsolutePath(), e);
throw new UncheckedIOException("Failed to read mappings from: " + options.mappingsFile(), e);
}
}
@@ -104,11 +100,11 @@ public abstract class MappingsService implements BuildService<MappingsService.Pa
}
public String getFromNamespace() {
return getParameters().getFromNamespace().get();
return options.from();
}
public String getToNamespace() {
return getParameters().getToNamespace().get();
return options.to();
}
@Override

View File

@@ -0,0 +1,69 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task.service;
import java.io.File;
import java.util.HashSet;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.tinyremapper.IMappingProvider;
public final class MixinMappingsService implements SharedService {
private final SharedServiceManager sharedServiceManager;
private final HashSet<File> mixinMappings = new HashSet<>();
private MixinMappingsService(SharedServiceManager sharedServiceManager) {
this.sharedServiceManager = sharedServiceManager;
}
public static File getMixinMappingFile(Project project, SourceSet sourceSet) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
File mixinMapping = new File(extension.getFiles().getProjectBuildCache(), "mixin-map-" + extension.getMappingsProvider().mappingsIdentifier() + "." + sourceSet.getName() + ".tiny");
getService(SharedServiceManager.get(project)).mixinMappings.add(mixinMapping);
return mixinMapping;
}
static synchronized MixinMappingsService getService(SharedServiceManager sharedServiceManager) {
return sharedServiceManager.getOrCreateService("MixinMappings", () -> new MixinMappingsService(sharedServiceManager));
}
IMappingProvider getMappingProvider(String from, String to) {
return out -> {
for (File mixinMapping : mixinMappings) {
if (!mixinMapping.exists()) continue;
MappingsService service = MappingsService.create(sharedServiceManager, mixinMapping.getAbsolutePath(), mixinMapping.toPath(), from, to, false);
service.getMappingsProvider().load(out);
}
};
}
}

View File

@@ -24,49 +24,57 @@
package net.fabricmc.loom.task.service;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
import org.cadixdev.mercury.remapper.MercuryRemapper;
import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.task.RemapSourcesJarTask;
import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.lorenztiny.TinyMappingsReader;
public abstract class SourceRemapperService implements BuildService<SourceRemapperService.Params>, AutoCloseable {
public interface Params extends BuildServiceParameters {
Property<Provider<MappingsService>> getMappings();
public final class SourceRemapperService implements SharedService {
public static synchronized SourceRemapperService create(RemapSourcesJarTask task) {
final Project project = task.getProject();
final String to = task.getTargetNamespace().get();
final String from = task.getSourceNamespace().get();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
final String id = extension.getMappingsProvider().getBuildServiceName("sourceremapper", from, to);
ConfigurableFileCollection getClasspath();
}
public static synchronized Provider<SourceRemapperService> create(Project project, Provider<MappingsService> mappings, FileCollection classpath) {
// TODO may need a better name, im not too sure
return project.getGradle().getSharedServices().registerIfAbsent("sourceremapper", SourceRemapperService.class, spec ->
spec.parameters(params -> {
params.getMappings().set(mappings);
params.getClasspath().from(classpath);
}
));
return sharedServiceManager.getOrCreateService(id, () ->
new SourceRemapperService(MappingsService.createDefault(project, from, to), task.getClasspath()
));
}
private static final Logger LOGGER = LoggerFactory.getLogger(SourceRemapperService.class);
private Mercury mercury;
private final MappingsService mappingsService;
private final ConfigurableFileCollection classpath;
private final Supplier<Mercury> mercury = Suppliers.memoize(this::createMercury);
private SourceRemapperService(MappingsService mappingsService, ConfigurableFileCollection classpath) {
this.mappingsService = mappingsService;
this.classpath = classpath;
}
public void remapSourcesJar(Path source, Path destination) throws IOException {
if (source.equals(destination)) {
@@ -99,35 +107,32 @@ public abstract class SourceRemapperService implements BuildService<SourceRemapp
}
}
private synchronized void doRemap(Path srcPath, Path dstPath, Path source) throws IOException {
if (mercury == null) {
mercury = new Mercury();
mercury.setGracefulClasspathChecks(true);
mercury.getProcessors().add(MercuryRemapper.create(getMappings()));
getParameters().getClasspath().forEach(file -> mercury.getClassPath().add(file.toPath()));
}
private synchronized void doRemap(Path srcPath, Path dstPath, Path source) {
try {
// Not thread safe!!
mercury.rewrite(srcPath, dstPath);
mercury.get().rewrite(srcPath, dstPath);
} catch (Exception e) {
LOGGER.warn("Could not remap " + source + " fully!", e);
}
}
private MappingSet getMappings() throws IOException {
return new TinyMappingsReader(mappingsService().getMemoryMappingTree(), mappingsService().getFromNamespace(), mappingsService().getToNamespace()).read();
return new TinyMappingsReader(mappingsService.getMemoryMappingTree(), mappingsService.getFromNamespace(), mappingsService.getToNamespace()).read();
}
private MappingsService mappingsService() {
return getParameters().getMappings().get().get();
}
private Mercury createMercury() {
var mercury = new Mercury();
mercury.setGracefulClasspathChecks(true);
@Override
public void close() throws Exception {
mercury = null;
// This is required (:
System.gc();
try {
mercury.getProcessors().add(MercuryRemapper.create(getMappings()));
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mercury mappings", e);
}
for (File file : classpath.getFiles()) {
mercury.getClassPath().add(file.toPath());
}
return mercury;
}
}

View File

@@ -0,0 +1,147 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task.service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension;
import net.fabricmc.loom.task.AbstractRemapJarTask;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.tinyremapper.IMappingProvider;
import net.fabricmc.tinyremapper.InputTag;
import net.fabricmc.tinyremapper.TinyRemapper;
public class TinyRemapperService implements SharedService {
public static synchronized TinyRemapperService getOrCreate(AbstractRemapJarTask remapJarTask) {
final Project project = remapJarTask.getProject();
final String to = remapJarTask.getTargetNamespace().get();
final String from = remapJarTask.getSourceNamespace().get();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get();
final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm");
// Generates an id that is used to share the remapper across projects. This tasks in the remap jar task name to handle custom remap jar tasks separately.
final String id = extension.getMappingsProvider().getBuildServiceName("remapJarService", from, to) + ":" + remapJarTask.getName() + (useKotlinExtension ? ":kotlin" : "");
TinyRemapperService service = sharedServiceManager.getOrCreateService(id, () -> {
List<IMappingProvider> mappings = new ArrayList<>();
mappings.add(MappingsService.createDefault(project, from, to).getMappingsProvider());
if (legacyMixin) {
mappings.add(MixinMappingsService.getService(SharedServiceManager.get(project)).getMappingProvider(from, to));
}
return new TinyRemapperService(mappings, !legacyMixin, useKotlinExtension);
});
service.readClasspath(remapJarTask.getClasspath().getFiles().stream().map(File::toPath).toList());
return service;
}
private TinyRemapper tinyRemapper;
private final Map<String, InputTag> inputTagMap = new HashMap<>();
private final HashSet<Path> classpath = new HashSet<>();
// Set to true once remapping has started, once set no inputs can be read.
private boolean isRemapping = false;
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension, boolean useKotlinExtension) {
TinyRemapper.Builder builder = TinyRemapper.newRemapper();
for (IMappingProvider provider : mappings) {
builder.withMappings(provider);
}
if (useMixinExtension) {
builder.extension(new net.fabricmc.tinyremapper.extension.mixin.MixinExtension());
}
if (useKotlinExtension) {
builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE);
}
tinyRemapper = builder.build();
}
public synchronized InputTag getOrCreateTag(Path file) {
InputTag tag = inputTagMap.get(file.toAbsolutePath().toString());
if (tag == null) {
tag = tinyRemapper.createInputTag();
inputTagMap.put(file.toAbsolutePath().toString(), tag);
}
return tag;
}
public TinyRemapper getTinyRemapperForRemapping() {
synchronized (this) {
isRemapping = true;
return Objects.requireNonNull(tinyRemapper, "Tiny remapper has not been setup");
}
}
public synchronized TinyRemapper getTinyRemapperForInputs() {
synchronized (this) {
if (isRemapping) {
throw new IllegalStateException("Cannot read inputs as remapping has already started");
}
return tinyRemapper;
}
}
void readClasspath(List<Path> paths) {
List<Path> toRead;
synchronized (classpath) {
toRead = paths.stream().filter(path -> !classpath.contains(path)).toList();
classpath.addAll(paths);
}
tinyRemapper.readClassPathAsync(toRead.toArray(Path[]::new));
}
@Override
public void close() throws IOException {
if (tinyRemapper != null) {
tinyRemapper.finish();
tinyRemapper = null;
}
}
}

View File

@@ -31,6 +31,7 @@ import java.nio.charset.StandardCharsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import com.google.common.io.Files;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
@@ -77,4 +78,8 @@ public class Checksum {
HashCode hash = Hashing.sha256().hashString(string, StandardCharsets.UTF_8);
return hash.asBytes();
}
public static String toHex(byte[] bytes) {
return BaseEncoding.base16().lowerCase().encode(bytes);
}
}

View File

@@ -35,7 +35,7 @@ import java.util.zip.GZIPInputStream;
import com.google.common.io.Files;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.slf4j.Logger;
import net.fabricmc.loom.LoomGradlePlugin;

View File

@@ -67,7 +67,7 @@ public class HashedDownloadUtil {
}
public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, boolean strict, Runnable startDownload) throws IOException {
if (LoomGradlePlugin.refreshDeps) {
if (LoomGradlePlugin.refreshDeps && !Boolean.getBoolean("loom.refresh")) {
delete(to);
}

View File

@@ -0,0 +1,59 @@
/*
* 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.util;
import java.util.Locale;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import net.fabricmc.tinyremapper.TinyRemapper;
/**
* Applies the @Environment annotation to all classes.
*/
public final class SidedClassVisitor extends ClassVisitor {
public static final TinyRemapper.ApplyVisitorProvider CLIENT = (cls, next) -> new SidedClassVisitor("client", next);
public static final TinyRemapper.ApplyVisitorProvider SERVER = (cls, next) -> new SidedClassVisitor("server", next);
private static final String ENVIRONMENT_DESCRIPTOR = "Lnet/fabricmc/api/Environment;";
private static final String SIDE_DESCRIPTOR = "Lnet/fabricmc/api/EnvType;";
private final String side;
private SidedClassVisitor(String side, ClassVisitor next) {
super(Constants.ASM_VERSION, next);
this.side = side;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
final AnnotationVisitor annotationVisitor = visitAnnotation(ENVIRONMENT_DESCRIPTOR, true);
annotationVisitor.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT));
annotationVisitor.visitEnd();
}
}

View File

@@ -202,8 +202,13 @@ public class SourceRemapper {
}
}
m.getClassPath().add(extension.getMinecraftMappedProvider().getMappedJar().toPath());
m.getClassPath().add(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath());
for (Path intermediaryJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
m.getClassPath().add(intermediaryJar);
}
for (Path intermediaryJar : extension.getMinecraftJars(MappingsNamespace.NAMED)) {
m.getClassPath().add(intermediaryJar);
}
if (extension.isForge()) {
m.getClassPath().add(extension.getMinecraftMappedProvider().getSrgJar().toPath());

View File

@@ -147,21 +147,31 @@ public final class TinyRemapperHelper {
public static IMappingProvider create(MappingTree mappings, String from, String to, boolean remapLocalVariables) {
return (acceptor) -> {
final int fromId = mappings.getNamespaceId(from);
final int toId = mappings.getNamespaceId(to);
for (MappingTree.ClassMapping classDef : mappings.getClasses()) {
String className = classDef.getName(from);
acceptor.acceptClass(className, classDef.getName(to));
String className = classDef.getName(fromId);
String dstName = classDef.getName(toId);
if (dstName == null) {
// Unsure if this is correct, should be better than crashing tho.
dstName = className;
}
acceptor.acceptClass(className, dstName);
for (MappingTree.FieldMapping field : classDef.getFields()) {
acceptor.acceptField(memberOf(className, field.getName(from), field.getDesc(from)), field.getName(to));
acceptor.acceptField(memberOf(className, field.getName(fromId), field.getDesc(fromId)), field.getName(toId));
}
for (MappingTree.MethodMapping method : classDef.getMethods()) {
IMappingProvider.Member methodIdentifier = memberOf(className, method.getName(from), method.getDesc(from));
acceptor.acceptMethod(methodIdentifier, method.getName(to));
IMappingProvider.Member methodIdentifier = memberOf(className, method.getName(fromId), method.getDesc(fromId));
acceptor.acceptMethod(methodIdentifier, method.getName(toId));
if (remapLocalVariables) {
for (MappingTree.MethodArgMapping parameter : method.getArgs()) {
String name = parameter.getName(to);
String name = parameter.getName(toId);
if (name == null) {
continue;
@@ -173,7 +183,7 @@ public final class TinyRemapperHelper {
for (MappingTree.MethodVarMapping localVariable : method.getVars()) {
acceptor.acceptMethodVar(methodIdentifier, localVariable.getLvIndex(),
localVariable.getStartOpIdx(), localVariable.getLvtRowIndex(),
localVariable.getName(to));
localVariable.getName(toId));
}
}
}

View File

@@ -46,6 +46,8 @@ public class IPCServer implements AutoCloseable {
private final CountDownLatch startupLock = new CountDownLatch(1);
private boolean receivedMessage = false;
public IPCServer(Path path, Consumer<String> consumer) {
this.path = path;
this.consumer = consumer;
@@ -71,6 +73,7 @@ public class IPCServer implements AutoCloseable {
Scanner scanner = new Scanner(clientChannel, StandardCharsets.UTF_8)) {
while (!Thread.currentThread().isInterrupted()) {
if (scanner.hasNextLine()) {
receivedMessage = true;
this.consumer.accept(scanner.nextLine());
}
}
@@ -85,4 +88,12 @@ public class IPCServer implements AutoCloseable {
loggerReceiverService.shutdownNow();
loggerReceiverService.awaitTermination(10, TimeUnit.SECONDS);
}
public boolean hasReceivedMessage() {
return receivedMessage;
}
public Path getPath() {
return path;
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2018-2021 FabricMC
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,20 +22,13 @@
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers;
package net.fabricmc.loom.util.service;
import java.io.File;
import java.io.Closeable;
import java.io.IOException;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
public interface MinecraftProvider {
File workingDir();
File dir(String path);
File file(String path);
String minecraftVersion();
MinecraftVersionMeta getVersionInfo();
public interface SharedService extends Closeable {
@Override
default void close() throws IOException {
}
}

View File

@@ -0,0 +1,110 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.gradle.BuildResult;
import org.gradle.api.Project;
import org.gradle.api.invocation.Gradle;
/**
* A simple manager for {@link SharedService} to be used across gradle (sub) projects.
* This is a basic replacement for gradle's build service api.
*/
public final class SharedServiceManager {
private static final Map<Gradle, SharedServiceManager> SERVICE_FACTORY_MAP = new ConcurrentHashMap<>();
private final Gradle gradle;
private final Map<String, SharedService> sharedServiceMap = new ConcurrentHashMap<>();
private boolean shutdown = false;
private SharedServiceManager(Gradle gradle) {
this.gradle = gradle;
this.gradle.buildFinished(this::onFinish);
}
public static SharedServiceManager get(Project project) {
return get(project.getGradle());
}
public static SharedServiceManager get(Gradle gradle) {
return SERVICE_FACTORY_MAP.computeIfAbsent(gradle, SharedServiceManager::new);
}
public <S extends SharedService> S getOrCreateService(String id, Supplier<S> function) {
synchronized (sharedServiceMap) {
if (shutdown) {
throw new UnsupportedOperationException("Cannot get or create service has the manager has been shutdown.");
}
//noinspection unchecked
S sharedService = (S) sharedServiceMap.get(id);
if (sharedService == null) {
sharedService = function.get();
sharedServiceMap.put(id, sharedService);
}
return sharedService;
}
}
private void onFinish(BuildResult buildResult) {
synchronized (sharedServiceMap) {
shutdown = true;
}
SERVICE_FACTORY_MAP.remove(gradle);
final List<IOException> exceptionList = new ArrayList<>();
for (SharedService sharedService : sharedServiceMap.values()) {
try {
sharedService.close();
} catch (IOException e) {
exceptionList.add(e);
}
}
sharedServiceMap.clear();
if (!exceptionList.isEmpty()) {
// Done to try and close all the services.
RuntimeException exception = new RuntimeException("Failed to close all shared services");
exceptionList.forEach(exception::addSuppressed);
throw exception;
}
// This is required to ensure that mercury releases all of the file handles.
System.gc();
}
}

View File

@@ -0,0 +1,60 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.service;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
// Massive hack to work around WorkerExecutor.noIsolation() doing isolation checks.
public final class UnsafeWorkQueueHelper {
private static final Map<String, SharedService> SERVICE_MAP = new ConcurrentHashMap<>();
private UnsafeWorkQueueHelper() {
}
public static String create(Project project, SharedService service) {
final String uuid = UUID.randomUUID().toString();
SERVICE_MAP.put(uuid, service);
// Ensure we don't make a mess if things go wrong.
project.getGradle().buildFinished(buildResult -> SERVICE_MAP.remove(uuid));
return uuid;
}
public static <S> S get(Property<String> property, Class<S> clazz) {
SharedService service = SERVICE_MAP.remove(property.get());
if (service == null) {
throw new NullPointerException("Failed to get service for " + clazz);
}
//noinspection unchecked
return (S) service;
}
}