diff --git a/src/main/java/net/fabricmc/loom/providers/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/providers/MinecraftPatchedProvider.java index dafa09eb..26bd8327 100644 --- a/src/main/java/net/fabricmc/loom/providers/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/providers/MinecraftPatchedProvider.java @@ -57,10 +57,9 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.*; -import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -220,8 +219,11 @@ public class MinecraftPatchedProvider extends DependencyProvider { File specialSourceJar = new File(getExtension().getUserCache(), "SpecialSource-1.8.3-shaded.jar"); DownloadUtil.downloadIfChanged(new URL("https://repo1.maven.org/maven2/net/md-5/SpecialSource/1.8.3/SpecialSource-1.8.3-shaded.jar"), specialSourceJar, getProject().getLogger(), true); - Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), specialSourceJar,minecraftProvider.minecraftClientJar.toPath(), tmpSrg[0]), minecraftClientSrgJar.toPath()); - Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), specialSourceJar, minecraftProvider.minecraftServerJar.toPath(), tmpSrg[0]), minecraftServerSrgJar.toPath()); + ThreadingUtils.run(() -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), specialSourceJar, minecraftProvider.minecraftClientJar.toPath(), tmpSrg[0]), minecraftClientSrgJar.toPath()); + }, () -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), specialSourceJar, minecraftProvider.minecraftServerJar.toPath(), tmpSrg[0]), minecraftServerSrgJar.toPath()); + }); } private void fixParameterAnnotation(File jarFile) throws Exception { @@ -250,11 +252,13 @@ public class MinecraftPatchedProvider extends DependencyProvider { private void injectForgeClasses(Logger logger) throws IOException { logger.lifecycle(":injecting forge classes into minecraft"); - copyAll(getExtension().getForgeUniversalProvider().getForge(), minecraftClientPatchedSrgJar); - copyAll(getExtension().getForgeUniversalProvider().getForge(), minecraftServerPatchedSrgJar); - - copyUserdevFiles(getExtension().getForgeUserdevProvider().getUserdevJar(), minecraftClientPatchedSrgJar); - copyUserdevFiles(getExtension().getForgeUserdevProvider().getUserdevJar(), minecraftServerPatchedSrgJar); + ThreadingUtils.run(() -> { + copyAll(getExtension().getForgeUniversalProvider().getForge(), minecraftClientPatchedSrgJar); + copyUserdevFiles(getExtension().getForgeUserdevProvider().getUserdevJar(), minecraftClientPatchedSrgJar); + }, () -> { + copyAll(getExtension().getForgeUniversalProvider().getForge(), minecraftServerPatchedSrgJar); + copyUserdevFiles(getExtension().getForgeUserdevProvider().getUserdevJar(), minecraftServerPatchedSrgJar); + }); logger.lifecycle(":injecting loom classes into minecraft"); File injection = File.createTempFile("loom-injection", ".jar"); @@ -263,13 +267,12 @@ public class MinecraftPatchedProvider extends DependencyProvider { FileUtils.copyInputStreamToFile(in, injection); } - walkFileSystems(injection, minecraftClientPatchedSrgJar, it -> !it.getFileName().toString().equals("MANIFEST.MF"), this::copyReplacing); - walkFileSystems(injection, minecraftServerPatchedSrgJar, it -> !it.getFileName().toString().equals("MANIFEST.MF"), this::copyReplacing); - - logger.lifecycle(":access transforming minecraft"); - for (Environment environment : Environment.values()) { + ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> { String side = environment.side(); File target = environment.patchedSrgJar.apply(this); + walkFileSystems(injection, target, it -> !it.getFileName().toString().equals("MANIFEST.MF"), this::copyReplacing); + + logger.lifecycle(":access transforming minecraft (" + side + ")"); File atJar = File.createTempFile("at" + side, ".jar"); File at = File.createTempFile("at" + side, ".cfg"); @@ -288,7 +291,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { } TransformerProcessor.main(args); - } + }); } private enum Environment { @@ -317,63 +320,49 @@ public class MinecraftPatchedProvider extends DependencyProvider { .map(File::toPath) .toArray(Path[]::new); - ExecutorService service = Executors.newFixedThreadPool(2); - List> futures = new LinkedList<>(); + ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> { + logger.lifecycle(":remapping minecraft (TinyRemapper, " + environment.side() + ", srg -> official)"); + TinyTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - for (Environment environment : Environment.values()) { - futures.add(service.submit(() -> { - try { - logger.lifecycle(":remapping minecraft (TinyRemapper, " + environment.side() + ", srg -> official)"); - TinyTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); + Path input = environment.patchedSrgJar.apply(this).toPath(); + Path output = environment.patchedOfficialJar.apply(this).toPath(); - Path input = environment.patchedSrgJar.apply(this).toPath(); - Path output = environment.patchedOfficialJar.apply(this).toPath(); + Files.deleteIfExists(output); - Files.deleteIfExists(output); + TinyRemapper remapper = TinyRemapper.newRemapper() + .withMappings(TinyRemapperMappingsHelper.create(mappingsWithSrg, "srg", "official", true)) + .withMappings(InnerClassRemapper.of(input, mappingsWithSrg, "srg", "official")) + .renameInvalidLocals(true) + .rebuildSourceFilenames(true) + .fixPackageAccess(true) + .build(); - TinyRemapper remapper = TinyRemapper.newRemapper() - .withMappings(TinyRemapperMappingsHelper.create(mappingsWithSrg, "srg", "official", true)) - .withMappings(InnerClassRemapper.of(input, mappingsWithSrg, "srg", "official")) - .renameInvalidLocals(true) - .rebuildSourceFilenames(true) - .fixPackageAccess(true) - .build(); + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) { + outputConsumer.addNonClassFiles(input); - try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) { - outputConsumer.addNonClassFiles(input); - - remapper.readClassPath(libraries); - remapper.readInputs(input); - remapper.apply(outputConsumer); - } finally { - remapper.finish(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - })); - } - - for (Future future : futures) { - future.get(); - } + remapper.readClassPath(libraries); + remapper.readInputs(input); + remapper.apply(outputConsumer); + } finally { + remapper.finish(); + } + }); } - private void patchJars(Logger logger) throws Exception { + private void patchJars(Logger logger) throws IOException { logger.lifecycle(":patching jars"); PatchProvider patchProvider = getExtension().getPatchProvider(); patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches); patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches); - - logger.lifecycle(":copying missing classes into patched jars"); - copyMissingClasses(minecraftClientSrgJar, minecraftClientPatchedSrgJar); - copyMissingClasses(minecraftServerSrgJar, minecraftServerPatchedSrgJar); - - logger.lifecycle(":fixing parameter annotations for patched jars"); - fixParameterAnnotation(minecraftClientPatchedSrgJar); - fixParameterAnnotation(minecraftServerPatchedSrgJar); + ThreadingUtils.run(() -> { + copyMissingClasses(minecraftClientSrgJar, minecraftClientPatchedSrgJar); + fixParameterAnnotation(minecraftClientPatchedSrgJar); + }, () -> { + copyMissingClasses(minecraftServerSrgJar, minecraftServerPatchedSrgJar); + fixParameterAnnotation(minecraftServerPatchedSrgJar); + }); } private void patchJars(File clean, File output, Path patches) throws IOException { @@ -407,9 +396,9 @@ public class MinecraftPatchedProvider extends DependencyProvider { private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) throws IOException { - try (FileSystem sourceFs = FileSystems.newFileSystem(new URI("jar:" + source.toURI()), ImmutableMap.of("create", false)); - FileSystem targetFs = FileSystems.newFileSystem(new URI("jar:" + target.toURI()), ImmutableMap.of("create", false))) { - for (Path sourceDir : toWalk.apply(sourceFs)) { + try (FileSystemUtil.FileSystemDelegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); + FileSystemUtil.FileSystemDelegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { + for (Path sourceDir : toWalk.apply(sourceFs.get())) { Path dir = sourceDir.toAbsolutePath(); Files.walk(dir) .filter(Files::isRegularFile) @@ -419,15 +408,13 @@ public class MinecraftPatchedProvider extends DependencyProvider { try { Path relativeSource = root ? it : dir.relativize(it); - Path targetPath = targetFs.getPath(relativeSource.toString()); - action.accept(sourceFs, targetFs, it, targetPath); + Path targetPath = targetFs.get().getPath(relativeSource.toString()); + action.accept(sourceFs.get(), targetFs.get(), it, targetPath); } catch (IOException e) { throw new UncheckedIOException(e); } }); } - } catch (URISyntaxException e) { - throw new IOException(e); } } 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 00000000..56027f5c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016 FabricMC + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +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.*; + +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; + } + + @Override + public void close() throws IOException { + if (owner) { + fileSystem.close(); + } + } + } + + private FileSystemUtil() { + + } + + private static final Map jfsArgsCreate = new HashMap<>(); + private static final Map jfsArgsEmpty = new HashMap<>(); + + static { + jfsArgsCreate.put("create", "true"); + } + + public static FileSystemDelegate getJarFileSystem(File file, boolean create) throws IOException { + return getJarFileSystem(file.toURI(), create); + } + + public static FileSystemDelegate getJarFileSystem(Path path, boolean create) throws IOException { + return getJarFileSystem(path.toUri(), create); + } + + public static FileSystemDelegate 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 FileSystemDelegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true); + } catch (FileSystemAlreadyExistsException e) { + return new FileSystemDelegate(FileSystems.getFileSystem(jarUri), false); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java new file mode 100644 index 00000000..6b9189ed --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java @@ -0,0 +1,91 @@ +package net.fabricmc.loom.util; + +import org.gradle.internal.impldep.com.google.api.client.repackaged.com.google.common.base.Function; + +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +public class ThreadingUtils { + public static void run(Collection values, UnsafeConsumer action) { + run(values.stream() + .map(t -> () -> action.accept(t)) + .collect(Collectors.toList())); + } + + public static void run(UnsafeRunnable... jobs) { + run(Arrays.asList(jobs)); + } + + public static void run(Collection jobs) { + try { + ExecutorService service = Executors.newFixedThreadPool(Math.min(jobs.size(), Runtime.getRuntime().availableProcessors() / 2)); + List> futures = new LinkedList<>(); + for (UnsafeRunnable runnable : jobs) { + futures.add(service.submit(() -> { + try { + runnable.run(); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + })); + } + for (Future future : futures) { + future.get(); + } + service.shutdownNow(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + public static List get(Collection values, Function action) { + return get(values.stream() + .>map(t -> () -> action.apply(t)) + .collect(Collectors.toList())); + } + + @SafeVarargs + public static List get(UnsafeCallable... jobs) { + return get(Arrays.asList(jobs)); + } + + public static List get(Collection> jobs) { + try { + ExecutorService service = Executors.newFixedThreadPool(Math.min(jobs.size(), Runtime.getRuntime().availableProcessors() / 2)); + List> futures = new LinkedList<>(); + for (UnsafeCallable runnable : jobs) { + futures.add(service.submit(() -> { + try { + return runnable.call(); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + })); + } + List result = new ArrayList<>(); + for (Future future : futures) { + result.add(future.get()); + } + service.shutdownNow(); + return result; + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + public interface UnsafeRunnable { + void run() throws Throwable; + } + + public interface UnsafeCallable { + T call() throws Throwable; + } + + public interface UnsafeConsumer { + void accept(T value) throws Throwable; + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index b5765ae1..3b44651a 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -1,6 +1,5 @@ package net.fabricmc.loom.util.srg; -import com.google.common.collect.ImmutableMap; import net.fabricmc.loom.LoomGradleExtension; import org.apache.commons.io.IOUtils; import org.gradle.api.Project; @@ -10,13 +9,15 @@ import org.zeroturnaround.zip.ZipUtil; import java.io.File; import java.io.IOException; -import java.net.URI; import java.nio.charset.StandardCharsets; -import java.nio.file.*; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Set; import java.util.UUID; +import java.util.jar.JarOutputStream; import java.util.stream.Collectors; +import java.util.zip.ZipEntry; public class SpecialSourceExecutor { public static Path produceSrgJar(Project project, File specialSourceJar, Path officialJar, Path srgPath) throws Exception { @@ -24,16 +25,14 @@ public class SpecialSourceExecutor { .filter(s -> !s.startsWith("\t")) .map(s -> s.split(" ")[0] + ".class") .collect(Collectors.toSet()); - Path stripped = project.getExtensions().getByType(LoomGradleExtension.class).getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 3) + "-filtered.jar"); + Path stripped = project.getExtensions().getByType(LoomGradleExtension.class).getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar"); Files.deleteIfExists(stripped); - try (FileSystem strippedFs = FileSystems.newFileSystem(URI.create("jar:" + stripped.toUri()), ImmutableMap.of("create", true))) { + try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(stripped))) { ZipUtil.iterate(officialJar.toFile(), (in, zipEntry) -> { if (filter.contains(zipEntry.getName())) { - Path path = strippedFs.getPath(zipEntry.getName()); - if (path.getParent() != null) { - Files.createDirectories(path.getParent()); - } - Files.write(path, IOUtils.toByteArray(in), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); + output.putNextEntry((ZipEntry) zipEntry.clone()); + IOUtils.write(IOUtils.toByteArray(in), output); + output.closeEntry(); } }); }