diff --git a/build.gradle b/build.gradle index 16a61d12..3480c7fa 100644 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index f5241ae5..4ae7af98 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -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. * diff --git a/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java index 9cf58d6e..931ab005 100644 --- a/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java +++ b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java @@ -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 options, int maxThreads) implements Serializable { } diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileMappingsSpecBuilder.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileMappingsSpecBuilder.java index 1504725c..8028881a 100644 --- a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileMappingsSpecBuilder.java +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileMappingsSpecBuilder.java @@ -44,7 +44,7 @@ public interface FileMappingsSpecBuilder { *

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); diff --git a/src/main/java/net/fabricmc/loom/configuration/RemapConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/RemapConfigurations.java index 7a556937..47b3ea2e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/RemapConfigurations.java +++ b/src/main/java/net/fabricmc/loom/configuration/RemapConfigurations.java @@ -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 targetNameFunc, boolean compileClasspath, boolean runtimeClasspath, RemapConfigurationSettings.PublishingMode publishingMode) { + @VisibleForTesting + public record ConfigurationOption(Function 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(); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java index ccaf627d..782da709 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java @@ -59,7 +59,7 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration { - 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 -> { diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java index 60cd7932..deb2982d 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java @@ -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 { + 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 createDecompileTasks(String name, Action 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 -> { diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java index 1ff85372..3771b617 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -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; diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/JarSplitter.java b/src/main/java/net/fabricmc/loom/configuration/mods/JarSplitter.java index 57348e0a..6090d0ec 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/JarSplitter.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/JarSplitter.java @@ -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 readClientEntries(Manifest manifest) { final Attributes attributes = manifest.getMainAttributes(); final String clientEntriesValue = attributes.getValue(AbstractRemapJarTask.MANIFEST_CLIENT_ENTRIES_KEY); diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index 6d65386a..95060572 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -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 -> { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/BundleMetadata.java b/src/main/java/net/fabricmc/loom/configuration/providers/BundleMetadata.java index aa6b9379..4705fca4 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/BundleMetadata.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/BundleMetadata.java @@ -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 libraries, List versions, String mainClass) { @@ -83,10 +87,38 @@ public record BundleMetadata(List libraries, List 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 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); } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index 2f959032..cba3cbb5 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -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 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()); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java index 87d7a800..7e11eba6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java @@ -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 entries, Path inputJar, Path outputJar) throws IOException { + private void copyEntriesToJar(Set 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")); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java index f79674da..588b20f7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java @@ -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); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java index 1b5b61db..4ba92f95 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java @@ -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() { diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index e7f53571..40722856 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -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 void configureIntermediateMappingsProviderInternal(T provider); @Override diff --git a/src/main/java/net/fabricmc/loom/task/DownloadAssetsTask.java b/src/main/java/net/fabricmc/loom/task/DownloadAssetsTask.java index da16ca3c..661636ed 100644 --- a/src/main/java/net/fabricmc/loom/task/DownloadAssetsTask.java +++ b/src/main/java/net/fabricmc/loom/task/DownloadAssetsTask.java @@ -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())); diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 4be42a90..50bd7a0a 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -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()); diff --git a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java index 7541f297..b175d3d3 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java +++ b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java @@ -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); }); } }); diff --git a/src/main/java/net/fabricmc/loom/task/ValidateMixinNameTask.java b/src/main/java/net/fabricmc/loom/task/ValidateMixinNameTask.java index e96f4d58..b37e0028 100644 --- a/src/main/java/net/fabricmc/loom/task/ValidateMixinNameTask.java +++ b/src/main/java/net/fabricmc/loom/task/ValidateMixinNameTask.java @@ -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); } diff --git a/src/main/java/net/fabricmc/loom/util/AttributeHelper.java b/src/main/java/net/fabricmc/loom/util/AttributeHelper.java index 587927bb..dc5d8f29 100644 --- a/src/main/java/net/fabricmc/loom/util/AttributeHelper.java +++ b/src/main/java/net/fabricmc/loom/util/AttributeHelper.java @@ -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 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); } } diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java index d8864b56..d3d56479 100644 --- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java @@ -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(); } diff --git a/src/main/java/net/fabricmc/loom/util/download/Download.java b/src/main/java/net/fabricmc/loom/util/download/Download.java index cd756118..12742ce6 100644 --- a/src/main/java/net/fabricmc/loom/util/download/Download.java +++ b/src/main/java/net/fabricmc/loom/util/download/Download.java @@ -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 { diff --git a/src/main/java/net/fabricmc/loom/util/gradle/ProgressGroup.java b/src/main/java/net/fabricmc/loom/util/gradle/ProgressGroup.java index 8248f9bd..02126565 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/ProgressGroup.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/ProgressGroup.java @@ -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; + } } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/RemapConfigurationsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/RemapConfigurationsTest.groovy new file mode 100644 index 00000000..acae4206 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/RemapConfigurationsTest.groovy @@ -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" + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ValidateMixinNameTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ValidateMixinNameTest.groovy new file mode 100644 index 00000000..0fa5c2b1 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ValidateMixinNameTest.groovy @@ -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 { + } +} \ No newline at end of file diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy index e19660c1..479e5256 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy @@ -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 + } }