Experimental support for split common and clientonly minecraft jars. (#561)

This lays the ground work for split client and server mod code. With this first phase when enabled loom will generate a clientonly and common minecraft jar. Fabric loader and API will both need changes to support this before it can be used to develop mods.

Phase two of this project will handle splitting mod code into a client and common source set along with spliting any dependencies. 

Mostly fixes #539 by sepreating decompile tasks
This commit is contained in:
modmuss50 2022-01-04 21:19:03 +00:00 committed by GitHub
parent ccfe12eb17
commit 4158062ce5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 2150 additions and 999 deletions

View file

@ -25,6 +25,7 @@
package net.fabricmc.loom;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Supplier;
@ -39,13 +40,15 @@ 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;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
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.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;
@ -83,17 +86,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(MappingsProviderImpl.class);
}
default MinecraftMappedProvider getMinecraftMappedProvider() {
return getMappingsProvider().mappedProvider;
}
FileCollection getMinecraftJarsCollection(MappingsNamespace mappingsNamespace);
File getMixinMappings(SourceSet sourceSet);

View file

@ -30,8 +30,8 @@ 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;
@ApiStatus.Experimental /* Very Experimental and not cleanly separated from the impl atm */
public interface MappingContext {

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;
@ -34,7 +33,6 @@ import org.gradle.api.plugins.JavaPlugin;
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;
@ -42,12 +40,21 @@ 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.decompile.MergedDecompileConfiguration;
import net.fabricmc.loom.configuration.decompile.SplitDecompileConfiguration;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
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;
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 {
@ -123,6 +130,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) {
@ -135,13 +147,14 @@ 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));
dependencyManager.addProvider(new MappingsProviderImpl(project));
dependencyManager.addProvider(new LaunchProvider(project));
dependencyManager.handleDependencies(project);
extension.getRemapArchives().finalizeValue();
@ -174,6 +187,82 @@ 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);
boolean split = project.getProperties().get("fabric.loom.experimental.splitMcJars") != null;
// Provide the vanilla mc jars
final MinecraftProvider minecraftProvider = split ? new SplitMinecraftProvider(project) : new MergedMinecraftProvider(project);
extension.setMinecraftProvider(minecraftProvider);
minecraftProvider.provide();
// Provide the mappings
final MappingsProviderImpl mappingsProvider = new MappingsProviderImpl(project, minecraftProvider);
extension.setMappingsProvider(mappingsProvider);
mappingsProvider.provide();
// Provide the remapped mc jars
final IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider;
NamedMinecraftProvider<?> namedMinecraftProvider;
if (split) {
intermediaryMinecraftProvider = new IntermediaryMinecraftProvider.SplitImpl(project, (SplitMinecraftProvider) minecraftProvider);
namedMinecraftProvider = new NamedMinecraftProvider.SplitImpl(project, (SplitMinecraftProvider) minecraftProvider);
} else {
intermediaryMinecraftProvider = new IntermediaryMinecraftProvider.MergedImpl(project, (MergedMinecraftProvider) minecraftProvider);
namedMinecraftProvider = new NamedMinecraftProvider.MergedImpl(project, (MergedMinecraftProvider) minecraftProvider);
}
final JarProcessorManager jarProcessorManager = createJarProcessorManager(project);
if (jarProcessorManager.active()) {
// Wrap the named MC provider for one that will provide the processed jars
if (split) {
namedMinecraftProvider = new ProcessedNamedMinecraftProvider.SplitImpl((NamedMinecraftProvider.SplitImpl) namedMinecraftProvider, jarProcessorManager);
} else {
namedMinecraftProvider = new ProcessedNamedMinecraftProvider.MergedImpl((NamedMinecraftProvider.MergedImpl) 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();
@ -198,39 +287,15 @@ 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;
if (extension.getNamedMinecraftProvider() instanceof MappedMinecraftProvider.Merged mergedMappedMinecraftProvider) {
new MergedDecompileConfiguration(project, mergedMappedMinecraftProvider).afterEvaluation();
} else if (extension.getNamedMinecraftProvider() instanceof MappedMinecraftProvider.Split splitMinecraftProvider) {
new SplitDecompileConfiguration(project, splitMinecraftProvider).afterEvaluation();
} else {
throw new UnsupportedOperationException();
}
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"));
}
});
});
}
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?");
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,105 +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<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(), e);
}
}
}
}
SourceRemapper sourceRemapper = new SourceRemapper(project, true);
String mappingsIdentifier = mappingsProvider.mappingsIdentifier();
if (extension.getInstallerData() == null) {
//If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking
@ -168,14 +77,17 @@ public class LoomDependencyManager {
}
}
if (extension.getInstallerData() == null) {
project.getLogger().warn("fabric-installer.json not found in classpath!");
}
SourceRemapper sourceRemapper = new SourceRemapper(project, true);
String mappingsIdentifier = extension.getMappingsProvider().mappingsIdentifier();
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

