diff --git a/build.gradle b/build.gradle index 852f30ef2..497d065b4 100644 --- a/build.gradle +++ b/build.gradle @@ -105,6 +105,76 @@ forgix { } +class NativeTransformer implements Transformer { + private boolean enabled = false + private final HashMap replacements = new HashMap() + private final HashMap rewrittenFiles = new HashMap() + private var nativeRelocator + + + NativeTransformer() { + try { + int exitCode = Runtime.getRuntime().exec(new String[]{"python", "--version"}).waitFor(); + if (exitCode == 0) { + enabled = true + } + } catch (IOException e) { + println(e) + } + } + + void relocateNative(String target, String replacement) { + if (replacement.length() > target.length()) { + throw new GradleException("Length of value \"${replacement}\" exceeds the length of \"${target}\": ${replacement.length()} > ${target.length()}") + } + + replacements.put(target, replacement) + } + + + @Override + boolean canTransformResource(@Nonnull FileTreeElement element) { + return replacements.keySet().stream().anyMatch { + element.name.startsWith(it as String) + } + } + + @Override + void transform(@Nonnull TransformerContext context) { + println("Transforming $context.path...") + byte[] content = context.is.readAllBytes() + + if (nativeRelocator == null) { + nativeRelocator = new NativeRelocator() + } + + try { + Map.Entry pathReplacement = replacements.entrySet().stream().filter { + context.path.startsWith(it.key as String) + }.findFirst().orElseThrow() + + String path = context.path.replace(pathReplacement.key as String, pathReplacement.value as String) + content = nativeRelocator.processBinary(path, content, replacements) + + rewrittenFiles.put(path, content) + } + catch (Throwable e) { + throw new GradleException("Failed to relocate", e) + } + } + + @Override + boolean hasTransformedResource() { return !rewrittenFiles.isEmpty() } + + @Override + void modifyOutputStream(@Nonnull ZipOutputStream os, boolean preserveFileTimestamps) { + for (Map.Entry rewrittenFile : rewrittenFiles.entrySet()) { + os.putNextEntry(new ZipEntry(rewrittenFile.key)) + os.write(rewrittenFile.value) + } + } +} + subprojects { p -> // Does the same as "p == project(":common") || p == project(":fabric") || p == project(":quilt") || p == project(":forge") || p == project("WhateverWeAddLaterOn")" // Useful later on so we dont have duplicated code @@ -317,57 +387,17 @@ subprojects { p -> relocate "org.slf4j", "${librariesLocation}.slf4j" // Sqlite Database - relocate("org.xerial", "dh_sqlite.org.xerial") - relocate("org.sqlite", "dh_sqlite") { - exclude("org/sqlite/native/**") + // librariesLocation isn't used because it's too long for replacing paths in native libraries + relocate "org.xerial", "dh_sqlite.org.xerial" + relocate "org.sqlite", "dh_sqlite", { + exclude "org/sqlite/native/**" } - relocate("jdbc:sqlite", "jdbc:dh_sqlite") + relocate "jdbc:sqlite", "jdbc:dh_sqlite" - // Should be pretty much enough for Linux & Windows - // Probably won't work for Mac, because it requires signing native libs - transform(new Transformer() { - final String name = "RelocateSqliteNatives" - - private HashMap rewrittenFiles = new HashMap() - - @Override - boolean canTransformResource(@Nonnull FileTreeElement element) { - System.out.println("canTransformResource " + element.name) - return element.name.contains("sqlitejdbc") - } - - @Override - void transform(@Nonnull TransformerContext context) { - System.out.println("transform " + context.path) - - String path = context.path.replace("org/sqlite", "dh_sqlite") - byte[] content = context.is.readAllBytes() - - try { - content = RelocateNatives.processNative(path, content) - } - catch (Throwable e) { - throw new GradleException("Failed to relocate", e) - } - rewrittenFiles.put(path, content) - } - - @Override - boolean hasTransformedResource() { - System.out.println("hasTransformedResource") - return true - } - - @Override - void modifyOutputStream(@Nonnull ZipOutputStream os, boolean preserveFileTimestamps) { - System.out.println("modifyOutputStream") - - for (Map.Entry rewrittenFile : rewrittenFiles.entrySet()) { - os.putNextEntry(new ZipEntry(rewrittenFile.key)) - os.write(rewrittenFile.value) - } - } - }) + transform(NativeTransformer) { + relocateNative "org/sqlite", "dh_sqlite" + relocateNative "org_sqlite", "dh_1sqlite" + } // JOML diff --git a/buildSrc/src/main/java/RelocateNatives.java b/buildSrc/src/main/java/NativeRelocator.java similarity index 79% rename from buildSrc/src/main/java/RelocateNatives.java rename to buildSrc/src/main/java/NativeRelocator.java index f8a5b1c4f..7823db358 100644 --- a/buildSrc/src/main/java/RelocateNatives.java +++ b/buildSrc/src/main/java/NativeRelocator.java @@ -1,52 +1,21 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; import java.util.concurrent.CompletableFuture; -class RelocateNatives +class NativeRelocator { - private static boolean prepared = false; - private static final Path rootDirectory = Path.of(System.getProperty("user.dir"), "relocate_natives"); private static final Path cacheRoot = rootDirectory.resolve("cache"); - @SuppressWarnings({"ResultOfMethodCallIgnored", "BusyWait"}) - private static CompletableFuture readOutputStreams(Process process) + NativeRelocator() throws Exception { - return CompletableFuture.runAsync(() -> { - try - { - while (process.isAlive() || process.getInputStream().available() > 0 || process.getErrorStream().available() > 0) - { - if (process.getInputStream().available() > 0) - { - byte[] data = new byte[process.getInputStream().available()]; - process.getInputStream().read(data); - System.out.write(data); - } - if (process.getErrorStream().available() > 0) - { - byte[] data = new byte[process.getErrorStream().available()]; - process.getErrorStream().read(data); - System.err.write(data); - } - Thread.sleep(100); - } - } - catch (Exception ignored) - { - } - }); - } - - private static void ensurePrepared() throws Exception - { - if (prepared) + if (rootDirectory.resolve(".venv").toFile().exists()) { return; } - prepared = true; ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.directory(rootDirectory.toFile()); @@ -77,39 +46,40 @@ class RelocateNatives } } - public static byte[] updateUsingLief(Path outputFilePath, byte[] content) throws Exception + + private static CompletableFuture readOutputStreams(Process process) { - ensurePrepared(); - - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.directory(rootDirectory.toFile()); - - processBuilder.command( - rootDirectory.resolve(".venv/Scripts").toFile().exists() - ? rootDirectory.resolve(".venv/Scripts/python.exe").toString() - : rootDirectory.resolve(".venv/bin/python").toString(), - "./process.py", - outputFilePath.toString() - ); - - Process process = processBuilder.start(); - CompletableFuture outputFuture = readOutputStreams(process); - - process.getOutputStream().write(content); - process.getOutputStream().close(); - - int exitCode = process.waitFor(); - outputFuture.get(); - - if (exitCode != 0) - { - throw new Exception("Process failed: " + exitCode); - } - - return Files.readAllBytes(outputFilePath); + return CompletableFuture.runAsync(() -> { + try + { + while (process.isAlive() || process.getInputStream().available() > 0 || process.getErrorStream().available() > 0) + { + if (process.getInputStream().available() > 0) + { + byte[] data = new byte[process.getInputStream().available()]; + //noinspection ResultOfMethodCallIgnored + process.getInputStream().read(data); + System.out.write(data); + } + if (process.getErrorStream().available() > 0) + { + byte[] data = new byte[process.getErrorStream().available()]; + //noinspection ResultOfMethodCallIgnored + process.getErrorStream().read(data); + System.err.write(data); + } + + //noinspection BusyWait + Thread.sleep(100); + } + } + catch (Throwable ignored) + { + } + }); } - private static void replaceInByteArray(byte[] byteArray, String target, String replacement) + private void replaceInNullTerminatedStrings(byte[] byteArray, String target, String replacement) { if (target.length() < replacement.length()) { @@ -147,9 +117,40 @@ class RelocateNatives } } - public static byte[] processNative(String path, byte[] content) throws Exception + public byte[] fixModifiedBinary(Path outputFilePath, byte[] content) throws Exception { - Path outputFilePath = cacheRoot.resolve(path); + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.directory(rootDirectory.toFile()); + + processBuilder.command( + rootDirectory.resolve(".venv/Scripts").toFile().exists() + ? rootDirectory.resolve(".venv/Scripts/python.exe").toString() + : rootDirectory.resolve(".venv/bin/python").toString(), + "./fix_modified_binary.py", + outputFilePath.toString() + ); + + Process process = processBuilder.start(); + CompletableFuture outputFuture = readOutputStreams(process); + + process.getOutputStream().write(content); + process.getOutputStream().close(); + + int exitCode = process.waitFor(); + outputFuture.get(); + + if (exitCode != 0) + { + throw new Exception("Process failed: " + exitCode); + } + + return Files.readAllBytes(outputFilePath); + } + + public byte[] processBinary(String outputPath, byte[] content, Map replacements) throws Exception + { + Path outputFilePath = cacheRoot.resolve(outputPath); + //noinspection ResultOfMethodCallIgnored outputFilePath.getParent().toFile().mkdirs(); if (outputFilePath.toFile().exists()) @@ -157,9 +158,12 @@ class RelocateNatives return Files.readAllBytes(outputFilePath); } - replaceInByteArray(content, "org_sqlite", "dh_1sqlite"); - replaceInByteArray(content, "org/sqlite", "dh_sqlite"); - return updateUsingLief(outputFilePath, content); + for (Map.Entry replacement : replacements.entrySet()) + { + this.replaceInNullTerminatedStrings(content, replacement.getKey(), replacement.getValue()); + } + + return this.fixModifiedBinary(outputFilePath, content); } } diff --git a/relocate_natives/process.py b/relocate_natives/fix_modified_binary.py similarity index 100% rename from relocate_natives/process.py rename to relocate_natives/fix_modified_binary.py