From 5c190cc3ef99507bbb38525c0f6a24480a7ec65c Mon Sep 17 00:00:00 2001 From: shedaniel Date: Tue, 26 Oct 2021 20:08:17 +0800 Subject: [PATCH] Use NIO instead of ZipUtil (#513) * Use nio for zip utils * Make tests work * Please work * Fix some issues with tests * Fix more issues with tests * NIOZipUtils -> ZipUtils * Resolve Juuxel's reviews * Use our own FS utils * Improve error handling, add loom Pair * Add Unit tests + fixes Co-authored-by: modmuss50 --- build.gradle | 1 - .../loom/build/MixinRefmapHelper.java | 21 +- .../loom/build/nesting/JarNester.java | 41 ++-- .../nesting/NestedDependencyProvider.java | 12 +- .../configuration/DependencyProvider.java | 39 +-- .../accesswidener/AccessWidenerFile.java | 21 +- .../AccessWidenerJarProcessor.java | 18 +- .../AccessWidenerTransformer.java | 43 ++-- .../loom/configuration/mods/ModProcessor.java | 23 +- .../processors/JarProcessorManager.java | 35 ++- .../mappings/LayeredMappingsDependency.java | 14 +- .../mappings/MappingsProviderImpl.java | 9 +- .../minecraft/MinecraftNativesProvider.java | 4 +- .../loom/task/GenerateSourcesTask.java | 6 +- .../net/fabricmc/loom/task/RemapJarTask.java | 46 ++-- .../fabricmc/loom/util/FileSystemUtil.java | 83 +++++++ .../java/net/fabricmc/loom/util/Pair.java | 28 +++ .../fabricmc/loom/util/SourceRemapper.java | 6 +- .../java/net/fabricmc/loom/util/ZipUtils.java | 230 ++++++++++++++++++ .../test/integration/AccessWidenerTest.groovy | 4 +- .../loom/test/integration/UnpickTest.groovy | 5 +- .../loom/test/unit/ZipUtilsTest.groovy | 124 ++++++++++ .../test/util/GradleProjectTestTrait.groovy | 6 +- 23 files changed, 660 insertions(+), 159 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/util/FileSystemUtil.java create mode 100644 src/main/java/net/fabricmc/loom/util/Pair.java create mode 100644 src/main/java/net/fabricmc/loom/util/ZipUtils.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy diff --git a/build.gradle b/build.gradle index d602019..9aac8e3 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,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') diff --git a/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java b/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java index cbacf26..4d1ba26 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 662fc2c..efae834 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 b211ad7..18dc816 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/DependencyProvider.java b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java index b54c362..74642f7 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 680aae9..a864861 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 0970717..3baeb08 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 bdde740..a0f3ad5 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/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index 7640160..4a1bc75 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; @@ -41,9 +42,6 @@ import java.util.zip.ZipEntry; import com.google.gson.JsonObject; 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; @@ -57,6 +55,7 @@ import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.tinyremapper.InputTag; import net.fabricmc.tinyremapper.NonClassCopyMode; import net.fabricmc.tinyremapper.OutputConsumerPath; @@ -95,14 +94,14 @@ public class ModProcessor { private static void stripNestedJars(File file) { // 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); + } } /** @@ -180,7 +179,7 @@ public class ModProcessor { 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)); @@ -199,7 +198,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); } info.finaliseRemapping(); 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 af1b2db..580dfef 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 7ab34f8..dcfae66 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 @@ -43,14 +43,12 @@ import org.gradle.api.artifacts.FileCollectionDependency; import org.gradle.api.artifacts.SelfResolvingDependency; 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; @@ -104,9 +102,8 @@ public class LayeredMappingsDependency implements SelfResolvingDependency, FileC 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)); } } @@ -119,10 +116,7 @@ public class LayeredMappingsDependency implements SelfResolvingDependency, FileC 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 f099e3c..09ffb37 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 @@ -37,8 +37,8 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Collections; -import java.util.Objects; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import java.util.regex.Pattern; @@ -48,9 +48,6 @@ import com.google.gson.JsonObject; import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; 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; @@ -65,6 +62,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvid import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DeletingFileVisitor; import net.fabricmc.loom.util.DownloadUtil; +import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; @@ -131,7 +129,8 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings mappingTree = readMappings(); 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 516df1a..03904aa 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/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index ecd9ad8..22211b0 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -62,6 +62,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; @@ -69,7 +70,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; @@ -265,8 +265,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/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index 3729326..600d682 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -24,20 +24,21 @@ package net.fabricmc.loom.task; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; 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.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.jar.Manifest; -import java.util.zip.ZipEntry; import com.google.common.base.Preconditions; import org.gradle.api.Action; @@ -52,9 +53,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.api.mappings.layered.MappingsNamespace; @@ -72,6 +70,7 @@ import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipReprocessorUtil; +import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.stitch.util.Pair; import net.fabricmc.tinyremapper.TinyRemapper; import net.fabricmc.tinyremapper.TinyUtils; @@ -190,26 +189,31 @@ 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); + } } // 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); - var manifestConfiguration = new JarManifestConfiguration(project); + 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); + manifestConfiguration.configure(manifest); + manifest.getMainAttributes().putValue("Fabric-Mapping-Namespace", toM); - manifest.write(out); - } - }) - }); - Preconditions.checkArgument(transformed, "Failed to transform jar manifest"); + 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); + } if (isReproducibleFileOrder() || !isPreserveFileTimestamps()) { try { diff --git a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java new file mode 100644 index 0000000..377bb8c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java @@ -0,0 +1,83 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016-2017 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.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.function.Supplier; + +public final class FileSystemUtil { + public record Delegate(FileSystem fs, boolean owner) implements AutoCloseable, Supplier { + @Override + public void close() throws IOException { + if (owner) { + fs.close(); + } + } + + @Override + public FileSystem get() { + return fs; + } + } + + private FileSystemUtil() { + } + + private static final Map jfsArgsCreate = Map.of("create", "true"); + private static final Map jfsArgsEmpty = Collections.emptyMap(); + + public static Delegate getJarFileSystem(File file, boolean create) throws IOException { + return getJarFileSystem(file.toURI(), create); + } + + public static Delegate getJarFileSystem(Path path, boolean create) throws IOException { + return getJarFileSystem(path.toUri(), create); + } + + public static Delegate getJarFileSystem(URI uri, boolean create) throws IOException { + URI jarUri; + + try { + jarUri = new URI("jar:" + uri.getScheme(), uri.getHost(), uri.getPath(), uri.getFragment()); + } catch (URISyntaxException e) { + throw new IOException(e); + } + + try { + return new Delegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true); + } catch (FileSystemAlreadyExistsException e) { + return new Delegate(FileSystems.getFileSystem(jarUri), false); + } + } +} 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 0000000..5bf056c --- /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 776fa6e..f37211b 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.api.mappings.layered.MappingsNamespace; @@ -46,7 +45,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; @@ -126,7 +124,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()) { @@ -135,7 +133,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/ZipUtils.java b/src/main/java/net/fabricmc/loom/util/ZipUtils.java new file mode 100644 index 0000000..3416db3 --- /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/integration/AccessWidenerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/AccessWidenerTest.groovy index 6ffe74f..87bd96a 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") 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 2b285fb..dbe37f9 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/ZipUtilsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy new file mode 100644 index 0000000..921edff --- /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 1bb8b46..6380992 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 { @@ -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) {