From 63ebc35e1d4300f2bcc6b7aaba78a0345173a091 Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Sat, 4 May 2024 12:16:32 -0500 Subject: [PATCH] Improve how `include` configuration works (#1080) * Initial work on better include configuration * Remove unused members * Substantially simplify * Only process configuration once * Hopefully fix tests * Make platform dependencies work again * Fix edge case where include or super configuration has withDependencies action * Fix including subproject/composite jars and add test * Remove needless following of external result * Change priority for module location discovery * Fix failing test * Apply suggestions from code review Co-authored-by: modmuss --------- Co-authored-by: modmuss --- .../build/nesting/IncludedJarFactory.java | 250 ----------------- .../nesting/NestableJarGenerationTask.java | 252 ++++++++++++++++++ .../configuration/LoomConfigurations.java | 47 +++- .../net/fabricmc/loom/task/RemapJarTask.java | 9 +- .../loom/task/RemapTaskConfiguration.java | 7 + .../net/fabricmc/loom/util/Constants.java | 8 + .../integration/CompositeBuildTest.groovy | 53 ++++ .../test/integration/MultiProjectTest.groovy | 2 +- .../test/integration/SemVerParsingTest.groovy | 21 +- .../projects/compositeBuild/build.gradle | 16 ++ .../compositeBuild/external/build.gradle | 9 + .../main/java/external/SomeExternalClass.java | 4 + .../src/main/resources/fabric.mod.json | 9 + .../projects/compositeBuild/settings.gradle | 9 + .../src/main/resources/fabric.mod.json | 9 + .../compositeBuild/subproject/build.gradle | 9 + .../java/subproject/SomeSubprojectClass.java | 4 + .../src/main/resources/fabric.mod.json | 9 + 18 files changed, 455 insertions(+), 272 deletions(-) delete mode 100644 src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java create mode 100644 src/main/java/net/fabricmc/loom/build/nesting/NestableJarGenerationTask.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/integration/CompositeBuildTest.groovy create mode 100644 src/test/resources/projects/compositeBuild/build.gradle create mode 100644 src/test/resources/projects/compositeBuild/external/build.gradle create mode 100644 src/test/resources/projects/compositeBuild/external/src/main/java/external/SomeExternalClass.java create mode 100644 src/test/resources/projects/compositeBuild/external/src/main/resources/fabric.mod.json create mode 100644 src/test/resources/projects/compositeBuild/settings.gradle create mode 100644 src/test/resources/projects/compositeBuild/src/main/resources/fabric.mod.json create mode 100644 src/test/resources/projects/compositeBuild/subproject/build.gradle create mode 100644 src/test/resources/projects/compositeBuild/subproject/src/main/java/subproject/SomeSubprojectClass.java create mode 100644 src/test/resources/projects/compositeBuild/subproject/src/main/resources/fabric.mod.json diff --git a/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java b/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java deleted file mode 100644 index 43bffc29..00000000 --- a/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2021 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.build.nesting; - -import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.Locale; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.google.common.collect.Sets; -import com.google.common.hash.Hashing; -import com.google.gson.JsonObject; -import org.apache.commons.io.FileUtils; -import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.ProjectDependency; -import org.gradle.api.artifacts.ResolvedArtifact; -import org.gradle.api.artifacts.ResolvedConfiguration; -import org.gradle.api.artifacts.ResolvedDependency; -import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.bundling.AbstractArchiveTask; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.LoomGradlePlugin; -import net.fabricmc.loom.task.RemapTaskConfiguration; -import net.fabricmc.loom.util.ZipReprocessorUtil; -import net.fabricmc.loom.util.fmj.FabricModJsonFactory; - -public final class IncludedJarFactory { - private final Project project; - private static final Logger LOGGER = LoggerFactory.getLogger(IncludedJarFactory.class); - private static final String SEMVER_REGEX = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"; - private static final Pattern SEMVER_PATTERN = Pattern.compile(SEMVER_REGEX); - - public IncludedJarFactory(Project project) { - this.project = project; - } - - public Provider getNestedJars(final Configuration configuration) { - return project.provider(() -> { - final ConfigurableFileCollection files = project.files(); - final Set visited = Sets.newHashSet(); - - files.from(getProjectDeps(configuration, visited)); - files.from(getFileDeps(configuration, visited)); - files.builtBy(configuration.getBuildDependencies()); - return files; - }); - } - - private ConfigurableFileCollection getFileDeps(Configuration configuration, Set visited) { - final ConfigurableFileCollection files = project.files(); - - final ResolvedConfiguration resolvedConfiguration = configuration.getResolvedConfiguration(); - final Set dependencies = resolvedConfiguration.getFirstLevelModuleDependencies(); - - for (ResolvedDependency dependency : dependencies) { - if (!visited.add(dependency.getModuleGroup() + ":" + dependency.getModuleName() + ":" + dependency.getModuleVersion())) { - continue; - } - - for (ResolvedArtifact artifact : dependency.getModuleArtifacts()) { - Metadata metadata = new Metadata( - dependency.getModuleGroup(), - dependency.getModuleName(), - dependency.getModuleVersion(), - artifact.getClassifier() - ); - - files.from(getNestableJar(artifact.getFile(), metadata)); - } - } - - return files; - } - - private ConfigurableFileCollection getProjectDeps(Configuration configuration, Set visited) { - final ConfigurableFileCollection files = project.files(); - - for (Dependency dependency : configuration.getDependencies()) { - if (dependency instanceof ProjectDependency projectDependency) { - if (!visited.add(dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion())) { - continue; - } - - // Get the outputs of the project - final Project dependentProject = projectDependency.getDependencyProject(); - - Collection remapJarTasks = dependentProject.getTasksByName(RemapTaskConfiguration.REMAP_JAR_TASK_NAME, false); - Collection jarTasks = dependentProject.getTasksByName(JavaPlugin.JAR_TASK_NAME, false); - - if (remapJarTasks.isEmpty() && jarTasks.isEmpty()) { - throw new UnsupportedOperationException("%s does not have a remapJar or jar task, cannot nest it".formatted(dependentProject.getName())); - } - - for (Task task : remapJarTasks.isEmpty() ? jarTasks : remapJarTasks) { - if (task instanceof AbstractArchiveTask archiveTask) { - final Metadata metadata = new Metadata( - projectDependency.getGroup(), - projectDependency.getName(), - projectDependency.getVersion(), - archiveTask.getArchiveClassifier().getOrNull() - ); - - Provider provider = archiveTask.getArchiveFile().map(regularFile -> getNestableJar(regularFile.getAsFile(), metadata)); - files.from(provider); - files.builtBy(task); - } else { - throw new UnsupportedOperationException("Cannot nest none AbstractArchiveTask task: " + task.getName()); - } - } - } - } - - return files; - } - - private File getNestableJar(final File input, final Metadata metadata) { - if (FabricModJsonFactory.isModJar(input)) { - // Input is a mod, nothing needs to be done. - return input; - } - - LoomGradleExtension extension = LoomGradleExtension.get(project); - String childName = "temp/modprocessing/%s/%s/%s/%s".formatted(metadata.group().replace(".", "/"), metadata.name(), metadata.version(), input.getName()); - File tempDir = new File(extension.getFiles().getProjectBuildCache(), childName); - - if (!tempDir.exists()) { - tempDir.mkdirs(); - } - - File tempFile = new File(tempDir, input.getName()); - - if (tempFile.exists() && FabricModJsonFactory.isModJar(tempFile)) { - return tempFile; - } - - try { - FileUtils.copyFile(input, tempFile); - ZipReprocessorUtil.appendZipEntry(tempFile.toPath(), "fabric.mod.json", generateModForDependency(metadata).getBytes(StandardCharsets.UTF_8)); - } catch (IOException e) { - throw new UncheckedIOException("Failed to add dummy mod while including %s".formatted(input), e); - } - - return tempFile; - } - - // Generates a barebones mod for a dependency - private static String generateModForDependency(Metadata metadata) { - String modId = (metadata.group() + "_" + metadata.name() + metadata.classifier()) - .replaceAll("\\.", "_") - .toLowerCase(Locale.ENGLISH); - - // Fabric Loader can't handle modIds longer than 64 characters - if (modId.length() > 64) { - String hash = Hashing.sha256() - .hashString(modId, StandardCharsets.UTF_8) - .toString(); - modId = modId.substring(0, 50) + hash.substring(0, 14); - } - - final JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("schemaVersion", 1); - - jsonObject.addProperty("id", modId); - String version = getVersion(metadata); - jsonObject.addProperty("version", version); - jsonObject.addProperty("name", metadata.name()); - - JsonObject custom = new JsonObject(); - custom.addProperty("fabric-loom:generated", true); - jsonObject.add("custom", custom); - - return LoomGradlePlugin.GSON.toJson(jsonObject); - } - - private record Metadata(String group, String name, String version, @Nullable String classifier) { - @Override - public String classifier() { - if (classifier == null) { - return ""; - } else { - return "_" + classifier; - } - } - - @Override - public String toString() { - return group + ":" + name + ":" + version + classifier(); - } - } - - private static String getVersion(Metadata metadata) { - String version = metadata.version(); - - if (validSemVer(version)) { - return version; - } - - if (version.endsWith(".Final") || version.endsWith(".final")) { - String trimmedVersion = version.substring(0, version.length() - 6); - - if (validSemVer(trimmedVersion)) { - return trimmedVersion; - } - } - - LOGGER.warn("({}) is not valid semver for dependency {}", version, metadata); - return version; - } - - private static boolean validSemVer(String version) { - Matcher matcher = SEMVER_PATTERN.matcher(version); - return matcher.find(); - } -} diff --git a/src/main/java/net/fabricmc/loom/build/nesting/NestableJarGenerationTask.java b/src/main/java/net/fabricmc/loom/build/nesting/NestableJarGenerationTask.java new file mode 100644 index 00000000..365a630f --- /dev/null +++ b/src/main/java/net/fabricmc/loom/build/nesting/NestableJarGenerationTask.java @@ -0,0 +1,252 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.build.nesting; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.hash.Hashing; +import com.google.gson.JsonObject; +import org.apache.commons.io.FileUtils; +import org.gradle.api.DefaultTask; +import org.gradle.api.artifacts.ArtifactView; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.component.ComponentIdentifier; +import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.gradle.api.artifacts.result.ResolvedVariantResult; +import org.gradle.api.artifacts.type.ArtifactTypeDefinition; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.util.ZipReprocessorUtil; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; + +public abstract class NestableJarGenerationTask extends DefaultTask { + private static final Logger LOGGER = LoggerFactory.getLogger(NestableJarGenerationTask.class); + private static final String SEMVER_REGEX = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"; + private static final Pattern SEMVER_PATTERN = Pattern.compile(SEMVER_REGEX); + + @InputFiles + @PathSensitive(PathSensitivity.NAME_ONLY) + protected abstract ConfigurableFileCollection getJars(); + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + + @Input + protected abstract MapProperty getJarIds(); + + @TaskAction + void makeNestableJars() { + Map fabricModJsons = new HashMap<>(); + getJarIds().get().forEach((fileName, metadata) -> { + fabricModJsons.put(fileName, generateModForDependency(metadata)); + }); + + try { + File targetDir = getOutputDirectory().get().getAsFile(); + FileUtils.deleteDirectory(targetDir); + targetDir.mkdirs(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + getJars().forEach(file -> { + File targetFile = getOutputDirectory().file(file.getName()).get().getAsFile(); + targetFile.delete(); + String fabricModJson = Objects.requireNonNull(fabricModJsons.get(file.getName()), "Could not generate fabric.mod.json for included dependency "+file.getName()); + makeNestableJar(file, targetFile, fabricModJson); + }); + } + + public void from(Configuration configuration) { + ArtifactView artifacts = configuration.getIncoming().artifactView(config -> { + config.attributes( + attr -> attr.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE) + ); + }); + getJars().from(artifacts.getFiles()); + dependsOn(configuration); + getJarIds().set(artifacts.getArtifacts().getResolvedArtifacts().map(set -> { + Map map = new HashMap<>(); + set.forEach(artifact -> { + ResolvedVariantResult variant = artifact.getVariant(); + + ComponentIdentifier id = variant.getOwner(); + Metadata moduleLocation = null; + + if (id instanceof ModuleComponentIdentifier moduleIdentifier) { + moduleLocation = new Metadata( + moduleIdentifier.getGroup(), + moduleIdentifier.getModule(), + moduleIdentifier.getVersion(), + null + ); + } + + List capabilityLocations = variant.getCapabilities().stream() + .map(capability -> new Metadata(capability.getGroup(), capability.getName(), capability.getVersion(), null)) + .toList(); + + if (!capabilityLocations.isEmpty() && (moduleLocation == null || !capabilityLocations.contains(moduleLocation))) { + moduleLocation = capabilityLocations.get(0); + } + + if (moduleLocation == null) { + throw new RuntimeException("Attempted to nest artifact " + id + " which is not a module component and has no capabilities."); + } else if (moduleLocation.version == null) { + throw new RuntimeException("Attempted to nest artifact " + id + " which has no version"); + } + + String group = moduleLocation.group; + String name = moduleLocation.name; + String version = moduleLocation.version; + String classifier = null; + + if (artifact.getFile().getName().startsWith(name + "-" + version + "-")) { + String rest = artifact.getFile().getName().substring(name.length() + version.length() + 2); + int dotIndex = rest.indexOf('.'); + + if (dotIndex != -1) { + classifier = rest.substring(0, dotIndex); + } + } + + Metadata metadata = new Metadata(group, name, version, classifier); + map.put(artifact.getFile().getName(), metadata); + }); + return map; + })); + } + + // Generates a barebones mod for a dependency + private static String generateModForDependency(Metadata metadata) { + String modId = (metadata.group() + "_" + metadata.name() + metadata.classifier()) + .replaceAll("\\.", "_") + .toLowerCase(Locale.ENGLISH); + + // Fabric Loader can't handle modIds longer than 64 characters + if (modId.length() > 64) { + String hash = Hashing.sha256() + .hashString(modId, StandardCharsets.UTF_8) + .toString(); + modId = modId.substring(0, 50) + hash.substring(0, 14); + } + + final JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("schemaVersion", 1); + + jsonObject.addProperty("id", modId); + String version = getVersion(metadata); + jsonObject.addProperty("version", version); + jsonObject.addProperty("name", metadata.name()); + + JsonObject custom = new JsonObject(); + custom.addProperty("fabric-loom:generated", true); + jsonObject.add("custom", custom); + + return LoomGradlePlugin.GSON.toJson(jsonObject); + } + + private static String getVersion(Metadata metadata) { + String version = metadata.version(); + + if (validSemVer(version)) { + return version; + } + + if (version.endsWith(".Final") || version.endsWith(".final")) { + String trimmedVersion = version.substring(0, version.length() - 6); + + if (validSemVer(trimmedVersion)) { + return trimmedVersion; + } + } + + LOGGER.warn("({}) is not valid semver for dependency {}", version, metadata); + return version; + } + + private static boolean validSemVer(String version) { + Matcher matcher = SEMVER_PATTERN.matcher(version); + return matcher.find(); + } + + private void makeNestableJar(final File input, final File output, final String modJsonFile) { + try { + FileUtils.copyFile(input, output); + } catch (IOException e) { + throw new UncheckedIOException("Failed to copy mod file %s".formatted(input), e); + } + + if (FabricModJsonFactory.isModJar(input)) { + // Input is a mod, nothing needs to be done. + return; + } + + try { + ZipReprocessorUtil.appendZipEntry(output.toPath(), "fabric.mod.json", modJsonFile.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException("Failed to add dummy mod while including %s".formatted(input), e); + } + } + + protected record Metadata(String group, String name, String version, @Nullable String classifier) implements Serializable { + @Override + public String classifier() { + if (classifier == null) { + return ""; + } else { + return "_" + classifier; + } + } + + @Override + public String toString() { + return group + ":" + name + ":" + version + classifier(); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java index 16d98b73..9d265443 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java @@ -24,14 +24,25 @@ package net.fabricmc.loom.configuration; +import java.util.ArrayList; +import java.util.List; + import javax.inject.Inject; import org.gradle.api.NamedDomainObjectProvider; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.attributes.Bundling; +import org.gradle.api.attributes.Category; +import org.gradle.api.attributes.HasConfigurableAttributes; +import org.gradle.api.attributes.LibraryElements; +import org.gradle.api.attributes.Usage; import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.Provider; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.util.Constants; @@ -81,8 +92,40 @@ public abstract class LoomConfigurations implements Runnable { registerNonTransitive(Constants.Configurations.LOADER_DEPENDENCIES, Role.RESOLVABLE); registerNonTransitive(Constants.Configurations.MINECRAFT, Role.NONE); - // We don't need to make this non-transitive due to the way we resolve it. Also, doing so would break platform dependencies. - register(Constants.Configurations.INCLUDE, Role.RESOLVABLE); + + Provider include = register(Constants.Configurations.INCLUDE, Role.NONE); + register(Constants.Configurations.INCLUDE_INTERNAL, Role.RESOLVABLE).configure(configuration -> { + configuration.getDependencies().addAllLater(getProject().provider(() -> { + List dependencies = new ArrayList<>(); + + for (Dependency dependency : include.get().getIncoming().getDependencies()) { + if (dependency instanceof HasConfigurableAttributes hasAttributes) { + Category category = hasAttributes.getAttributes().getAttribute(Category.CATEGORY_ATTRIBUTE); + + if (category != null && (category.getName().equals(Category.ENFORCED_PLATFORM) || category.getName().equals(Category.REGULAR_PLATFORM))) { + dependencies.add(dependency); + continue; + } else if (dependency instanceof ModuleDependency moduleDependency) { + ModuleDependency copy = moduleDependency.copy(); + copy.setTransitive(false); + dependencies.add(copy); + continue; + } + } + + dependencies.add(dependency); + } + + return dependencies; + })); + configuration.attributes(attributes -> { + attributes.attribute(Usage.USAGE_ATTRIBUTE, getProject().getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); + attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, getProject().getObjects().named(LibraryElements.class, LibraryElements.JAR)); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, getProject().getObjects().named(Category.class, Category.LIBRARY)); + attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, getProject().getObjects().named(Bundling.class, Bundling.EXTERNAL)); + }); + }); + registerNonTransitive(Constants.Configurations.MAPPING_CONSTANTS, Role.RESOLVABLE); register(Constants.Configurations.NAMED_ELEMENTS, Role.CONSUMABLE).configure(configuration -> { diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index 54dd46bb..d986c6ed 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -40,7 +40,6 @@ import java.util.stream.Stream; import javax.inject.Inject; import com.google.gson.JsonObject; -import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; @@ -53,6 +52,7 @@ import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -62,8 +62,8 @@ import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerRemapper; import net.fabricmc.accesswidener.AccessWidenerWriter; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.build.nesting.IncludedJarFactory; import net.fabricmc.loom.build.nesting.JarNester; +import net.fabricmc.loom.build.nesting.NestableJarGenerationTask; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.configuration.mods.ArtifactMetadata; import net.fabricmc.loom.extension.MixinExtension; @@ -111,8 +111,9 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { getAddNestedDependencies().convention(true).finalizeValueOnRead(); getOptimizeFabricModJson().convention(false).finalizeValueOnRead(); - Configuration includeConfiguration = configurations.getByName(Constants.Configurations.INCLUDE); - getNestedJars().from(new IncludedJarFactory(getProject()).getNestedJars(includeConfiguration)); + TaskProvider processIncludeJars = getProject().getTasks().named(Constants.Task.PROCESS_INCLUDE_JARS, NestableJarGenerationTask.class); + getNestedJars().from(getProject().fileTree(processIncludeJars.get().getOutputDirectory())); + getNestedJars().builtBy(processIncludeJars); getUseMixinAP().set(LoomGradleExtension.get(getProject()).getMixin().getUseLegacyMixinAp()); diff --git a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java index a6c57e10..58df34b1 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java +++ b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java @@ -40,6 +40,7 @@ import org.gradle.api.tasks.bundling.AbstractArchiveTask; import org.gradle.jvm.tasks.Jar; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.build.nesting.NestableJarGenerationTask; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.gradle.GradleUtils; import net.fabricmc.loom.util.gradle.SourceSetHelper; @@ -71,6 +72,12 @@ public abstract class RemapTaskConfiguration implements Runnable { return; } + Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE_INTERNAL); + getTasks().register(Constants.Task.PROCESS_INCLUDE_JARS, NestableJarGenerationTask.class, task -> { + task.from(includeConfiguration); + task.getOutputDirectory().set(getProject().getLayout().getBuildDirectory().dir(task.getName())); + }); + Action remapJarTaskAction = task -> { final AbstractArchiveTask jarTask = getTasks().named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class).get(); diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index 7b7157d0..ff94b011 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -46,6 +46,7 @@ public class Constants { public static final String MOD_COMPILE_CLASSPATH = "modCompileClasspath"; public static final String MOD_COMPILE_CLASSPATH_MAPPED = "modCompileClasspathMapped"; public static final String INCLUDE = "include"; + public static final String INCLUDE_INTERNAL = "includeInternal"; public static final String MINECRAFT = "minecraft"; public static final String MINECRAFT_COMPILE_LIBRARIES = "minecraftLibraries"; @@ -113,6 +114,13 @@ public class Constants { } } + public static final class Task { + public static final String PROCESS_INCLUDE_JARS = "processIncludeJars"; + + private Task() { + } + } + public static final class CustomModJsonKeys { public static final String INJECTED_INTERFACE = "loom:injected_interfaces"; public static final String PROVIDED_JAVADOC = "loom:provided_javadoc"; diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/CompositeBuildTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/CompositeBuildTest.groovy new file mode 100644 index 00000000..08f87e2c --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/CompositeBuildTest.groovy @@ -0,0 +1,53 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2018-2021 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.integration + +import spock.lang.Specification +import spock.lang.Unroll + +import net.fabricmc.loom.test.util.GradleProjectTestTrait + +import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class CompositeBuildTest extends Specification implements GradleProjectTestTrait { + @Unroll + def "build (gradle #version)"() { + setup: + def gradle = gradleProject(project: "compositeBuild", version: version) + + when: + def result = gradle.run(tasks: ["remapJar"]) + + then: + result.task(":remapJar").outcome == SUCCESS + + gradle.hasOutputZipEntry("compositeBuild.jar", "META-INF/jars/external-0.0.1.jar") + gradle.hasOutputZipEntry("compositeBuild.jar", "META-INF/jars/subproject-0.0.1.jar") + + where: + version << STANDARD_TEST_VERSIONS + } +} \ No newline at end of file diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/MultiProjectTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/MultiProjectTest.groovy index c5669df0..da866213 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/MultiProjectTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/MultiProjectTest.groovy @@ -53,7 +53,7 @@ class MultiProjectTest extends Specification implements GradleProjectTestTrait { gradle.hasOutputZipEntry("multiproject-1.0.0.jar", "META-INF/jars/example-1.0.0.jar") gradle.hasOutputZipEntry("multiproject-1.0.0.jar", "META-INF/jars/core-1.0.0.jar") - gradle.hasOutputZipEntry("multiproject-1.0.0.jar", "META-INF/jars/fabric-api-base-0.3.0+f74f7c7d7d.jar") + gradle.hasOutputZipEntry("multiproject-1.0.0.jar", "META-INF/jars/fabric-api-base-0.2.1+9354966b7d.jar") where: version << STANDARD_TEST_VERSIONS diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/SemVerParsingTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/SemVerParsingTest.groovy index 1ddda621..6e9ccd4f 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/SemVerParsingTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/SemVerParsingTest.groovy @@ -27,17 +27,14 @@ package net.fabricmc.loom.test.integration import spock.lang.Specification import spock.lang.Unroll -import net.fabricmc.loom.build.nesting.IncludedJarFactory +import net.fabricmc.loom.build.nesting.NestableJarGenerationTask import net.fabricmc.loom.test.util.GradleProjectTestTrait class SemVerParsingTest extends Specification implements GradleProjectTestTrait { @Unroll def "test valid Semantic Versioning strings"() { - given: - IncludedJarFactory includedJarFactory = new IncludedJarFactory(null) - expect: - includedJarFactory.validSemVer(version) == true + NestableJarGenerationTask.validSemVer(version) == true where: version | _ @@ -50,11 +47,8 @@ class SemVerParsingTest extends Specification implements GradleProjectTestTrait @Unroll def "test non-Semantic Versioning strings"() { - given: - IncludedJarFactory includedJarFactory = new IncludedJarFactory(null) - expect: - includedJarFactory.validSemVer(version) == false + NestableJarGenerationTask.validSemVer(version) == false where: version | _ @@ -66,15 +60,12 @@ class SemVerParsingTest extends Specification implements GradleProjectTestTrait @Unroll def "test '.Final' suffixed SemVer"() { - given: - IncludedJarFactory includedJarFactory = new IncludedJarFactory(null) - expect: - includedJarFactory.getVersion(metadata) == expectedVersion + NestableJarGenerationTask.getVersion(metadata) == expectedVersion where: metadata | expectedVersion - new IncludedJarFactory.Metadata("group", "name", "1.0.0.Final", null) | "1.0.0" - new IncludedJarFactory.Metadata("group", "name", "2.5.3.final", null) | "2.5.3" + new NestableJarGenerationTask.Metadata("group", "name", "1.0.0.Final", null) | "1.0.0" + new NestableJarGenerationTask.Metadata("group", "name", "2.5.3.final", null) | "2.5.3" } } diff --git a/src/test/resources/projects/compositeBuild/build.gradle b/src/test/resources/projects/compositeBuild/build.gradle new file mode 100644 index 00000000..0fd841a2 --- /dev/null +++ b/src/test/resources/projects/compositeBuild/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'fabric-loom' +} + +repositories { + mavenCentral() +} + +dependencies { + minecraft 'com.mojang:minecraft:1.18.2' + mappings 'net.fabricmc:yarn:1.18.2+build.1:v2' + modImplementation 'net.fabricmc:fabric-loader:0.13.3' + + include 'external:external' + include project(':subproject') +} diff --git a/src/test/resources/projects/compositeBuild/external/build.gradle b/src/test/resources/projects/compositeBuild/external/build.gradle new file mode 100644 index 00000000..f868232a --- /dev/null +++ b/src/test/resources/projects/compositeBuild/external/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' +} + +group = 'external' +version = '0.0.1' + +dependencies { +} \ No newline at end of file diff --git a/src/test/resources/projects/compositeBuild/external/src/main/java/external/SomeExternalClass.java b/src/test/resources/projects/compositeBuild/external/src/main/java/external/SomeExternalClass.java new file mode 100644 index 00000000..d7ae334c --- /dev/null +++ b/src/test/resources/projects/compositeBuild/external/src/main/java/external/SomeExternalClass.java @@ -0,0 +1,4 @@ +package external; + +public class SomeExternalClass { +} \ No newline at end of file diff --git a/src/test/resources/projects/compositeBuild/external/src/main/resources/fabric.mod.json b/src/test/resources/projects/compositeBuild/external/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..ca71d444 --- /dev/null +++ b/src/test/resources/projects/compositeBuild/external/src/main/resources/fabric.mod.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "id": "external", + "version": "0.0.0", + + "name": "Example Mod", + "description": "This is an example description! Tell everyone what your mod is about!", + "environment": "*" +} diff --git a/src/test/resources/projects/compositeBuild/settings.gradle b/src/test/resources/projects/compositeBuild/settings.gradle new file mode 100644 index 00000000..54f0dc88 --- /dev/null +++ b/src/test/resources/projects/compositeBuild/settings.gradle @@ -0,0 +1,9 @@ +rootProject.name = "compositeBuild" + +includeBuild('external') { + dependencySubstitution { + substitute module('external:external') using project(':') + } +} + +include('subproject') diff --git a/src/test/resources/projects/compositeBuild/src/main/resources/fabric.mod.json b/src/test/resources/projects/compositeBuild/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..33bad8c2 --- /dev/null +++ b/src/test/resources/projects/compositeBuild/src/main/resources/fabric.mod.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "id": "modid", + "version": "0.0.0", + + "name": "Example Mod", + "description": "This is an example description! Tell everyone what your mod is about!", + "environment": "*" +} diff --git a/src/test/resources/projects/compositeBuild/subproject/build.gradle b/src/test/resources/projects/compositeBuild/subproject/build.gradle new file mode 100644 index 00000000..27d941ef --- /dev/null +++ b/src/test/resources/projects/compositeBuild/subproject/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' +} + +group = 'subproject' +version = '0.0.1' + +dependencies { +} \ No newline at end of file diff --git a/src/test/resources/projects/compositeBuild/subproject/src/main/java/subproject/SomeSubprojectClass.java b/src/test/resources/projects/compositeBuild/subproject/src/main/java/subproject/SomeSubprojectClass.java new file mode 100644 index 00000000..d1814a16 --- /dev/null +++ b/src/test/resources/projects/compositeBuild/subproject/src/main/java/subproject/SomeSubprojectClass.java @@ -0,0 +1,4 @@ +package external; + +public class SomeSubprojectClass { +} \ No newline at end of file diff --git a/src/test/resources/projects/compositeBuild/subproject/src/main/resources/fabric.mod.json b/src/test/resources/projects/compositeBuild/subproject/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..a836812f --- /dev/null +++ b/src/test/resources/projects/compositeBuild/subproject/src/main/resources/fabric.mod.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "id": "subproject", + "version": "0.0.0", + + "name": "Example Mod", + "description": "This is an example description! Tell everyone what your mod is about!", + "environment": "*" +}