diff --git a/build.gradle b/build.gradle index 2e888cb2..5928dc08 100644 --- a/build.gradle +++ b/build.gradle @@ -74,7 +74,6 @@ dependencies { // libraries implementation ('commons-io:commons-io:2.8.0') - implementation ('org.zeroturnaround:zt-zip:1.14') implementation ('com.google.code.gson:gson:2.8.8') implementation ('com.fasterxml.jackson.core:jackson-databind:2.12.5') implementation ('com.google.guava:guava:30.1.1-jre') @@ -93,7 +92,7 @@ dependencies { // tinyfile management implementation ('dev.architectury:tiny-remapper:1.4.12') - implementation 'net.fabricmc:access-widener:2.0.0' + implementation 'net.fabricmc:access-widener:2.0.1' implementation 'net.fabricmc:mapping-io:0.2.1' implementation ('net.fabricmc:lorenz-tiny:4.0.2') { diff --git a/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java b/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java index a650d697..580d5659 100644 --- a/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java +++ b/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java @@ -27,6 +27,7 @@ package net.fabricmc.loom.build; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; @@ -42,13 +43,12 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import org.gradle.api.Project; import org.jetbrains.annotations.NotNull; -import org.zeroturnaround.zip.ZipUtil; -import org.zeroturnaround.zip.transform.StringZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.extension.MixinExtension; +import net.fabricmc.loom.util.Pair; +import net.fabricmc.loom.util.ZipUtils; public final class MixinRefmapHelper { private MixinRefmapHelper() { } @@ -76,18 +76,17 @@ public final class MixinRefmapHelper { String refmapName = container.refmapNameProvider().get(); - return ZipUtil.transformEntries(output, mixinConfigs.map(f -> new ZipEntryTransformerEntry(f, new StringZipEntryTransformer("UTF-8") { - @Override - protected String transform(ZipEntry zipEntry, String input) { - JsonObject json = LoomGradlePlugin.GSON.fromJson(input, JsonObject.class); - + try { + return ZipUtils.transformJson(JsonObject.class, outputPath, mixinConfigs.map(f -> new Pair<>(f, json -> { if (!json.has("refmap")) { json.addProperty("refmap", refmapName); } - return LoomGradlePlugin.GSON.toJson(json); - } - })).toArray(ZipEntryTransformerEntry[]::new)); + return json; + }))) > 0; + } catch (IOException e) { + throw new UncheckedIOException("Failed to transform mixin configs in jar", e); + } }).reduce(false, Boolean::logicalOr); } catch (Exception e) { project.getLogger().error(e.getMessage()); diff --git a/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java b/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java index 662fc2cf..efae834d 100644 --- a/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java +++ b/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java @@ -25,22 +25,22 @@ package net.fabricmc.loom.build.nesting; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.Collection; -import java.util.zip.ZipEntry; +import java.util.stream.Collectors; +import java.util.stream.Stream; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import org.gradle.api.UncheckedIOException; import org.gradle.api.logging.Logger; -import org.zeroturnaround.zip.FileSource; -import org.zeroturnaround.zip.ZipEntrySource; -import org.zeroturnaround.zip.ZipUtil; -import org.zeroturnaround.zip.transform.StringZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; -import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.util.ModUtils; +import net.fabricmc.loom.util.Pair; +import net.fabricmc.loom.util.ZipUtils; public class JarNester { public static void nestJars(Collection jars, File modJar, Logger logger) { @@ -51,12 +51,16 @@ public class JarNester { Preconditions.checkArgument(ModUtils.isMod(modJar), "Cannot nest jars into none mod jar " + modJar.getName()); - ZipUtil.addEntries(modJar, jars.stream().map(file -> new FileSource("META-INF/jars/" + file.getName(), file)).toArray(ZipEntrySource[]::new)); + try { + ZipUtils.add(modJar.toPath(), jars.stream().map(file -> { + try { + return new Pair<>("META-INF/jars/" + file.getName(), Files.readAllBytes(file.toPath())); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }).collect(Collectors.toList())); - boolean didNest = ZipUtil.transformEntries(modJar, single(new ZipEntryTransformerEntry("fabric.mod.json", new StringZipEntryTransformer() { - @Override - protected String transform(ZipEntry zipEntry, String input) { - JsonObject json = LoomGradlePlugin.GSON.fromJson(input, JsonObject.class); + int count = ZipUtils.transformJson(JsonObject.class, modJar.toPath(), Stream.of(new Pair<>("fabric.mod.json", json -> { JsonArray nestedJars = json.getAsJsonArray("jars"); if (nestedJars == null || !json.has("jars")) { @@ -83,13 +87,12 @@ public class JarNester { json.add("jars", nestedJars); - return LoomGradlePlugin.GSON.toJson(json); - } - }))); - Preconditions.checkArgument(didNest, "Failed to nest jars into " + modJar.getName()); - } + return json; + }))); - private static ZipEntryTransformerEntry[] single(ZipEntryTransformerEntry element) { - return new ZipEntryTransformerEntry[]{element}; + Preconditions.checkState(count > 0, "Failed to transform fabric.mod.json"); + } catch (IOException e) { + throw new java.io.UncheckedIOException("Failed to nest jars into " + modJar.getName(), e); + } } } diff --git a/src/main/java/net/fabricmc/loom/build/nesting/NestedDependencyProvider.java b/src/main/java/net/fabricmc/loom/build/nesting/NestedDependencyProvider.java index b211ad7d..18dc816b 100644 --- a/src/main/java/net/fabricmc/loom/build/nesting/NestedDependencyProvider.java +++ b/src/main/java/net/fabricmc/loom/build/nesting/NestedDependencyProvider.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.build.nesting; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -47,12 +48,12 @@ import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedConfiguration; import org.gradle.api.artifacts.ResolvedDependency; import org.gradle.api.tasks.bundling.AbstractArchiveTask; -import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.task.RemapJarTask; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ZipUtils; public final class NestedDependencyProvider implements NestedJarProvider { final Project project; @@ -157,7 +158,7 @@ public final class NestedDependencyProvider implements NestedJarProvider { File file = metaFile.file; //A lib that doesnt have a mod.json, we turn it into a fake mod - if (!ZipUtil.containsEntry(file, "fabric.mod.json")) { + if (!ZipUtils.contains(file.toPath(), "fabric.mod.json")) { LoomGradleExtension extension = LoomGradleExtension.get(project); File tempDir = new File(extension.getFiles().getUserCache(), "temp/modprocessing"); @@ -177,7 +178,12 @@ public final class NestedDependencyProvider implements NestedJarProvider { throw new RuntimeException("Failed to copy file", e); } - ZipUtil.addEntry(tempFile, "fabric.mod.json", generateModForDependency(metaFile).getBytes()); + try { + ZipUtils.add(tempFile.toPath(), "fabric.mod.json", generateModForDependency(metaFile).getBytes()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to add dummy mod while including %s".formatted(file), e); + } + fileList.add(tempFile); } else { // Default copy the jar right in diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index 6852945b..42cccfe3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -145,6 +145,7 @@ public final class CompileConfiguration { extendsFrom(Constants.Configurations.MINECRAFT_NAMED, Constants.Configurations.LOADER_DEPENDENCIES, project); extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MAPPINGS_FINAL, project); + extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MAPPINGS_FINAL, project); 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); diff --git a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java index 4d4ae868..c50102d7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java @@ -25,6 +25,8 @@ 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; @@ -45,12 +47,12 @@ 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 org.zeroturnaround.zip.ZipUtil; 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; @@ -239,26 +241,31 @@ public abstract class DependencyProvider { } 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; - if ("jar".equals(FilenameUtils.getExtension(root.getName())) && ZipUtil.containsEntry(root, "fabric.mod.json")) { - //It's a Fabric mod, see how much we can extract out - JsonObject json = new Gson().fromJson(new String(ZipUtil.unpackEntry(root, "fabric.mod.json"), StandardCharsets.UTF_8), JsonObject.class); + 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 == 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(); + 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 { - name = json.get("id").getAsString(); + //Not a Fabric mod, just have to make something up + name = FilenameUtils.removeExtension(root.getName()); + version = "1.0"; } - - 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); } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java index 680aae92..a8648611 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java @@ -24,12 +24,15 @@ package net.fabricmc.loom.configuration.accesswidener; +import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import com.google.gson.Gson; import com.google.gson.JsonObject; -import org.zeroturnaround.zip.ZipUtil; + +import net.fabricmc.loom.util.ZipUtils; public record AccessWidenerFile( String name, @@ -40,7 +43,13 @@ public record AccessWidenerFile( * Reads the access-widener contained in a mod jar, or returns null if there is none. */ public static AccessWidenerFile fromModJar(Path modJarPath) { - byte[] modJsonBytes = ZipUtil.unpackEntry(modJarPath.toFile(), "fabric.mod.json"); + byte[] modJsonBytes; + + try { + modJsonBytes = ZipUtils.unpackNullable(modJarPath, "fabric.mod.json"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read access-widener file from: " + modJarPath.toAbsolutePath(), e); + } if (modJsonBytes == null) { return null; @@ -55,7 +64,13 @@ public record AccessWidenerFile( String awPath = jsonObject.get("accessWidener").getAsString(); String modId = jsonObject.get("id").getAsString(); - byte[] content = ZipUtil.unpackEntry(modJarPath.toFile(), awPath); + byte[] content; + + try { + content = ZipUtils.unpack(modJarPath, awPath); + } catch (IOException e) { + throw new UncheckedIOException("Could not find access widener file (%s) defined in the fabric.mod.json file of %s".formatted(awPath, modJarPath.toAbsolutePath()), e); + } return new AccessWidenerFile( awPath, diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java index 0970717c..3baeb087 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.configuration.accesswidener; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -34,7 +35,6 @@ import java.util.Arrays; import com.google.common.hash.Hashing; import org.gradle.api.Project; import org.objectweb.asm.commons.Remapper; -import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.accesswidener.AccessWidener; import net.fabricmc.accesswidener.AccessWidenerReader; @@ -43,6 +43,7 @@ import net.fabricmc.accesswidener.AccessWidenerWriter; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.util.ZipUtils; public class AccessWidenerJarProcessor implements JarProcessor { // Filename used to store hash of input access widener in processed jar file @@ -87,7 +88,12 @@ public class AccessWidenerJarProcessor implements JarProcessor { public void process(File file) { AccessWidenerTransformer applier = new AccessWidenerTransformer(project.getLogger(), accessWidener); applier.apply(file); - ZipUtil.addEntry(file, HASH_FILENAME, inputHash); + + try { + ZipUtils.add(file.toPath(), HASH_FILENAME, inputHash); + } catch (IOException e) { + throw new UncheckedIOException("Failed to write aw jar hash", e); + } } /** @@ -111,7 +117,13 @@ public class AccessWidenerJarProcessor implements JarProcessor { @Override public boolean isInvalid(File file) { - byte[] hash = ZipUtil.unpackEntry(file, HASH_FILENAME); + byte[] hash; + + try { + hash = ZipUtils.unpackNullable(file.toPath(), HASH_FILENAME); + } catch (IOException e) { + return true; + } if (hash == null) { return true; diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerTransformer.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerTransformer.java index bdde7403..a0f3ad50 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerTransformer.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerTransformer.java @@ -25,21 +25,22 @@ package net.fabricmc.loom.configuration.accesswidener; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; import java.util.Set; -import java.util.zip.ZipEntry; +import java.util.stream.Collectors; import org.gradle.api.logging.Logger; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; -import org.zeroturnaround.zip.ZipUtil; -import org.zeroturnaround.zip.transform.ByteArrayZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; import net.fabricmc.accesswidener.AccessWidener; import net.fabricmc.accesswidener.AccessWidenerClassVisitor; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.Pair; +import net.fabricmc.loom.util.ZipUtils; final class AccessWidenerTransformer { private final Logger logger; @@ -55,28 +56,30 @@ final class AccessWidenerTransformer { */ void apply(File jarFile) { logger.lifecycle("Processing file: " + jarFile.getName()); - ZipUtil.transformEntries(jarFile, getTransformers(accessWidener.getTargets())); + + try { + ZipUtils.transform(jarFile.toPath(), getTransformers(accessWidener.getTargets())); + } catch (IOException e) { + throw new UncheckedIOException("Failed to apply access wideners to %s".formatted(jarFile), e); + } } - private ZipEntryTransformerEntry[] getTransformers(Set classes) { + private List>> getTransformers(Set classes) { return classes.stream() - .map(string -> new ZipEntryTransformerEntry(string.replaceAll("\\.", "/") + ".class", getTransformer(string))) - .toArray(ZipEntryTransformerEntry[]::new); + .map(string -> new Pair<>(string.replaceAll("\\.", "/") + ".class", getTransformer(string))) + .collect(Collectors.toList()); } - private ZipEntryTransformer getTransformer(String className) { - return new ByteArrayZipEntryTransformer() { - @Override - protected byte[] transform(ZipEntry zipEntry, byte[] input) { - ClassReader reader = new ClassReader(input); - ClassWriter writer = new ClassWriter(0); - ClassVisitor classVisitor = AccessWidenerClassVisitor.createClassVisitor(Constants.ASM_VERSION, writer, accessWidener); + private ZipUtils.UnsafeUnaryOperator getTransformer(String className) { + return input -> { + ClassReader reader = new ClassReader(input); + ClassWriter writer = new ClassWriter(0); + ClassVisitor classVisitor = AccessWidenerClassVisitor.createClassVisitor(Constants.ASM_VERSION, writer, accessWidener); - logger.info("Applying access widener to " + className); + logger.info("Applying access widener to " + className); - reader.accept(classVisitor, 0); - return writer.toByteArray(); - } + reader.accept(classVisitor, 0); + return writer.toByteArray(); }; } } diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java index 494c4169..9ab13264 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java @@ -26,13 +26,20 @@ package net.fabricmc.loom.configuration.accesswidener; 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.HashSet; import java.util.List; import java.util.Set; import com.google.common.base.Preconditions; import dev.architectury.tinyremapper.TinyRemapper; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.FileCollectionDependency; +import org.gradle.api.artifacts.ResolvedArtifact; +import org.gradle.api.file.FileCollection; import net.fabricmc.accesswidener.AccessWidener; import net.fabricmc.accesswidener.AccessWidenerReader; @@ -80,7 +87,8 @@ public class TransitiveAccessWidenerJarProcessor implements JarProcessor { } private List getTransitiveAccessWideners() { - List accessWideners = new ArrayList<>(); + final List accessWideners = new ArrayList<>(); + final Set possibleModJars = new HashSet<>(); for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) { // Only apply global AWs from mods that are part of the compile classpath @@ -88,24 +96,40 @@ public class TransitiveAccessWidenerJarProcessor implements JarProcessor { continue; } - Set artifacts = extension.getLazyConfigurationProvider(entry.sourceConfiguration()) - .get() - .resolve(); + final Configuration configuration = extension.getLazyConfigurationProvider(entry.sourceConfiguration()).get(); - for (File artifact : artifacts) { - AccessWidenerFile accessWidener = AccessWidenerFile.fromModJar(artifact.toPath()); - - if (accessWidener == null) { - continue; - } - - if (!TransitiveDetectorVisitor.isTransitive(accessWidener.content())) { - // AW does not contain anything transitive, skip over it - continue; - } - - accessWideners.add(accessWidener); + // Based off the logic in ModCompileRemapper. + for (ResolvedArtifact artifact : configuration.getResolvedConfiguration().getResolvedArtifacts()) { + possibleModJars.add(artifact.getFile().toPath()); } + + for (FileCollectionDependency dependency : configuration.getAllDependencies().withType(FileCollectionDependency.class)) { + FileCollection files = dependency.getFiles(); + + for (File artifact : files) { + possibleModJars.add(artifact.toPath()); + } + } + } + + for (Path path : possibleModJars) { + if (!Files.exists(path)) { + project.getLogger().debug("Could not find transitive access widener in {} as it does not exist", path.toAbsolutePath()); + continue; + } + + AccessWidenerFile accessWidener = AccessWidenerFile.fromModJar(path); + + if (accessWidener == null) { + continue; + } + + if (!TransitiveDetectorVisitor.isTransitive(accessWidener.content())) { + // AW does not contain anything transitive, skip over it + continue; + } + + accessWideners.add(accessWidener); } return accessWideners; 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 1142357a..0d180e6b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -35,7 +35,6 @@ import java.util.Map; import java.util.Objects; import java.util.function.Consumer; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -49,7 +48,6 @@ import org.w3c.dom.Node; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.InstallerData; -import net.fabricmc.loom.util.OperatingSystem; public class RunConfig { public String configName; @@ -59,8 +57,8 @@ public class RunConfig { public String mainClass; public String runDirIdeaUrl; public String runDir; - public String vmArgs; - public String programArgs; + public List vmArgs = new ArrayList<>(); + public List programArgs = new ArrayList<>(); public List vscodeBeforeRun = new ArrayList<>(); public final Map envVariables = new HashMap<>(); public SourceSet sourceSet; @@ -73,12 +71,12 @@ public class RunConfig { this.addXml(root, "option", ImmutableMap.of("name", "MAIN_CLASS_NAME", "value", mainClass)); this.addXml(root, "option", ImmutableMap.of("name", "WORKING_DIRECTORY", "value", runDirIdeaUrl)); - if (!Strings.isNullOrEmpty(vmArgs)) { - this.addXml(root, "option", ImmutableMap.of("name", "VM_PARAMETERS", "value", vmArgs)); + if (!vmArgs.isEmpty()) { + this.addXml(root, "option", ImmutableMap.of("name", "VM_PARAMETERS", "value", joinArguments(vmArgs))); } - if (!Strings.isNullOrEmpty(programArgs)) { - this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", programArgs)); + if (!programArgs.isEmpty()) { + this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", joinArguments(programArgs))); } if (!envVariables.isEmpty()) { @@ -123,11 +121,10 @@ public class RunConfig { runConfig.configName += extension.isRootProject() ? "" : " (" + project.getPath() + ")"; runConfig.eclipseProjectName = project.getExtensions().getByType(EclipseModel.class).getProject().getName(); runConfig.vscodeProjectName = extension.isRootProject() ? "" : StringUtils.removePrefix(project.getPath(), ":"); - runConfig.vmArgs = ""; - runConfig.programArgs = ""; runConfig.mainClass = "net.fabricmc.devlaunchinjector.Main"; - runConfig.vmArgs = "-Dfabric.dli.config=" + encodeEscaped(extension.getFiles().getDevLauncherConfig().getAbsolutePath()) + " -Dfabric.dli.env=" + environment.toLowerCase(); + runConfig.vmArgs.add("-Dfabric.dli.config=" + encodeEscaped(extension.getFiles().getDevLauncherConfig().getAbsolutePath())); + runConfig.vmArgs.add("-Dfabric.dli.env=" + environment.toLowerCase()); } // Turns camelCase/PascalCase into Capital Case @@ -184,19 +181,9 @@ public class RunConfig { runConfig.sourceSet = sourceSet; // Custom parameters - for (String progArg : settings.getProgramArgs()) { - runConfig.programArgs += " " + progArg; - } - - for (String vmArg : settings.getVmArgs()) { - runConfig.vmArgs += " " + vmArg; - } - - runConfig.vmArgs += " -Dfabric.dli.main=" + getMainClass(environment, extension, defaultMain); - - // Remove unnecessary leading/trailing whitespaces we might have generated - runConfig.programArgs = runConfig.programArgs.trim(); - runConfig.vmArgs = runConfig.vmArgs.trim(); + runConfig.programArgs.addAll(settings.getProgramArgs()); + runConfig.vmArgs.addAll(settings.getVmArgs()); + runConfig.vmArgs.add("-Dfabric.dli.main=" + getMainClass(environment, extension, defaultMain)); for (Consumer consumer : extension.getSettingsPostEdit()) { consumer.accept(runConfig); @@ -227,8 +214,8 @@ public class RunConfig { dummyConfig = dummyConfig.replace("%ECLIPSE_PROJECT%", eclipseProjectName); dummyConfig = dummyConfig.replace("%IDEA_MODULE%", ideaModuleName); dummyConfig = dummyConfig.replace("%RUN_DIRECTORY%", runDir); - dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", programArgs.replaceAll("\"", """)); - dummyConfig = dummyConfig.replace("%VM_ARGS%", vmArgs.replaceAll("\"", """)); + dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", joinArguments(programArgs).replaceAll("\"", """)); + dummyConfig = dummyConfig.replace("%VM_ARGS%", joinArguments(vmArgs).replaceAll("\"", """)); String envs = ""; @@ -252,12 +239,20 @@ public class RunConfig { return dummyConfig; } - public static String getOSClientJVMArgs() { - if (OperatingSystem.getOS().equalsIgnoreCase("osx")) { - return " -XstartOnFirstThread"; + public static String joinArguments(List args) { + final var sb = new StringBuilder(); + boolean first = true; + + for (String arg : args) { + if (!first) { + sb.append(" "); + } + + first = false; + sb.append("\"").append(arg).append("\""); } - return ""; + return sb.toString(); } private static String getMainClass(String side, LoomGradleExtension extension, String defaultMainClass) { 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 8e0c28c5..fcfb4336 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -27,6 +27,7 @@ package net.fabricmc.loom.configuration.mods; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -49,9 +50,6 @@ import dev.architectury.tinyremapper.OutputConsumerPath; import dev.architectury.tinyremapper.TinyRemapper; import org.gradle.api.Project; import org.objectweb.asm.commons.Remapper; -import org.zeroturnaround.zip.ZipUtil; -import org.zeroturnaround.zip.transform.StringZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerRemapper; @@ -66,7 +64,9 @@ import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvid import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.LoggerFilter; import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.srg.AtRemapper; +import net.fabricmc.tinyremapper.NonClassCopyMode; import net.fabricmc.loom.util.srg.CoreModClassRemapper; import net.fabricmc.mappingio.tree.MemoryMappingTree; @@ -104,14 +104,14 @@ public class ModProcessor { private static void stripNestedJars(File file) { if (!ZipUtil.containsEntry(file, "fabric.mod.json")) return; // Strip out all contained jar info as we dont want loader to try and load the jars contained in dev. - ZipUtil.transformEntries(file, new ZipEntryTransformerEntry[]{(new ZipEntryTransformerEntry("fabric.mod.json", new StringZipEntryTransformer() { - @Override - protected String transform(ZipEntry zipEntry, String input) { - JsonObject json = LoomGradlePlugin.GSON.fromJson(input, JsonObject.class); + try { + ZipUtils.transformJson(JsonObject.class, file.toPath(), Map.of("fabric.mod.json", json -> { json.remove("jars"); - return LoomGradlePlugin.GSON.toJson(json); - } - }))}); + return json; + })); + } catch (IOException e) { + throw new UncheckedIOException("Failed to strip nested jars from %s".formatted(file), e); + } } /** @@ -194,12 +194,12 @@ public class ModProcessor { try { OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(info.getRemappedOutput().toPath()).build(); - outputConsumer.addNonClassFiles(info.getInputFile().toPath()); + outputConsumer.addNonClassFiles(info.getInputFile().toPath(), NonClassCopyMode.FIX_META_INF, remapper); outputConsumerMap.put(info, outputConsumer); String accessWidener = info.getAccessWidener(); if (accessWidener != null) { - accessWidenerMap.put(info, remapAccessWidener(ZipUtil.unpackEntry(info.inputFile, accessWidener), remapper.getRemapper())); + accessWidenerMap.put(info, remapAccessWidener(ZipUtils.unpack(info.inputFile.toPath(), accessWidener), remapper.getRemapper())); } remapper.apply(outputConsumer, tagMap.get(info)); @@ -219,7 +219,7 @@ public class ModProcessor { byte[] accessWidener = accessWidenerMap.get(info); if (accessWidener != null) { - ZipUtil.replaceEntry(info.getRemappedOutput(), info.getAccessWidener(), accessWidener); + ZipUtils.replace(info.getRemappedOutput().toPath(), info.getAccessWidener(), accessWidener); } if (extension.isForge()) { diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java index af1b2db7..580dfefa 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java @@ -24,24 +24,24 @@ package net.fabricmc.loom.configuration.processors; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.stream.Collectors; -import java.util.zip.ZipEntry; +import com.google.common.base.Preconditions; import com.google.common.hash.Hashing; import com.google.common.io.CharSource; -import org.zeroturnaround.zip.ZipUtil; -import org.zeroturnaround.zip.transform.StreamZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; + +import net.fabricmc.loom.util.ZipUtils; public class JarProcessorManager { private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; @@ -107,19 +107,18 @@ public class JarProcessorManager { jarProcessor.process(file); } - boolean manifestTransformed = ZipUtil.transformEntries(file, new ZipEntryTransformerEntry[] { - new ZipEntryTransformerEntry(MANIFEST_PATH, new StreamZipEntryTransformer() { - @Override - protected void transform(ZipEntry zipEntry, InputStream in, OutputStream out) throws IOException { - Manifest manifest = new Manifest(in); - manifest.getMainAttributes().putValue(JAR_PROCESSOR_HASH_ATTRIBUTE, getJarProcessorHash()); - manifest.write(out); - } - }) - }); + try { + int count = ZipUtils.transform(file.toPath(), Map.of(MANIFEST_PATH, bytes -> { + Manifest manifest = new Manifest(new ByteArrayInputStream(bytes)); + manifest.getMainAttributes().putValue(JAR_PROCESSOR_HASH_ATTRIBUTE, getJarProcessorHash()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + manifest.write(out); + return out.toByteArray(); + })); - if (!manifestTransformed) { - throw new RuntimeException("Could not add data to jar manifest in " + file); + Preconditions.checkState(count > 0, "Did not add data to jar manifest in " + file); + } catch (IOException e) { + throw new UncheckedIOException("Could not add data to jar manifest in " + file, e); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java index fc0d3adc..6a47ed71 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java @@ -54,14 +54,12 @@ import org.gradle.api.internal.artifacts.dependencies.AbstractModuleDependency; import org.gradle.api.internal.artifacts.dependencies.DefaultMutableVersionConstraint; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.TaskDependency; -import org.zeroturnaround.zip.ByteSource; -import org.zeroturnaround.zip.ZipEntrySource; -import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.api.mappings.layered.MappingContext; import net.fabricmc.loom.api.mappings.layered.MappingLayer; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.mappingio.adapter.MappingDstNsReorder; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.format.Tiny2Writer; @@ -116,9 +114,8 @@ public class LayeredMappingsDependency extends AbstractModuleDependency implemen MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nsReorder, MappingsNamespace.INTERMEDIARY.toString(), true); mappings.accept(nsSwitch); - ZipUtil.pack(new ZipEntrySource[] { - new ByteSource("mappings/mappings.tiny", writer.toString().getBytes(StandardCharsets.UTF_8)) - }, mappingsFile.toFile()); + Files.deleteIfExists(mappingsFile); + ZipUtils.add(mappingsFile, "mappings/mappings.tiny", writer.toString().getBytes(StandardCharsets.UTF_8)); } } @@ -131,10 +128,7 @@ public class LayeredMappingsDependency extends AbstractModuleDependency implemen byte[] data = LoomGradlePlugin.OBJECT_MAPPER.writeValueAsString(signatureFixes).getBytes(StandardCharsets.UTF_8); - ZipUtil.addEntry( - mappingsFile.toFile(), - new ByteSource("extras/record_signatures.json", data) - ); + ZipUtils.add(mappingsFile, "extras/record_signatures.json", data); } @Override 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 7f4a1994..fba80008 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 @@ -43,6 +43,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import java.util.regex.Pattern; @@ -53,9 +54,6 @@ import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.jetbrains.annotations.Nullable; -import org.zeroturnaround.zip.FileSource; -import org.zeroturnaround.zip.ZipEntrySource; -import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; @@ -75,6 +73,7 @@ import net.fabricmc.loom.util.DownloadUtil; import net.fabricmc.loom.util.srg.MCPReader; import net.fabricmc.loom.util.srg.SrgMerger; import net.fabricmc.loom.util.srg.SrgNamedWriter; +import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; @@ -167,7 +166,8 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings } if (Files.notExists(tinyMappingsJar) || isRefreshDeps()) { - ZipUtil.pack(new ZipEntrySource[] {new FileSource("mappings/mappings.tiny", tinyMappings.toFile())}, tinyMappingsJar.toFile()); + Files.deleteIfExists(tinyMappingsJar); + ZipUtils.add(tinyMappingsJar, "mappings/mappings.tiny", Files.readAllBytes(tinyMappings)); } if (hasUnpickDefinitions()) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftNativesProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftNativesProvider.java index 516df1a1..03904aa6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftNativesProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftNativesProvider.java @@ -35,12 +35,12 @@ import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.gradle.api.GradleException; import org.gradle.api.Project; -import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.util.HashedDownloadUtil; import net.fabricmc.loom.util.MirrorUtil; +import net.fabricmc.loom.util.ZipUtils; public class MinecraftNativesProvider { private final Project project; @@ -101,7 +101,7 @@ public class MinecraftNativesProvider { throw new GradleException("Native jar not found at " + libJarFile.getAbsolutePath()); } - ZipUtil.unpack(libJarFile, nativesDir); + ZipUtils.unpackAll(libJarFile.toPath(), nativesDir.toPath()); // Store a file containing the hash of the extracted natives, used on subsequent runs to skip extracting all the natives if they haven't changed File libSha1File = new File(nativesDir, libJarFile.getName() + ".sha1"); diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java b/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java index 83afc21b..c8baa2a7 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java +++ b/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java @@ -180,17 +180,20 @@ public class CFRObfuscationMapping extends NullMapping { public Dumper dumpFieldDoc(Field field, JavaTypeInstance owner) { // None static fields in records are handled in the class javadoc. if (isRecord(owner) && !isStatic(field)) { - return null; + return this; } MappingTree.ClassMapping classMapping = getClassMapping(owner); if (classMapping == null) { - return null; + return this; } MappingTree.FieldMapping fieldMapping = classMapping.getField(field.getFieldName(), field.getDescriptor()); - dumpComment(fieldMapping.getComment()); + + if (fieldMapping != null) { + dumpComment(fieldMapping.getComment()); + } return this; } diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java index 796ac49f..f3c2b7bd 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java @@ -26,7 +26,6 @@ package net.fabricmc.loom.task; import java.io.File; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -46,39 +45,11 @@ public abstract class AbstractRunTask extends JavaExec { this.config = configProvider.apply(getProject()); setClasspath(config.sourceSet.getRuntimeClasspath()); + args(config.programArgs); } @Override public void exec() { - List argsSplit = new ArrayList<>(); - String[] args = config.programArgs.split(" "); - int partPos = -1; - - for (int i = 0; i < args.length; i++) { - if (partPos < 0) { - if (args[i].startsWith("\"")) { - if (args[i].endsWith("\"")) { - argsSplit.add(args[i].substring(1, args[i].length() - 1)); - } else { - partPos = i; - } - } else { - argsSplit.add(args[i]); - } - } else if (args[i].endsWith("\"")) { - StringBuilder builder = new StringBuilder(args[partPos].substring(1)); - - for (int j = partPos + 1; j < i; j++) { - builder.append(" ").append(args[j]); - } - - builder.append(" ").append(args[i], 0, args[i].length() - 1); - argsSplit.add(builder.toString()); - partPos = -1; - } - } - - args(argsSplit); setWorkingDir(new File(getProject().getRootDir(), config.runDir)); environment(config.envVariables); @@ -103,7 +74,7 @@ public abstract class AbstractRunTask extends JavaExec { public List getJvmArgs() { List superArgs = super.getJvmArgs(); List args = new ArrayList<>(superArgs != null ? superArgs : Collections.emptyList()); - args.addAll(Arrays.asList(config.vmArgs.split(" "))); + args.addAll(config.vmArgs); return args; } } diff --git a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java index 2dff18f5..476dcd15 100644 --- a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java @@ -195,8 +195,8 @@ public class GenVsCodeProjectTask extends AbstractLoomTask { VsCodeConfiguration(Project project, RunConfig runConfig) { this.name = runConfig.configName; this.mainClass = runConfig.mainClass; - this.vmArgs = runConfig.vmArgs; - this.args = runConfig.programArgs; + this.vmArgs = RunConfig.joinArguments(runConfig.vmArgs); + this.args = RunConfig.joinArguments(runConfig.programArgs); this.cwd = "${workspaceFolder}/" + runConfig.runDir; this.projectName = runConfig.vscodeProjectName; this.env.putAll(runConfig.envVariables); diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index d4c613ae..e124751c 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -63,6 +63,7 @@ import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMapp 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; import net.fabricmc.loom.util.IOStringConsumer; import net.fabricmc.loom.util.OperatingSystem; import net.fabricmc.loom.util.gradle.ThreadedProgressLoggerConsumer; @@ -70,7 +71,6 @@ import net.fabricmc.loom.util.gradle.ThreadedSimpleProgressLogger; import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper; import net.fabricmc.loom.util.ipc.IPCClient; import net.fabricmc.loom.util.ipc.IPCServer; -import net.fabricmc.stitch.util.StitchUtil; public abstract class GenerateSourcesTask extends AbstractLoomTask { public final LoomDecompiler decompiler; @@ -149,13 +149,15 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { params.getClassPath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES)); }); - workQueue.await(); + try { + workQueue.await(); + } finally { + if (useProcessIsolation()) { + boolean stopped = WorkerDaemonClientsManagerHelper.stopIdleJVM(getWorkerDaemonClientsManager(), jvmMarkerValue); - if (useProcessIsolation()) { - boolean stopped = WorkerDaemonClientsManagerHelper.stopIdleJVM(getWorkerDaemonClientsManager(), jvmMarkerValue); - - if (!stopped) { - throw new RuntimeException("Failed to stop decompile worker JVM"); + if (!stopped) { + throw new RuntimeException("Failed to stop decompile worker JVM"); + } } } } @@ -264,8 +266,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { LineNumberRemapper remapper = new LineNumberRemapper(); remapper.readMappings(linemap.toFile()); - try (StitchUtil.FileSystemDelegate inFs = StitchUtil.getJarFileSystem(oldCompiledJar.toFile(), true); - StitchUtil.FileSystemDelegate outFs = StitchUtil.getJarFileSystem(linemappedJarDestination.toFile(), true)) { + try (FileSystemUtil.Delegate inFs = FileSystemUtil.getJarFileSystem(oldCompiledJar.toFile(), true); + FileSystemUtil.Delegate outFs = FileSystemUtil.getJarFileSystem(linemappedJarDestination.toFile(), true)) { remapper.process(logger, inFs.get().getPath("/"), outFs.get().getPath("/")); } } diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 833ea6be..57b9da84 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -60,6 +60,13 @@ public final class LoomTasks { tasks.register("downloadAssets", DownloadAssetsTask.class, t -> t.setDescription("Downloads required assets for Fabric.")); tasks.register("remapSourcesJar", RemapSourcesJarTask.class, t -> t.setDescription("Remaps the project sources jar to intermediary names.")); + tasks.getByName("check").dependsOn( + tasks.register("validateAccessWidener", ValidateAccessWidenerTask.class, t -> { + t.setDescription("Validate all the rules in the access widener against the Minecraft jar"); + t.setGroup("verification"); + }) + ); + registerIDETasks(tasks); registerRunTasks(tasks, project); registerLaunchSettings(project); diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index 732f4484..4bdf8eb4 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -24,12 +24,13 @@ package net.fabricmc.loom.task; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.UncheckedIOException; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.FileAlreadyExistsException; @@ -41,10 +42,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.jar.Manifest; -import java.util.zip.ZipEntry; import com.google.common.base.Preconditions; import dev.architectury.tinyremapper.IMappingProvider; @@ -67,9 +68,6 @@ import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.jvm.tasks.Jar; import org.jetbrains.annotations.ApiStatus; -import org.zeroturnaround.zip.ZipUtil; -import org.zeroturnaround.zip.transform.StreamZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.build.JarRemapper; @@ -90,6 +88,7 @@ import net.fabricmc.loom.util.LfWriter; import net.fabricmc.loom.util.SourceRemapper; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipReprocessorUtil; +import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.aw2at.Aw2At; import net.fabricmc.lorenztiny.TinyMappingsReader; import net.fabricmc.mappingio.tree.MappingTree; @@ -224,27 +223,32 @@ public class RemapJarTask extends Jar { } if (accessWidener != null) { - boolean replaced = ZipUtil.replaceEntry(data.output.toFile(), accessWidener.getLeft(), accessWidener.getRight()); - Preconditions.checkArgument(replaced, "Failed to remap access widener"); + try { + ZipUtils.replace(data.output, accessWidener.getLeft(), accessWidener.getRight()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to replace access widener in output jar", e); + } } if (!extension.isForge()) { // Add data to the manifest - boolean transformed = ZipUtil.transformEntries(data.output.toFile(), new ZipEntryTransformerEntry[] { - new ZipEntryTransformerEntry(MANIFEST_PATH, new StreamZipEntryTransformer() { - @Override - protected void transform(ZipEntry zipEntry, InputStream in, OutputStream out) throws IOException { - var manifest = new Manifest(in); + try { + int count = ZipUtils.transform(data.output, Map.of(MANIFEST_PATH, bytes -> { + var manifest = new Manifest(new ByteArrayInputStream(bytes)); var manifestConfiguration = new JarManifestConfiguration(project); manifestConfiguration.configure(manifest); manifest.getMainAttributes().putValue("Fabric-Mapping-Namespace", toM); + ByteArrayOutputStream out = new ByteArrayOutputStream(); manifest.write(out); + return out.toByteArray(); + })); + + Preconditions.checkState(count > 0, "Did not transform any jar manifest"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to transform jar manifest", e); } - }) - }); - Preconditions.checkArgument(transformed, "Failed to transform jar manifest"); } if (isReproducibleFileOrder() || !isPreserveFileTimestamps()) { diff --git a/src/main/java/net/fabricmc/loom/task/RunGameTask.java b/src/main/java/net/fabricmc/loom/task/RunGameTask.java index 421d34dc..faf7cebb 100644 --- a/src/main/java/net/fabricmc/loom/task/RunGameTask.java +++ b/src/main/java/net/fabricmc/loom/task/RunGameTask.java @@ -33,5 +33,8 @@ public class RunGameTask extends AbstractRunTask { @Inject public RunGameTask(RunConfigSettings settings) { super(proj -> RunConfig.runConfig(proj, settings)); + + // Defaults to empty, forwards stdin to mc. + setStandardInput(System.in); } } diff --git a/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java new file mode 100644 index 00000000..51f9d97c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java @@ -0,0 +1,109 @@ +/* + * 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; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +import javax.inject.Inject; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.api.tasks.TaskAction; + +import net.fabricmc.accesswidener.AccessWidenerFormatException; +import net.fabricmc.accesswidener.AccessWidenerReader; +import net.fabricmc.accesswidener.AccessWidenerVisitor; +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.tinyremapper.TinyRemapper; +import net.fabricmc.tinyremapper.api.TrEnvironment; + +public abstract class ValidateAccessWidenerTask extends DefaultTask { + @SkipWhenEmpty + @InputFile + public abstract RegularFileProperty getAccessWidener(); + + @InputFile + public abstract RegularFileProperty getTargetJar(); + + @Inject + public ValidateAccessWidenerTask() { + final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + + getAccessWidener().convention(extension.getAccessWidenerPath()).finalizeValueOnRead(); + getTargetJar().convention(getProject().getObjects().fileProperty().fileValue(extension.getMinecraftMappedProvider().getMappedJar())).finalizeValueOnRead(); + } + + @TaskAction + public void run() { + final TinyRemapper tinyRemapper = TinyRemapper.newRemapper().build(); + tinyRemapper.readClassPath(getTargetJar().get().getAsFile().toPath()); + + final AccessWidenerValidator validator = new AccessWidenerValidator(tinyRemapper.getEnvironment()); + final AccessWidenerReader accessWidenerReader = new AccessWidenerReader(validator); + + try (BufferedReader reader = Files.newBufferedReader(getAccessWidener().get().getAsFile().toPath(), StandardCharsets.UTF_8)) { + accessWidenerReader.read(reader, "named"); + } catch (AccessWidenerFormatException e) { + getProject().getLogger().error("Failed to validate access-widener file {} on line {}: {}", getAccessWidener().get().getAsFile().getName(), e.getLineNumber(), e.getMessage()); + throw e; + } catch (IOException e) { + throw new UncheckedIOException("Failed to read access widener", e); + } finally { + tinyRemapper.finish(); + } + } + + /** + * Validates that all entries in an access-widner file relate to a class/method/field in the mc jar. + */ + private static record AccessWidenerValidator(TrEnvironment environment) implements AccessWidenerVisitor { + @Override + public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) { + if (environment().getClass(name) == null) { + throw new RuntimeException("Could not find class (%s)".formatted(name)); + } + } + + @Override + public void visitMethod(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) { + if (environment().getMethod(owner, name, descriptor) == null) { + throw new RuntimeException("Could not find method (%s%s) in class (%s)".formatted(name, descriptor, owner)); + } + } + + @Override + public void visitField(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) { + if (environment().getField(owner, name, descriptor) == null) { + throw new RuntimeException("Could not find field (%s%s) in class (%s)".formatted(name, descriptor, owner)); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java index 89c1024b..05f0642f 100644 --- a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java +++ b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java @@ -1,17 +1,25 @@ /* - * Copyright 2016 FabricMC + * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Copyright (c) 2016-2017 FabricMC * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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: * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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; @@ -24,50 +32,40 @@ import java.nio.file.FileSystem; import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.FileSystems; import java.nio.file.Path; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; +import java.util.function.Supplier; public final class FileSystemUtil { - public static class FileSystemDelegate implements AutoCloseable { - private final FileSystem fileSystem; - private final boolean owner; - - public FileSystemDelegate(FileSystem fileSystem, boolean owner) { - this.fileSystem = fileSystem; - this.owner = owner; - } - - public FileSystem get() { - return fileSystem; - } - + public record Delegate(FileSystem fs, boolean owner) implements AutoCloseable, Supplier { @Override public void close() throws IOException { if (owner) { - fileSystem.close(); + fs.close(); } } + + @Override + public FileSystem get() { + return fs; + } } private FileSystemUtil() { } - private static final Map jfsArgsCreate = new HashMap<>(); - private static final Map jfsArgsEmpty = new HashMap<>(); + private static final Map jfsArgsCreate = Map.of("create", "true"); + private static final Map jfsArgsEmpty = Collections.emptyMap(); - static { - jfsArgsCreate.put("create", "true"); - } - - public static FileSystemDelegate getJarFileSystem(File file, boolean create) throws IOException { + public static Delegate getJarFileSystem(File file, boolean create) throws IOException { return getJarFileSystem(file.toURI(), create); } - public static FileSystemDelegate getJarFileSystem(Path path, boolean create) throws IOException { + public static Delegate getJarFileSystem(Path path, boolean create) throws IOException { return getJarFileSystem(path.toUri(), create); } - public static FileSystemDelegate getJarFileSystem(URI uri, boolean create) throws IOException { + public static Delegate getJarFileSystem(URI uri, boolean create) throws IOException { URI jarUri; try { @@ -77,9 +75,9 @@ public final class FileSystemUtil { } try { - return new FileSystemDelegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true); + return new Delegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true); } catch (FileSystemAlreadyExistsException e) { - return new FileSystemDelegate(FileSystems.getFileSystem(jarUri), false); + return new Delegate(FileSystems.getFileSystem(jarUri), false); } catch (IOException e) { throw new IOException("Could not create JAR file system for " + uri + " (create: " + create + ")", e); } diff --git a/src/main/java/net/fabricmc/loom/util/Pair.java b/src/main/java/net/fabricmc/loom/util/Pair.java new file mode 100644 index 00000000..5bf056ca --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/Pair.java @@ -0,0 +1,28 @@ +/* + * 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; + +public record Pair(L left, R right) { +} diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java index 3850e6f2..7873c78f 100644 --- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java @@ -37,7 +37,6 @@ import org.cadixdev.lorenz.MappingSet; import org.cadixdev.mercury.Mercury; import org.cadixdev.mercury.remapper.MercuryRemapper; import org.gradle.api.Project; -import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.RemappedConfigurationEntry; @@ -45,7 +44,6 @@ import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.gradle.ProgressLoggerHelper; import net.fabricmc.lorenztiny.TinyMappingsReader; import net.fabricmc.mappingio.tree.MemoryMappingTree; -import net.fabricmc.stitch.util.StitchUtil; public class SourceRemapper { private final Project project; @@ -136,7 +134,7 @@ public class SourceRemapper { // create tmp directory isSrcTmp = true; srcPath = Files.createTempDirectory("fabric-loom-src"); - ZipUtil.unpack(source, srcPath.toFile()); + ZipUtils.unpackAll(source.toPath(), srcPath); } if (!destination.isDirectory() && destination.exists()) { @@ -145,7 +143,7 @@ public class SourceRemapper { } } - StitchUtil.FileSystemDelegate dstFs = destination.isDirectory() ? null : StitchUtil.getJarFileSystem(destination, true); + FileSystemUtil.Delegate dstFs = destination.isDirectory() ? null : FileSystemUtil.getJarFileSystem(destination, true); Path dstPath = dstFs != null ? dstFs.get().getPath("/") : destination.toPath(); try { diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index 4be7151e..fcf0ec2a 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -102,7 +102,8 @@ public final class TinyRemapperHelper { } builder.invalidLvNamePattern(MC_LV_PATTERN); - builder.extraPreApplyVisitor((cls, next) -> { + builder.inferNameFromSameLvIndex(true) + .extraPreApplyVisitor((cls, next) -> { if (fixRecords && !cls.isRecord() && "java/lang/Record".equals(cls.getSuperName()) && mappings.getValue() != null) { return new RecordComponentFixVisitor(next, mappings.getValue(), mappings.getValue().getNamespaceId(MappingsNamespace.INTERMEDIARY.toString())); } diff --git a/src/main/java/net/fabricmc/loom/util/ZipUtils.java b/src/main/java/net/fabricmc/loom/util/ZipUtils.java new file mode 100644 index 00000000..3416db37 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/ZipUtils.java @@ -0,0 +1,230 @@ +/* + * 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.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.LoomGradlePlugin; + +public class ZipUtils { + public static boolean contains(Path zip, String path) { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, false)) { + Path fsPath = fs.get().getPath(path); + + return Files.exists(fsPath); + } catch (IOException e) { + throw new UncheckedIOException("Failed to check file from zip", e); + } + } + + public static void unpackAll(Path zip, Path output) throws IOException { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, false); + Stream walk = Files.walk(fs.get().getPath("/"))) { + Iterator iterator = walk.iterator(); + + while (iterator.hasNext()) { + Path fsPath = iterator.next(); + if (!Files.isRegularFile(fsPath)) continue; + Path dstPath = output.resolve(fs.get().getPath("/").relativize(fsPath).toString()); + Path dstPathParent = dstPath.getParent(); + if (dstPathParent != null) Files.createDirectories(dstPathParent); + Files.copy(fsPath, dstPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + } + } + } + + public static byte @Nullable [] unpackNullable(Path zip, String path) throws IOException { + try { + return unpack(zip, path); + } catch (NoSuchFileException e) { + return null; + } + } + + public static byte[] unpack(Path zip, String path) throws IOException { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, false)) { + Path fsPath = fs.get().getPath(path); + + if (Files.exists(fsPath)) { + return Files.readAllBytes(fsPath); + } else { + throw new NoSuchFileException(fsPath.toString()); + } + } + } + + public static void pack(Path from, Path zip) throws IOException { + Files.deleteIfExists(zip); + + if (!Files.isDirectory(from)) throw new IllegalArgumentException(from + " is not a directory!"); + + int count = 0; + + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, true); + Stream walk = Files.walk(from)) { + Iterator iterator = walk.iterator(); + + while (iterator.hasNext()) { + Path fromPath = iterator.next(); + if (!Files.isRegularFile(fromPath)) continue; + Path fsPath = fs.get().getPath(from.relativize(fromPath).toString()); + Path fsPathParent = fsPath.getParent(); + if (fsPathParent != null) Files.createDirectories(fsPathParent); + Files.copy(fromPath, fsPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + count++; + } + } + + if (count == 0) { + throw new IOException("Noting packed into %s from %s".formatted(zip, from)); + } + } + + public static void add(Path zip, String path, byte[] bytes) throws IOException { + add(zip, Collections.singleton(new Pair<>(path, bytes))); + } + + public static void add(Path zip, Iterable> files) throws IOException { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, true)) { + for (Pair pair : files) { + Path fsPath = fs.get().getPath(pair.left()); + Path fsPathParent = fsPath.getParent(); + if (fsPathParent != null) Files.createDirectories(fsPathParent); + Files.write(fsPath, pair.right(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + } + } + + public static void replace(Path zip, String path, byte[] bytes) throws IOException { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, true)) { + Path fsPath = fs.get().getPath(path); + + if (Files.exists(fsPath)) { + Files.write(fsPath, bytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } else { + throw new NoSuchFileException(fsPath.toString()); + } + } + } + + public static int transformString(Path zip, Collection>> transforms) throws IOException { + return transformString(zip, transforms.stream()); + } + + public static int transformString(Path zip, Stream>> transforms) throws IOException { + return transformString(zip, collectTransformersStream(transforms)); + } + + public static int transformString(Path zip, Map> transforms) throws IOException { + return transformMapped(zip, transforms, bytes -> new String(bytes, StandardCharsets.UTF_8), s -> s.getBytes(StandardCharsets.UTF_8)); + } + + public static int transformJson(Class typeOfT, Path zip, Collection>> transforms) throws IOException { + return transformJson(typeOfT, zip, transforms.stream()); + } + + public static int transformJson(Class typeOfT, Path zip, Stream>> transforms) throws IOException { + return transformJson(typeOfT, zip, collectTransformersStream(transforms)); + } + + public static int transformJson(Class typeOfT, Path zip, Map> transforms) throws IOException { + return transformMapped(zip, transforms, bytes -> LoomGradlePlugin.GSON.fromJson(new InputStreamReader(new ByteArrayInputStream(bytes)), typeOfT), + s -> LoomGradlePlugin.GSON.toJson(s, typeOfT).getBytes(StandardCharsets.UTF_8)); + } + + public static int transform(Path zip, Collection>> transforms) throws IOException { + return transform(zip, transforms.stream()); + } + + public static int transform(Path zip, Stream>> transforms) throws IOException { + return transform(zip, collectTransformersStream(transforms)); + } + + public static int transformMapped(Path zip, Map> transforms, Function deserializer, Function serializer) throws IOException { + Map> newTransforms = new HashMap<>(); + + for (Map.Entry> entry : transforms.entrySet()) { + if (entry.getValue() != null) { + newTransforms.put(entry.getKey(), bytes -> { + return serializer.apply(entry.getValue().apply(deserializer.apply(bytes))); + }); + } + } + + return transform(zip, newTransforms); + } + + public static int transform(Path zip, Map> transforms) throws IOException { + int replacedCount = 0; + + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, false)) { + for (Map.Entry> entry : transforms.entrySet()) { + Path fsPath = fs.get().getPath(entry.getKey()); + + if (Files.exists(fsPath) && entry.getValue() != null) { + Files.write(fsPath, entry.getValue().apply(Files.readAllBytes(fsPath)), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + replacedCount++; + } + } + } + + return replacedCount; + } + + @FunctionalInterface + public interface UnsafeUnaryOperator { + T apply(T arg) throws IOException; + } + + private static Map> collectTransformersStream(Stream>> transforms) { + Map> map = new HashMap<>(); + Iterator>> iterator = transforms.iterator(); + + while (iterator.hasNext()) { + Pair> next = iterator.next(); + map.put(next.left(), next.right()); + } + + return map; + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy index d1985600..5329c9fe 100644 --- a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy @@ -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.4-20211011231946+0000" + public final static String PRE_RELEASE_GRADLE = "7.4-20211023222429+0000" public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE] } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/AccessWidenerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/AccessWidenerTest.groovy index 43c793cb..87bd96a2 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/AccessWidenerTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/AccessWidenerTest.groovy @@ -25,7 +25,7 @@ package net.fabricmc.loom.test.integration import net.fabricmc.loom.test.util.GradleProjectTestTrait -import org.zeroturnaround.zip.ZipUtil +import net.fabricmc.loom.util.ZipUtils import spock.lang.Specification import spock.lang.Unroll @@ -54,7 +54,7 @@ class AccessWidenerTest extends Specification implements GradleProjectTestTrait def "transitive accesswidener (gradle #version)"() { setup: def gradle = gradleProject(project: "transitiveAccesswidener", version: version) - ZipUtil.pack(new File(gradle.projectDir, "dummyDependency"), new File(gradle.projectDir, "dummy.jar")) + ZipUtils.pack(new File(gradle.projectDir, "dummyDependency").toPath(), new File(gradle.projectDir, "dummy.jar").toPath()) when: def result = gradle.run(task: "build") @@ -65,4 +65,24 @@ class AccessWidenerTest extends Specification implements GradleProjectTestTrait where: version << STANDARD_TEST_VERSIONS } + + @Unroll + def "invalid (#awLine)"() { + setup: + def gradle = gradleProject(project: "accesswidener", version: version) + new File(gradle.projectDir, "src/main/resources/modid.accesswidener").append(awLine) + def errorPrefix = "Failed to validate access-widener file modid.accesswidener on line 10: java.lang.RuntimeException: " + + when: + def result = gradle.run(task: "check", expectFailure: true) + + then: + result.output.contains(errorPrefix + error) + + where: + awLine | error | version + 'accessible\tclass\tnet/minecraft/DoesntExists' | "Could not find class (net/minecraft/DoesntExists)" | DEFAULT_GRADLE + 'accessible\tfield\tnet/minecraft/screen/slot/Slot\tabc\tI' | "Could not find field (abcI) in class (net/minecraft/screen/slot/Slot)" | DEFAULT_GRADLE + 'accessible\tmethod\tnet/minecraft/client/main/Main\tmain\t([Ljava/lang/NotAString;)V' | "Could not find method (main([Ljava/lang/NotAString;)V) in class (net/minecraft/client/main/Main)" | DEFAULT_GRADLE + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy index e893c352..46d8b3a3 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy @@ -42,6 +42,7 @@ class RunConfigTest extends Specification implements GradleProjectTestTrait { then: result.task(":${task}").outcome == SUCCESS + result.output.contains("This contains a space") where: task | _ diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/UnpickTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/UnpickTest.groovy index 2b285fb6..dbe37f99 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/UnpickTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/UnpickTest.groovy @@ -25,8 +25,7 @@ package net.fabricmc.loom.test.integration import net.fabricmc.loom.test.util.GradleProjectTestTrait - -import org.zeroturnaround.zip.ZipUtil +import net.fabricmc.loom.util.ZipUtils import spock.lang.Specification import java.nio.charset.StandardCharsets @@ -66,6 +65,6 @@ class UnpickTest extends Specification implements GradleProjectTestTrait { private static String getClassSource(GradleProject gradle, String classname, String mappings = MAPPINGS) { File sourcesJar = gradle.getGeneratedSources(mappings) - return new String(ZipUtil.unpackEntry(sourcesJar, classname), StandardCharsets.UTF_8) + return new String(ZipUtils.unpack(sourcesJar.toPath(), classname), StandardCharsets.UTF_8) } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/RunConfigUnitTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/RunConfigUnitTest.groovy new file mode 100644 index 00000000..8a804d71 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/RunConfigUnitTest.groovy @@ -0,0 +1,38 @@ +/* + * 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.ide.RunConfig +import spock.lang.Specification + +class RunConfigUnitTest extends Specification { + def "escape arguments"() { + when: + def args = RunConfig.joinArguments(["-Dfabric.test=123", "-Dfabric.test=abc 123"]) + + then: + args == '"-Dfabric.test=123" "-Dfabric.test=abc 123"' + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy new file mode 100644 index 00000000..921edff8 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy @@ -0,0 +1,124 @@ +/* + * 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.util.Pair +import net.fabricmc.loom.util.ZipUtils +import spock.lang.Specification + +import java.nio.charset.StandardCharsets +import java.nio.file.Files + +class ZipUtilsTest extends Specification { + def "pack"() { + given: + def dir = File.createTempDir() + def zip = File.createTempFile("loom-zip-test", ".zip").toPath() + new File(dir, "test.txt").text = "This is a test of packing" + + when: + ZipUtils.pack(dir.toPath(), zip) + + then: + Files.exists(zip) + ZipUtils.contains(zip, "test.txt") + !ZipUtils.contains(zip, "nope.txt") + new String( ZipUtils.unpack(zip, "test.txt"), StandardCharsets.UTF_8) == "This is a test of packing" + } + + def "transform string"() { + given: + def dir = File.createTempDir() + def zip = File.createTempFile("loom-zip-test", ".zip").toPath() + new File(dir, "test.txt").text = "This is a test of transforming" + + when: + ZipUtils.pack(dir.toPath(), zip) + def transformed = ZipUtils.transformString(zip, [ + new Pair>("test.txt", new ZipUtils.UnsafeUnaryOperator() { + @Override + String apply(String arg) throws IOException { + return arg.toUpperCase() + } + }) + ]) + + then: + transformed == 1 + ZipUtils.contains(zip, "test.txt") + new String( ZipUtils.unpack(zip, "test.txt"), StandardCharsets.UTF_8) == "THIS IS A TEST OF TRANSFORMING" + } + + def "replace string"() { + given: + def dir = File.createTempDir() + def zip = File.createTempFile("loom-zip-test", ".zip").toPath() + new File(dir, "test.txt").text = "This has not been replaced" + + when: + ZipUtils.pack(dir.toPath(), zip) + ZipUtils.replace(zip, "test.txt", "This has been replaced".bytes) + + then: + ZipUtils.contains(zip, "test.txt") + new String(ZipUtils.unpack(zip, "test.txt"), StandardCharsets.UTF_8) == "This has been replaced" + } + + def "add file"() { + given: + def dir = File.createTempDir() + def zip = File.createTempFile("loom-zip-test", ".zip").toPath() + new File(dir, "test.txt").text = "This is original" + + when: + ZipUtils.pack(dir.toPath(), zip) + ZipUtils.add(zip, "test2.txt", "This has been added".bytes) + + then: + ZipUtils.contains(zip, "test.txt") + ZipUtils.contains(zip, "test2.txt") + new String(ZipUtils.unpack(zip, "test.txt"), StandardCharsets.UTF_8) == "This is original" + new String(ZipUtils.unpack(zip, "test2.txt"), StandardCharsets.UTF_8) == "This has been added" + } + + def "unpack all"() { + given: + def input = File.createTempDir() + def output = File.createTempDir() + + def zip = File.createTempFile("loom-zip-test", ".zip").toPath() + new File(input, "test.txt").text = "This is a test of unpacking all" + + def outputFile = new File(output, "test.txt") + + when: + ZipUtils.pack(input.toPath(), zip) + ZipUtils.unpackAll(zip, output.toPath()) + + then: + outputFile.exists() + outputFile.text == "This is a test of unpacking all" + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy index 91a19b91..6380992e 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy @@ -26,9 +26,9 @@ package net.fabricmc.loom.test.util import groovy.transform.Immutable import net.fabricmc.loom.test.LoomTestConstants +import net.fabricmc.loom.util.ZipUtils import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner -import org.zeroturnaround.zip.ZipUtil import spock.lang.Shared trait GradleProjectTestTrait { @@ -156,7 +156,7 @@ trait GradleProjectTestTrait { runner.withArguments(args as String[]) - return runner.build() + return options.expectFailure ? runner.buildAndFail() : runner.build() } private GradleRunner getRunner() { @@ -192,7 +192,7 @@ trait GradleProjectTestTrait { String getOutputZipEntry(String filename, String entryName) { def file = getOutputFile(filename) - def bytes = ZipUtil.unpackEntry(file, entryName) + def bytes = ZipUtils.unpackNullable(file.toPath(), entryName) if (bytes == null) { throw new FileNotFoundException("Could not find ${entryName} in ${entryName}") @@ -203,7 +203,7 @@ trait GradleProjectTestTrait { boolean hasOutputZipEntry(String filename, String entryName) { def file = getOutputFile(filename) - return ZipUtil.unpackEntry(file, entryName) != null + return ZipUtils.unpackNullable(file.toPath(), entryName) != null } File getGeneratedSources(String mappings) { diff --git a/src/test/resources/projects/runconfigs/build.gradle b/src/test/resources/projects/runconfigs/build.gradle index 9ea9de9e..c4a6d3f5 100644 --- a/src/test/resources/projects/runconfigs/build.gradle +++ b/src/test/resources/projects/runconfigs/build.gradle @@ -21,6 +21,10 @@ loom { vmArg "-Dfabric.autoTest" } } + + runConfigs.configureEach { + vmArg "-Dfabric.loom.test.space=This contains a space" + } } archivesBaseName = "fabric-example-mod" diff --git a/src/test/resources/projects/runconfigs/src/main/java/net/fabricmc/example/ExampleMod.java b/src/test/resources/projects/runconfigs/src/main/java/net/fabricmc/example/ExampleMod.java index 21928e56..f756397f 100644 --- a/src/test/resources/projects/runconfigs/src/main/java/net/fabricmc/example/ExampleMod.java +++ b/src/test/resources/projects/runconfigs/src/main/java/net/fabricmc/example/ExampleMod.java @@ -7,6 +7,8 @@ public class ExampleMod implements ModInitializer { public void onInitialize() { System.out.println("Hello Fabric world!"); + System.out.println(System.getProperty("fabric.loom.test.space")); + // Quit now, we dont need to load the whole game to know the run configs are works System.exit(0); }