@ -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) {

View file

@ -0,0 +1,92 @@
/*
* 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.Project;
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.GenerateSourcesTask;
import net.fabricmc.loom.task.UnpickJarTask;
import net.fabricmc.loom.util.Constants;
public final class MergedDecompileConfiguration {
private final Project project;
private final MappedMinecraftProvider.Merged minecraftProvider;
private final LoomGradleExtension extension;
private final MappingsProviderImpl mappingsProvider;
public MergedDecompileConfiguration(Project project, MappedMinecraftProvider.Merged minecraftProvider) {
this.project = project;
this.minecraftProvider = minecraftProvider;
this.extension = LoomGradleExtension.get(project);
this.mappingsProvider = extension.getMappingsProvider();
}
public void afterEvaluation() {
File mappedJar = minecraftProvider.getMergedJar().toFile();
if (mappingsProvider.hasUnpickDefinitions()) {
File outputJar = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar");
project.getTasks().register("unpickJar", UnpickJarTask.class, unpickJarTask -> {
unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
unpickJarTask.getInputJar().set(minecraftProvider.getMergedJar().toFile());
unpickJarTask.getOutputJar().set(outputJar);
});
mappedJar = outputJar;
}
final File inputJar = mappedJar;
LoomGradleExtension.get(project).getGameDecompilers().forEach(decompiler -> {
String taskName = "genSourcesWith" + decompiler.name();
// Decompiler will be passed to the constructor of GenerateSourcesTask
project.getTasks().register(taskName, GenerateSourcesTask.class, decompiler).configure(task -> {
task.getInputJar().set(inputJar);
task.getRuntimeJar().set(minecraftProvider.getMergedJar().toFile());
task.dependsOn(project.getTasks().named("validateAccessWidener"));
task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name()));
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,132 @@
/*
* 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.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
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 {
private final Project project;
private final MappedMinecraftProvider.Split minecraftProvider;
private final LoomGradleExtension extension;
private final MappingsProviderImpl mappingsProvider;
public SplitDecompileConfiguration(Project project, MappedMinecraftProvider.Split minecraftProvider) {
this.project = project;
this.minecraftProvider = minecraftProvider;
this.extension = LoomGradleExtension.get(project);
this.mappingsProvider = extension.getMappingsProvider();
}
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("Common", minecraftProvider.getCommonJar().toFile(), commonJarToDecompile);
unpickClientOnlyJar = createUnpickJarTask("ClientOnly", 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<UnpickJarTask> createUnpickJarTask(String name, File inputJar, File outputJar) {
return project.getTasks().register("unpick%sJar".formatted(name), UnpickJarTask.class, unpickJarTask -> {
unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
unpickJarTask.getInputJar().set(inputJar);
unpickJarTask.getOutputJar().set(outputJar);
});
}
private TaskProvider<Task> createDecompileTasks(String name, Action<GenerateSourcesTask> configureAction) {
extension.getGameDecompilers().forEach(decompiler -> {
final String taskName = "gen%sSourcesWith%s".formatted(name, decompiler.name());
project.getTasks().register(taskName, GenerateSourcesTask.class, decompiler).configure(task -> {
configureAction.execute(task);
task.dependsOn(project.getTasks().named("validateAccessWidener"));
task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name()));
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

@ -33,7 +33,7 @@ import org.gradle.execution.taskgraph.TaskExecutionGraphInternal;
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"));
ideaSyncTask1.dependsOn(project.getTasks().named("configureLaunch"));
});
if (!IdeaUtils.isIdeaSync()) {

View file

@ -51,6 +51,7 @@ 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.Constants;
@ -272,7 +273,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

@ -46,7 +46,6 @@ 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.util.Constants;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils;
@ -134,10 +133,8 @@ 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();
Path intermediaryJar = mappedProvider.getIntermediaryJar().toPath();
Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles()
.stream().map(File::toPath).toArray(Path[]::new);
@ -148,7 +145,10 @@ public class ModProcessor {
.renameInvalidLocals(false)
.build();
remapper.readClassPathAsync(intermediaryJar);
for (Path minecraftJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
remapper.readClassPathAsync(minecraftJar);
}
remapper.readClassPathAsync(mcDeps);
final Map<ModDependencyInfo, InputTag> tagMap = new HashMap<>();

View file

@ -1,96 +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) {
if (jarProcessorManager.isInvalid(projectMappedJar) || isRefreshDeps()) {
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: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() + "/minecraft-" + projectMappedClassifier + ".jar");
}
@Override
public File getMappedJar() {
return projectMappedJar;
}
}

View file

@ -35,7 +35,7 @@ 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;
public class GradleMappingContext implements MappingContext {
private final Project project;

View file

@ -39,7 +39,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import com.google.common.base.Stopwatch;
@ -53,14 +52,9 @@ import org.objectweb.asm.Opcodes;
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.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.configuration.DependencyInfo;
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;
@ -76,9 +70,7 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.stitch.Command;
import net.fabricmc.stitch.commands.CommandProposeFieldNames;
public class MappingsProviderImpl extends DependencyProvider implements MappingsProvider {
public MinecraftMappedProvider mappedProvider;
public class MappingsProviderImpl implements MappingsProvider {
public String mappingsIdentifier;
private Path mappingsWorkingDir;
@ -95,19 +87,23 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
private MemoryMappingTree mappingTree;
private Map<String, String> signatureFixes;
public MappingsProviderImpl(Project project) {
super(project);
private final Project project;
private final MinecraftProvider minecraftProvider;
private final LoomGradleExtension extension;
public MappingsProviderImpl(Project project, MinecraftProvider minecraftProvider) {
this.project = project;
this.minecraftProvider = minecraftProvider;
this.extension = LoomGradleExtension.get(project);
}
public MemoryMappingTree getMappings() throws IOException {
return Objects.requireNonNull(mappingTree, "Cannot get mappings before they have been read");
}
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
MinecraftProviderImpl minecraftProvider = getDependencyManager().getProvider(MinecraftProviderImpl.class);
public void provide() throws Exception {
final DependencyInfo dependency = DependencyInfo.create(project, Constants.Configurations.MAPPINGS);
getProject().getLogger().info(":setting up mappings (" + dependency.getDependency().getName() + " " + dependency.getResolvedVersion() + ")");
project.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));
@ -119,7 +115,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
initFiles();
if (Files.notExists(tinyMappings) || isRefreshDeps()) {
storeMappings(getProject(), minecraftProvider, mappingsJar.toPath());
storeMappings(project, minecraftProvider, mappingsJar.toPath());
} else {
try (FileSystem fileSystem = FileSystems.newFileSystem(mappingsJar.toPath(), (ClassLoader) null)) {
extractExtras(fileSystem);
@ -140,49 +136,11 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
dependency.getDependency().getVersion()
);
getProject().getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation);
project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation);
populateUnpickClasspath();
}
addDependency(tinyMappingsJar.toFile(), 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 (processorManager.active()) {
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()));
}
private String getMappingsClassifier(DependencyInfo dependency, boolean isV2) {
@ -196,7 +154,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
private boolean isV2(DependencyInfo dependency, File mappingsJar) throws IOException {
String minecraftVersion = getMinecraftProvider().minecraftVersion();
String minecraftVersion = minecraftProvider.minecraftVersion();
// 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) {
@ -205,7 +163,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
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);
project.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
@ -215,7 +173,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
}
private void storeMappings(Project project, MinecraftProviderImpl minecraftProvider, Path yarnJar) throws IOException {
private void storeMappings(Project project, MinecraftProvider minecraftProvider, Path yarnJar) throws IOException {
project.getLogger().info(":extracting " + yarnJar.getFileName());
try (FileSystem fileSystem = FileSystems.newFileSystem(yarnJar, (ClassLoader) null)) {
@ -227,10 +185,14 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
// These are unmerged v2 mappings
mergeAndSaveMappings(project, baseTinyMappings, tinyMappings);
} else {
if (minecraftProvider instanceof MergedMinecraftProvider mergedMinecraftProvider) {
// These are merged v1 mappings
Files.deleteIfExists(tinyMappings);
project.getLogger().lifecycle(":populating field names");
suggestFieldNames(minecraftProvider, baseTinyMappings, tinyMappings);
suggestFieldNames(mergedMinecraftProvider, baseTinyMappings, tinyMappings);
} else {
throw new UnsupportedOperationException("V1 mappings only support merged minecraft");
}
}
}
@ -311,7 +273,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
private void populateUnpickClasspath() {
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)
);
@ -324,7 +286,7 @@ 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())
);
}
@ -400,7 +362,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
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(),
@ -416,7 +378,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
private void initFiles() {
mappingsWorkingDir = getMinecraftProvider().dir(mappingsIdentifier).toPath();
mappingsWorkingDir = minecraftProvider.dir(mappingsIdentifier).toPath();
baseTinyMappings = mappingsWorkingDir.resolve("mappings-base.tiny");
tinyMappings = mappingsWorkingDir.resolve("mappings.tiny");
tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar");
@ -439,23 +401,18 @@ 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();
intermediaryTiny = minecraftProvider.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());
String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(minecraftProvider.minecraftVersion());
String intermediaryArtifactUrl = extension.getIntermediaryUrl(encodedMinecraftVersion);
File intermediaryJar = minecraftProvider.file("intermediary-v2.jar");
DownloadUtil.downloadIfChanged(new URL(intermediaryArtifactUrl), intermediaryJar, project.getLogger());
extractMappings(intermediaryJar.toPath(), intermediaryTiny);
}
}
@ -471,7 +428,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
private String createMappingsIdentifier(String mappingsName, String version, String classifier) {
// 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 + "." + minecraftProvider.minecraftVersion().replace(' ', '_').replace('.', '_').replace('-', '_') + "." + version + classifier;
}
public String mappingsIdentifier() {
@ -506,4 +463,8 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
public record UnpickMetadata(String unpickGroup, String unpickVersion) {
}
protected boolean isRefreshDeps() {
return LoomGradlePlugin.refreshDeps;
}
}

View file

@ -0,0 +1,95 @@
/*
* 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.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 File minecraftMergedJar;
public MergedMinecraftProvider(Project project) {
super(project);
}
@Override
protected void initFiles() {
super.initFiles();
minecraftMergedJar = file("minecraft-merged.jar");
}
@Override
public List<Path> getMinecraftJars() {
return List.of(minecraftMergedJar.toPath());
}
@Override
public void provide() throws Exception {
super.provide();
if (!minecraftMergedJar.exists() || isRefreshDeps()) {
try {
mergeJars();
} catch (Throwable e) {
HashedDownloadUtil.delete(getMinecraftClientJar());
HashedDownloadUtil.delete(getMinecraftServerJar());
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 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)) {
jarMerger.enableSyntheticParamsOffset();
jarMerger.merge();
}
}
public File getMergedJar() {
return minecraftMergedJar;
}
}

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

@ -31,13 +31,12 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.ExternalModuleDependency;
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) {
public void provide(MinecraftProvider minecraftProvider, Project project) {
MinecraftVersionMeta versionInfo = minecraftProvider.getVersionInfo();
BundleMetadata serverBundleMetadata = minecraftProvider.getServerBundleMetadata();

View file

@ -1,170 +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.Arrays;
import java.util.Map;
import java.util.function.Consumer;
import org.gradle.api.Project;
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.util.Constants;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
public class MinecraftMappedProvider extends DependencyProvider {
private File minecraftMappedJar;
private File minecraftIntermediaryJar;
private 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 (!getExtension().getMinecraftProvider().getMergedJar().exists()) {
throw new RuntimeException("input merged jar not found");
}
if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || isRefreshDeps()) {
if (minecraftMappedJar.exists()) {
minecraftMappedJar.delete();
}
minecraftMappedJar.getParentFile().mkdirs();
if (minecraftIntermediaryJar.exists()) {
minecraftIntermediaryJar.delete();
}
try {
mapMinecraftJar();
} catch (Throwable t) {
// Cleanup some some things that may be in a bad state now
minecraftMappedJar.delete();
minecraftIntermediaryJar.delete();
getExtension().getMappingsProvider().cleanFiles();
throw new RuntimeException("Failed to remap minecraft", t);
}
}
if (!minecraftMappedJar.exists()) {
throw new RuntimeException("mapped jar not found");
}
addDependencies(dependency, postPopulationScheduler);
}
private void mapMinecraftJar() throws IOException {
String fromM = MappingsNamespace.OFFICIAL.toString();
MappingsProviderImpl mappingsProvider = getExtension().getMappingsProvider();
Path input = minecraftProvider.getMergedJar().toPath();
Path outputMapped = minecraftMappedJar.toPath();
Path outputIntermediary = minecraftIntermediaryJar.toPath();
for (String toM : Arrays.asList(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString())) {
final boolean toNamed = MappingsNamespace.NAMED.toString().equals(toM);
final boolean toIntermediary = MappingsNamespace.INTERMEDIARY.toString().equals(toM);
final Path output = toNamed ? outputMapped : outputIntermediary;
getProject().getLogger().info(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")");
Files.deleteIfExists(output);
final Map<String, String> remappedSignatures = SignatureFixerApplyVisitor.getRemappedSignatures(toIntermediary, mappingsProvider, getProject(), toM);
TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(getProject(), fromM, toM, true, (builder) -> {
builder.extraPostApplyVisitor(new SignatureFixerApplyVisitor(remappedSignatures));
});
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) {
outputConsumer.addNonClassFiles(input);
remapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(getProject()));
remapper.readInputs(input);
remapper.apply(outputConsumer);
} catch (Exception e) {
throw new RuntimeException("Failed to remap JAR " + input + " with mappings from " + mappingsProvider.tinyMappings, e);
} finally {
remapper.finish();
}
}
}
protected void addDependencies(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) {
getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED,
getProject().getDependencies().module("net.minecraft: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");
minecraftMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-mapped.jar");
}
protected File getJarDirectory(File parentDirectory, String type) {
return new File(parentDirectory, getJarVersionString(type));
}
protected String getJarVersionString(String type) {
return String.format("%s-%s", type, getExtension().getMappingsProvider().mappingsIdentifier());
}
public File getIntermediaryJar() {
return minecraftIntermediaryJar;
}
public File getMappedJar() {
return minecraftMappedJar;
}
public final File getBaseMappedJar() {
return minecraftMappedJar;
}
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,15 +22,17 @@
* 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.io.Files;
import org.gradle.api.GradleException;
@ -38,18 +40,16 @@ 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;
@ -64,16 +64,17 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
private File minecraftExtractedServerJar;
@Nullable
private BundleMetadata serverBundleMetadata;
private File minecraftMergedJar;
private File versionManifestJson;
private File experimentalVersionsJson;
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();
boolean offline = getProject().getGradle().getStartParameter().isOffline();
@ -89,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());
}
@ -103,31 +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");
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 {
@ -254,55 +238,52 @@ 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");
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()));
if (getServerBundleMetadata().versions().size() != 1) {
throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(getServerBundleMetadata().versions().size()));
}
serverBundleMetadata.versions().get(0).unpackEntry(minecraftServerJar.toPath(), minecraftExtractedServerJar.toPath());
jarToMerge = minecraftExtractedServerJar;
getServerBundleMetadata().versions().get(0).unpackEntry(minecraftServerJar.toPath(), getMinecraftExtractedServerJar().toPath());
}
try (JarMerger jarMerger = new JarMerger(minecraftClientJar, jarToMerge, minecraftMergedJar)) {
jarMerger.enableSyntheticParamsOffset();
jarMerger.merge();
}
}
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 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.
@Deprecated
public File getMinecraftServerJar() {
return minecraftServerJar;
}
public String minecraftVersion() {
return minecraftVersion;
}
@Override
public MinecraftVersionMeta getVersionInfo() {
return versionInfo;
}
@ -311,7 +292,6 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
return libraryProvider;
}
@Override
public String getTargetConfig() {
return Constants.Configurations.MINECRAFT;
}
@ -320,4 +300,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,99 @@
/*
* 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.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 File minecraftClientOnlyJar;
private File minecraftCommonJar;
public SplitMinecraftProvider(Project project) {
super(project);
}
@Override
protected void initFiles() {
super.initFiles();
minecraftClientOnlyJar = file("minecraft-client-only.jar");
minecraftCommonJar = file("minecraft-common.jar");
}
@Override
public List<Path> getMinecraftJars() {
return List.of(minecraftClientOnlyJar.toPath(), minecraftCommonJar.toPath());
}
@Override
public void provide() throws Exception {
super.provide();
boolean requiresRefresh = isRefreshDeps() || !minecraftClientOnlyJar.exists() || !minecraftCommonJar.exists();
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.toPath(), minecraftCommonJar.toPath());
} catch (Exception e) {
Files.deleteIfExists(minecraftClientOnlyJar.toPath());
Files.deleteIfExists(minecraftCommonJar.toPath());
throw new RuntimeException("Failed to split minecraft", e);
}
}
public File getMinecraftClientOnlyJar() {
return minecraftClientOnlyJar;
}
public File getMinecraftCommonJar() {
return minecraftCommonJar;
}
}

View file

@ -41,14 +41,14 @@ 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;
import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
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,87 @@
/*
* 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.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.SplitImpl, IntermediaryMinecraftProvider.MergedImpl {
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().toPath(), 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().toPath(), getCommonJar(), MappingsNamespace.OFFICIAL),
new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar().toPath(), getClientOnlyJar(), MappingsNamespace.OFFICIAL, minecraftProvider.getMinecraftCommonJar().toPath())
);
}
@Override
protected void configureRemapper(RemappedJars remappedJars, TinyRemapper.Builder tinyRemapperBuilder) {
if (remappedJars.outputJar().equals(getClientOnlyJar())) {
tinyRemapperBuilder.extraPostApplyVisitor(SidedClassVisitor.CLIENT);
}
}
}
}

View file

@ -0,0 +1,67 @@
/*
* 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());
}
}
}

View file

@ -0,0 +1,100 @@
/*
* 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.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().toPath(), 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().toPath(), getCommonJar(), MappingsNamespace.OFFICIAL),
new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar().toPath(), getClientOnlyJar(), MappingsNamespace.OFFICIAL, minecraftProvider.getMinecraftCommonJar().toPath())
);
}
@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);
}
}
}

View file

@ -0,0 +1,147 @@
/*
* 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.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);
Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING);
jarProcessorManager.process(outputJar.toFile());
}
}
if (applyDependencies) {
parentMinecraftProvider.applyDependencies((configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)));
}
}
@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());
}
}
}

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

@ -96,6 +96,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.versionParser = new ModVersionParser(project);
this.deprecationHelper = new DeprecationHelper.ProjectBased(project);
this.accessWidener.finalizeValueOnRead();
this.getGameJarProcessors().finalizeValueOnRead();
}
@Override

View file

@ -25,6 +25,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;
@ -43,10 +44,15 @@ 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.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;
public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implements LoomGradleExtension {
private final Project project;
@ -62,6 +68,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;
public LoomGradleExtensionImpl(Project project, LoomFiles files) {
@ -116,6 +126,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) {
return srcMappingCache[id] != null ? srcMappingCache[id] : (srcMappingCache[id] = factory.get());

View file

@ -57,12 +57,10 @@ 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.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;
@ -77,9 +75,18 @@ import net.fabricmc.loom.util.ipc.IPCServer;
public abstract class GenerateSourcesTask extends AbstractLoomTask {
public final LoomDecompiler decompiler;
/**
* The jar to decompile, can be the unpick jar.
*/
@InputFile
public abstract RegularFileProperty getInputJar();
/**
* The jar used at runtime.
*/
@InputFile
public abstract RegularFileProperty getRuntimeJar();
/**
* Max memory for forked JVM in megabytes.
*/
@ -153,7 +160,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
params.getOptions().set(getOptions());
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"));
@ -299,10 +306,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
private File getMappedJarFileWithSuffix(String suffix) {
LoomGradleExtension extension = LoomGradleExtension.get(getProject());
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
File mappedJar = 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

@ -31,6 +31,9 @@ import org.gradle.api.tasks.TaskProvider;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
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 +50,32 @@ 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("extractNatives"));
task.dependsOn(tasks.named("downloadAssets"));
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);
});
TaskProvider<ValidateAccessWidenerTask> validateAccessWidener = tasks.register("validateAccessWidener", ValidateAccessWidenerTask.class, t -> {
t.setDescription("Validate all the rules in the access widener against the Minecraft jar");
@ -62,19 +86,18 @@ public final class LoomTasks {
registerIDETasks(tasks);
registerRunTasks(tasks, 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", "configureLaunch");
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("configureLaunch");
t.setGroup(Constants.TaskGroup.IDE);
});
@ -85,7 +108,7 @@ public final class LoomTasks {
tasks.register("vscode", GenVsCodeProjectTask.class, t -> {
t.setDescription("Generates VSCode launch configurations.");
t.dependsOn("downloadAssets");
t.dependsOn("configureLaunch");
t.setGroup(Constants.TaskGroup.IDE);
});
}
@ -102,32 +125,11 @@ 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("configureLaunch");
});
});
extension.getRunConfigs().create("client", RunConfigSettings::client);
extension.getRunConfigs().create("server", RunConfigSettings::server);
}
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"));
});
});
tasks.register("genSources", task -> {
task.setDescription("Decompile minecraft using the default decompiler.");
task.setGroup(Constants.TaskGroup.FABRIC);
task.dependsOn(project.getTasks().named("genSourcesWithCfr"));
});
}
}

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.getMinecraftMappedProvider(), inputDir, outputDir, currentMappings, targetMappings);
migrateMappings(project, 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,7 +156,7 @@ public class MigrateMappingsTask extends AbstractLoomTask {
return mappingTree;
}
private static void migrateMappings(Project project, MinecraftMappedProvider minecraftMappedProvider,
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);
}
mercury.getProcessors().add(MercuryRemapper.create(mappingSet));

