diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 29e7366..70c88d2 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -4,4 +4,5 @@ repositories { dependencies { implementation 'de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0' + implementation 'org.apache.maven:maven-artifact:3.9.9' } diff --git a/buildSrc/src/main/groovy/net/neoforged/minecraftdependencies/GenerateModuleMetadata.groovy b/buildSrc/src/main/groovy/net/neoforged/minecraftdependencies/GenerateModuleMetadata.groovy index 34db00e..d9aa9c3 100644 --- a/buildSrc/src/main/groovy/net/neoforged/minecraftdependencies/GenerateModuleMetadata.groovy +++ b/buildSrc/src/main/groovy/net/neoforged/minecraftdependencies/GenerateModuleMetadata.groovy @@ -3,6 +3,7 @@ package net.neoforged.minecraftdependencies import groovy.json.JsonOutput import groovy.json.JsonSlurper import groovy.transform.CompileStatic +import org.apache.maven.artifact.versioning.ComparableVersion import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property @@ -62,9 +63,10 @@ abstract class GenerateModuleMetadata extends DefaultTask implements HasMinecraf metadata.variants = variants List clientDeps = [] + List clientCompileOnlyDeps = [] List serverDeps = [] Map> clientNatives = [:] - getMcDeps(serverDeps, clientDeps, clientNatives) + getMcDeps(serverDeps, clientDeps, clientCompileOnlyDeps, clientNatives) Map metaJson = new JsonSlurper().parse(meta.get().asFile) as Map int javaVersion = (metaJson.javaVersion as Map).majorVersion as int @@ -93,6 +95,7 @@ abstract class GenerateModuleMetadata extends DefaultTask implements HasMinecraf if (objcBridge) { clientDepEntries.add(depOf(objcBridge)) } + clientDepEntries.addAll(depsOf(clientCompileOnlyDeps)) variants.add([ name : 'clientCompileDependencies', @@ -243,6 +246,50 @@ abstract class GenerateModuleMetadata extends DefaultTask implements HasMinecraf } } } + + // Remove duplicates in natives + for (var libs in clientNatives.values()) { + var dedupedLibs = new LinkedHashSet(libs) + libs.clear() + libs.addAll(dedupedLibs) + } + + // Promote natives to compile time dependencies if the same G+A+Classifier is present for all platforms + // Use the lowest version. This happens on some versions (i.e. 1.18.2) where lwjgl and all such dependencies + // are used in a lower version on OSX + for (String windowNativeArtifact in clientNatives.getOrDefault(platforms[0], [])) { + var coordinate = MavenCoordinate.parse(windowNativeArtifact) + var version = new ComparableVersion(coordinate.version()) + + var inAllPlatforms = true + for (var otherPlatform in platforms.drop(1)) { + var found = false + for (var otherPlatformArtifact in clientNatives.getOrDefault(otherPlatform, [])) { + var otherPlatformCoordinate = MavenCoordinate.parse(otherPlatformArtifact) + if (coordinate.equalsIgnoringVersion(otherPlatformCoordinate)) { + found = true + var otherVersion = new ComparableVersion(otherPlatformCoordinate.version()) + if (otherVersion < version) { + version = otherVersion // use lowest + } + break + } + } + if (!found) { + inAllPlatforms = false + break + } + } + + if (inAllPlatforms) { + println("Promoting " + coordinate + " (" + version + ") from natives to compile time dependency") + coordinate = coordinate.withVersion(version.toString()) + if (client.stream().map(MavenCoordinate::parse).noneMatch { c -> c.equalsIgnoringVersion(coordinate) }) { + clientCompileOnly.add(coordinate.toString()) + } + } + } + try (def zf = new ZipFile(serverJar.get().getAsFile())) { def librariesListEntry = zf.getEntry('META-INF/libraries.list') if (librariesListEntry != null) { diff --git a/buildSrc/src/main/java/net/neoforged/minecraftdependencies/MavenCoordinate.java b/buildSrc/src/main/java/net/neoforged/minecraftdependencies/MavenCoordinate.java new file mode 100644 index 0000000..6cac638 --- /dev/null +++ b/buildSrc/src/main/java/net/neoforged/minecraftdependencies/MavenCoordinate.java @@ -0,0 +1,122 @@ +package net.neoforged.minecraftdependencies; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +/** + * Models the Maven coordinates for an artifact. + */ +public record MavenCoordinate(String groupId, String artifactId, String extension, String classifier, String version) { + public MavenCoordinate { + Objects.requireNonNull(groupId); + Objects.requireNonNull(artifactId); + Objects.requireNonNull(version); + if (extension == null) { + extension = ""; + } + if (classifier == null) { + classifier = ""; + } + } + + /** + * Valid forms: + *
    + *
  • {@code groupId:artifactId:version}
  • + *
  • {@code groupId:artifactId:version:classifier}
  • + *
  • {@code groupId:artifactId:version:classifier@extension}
  • + *
  • {@code groupId:artifactId:version@extension}
  • + *
+ */ + public static MavenCoordinate parse(String coordinate) { + var coordinateAndExt = coordinate.split("@"); + String extension = ""; + if (coordinateAndExt.length > 2) { + throw new IllegalArgumentException("Malformed Maven coordinate: " + coordinate); + } else if (coordinateAndExt.length == 2) { + extension = coordinateAndExt[1]; + coordinate = coordinateAndExt[0]; + } + + var parts = coordinate.split(":"); + if (parts.length != 3 && parts.length != 4) { + throw new IllegalArgumentException("Malformed Maven coordinate: " + coordinate); + } + + var groupId = parts[0]; + var artifactId = parts[1]; + var version = parts[2]; + var classifier = parts.length == 4 ? parts[3] : ""; + return new MavenCoordinate(groupId, artifactId, extension, classifier, version); + } + + /** + * Constructs a path relative to the root of a Maven repository pointing to the artifact expressed through + * these coordinates. + */ + public Path toRelativeRepositoryPath() { + final String fileName = artifactId + "-" + version + + (!classifier.isEmpty() ? "-" + classifier : "") + + (!extension.isEmpty() ? "." + extension : ".jar"); + + String[] groups = groupId.split("\\."); + Path result = Paths.get(groups[0]); + for (int i = 1; i < groups.length; i++) { + result = result.resolve(groups[i]); + } + + return result.resolve(artifactId).resolve(version).resolve(fileName); + } + + @Override + public String toString() { + var result = new StringBuilder(); + result.append(groupId).append(":").append(artifactId).append(":").append(version); + if (!classifier.isEmpty()) { + result.append(":").append(classifier); + } + if (!extension.isEmpty()) { + result.append("@").append(extension); + } + return result.toString(); + } + + public URI toRepositoryUri(URI baseUri) { + var originalBaseUri = baseUri.toString(); + var relativePath = toRelativeRepositoryPath().toString().replace('\\', '/'); + if (originalBaseUri.endsWith("/")) { + return URI.create(originalBaseUri + relativePath); + } else { + return URI.create(originalBaseUri + "/" + relativePath); + } + } + + public boolean equalsIgnoringVersion(MavenCoordinate other) { + return groupId.equals(other.groupId) + && artifactId.equals(other.artifactId) + && extension.equals(other.extension) + && classifier.equals(other.classifier); + } + + public MavenCoordinate withClassifier(String classifier) { + return new MavenCoordinate( + groupId, + artifactId, + extension, + classifier, + version + ); + } + + public MavenCoordinate withVersion(String version) { + return new MavenCoordinate( + groupId, + artifactId, + extension, + classifier, + version + ); + } +}