Skip to content

Commit 8ffaff4

Browse files
committed
return of the funny URL hack
1 parent 609e08a commit 8ffaff4

File tree

17 files changed

+416
-93
lines changed

17 files changed

+416
-93
lines changed

build.gradle.kts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ repositories {
1717
val mcVer = "1.21.5"
1818

1919
dependencies {
20-
implementation(minecraft.client(mcVer))
20+
implementation(minecraft.of {
21+
client()
22+
version(mcVer)
23+
})
2124

2225
compileOnly(cichlid.api("0.3.2"))
2326
cichlidRuntime(cichlid.runtime("0.3.2"))
@@ -45,3 +48,25 @@ cichlid {
4548
// }
4649
}
4750
}
51+
52+
tasks.register("resolveCompileSources") {
53+
val view = configurations.compileClasspath.get().incoming.artifactView {
54+
componentFilter {
55+
it.displayName.contains("minecraft")
56+
}
57+
58+
withVariantReselection()
59+
60+
attributes {
61+
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
62+
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
63+
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
64+
}
65+
}.artifacts
66+
67+
doLast {
68+
view.artifactFiles.forEach {
69+
println(it)
70+
}
71+
}
72+
}
Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.cichlidmc.cichlid_gradle;
22

33
import io.github.cichlidmc.cichlid_gradle.cache.CichlidCache;
4+
import io.github.cichlidmc.cichlid_gradle.cache.mcmaven.McMavenConnectorFactory;
45
import io.github.cichlidmc.cichlid_gradle.extension.CichlidExtension;
56
import io.github.cichlidmc.cichlid_gradle.extension.dep.CichlidDepsExtension;
67
import io.github.cichlidmc.cichlid_gradle.extension.dep.MinecraftDepsExtension;
@@ -11,17 +12,24 @@
1112
import org.gradle.api.Plugin;
1213
import org.gradle.api.Project;
1314
import org.gradle.api.artifacts.ConfigurationContainer;
14-
import org.gradle.api.artifacts.Dependency;
1515
import org.gradle.api.artifacts.DependencyScopeConfiguration;
16+
import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
1617

17-
import java.util.Objects;
18+
import javax.inject.Inject;
1819

19-
public abstract class CichlidGradlePlugin implements Plugin<Project> {
20+
public class CichlidGradlePlugin implements Plugin<Project> {
2021
public static final String NAME = "CichlidGradle";
2122
public static final String VERSION = "1.0-SNAPSHOT";
2223

2324
public static final String CICHLID_CONFIGURATION = "cichlid";
2425

26+
private final RepositoryTransportFactory repositoryTransportFactory;
27+
28+
@Inject
29+
public CichlidGradlePlugin(RepositoryTransportFactory repositoryTransportFactory) {
30+
this.repositoryTransportFactory = repositoryTransportFactory;
31+
}
32+
2533
@Override
2634
public void apply(Project project) {
2735
CichlidExtension.setup(project);
@@ -34,16 +42,7 @@ public void apply(Project project) {
3442
ConfigurationContainer configurations = project.getConfigurations();
3543
setupConfigurations(configurations);
3644

37-
// listen for all minecraft dependencies to ensure they're downloaded
38-
configurations.configureEach(
39-
configuration -> configuration.getDependencies().configureEach(dep -> {
40-
if (isMinecraftDependency(dep)) {
41-
String version = Objects.requireNonNull(dep.getVersion());
42-
CichlidCache cache = CichlidCache.get(project);
43-
cache.ensureVersionIsCached(version);
44-
}
45-
})
46-
);
45+
McMavenConnectorFactory.inject(this.repositoryTransportFactory, CichlidCache.get(project));
4746
}
4847

4948
private static void setupConfigurations(ConfigurationContainer configurations) {
@@ -56,10 +55,4 @@ private static void setupConfigurations(ConfigurationContainer configurations) {
5655
// add Cichlid and its dependencies to the runtime classpath
5756
configurations.named("runtimeClasspath", runtime -> runtime.extendsFrom(cichlidRuntime.get()));
5857
}
59-
60-
private static boolean isMinecraftDependency(Dependency dep) {
61-
return "net.minecraft".equals(dep.getGroup())
62-
&& CichlidCache.MINECRAFT_MODULES.contains(dep.getName())
63-
&& dep.getVersion() != null;
64-
}
6558
}

plugin/src/main/java/io/github/cichlidmc/cichlid_gradle/cache/CichlidCache.java

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,31 @@
1313

1414
/**
1515
* Interface for Cichlid's global Minecraft cache. Holds no state, just interfaces with the filesystem.
16+
* Layout is versioned, and each one can be found at {@code "root/v" + FORMAT}.
17+
* <p>
1618
* Layout:
17-
* - root
18-
* - v1
19-
* - assets
20-
* - indices
21-
* - objects
22-
* - .lock
23-
* - <version>
24-
* - natives
25-
* - mappings
26-
* - jars
27-
* - runs
19+
* <ul>
20+
* <li>
21+
* assets
22+
* <ul>
23+
* <li>indices</li>
24+
* <li>objects</li>
25+
* <li>.lock</li>
26+
* </ul>
27+
* </li>
28+
* <li>
29+
* $version
30+
* <ul>
31+
* <li>natives</li>
32+
* <li>mappings</li>
33+
* <li>jars</li>
34+
* <li>runs</li>
35+
* </ul>
36+
* </li>
37+
* </ul>
2838
*/
29-
public class CichlidCache {
39+
public final class CichlidCache {
3040
// pattern is relative to root
31-
public static final String IVY_PATTERN = "[revision]/jars/[module]-[revision].[ext]";
3241
public static final String MINECRAFT_GROUP = "net.minecraft";
3342
public static final Set<String> MINECRAFT_MODULES = Set.of(
3443
"minecraft-client", "minecraft-server", "minecraft-merged", "minecraft-bundler"
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.github.cichlidmc.cichlid_gradle.cache.mcmaven;
2+
3+
import io.github.cichlidmc.cichlid_gradle.cache.CichlidCache;
4+
import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
5+
import org.gradle.authentication.Authentication;
6+
import org.gradle.internal.resource.connector.ResourceConnectorFactory;
7+
import org.gradle.internal.resource.connector.ResourceConnectorSpecification;
8+
import org.gradle.internal.resource.transfer.DefaultExternalResourceConnector;
9+
import org.gradle.internal.resource.transfer.ExternalResourceConnector;
10+
11+
import java.lang.reflect.Field;
12+
import java.nio.file.Path;
13+
import java.util.List;
14+
import java.util.Set;
15+
16+
public final class McMavenConnectorFactory implements ResourceConnectorFactory {
17+
public static final Set<String> PROTOCOLS = Set.of(MinecraftMaven.PROTOCOL);
18+
19+
private final CichlidCache cache;
20+
21+
public McMavenConnectorFactory(CichlidCache cache) {
22+
this.cache = cache;
23+
}
24+
25+
@Override
26+
public Set<String> getSupportedProtocols() {
27+
return PROTOCOLS;
28+
}
29+
30+
@Override
31+
public Set<Class<? extends Authentication>> getSupportedAuthentication() {
32+
return Set.of();
33+
}
34+
35+
@Override
36+
public ExternalResourceConnector createResourceConnector(ResourceConnectorSpecification connectionDetails) {
37+
return new DefaultExternalResourceConnector(
38+
new McMavenResourceAccessor(this.cache),
39+
NoOpResourceLister.INSTANCE,
40+
NoOpUploader.INSTANCE
41+
);
42+
}
43+
44+
// built-in implementations use ServiceLoader, but that only works for Gradle modules.
45+
public static void inject(RepositoryTransportFactory factory, CichlidCache cache) {
46+
try {
47+
Field registeredProtocols = RepositoryTransportFactory.class.getDeclaredField("registeredProtocols");
48+
registeredProtocols.setAccessible(true);
49+
@SuppressWarnings("unchecked")
50+
List<ResourceConnectorFactory> list = (List<ResourceConnectorFactory>) registeredProtocols.get(factory);
51+
// don't add if it already exists
52+
if (list.stream().anyMatch(f -> f instanceof McMavenConnectorFactory))
53+
return;
54+
55+
list.add(new McMavenConnectorFactory(cache));
56+
} catch (ReflectiveOperationException e) {
57+
throw new RuntimeException("Error accessing Gradle internals! You probably need to update Gradle, CichlidGradle, or both", e);
58+
}
59+
}
60+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package io.github.cichlidmc.cichlid_gradle.cache.mcmaven;
2+
3+
import io.github.cichlidmc.cichlid_gradle.cache.CichlidCache;
4+
import io.github.cichlidmc.cichlid_gradle.util.FileUtils;
5+
import org.gradle.api.resources.ResourceException;
6+
import org.gradle.internal.hash.HashCode;
7+
import org.gradle.internal.resource.ExternalResource;
8+
import org.gradle.internal.resource.ExternalResourceName;
9+
import org.gradle.internal.resource.ResourceExceptions;
10+
import org.gradle.internal.resource.metadata.DefaultExternalResourceMetaData;
11+
import org.gradle.internal.resource.metadata.ExternalResourceMetaData;
12+
import org.gradle.internal.resource.transfer.ExternalResourceAccessor;
13+
import org.jetbrains.annotations.Nullable;
14+
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.net.URI;
18+
import java.nio.file.Files;
19+
import java.nio.file.Path;
20+
import java.util.Date;
21+
22+
public final class McMavenResourceAccessor implements ExternalResourceAccessor {
23+
private final MinecraftMaven mcMaven;
24+
25+
public McMavenResourceAccessor(CichlidCache cache) {
26+
this.mcMaven = new MinecraftMaven(cache);
27+
}
28+
29+
@Nullable
30+
@Override
31+
public <T> T withContent(ExternalResourceName location, boolean revalidate,
32+
ExternalResource.ContentAndMetadataAction<T> action) throws ResourceException {
33+
URI uri = location.getUri();
34+
System.out.println("checking " + uri);
35+
Path file = this.mcMaven.getFile(uri);
36+
if (file == null)
37+
return null;
38+
39+
try (InputStream stream = Files.newInputStream(file)) {
40+
return action.execute(stream, this.getMetaData(location, revalidate));
41+
} catch (IOException e) {
42+
throw ResourceExceptions.getFailed(uri, e);
43+
}
44+
}
45+
46+
@Nullable
47+
@Override
48+
public ExternalResourceMetaData getMetaData(ExternalResourceName location, boolean revalidate) throws ResourceException {
49+
URI uri = location.getUri();
50+
System.out.println("checking " + uri);
51+
Path file = this.mcMaven.getFile(uri);
52+
if (file == null)
53+
return null;
54+
55+
try {
56+
return new DefaultExternalResourceMetaData(
57+
uri,
58+
new Date(Files.getLastModifiedTime(file).toMillis()),
59+
Files.size(file),
60+
null,
61+
null,
62+
HashCode.fromString(FileUtils.sha1(file)),
63+
file.getFileName().toString(),
64+
false
65+
);
66+
} catch (IOException e) {
67+
throw ResourceExceptions.getFailed(uri, e);
68+
}
69+
}
70+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package io.github.cichlidmc.cichlid_gradle.cache.mcmaven;
2+
3+
import io.github.cichlidmc.cichlid_gradle.cache.CichlidCache;
4+
import io.github.cichlidmc.cichlid_gradle.cache.storage.JarsStorage;
5+
import io.github.cichlidmc.cichlid_gradle.util.Distribution;
6+
import org.gradle.api.logging.Logger;
7+
import org.gradle.api.logging.Logging;
8+
import org.jetbrains.annotations.Nullable;
9+
10+
import java.net.URI;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
15+
16+
/**
17+
* Intercepts requests for Minecraft files from a simulated maven repo.
18+
* Provides Minecraft jars and pom files for each version.
19+
*/
20+
public final class MinecraftMaven {
21+
public static final String PROTOCOL = "mcmaven";
22+
public static final URI ROOT = URI.create(PROTOCOL + ":///");
23+
/**
24+
* Regex for files that could possibly be provided.
25+
*/
26+
public static final Pattern VALID_FILE = Pattern.compile(
27+
// groups: |- 1 -| |2 | |- 3 -| |4 | |- 5 -|
28+
"/net/minecraft/minecraft-(client|server|merged|bundler)/(.+)/minecraft-(client|server|merged|bundler)-(.+)\\.(pom|jar)"
29+
);
30+
31+
private static final Logger logger = Logging.getLogger(MinecraftMaven.class);
32+
33+
private final CichlidCache cache;
34+
35+
public MinecraftMaven(CichlidCache cache) {
36+
this.cache = cache;
37+
}
38+
39+
/**
40+
* Find a path corresponding to the file at the requested location.
41+
* Returns null if one does not exist.
42+
*/
43+
@Nullable
44+
public Path getFile(URI uri) {
45+
Request request = extractRequest(uri);
46+
if (request == null)
47+
return null;
48+
49+
this.cache.ensureVersionIsCached(request.version);
50+
JarsStorage storage = this.cache.getVersion(request.version).jars;
51+
Path path = switch (request.type) {
52+
case JAR -> storage.path(request.dist);
53+
case SOURCES -> storage.sources(request.dist);
54+
case POM -> storage.metadata(request.dist);
55+
};
56+
57+
return Files.exists(path) ? path : null;
58+
}
59+
60+
@Nullable
61+
private static Request extractRequest(URI uri) {
62+
Matcher matcher = VALID_FILE.matcher(uri.getPath());
63+
if (!matcher.matches())
64+
return null;
65+
66+
// check that both distributions are the same
67+
String dist1 = matcher.group(1);
68+
String dist2 = matcher.group(3);
69+
if (!dist1.equals(dist2))
70+
return null;
71+
72+
Distribution dist = Distribution.ofName(dist1).orElseThrow();
73+
74+
// make sure the versions match too
75+
String version1 = matcher.group(2);
76+
String version2 = matcher.group(4);
77+
78+
// -sources will get caught in group 4 if present, check for it
79+
boolean sources = false;
80+
if (version2.endsWith("-sources")) {
81+
sources = true;
82+
version2 = version2.substring(0, version2.length() - "-sources".length());
83+
}
84+
85+
if (!version1.equals(version2))
86+
return null;
87+
88+
Request.Type type = switch (matcher.group(5)) {
89+
case "jar" -> sources ? Request.Type.SOURCES : Request.Type.JAR;
90+
case "pom" -> Request.Type.POM;
91+
default -> throw new RuntimeException("Invalid Type");
92+
};
93+
94+
logger.debug("Intercepted request for Minecraft {} {}, {}", dist, version1, type);
95+
96+
return new Request(dist, version1, type);
97+
}
98+
99+
private record Request(Distribution dist, String version, Type type) {
100+
private enum Type {
101+
JAR, SOURCES, POM
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)