View file

@ -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;
@ -33,8 +34,10 @@ import java.nio.file.Files;
import javax.inject.Inject;
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;
@ -42,6 +45,7 @@ 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;
@ -50,15 +54,15 @@ public abstract class ValidateAccessWidenerTask extends DefaultTask {
@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

@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2019-2021 FabricMC
* 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
@ -22,43 +22,33 @@
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers;
package net.fabricmc.loom.task.launch;
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.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 org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.configuration.DependencyProvider;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.task.AbstractLoomTask;
public class LaunchProvider extends DependencyProvider {
public LaunchProvider(Project project) {
super(project);
}
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws IOException {
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", getRemapClasspathFile().getAbsolutePath())
.property("fabric.remapClasspathFile", getExtension().getFiles().getRemapClasspathFile().getAbsolutePath())
.property("log4j.configurationFile", getAllLog4JConfigFiles())
.property("log4j2.formatMsgNoLookups", "true")
@ -68,7 +58,7 @@ public class LaunchProvider extends DependencyProvider {
.argument("client", "--assetIndex")
.argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion()))
.argument("client", "--assetsDir")
.argument("client", new File(getDirectories().getUserCache(), "assets").getAbsolutePath());
.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()
@ -80,18 +70,7 @@ public class LaunchProvider extends DependencyProvider {
launchConfig.property("fabric.log.disableAnsi", "false");
}
writeLog4jConfig();
FileUtils.writeStringToFile(getDirectories().getDevLauncherConfig(), launchConfig.asString(), StandardCharsets.UTF_8);
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);
postPopulationScheduler.accept(this::writeRemapClassPath);
}
private File getLog4jConfigFile() {
return getDirectories().getDefaultLog4jConfigFile();
FileUtils.writeStringToFile(getExtension().getFiles().getDevLauncherConfig(), launchConfig.asString(), StandardCharsets.UTF_8);
}
private String getAllLog4JConfigFiles() {
@ -100,48 +79,6 @@ public class LaunchProvider extends DependencyProvider {
.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());
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<>();

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) 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
@ -22,20 +22,27 @@
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers;
package net.fabricmc.loom.task.launch;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import org.gradle.api.tasks.TaskAction;
public interface MinecraftProvider {
File workingDir();
import net.fabricmc.loom.task.AbstractLoomTask;
File dir(String path);
public abstract class GenerateLog4jConfigTask extends AbstractLoomTask {
@TaskAction
public void run() {
Path outputFile = getExtension().getFiles().getDefaultLog4jConfigFile().toPath();
File file(String path);
String minecraftVersion();
MinecraftVersionMeta getVersionInfo();
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

@ -48,7 +48,7 @@ public class HashedDownloadUtil {
}
public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, 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

@ -183,8 +183,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);
}
Set<File> files = project.getConfigurations()
.detachedConfiguration(project.getDependencies().create(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS))

View file

@ -28,7 +28,7 @@ import org.gradle.util.GradleVersion
class LoomTestConstants {
public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion()
public final static String PRE_RELEASE_GRADLE = "7.5-20211228231407+0000"
public final static String PRE_RELEASE_GRADLE = "7.5-20220101231120+0000"
public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE]
}

