Skip to content

Commit d71af0c

Browse files
modmuss50Juuxel
andauthored
Kotlin metadata annotation remapping (FabricMC#573)
Co-authored-by: Juuxel <[email protected]>
1 parent 421b41e commit d71af0c

File tree

21 files changed

+984
-46
lines changed

21 files changed

+984
-46
lines changed

.editorconfig

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[*.{gradle,java}]
1+
[*.{gradle,java,kotlin}]
22
indent_style = tab
33
ij_continuation_indent_size = 8
44
ij_java_imports_layout = $*,|,java.**,|,javax.**,|,*,|,net.fabricmc.**

build.gradle

+22-21
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ plugins {
88
id 'checkstyle'
99
id 'jacoco'
1010
id 'codenarc'
11+
id "org.jetbrains.kotlin.jvm" version "1.5.31" // Must match the version included with gradle.
1112
id "com.diffplug.spotless" version "5.14.1"
1213
}
1314

@@ -19,6 +20,11 @@ tasks.withType(JavaCompile).configureEach {
1920
it.options.release = 17
2021
}
2122

23+
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
24+
kotlinOptions {
25+
jvmTarget = "16" // Change to 17 when updating gradle/kotlin to 1.6.10
26+
}
27+
}
2228

2329
group = 'net.fabricmc'
2430
archivesBaseName = project.name
@@ -92,6 +98,11 @@ dependencies {
9298
// source code remapping
9399
implementation ('net.fabricmc:mercury:0.2.4')
94100

101+
// Kotlin
102+
implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.4.1") {
103+
transitive = false
104+
}
105+
95106
// Kapt integration
96107
compileOnly('org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0')
97108

