44import net .neoforged .jarjar .metadata .MetadataIOHandler ;
55import net .neoforged .moddevgradle .internal .jarjar .JarJarArtifacts ;
66import net .neoforged .moddevgradle .internal .jarjar .ResolvedJarJarArtifact ;
7+ import net .neoforged .moddevgradle .internal .utils .FileUtils ;
78import org .gradle .api .DefaultTask ;
9+ import org .gradle .api .GradleException ;
810import org .gradle .api .Project ;
911import org .gradle .api .artifacts .Configuration ;
1012import org .gradle .api .attributes .Bundling ;
1820import org .gradle .api .model .ObjectFactory ;
1921import org .gradle .api .plugins .JavaPluginExtension ;
2022import org .gradle .api .tasks .InputFiles ;
23+ import org .gradle .api .tasks .Internal ;
2124import org .gradle .api .tasks .Nested ;
2225import org .gradle .api .tasks .OutputDirectory ;
2326import org .gradle .api .tasks .SkipWhenEmpty ;
3134import java .nio .file .Files ;
3235import java .nio .file .Path ;
3336import java .nio .file .StandardOpenOption ;
37+ import java .util .ArrayList ;
3438import java .util .Collection ;
3539import java .util .List ;
3640import java .util .regex .Matcher ;
@@ -57,12 +61,16 @@ public abstract class JarJar extends DefaultTask {
5761 @ OutputDirectory
5862 public abstract DirectoryProperty getOutputDirectory ();
5963
64+ @ Internal
65+ public abstract DirectoryProperty getBuildDirectory ();
66+
6067 private final FileSystemOperations fileSystemOperations ;
6168
6269 @ Inject
6370 public JarJar (FileSystemOperations fileSystemOperations ) {
6471 this .fileSystemOperations = fileSystemOperations ;
6572 this .getOutputDirectory ().convention (getProject ().getLayout ().getBuildDirectory ().dir ("generated/" + getName ()));
73+ this .getBuildDirectory ().convention (getProject ().getLayout ().getBuildDirectory ());
6674 setGroup (DEFAULT_GROUP );
6775 }
6876
@@ -100,16 +108,60 @@ public static TaskProvider<JarJar> registerWithConfiguration(Project project, St
100108 }
101109
102110 @ TaskAction
103- protected void run () {
104- List <ResolvedJarJarArtifact > includedJars = getJarJarArtifacts ().getResolvedArtifacts ().get ();
111+ protected void run () throws IOException {
112+ List <ResolvedJarJarArtifact > includedJars = new ArrayList <>( getJarJarArtifacts ().getResolvedArtifacts ().get () );
105113 fileSystemOperations .delete (spec -> spec .delete (getOutputDirectory ()));
106114
115+ var artifactFiles = new ArrayList <>(includedJars .stream ().map (ResolvedJarJarArtifact ::getFile ).toList ());
116+ // Now we have to handle pure file collection dependencies that do not have artifact ids
117+ for (var file : getInputFiles ()) {
118+ if (!artifactFiles .contains (file )) {
119+ // Determine the module-name of the file, which is also what Java will use as the unique key
120+ // when it tries to load the file. No two files can have the same module name, so it seems
121+ // like a fitting key for conflict resolution by JiJ.
122+ var moduleName = FileUtils .getExplicitJavaModuleName (file );
123+ if (moduleName .isEmpty ()) {
124+ throw new GradleException ("Cannot embed local file dependency " + file + " because it has no explicit Java module name.\n " +
125+ "Please set either 'Automatic-Module-Name' in the Jar manifest, or make it an explicit Java module.\n " +
126+ "This ensures that your file does not conflict with another mods library that has the same or a similar filename." );
127+ }
128+
129+ // Create a hashcode to use as a version
130+ var hashCode = FileUtils .hashFile (file , "MD5" );
131+ includedJars .add (new ResolvedJarJarArtifact (
132+ file ,
133+ file .getName (),
134+ hashCode ,
135+ "[" + hashCode + "]" ,
136+ "" ,
137+ moduleName .get ()
138+ ));
139+ artifactFiles .add (file );
140+ }
141+ }
142+
107143 // Only copy metadata if not empty, always delete
108144 if (!includedJars .isEmpty ()) {
109145 fileSystemOperations .copy (spec -> {
110146 spec .into (getOutputDirectory ().dir ("META-INF/jarjar" ));
111- spec .from (includedJars . stream (). map ( ResolvedJarJarArtifact :: getFile ) .toArray ());
147+ spec .from (artifactFiles .toArray ());
112148 for (var includedJar : includedJars ) {
149+ // Warn if any included jar is using the cursemaven group.
150+ // We know that cursemaven versions are not comparable, and the same artifact might also be
151+ // available under a "normal" group and artifact from another Maven repository.
152+ // JIJ will not correctly detect the conflicting file at runtime if another mod uses the normal Maven dependency.
153+ // For a description of Curse Maven, see https://www.cursemaven.com/
154+ if ("curse.maven" .equals (includedJar .getGroup ())) {
155+ getLogger ().warn ("Embedding dependency {}:{}:{} from cursemaven using JiJ is likely to cause conflicts at runtime when other mods include the same library from a normal Maven repository." ,
156+ includedJar .getGroup (), includedJar .getArtifact (), includedJar .getVersion ());
157+ }
158+ // Same with the Modrinth official maven (see https://support.modrinth.com/en/articles/8801191-modrinth-maven)
159+ // While actual versions can be used, version IDs (which are random strings) can also be used
160+ else if ("maven.modrinth" .equals (includedJar .getGroup ())) {
161+ getLogger ().warn ("Embedding dependency {}:{}:{} from Modrinth Maven using JiJ is likely to cause conflicts at runtime when other mods include the same library from a normal Maven repository." ,
162+ includedJar .getGroup (), includedJar .getArtifact (), includedJar .getVersion ());
163+ }
164+
113165 var originalName = includedJar .getFile ().getName ();
114166 var embeddedName = includedJar .getEmbeddedFilename ();
115167 if (!originalName .equals (embeddedName )) {
@@ -123,8 +175,8 @@ protected void run() {
123175
124176 @ SuppressWarnings ("ResultOfMethodCallIgnored" )
125177 private Path writeMetadata (List <ResolvedJarJarArtifact > includedJars ) {
126- final Path metadataPath = getJarJarMetadataPath ();
127- final Metadata metadata = createMetadata (includedJars );
178+ var metadataPath = getJarJarMetadataPath ();
179+ var metadata = createMetadata (includedJars );
128180
129181 try {
130182 metadataPath .toFile ().getParentFile ().mkdirs ();
0 commit comments