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
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,28 @@ public Artifact get(MavenCoordinate mavenCoordinate) throws IOException {
return externalArtifact;
}

// Yet another special case: dynamic versions!
// Used in 1.12.1, for example. And yes, this will be very slow.
if (mavenCoordinate.isDynamicVersion()) {
var availableVersions = MavenMetadata.gatherVersions(
downloadManager,
repositoryBaseUrls,
mavenCoordinate.groupId(),
mavenCoordinate.artifactId()
);
for (var availableVersion : availableVersions) {
if (mavenCoordinate.matchesVersion(availableVersion.version())) {
var concreteMavenCoordinate = mavenCoordinate.withVersion(availableVersion.version());
return get(concreteMavenCoordinate, availableVersion.repositoryUrl());
}
}
}

var finalLocation = artifactsCache.resolve(mavenCoordinate.toRelativeRepositoryPath());

// Special case: NeoForge reference libraries that are only available via the Mojang download server
if (mavenCoordinate.groupId().equals("com.mojang") && mavenCoordinate.artifactId().equals("logging")) {
if ((mavenCoordinate.groupId().equals("com.mojang") && mavenCoordinate.artifactId().equals("logging"))
|| (mavenCoordinate.groupId().equals("net.minecraft") && mavenCoordinate.artifactId().equals("launchwrapper"))) {
return get(mavenCoordinate, MINECRAFT_LIBRARIES_URI);
}

Expand Down Expand Up @@ -219,8 +237,8 @@ public Artifact downloadFromManifest(MinecraftVersionManifest versionManifest, S
var downloadSpec = versionManifest.downloads().get(type);
if (downloadSpec == null) {
throw new IllegalArgumentException("Minecraft version manifest " + versionManifest.id()
+ " does not declare a download for " + type + ". Available: "
+ versionManifest.downloads().keySet());
+ " does not declare a download for " + type + ". Available: "
+ versionManifest.downloads().keySet());
}

var extension = FilenameUtil.getExtension(downloadSpec.uri().getPath());
Expand Down Expand Up @@ -261,11 +279,22 @@ public interface DownloadAction {
private Artifact getFromExternalManifest(MavenCoordinate artifactCoordinate) {
artifactCoordinate = normalizeExtension(artifactCoordinate);

// Try direct match first
var artifact = externallyProvided.get(artifactCoordinate);
if (artifact != null) {
return artifact;
}

// Find any manifest entry for the same group/artifact/classifier and evaluate if it matches a dynamic version constraint
if (artifactCoordinate.isDynamicVersion()) {
for (var entry : externallyProvided.entrySet()) {
if (artifactCoordinate.equalsWithoutVersion(entry.getKey())
&& artifactCoordinate.matchesVersion(entry.getKey().version())) {
return entry.getValue();
}
}
}

// Fall back to looking up a wildcard version for dependency replacement in includeBuild scenarios
if (!"*".equals(artifactCoordinate.version())) {
artifact = externallyProvided.get(artifactCoordinate.withVersion("*"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package net.neoforged.neoform.runtime.artifacts;

import net.neoforged.neoform.runtime.downloads.DownloadManager;
import net.neoforged.neoform.runtime.utils.Logger;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;

/**
* Support class for querying maven metadata from a remote repository.
* The format is documented here: https://maven.apache.org/repositories/metadata.html
* We only deal with A-level metadata since we're interested in listing the versions of
* a specific artifact.
*/
final class MavenMetadata {
private static final Logger LOG = Logger.create();

private MavenMetadata() {
}

static List<AvailableVersion> gatherVersions(DownloadManager downloadManager,
List<URI> repositoryBaseUrls,
String groupId,
String artifactId) throws IOException {
var versions = new ArrayList<AvailableVersion>();
for (var repositoryBaseUrl : repositoryBaseUrls) {
versions.addAll(gatherVersions(downloadManager, repositoryBaseUrl, groupId, artifactId));
}
return versions;
}

static List<AvailableVersion> gatherVersions(DownloadManager downloadManager,
URI repositoryBaseUrl,
String groupId,
String artifactId) throws IOException {
var metadataUri = repositoryBaseUrl.toString();
if (!metadataUri.endsWith("/")) {
metadataUri += "/";
}
metadataUri += groupId.replace('.', '/') + "/" + artifactId + "/maven-metadata.xml";

byte[] metadataContent;

var tempFile = Files.createTempFile("maven-metadata", ".xml");
try {
Files.deleteIfExists(tempFile); // The downloader should assume it does not exist yet
downloadManager.download(URI.create(metadataUri), tempFile);
metadataContent = Files.readAllBytes(tempFile);
} catch (FileNotFoundException fnf) {
return List.of(); // Repository doesn't have artifact
} finally {
Files.deleteIfExists(tempFile);
}

try (var in = new ByteArrayInputStream(metadataContent)) {
var result = new ArrayList<AvailableVersion>();
var documentBuilder = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder();
var document = documentBuilder.parse(in).getDocumentElement();
var nodes = document.getChildNodes();
for (var i = 0; i < nodes.getLength(); i++) {
if (nodes.item(i) instanceof Element versioningEl && "versioning".equals(versioningEl.getTagName())) {
for (var versions = versioningEl.getFirstChild(); versions != null; versions = versions.getNextSibling()) {
if (versions instanceof Element versionsEl && "versions".equals(versionsEl.getTagName())) {
for (var child = versionsEl.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof Element childEl && "version".equals(childEl.getTagName())) {
result.add(new AvailableVersion(
repositoryBaseUrl,
childEl.getTextContent().trim()
));
}
}
}
}
}
}
return result;
} catch (Exception e) {
LOG.println("Failed to parse Maven metadata from " + metadataUri + ": " + e);
throw new RuntimeException(e);
}
}

record AvailableVersion(URI repositoryUrl, String version) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ public static MavenCoordinate parse(String coordinate) {
*/
public Path toRelativeRepositoryPath() {
final String fileName = artifactId + "-" + version +
(!classifier.isEmpty() ? "-" + classifier : "") +
(!extension.isEmpty() ? "." + extension : ".jar");
(!classifier.isEmpty() ? "-" + classifier : "") +
(!extension.isEmpty() ? "." + extension : ".jar");

String[] groups = groupId.split("\\.");
Path result = Paths.get(groups[0]);
Expand Down Expand Up @@ -138,4 +138,28 @@ public MavenCoordinate withVersion(String version) {
version
);
}

public boolean isDynamicVersion() {
// We only support extremely simple cases right now.
return version.endsWith("+");
}

/**
* True if this and the given coordinate have the same {@code group}, {@code artifact} and {@code classifier}.
*/
public boolean equalsWithoutVersion(MavenCoordinate other) {
return Objects.equals(groupId, other.groupId)
&& Objects.equals(artifactId, other.artifactId)
&& Objects.equals(classifier, other.classifier);
}

public boolean matchesVersion(String version) {
// "+" acts as a prefix match according to Gradle
// https://docs.gradle.org/current/userguide/dependency_versions.html#sec:single-version-declarations
if (this.version.endsWith("+")) {
return version.startsWith(this.version.substring(0, this.version.length() - 1));
} else {
return this.version.equals(version);
}
}
}