From 40062b8c834557afec31489feb3c69c2bb6bbf5e Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Wed, 8 Jul 2026 21:42:52 -0400 Subject: [PATCH 1/3] Add DSL for mapping classifiers to feature variants in order to jarJar and error on currently-ill-behavior jarJar-ed artifact selectors --- README.md | 29 ++++++++ .../internal/LegacyRepositoriesPlugin.java | 7 ++ .../moddevgradle/dsl/DependencyTools.java | 66 +++++++++++++++++ .../moddevgradle/dsl/ModDevExtension.java | 6 ++ .../dsl/ModDevSettingsExtension.java | 24 ++++++ .../internal/ClassifierToFeatureRule.java | 40 ++++++++++ .../internal/RepositoriesPlugin.java | 7 ++ .../internal/utils/ProblemCapturer.java | 13 ++++ .../neoforged/moddevgradle/tasks/JarJar.java | 50 +++++++++++++ .../moddevgradle/tasks/JarJarTest.java | 74 +++++++++++++++---- 10 files changed, 301 insertions(+), 15 deletions(-) create mode 100644 src/main/java/net/neoforged/moddevgradle/dsl/DependencyTools.java create mode 100644 src/main/java/net/neoforged/moddevgradle/dsl/ModDevSettingsExtension.java create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/ClassifierToFeatureRule.java create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/utils/ProblemCapturer.java diff --git a/README.md b/README.md index ae299905..69886cf5 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,35 @@ the [Maven version range format](https://cwiki.apache.org/confluence/display/MAV | (,1.0],[1.2,) | x <= 1.0 or x >= 1.2. Multiple sets are comma-separated | | (,1.1),(1.1,) | This excludes 1.1 if it is known not to work in combination with this library | +#### Handling Classifiers + +Gradle's dependency management system does not treat classifiers (a maven feature) nicely, and prefers use of its +first-party alternative, [feature variants](https://docs.gradle.org/current/userguide/how_to_create_feature_variants_of_a_library.html). +`jarJar` supports these out of the box, so if possible you are encouraged to use them instead. However, this may not be +possible when bundling existing dependencies that only publish Maven metadata. MDG provides utilities to map classifier +dependencies to feature variants for use with `jarJar`: + +```gradle +dependencies { + jarJar(neoForge.dependencyTools.mapClassifierToFeature("org.example.group:module-name:0.1.0", "my-classifier")) + // Or, to specify version ranges more explicitly: + jarJar(neoForge.dependencyTools.mapClassifierToFeature("org.example.group:module-name", "my-classifier")) { + version { /* ... */ } + } +``` + +Internally, this makes use of a component rule that modifies the metadata of your dependency during resolution. What this +means is that if you publish a runtime dependency on that module, consumers will also need to apply the same mapping in +their own buildscripts. If this is not desired, you can `jarJar` the mapped dependency but otherwise depend on the unmapped +one: + +```gradle +dependencies { + jarJar(neoForge.dependencyTools.mapClassifierToFeature("org.example.group:module-name:0.1.0", "my-classifier")) + implementation("org.example.group:module-name:0.1.0:my-classifier") +} +``` + #### Local Files You can also include files built by other tasks in your project, for example, jar tasks of other source sets. diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyRepositoriesPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyRepositoriesPlugin.java index 20e5d2a7..f8c2c428 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyRepositoriesPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyRepositoriesPlugin.java @@ -1,6 +1,8 @@ package net.neoforged.moddevgradle.legacyforge.internal; import java.net.URI; + +import net.neoforged.moddevgradle.dsl.ModDevSettingsExtension; import net.neoforged.moddevgradle.internal.RepositoriesPlugin; import org.gradle.api.GradleException; import org.gradle.api.Plugin; @@ -21,6 +23,11 @@ public void apply(PluginAware target) { if (target instanceof Project project) { applyRepositories(project.getRepositories()); } else if (target instanceof Settings settings) { + settings.getExtensions().create( + ModDevSettingsExtension.NAME, + ModDevSettingsExtension.class, + settings + ); applyRepositories(settings.getDependencyResolutionManagement().getRepositories()); settings.getGradle().getPlugins().apply(getClass()); // Add a marker to Gradle } else if (target instanceof Gradle gradle) { diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/DependencyTools.java b/src/main/java/net/neoforged/moddevgradle/dsl/DependencyTools.java new file mode 100644 index 00000000..cbb07931 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/dsl/DependencyTools.java @@ -0,0 +1,66 @@ +package net.neoforged.moddevgradle.dsl; + +import net.neoforged.moddevgradle.internal.ClassifierToFeatureRule; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.dsl.ComponentMetadataHandler; +import org.gradle.api.artifacts.dsl.DependencyFactory; +import org.jetbrains.annotations.Nullable; + +import javax.inject.Inject; +import java.util.HashSet; +import java.util.Set; + +public abstract class DependencyTools { + private final ComponentMetadataHandler componentMetadataHandler; + private final Set existing = new HashSet<>(); + + record ExistingRuleParams(String group, String module, String classifier) {} + + @Inject + public DependencyTools(ComponentMetadataHandler componentMetadataHandler) { + this.componentMetadataHandler = componentMetadataHandler; + } + + @Inject + protected abstract DependencyFactory getDependencyFactory(); + + /** + * Creates a rule that modifies the metadata of the provided dependency during resolution to create a feature variant + * with the same artifact as the provided classifier would target. This feature will have the same dependencies as + * the main feature of the module. + * @param group the group of the module to transform + * @param module the name of the module to transform + * @param version the version of the dependency to be created + * @param classifier the classifier to wrap into a feature + * @return a dependency on the generated feature of the module + */ + public ExternalModuleDependency mapClassifierToFeature(String group, String module, @Nullable String version, String classifier) { + makeRule(group, module, classifier); + var dep = getDependencyFactory().create(group, module, version); + dep.capabilities(caps -> caps.requireCapability(group + ":" + module + "-" + classifier)); + return dep; + } + + /** + * Creates a rule that modifies the metadata of the provided dependency during resolution to create a feature variant + * with the same artifact as the provided classifier would target. This feature will have the same dependencies as + * the main feature of the module. + * @param notation the module to transform + * @param classifier the classifier to wrap into a feature + * @return a dependency on the generated feature of the module + */ + public ExternalModuleDependency mapClassifierToFeature(CharSequence notation, String classifier) { + var dummyDep = getDependencyFactory().create(notation); + var group = dummyDep.getGroup(); + var module = dummyDep.getName(); + var version = dummyDep.getVersion(); + makeRule(group, module, classifier); + return mapClassifierToFeature(group, module, version, classifier); + } + + private void makeRule(String group, String module, String classifier) { + if (existing.add(new ExistingRuleParams(group, module, classifier))) { + componentMetadataHandler.withModule(group + ":" + module, ClassifierToFeatureRule.class, config -> config.params(classifier)); + } + } +} diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java index 021719a8..5e7ae964 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java @@ -24,6 +24,7 @@ public abstract class ModDevExtension { private final Project project; private final DataFileCollection accessTransformers; private final DataFileCollection interfaceInjectionData; + private final DependencyTools dependencyTools; @Inject public ModDevExtension(Project project, @@ -32,6 +33,7 @@ public ModDevExtension(Project project, mods = project.container(ModModel.class); runs = project.container(RunModel.class, name -> project.getObjects().newInstance(RunModel.class, name, project, mods)); parchment = project.getObjects().newInstance(Parchment.class); + dependencyTools = project.getObjects().newInstance(DependencyTools.class, project.getDependencies().getComponents()); this.project = project; this.accessTransformers = accessTransformers; this.interfaceInjectionData = interfaceInjectionData; @@ -95,6 +97,10 @@ public void setInterfaceInjectionData(Object... paths) { * Default {@code false}
*/ public abstract Property getValidateAccessTransformers(); + + public DependencyTools getDependencyTools() { + return dependencyTools; + } public NamedDomainObjectContainer getMods() { return mods; diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevSettingsExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevSettingsExtension.java new file mode 100644 index 00000000..7797a323 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevSettingsExtension.java @@ -0,0 +1,24 @@ +package net.neoforged.moddevgradle.dsl; + +import org.gradle.api.initialization.Settings; +import org.gradle.api.model.ObjectFactory; + +import javax.inject.Inject; + +public abstract class ModDevSettingsExtension { + public static final String NAME = "modDev"; + + private final DependencyTools dependencyTools; + + @Inject + public ModDevSettingsExtension(Settings settings) { + this.dependencyTools = getObjects().newInstance(DependencyTools.class, settings.getDependencyResolutionManagement().getComponents()); + } + + public DependencyTools getDependencyTools() { + return this.dependencyTools; + } + + @Inject + protected abstract ObjectFactory getObjects(); +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ClassifierToFeatureRule.java b/src/main/java/net/neoforged/moddevgradle/internal/ClassifierToFeatureRule.java new file mode 100644 index 00000000..839679cf --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/ClassifierToFeatureRule.java @@ -0,0 +1,40 @@ +package net.neoforged.moddevgradle.internal; + +import org.gradle.api.Action; +import org.gradle.api.artifacts.ComponentMetadataContext; +import org.gradle.api.artifacts.ComponentMetadataRule; +import org.gradle.api.artifacts.VariantMetadata; + +import javax.inject.Inject; + +public abstract class ClassifierToFeatureRule implements ComponentMetadataRule { + private final String classifier; + + @Inject + public ClassifierToFeatureRule(String classifier) { + this.classifier = classifier; + } + + @Override + public void execute(ComponentMetadataContext context) { + var details = context.getDetails(); + Action createdVariant = variant -> { + variant.withCapabilities(capabilities -> { + for (var cap : capabilities.getCapabilities()) { + capabilities.removeCapability(cap.getGroup(), cap.getName()); + } + capabilities.addCapability(details.getId().getGroup(), details.getId().getName() + "-" + classifier, details.getId().getVersion()); + }); + variant.withFiles(files -> { + files.removeAllFiles(); + files.addFile(details.getId().getName() + "-" + details.getId().getVersion() + "-" + classifier + ".jar"); + }); + }; + // Which of these exists depends on whether the module in question publishes Gradle module metadata or just a + // maven pom. `maybeAddVariant` is lenient. + details.maybeAddVariant(classifier+"Runtime", "runtime", createdVariant); + details.maybeAddVariant(classifier+"RuntimeElements", "runtimeElements", createdVariant); + details.maybeAddVariant(classifier+"Compile", "compile", createdVariant); + details.maybeAddVariant(classifier+"ApiElements", "apiElements", createdVariant); + } +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/RepositoriesPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/RepositoriesPlugin.java index ec296a1a..acaa6d45 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/RepositoriesPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/RepositoriesPlugin.java @@ -1,6 +1,8 @@ package net.neoforged.moddevgradle.internal; import java.net.URI; + +import net.neoforged.moddevgradle.dsl.ModDevSettingsExtension; import net.neoforged.moddevgradle.internal.generated.MojangRepositoryFilter; import org.gradle.api.GradleException; import org.gradle.api.Plugin; @@ -25,6 +27,11 @@ public void apply(PluginAware target) { if (target instanceof Project project) { applyRepositories(project.getRepositories()); } else if (target instanceof Settings settings) { + settings.getExtensions().create( + ModDevSettingsExtension.NAME, + ModDevSettingsExtension.class, + settings + ); applyRepositories(settings.getDependencyResolutionManagement().getRepositories()); settings.getGradle().getPlugins().apply(getClass()); // Add a marker to Gradle } else if (target instanceof Gradle gradle) { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/utils/ProblemCapturer.java b/src/main/java/net/neoforged/moddevgradle/internal/utils/ProblemCapturer.java new file mode 100644 index 00000000..8fb31e37 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/utils/ProblemCapturer.java @@ -0,0 +1,13 @@ +package net.neoforged.moddevgradle.internal.utils; + +import org.gradle.api.problems.Problems; + +import javax.inject.Inject; + +/** + * Sometimes, you've got problems, but all you have is a Project + */ +public abstract class ProblemCapturer { + @Inject + public abstract Problems getProblems(); +} diff --git a/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java b/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java index 50e9cc01..a077c07c 100644 --- a/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java +++ b/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java @@ -16,10 +16,17 @@ import net.neoforged.moddevgradle.internal.jarjar.JarJarArtifacts; import net.neoforged.moddevgradle.internal.jarjar.ResolvedJarJarArtifact; import net.neoforged.moddevgradle.internal.utils.FileUtils; +import net.neoforged.moddevgradle.internal.utils.ProblemCapturer; +import net.neoforged.moddevgradle.internal.utils.ProblemReportingUtil; +import net.neoforged.problems.Problem; +import net.neoforged.problems.ProblemGroup; +import net.neoforged.problems.ProblemId; +import net.neoforged.problems.ProblemSeverity; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.attributes.Bundling; import org.gradle.api.attributes.Category; import org.gradle.api.attributes.LibraryElements; @@ -42,6 +49,7 @@ public abstract class JarJar extends DefaultTask { private static final String DEFAULT_GROUP = "jarjar"; + private static final ProblemGroup PROBLEM_GROUP = ProblemGroup.create("jarjar", "JarJar"); @Nested @ApiStatus.Internal @@ -84,6 +92,48 @@ public static TaskProvider registerWithConfiguration(Project project, St // as it has attributes, it could conflict with normal exposed configurations configuration.setCanBeResolved(true); configuration.setCanBeConsumed(false); + + // Use of artifact selectors within jarJar configurations is ill-advised, as it can lead to incorrect metadata + // being created (any selector for classifier/extension will not be respected in the JarJar ID) + configuration.withDependencies(dependencies -> { + dependencies.configureEach(dependency -> { + if (dependency instanceof ModuleDependency moduleDependency) { + for (var artifact : moduleDependency.getArtifacts()) { + if (artifact.getExtension() != null || artifact.getClassifier() != null) { + List issues = new ArrayList<>(); + if (artifact.getExtension() != null) { + issues.add(String.format("extension '%s'", artifact.getExtension())); + } + if (artifact.getClassifier() != null) { + issues.add(String.format("classifier '%s'", artifact.getClassifier())); + } + var errorString = String.format( + "Dependency %s artifact %s has selectors that will not be reflected in jarJar metadata", + dependency, + String.join(", ", issues) + ); + var builder = Problem.builder(ProblemId.create( + "artifact-selector", + "Artifact Selector in Dependency", + PROBLEM_GROUP + )) + .contextualLabel(errorString) + .severity(ProblemSeverity.ERROR) + .documentedAt("https://github.com/neoforged/ModDevGradle/#handling-classifiers"); + if (artifact.getExtension() == null) { + // Only applicable if a classifier is the only issue + builder.solution("Use DependencyTools#mapClassifierToFeature"); + } + ProblemReportingUtil.report( + project.getObjects().newInstance(ProblemCapturer.class).getProblems(), + builder.build() + ); + throw new RuntimeException(errorString); + } + } + } + }); + }); var javaPlugin = project.getExtensions().getByType(JavaPluginExtension.class); diff --git a/src/test/java/net/neoforged/moddevgradle/tasks/JarJarTest.java b/src/test/java/net/neoforged/moddevgradle/tasks/JarJarTest.java index 9f4ccae6..b63d085e 100644 --- a/src/test/java/net/neoforged/moddevgradle/tasks/JarJarTest.java +++ b/src/test/java/net/neoforged/moddevgradle/tasks/JarJarTest.java @@ -3,8 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.gradle.testkit.runner.TaskOutcome.NO_SOURCE; import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import java.io.File; import java.io.IOException; @@ -139,7 +138,7 @@ void testEmbeddingSubprojectUsesDefaultCapabilityCoordinate() throws Exception { version = "9.0.0" """); - var result = run(); + var result = run(false); assertEquals(SUCCESS, result.task(":jarJar").getOutcome()); assertEquals(new Metadata( List.of( @@ -180,7 +179,7 @@ void testEmbeddingSubprojectWithExplicitGroupIdSet() throws Exception { group = "net.somegroup" """); - var result = run(); + var result = run(false); assertEquals(SUCCESS, result.task(":jarJar").getOutcome()); assertEquals(new Metadata( List.of( @@ -264,15 +263,53 @@ public void testSimpleStringVersion() throws Exception { assertThat(listFiles()).containsOnly( "META-INF/jarjar/metadata.json", "META-INF/jarjar/slf4j-api-2.0.13.jar"); assertEquals(new Metadata( - List.of( - new ContainedJarMetadata( - new ContainedJarIdentifier("org.slf4j", "slf4j-api"), - new ContainedVersion(VersionRange.createFromVersionSpec("[2.0.13,)"), new DefaultArtifactVersion("2.0.13")), - "META-INF/jarjar/slf4j-api-2.0.13.jar", - false))), + List.of( + new ContainedJarMetadata( + new ContainedJarIdentifier("org.slf4j", "slf4j-api"), + new ContainedVersion(VersionRange.createFromVersionSpec("[2.0.13,)"), new DefaultArtifactVersion("2.0.13")), + "META-INF/jarjar/slf4j-api-2.0.13.jar", + false))), + readMetadata()); + } + + @Test + public void testMappedClassifier() throws Exception { + // See https://github.com/neoforged/ModDevGradle/issues/306 + var result = runWithSource(""" + dependencies { + jarJar(neoForge.dependencyTools.mapClassifierToFeature("dev.onvoid.webrtc:webrtc-java", "windows-x86_64")) { + version { + require "0.14.0" + } + } + } + """); + assertEquals(SUCCESS, result.task(":jarJar").getOutcome()); + + assertThat(listFiles()).containsOnly( + "META-INF/jarjar/metadata.json", "META-INF/jarjar/webrtc-java-0.14.0-windows-x86_64.jar"); + assertEquals(new Metadata( + List.of( + new ContainedJarMetadata( + new ContainedJarIdentifier("dev.onvoid.webrtc", "webrtc-java-windows-x86_64"), + new ContainedVersion(VersionRange.createFromVersionSpec("[0.14.0,)"), new DefaultArtifactVersion("0.14.0")), + "META-INF/jarjar/webrtc-java-0.14.0-windows-x86_64.jar", + false))), readMetadata()); } + @Test + public void testDisallowsClassifierWithoutMapping() throws Exception { + var result = runWithSource(""" + dependencies { + jarJar(implementation("dev.onvoid.webrtc:webrtc-java:0.14.0:windows-x86_64")) + } + """, true); + var expectedFailure = """ + [ERROR] jarjar:artifact-selector: Dependency DefaultExternalModuleDependency{group='dev.onvoid.webrtc', name='webrtc-java', version='0.14.0', configuration='default'} artifact extension 'jar', classifier 'windows-x86_64' has selectors that will not be reflected in jarJar metadata"""; + assertTrue(result.getOutput().contains(expectedFailure), "Output does not contain expected failure: "+expectedFailure); + } + /** * When a single version is specified, the jarjar metadata should use the real resolved version, * rather than the one the user indicated (which can get upgraded). @@ -359,6 +396,10 @@ public void testUnsupportedDynamicVersion() { } private BuildResult runWithSource(String source) throws IOException { + return runWithSource(source, false); + } + + private BuildResult runWithSource(String source, boolean expectFailure) throws IOException { writeProjectFile("settings.gradle", """ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" @@ -374,16 +415,19 @@ private BuildResult runWithSource(String source) throws IOException { } """ + source); - return run(); + return run(expectFailure); } - private BuildResult run() { - return GradleRunner.create() + private BuildResult run(boolean expectFailure) { + var runner = GradleRunner.create() .withPluginClasspath() .withProjectDir(testProjectDir) .withArguments("jarjar", "--stacktrace") - .withDebug(true) - .build(); + .withDebug(true); + if (expectFailure) { + return runner.buildAndFail(); + } + return runner.build(); } private List listFiles() throws IOException { From 1c58f42e5d4eb366cc6970ece4fcea3243902759 Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Wed, 8 Jul 2026 21:45:09 -0400 Subject: [PATCH 2/3] Apply formatting --- .../internal/LegacyRepositoriesPlugin.java | 4 +-- .../moddevgradle/dsl/DependencyTools.java | 15 +++++----- .../moddevgradle/dsl/ModDevExtension.java | 2 +- .../dsl/ModDevSettingsExtension.java | 11 ++++--- .../internal/ClassifierToFeatureRule.java | 15 +++++----- .../internal/RepositoriesPlugin.java | 4 +-- .../internal/utils/ProblemCapturer.java | 3 +- .../neoforged/moddevgradle/tasks/JarJar.java | 17 +++++------ .../moddevgradle/tasks/JarJarTest.java | 30 ++++++++++--------- 9 files changed, 47 insertions(+), 54 deletions(-) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyRepositoriesPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyRepositoriesPlugin.java index f8c2c428..b65e07e4 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyRepositoriesPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyRepositoriesPlugin.java @@ -1,7 +1,6 @@ package net.neoforged.moddevgradle.legacyforge.internal; import java.net.URI; - import net.neoforged.moddevgradle.dsl.ModDevSettingsExtension; import net.neoforged.moddevgradle.internal.RepositoriesPlugin; import org.gradle.api.GradleException; @@ -26,8 +25,7 @@ public void apply(PluginAware target) { settings.getExtensions().create( ModDevSettingsExtension.NAME, ModDevSettingsExtension.class, - settings - ); + settings); applyRepositories(settings.getDependencyResolutionManagement().getRepositories()); settings.getGradle().getPlugins().apply(getClass()); // Add a marker to Gradle } else if (target instanceof Gradle gradle) { diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/DependencyTools.java b/src/main/java/net/neoforged/moddevgradle/dsl/DependencyTools.java index cbb07931..cc8a0d3f 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/DependencyTools.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/DependencyTools.java @@ -1,26 +1,25 @@ package net.neoforged.moddevgradle.dsl; +import java.util.HashSet; +import java.util.Set; +import javax.inject.Inject; import net.neoforged.moddevgradle.internal.ClassifierToFeatureRule; import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.artifacts.dsl.ComponentMetadataHandler; import org.gradle.api.artifacts.dsl.DependencyFactory; import org.jetbrains.annotations.Nullable; -import javax.inject.Inject; -import java.util.HashSet; -import java.util.Set; - public abstract class DependencyTools { private final ComponentMetadataHandler componentMetadataHandler; private final Set existing = new HashSet<>(); - + record ExistingRuleParams(String group, String module, String classifier) {} - + @Inject public DependencyTools(ComponentMetadataHandler componentMetadataHandler) { this.componentMetadataHandler = componentMetadataHandler; } - + @Inject protected abstract DependencyFactory getDependencyFactory(); @@ -28,6 +27,7 @@ public DependencyTools(ComponentMetadataHandler componentMetadataHandler) { * Creates a rule that modifies the metadata of the provided dependency during resolution to create a feature variant * with the same artifact as the provided classifier would target. This feature will have the same dependencies as * the main feature of the module. + * * @param group the group of the module to transform * @param module the name of the module to transform * @param version the version of the dependency to be created @@ -45,6 +45,7 @@ public ExternalModuleDependency mapClassifierToFeature(String group, String modu * Creates a rule that modifies the metadata of the provided dependency during resolution to create a feature variant * with the same artifact as the provided classifier would target. This feature will have the same dependencies as * the main feature of the module. + * * @param notation the module to transform * @param classifier the classifier to wrap into a feature * @return a dependency on the generated feature of the module diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java index 5e7ae964..5cca79a2 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java @@ -97,7 +97,7 @@ public void setInterfaceInjectionData(Object... paths) { * Default {@code false}
*/ public abstract Property getValidateAccessTransformers(); - + public DependencyTools getDependencyTools() { return dependencyTools; } diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevSettingsExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevSettingsExtension.java index 7797a323..b36c85ab 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevSettingsExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevSettingsExtension.java @@ -1,24 +1,23 @@ package net.neoforged.moddevgradle.dsl; +import javax.inject.Inject; import org.gradle.api.initialization.Settings; import org.gradle.api.model.ObjectFactory; -import javax.inject.Inject; - public abstract class ModDevSettingsExtension { public static final String NAME = "modDev"; - + private final DependencyTools dependencyTools; - + @Inject public ModDevSettingsExtension(Settings settings) { this.dependencyTools = getObjects().newInstance(DependencyTools.class, settings.getDependencyResolutionManagement().getComponents()); } - + public DependencyTools getDependencyTools() { return this.dependencyTools; } - + @Inject protected abstract ObjectFactory getObjects(); } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ClassifierToFeatureRule.java b/src/main/java/net/neoforged/moddevgradle/internal/ClassifierToFeatureRule.java index 839679cf..8342ac56 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ClassifierToFeatureRule.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ClassifierToFeatureRule.java @@ -1,20 +1,19 @@ package net.neoforged.moddevgradle.internal; +import javax.inject.Inject; import org.gradle.api.Action; import org.gradle.api.artifacts.ComponentMetadataContext; import org.gradle.api.artifacts.ComponentMetadataRule; import org.gradle.api.artifacts.VariantMetadata; -import javax.inject.Inject; - public abstract class ClassifierToFeatureRule implements ComponentMetadataRule { private final String classifier; - + @Inject public ClassifierToFeatureRule(String classifier) { this.classifier = classifier; } - + @Override public void execute(ComponentMetadataContext context) { var details = context.getDetails(); @@ -32,9 +31,9 @@ public void execute(ComponentMetadataContext context) { }; // Which of these exists depends on whether the module in question publishes Gradle module metadata or just a // maven pom. `maybeAddVariant` is lenient. - details.maybeAddVariant(classifier+"Runtime", "runtime", createdVariant); - details.maybeAddVariant(classifier+"RuntimeElements", "runtimeElements", createdVariant); - details.maybeAddVariant(classifier+"Compile", "compile", createdVariant); - details.maybeAddVariant(classifier+"ApiElements", "apiElements", createdVariant); + details.maybeAddVariant(classifier + "Runtime", "runtime", createdVariant); + details.maybeAddVariant(classifier + "RuntimeElements", "runtimeElements", createdVariant); + details.maybeAddVariant(classifier + "Compile", "compile", createdVariant); + details.maybeAddVariant(classifier + "ApiElements", "apiElements", createdVariant); } } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/RepositoriesPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/RepositoriesPlugin.java index acaa6d45..83e4cf9a 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/RepositoriesPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/RepositoriesPlugin.java @@ -1,7 +1,6 @@ package net.neoforged.moddevgradle.internal; import java.net.URI; - import net.neoforged.moddevgradle.dsl.ModDevSettingsExtension; import net.neoforged.moddevgradle.internal.generated.MojangRepositoryFilter; import org.gradle.api.GradleException; @@ -30,8 +29,7 @@ public void apply(PluginAware target) { settings.getExtensions().create( ModDevSettingsExtension.NAME, ModDevSettingsExtension.class, - settings - ); + settings); applyRepositories(settings.getDependencyResolutionManagement().getRepositories()); settings.getGradle().getPlugins().apply(getClass()); // Add a marker to Gradle } else if (target instanceof Gradle gradle) { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/utils/ProblemCapturer.java b/src/main/java/net/neoforged/moddevgradle/internal/utils/ProblemCapturer.java index 8fb31e37..b4c37265 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/utils/ProblemCapturer.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/utils/ProblemCapturer.java @@ -1,8 +1,7 @@ package net.neoforged.moddevgradle.internal.utils; -import org.gradle.api.problems.Problems; - import javax.inject.Inject; +import org.gradle.api.problems.Problems; /** * Sometimes, you've got problems, but all you have is a Project diff --git a/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java b/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java index a077c07c..6af79e43 100644 --- a/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java +++ b/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java @@ -92,7 +92,7 @@ public static TaskProvider registerWithConfiguration(Project project, St // as it has attributes, it could conflict with normal exposed configurations configuration.setCanBeResolved(true); configuration.setCanBeConsumed(false); - + // Use of artifact selectors within jarJar configurations is ill-advised, as it can lead to incorrect metadata // being created (any selector for classifier/extension will not be respected in the JarJar ID) configuration.withDependencies(dependencies -> { @@ -110,13 +110,11 @@ public static TaskProvider registerWithConfiguration(Project project, St var errorString = String.format( "Dependency %s artifact %s has selectors that will not be reflected in jarJar metadata", dependency, - String.join(", ", issues) - ); + String.join(", ", issues)); var builder = Problem.builder(ProblemId.create( - "artifact-selector", - "Artifact Selector in Dependency", - PROBLEM_GROUP - )) + "artifact-selector", + "Artifact Selector in Dependency", + PROBLEM_GROUP)) .contextualLabel(errorString) .severity(ProblemSeverity.ERROR) .documentedAt("https://github.com/neoforged/ModDevGradle/#handling-classifiers"); @@ -126,13 +124,12 @@ public static TaskProvider registerWithConfiguration(Project project, St } ProblemReportingUtil.report( project.getObjects().newInstance(ProblemCapturer.class).getProblems(), - builder.build() - ); + builder.build()); throw new RuntimeException(errorString); } } } - }); + }); }); var javaPlugin = project.getExtensions().getByType(JavaPluginExtension.class); diff --git a/src/test/java/net/neoforged/moddevgradle/tasks/JarJarTest.java b/src/test/java/net/neoforged/moddevgradle/tasks/JarJarTest.java index b63d085e..f186d2a4 100644 --- a/src/test/java/net/neoforged/moddevgradle/tasks/JarJarTest.java +++ b/src/test/java/net/neoforged/moddevgradle/tasks/JarJarTest.java @@ -3,7 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.gradle.testkit.runner.TaskOutcome.NO_SOURCE; import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; @@ -263,12 +265,12 @@ public void testSimpleStringVersion() throws Exception { assertThat(listFiles()).containsOnly( "META-INF/jarjar/metadata.json", "META-INF/jarjar/slf4j-api-2.0.13.jar"); assertEquals(new Metadata( - List.of( - new ContainedJarMetadata( - new ContainedJarIdentifier("org.slf4j", "slf4j-api"), - new ContainedVersion(VersionRange.createFromVersionSpec("[2.0.13,)"), new DefaultArtifactVersion("2.0.13")), - "META-INF/jarjar/slf4j-api-2.0.13.jar", - false))), + List.of( + new ContainedJarMetadata( + new ContainedJarIdentifier("org.slf4j", "slf4j-api"), + new ContainedVersion(VersionRange.createFromVersionSpec("[2.0.13,)"), new DefaultArtifactVersion("2.0.13")), + "META-INF/jarjar/slf4j-api-2.0.13.jar", + false))), readMetadata()); } @@ -289,12 +291,12 @@ public void testMappedClassifier() throws Exception { assertThat(listFiles()).containsOnly( "META-INF/jarjar/metadata.json", "META-INF/jarjar/webrtc-java-0.14.0-windows-x86_64.jar"); assertEquals(new Metadata( - List.of( - new ContainedJarMetadata( - new ContainedJarIdentifier("dev.onvoid.webrtc", "webrtc-java-windows-x86_64"), - new ContainedVersion(VersionRange.createFromVersionSpec("[0.14.0,)"), new DefaultArtifactVersion("0.14.0")), - "META-INF/jarjar/webrtc-java-0.14.0-windows-x86_64.jar", - false))), + List.of( + new ContainedJarMetadata( + new ContainedJarIdentifier("dev.onvoid.webrtc", "webrtc-java-windows-x86_64"), + new ContainedVersion(VersionRange.createFromVersionSpec("[0.14.0,)"), new DefaultArtifactVersion("0.14.0")), + "META-INF/jarjar/webrtc-java-0.14.0-windows-x86_64.jar", + false))), readMetadata()); } @@ -307,7 +309,7 @@ public void testDisallowsClassifierWithoutMapping() throws Exception { """, true); var expectedFailure = """ [ERROR] jarjar:artifact-selector: Dependency DefaultExternalModuleDependency{group='dev.onvoid.webrtc', name='webrtc-java', version='0.14.0', configuration='default'} artifact extension 'jar', classifier 'windows-x86_64' has selectors that will not be reflected in jarJar metadata"""; - assertTrue(result.getOutput().contains(expectedFailure), "Output does not contain expected failure: "+expectedFailure); + assertTrue(result.getOutput().contains(expectedFailure), "Output does not contain expected failure: " + expectedFailure); } /** From 375d4bfe15b9bc43aee1ae9115dab3b092b47504 Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Wed, 8 Jul 2026 21:51:26 -0400 Subject: [PATCH 3/3] Fix README typo --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 69886cf5..186fe2c8 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,7 @@ dependencies { jarJar(neoForge.dependencyTools.mapClassifierToFeature("org.example.group:module-name", "my-classifier")) { version { /* ... */ } } +} ``` Internally, this makes use of a component rule that modifies the metadata of your dependency during resolution. What this