@@ -100,7 +111,10 @@ dependencies {
100111
testImplementation('org.spockframework:spock-core:2.0-groovy-3.0') {
101112
exclude module: 'groovy-all'
102113
}
103-
testImplementation 'io.javalin:javalin:3.13.11'
114+
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
115+
testImplementation ('io.javalin:javalin:3.13.11') {
116+
exclude group: 'org.jetbrains.kotlin'
117+
}
104118
testImplementation 'net.fabricmc:fabric-installer:0.9.0'
105119

106120
compileOnly 'org.jetbrains:annotations:23.0.0'
@@ -127,6 +141,13 @@ spotless {
127141
groovy {
128142
licenseHeaderFile(rootProject.file("HEADER")).yearSeparator("-")
129143
}
144+
145+
kotlin {
146+
licenseHeaderFile(rootProject.file("HEADER")).yearSeparator("-")
147+
targetExclude("**/build.gradle.kts")
148+
targetExclude("src/test/resources/projects/*/**")
149+
ktlint()
150+
}
130151
}
131152

132153
checkstyle {
@@ -172,18 +193,6 @@ import org.w3c.dom.Document
172193
import org.w3c.dom.Element
173194
import org.w3c.dom.Node
174195

175-
def patchPom(groovy.util.Node node) {
176-
node.dependencies.first().each {
177-
def groupId = it.get("groupId").first().value().first()
178-
179-
// Patch all eclipse deps to use a strict version
180-
if (groupId.startsWith("org.eclipse.")) {
181-
def version = it.get("version").first().value().first()
182-
it.get("version").first().value = new groovy.util.NodeList(["[$version]"])
183-
}
184-
}
185-
}
186-
187196
publishing {
188197
publications {
189198
plugin(MavenPublication) { publication ->
@@ -192,10 +201,6 @@ publishing {
192201
version project.version
193202

194203
from components.java
195-
196-
pom.withXml {
197-
patchPom(asNode())
198-
}
199204
}
200205

201206
// Also publish a snapshot so people can use the latest version if they wish
@@ -205,10 +210,6 @@ publishing {
205210
version baseVersion + '-SNAPSHOT'
206211

207212
from components.java
208-
209-
pom.withXml {
210-
patchPom(asNode())
211-
}
212213
}
213214

214215
// Manually crate the plugin marker for snapshot versions

gradle.properties

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
name = fabric-loom
22
description = The Gradle plugin for Fabric
3-
url = https://github.com/FabricMC/fabric-loom
3+
url = https://github.com/FabricMC/fabric-loom
4+
5+
kotlin.stdlib.default.dependency = false

src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
4747
import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo;
4848
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
49+
import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension;
4950
import net.fabricmc.loom.util.Constants;
5051
import net.fabricmc.loom.util.TinyRemapperHelper;
5152
import net.fabricmc.loom.util.ZipUtils;
@@ -134,16 +135,22 @@ private byte[] remapAccessWidener(byte[] input, Remapper remapper) {
134135
private void remapJars(List<ModDependencyInfo> remapList) throws IOException {
135136
final LoomGradleExtension extension = LoomGradleExtension.get(project);
136137
final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
138+
final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm");
137139

138140
Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles()
139141
.stream().map(File::toPath).toArray(Path[]::new);
140142

141143
project.getLogger().lifecycle(":remapping " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ")");
142144

143-
final TinyRemapper remapper = TinyRemapper.newRemapper()
145+
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
144146
.withMappings(TinyRemapperHelper.create(mappingsProvider.getMappings(), fromM, toM, false))
145-
.renameInvalidLocals(false)
146-
.build();
147+
.renameInvalidLocals(false);
148+
149+
if (useKotlinExtension) {
150+
builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE);
151+
}
152+
153+
final TinyRemapper remapper = builder.build();
147154

148155
for (Path minecraftJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
149156
remapper.readClassPathAsync(minecraftJar);

src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.gradle.api.Project;
3838

3939
import net.fabricmc.loom.LoomGradleExtension;
40+
import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension;
4041
import net.fabricmc.loom.task.AbstractRemapJarTask;
4142
import net.fabricmc.loom.util.service.SharedService;
4243
import net.fabricmc.loom.util.service.SharedServiceManager;
@@ -52,9 +53,10 @@ public static synchronized TinyRemapperService getOrCreate(AbstractRemapJarTask
5253
final LoomGradleExtension extension = LoomGradleExtension.get(project);
5354
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
5455
final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get();
56+
final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm");
5557

5658
// Generates an id that is used to share the remapper across projects. This tasks in the remap jar task name to handle custom remap jar tasks separately.
57-
final String id = extension.getMappingsProvider().getBuildServiceName("remapJarService", from, to) + ":" + remapJarTask.getName();
59+
final String id = extension.getMappingsProvider().getBuildServiceName("remapJarService", from, to) + ":" + remapJarTask.getName() + (useKotlinExtension ? ":kotlin" : "");
5860

5961
TinyRemapperService service = sharedServiceManager.getOrCreateService(id, () -> {
6062
List<IMappingProvider> mappings = new ArrayList<>();
@@ -64,7 +66,7 @@ public static synchronized TinyRemapperService getOrCreate(AbstractRemapJarTask
6466
mappings.add(MixinMappingsService.getService(SharedServiceManager.get(project)).getMappingProvider(from, to));
6567
}
6668

67-
return new TinyRemapperService(mappings, !legacyMixin);
69+
return new TinyRemapperService(mappings, !legacyMixin, useKotlinExtension);
6870
});
6971

7072
service.readClasspath(remapJarTask.getClasspath().getFiles().stream().map(File::toPath).toList());
@@ -78,7 +80,7 @@ public static synchronized TinyRemapperService getOrCreate(AbstractRemapJarTask
7880
// Set to true once remapping has started, once set no inputs can be read.
7981
private boolean isRemapping = false;
8082

81-
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension) {
83+
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension, boolean useKotlinExtension) {
8284
TinyRemapper.Builder builder = TinyRemapper.newRemapper();
8385

8486
for (IMappingProvider provider : mappings) {
@@ -89,6 +91,10 @@ public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExte
8991
builder.extension(new net.fabricmc.tinyremapper.extension.mixin.MixinExtension());
9092
}
9193

94+
if (useKotlinExtension) {
95+
builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE);
96+
}
97+
9298
tinyRemapper = builder.build();
9399
}
94100

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* This file is part of fabric-loom, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) 2022 FabricMC
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package net.fabricmc.loom.kotlin.remapping
26+
27+
import kotlinx.metadata.jvm.KotlinClassHeader
28+
import kotlinx.metadata.jvm.KotlinClassMetadata
29+
import org.objectweb.asm.AnnotationVisitor
30+
import org.objectweb.asm.Opcodes
31+
import org.objectweb.asm.commons.Remapper
32+
import org.objectweb.asm.tree.AnnotationNode
33+
34+
class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapper, val next: AnnotationVisitor) :
35+
AnnotationNode(Opcodes.ASM9, KotlinMetadataRemappingClassVisitor.ANNOTATION_DESCRIPTOR) {
36+
37+
private var _name: String? = null
38+
39+
override fun visit(name: String?, value: Any?) {
40+
super.visit(name, value)
41+
this._name = name
42+
}
43+
44+
override fun visitEnd() {
45+
super.visitEnd()
46+
when (val metadata = readMetadata()) {
47+
is KotlinClassMetadata.Class -> {
48+
val klass = metadata.toKmClass()
49+
val writer = KotlinClassMetadata.Class.Writer()
50+
klass.accept(RemappingKmVisitors(remapper).RemappingKmClassVisitor(writer))
51+
writeClassHeader(writer.write().header)
52+
}
53+
is KotlinClassMetadata.SyntheticClass -> {
54+
val klambda = metadata.toKmLambda()
55+
56+
if (klambda != null) {
57+
val writer = KotlinClassMetadata.SyntheticClass.Writer()
58+
klambda.accept(RemappingKmVisitors(remapper).RemappingKmLambdaVisitor(writer))
59+
writeClassHeader(writer.write().header)
60+
} else {
61+
accept(next)
62+
}
63+
}
64+
// Can only be turned into KmPackage which is useless data
65+
is KotlinClassMetadata.FileFacade, is KotlinClassMetadata.MultiFileClassPart,
66+
// Can't be turned into data
67+
is KotlinClassMetadata.MultiFileClassFacade, is KotlinClassMetadata.Unknown, null -> {
68+
// do nothing
69+
accept(next)
70+
}
71+
}
72+
}
73+
74+
@Suppress("UNCHECKED_CAST")
75+
private fun readMetadata(): KotlinClassMetadata? {
76+
var kind: Int? = null
77+
var metadataVersion: IntArray? = null
78+
var data1: Array<String>? = null
79+
var data2: Array<String>? = null
80+
var extraString: String? = null
81+
var packageName: String? = null
82+
var extraInt: Int? = null
83+
84+
if (values == null) {
85+
return null
86+
}
87+
88+
values.chunked(2).forEach { (name, value) ->
89+
when (name) {
90+
"k" -> kind = value as Int
91+
"mv" -> metadataVersion = (value as List<Int>).toIntArray()
92+
"d1" -> data1 = (value as List<String>).toTypedArray()
93+
"d2" -> data2 = (value as List<String>).toTypedArray()
94+
"xs" -> extraString = value as String
95+
"pn" -> packageName = value as String
96+
"xi" -> extraInt = value as Int
97+
}
98+
}
99+
100+
val header = KotlinClassHeader(kind, metadataVersion, data1, data2, extraString, packageName, extraInt)
101+
return KotlinClassMetadata.read(header)
102+
}
103+
104+
private fun writeClassHeader(header: KotlinClassHeader) {
105+
val newNode = AnnotationNode(api, desc)
106+
newNode.values = this.values.toMutableList()
107+
108+
newNode.run {
109+
for (i in values.indices step 2) {
110+
when (values[i]) {
111+
"k" -> values[i + 1] = header.kind
112+
"mv" -> values[i + 1] = header.metadataVersion.toList()
113+
"d1" -> values[i + 1] = header.data1.toList()
114+
"d2" -> values[i + 1] = header.data2.toList()
115+
"xs" -> values[i + 1] = header.extraString
116+
"pn" -> values[i + 1] = header.packageName
117+
"xi" -> values[i + 1] = header.extraInt
118+
}
119+
}
120+
}
121+
122+
newNode.accept(next)
123+
}
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* This file is part of fabric-loom, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) 2022 FabricMC
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package net.fabricmc.loom.kotlin.remapping
26+
27+
import org.objectweb.asm.AnnotationVisitor
28+
import org.objectweb.asm.ClassVisitor
29+
import org.objectweb.asm.Opcodes
30+
import org.objectweb.asm.Type
31+
import org.objectweb.asm.commons.Remapper
32+
33+
class KotlinMetadataRemappingClassVisitor(private val remapper: Remapper, next: ClassVisitor?) : ClassVisitor(Opcodes.ASM9, next) {
34+
companion object {
35+
val ANNOTATION_DESCRIPTOR: String = Type.getDescriptor(Metadata::class.java)
36+
}
37+
38+
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
39+
var result: AnnotationVisitor? = super.visitAnnotation(descriptor, visible)
40+
41+
if (descriptor == ANNOTATION_DESCRIPTOR && result != null) {
42+
result = KotlinClassMetadataRemappingAnnotationVisitor(remapper, result)
43+
}
44+
45+
return result
46+
}
47+
}

0 commit comments

Comments
 (0)