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 .artifacts .Configuration ;
911import org .gradle .api .file .ConfigurableFileCollection ;
1012import org .gradle .api .file .DirectoryProperty ;
1113import org .gradle .api .file .FileSystemOperations ;
1214import org .gradle .api .model .ObjectFactory ;
1315import org .gradle .api .tasks .InputFiles ;
16+ import org .gradle .api .tasks .Internal ;
1417import org .gradle .api .tasks .Nested ;
1518import org .gradle .api .tasks .OutputDirectory ;
1619import org .gradle .api .tasks .SkipWhenEmpty ;
2225import java .nio .file .Files ;
2326import java .nio .file .Path ;
2427import java .nio .file .StandardOpenOption ;
28+ import java .util .ArrayList ;
2529import java .util .Collection ;
2630import java .util .List ;
2731import java .util .regex .Matcher ;
@@ -47,25 +51,67 @@ public abstract class JarJar extends DefaultTask {
4751 @ OutputDirectory
4852 public abstract DirectoryProperty getOutputDirectory ();
4953
54+ @ Internal
55+ public abstract DirectoryProperty getBuildDirectory ();
56+
5057 private final FileSystemOperations fileSystemOperations ;
5158
5259 @ Inject
5360 public JarJar (FileSystemOperations fileSystemOperations ) {
5461 this .fileSystemOperations = fileSystemOperations ;
5562 this .getOutputDirectory ().convention (getProject ().getLayout ().getBuildDirectory ().dir ("generated/" + getName ()));
63+ this .getBuildDirectory ().convention (getProject ().getLayout ().getBuildDirectory ());
5664 }
5765
5866 @ TaskAction
59- protected void run () {
60- List <ResolvedJarJarArtifact > includedJars = getJarJarArtifacts ().getResolvedArtifacts ().get ();
67+ protected void run () throws IOException {
68+ List <ResolvedJarJarArtifact > includedJars = new ArrayList <>( getJarJarArtifacts ().getResolvedArtifacts ().get () );
6169 fileSystemOperations .delete (spec -> spec .delete (getOutputDirectory ()));
6270
71+ var artifactFiles = new ArrayList <>(includedJars .stream ().map (ResolvedJarJarArtifact ::getFile ).toList ());
72+ // Now we have to handle pure file collection dependencies that do not have artifact ids
73+ for (var file : getInputFiles ()) {
74+ if (!artifactFiles .contains (file )) {
75+ // Determine the module-name of the file, which is also what Java will use as the unique key
76+ // when it tries to load the file. No two files can have the same module name, so it seems
77+ // like a fitting key for conflict resolution by JiJ.
78+ var moduleName = FileUtils .getExplicitJavaModuleName (file );
79+ if (moduleName .isEmpty ()) {
80+ throw new GradleException ("Cannot embed local file dependency " + file + " because it has no explicit Java module name.\n " +
81+ "Please set either 'Automatic-Module-Name' in the Jar manifest, or make it an explicit Java module.\n " +
82+ "This ensures that your file does not conflict with another mods library that has the same or a similar filename." );
83+ }
84+
85+ // Create a hashcode to use as a version
86+ var hashCode = FileUtils .hashFile (file , "MD5" );
87+ includedJars .add (new ResolvedJarJarArtifact (
88+ file ,
89+ file .getName (),
90+ hashCode ,
91+ "[" + hashCode + "]" ,
92+ "" ,
93+ moduleName .get ()
94+ ));
95+ artifactFiles .add (file );
96+ }
97+ }
98+
6399 // Only copy metadata if not empty, always delete
64100 if (!includedJars .isEmpty ()) {
65101 fileSystemOperations .copy (spec -> {
66102 spec .into (getOutputDirectory ().dir ("META-INF/jarjar" ));
67- spec .from (includedJars . stream (). map ( ResolvedJarJarArtifact :: getFile ) .toArray ());
103+ spec .from (artifactFiles .toArray ());
68104 for (var includedJar : includedJars ) {
105+ // Warn if any included jar is using the cursemaven group.
106+ // We know that cursemaven versions are not comparable, and the same artifact might also be
107+ // available under a "normal" group and artifact from another Maven repository.
108+ // JIJ will not correctly detect the conflicting file at runtime if another mod uses the normal Maven dependency.
109+ // For a description of Curse Maven, see https://www.cursemaven.com/
110+ if ("curse.maven" .equals (includedJar .getGroup ())) {
111+ 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." ,
112+ includedJar .getGroup (), includedJar .getArtifact (), includedJar .getVersion ());
113+ }
114+
69115 var originalName = includedJar .getFile ().getName ();
70116 var embeddedName = includedJar .getEmbeddedFilename ();
71117 if (!originalName .equals (embeddedName )) {
@@ -79,8 +125,8 @@ protected void run() {
79125
80126 @ SuppressWarnings ("ResultOfMethodCallIgnored" )
81127 private Path writeMetadata (List <ResolvedJarJarArtifact > includedJars ) {
82- final Path metadataPath = getJarJarMetadataPath ();
83- final Metadata metadata = createMetadata (includedJars );
128+ var metadataPath = getJarJarMetadataPath ();
129+ var metadata = createMetadata (includedJars );
84130
85131 try {
86132 metadataPath .toFile ().getParentFile ().mkdirs ();
0 commit comments