diff --git a/src/main/java/net/fabricmc/loom/configuration/InstallerData.java b/src/main/java/net/fabricmc/loom/configuration/InstallerData.java index 9196b04d..9496320b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/InstallerData.java +++ b/src/main/java/net/fabricmc/loom/configuration/InstallerData.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 @@ -25,6 +25,55 @@ package net.fabricmc.loom.configuration; import com.google.gson.JsonObject; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomRepositoryPlugin; +import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; +import net.fabricmc.loom.util.Constants; public record InstallerData(String version, JsonObject installerJson) { + public void applyToProject(Project project) { + LoomGradleExtension extension = LoomGradleExtension.get(project); + + if (extension.getInstallerData() != null) { + throw new IllegalStateException("Already applied installer data"); + } + + extension.setInstallerData(this); + + JsonObject libraries = installerJson.get("libraries").getAsJsonObject(); + Configuration loaderDepsConfig = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES); + Configuration apDepsConfig = project.getConfigurations().getByName("annotationProcessor"); + + libraries.get("common").getAsJsonArray().forEach(jsonElement -> { + String name = jsonElement.getAsJsonObject().get("name").getAsString(); + project.getLogger().debug("Adding dependency ({}) from installer JSON", name); + + ExternalModuleDependency modDep = (ExternalModuleDependency) project.getDependencies().create(name); + modDep.setTransitive(false); + loaderDepsConfig.getDependencies().add(modDep); + + // TODO: work around until https://github.com/FabricMC/Mixin/pull/60 and https://github.com/FabricMC/fabric-mixin-compile-extensions/issues/14 is fixed. + if (!IdeaUtils.isIdeaSync() && extension.getMixin().getUseLegacyMixinAp().get()) { + apDepsConfig.getDependencies().add(modDep); + } + + // If user choose to use dependencyResolutionManagement, then they should declare + // these repositories manually in the settings file. + if (jsonElement.getAsJsonObject().has("url") && !project.getGradle().getPlugins().hasPlugin(LoomRepositoryPlugin.class)) { + String url = jsonElement.getAsJsonObject().get("url").getAsString(); + long count = project.getRepositories().stream().filter(artifactRepository -> artifactRepository instanceof MavenArtifactRepository) + .map(artifactRepository -> (MavenArtifactRepository) artifactRepository) + .filter(mavenArtifactRepository -> mavenArtifactRepository.getUrl().toString().equalsIgnoreCase(url)).count(); + + if (count == 0) { + project.getRepositories().maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(jsonElement.getAsJsonObject().get("url").getAsString())); + } + } + }); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java index 12eec24e..b41b39bf 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java @@ -24,58 +24,18 @@ package net.fabricmc.loom.configuration; -import java.io.File; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -import com.google.gson.JsonObject; import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.LoomGradlePlugin; -import net.fabricmc.loom.LoomRepositoryPlugin; -import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; import net.fabricmc.loom.configuration.mods.ModConfigurationRemapper; -import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.SourceRemapper; -import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.service.SharedServiceManager; public class LoomDependencyManager { public void handleDependencies(Project project, SharedServiceManager serviceManager) { - List afterTasks = new ArrayList<>(); - project.getLogger().info(":setting up loom dependencies"); LoomGradleExtension extension = LoomGradleExtension.get(project); - if (extension.getInstallerData() == null) { - //If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking - project.getLogger().info("Searching through modCompileClasspath for installer JSON"); - final Configuration configuration = project.getConfigurations().getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH); - - for (Dependency dependency : configuration.getAllDependencies()) { - for (File input : configuration.files(dependency)) { - JsonObject jsonObject = readInstallerJson(input); - - if (jsonObject != null) { - if (extension.getInstallerData() != null) { - project.getLogger().info("Found another installer JSON in, ignoring it! " + input); - continue; - } - - project.getLogger().info("Found installer JSON in " + input); - extension.setInstallerData(new InstallerData(dependency.getVersion(), jsonObject)); - handleInstallerJson(jsonObject, project); - } - } - } - } - SourceRemapper sourceRemapper = new SourceRemapper(project, serviceManager, true); String mappingsIdentifier = extension.getMappingConfiguration().mappingsIdentifier(); @@ -84,61 +44,7 @@ public class LoomDependencyManager { sourceRemapper.remapAll(); if (extension.getInstallerData() == null) { - project.getLogger().warn("fabric-installer.json not found in classpath!"); + project.getLogger().warn("fabric-installer.json not found in dependencies!"); } - - for (Runnable runnable : afterTasks) { - runnable.run(); - } - } - - public static JsonObject readInstallerJson(File file) { - try { - byte[] bytes = ZipUtils.unpackNullable(file.toPath(), "fabric-installer.json"); - - if (bytes == null) { - return null; - } - - return LoomGradlePlugin.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), JsonObject.class); - } catch (Exception e) { - throw new RuntimeException("Failed to try and read installer json from " + file, e); - } - } - - private static void handleInstallerJson(JsonObject jsonObject, Project project) { - LoomGradleExtension extension = LoomGradleExtension.get(project); - - JsonObject libraries = jsonObject.get("libraries").getAsJsonObject(); - Configuration loaderDepsConfig = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES); - Configuration apDepsConfig = project.getConfigurations().getByName("annotationProcessor"); - - libraries.get("common").getAsJsonArray().forEach(jsonElement -> { - String name = jsonElement.getAsJsonObject().get("name").getAsString(); - - ExternalModuleDependency modDep = (ExternalModuleDependency) project.getDependencies().create(name); - modDep.setTransitive(false); - loaderDepsConfig.getDependencies().add(modDep); - - // TODO: work around until https://github.com/FabricMC/Mixin/pull/60 and https://github.com/FabricMC/fabric-mixin-compile-extensions/issues/14 is fixed. - if (!IdeaUtils.isIdeaSync() && extension.getMixin().getUseLegacyMixinAp().get()) { - apDepsConfig.getDependencies().add(modDep); - } - - project.getLogger().debug("Loom adding " + name + " from installer JSON"); - - // If user choose to use dependencyResolutionManagement, then they should declare - // these repositories manually in the settings file. - if (jsonElement.getAsJsonObject().has("url") && !project.getGradle().getPlugins().hasPlugin(LoomRepositoryPlugin.class)) { - String url = jsonElement.getAsJsonObject().get("url").getAsString(); - long count = project.getRepositories().stream().filter(artifactRepository -> artifactRepository instanceof MavenArtifactRepository) - .map(artifactRepository -> (MavenArtifactRepository) artifactRepository) - .filter(mavenArtifactRepository -> mavenArtifactRepository.getUrl().toString().equalsIgnoreCase(url)).count(); - - if (count == 0) { - project.getRepositories().maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(jsonElement.getAsJsonObject().get("url").getAsString())); - } - } - }); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java b/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java new file mode 100644 index 00000000..024a35c8 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java @@ -0,0 +1,103 @@ +/* + * 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.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Predicate; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import com.google.gson.JsonObject; +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.configuration.InstallerData; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; + +public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequirements, @Nullable InstallerData installerData) { + private static final String INSTALLER_PATH = "fabric-installer.json"; + private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; + private static final String MANIFEST_REMAP_KEY = "Fabric-Loom-Remap"; + + public static ArtifactMetadata create(ArtifactRef artifact) throws IOException { + boolean isFabricMod; + RemapRequirements remapRequirements = RemapRequirements.DEFAULT; + InstallerData installerData = null; + + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(artifact.path())) { + isFabricMod = FabricModJsonFactory.containsMod(fs); + final Path manifestPath = fs.getPath(MANIFEST_PATH); + + if (Files.exists(manifestPath)) { + final var manifest = new Manifest(new ByteArrayInputStream(Files.readAllBytes(manifestPath))); + final Attributes mainAttributes = manifest.getMainAttributes(); + final String value = mainAttributes.getValue(MANIFEST_REMAP_KEY); + + if (value != null) { + // Support opting into and out of remapping with "Fabric-Loom-Remap" manifest entry + remapRequirements = Boolean.parseBoolean(value) ? RemapRequirements.OPT_IN : RemapRequirements.OPT_OUT; + } + } + + final Path installerPath = fs.getPath(INSTALLER_PATH); + + if (isFabricMod && Files.exists(installerPath)) { + final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(Files.readString(installerPath, StandardCharsets.UTF_8), JsonObject.class); + installerData = new InstallerData(artifact.version(), jsonObject); + } + } + + return new ArtifactMetadata(isFabricMod, remapRequirements, installerData); + } + + public boolean shouldRemap() { + return remapRequirements().getShouldRemap().test(this); + } + + public enum RemapRequirements { + DEFAULT(ArtifactMetadata::isFabricMod), + OPT_IN(true), + OPT_OUT(false); + + private final Predicate shouldRemap; + + RemapRequirements(Predicate shouldRemap) { + this.shouldRemap = shouldRemap; + } + + RemapRequirements(final boolean shouldRemap) { + this.shouldRemap = artifactMetadata -> shouldRemap; + } + + private Predicate getShouldRemap() { + return shouldRemap; + } + } +} 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 e25f7c79..d5321428 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java @@ -57,7 +57,6 @@ import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.OperatingSystem; import net.fabricmc.loom.util.SourceRemapper; -import net.fabricmc.loom.util.fmj.FabricModJsonFactory; import net.fabricmc.loom.util.service.SharedServiceManager; @SuppressWarnings("UnstableApiUsage") @@ -89,7 +88,24 @@ public class ModConfigurationRemapper { final List modDependencies = new ArrayList<>(); for (ArtifactRef artifact : resolveArtifacts(project, sourceConfig)) { - if (!FabricModJsonFactory.isModJar(artifact.path())) { + final ArtifactMetadata artifactMetadata; + + try { + artifactMetadata = ArtifactMetadata.create(artifact); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read metadata from" + artifact.path(), e); + } + + if (artifactMetadata.installerData() != null) { + if (extension.getInstallerData() != null) { + project.getLogger().info("Found another installer JSON in ({}), ignoring", artifact.path()); + } else { + project.getLogger().info("Applying installer data from {}", artifact.path()); + artifactMetadata.installerData().applyToProject(project); + } + } + + if (!artifactMetadata.shouldRemap()) { artifact.applyToConfiguration(project, targetConfig); continue; } diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java index 917ef123..bbd22916 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java @@ -41,6 +41,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.gradle.SourceSetHelper; @@ -124,4 +125,8 @@ public final class FabricModJsonFactory { public static boolean isModJar(Path input) { return ZipUtils.contains(input, FABRIC_MOD_JSON); } + + public static boolean containsMod(FileSystemUtil.Delegate fs) { + return Files.exists(fs.getPath(FABRIC_MOD_JSON)); + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy b/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy index 8905380c..5acf17ee 100644 --- a/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy @@ -49,7 +49,7 @@ class FabricAPIBenchmark implements GradleProjectTestTrait { def timeStart = new Date() - def result = gradle.run(tasks: ["clean", "build"], args: ["--parallel", "-x", "check", "-x", "test", "-x", ":fabric-data-generation-api-v1:runDatagen", "-x", "javadoc"]) + def result = gradle.run(tasks: ["clean"], args: []) def timeStop = new Date() TimeDuration duration = TimeCategory.minus(timeStop, timeStart) diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ArtifactMetadataTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ArtifactMetadataTest.groovy new file mode 100644 index 00000000..cc499ecb --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ArtifactMetadataTest.groovy @@ -0,0 +1,140 @@ +/* + * 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.ArtifactMetadata +import net.fabricmc.loom.configuration.mods.ArtifactRef +import net.fabricmc.loom.util.FileSystemUtil +import spock.lang.Specification + +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path +import java.util.jar.Attributes +import java.util.jar.Manifest + +import static net.fabricmc.loom.configuration.mods.ArtifactMetadata.RemapRequirements.* + +class ArtifactMetadataTest extends Specification { + def "is fabric mod"() { + given: + def zip = createZip(entries) + when: + def metadata = createMetadata(zip) + then: + isMod == metadata.isFabricMod() + where: + isMod | entries + false | ["hello.json": "{}"] // None Mod jar + true | ["fabric.mod.json": "{}"] // Fabric mod + } + + def "remap requirements"() { + given: + def zip = createZip(entries) + when: + def metadata = createMetadata(zip) + then: + requirements == metadata.remapRequirements() + where: + requirements | entries + DEFAULT | ["fabric.mod.json": "{}"] // Default + OPT_OUT | ["META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "false")] // opt-out + OPT_IN | ["META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "true")] // opt-in + } + + def "Should Remap" () { + given: + def zip = createZip(entries) + when: + def metadata = createMetadata(zip) + def result = metadata.shouldRemap() + then: + result == shouldRemap + where: + shouldRemap | entries + false | ["hello.json": "{}"] // None Mod jar + true | ["fabric.mod.json": "{}"] // Fabric mod + false | ["fabric.mod.json": "{}", + "META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "false")] // Fabric mod opt-out + true | ["fabric.mod.json": "{}", + "META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "true")] // Fabric mod opt-in + false | ["hello.json": "{}", + "META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "false")] // None opt-out + true | ["hello.json": "{}", + "META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "true")] // None opt-int + false | ["hello.json": "{}", + "META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "broken")]// Invalid format + false | ["hello.json": "{}", + "META-INF/MANIFEST.MF": manifest("Something", "Hello")] // Invalid format + } + + def "Installer data"() { + given: + def zip = createZip(entries) + when: + def metadata = createMetadata(zip) + then: + isLoader == (metadata.installerData() != null) + where: + isLoader | entries + true | ["fabric.mod.json": "{}", "fabric-installer.json": "{}"] // Fabric mod, with installer data + false | ["fabric.mod.json": "{}"] // Fabric mod, no installer data + } + + private static ArtifactMetadata createMetadata(Path zip) { + return ArtifactMetadata.create(createArtifact(zip)) + } + + private static ArtifactRef createArtifact(Path zip) { + return new ArtifactRef.FileArtifactRef(zip, "net.fabric", "loom-test", "1.0") + } + + private static Path createZip(Map entries) { + def file = Files.createTempFile("loom-test", ".zip") + Files.delete(file) + + FileSystemUtil.getJarFileSystem(file, true).withCloseable { zip -> + entries.forEach { path, value -> + def fsPath = zip.getPath(path) + def fsPathParent = fsPath.getParent() + if (fsPathParent != null) Files.createDirectories(fsPathParent) + Files.writeString(fsPath, value, StandardCharsets.UTF_8) + } + } + + return file + } + + private String manifest(String key, String value) { + def manifest = new Manifest() + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0") + manifest.getMainAttributes().putValue(key, value) + + def out = new ByteArrayOutputStream() + manifest.write(out) + return out.toString(StandardCharsets.UTF_8) + } +}