View file

@ -0,0 +1,75 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.benchmark
import groovy.time.TimeCategory
import groovy.time.TimeDuration
import net.fabricmc.loom.test.LoomTestConstants
import net.fabricmc.loom.test.util.GradleProjectTestTrait
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
/**
* Run this class, passing a working dir as the first argument.
* Allow for one warm up run before profiling, follow up runs should not be using the network.
*/
@Singleton
class SimpleBenchmark implements GradleProjectTestTrait {
def run(File dir) {
// Forces loom to refresh files
System.setProperty("loom.refresh", "true")
def gradle = gradleProject(
project: "minimalBase",
version: LoomTestConstants.PRE_RELEASE_GRADLE,
projectDir: new File(dir, "project"),
gradleHomeDir: new File(dir, "gradlehome")
)
gradle.buildGradle << '''
dependencies {
minecraft "com.mojang:minecraft:1.18.1"
mappings "net.fabricmc:yarn:1.18.1+build.17:v2"
modImplementation "net.fabricmc.fabric-api:fabric-api:0.45.1+1.18"
}
'''
def timeStart = new Date()
def result = gradle.run(tasks: ["clean", "build"])
def timeStop = new Date()
TimeDuration duration = TimeCategory.minus(timeStop, timeStart)
println(duration)
assert result.task(":build").outcome == SUCCESS
System.exit(0)
}
static void main(String[] args) {
getInstance().run(new File(args[0]))
}
}

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.test.unit
import net.fabricmc.loom.configuration.providers.BundleMetadata
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarSplitter
import spock.lang.Specification
class MinecraftJarSplitterTest extends Specification {
public static final String CLIENT_JAR_URL = "https://launcher.mojang.com/v1/objects/7e46fb47609401970e2818989fa584fd467cd036/client.jar"
public static final String SERVER_BUNDLE_JAR_URL = "https://launcher.mojang.com/v1/objects/125e5adf40c659fd3bce3e66e67a16bb49ecc1b9/server.jar"
public static final File mcJarDir = File.createTempDir()
def "split jars"() {
given:
def clientJar = downloadJarIfNotExists(CLIENT_JAR_URL, "client.jar")
def serverBundleJar = downloadJarIfNotExists(SERVER_BUNDLE_JAR_URL, "server_bundle.jar")
def serverJar = new File(mcJarDir, "server.jar")
def clientOnlyJar = new File(mcJarDir, "client_only.jar")
def commonJar = new File(mcJarDir, "common.jar")
when:
def serverBundleMetadata = BundleMetadata.fromJar(serverBundleJar.toPath())
serverBundleMetadata.versions().find().unpackEntry(serverBundleJar.toPath(), serverJar.toPath())
clientOnlyJar.delete()
commonJar.delete()
new MinecraftJarSplitter(clientJar.toPath(), serverJar.toPath()).withCloseable {
it.split(clientOnlyJar.toPath(), commonJar.toPath())
}
then:
serverBundleMetadata.versions().size() == 1
}
File downloadJarIfNotExists(String url, String name) {
File dst = new File(mcJarDir, name)
if (!dst.exists()) {
dst.parentFile.mkdirs()
dst << new URL(url).newInputStream()
}
return dst
}
}

View file

@ -25,7 +25,7 @@
package net.fabricmc.loom.test.unit.layeredmappings
import groovy.transform.CompileStatic
import net.fabricmc.loom.configuration.providers.MinecraftProvider
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsProcessor
import net.fabricmc.loom.api.mappings.layered.MappingContext

View file

@ -211,7 +211,7 @@ trait GradleProjectTestTrait {
}
File getGeneratedSources(String mappings) {
return new File(getGradleHomeDir(), "caches/fabric-loom/${mappings}/minecraft-mapped-sources.jar")
return new File(getGradleHomeDir(), "caches/fabric-loom/${mappings}/minecraft-merged-named-sources.jar")
}
}
}