Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,36 @@ 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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;
Expand All @@ -21,6 +22,10 @@ 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) {
Expand Down
67 changes: 67 additions & 0 deletions src/main/java/net/neoforged/moddevgradle/dsl/DependencyTools.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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;

public abstract class DependencyTools {
private final ComponentMetadataHandler componentMetadataHandler;
private final Set<ExistingRuleParams> 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -96,6 +98,10 @@ public void setInterfaceInjectionData(Object... paths) {
*/
public abstract Property<Boolean> getValidateAccessTransformers();

public DependencyTools getDependencyTools() {
return dependencyTools;
}

public NamedDomainObjectContainer<ModModel> getMods() {
return mods;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.neoforged.moddevgradle.dsl;

import javax.inject.Inject;
import org.gradle.api.initialization.Settings;
import org.gradle.api.model.ObjectFactory;

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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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;

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<VariantMetadata> 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);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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;
Expand All @@ -25,6 +26,10 @@ 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.neoforged.moddevgradle.internal.utils;

import javax.inject.Inject;
import org.gradle.api.problems.Problems;

/**
* Sometimes, you've got problems, but all you have is a Project
*/
public abstract class ProblemCapturer {
@Inject
public abstract Problems getProblems();
}
47 changes: 47 additions & 0 deletions src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -85,6 +93,45 @@ public static TaskProvider<JarJar> registerWithConfiguration(Project project, St
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<String> 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);

configuration.attributes(attributes -> {
Expand Down
Loading
Loading