Skip to content

Commit b10ae0a

Browse files
committed
getting there...
1 parent e44a915 commit b10ae0a

25 files changed

+717
-145
lines changed

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import fish.cichlidmc.cichlid_gradle.util.Distribution
2+
13
plugins {
24
id("java")
35
id("fish.cichlidmc.cichlid-gradle")
@@ -16,6 +18,7 @@ repositories {
1618

1719
val mc by minecraft.creating {
1820
version = "1.21.5"
21+
distribution = Distribution.CLIENT
1922
}
2023

2124
dependencies {

plugin/src/main/java/fish/cichlidmc/cichlid_gradle/cache/CichlidCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ private CichlidCache(Path root) {
4242
}
4343

4444
public VersionStorage getVersion(String version) {
45-
return new VersionStorage(this.root.resolve("versions").resolve(version), version);
45+
return new VersionStorage(this.root.resolve("versions").resolve(version));
4646
}
4747

4848
public static CichlidCache get(Project project) {
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package fish.cichlidmc.cichlid_gradle.cache.mcmaven;
2+
3+
import fish.cichlidmc.cichlid_gradle.util.Pair;
4+
import fish.cichlidmc.cichlid_gradle.util.Utils;
5+
import fish.cichlidmc.cichlid_gradle.util.hash.Encoding;
6+
import fish.cichlidmc.cichlid_gradle.util.hash.HashAlgorithm;
7+
import fish.cichlidmc.cichlid_gradle.util.io.FileUtils;
8+
9+
import java.io.IOException;
10+
import java.nio.file.FileSystem;
11+
import java.nio.file.FileSystems;
12+
import java.nio.file.FileVisitResult;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
import java.nio.file.SimpleFileVisitor;
16+
import java.nio.file.attribute.BasicFileAttributes;
17+
import java.util.ArrayList;
18+
import java.util.Collection;
19+
import java.util.Comparator;
20+
import java.util.HashMap;
21+
import java.util.HashSet;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Objects;
25+
import java.util.stream.Collectors;
26+
27+
/**
28+
* Applies a function to every class within a jar file, and places the results in a new output jar.
29+
* Non-classes are copied as-is.
30+
*/
31+
public final class JarProcessor {
32+
public static void run(Path inputJar, Path outputJar, CacheFunction function) throws IOException {
33+
FileUtils.initEmptyZip(outputJar);
34+
35+
try (FileSystem inputFs = FileSystems.newFileSystem(inputJar); FileSystem outputFs = FileSystems.newFileSystem(outputJar)) {
36+
// jars should have 1 root
37+
Path inputRoot = Utils.getOnly(inputFs.getRootDirectories());
38+
Path outputRoot = Utils.getOnly(outputFs.getRootDirectories());
39+
40+
Input input = collectInput(inputRoot);
41+
for (ClassGroup group : input.groups.values()) {
42+
ClassGroup processed = function.apply(group);
43+
44+
addEntry(outputRoot, processed.main);
45+
for (ClassEntry inner : processed.inner) {
46+
addEntry(outputRoot, inner);
47+
}
48+
}
49+
50+
for (Path nonClass : input.nonClasses) {
51+
Path relative = inputRoot.relativize(nonClass);
52+
Path target = outputRoot.resolve(relative);
53+
FileUtils.copy(nonClass, target);
54+
}
55+
}
56+
}
57+
58+
private static void addEntry(Path outputRoot, ClassEntry entry) throws IOException {
59+
Path target = outputRoot.resolve(entry.fileName);
60+
FileUtils.copy(entry.content, target);
61+
}
62+
63+
public static Input collectInput(Path inputRoot) throws IOException {
64+
Map<String, ClassGroup> groups = new HashMap<>();
65+
Collection<Path> nonClasses = new HashSet<>();
66+
67+
Files.walkFileTree(inputRoot, new SimpleFileVisitor<>() {
68+
@Override
69+
public FileVisitResult visitFile(Path entry, BasicFileAttributes attrs) throws IOException {
70+
// net/minecraft/ClassName$Inner.(class/java)
71+
String name = inputRoot.relativize(entry).toString();
72+
73+
if (!name.endsWith(".java") && !name.endsWith(".class")) {
74+
nonClasses.add(entry);
75+
return FileVisitResult.CONTINUE;
76+
}
77+
78+
Pair<String, String> split = Utils.splitLast(name, '.');
79+
Objects.requireNonNull(split);
80+
81+
// net/minecraft/ClassName$Inner
82+
String fqn = split.left();
83+
// .(class/java)
84+
String extension = split.right();
85+
// net/minecraft/ClassName
86+
String outerName = Utils.until(fqn, '$');
87+
88+
Path outerPath = inputRoot.resolve(outerName + extension);
89+
FileUtils.assertExists(outerPath);
90+
91+
groups.compute(outerName, ($, group) -> {
92+
if (group == null) {
93+
ClassEntry main = new ClassEntry(outerName + extension, outerPath);
94+
group = new ClassGroup(main, new ArrayList<>());
95+
}
96+
97+
if (!fqn.equals(outerName)) {
98+
// this is an inner class
99+
group.inner.add(new ClassEntry(name, entry));
100+
}
101+
102+
return group;
103+
});
104+
105+
return FileVisitResult.CONTINUE;
106+
}
107+
});
108+
109+
return new Input(groups, nonClasses);
110+
}
111+
112+
public record Input(Map<String, ClassGroup> groups, Collection<Path> nonClasses) {
113+
}
114+
115+
/**
116+
* A class contained in the input jar, grouped with all of its inner classes.
117+
*/
118+
public record ClassGroup(ClassEntry main, Collection<ClassEntry> inner) {
119+
public ClassGroup(ClassEntry main) {
120+
this(main, List.of());
121+
}
122+
123+
public String hash() throws IOException {
124+
List<Path> paths = this.inner.stream()
125+
.sorted(Comparator.comparing(ClassEntry::fileName))
126+
.map(ClassEntry::content)
127+
// collect into a mutable list
128+
.collect(Collectors.toCollection(ArrayList::new));
129+
paths.addFirst(this.main.content);
130+
131+
return Encoding.BASE_FUNNY.encode(HashAlgorithm.SHA256.hash(paths));
132+
}
133+
}
134+
135+
/**
136+
* @param fileName the fully qualified class name + extension, ex. {@code net/minecraft/ClassName$Inner.class}
137+
* @param content path to the file to use as the source of this class's content
138+
*/
139+
public record ClassEntry(String fileName, Path content) {
140+
}
141+
142+
@FunctionalInterface
143+
public interface CacheFunction {
144+
ClassGroup apply(ClassGroup group) throws IOException;
145+
}
146+
}

plugin/src/main/java/fish/cichlidmc/cichlid_gradle/cache/mcmaven/McMavenResourceAccessor.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package fish.cichlidmc.cichlid_gradle.cache.mcmaven;
22

3+
import org.gradle.api.logging.Logger;
4+
import org.gradle.api.logging.Logging;
35
import org.gradle.api.resources.ResourceException;
46
import org.gradle.internal.resource.ExternalResource;
57
import org.gradle.internal.resource.ExternalResourceName;
@@ -13,6 +15,8 @@
1315
import java.net.URI;
1416

1517
public final class McMavenResourceAccessor implements ExternalResourceAccessor {
18+
private static final Logger logger = Logging.getLogger(McMavenResourceAccessor.class);
19+
1620
private final MinecraftMaven mcMaven;
1721

1822
public McMavenResourceAccessor(MinecraftMaven mcMaven) {
@@ -28,11 +32,15 @@ public <T> T withContent(ExternalResourceName location, boolean revalidate,
2832

2933
try {
3034
InputStream stream = this.mcMaven.get(uri);
31-
if (stream == null)
35+
if (stream == null) {
36+
System.out.println("nope :( " + uri);
3237
return null;
38+
}
3339

40+
System.out.println("yep :)");
3441
return action.execute(stream, this.getMetaData(location, revalidate));
3542
} catch (Exception e) {
43+
logger.error("Error while getting URL from mcmaven: {}", uri, e);
3644
throw ResourceExceptions.getFailed(uri, e);
3745
}
3846
}
@@ -46,6 +54,7 @@ public ExternalResourceMetaData getMetaData(ExternalResourceName location, boole
4654
try {
4755
return this.mcMaven.get(uri) == null ? null : new DefaultExternalResourceMetaData(uri, -1, -1);
4856
} catch (Exception e) {
57+
logger.error("Error while getting URL from mcmaven: {}", uri, e);
4958
throw ResourceExceptions.getFailed(uri, e);
5059
}
5160
}

plugin/src/main/java/fish/cichlidmc/cichlid_gradle/cache/mcmaven/MinecraftMaven.java

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
import fish.cichlidmc.cichlid_gradle.cache.Transformers;
77
import fish.cichlidmc.cichlid_gradle.cache.task.CacheTaskEnvironment;
88
import fish.cichlidmc.cichlid_gradle.cache.task.impl.AssetsTask;
9-
import fish.cichlidmc.cichlid_gradle.cache.task.impl.DecompileTask;
10-
import fish.cichlidmc.cichlid_gradle.cache.task.impl.ReassembleTask;
9+
import fish.cichlidmc.cichlid_gradle.cache.task.impl.ReassembleBinaryTask;
10+
import fish.cichlidmc.cichlid_gradle.cache.task.impl.ReassembleSourcesTask;
1111
import fish.cichlidmc.cichlid_gradle.extension.def.MinecraftDefinition;
1212
import fish.cichlidmc.cichlid_gradle.extension.def.MinecraftDefinitionImpl;
1313
import fish.cichlidmc.cichlid_gradle.util.Distribution;
14+
import fish.cichlidmc.cichlid_gradle.util.hash.Encoding;
15+
import fish.cichlidmc.cichlid_gradle.util.hash.HashAlgorithm;
1416
import fish.cichlidmc.pistonmetaparser.FullVersion;
1517
import fish.cichlidmc.pistonmetaparser.manifest.Version;
1618
import org.gradle.api.InvalidUserDataException;
@@ -28,6 +30,7 @@
2830
import java.nio.charset.StandardCharsets;
2931
import java.nio.file.Files;
3032
import java.nio.file.Path;
33+
import java.util.Optional;
3134
import java.util.regex.Matcher;
3235
import java.util.regex.Pattern;
3336

@@ -43,8 +46,8 @@ public final class MinecraftMaven {
4346
* Regex for files that could possibly be provided.
4447
*/
4548
public static final Pattern VALID_FILE = Pattern.compile(
46-
// groups: |1 | |2 | |- 3 -|
47-
"/net/minecraft/minecraft/(.+)/minecraft-(.+)\\.(pom|jar)"
49+
// groups: |1 | |2 | |- 3 -||- 4 -|
50+
"/net/minecraft/minecraft/(.+)/minecraft-(.+)\\.(pom|jar)(.md5|.sha1|.sha256|.sha512)?"
4851
);
4952

5053
private static final Logger logger = Logging.getLogger(MinecraftMaven.class);
@@ -73,9 +76,16 @@ public InputStream get(URI uri) throws IOException {
7376
Iterable<File> transformerFiles = def.resolvableTransformers().getIncoming().getFiles();
7477
Transformers transformers = new Transformers(transformerFiles, request.hash);
7578

76-
return this.getArtifact(version, def.dist(), transformers, request);
79+
InputStream stream = this.getArtifact(version, def.dist(), transformers, request);
80+
if (stream == null || request.hashAlgorithm.isEmpty())
81+
return stream;
82+
83+
HashAlgorithm hashAlgorithm = request.hashAlgorithm.get();
84+
String hash = Encoding.HEX.encode(hashAlgorithm.hash(stream.readAllBytes()));
85+
return new ByteArrayInputStream(hash.getBytes(StandardCharsets.UTF_8));
7786
}
7887

88+
7989
@Nullable
8090
private InputStream getArtifact(String versionId, Distribution dist, Transformers transformers, Request request) throws IOException {
8191
// see if this version actually exists
@@ -111,7 +121,10 @@ private InputStream getArtifact(String versionId, Distribution dist, Transformer
111121
}
112122

113123
if (needsJar) {
114-
builder.add(env -> new ReassembleTask(env, request.artifact == Artifact.SOURCES));
124+
switch (request.artifact) {
125+
case JAR -> builder.add(ReassembleBinaryTask::new);
126+
case SOURCES -> builder.add(ReassembleSourcesTask::new);
127+
}
115128
}
116129

117130
builder.start().report();
@@ -169,9 +182,22 @@ private static Request extractRequest(URI uri) {
169182
if (artifact == null)
170183
return null;
171184

172-
logger.quiet("Intercepted request for Minecraft definition {}, {}", defName, artifact);
185+
HashAlgorithm hashAlgorithm;
186+
switch (matcher.group(4)) {
187+
case ".md5" -> hashAlgorithm = HashAlgorithm.MD5;
188+
case ".sha1" -> hashAlgorithm = HashAlgorithm.SHA1;
189+
case ".sha256" -> hashAlgorithm = HashAlgorithm.SHA256;
190+
case ".sha512" -> hashAlgorithm = HashAlgorithm.SHA512;
191+
case null -> hashAlgorithm = null;
192+
default -> {
193+
// when invalid, don't process it at all
194+
return null;
195+
}
196+
}
197+
198+
logger.quiet("Intercepted request for Minecraft definition {}, {}, {}", defName, artifact, hashAlgorithm);
173199

174-
return new Request(defName, hash, artifact);
200+
return new Request(defName, hash, artifact, Optional.ofNullable(hashAlgorithm));
175201
}
176202

177203
public static String createProtocol(Project project) {
@@ -194,7 +220,7 @@ private static char filterChar(char c) {
194220
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '+' || c == '-' || c == '.') ? c : '-';
195221
}
196222

197-
private record Request(String def, String hash, Artifact artifact) {
223+
private record Request(String def, String hash, Artifact artifact, Optional<HashAlgorithm> hashAlgorithm) {
198224
// the version specified in the pom needs to match what gradle requested exactly (defName$hash) or it'll be rejected
199225
private String gradleRequestedVersion() {
200226
return this.def + '$' + this.hash;

plugin/src/main/java/fish/cichlidmc/cichlid_gradle/cache/mcmaven/PomGenerator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ public static void generate(FullVersion version, Path output) throws IOException
2323
XmlBuilder.create().add(new XmlBuilder.XmlElement("project", List.of(
2424
new XmlBuilder.XmlElement("modelVersion", "4.0.0"),
2525
new XmlBuilder.XmlElement("groupId", "net.minecraft"),
26-
new XmlBuilder.XmlElement("artifactId", VERSION_PLACEHOLDER),
27-
new XmlBuilder.XmlElement("version", version.id),
26+
new XmlBuilder.XmlElement("artifactId", "minecraft"),
27+
new XmlBuilder.XmlElement("version", VERSION_PLACEHOLDER),
2828
new XmlBuilder.XmlElement("dependencies", version.libraries.stream().flatMap(PomGenerator::makeDependencyElements).toList())
2929
))).write(output);
3030
}

plugin/src/main/java/fish/cichlidmc/cichlid_gradle/cache/storage/DecompiledClassStorage.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@ public DecompiledClassStorage(Path root) {
99
this.root = root;
1010
}
1111

12+
/**
13+
* Note that for classes containing inner classes, the hash includes the inner class bytecode, hashed alphabetically.
14+
* The inner classes are not cached individually.
15+
*/
1216
public Path get(String bytecodeHash) {
1317
return this.root.resolve(bytecodeHash + ".java");
1418
}
19+
20+
public Path linemap(String bytecodeHash) {
21+
return this.root.resolve(bytecodeHash + ".linemap");
22+
}
1523
}

plugin/src/main/java/fish/cichlidmc/cichlid_gradle/cache/storage/JarsStorage.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package fish.cichlidmc.cichlid_gradle.cache.storage;
22

3+
import fish.cichlidmc.cichlid_gradle.util.Distribution;
34
import fish.cichlidmc.distmarker.Dist;
45

56
import java.nio.file.Path;
@@ -17,6 +18,10 @@ public Path get(Dist dist) {
1718
return this.root.resolve(name + ".jar");
1819
}
1920

21+
public Path get(Distribution dist) {
22+
return this.root.resolve(dist.name + ".jar");
23+
}
24+
2025
public Path merged() {
2126
return this.root.resolve("merged.jar");
2227
}

plugin/src/main/java/fish/cichlidmc/cichlid_gradle/cache/storage/LockableStorage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public abstract class LockableStorage {
3535
protected LockableStorage(Path root) {
3636
this.root = root;
3737
this.lockFile = root.resolve(LOCK_FILE);
38-
this.lock = locks.computeIfAbsent(this.root.toAbsolutePath(), _ -> new ReentrantLock());
38+
this.lock = locks.computeIfAbsent(this.root.toAbsolutePath(), $ -> new ReentrantLock());
3939
}
4040

4141
/**

plugin/src/main/java/fish/cichlidmc/cichlid_gradle/cache/storage/VersionStorage.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@
33
import java.nio.file.Path;
44

55
public class VersionStorage extends LockableStorage {
6-
public final String version;
76

87
public final MappingsStorage mappings;
98
public final JarsStorage jars;
109
public final Path natives;
1110
public final RunTemplateStorage runs;
1211

13-
public VersionStorage(Path root, String version) {
12+
public VersionStorage(Path root) {
1413
super(root);
15-
this.version = version;
1614
this.mappings = new MappingsStorage(root.resolve("mappings"));
17-
this.jars = new JarsStorage(root.resolve("jars"), version);
15+
this.jars = new JarsStorage(root.resolve("jars"));
1816
this.natives = root.resolve("natives");
1917
this.runs = new RunTemplateStorage(root.resolve("runs"));
2018
}

0 commit comments

Comments
 (0)