From 3700fad9ca0efcc71a2ec8639e912214ed6df2c4 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 26 Jun 2022 00:02:46 +0100 Subject: [PATCH 1/5] ARM64 Windows & Linux support for MC versions that have classpath natives. Closes #675 --- .../fabricmc/loom/LoomRepositoryPlugin.java | 20 ++- .../minecraft/LWJGLVersionOverride.java | 19 +- .../minecraft/MinecraftLibraryProvider.java | 167 +++++++++++------- .../minecraft/MinecraftProvider.java | 4 +- .../net/fabricmc/loom/util/Architecture.java | 12 +- 5 files changed, 145 insertions(+), 77 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java index 2301f575..c0da3e8d 100644 --- a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-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 @@ -117,4 +117,22 @@ public class LoomRepositoryPlugin implements Plugin { }); }); } + + public static void forceLWJGLFromMavenCentral(Project project) { + // Force LWJGL from central, as it contains all the platform natives. + MavenArtifactRepository central = project.getRepositories().maven(repo -> { + repo.setName("MavenCentralLWJGL"); + repo.setUrl(ArtifactRepositoryContainer.MAVEN_CENTRAL_URL); + repo.content(content -> { + content.includeGroup("org.lwjgl"); + }); + }); + + project.getRepositories().exclusiveContent(repository -> { + repository.forRepositories(central); + repository.filter(filter -> { + filter.includeGroup("org.lwjgl"); + }); + }); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java index 7cdcde94..f06f613d 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-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 @@ -31,9 +31,11 @@ import static net.fabricmc.loom.util.OperatingSystem.WINDOWS; import java.util.List; import org.gradle.api.Project; +import org.gradle.api.artifacts.ExternalModuleDependency; import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.util.Architecture; +import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.OperatingSystem; public class LWJGLVersionOverride { @@ -81,6 +83,21 @@ public class LWJGLVersionOverride { return project.getProperties().get("fabric.loom.override-lwjgl") != null; } + public static void applyOverrides(Project project, boolean isMacOS) { + DEPENDENCIES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, s)); + NATIVES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, s)); + + if (isMacOS) { + MACOS_DEPENDENCIES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, s)); + MACOS_NATIVES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, s)); + } + + // Add the native support mod that fixes a handful of issues related to the LWJGL update at runtime. + ExternalModuleDependency dependency = (ExternalModuleDependency) project.getDependencies().create(Constants.Dependencies.NATIVE_SUPPORT + Constants.Dependencies.Versions.NATIVE_SUPPORT_VERSION); + dependency.setTransitive(false); + project.getDependencies().add("modLocalRuntime", dependency); + } + @Nullable private static String getNativesClassifier() { return switch (OperatingSystem.CURRENT_OS) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java index 3d155030..f79674da 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java @@ -28,112 +28,155 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.gradle.api.Project; -import org.gradle.api.artifacts.ExternalModuleDependency; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomRepositoryPlugin; import net.fabricmc.loom.configuration.providers.BundleMetadata; +import net.fabricmc.loom.util.Architecture; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.OperatingSystem; public class MinecraftLibraryProvider { private static final Pattern NATIVES_PATTERN = Pattern.compile("^(?.*)/(.*?)/(?.*)/((?.*?)-(\\k)-)(?.*).jar$"); + private static final boolean IS_MACOS = OperatingSystem.CURRENT_OS.equals(OperatingSystem.MAC_OS); - public void provide(MinecraftProvider minecraftProvider, Project project) { + private final Project project; + private final MinecraftVersionMeta versionInfo; + private final BundleMetadata serverBundleMetadata; + private final boolean runtimeOnlyLog4j; + private final boolean provideClient; + private final boolean provideServer; + + public MinecraftLibraryProvider(MinecraftProvider minecraftProvider, Project project) { final LoomGradleExtension extension = LoomGradleExtension.get(project); final MinecraftJarConfiguration jarConfiguration = extension.getMinecraftJarConfiguration().get(); - final MinecraftVersionMeta versionInfo = minecraftProvider.getVersionInfo(); - final BundleMetadata serverBundleMetadata = minecraftProvider.getServerBundleMetadata(); - final boolean runtimeOnlyLog4j = extension.getRuntimeOnlyLog4j().get(); - final boolean hasNativesToExtract = versionInfo.hasNativesToExtract(); - final boolean overrideLWJGL = hasNativesToExtract && (LWJGLVersionOverride.overrideByDefault(versionInfo) || LWJGLVersionOverride.forceOverride(project) || Boolean.getBoolean("loom.test.lwjgloverride")); - final boolean isMacOS = OperatingSystem.CURRENT_OS.equals(OperatingSystem.MAC_OS); + this.project = project; + this.versionInfo = minecraftProvider.getVersionInfo(); + this.serverBundleMetadata = minecraftProvider.getServerBundleMetadata(); + this.runtimeOnlyLog4j = extension.getRuntimeOnlyLog4j().get(); + this.provideClient = jarConfiguration.getSupportedEnvironments().contains("client"); + this.provideServer = jarConfiguration.getSupportedEnvironments().contains("server"); + assert provideClient || provideServer; + } - if (overrideLWJGL) { - project.getLogger().warn("Loom is upgrading Minecraft's LWJGL version to {}", LWJGLVersionOverride.LWJGL_VERSION); + public void provide() { + if (provideClient) { + // Modern 1.19 version put the natives on the classpath. + final boolean hasNativesToExtract = versionInfo.hasNativesToExtract(); + final boolean overrideLWJGL = hasNativesToExtract && (LWJGLVersionOverride.overrideByDefault(versionInfo) || LWJGLVersionOverride.forceOverride(project) || Boolean.getBoolean("loom.test.lwjgloverride")); + + if (overrideLWJGL) { + project.getLogger().warn("Loom is upgrading Minecraft's LWJGL version to {}", LWJGLVersionOverride.LWJGL_VERSION); + } + + if (hasNativesToExtract) { + // Create a configuration for + project.getConfigurations().register(Constants.Configurations.MINECRAFT_NATIVES, configuration -> configuration.setTransitive(false)); + } + + provideClientLibraries(overrideLWJGL, hasNativesToExtract); + + if (overrideLWJGL) { + LWJGLVersionOverride.applyOverrides(project, IS_MACOS); + } } - if (hasNativesToExtract) { - project.getConfigurations().register(Constants.Configurations.MINECRAFT_NATIVES, configuration -> configuration.setTransitive(false)); + if (provideServer) { + provideServerLibraries(); + } + } + + private void provideClientLibraries(boolean overrideLWJGL, boolean hasNativesToExtract) { + final boolean isArm = Architecture.CURRENT.isArm(); + final boolean classpathArmNatives = !hasNativesToExtract && isArm; + + if (classpathArmNatives) { + LoomRepositoryPlugin.forceLWJGLFromMavenCentral(project); } for (MinecraftVersionMeta.Library library : versionInfo.libraries()) { if (overrideLWJGL && library.name().startsWith("org.lwjgl")) { + // Skip over Minecraft's LWJGL version, will will replace this with a newer version later. continue; } if (library.isValidForOS() && !library.hasNatives() && library.artifact() != null) { - // 1.4.7 contains an LWJGL version with an invalid maven pom, set the metadata sources to not use the pom for this version. - if ("org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3".equals(library.name()) || "org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20131017".equals(library.name())) { + final String name = library.name(); + + if ("org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3".equals(name) || "org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20131017".equals(name)) { + // 1.4.7 contains an LWJGL version with an invalid maven pom, set the metadata sources to not use the pom for this version. LoomRepositoryPlugin.setupForLegacyVersions(project); - } else if (library.name().startsWith("org.ow2.asm:asm-all")) { + } + + if (name.startsWith("org.ow2.asm:asm-all")) { // Don't want asm-all, use the modern split version. continue; } - if (runtimeOnlyLog4j && library.name().startsWith("org.apache.logging.log4j")) { + if (runtimeOnlyLog4j && name.startsWith("org.apache.logging.log4j")) { // Make log4j a runtime only dep to force slf4j. - project.getDependencies().add(Constants.Configurations.MINECRAFT_RUNTIME_DEPENDENCIES, library.name()); - } else if (jarConfiguration.getSupportedEnvironments().contains("client")) { - // Client only library, or legacy version - project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, library.name()); - } else { - project.getLogger().debug("Minecraft library ({}) was not added to any configuration", library.name()); + project.getDependencies().add(Constants.Configurations.MINECRAFT_RUNTIME_DEPENDENCIES, name); + continue; } + + if (classpathArmNatives && name.startsWith("org.lwjgl:") + && (name.endsWith("natives-windows") || name.endsWith("natives-linux"))) { + // Add windows and Linux arm64 natives for modern classpath native MC versions. + project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, name + "-arm64"); + } + + project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, name); } if (library.hasNativesForOS()) { - MinecraftVersionMeta.Download nativeDownload = library.classifierForOS(); - - if (nativeDownload == null) { - continue; - } - - final String path = nativeDownload.path(); - final Matcher matcher = NATIVES_PATTERN.matcher(path); - - if (!matcher.find()) { - project.getLogger().warn("Failed to match regex for natives path : " + path); - continue; - } - - final String group = matcher.group("group").replace("/", "."); - final String name = matcher.group("name"); - final String version = matcher.group("version"); - final String classifier = matcher.group("classifier"); - - final String dependencyNotation = "%s:%s:%s:%s".formatted(group, name, version, classifier); - - if (overrideLWJGL && isMacOS && "java-objc-bridge".equals(name)) { - // Mojang split out the natives into their own jar, skip over Mojang's jar and use the official jar later on. - continue; - } - - project.getLogger().debug("Add native dependency '{}'", dependencyNotation); - project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, dependencyNotation); + provideNativesForLibrary(library, overrideLWJGL, IS_MACOS); } } + } + private void provideServerLibraries() { if (serverBundleMetadata != null) { for (BundleMetadata.Entry library : serverBundleMetadata.libraries()) { + if (runtimeOnlyLog4j && library.name().startsWith("org.apache.logging.log4j")) { + // Make log4j a runtime only dep to force slf4j. + project.getDependencies().add(Constants.Configurations.MINECRAFT_RUNTIME_DEPENDENCIES, library.name()); + continue; + } + project.getDependencies().add(Constants.Configurations.MINECRAFT_SERVER_DEPENDENCIES, library.name()); } } + } - if (overrideLWJGL) { - LWJGLVersionOverride.DEPENDENCIES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, s)); - LWJGLVersionOverride.NATIVES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, s)); + private void provideNativesForLibrary(MinecraftVersionMeta.Library library, boolean overrideLWJGL, boolean isMacOS) { + MinecraftVersionMeta.Download nativeDownload = library.classifierForOS(); - if (isMacOS) { - LWJGLVersionOverride.MACOS_DEPENDENCIES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, s)); - LWJGLVersionOverride.MACOS_NATIVES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, s)); - } - - // Add the native support mod that fixes a handful of issues related to the LWJGL update at runtime. - ExternalModuleDependency dependency = (ExternalModuleDependency) project.getDependencies().create(Constants.Dependencies.NATIVE_SUPPORT + Constants.Dependencies.Versions.NATIVE_SUPPORT_VERSION); - dependency.setTransitive(false); - project.getDependencies().add("modLocalRuntime", dependency); + if (nativeDownload == null) { + return; } + + final String path = nativeDownload.path(); + final Matcher matcher = NATIVES_PATTERN.matcher(path); + + if (!matcher.find()) { + project.getLogger().warn("Failed to match regex for natives path : " + path); + return; + } + + final String group = matcher.group("group").replace("/", "."); + final String name = matcher.group("name"); + final String version = matcher.group("version"); + final String classifier = matcher.group("classifier"); + + final String dependencyNotation = "%s:%s:%s:%s".formatted(group, name, version, classifier); + + if (overrideLWJGL && isMacOS && "java-objc-bridge".equals(name)) { + // Mojang split out the natives into their own jar, skip over Mojang's jar and use the official jar later on. + return; + } + + project.getLogger().debug("Add native dependency '{}'", dependencyNotation); + project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, dependencyNotation); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java index 4c91dec5..77af1a19 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java @@ -120,8 +120,8 @@ public abstract class MinecraftProvider { serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath()); } - libraryProvider = new MinecraftLibraryProvider(); - libraryProvider.provide(this, getProject()); + libraryProvider = new MinecraftLibraryProvider(this, project); + libraryProvider.provide(); } protected void initFiles() { diff --git a/src/main/java/net/fabricmc/loom/util/Architecture.java b/src/main/java/net/fabricmc/loom/util/Architecture.java index 3208a306..afebd26c 100644 --- a/src/main/java/net/fabricmc/loom/util/Architecture.java +++ b/src/main/java/net/fabricmc/loom/util/Architecture.java @@ -24,15 +24,9 @@ package net.fabricmc.loom.util; -public class Architecture { +public record Architecture(String name) { public static final Architecture CURRENT = new Architecture(System.getProperty("os.arch")); - private final String name; - - public Architecture(String name) { - this.name = name; - } - public boolean is64Bit() { return name.contains("64") || name.startsWith("armv8"); } @@ -40,8 +34,4 @@ public class Architecture { public boolean isArm() { return name.startsWith("arm") || name.startsWith("aarch64"); } - - public String getName() { - return name; - } } From da34ea4bf0dd940322aedabbba79ba44f33ccce3 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 26 Jun 2022 14:04:53 +0100 Subject: [PATCH 2/5] Only run the idea sync task when one or more run configs is generated for the project. Speeds up a fabric API sync by a reasonable amount. --- .../loom/configuration/ide/idea/IdeaConfiguration.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaConfiguration.java index 38ab079b..71029e8f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaConfiguration.java @@ -33,12 +33,18 @@ import org.gradle.api.Project; import org.gradle.api.tasks.TaskProvider; import org.gradle.internal.DefaultTaskExecutionRequest; +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.task.LoomTasks; public class IdeaConfiguration { public static void setup(Project project) { TaskProvider ideaSyncTask = project.getTasks().register("ideaSyncTask", IdeaSyncTask.class, task -> { - task.dependsOn(LoomTasks.getIDELaunchConfigureTaskName(project)); + if (LoomGradleExtension.get(project).getRunConfigs().stream().anyMatch(RunConfigSettings::isIdeConfigGenerated)) { + task.dependsOn(LoomTasks.getIDELaunchConfigureTaskName(project)); + } else { + task.setEnabled(false); + } }); if (!IdeaUtils.isIdeaSync()) { From 1e77bb0404d2fb63ab3c16572e1f13e70e276039 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 29 Jun 2022 18:22:21 +0100 Subject: [PATCH 3/5] Reject the unmapped dependency from the target configuration. Completes #679 Prevents having duplicate dependencies on the classpath. --- .../mods/ModConfigurationRemapper.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java index 8a2a2dbd..984cff20 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java @@ -35,6 +35,7 @@ import com.google.common.io.Files; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.FileCollectionDependency; +import org.gradle.api.artifacts.MutableVersionConstraint; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.query.ArtifactResolutionQuery; @@ -124,6 +125,20 @@ public class ModConfigurationRemapper { // Add all of the remapped mods onto the config for (ModDependencyInfo info : modDependencies) { project.getDependencies().add(info.targetConfig.getName(), info.getRemappedNotation()); + + if (info.getArtifact() instanceof ArtifactRef.ResolvedArtifactRef mavenArtifact) { + final String dependencyCoordinate = "%s:%s".formatted(mavenArtifact.group(), mavenArtifact.name()); + + // Prevent adding the same un-remapped dependency to the target configuration. + targetConfig.getDependencyConstraints().add(dependencies.getConstraints().create(dependencyCoordinate, constraint -> { + constraint.because("configuration (%s) already contains the remapped module from configuration (%s)".formatted( + targetConfig.getName(), + sourceConfig.getName() + )); + + constraint.version(MutableVersionConstraint::rejectAll); + })); + } } // Export to other projects From 35d37def55cf2bc51d6987c8ebb2ac38b42bd5bc Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 3 Jul 2022 15:07:13 +0100 Subject: [PATCH 4/5] Add JarSplitter. Still needs writing up. --- .../loom/configuration/mods/JarSplitter.java | 178 ++++++++++++++++++ .../loom/task/AbstractRemapJarTask.java | 9 +- .../fabricmc/loom/util/FileSystemUtil.java | 13 +- .../net/fabricmc/loom/util/IOFunction.java | 32 ++++ .../loom/test/unit/JarSplitterTest.groovy | 66 +++++++ 5 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/configuration/mods/JarSplitter.java create mode 100644 src/main/java/net/fabricmc/loom/util/IOFunction.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/JarSplitterTest.groovy diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/JarSplitter.java b/src/main/java/net/fabricmc/loom/configuration/mods/JarSplitter.java new file mode 100644 index 00000000..91bc25bd --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/JarSplitter.java @@ -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 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 walk = Files.walk(input.get().getPath("/"))) { + final Iterator 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 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 it = manifest.getEntries().values().iterator(); it.hasNext(); ) { + Attributes attrs = it.next(); + + for (Iterator 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); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java index dbc8edb4..053d8ec5 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java @@ -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 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) )); } diff --git a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java index a031ffa0..0a097c83 100644 --- a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java +++ b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java @@ -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 { + 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 fromInputStream(IOFunction 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); } diff --git a/src/main/java/net/fabricmc/loom/util/IOFunction.java b/src/main/java/net/fabricmc/loom/util/IOFunction.java new file mode 100644 index 00000000..d1281b92 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/IOFunction.java @@ -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 { + R apply(T t) throws IOException; +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/JarSplitterTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/JarSplitterTest.groovy new file mode 100644 index 00000000..a0a2a78a --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/JarSplitterTest.groovy @@ -0,0 +1,66 @@ +/* + * 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.test.unit + +import net.fabricmc.loom.configuration.mods.JarSplitter +import spock.lang.Specification + +class JarSplitterTest extends Specification { + public static final String INPUT_JAR_URL = "https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-lifecycle-events-v1/2.1.0%2B33fbc738a9/fabric-lifecycle-events-v1-2.1.0%2B33fbc738a9.jar" + + public static final File workingDir = new File("build/test/split") + + def "split jar"() { + given: + def inputJar = downloadJarIfNotExists(INPUT_JAR_URL, "input.jar") + def commonOutputJar = getFile("common.jar") + def clientOutputJar = getFile("client.jar") + + def jarSplitter = new JarSplitter(inputJar.toPath()) + when: + jarSplitter.split(commonOutputJar.toPath(), clientOutputJar.toPath()) + + then: + commonOutputJar.exists() + clientOutputJar.exists() + } + + File downloadJarIfNotExists(String url, String name) { + File dst = new File(workingDir, name) + + if (!dst.exists()) { + dst.parentFile.mkdirs() + dst << new URL(url).newInputStream() + } + + return dst + } + + File getFile(String name) { + File file = new File(workingDir, name) + file.delete() + return file + } +} From 48fafb9a641eabc2b83b0ec08f9c57db67ef13d2 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 3 Jul 2022 15:09:45 +0100 Subject: [PATCH 5/5] Remove unused properties. --- .../loom/api/LoomGradleExtensionAPI.java | 12 ------------ .../extension/LoomGradleExtensionApiImpl.java | 16 ---------------- 2 files changed, 28 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 988cbff3..cef59f33 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -78,12 +78,6 @@ public interface LoomGradleExtensionAPI { Dependency layered(Action action); - /** - * @deprecated Broken due to be evaluated too early. Replaced with "fabric.loom.dontRemap" gradle property. - */ - @Deprecated(forRemoval = true) - Property getRemapArchives(); - void runs(Action> action); NamedDomainObjectContainer getRunConfigs(); @@ -128,12 +122,6 @@ public interface LoomGradleExtensionAPI { Property getCustomMinecraftManifest(); - /** - * @deprecated Broken due to be evaluated too early. Replaced with "fabric.loom.disableRemappedVariants" gradle property. - */ - @Deprecated(forRemoval = true) - Property getSetupRemappedVariants(); - /** * Disables the deprecated POM generation for a publication. * This is useful if you want to suppress deprecation warnings when you're not using software components. diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index e72fa04d..e26c5fb1 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -66,9 +66,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA protected final ConfigurableFileCollection log4jConfigs; protected final RegularFileProperty accessWidener; protected final Property shareCaches; - protected final Property remapArchives; protected final Property customManifest; - protected final Property setupRemappedVariants; protected final Property transitiveAccessWideners; protected final Property modProvidedJavadoc; protected final Property intermediary; @@ -95,11 +93,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA this.accessWidener = project.getObjects().fileProperty(); this.shareCaches = project.getObjects().property(Boolean.class) .convention(false); - this.remapArchives = project.getObjects().property(Boolean.class) - .convention(true); this.customManifest = project.getObjects().property(String.class); - this.setupRemappedVariants = project.getObjects().property(Boolean.class) - .convention(true); this.transitiveAccessWideners = project.getObjects().property(Boolean.class) .convention(true); this.transitiveAccessWideners.finalizeValueOnRead(); @@ -197,11 +191,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA return new LayeredMappingsDependency(getProject(), new GradleMappingContext(getProject(), builtSpec.getVersion().replace("+", "_").replace(".", "_")), builtSpec, builtSpec.getVersion()); } - @Override - public Property getRemapArchives() { - return remapArchives; - } - @Override public void runs(Action> action) { action.execute(runConfigs); @@ -227,11 +216,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA return customManifest; } - @Override - public Property getSetupRemappedVariants() { - return setupRemappedVariants; - } - @Override public String getModVersion() { return versionParser.getModVersion();