mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-30 13:05:27 -05:00
Add JarSplitter.
Still needs writing up.
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.configuration.mods;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.fabricmc.loom.task.AbstractRemapJarTask;
|
||||
import net.fabricmc.loom.util.FileSystemUtil;
|
||||
|
||||
public class JarSplitter {
|
||||
final Path inputJar;
|
||||
|
||||
public JarSplitter(Path inputJar) {
|
||||
this.inputJar = inputJar;
|
||||
}
|
||||
|
||||
public boolean split(Path commonOutputJar, Path clientOutputJar) throws IOException {
|
||||
try (FileSystemUtil.Delegate input = FileSystemUtil.getJarFileSystem(inputJar)) {
|
||||
final Manifest manifest = input.fromInputStream(Manifest::new, AbstractRemapJarTask.MANIFEST_PATH);
|
||||
final List<String> clientEntries = readClientEntries(manifest);
|
||||
|
||||
if (clientEntries.isEmpty()) {
|
||||
// No client entries, just copy the input jar
|
||||
Files.copy(inputJar, commonOutputJar);
|
||||
return false;
|
||||
}
|
||||
|
||||
try (FileSystemUtil.Delegate commonOutput = FileSystemUtil.getJarFileSystem(commonOutputJar, true);
|
||||
FileSystemUtil.Delegate clientOutput = FileSystemUtil.getJarFileSystem(clientOutputJar, true);
|
||||
Stream<Path> walk = Files.walk(input.get().getPath("/"))) {
|
||||
final Iterator<Path> iterator = walk.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
final Path entry = iterator.next();
|
||||
|
||||
if (!Files.isRegularFile(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Path relativePath = input.get().getPath("/").relativize(entry);
|
||||
|
||||
if (relativePath.startsWith("META-INF")) {
|
||||
if (isSignatureData(relativePath)) {
|
||||
// Strip any signature data
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final String entryPath = relativePath.toString();
|
||||
|
||||
/*
|
||||
Copy the manifest to both jars
|
||||
- Remove signature data
|
||||
- Remove split data as its already been split.
|
||||
*/
|
||||
if (entryPath.equals(AbstractRemapJarTask.MANIFEST_PATH)) {
|
||||
final Manifest outManifest = new Manifest(manifest);
|
||||
final Attributes attributes = outManifest.getMainAttributes();
|
||||
stripSignatureData(outManifest);
|
||||
|
||||
attributes.remove(Attributes.Name.SIGNATURE_VERSION);
|
||||
Objects.requireNonNull(attributes.remove(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_NAME));
|
||||
Objects.requireNonNull(attributes.remove(AbstractRemapJarTask.MANIFEST_CLIENT_ENTRIES_NAME));
|
||||
|
||||
// TODO add an attribute to denote if the jar is common or client now
|
||||
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
outManifest.write(out);
|
||||
final byte[] manifestBytes = out.toByteArray();
|
||||
|
||||
writeBytes(manifestBytes, commonOutput.getPath(AbstractRemapJarTask.MANIFEST_PATH));
|
||||
writeBytes(manifestBytes, clientOutput.getPath(AbstractRemapJarTask.MANIFEST_PATH));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
final FileSystemUtil.Delegate target = clientEntries.contains(entryPath) ? clientOutput : commonOutput;
|
||||
final Path outputEntry = target.getPath(entryPath);
|
||||
final Path outputParent = outputEntry.getParent();
|
||||
|
||||
if (outputParent != null) {
|
||||
Files.createDirectories(outputParent);
|
||||
}
|
||||
|
||||
Files.copy(entry, outputEntry, StandardCopyOption.COPY_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<String> readClientEntries(Manifest manifest) {
|
||||
final Attributes attributes = manifest.getMainAttributes();
|
||||
final String splitEnvValue = attributes.getValue(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_KEY);
|
||||
final String clientEntriesValue = attributes.getValue(AbstractRemapJarTask.MANIFEST_CLIENT_ENTRIES_KEY);
|
||||
|
||||
if (splitEnvValue == null || !splitEnvValue.equals("true")) {
|
||||
throw new UnsupportedOperationException("Cannot split jar that has not been built with a split env");
|
||||
}
|
||||
|
||||
if (clientEntriesValue == null) {
|
||||
throw new IllegalStateException("Split jar does not contain any client only classes");
|
||||
}
|
||||
|
||||
return Arrays.stream(clientEntriesValue.split(";")).toList();
|
||||
}
|
||||
|
||||
private boolean isSignatureData(Path path) {
|
||||
final String fileName = path.getFileName().toString();
|
||||
return fileName.endsWith(".SF")
|
||||
|| fileName.endsWith(".DSA")
|
||||
|| fileName.endsWith(".RSA")
|
||||
|| fileName.startsWith("SIG-");
|
||||
}
|
||||
|
||||
// Based off tiny-remapper's MetaInfFixer
|
||||
private static void stripSignatureData(Manifest manifest) {
|
||||
for (Iterator<Attributes> it = manifest.getEntries().values().iterator(); it.hasNext(); ) {
|
||||
Attributes attrs = it.next();
|
||||
|
||||
for (Iterator<Object> it2 = attrs.keySet().iterator(); it2.hasNext(); ) {
|
||||
Attributes.Name attrName = (Attributes.Name) it2.next();
|
||||
String name = attrName.toString();
|
||||
|
||||
if (name.endsWith("-Digest") || name.contains("-Digest-") || name.equals("Magic")) {
|
||||
it2.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.isEmpty()) it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeBytes(byte[] bytes, Path path) throws IOException {
|
||||
final Path parent = path.getParent();
|
||||
|
||||
if (parent != null) {
|
||||
Files.createDirectories(parent);
|
||||
}
|
||||
|
||||
Files.write(path, bytes);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -62,6 +63,10 @@ import net.fabricmc.loom.util.ZipUtils;
|
||||
public abstract class AbstractRemapJarTask extends Jar {
|
||||
public static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
|
||||
public static final String MANIFEST_NAMESPACE_KEY = "Fabric-Mapping-Namespace";
|
||||
public static final String MANIFEST_SPLIT_ENV_KEY = "Fabric-Loom-Split-Environment";
|
||||
public static final String MANIFEST_CLIENT_ENTRIES_KEY = "Fabric-Loom-Client-Only-Entries";
|
||||
public static final Attributes.Name MANIFEST_SPLIT_ENV_NAME = new Attributes.Name(MANIFEST_SPLIT_ENV_KEY);
|
||||
public static final Attributes.Name MANIFEST_CLIENT_ENTRIES_NAME = new Attributes.Name(MANIFEST_CLIENT_ENTRIES_KEY);
|
||||
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getInputFile();
|
||||
@@ -141,8 +146,8 @@ public abstract class AbstractRemapJarTask extends Jar {
|
||||
|
||||
protected void applyClientOnlyManifestAttributes(AbstractRemapParams params, List<String> entries) {
|
||||
params.getManifestAttributes().set(Map.of(
|
||||
"Fabric-Loom-Split-Environment", "true",
|
||||
"Fabric-Loom-Client-Only-Entries", String.join(";", entries)
|
||||
MANIFEST_SPLIT_ENV_KEY, "true",
|
||||
MANIFEST_CLIENT_ENTRIES_KEY, String.join(";", entries)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ package net.fabricmc.loom.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
@@ -38,8 +39,12 @@ import net.fabricmc.tinyremapper.FileSystemReference;
|
||||
|
||||
public final class FileSystemUtil {
|
||||
public record Delegate(FileSystemReference reference) implements AutoCloseable, Supplier<FileSystem> {
|
||||
public Path getPath(String path, String... more) {
|
||||
return get().getPath(path, more);
|
||||
}
|
||||
|
||||
public byte[] readAllBytes(String path) throws IOException {
|
||||
Path fsPath = get().getPath(path);
|
||||
Path fsPath = getPath(path);
|
||||
|
||||
if (Files.exists(fsPath)) {
|
||||
return Files.readAllBytes(fsPath);
|
||||
@@ -48,6 +53,12 @@ public final class FileSystemUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T fromInputStream(IOFunction<InputStream, T> function, String path, String... more) throws IOException {
|
||||
try (InputStream inputStream = Files.newInputStream(getPath(path, more))) {
|
||||
return function.apply(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
public String readString(String path) throws IOException {
|
||||
return new String(readAllBytes(path), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
32
src/main/java/net/fabricmc/loom/util/IOFunction.java
Normal file
32
src/main/java/net/fabricmc/loom/util/IOFunction.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface IOFunction<T, R> {
|
||||
R apply(T t) throws IOException;
|
||||
}
|
||||
Reference in New Issue
Block a user