Merge remote-tracking branch 'FabricMC/dev/0.13' into dev/0.13.0

Signed-off-by: shedaniel <daniel@shedaniel.me>

# Conflicts:
#	src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java
#	src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
This commit is contained in:
shedaniel
2022-08-11 00:56:49 +08:00
27 changed files with 462 additions and 106 deletions

View File

@@ -143,6 +143,10 @@ dependencies {
compileOnly 'org.jetbrains:annotations:23.0.0'
testCompileOnly 'org.jetbrains:annotations:23.0.0'
testCompileOnly ('net.fabricmc:sponge-mixin:0.11.4+mixin.0.8.5') {
transitive = false
}
}
jar {

View File

@@ -24,6 +24,7 @@
package net.fabricmc.loom.api;
import java.io.File;
import java.util.List;
import java.util.function.Consumer;
@@ -49,6 +50,7 @@ import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.configuration.providers.mappings.NoOpIntermediateMappingsProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.util.DeprecationHelper;
import net.fabricmc.loom.util.ModPlatform;
@@ -167,6 +169,17 @@ public interface LoomGradleExtensionAPI {
setIntermediateMappingsProvider(NoOpIntermediateMappingsProvider.class, p -> { });
}
/**
* Returns the tiny mappings file used to remap the game and mods.
*/
File getMappingsFile();
/**
* Returns the {@link GenerateSourcesTask} for the given {@link DecompilerOptions}.
* When env source sets are split and the client param is true the decompile task for the client jar will be returned.
*/
GenerateSourcesTask getDecompileTask(DecompilerOptions options, boolean client);
/**
* Use "%1$s" as a placeholder for the minecraft version.
*

View File

@@ -67,6 +67,10 @@ public abstract class DecompilerOptions implements Named {
getMaxThreads().convention(Runtime.getRuntime().availableProcessors()).finalizeValueOnRead();
}
public String getFormattedName() {
return getName().substring(0, 1).toUpperCase() + getName().substring(1);
}
// Done to work around weird issues with the workers, possibly https://github.com/gradle/gradle/issues/13422
public record Dto(String className, Map<String, String> options, int maxThreads) implements Serializable { }

View File

@@ -44,7 +44,7 @@ public interface FileMappingsSpecBuilder {
* <p>The default mapping path is {@code mappings/mappings.tiny}, matching regular mapping dependency jars
* such as Yarn's.
*
* @param mappingPath the mapping path, or null if a bare file
* @param mappingPath the mapping path
* @return this builder
*/
FileMappingsSpecBuilder mappingPath(String mappingPath);

View File

@@ -34,6 +34,7 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.VisibleForTesting;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
@@ -161,7 +162,8 @@ public final class RemapConfigurations {
return str.substring(0, 1).toUpperCase(Locale.ROOT) + str.substring(1);
}
private record ConfigurationOption(Function<SourceSet, String> targetNameFunc, boolean compileClasspath, boolean runtimeClasspath, RemapConfigurationSettings.PublishingMode publishingMode) {
@VisibleForTesting
public record ConfigurationOption(Function<SourceSet, String> targetNameFunc, boolean compileClasspath, boolean runtimeClasspath, RemapConfigurationSettings.PublishingMode publishingMode) {
String targetName(SourceSet sourceSet) {
return targetNameFunc.apply(sourceSet);
}
@@ -170,27 +172,25 @@ public final class RemapConfigurations {
return targetName(sourceSet) != null;
}
String name(SourceSet sourceSet) {
public String name(SourceSet sourceSet) {
String targetName = targetName(sourceSet);
if (targetName == null) {
throw new UnsupportedOperationException("Configuration option is not available for sourceset (%s)".formatted(sourceSet.getName()));
}
final StringBuilder builder = new StringBuilder();
if (!SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) {
builder.append(sourceSet.getName());
if (targetName.startsWith(sourceSet.getName())) {
targetName = targetName.substring(sourceSet.getName().length());
}
if (builder.isEmpty()) {
builder.append("mod");
} else {
builder.append("Mod");
final StringBuilder builder = new StringBuilder();
builder.append("mod");
if (!SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) {
builder.append(capitalise(sourceSet.getName()));
}
builder.append(capitalise(targetName));
return builder.toString();
}
}

View File

@@ -59,7 +59,7 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration<Mapp
final File inputJar = mappedJar;
LoomGradleExtension.get(project).getDecompilerOptions().forEach(options -> {
final String decompilerName = options.getName().substring(0, 1).toUpperCase() + options.getName().substring(1);
final String decompilerName = options.getFormattedName();
String taskName = "genSourcesWith" + decompilerName;
// Decompiler will be passed to the constructor of GenerateSourcesTask
project.getTasks().register(taskName, GenerateSourcesTask.class, options).configure(task -> {

View File

@@ -31,6 +31,7 @@ import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskProvider;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.task.UnpickJarTask;
@@ -84,6 +85,18 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration<Ma
task.mustRunAfter(commonDecompileTask);
});
for (DecompilerOptions options : extension.getDecompilerOptions()) {
final String decompilerName = options.getFormattedName();
project.getTasks().register("genSourcesWith" + decompilerName, task -> {
task.setDescription("Decompile minecraft using %s.".formatted(decompilerName));
task.setGroup(Constants.TaskGroup.FABRIC);
task.dependsOn(project.getTasks().named("gen%sSourcesWith%s".formatted("Common", decompilerName)));
task.dependsOn(project.getTasks().named("gen%sSourcesWith%s".formatted("ClientOnly", decompilerName)));
});
}
project.getTasks().register("genSources", task -> {
task.setDescription("Decompile minecraft using the default decompiler.");
task.setGroup(Constants.TaskGroup.FABRIC);
@@ -95,7 +108,7 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration<Ma
private TaskProvider<Task> createDecompileTasks(String name, Action<GenerateSourcesTask> configureAction) {
extension.getDecompilerOptions().forEach(options -> {
final String decompilerName = options.getName().substring(0, 1).toUpperCase() + options.getName().substring(1);
final String decompilerName = options.getFormattedName();
final String taskName = "gen%sSourcesWith%s".formatted(name, decompilerName);
project.getTasks().register(taskName, GenerateSourcesTask.class, options).configure(task -> {

View File

@@ -43,7 +43,6 @@ import java.util.stream.Collectors;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.tools.ant.util.StringUtils;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedArtifact;

View File

@@ -47,6 +47,8 @@ import net.fabricmc.loom.task.AbstractRemapJarTask;
import net.fabricmc.loom.util.FileSystemUtil;
public class JarSplitter {
public static final String MANIFEST_SPLIT_ENV_NAME_KEY = "Fabric-Loom-Split-Environment-Name";
final Path inputJar;
public JarSplitter(Path inputJar) {
@@ -155,30 +157,7 @@ public class JarSplitter {
final String entryPath = relativePath.toString();
/*
Copy the manifest to both jars
- Remove signature data
- Remove split data as its already been split.
*/
if (entryPath.equals(AbstractRemapJarTask.MANIFEST_PATH)) {
final Manifest outManifest = new Manifest(manifest);
final Attributes attributes = outManifest.getMainAttributes();
stripSignatureData(outManifest);
attributes.remove(Attributes.Name.SIGNATURE_VERSION);
Objects.requireNonNull(attributes.remove(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_NAME));
Objects.requireNonNull(attributes.remove(AbstractRemapJarTask.MANIFEST_CLIENT_ENTRIES_NAME));
// TODO add an attribute to denote if the jar is common or client now
final ByteArrayOutputStream out = new ByteArrayOutputStream();
outManifest.write(out);
final byte[] manifestBytes = out.toByteArray();
writeBytes(manifestBytes, commonOutput.getPath(AbstractRemapJarTask.MANIFEST_PATH));
writeBytes(manifestBytes, clientOutput.getPath(AbstractRemapJarTask.MANIFEST_PATH));
continue;
}
@@ -192,12 +171,39 @@ public class JarSplitter {
Files.copy(entry, outputEntry, StandardCopyOption.COPY_ATTRIBUTES);
}
/*
Write the manifest to both jars
- Remove signature data
- Remove split data as its already been split.
- Add env name.
*/
final Manifest outManifest = new Manifest(manifest);
final Attributes attributes = outManifest.getMainAttributes();
stripSignatureData(outManifest);
attributes.remove(Attributes.Name.SIGNATURE_VERSION);
Objects.requireNonNull(attributes.remove(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_NAME));
Objects.requireNonNull(attributes.remove(AbstractRemapJarTask.MANIFEST_CLIENT_ENTRIES_NAME));
writeBytes(writeWithEnvironment(outManifest, "common"), commonOutput.getPath(AbstractRemapJarTask.MANIFEST_PATH));
writeBytes(writeWithEnvironment(outManifest, "client"), clientOutput.getPath(AbstractRemapJarTask.MANIFEST_PATH));
}
}
return true;
}
private byte[] writeWithEnvironment(Manifest in, String value) throws IOException {
final Manifest manifest = new Manifest(in);
final Attributes attributes = manifest.getMainAttributes();
attributes.putValue(MANIFEST_SPLIT_ENV_NAME_KEY, value);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
return out.toByteArray();
}
private List<String> readClientEntries(Manifest manifest) {
final Attributes attributes = manifest.getMainAttributes();
final String clientEntriesValue = attributes.getValue(AbstractRemapJarTask.MANIFEST_CLIENT_ENTRIES_KEY);

View File

@@ -103,6 +103,7 @@ public class ModProcessor {
return;
}
// Strip out all contained jar info as we dont want loader to try and load the jars contained in dev.
try {
ZipUtils.transformJson(JsonObject.class, path, Map.of("fabric.mod.json", json -> {

View File

@@ -25,16 +25,20 @@
package net.fabricmc.loom.configuration.providers;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.AttributeHelper;
import net.fabricmc.loom.util.FileSystemUtil;
public record BundleMetadata(List<Entry> libraries, List<Entry> versions, String mainClass) {
@@ -83,10 +87,38 @@ public record BundleMetadata(List<Entry> libraries, List<Entry> versions, String
}
public record Entry(String sha1, String name, String path) {
public void unpackEntry(Path jar, Path dest) throws IOException {
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar);
InputStream is = Files.newInputStream(fs.get().getPath(path()))) {
Files.copy(is, dest, StandardCopyOption.REPLACE_EXISTING);
public void unpackEntry(Path jar, Path dest, Project project) throws IOException {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
if (!extension.refreshDeps() && Files.exists(dest)) {
final String hash = readHash(dest).orElse("");
if (hash.equals(sha1)) {
// File exists with expected hash
return;
}
}
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar)) {
Files.copy(fs.get().getPath(path()), dest, StandardCopyOption.REPLACE_EXISTING);
}
writeHash(dest, sha1);
}
private Optional<String> readHash(Path output) {
try {
return AttributeHelper.readAttribute(output, "LoomHash");
} catch (IOException e) {
return Optional.empty();
}
}
private void writeHash(Path output, String eTag) {
try {
AttributeHelper.writeAttribute(output, "LoomHash", eTag);
} catch (IOException e) {
throw new UncheckedIOException("Failed to write hash to (%s)".formatted(output), e);
}
}
}

View File

@@ -63,7 +63,6 @@ import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingsProv
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.mappings.tiny.MappingsMerger;
import net.fabricmc.loom.configuration.providers.mappings.tiny.TinyJarInfo;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.DeletingFileVisitor;
@@ -314,14 +313,16 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
// These are unmerged v2 mappings
MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, intermediaryService.get());
} else {
if (minecraftProvider instanceof MergedMinecraftProvider mergedMinecraftProvider) {
// These are merged v1 mappings
Files.deleteIfExists(tinyMappings);
LOGGER.info(":populating field names");
suggestFieldNames(mergedMinecraftProvider, baseTinyMappings, tinyMappings);
} else {
throw new UnsupportedOperationException("V1 mappings only support merged minecraft");
final List<Path> minecraftJars = minecraftProvider.getMinecraftJars();
if (minecraftJars.size() != 1) {
throw new UnsupportedOperationException("V1 mappings only support single jar minecraft providers");
}
// These are merged v1 mappings
Files.deleteIfExists(tinyMappings);
LOGGER.info(":populating field names");
suggestFieldNames(minecraftJars.get(0), baseTinyMappings, tinyMappings);
}
}
@@ -459,9 +460,9 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
}
}
private void suggestFieldNames(MergedMinecraftProvider minecraftProvider, Path oldMappings, Path newMappings) {
private void suggestFieldNames(Path inputJar, Path oldMappings, Path newMappings) {
Command command = new CommandProposeFieldNames();
runCommand(command, minecraftProvider.getMergedJar().toFile().getAbsolutePath(),
runCommand(command, inputJar.toFile().getAbsolutePath(),
oldMappings.toAbsolutePath().toString(),
newMappings.toAbsolutePath().toString());
}

View File

@@ -39,6 +39,7 @@ import java.util.stream.Stream;
import com.google.common.collect.Sets;
import net.fabricmc.loom.configuration.mods.JarSplitter;
import net.fabricmc.loom.util.FileSystemUtil;
public class MinecraftJarSplitter implements AutoCloseable {
@@ -65,8 +66,8 @@ public class MinecraftJarSplitter implements AutoCloseable {
// 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);
copyEntriesToJar(entryData.commonEntries, serverInputJar, commonOutputJar, "common");
copyEntriesToJar(entryData.clientOnlyEntries, clientInputJar, clientOnlyOutputJar, "client");
}
public void sharedEntry(String path) {
@@ -104,7 +105,7 @@ public class MinecraftJarSplitter implements AutoCloseable {
return entries;
}
private void copyEntriesToJar(Set<String> entries, Path inputJar, Path outputJar) throws IOException {
private void copyEntriesToJar(Set<String> entries, Path inputJar, Path outputJar, String env) throws IOException {
Files.deleteIfExists(outputJar);
try (FileSystemUtil.Delegate inputFs = FileSystemUtil.getJarFileSystem(inputJar);
@@ -124,13 +125,14 @@ public class MinecraftJarSplitter implements AutoCloseable {
Files.copy(inputPath, outputPath, StandardCopyOption.COPY_ATTRIBUTES);
}
writeManifest(outputFs);
writeManifest(outputFs, env);
}
}
private void writeManifest(FileSystemUtil.Delegate outputFs) throws IOException {
private void writeManifest(FileSystemUtil.Delegate outputFs, String env) throws IOException {
final Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
manifest.getMainAttributes().putValue(JarSplitter.MANIFEST_SPLIT_ENV_NAME_KEY, env);
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
Files.createDirectories(outputFs.get().getPath("META-INF"));

View File

@@ -89,7 +89,7 @@ public class MinecraftLibraryProvider {
private void provideClientLibraries(boolean overrideLWJGL, boolean hasNativesToExtract) {
final boolean isArm = Architecture.CURRENT.isArm();
final boolean classpathArmNatives = !hasNativesToExtract && isArm;
final boolean classpathArmNatives = !hasNativesToExtract && isArm && !IS_MACOS;
if (classpathArmNatives) {
LoomRepositoryPlugin.forceLWJGLFromMavenCentral(project);

View File

@@ -225,7 +225,7 @@ public abstract class MinecraftProvider {
throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(getServerBundleMetadata().versions().size()));
}
getServerBundleMetadata().versions().get(0).unpackEntry(minecraftServerJar.toPath(), getMinecraftExtractedServerJar().toPath());
getServerBundleMetadata().versions().get(0).unpackEntry(minecraftServerJar.toPath(), getMinecraftExtractedServerJar().toPath(), project);
}
public File workingDir() {

View File

@@ -24,6 +24,7 @@
package net.fabricmc.loom.extension;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -45,6 +46,7 @@ import org.gradle.api.provider.Provider;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI;
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
@@ -65,6 +67,7 @@ import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuil
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.util.DeprecationHelper;
import net.fabricmc.loom.util.ModPlatform;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
@@ -305,6 +308,25 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
intermediateMappingsProvider.set(provider);
}
@Override
public File getMappingsFile() {
return LoomGradleExtension.get(getProject()).getMappingsProvider().tinyMappings.toFile();
}
@Override
public GenerateSourcesTask getDecompileTask(DecompilerOptions options, boolean client) {
final String decompilerName = options.getFormattedName();
final String taskName;
if (areEnvironmentSourceSetsSplit()) {
taskName = "gen%sSourcesWith%s".formatted(client ? "ClientOnly" : "Common", decompilerName);
} else {
taskName = "genSourcesWith" + decompilerName;
}
return (GenerateSourcesTask) getProject().getTasks().getByName(taskName);
}
protected abstract <T extends IntermediateMappingsProvider> void configureIntermediateMappingsProviderInternal(T provider);
@Override

View File

@@ -75,6 +75,8 @@ public abstract class DownloadAssetsTask extends AbstractLoomTask {
getMinecraftVersion().set(versionInfo.id());
getMinecraftVersion().finalizeValue();
getDownloadThreads().convention(Runtime.getRuntime().availableProcessors());
getMinecraftVersion().set(versionInfo.id());
getMinecraftVersion().finalizeValue();
if (versionInfo.assets().equals("legacy")) {
getLegacyResourcesDirectory().set(new File(assetsDir, "/legacy/" + versionInfo.id()));

View File

@@ -51,7 +51,9 @@ import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.work.DisableCachingByDefault;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
@@ -81,6 +83,7 @@ import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
@DisableCachingByDefault
public abstract class GenerateSourcesTask extends AbstractLoomTask {
private final DecompilerOptions decompilerOptions;
@@ -99,6 +102,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
@InputFiles
public abstract ConfigurableFileCollection getClasspath();
@OutputFile
public abstract RegularFileProperty getOutputJar();
@Inject
public abstract WorkerExecutor getWorkerExecutor();
@@ -112,6 +118,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
getOutputs().upToDateWhen((o) -> false);
getClasspath().from(decompilerOptions.getClasspath()).finalizeValueOnRead();
dependsOn(decompilerOptions.getClasspath().getBuiltBy());
getOutputJar().fileProvider(getProject().provider(() -> getMappedJarFileWithSuffix("-sources.jar")));
}
@TaskAction
@@ -150,7 +158,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
params.getInputJar().set(getInputJar());
params.getRuntimeJar().set(getRuntimeJar());
params.getSourcesDestinationJar().set(getMappedJarFileWithSuffix("-sources.jar"));
params.getSourcesDestinationJar().set(getOutputJar());
params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap"));
params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar"));
params.getMappings().set(getMappings().toFile());

View File

@@ -36,7 +36,7 @@ import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.jvm.tasks.Jar;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.Constants;
@@ -111,10 +111,10 @@ 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);
final Jar jarTask = (Jar) project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME);
configuration.getArtifacts().removeIf(artifact -> {
// if the artifact is a -dev jar and "builtBy jar"
return "dev".equals(artifact.getClassifier()) && artifact.getBuildDependencies().getDependencies(null).contains(jarTask);
// if the artifact is built by the jar task, and has the same output path.
return artifact.getFile().getAbsolutePath().equals(jarTask.getArchiveFile().get().getAsFile().getAbsolutePath()) && artifact.getBuildDependencies().getDependencies(null).contains(jarTask);
});
}
});

View File

@@ -49,6 +49,7 @@ import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
@@ -115,7 +116,7 @@ public abstract class ValidateMixinNameTask extends SourceTask {
}
final String mixinClassName = toSimpleName(mixin.className);
final String expectedMixinClassName = toSimpleName(mixin.target.getInternalName()).replace("$", "") + (mixin.accessor ? "Accessor" : "Mixin");
final String expectedMixinClassName = mixin.expectedClassName();
if (expectedMixinClassName.startsWith("class_")) {
// Don't enforce intermediary named mixins.
@@ -140,36 +141,48 @@ public abstract class ValidateMixinNameTask extends SourceTask {
throw new GradleException("Mixin name validation failed: " + errors.stream().collect(Collectors.joining(System.lineSeparator())));
}
}
private static String toSimpleName(String internalName) {
return internalName.substring(internalName.lastIndexOf("/") + 1);
}
private static String toSimpleName(String internalName) {
return internalName.substring(internalName.lastIndexOf("/") + 1);
}
@Nullable
private Mixin getMixin(File file) {
try (InputStream is = new FileInputStream(file)) {
ClassReader reader = new ClassReader(is);
var classVisitor = new MixinTargetClassVisitor();
reader.accept(classVisitor, ClassReader.SKIP_CODE);
if (classVisitor.mixinTarget != null) {
return new Mixin(classVisitor.className, classVisitor.mixinTarget, classVisitor.accessor);
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to read input file: " + file, e);
}
return null;
@VisibleForTesting
public record Mixin(String className, Type target, boolean accessor) {
public String expectedClassName() {
return toSimpleName(target.getInternalName()).replace("$", "") + (accessor ? "Accessor" : "Mixin");
}
}
private record Mixin(String className, Type target, boolean accessor) { }
@Nullable
private static Mixin getMixin(File file) {
try (InputStream is = new FileInputStream(file)) {
return getMixin(is);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read input file: " + file, e);
}
}
@Nullable
@VisibleForTesting
public static Mixin getMixin(InputStream is) throws IOException {
final ClassReader reader = new ClassReader(is);
var classVisitor = new MixinTargetClassVisitor();
reader.accept(classVisitor, ClassReader.SKIP_CODE);
if (classVisitor.mixinTarget != null && classVisitor.targets == 1) {
return new Mixin(classVisitor.className, classVisitor.mixinTarget, classVisitor.accessor);
}
return null;
}
private static class MixinTargetClassVisitor extends ClassVisitor {
Type mixinTarget;
String className;
boolean accessor;
int targets = 0;
boolean isInterface;
@@ -220,6 +233,7 @@ public abstract class ValidateMixinNameTask extends SourceTask {
@Override
public void visit(String name, Object value) {
mixinTarget = Objects.requireNonNull((Type) value);
targets++;
super.visit(name, value);
}

View File

@@ -27,6 +27,8 @@ package net.fabricmc.loom.util;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.UserDefinedFileAttributeView;
@@ -37,25 +39,55 @@ public final class AttributeHelper {
}
public static Optional<String> readAttribute(Path path, String key) throws IOException {
final UserDefinedFileAttributeView attributeView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
final Path attributesFile = getFallbackPath(path, key);
if (!attributeView.list().contains(key)) {
return Optional.empty();
if (exists(attributesFile)) {
// Use the fallback file if it exists.
return Optional.of(Files.readString(attributesFile, StandardCharsets.UTF_8));
}
final ByteBuffer buffer = ByteBuffer.allocate(attributeView.size(key));
attributeView.read(key, buffer);
buffer.flip();
final String value = StandardCharsets.UTF_8.decode(buffer).toString();
return Optional.of(value);
try {
final UserDefinedFileAttributeView attributeView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
if (!attributeView.list().contains(key)) {
return Optional.empty();
}
final ByteBuffer buffer = ByteBuffer.allocate(attributeView.size(key));
attributeView.read(key, buffer);
buffer.flip();
final String value = StandardCharsets.UTF_8.decode(buffer).toString();
return Optional.of(value);
} catch (FileSystemException ignored) {
return Optional.empty();
}
}
public static void writeAttribute(Path path, String key, String value) throws IOException {
// TODO may need to fallback to creating a separate file if this isnt supported.
final UserDefinedFileAttributeView attributeView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
final int written = attributeView.write(key, buffer);
assert written == bytes.length;
final Path attributesFile = getFallbackPath(path, key);
try {
final UserDefinedFileAttributeView attributeView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
final int written = attributeView.write(key, buffer);
assert written == bytes.length;
if (exists(attributesFile)) {
Files.delete(attributesFile);
}
} catch (FileSystemException ignored) {
// Fallback to a separate file when using a file system that does not attributes.
Files.writeString(attributesFile, value, StandardCharsets.UTF_8);
}
}
private static Path getFallbackPath(Path path, String key) {
return path.resolveSibling(path.getFileName() + "." + key + ".att");
}
// A faster exists check
private static boolean exists(Path path) {
return path.getFileSystem() == FileSystems.getDefault() ? path.toFile().exists() : Files.exists(path);
}
}

View File

@@ -70,7 +70,8 @@ public class SourceRemapper {
public static void remapSources(Project project, File input, File output, String from, String to, boolean reproducibleFileOrder, boolean preserveFileTimestamps) {
SourceRemapper sourceRemapper = new SourceRemapper(project, from, to);
sourceRemapper.scheduleRemapSources(input, output, reproducibleFileOrder, preserveFileTimestamps, () -> {});
sourceRemapper.scheduleRemapSources(input, output, reproducibleFileOrder, preserveFileTimestamps, () -> {
});
sourceRemapper.remapAll();
}

View File

@@ -250,6 +250,7 @@ public class Download {
if (isHashValid(output)) {
// Valid hash, no need to re-download
writeHash(output, expectedHash);
return false;
}
@@ -322,9 +323,9 @@ public class Download {
}
}
private void writeHash(Path output, String eTag) throws DownloadException {
private void writeHash(Path output, String value) throws DownloadException {
try {
AttributeHelper.writeAttribute(output, "LoomHash", eTag);
AttributeHelper.writeAttribute(output, "LoomHash", value);
} catch (IOException e) {
throw error(e, "Failed to write hash to (%s)", output);
}
@@ -354,7 +355,7 @@ public class Download {
private boolean getAndResetLock(Path output) throws DownloadException {
final Path lock = getLockFile(output);
final boolean exists = Files.exists(lock);
final boolean exists = exists(lock);
if (exists) {
try {

View File

@@ -33,16 +33,26 @@ import org.gradle.internal.logging.progress.ProgressLogger;
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
public class ProgressGroup implements Closeable {
private final String name;
private final ProgressLoggerFactory progressLoggerFactory;
private final ProgressLogger progressGroup;
private ProgressLogger progressGroup;
public ProgressGroup(Project project, String name) {
this.name = name;
this.progressLoggerFactory = ((ProjectInternal) project).getServices().get(ProgressLoggerFactory.class);
}
private void start() {
this.progressGroup = this.progressLoggerFactory.newOperation(name).setDescription(name);
this.progressGroup.started();
}
public ProgressLogger createProgressLogger(String name) {
if (progressGroup == null) {
start();
}
ProgressLogger progressLogger = this.progressLoggerFactory.newOperation(getClass(), progressGroup);
progressLogger.setDescription(name);
progressLogger.start(name, null);
@@ -51,6 +61,9 @@ public class ProgressGroup implements Closeable {
@Override
public void close() throws IOException {
this.progressGroup.completed();
if (this.progressGroup != null) {
this.progressGroup.completed();
this.progressGroup = null;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit
import net.fabricmc.loom.api.RemapConfigurationSettings
import net.fabricmc.loom.configuration.RemapConfigurations
import net.fabricmc.loom.test.util.GradleTestUtil
import org.gradle.api.tasks.SourceSet
import spock.lang.Specification
class RemapConfigurationsTest extends Specification {
private static final RemapConfigurations.ConfigurationOption IMPLEMENTATION_OPTION = new RemapConfigurations.ConfigurationOption(SourceSet::getImplementationConfigurationName, true, true, RemapConfigurationSettings.PublishingMode.RUNTIME_ONLY)
def "testmod impl name"() {
given:
def sourceSet = GradleTestUtil.mockSourceSet("testmod")
when:
def name = IMPLEMENTATION_OPTION.name(sourceSet)
then:
name == "modTestmodImplementation"
}
def "main impl name"() {
given:
def sourceSet = GradleTestUtil.mockSourceSet("main")
when:
def name = IMPLEMENTATION_OPTION.name(sourceSet)
then:
name == "modImplementation"
}
}

View File

@@ -0,0 +1,102 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit
import net.fabricmc.loom.task.ValidateMixinNameTask
import org.spongepowered.asm.mixin.Mixin
import org.spongepowered.asm.mixin.gen.Accessor
import spock.lang.Specification
class ValidateMixinNameTest extends Specification {
def "TestMixin"() {
when:
def mixin = getMixin(TestMixin.class)
then:
mixin.className() == "net/fabricmc/loom/test/unit/TestMixin"
mixin.target().internalName == "net/fabricmc/loom/test/unit/Test"
mixin.expectedClassName() == "TestMixin"
!mixin.accessor()
}
def "TestInnerMixin"() {
when:
def mixin = getMixin(TestInnerMixin.class)
then:
mixin.className() == "net/fabricmc/loom/test/unit/TestInnerMixin"
mixin.target().internalName == "net/fabricmc/loom/test/unit/Test\$Inner"
mixin.expectedClassName() == "TestInnerMixin"
!mixin.accessor()
}
def "TestAccessor"() {
when:
def mixin = getMixin(TestAccessor.class)
then:
mixin.className() == "net/fabricmc/loom/test/unit/TestAccessor"
mixin.target().internalName == "net/fabricmc/loom/test/unit/Test"
mixin.expectedClassName() == "TestAccessor"
mixin.accessor()
}
def "TestManyTargetsMixin"() {
when:
def mixin = getMixin(TestManyTargetsMixin.class)
then:
mixin == null
}
static ValidateMixinNameTask.Mixin getMixin(Class<?> clazz) {
return getInput(clazz).withCloseable {
return ValidateMixinNameTask.getMixin(it)
}
}
static InputStream getInput(Class<?> clazz) {
return clazz.classLoader.getResourceAsStream(clazz.name.replace('.', '/') + ".class")
}
}
@Mixin(Test.class)
class TestMixin {
}
@Mixin(Test.Inner.class)
class TestInnerMixin {
}
@Mixin(Test.class)
interface TestAccessor {
@Accessor
Object getNothing();
}
@Mixin([Test.class, Test.Inner.class])
class TestManyTargetsMixin {
}
class Test {
class Inner {
}
}

View File

@@ -24,8 +24,15 @@
package net.fabricmc.loom.test.util
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.tasks.DefaultSourceSet
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.api.provider.Property
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.util.PatternFilterable
import static org.mockito.ArgumentMatchers.any
import static org.mockito.Mockito.mock
import static org.mockito.Mockito.when
@@ -35,4 +42,30 @@ class GradleTestUtil {
when(mock.get()).thenReturn(Objects.requireNonNull(value))
return mock
}
static SourceSet mockSourceSet(String name) {
def sourceSet = new DefaultSourceSet(name, mockObjectFactory()) {
final ExtensionContainer extensions = null
}
return sourceSet
}
static ObjectFactory mockObjectFactory() {
def mock = mock(ObjectFactory.class)
def mockSourceDirectorySet = mockSourceDirectorySet()
when(mock.sourceDirectorySet(any(), any())).thenReturn(mockSourceDirectorySet)
return mock
}
static SourceDirectorySet mockSourceDirectorySet() {
def mock = mock(SourceDirectorySet.class)
def mockPatternFilterable = mockPatternFilterable()
when(mock.getFilter()).thenReturn(mockPatternFilterable)
return mock
}
static PatternFilterable mockPatternFilterable() {
def mock = mock(PatternFilterable.class)
return mock
}
}