Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic version support #55

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm being pedantic but I would surround the ands in their own parenthesis for clarity's sake

|| 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;
}

// See if it's a dynamic version and match against all entries
if (artifactCoordinate.isDynamicVersion()) {
for (var entry : externallyProvided.entrySet()) {
if (artifactCoordinate.matchesVersion(entry.getKey().version())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check should be matches artifact first and version second

&& entry.getKey().withVersion(artifactCoordinate.version()).equals(artifactCoordinate)) {
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 @@ -138,4 +138,19 @@ public MavenCoordinate withVersion(String version) {
version
);
}

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

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);
}
}
}
Loading