Merge remote-tracking branch 'upstream/exp/1.4' into exp/1.4

# Conflicts:
#	.gitignore
#	build.gradle
#	settings.gradle
#	src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
#	src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
#	src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java
#	src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
#	src/main/java/net/fabricmc/loom/task/service/JarManifestService.java
#	src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java
#	src/main/java/net/fabricmc/loom/util/Constants.java
This commit is contained in:
shedaniel
2023-09-23 16:11:29 +08:00
61 changed files with 1597 additions and 305 deletions

View File

@@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
version: [8.1.0-jdk17]
version: [8.3.0-jdk17]
runs-on: ubuntu-22.04
container:
image: gradle:${{ matrix.version }}
@@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-22.04
container:
image: gradle:8.1.0-jdk17
image: gradle:8.3.0-jdk17
options: --user root
steps:
@@ -51,7 +51,7 @@ jobs:
strategy:
fail-fast: false
matrix:
version: [8.1.0-jdk17]
version: [8.3.0-jdk17]
test: ${{ fromJson(needs.prepare_test_matrix.outputs.matrix) }}
runs-on: ubuntu-22.04

2
.gitignore vendored
View File

@@ -22,3 +22,5 @@
!/checkstyle.xml
!/codenarc.groovy
!/bootstrap
/src/**/generated

View File

@@ -3,8 +3,10 @@ plugins {
id 'groovy'
}
sourceCompatibility = 8
targetCompatibility = 8
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8"

View File

@@ -14,7 +14,7 @@ import org.gradle.util.GradleVersion;
*/
@SuppressWarnings("unused")
public class LoomGradlePluginBootstrap implements Plugin<PluginAware> {
private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.1";
private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.3";
private static final int MIN_SUPPORTED_MAJOR_JAVA_VERSION = 17;
private static final int MIN_SUPPORTED_MAJOR_IDEA_VERSION = 2021;

View File

@@ -8,11 +8,37 @@ plugins {
id 'checkstyle'
id 'jacoco'
id 'codenarc'
alias(libs.plugins.kotlin)
id "com.diffplug.spotless" version "6.18.0"
id "org.gradle.test-retry" version "1.5.2"
alias(libs.plugins.kotlin) apply false // Delay this so we can perform magic 🪄 first.
alias(libs.plugins.spotless)
alias(libs.plugins.retry)
}
/**
* Haha this is fun :) The Kotlin gradle plugin triggers deprecation warnings for custom configurations (https://youtrack.jetbrains.com/issue/KT-60879)
* We need to make DefaultConfiguration.isSpecialCaseOfChangingUsage think that our configurstion is a special case and not deprecated.
* We do this by setting DefaultConfiguration.roleAtCreation to LEGACY, thus isInLegacyRole will now return true.
*
* Yeah I know we can just ignore the deprecation warning, but doing so wouldn't alert us to issues when testing against pre-release Gradle versions. Also this is more fun :)
*/
def brokenConfigurations = [
"commonDecompilerRuntimeClasspath",
"fernflowerRuntimeClasspath",
"cfrRuntimeClasspath",
"vineflowerRuntimeClasspath"
]
configurations.configureEach {
if (brokenConfigurations.contains(it.name)) {
// For some reason Gradle stops us from using Groovy magic to do this, so lets do it the boring way.
def field = org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.class.getDeclaredField("roleAtCreation")
field.setAccessible(true)
field.set(it, ConfigurationRoles.LEGACY)
}
}
// Ensure we apply the Kotlin plugin after, to allow for the above configuration to take place first
apply plugin: libs.plugins.kotlin.get().pluginId
tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8"
}
@@ -24,8 +50,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
}
group = "dev.architectury"
archivesBaseName = project.name
def baseVersion = '1.3'
def baseVersion = '1.4'
def ENV = System.getenv()
def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999"
@@ -74,48 +99,82 @@ configurations.all {
}
}
sourceSets {
commonDecompiler {
java {
srcDir("src/decompilers/common")
}
}
fernflower {
java {
srcDir("src/decompilers/fernflower")
}
}
cfr {
java {
srcDir("src/decompilers/cfr")
}
}
vineflower {
java {
srcDir("src/decompilers/vineflower")
}
}
}
dependencies {
implementation gradleApi()
bootstrap project(":bootstrap")
// libraries
implementation ('commons-io:commons-io:2.11.0')
implementation ('com.google.code.gson:gson:2.10.1')
implementation ('com.fasterxml.jackson.core:jackson-databind:2.14.2')
implementation ('com.google.guava:guava:31.1-jre')
implementation ('org.ow2.asm:asm:9.5')
implementation ('org.ow2.asm:asm-analysis:9.5')
implementation ('org.ow2.asm:asm-commons:9.5')
implementation ('org.ow2.asm:asm-tree:9.5')
implementation ('org.ow2.asm:asm-util:9.5')
implementation ('me.tongfei:progressbar:0.9.0')
implementation libs.commons.io
implementation libs.gson
implementation libs.jackson
implementation libs.guava
implementation libs.bundles.asm
implementation libs.progressbar
// game handling utils
implementation ('net.fabricmc:stitch:0.6.2') {
implementation (libs.fabric.stitch) {
exclude module: 'mercury'
exclude module: 'enigma'
}
// tinyfile management
implementation ('dev.architectury:tiny-remapper:1.9.21')
implementation 'net.fabricmc:access-widener:2.1.0'
implementation 'net.fabricmc:mapping-io:0.2.1'
implementation libs.fabric.tiny.remapper
implementation libs.fabric.access.widener
implementation libs.fabric.mapping.io
implementation ('net.fabricmc:lorenz-tiny:4.0.2') {
implementation (libs.fabric.lorenz.tiny) {
transitive = false
}
implementation "dev.architectury:refmap-remapper:1.0.5"
// decompilers
implementation ('net.fabricmc:fabric-fernflower:2.0.0')
implementation ('net.fabricmc:cfr:0.2.1')
fernflowerCompileOnly runtimeLibs.fernflower
fernflowerCompileOnly libs.fabric.mapping.io
cfrCompileOnly runtimeLibs.cfr
cfrCompileOnly libs.fabric.mapping.io
vineflowerCompileOnly runtimeLibs.vineflower
vineflowerCompileOnly libs.fabric.mapping.io
fernflowerApi sourceSets.commonDecompiler.output
cfrApi sourceSets.commonDecompiler.output
vineflowerApi sourceSets.commonDecompiler.output
implementation sourceSets.commonDecompiler.output
implementation sourceSets.fernflower.output
implementation sourceSets.cfr.output
implementation sourceSets.vineflower.output
// source code remapping
implementation ('dev.architectury:mercury:0.1.2.15')
implementation libs.fabric.mercury
// Kotlin
implementation('org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.2') {
implementation(libs.kotlin.metadata) {
transitive = false
}
@@ -135,20 +194,21 @@ dependencies {
// Testing
testImplementation(gradleTestKit())
testImplementation('org.spockframework:spock-core:2.3-groovy-3.0') {
testImplementation(testLibs.spock) {
exclude module: 'groovy-all'
}
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
testImplementation ('io.javalin:javalin:5.4.2') {
testImplementation testLibs.junit.jupiter.engine
testRuntimeOnly testLibs.junit.platform.launcher
testImplementation (testLibs.javalin) {
exclude group: 'org.jetbrains.kotlin'
}
testImplementation 'org.mockito:mockito-core:5.2.0'
testImplementation 'com.microsoft.java:com.microsoft.java.debug.core:0.46.0'
testImplementation testLibs.mockito
testImplementation testLibs.java.debug
compileOnly 'org.jetbrains:annotations:24.0.1'
testCompileOnly 'org.jetbrains:annotations:24.0.1'
compileOnly runtimeLibs.jetbrains.annotations
testCompileOnly runtimeLibs.jetbrains.annotations
testCompileOnly ('net.fabricmc:sponge-mixin:0.11.4+mixin.0.8.5') {
testCompileOnly (testLibs.mixin) {
transitive = false
}
}
@@ -159,10 +219,15 @@ jar {
}
from configurations.bootstrap.collect { it.isDirectory() ? it : zipTree(it) }
from sourceSets.commonDecompiler.output.classesDirs
from sourceSets.cfr.output.classesDirs
from sourceSets.fernflower.output.classesDirs
from sourceSets.vineflower.output.classesDirs
}
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
base {
archivesName = project.name
}
tasks.withType(JavaCompile).configureEach {
it.options.release = 17
@@ -170,6 +235,8 @@ tasks.withType(JavaCompile).configureEach {
java {
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
spotless {
@@ -203,11 +270,11 @@ spotless {
checkstyle {
configFile = file('checkstyle.xml')
toolVersion = '10.6.0'
toolVersion = libs.versions.checkstyle.get()
}
codenarc {
toolVersion = "3.2.0"
toolVersion = libs.versions.codenarc.get()
configFile = file("codenarc.groovy")
}
@@ -221,7 +288,7 @@ gradlePlugin {
}
jacoco {
toolVersion = "0.8.8"
toolVersion = libs.versions.jacoco.get()
}
// Run to get test coverage.
@@ -230,7 +297,7 @@ jacocoTestReport {
reports {
xml.required = false
csv.required = false
html.outputLocation = file("${buildDir}/jacocoHtml")
html.outputLocation = file("${layout.buildDirectory.get().asFile}/jacocoHtml")
}
}
@@ -252,6 +319,8 @@ test {
}
}
import org.gradle.api.internal.artifacts.configurations.ConfigurationRoles
import org.gradle.launcher.cli.KotlinDslVersion
import org.gradle.util.GradleVersion
import org.w3c.dom.Document
@@ -265,7 +334,7 @@ publishing {
// Also publish a snapshot so people can use the latest version if they wish
snapshot(MavenPublication) { publication ->
groupId project.group
artifactId project.archivesBaseName
artifactId project.base.archivesName.get()
version baseVersion + '-SNAPSHOT'
from components.java
@@ -403,3 +472,5 @@ class PrintActionsTestName extends DefaultTask {
new File(System.getenv().GITHUB_OUTPUT) << "\ntest=$sanitised"
}
}
apply from: rootProject.file('gradle/versions.gradle')

View File

@@ -1,8 +1,54 @@
[versions]
kotlin = "1.8.10"
kotlin = "1.9.0"
asm = "9.5"
commons-io = "2.13.0"
gson = "2.10.1"
jackson = "2.15.2"
guava = "32.1.2-jre"
stitch = "0.6.2"
tiny-remapper = "0.8.9"
access-widener = "2.1.0"
mapping-io = "0.4.2"
lorenz-tiny = "4.0.2"
mercury = "0.4.0"
kotlinx-metadata = "0.7.0"
# Plugins
spotless = "6.20.0"
test-retry = "1.5.4"
checkstyle = "10.12.2"
codenarc = "3.3.0"
jacoco = "0.8.10"
[libraries]
# Loom compile libraries
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
asm-analysis = { module = "org.ow2.asm:asm-analysis", version.ref = "asm" }
asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" }
asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" }
commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
jackson = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
fabric-stitch = { module = "net.fabricmc:stitch", version.ref = "stitch" }
fabric-tiny-remapper = { module = "net.fabricmc:tiny-remapper", version.ref = "tiny-remapper" }
fabric-access-widener = { module = "net.fabricmc:access-widener", version.ref = "access-widener" }
fabric-mapping-io = { module = "net.fabricmc:mapping-io", version.ref = "mapping-io" }
fabric-lorenz-tiny = { module = "net.fabricmc:lorenz-tiny", version.ref = "lorenz-tiny" }
fabric-mercury = { module = "net.fabricmc:mercury", version.ref = "mercury" }
# Misc
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-metadata = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version.ref = "kotlinx-metadata" }
[plugins]
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
retry = { id = "org.gradle.test-retry", version.ref = "test-retry" }
[bundles]
asm = ["asm", "asm-analysis", "asm-commons", "asm-tree", "asm-util"]

View File

@@ -0,0 +1,25 @@
[versions]
# Decompilers
fernflower = "2.0.0"
cfr = "0.2.1"
vineflower = "1.9.3"
# Runtime depedencies
mixin-compile-extensions = "0.6.0"
dev-launch-injector = "0.2.1+build.8"
terminal-console-appender = "1.2.0"
jetbrains-annotations = "24.0.1"
native-support = "1.0.1"
[libraries]
# Decompilers
fernflower = { module = "net.fabricmc:fabric-fernflower", version.ref = "fernflower" }
cfr = { module = "net.fabricmc:cfr", version.ref = "cfr" }
vineflower = { module = "org.vineflower:vineflower", version.ref = "vineflower" }
# Runtime depedencies
mixin-compile-extensions = { module = "net.fabricmc:fabric-mixin-compile-extensions", version.ref = "mixin-compile-extensions" }
dev-launch-injector = { module = "net.fabricmc:dev-launch-injector", version.ref = "dev-launch-injector" }
terminal-console-appender = { module = "net.minecrell:terminalconsoleappender", version.ref = "terminal-console-appender" }
jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" }
native-support = { module = "net.fabricmc:fabric-loom-native-support", version.ref = "native-support" }

View File

@@ -0,0 +1,23 @@
[versions]
spock = "2.3-groovy-3.0"
junit = "5.10.0"
javalin = "5.6.2"
mockito = "5.4.0"
java-debug = "0.48.0"
mixin = "0.11.4+mixin.0.8.5"
gradle-nightly = "8.5-20230908221250+0000"
fabric-loader = "0.14.22"
fabric-installer = "0.11.1"
[libraries]
spock = { module = "org.spockframework:spock-core", version.ref = "spock" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" }
javalin = { module = "io.javalin:javalin", version.ref = "javalin" }
mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
java-debug = { module = "com.microsoft.java:com.microsoft.java.debug.core", version.ref = "java-debug" }
mixin = { module = "net.fabricmc:sponge-mixin", version.ref = "mixin" }
gradle-nightly = { module = "org.gradle:dummy", version.ref = "gradle-nightly" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" }

85
gradle/versions.gradle Normal file
View File

@@ -0,0 +1,85 @@
/**
* Generates a java source file containing all of the version from the Gradle version catalog.
*/
import java.nio.file.Files
import java.time.LocalDate
generateVersionConstants(sourceSets.main, "runtimeLibs", "net/fabricmc/loom/util/LoomVersions")
generateVersionConstants(sourceSets.test, "testLibs", "net/fabricmc/loom/test/LoomTestVersions")
def generateVersionConstants(def sourceSet, def catalogName, def sourcesName) {
def versionCatalog = extensions.getByType(VersionCatalogsExtension.class).named(catalogName)
def task = tasks.register("${catalogName}GenerateConstants", GenerateVersions.class) {
versionCatalog.getLibraryAliases().forEach {
def lib = versionCatalog.findLibrary(it).get().get()
getVersions().put(it, lib.toString())
}
className = sourcesName
headerFile = file("HEADER")
outputDir = file("src/${sourceSet.name}/generated")
}
sourceSet.java.srcDir task
spotlessGroovyGradle.dependsOn task // Not quite sure why this is needed, but it fixes a warning.
compileKotlin.dependsOn task
sourcesJar.dependsOn task
}
abstract class GenerateVersions extends DefaultTask {
@Input
abstract MapProperty<String, String> getVersions()
@Input
abstract Property<String> getClassName()
@InputFile
abstract RegularFileProperty getHeaderFile()
@OutputDirectory
abstract DirectoryProperty getOutputDir()
@TaskAction
def run() {
def output = outputDir.get().asFile.toPath()
output.deleteDir()
def className = getClassName().get()
def si = className.lastIndexOf("/")
def packageName = className.substring(0, si)
def packagePath = output.resolve(packageName)
def sourceName = className.substring(si + 1, className.length())
def sourcePath = packagePath.resolve(sourceName + ".java")
Files.createDirectories(packagePath)
def constants = getVersions().get().collect { entry ->
def split = entry.value.split(":")
if (split.length != 3) return ""
"\tpublic static final ${sourceName} ${toSnakeCase(entry.key)} = new ${sourceName}(\"${split[0]}\", \"${split[1]}\", \"${split[2]}\");"
}.findAll { !it.blank }.join("\n")
def header = headerFile.get().getAsFile().text.replace("\$YEAR", "${LocalDate.now().year}").trim()
sourcePath.write(
"""${header}
package ${packageName.replace("/", ".")};
/**
* Auto generated class, do not edit.
*/
public record ${sourceName}(String group, String module, String version) {
${constants}
public String mavenNotation() {
return "%s:%s:%s".formatted(group, module, version);
}
}
""")
}
static def toSnakeCase(String input) {
return input.trim().replaceAll(/[^a-zA-Z0-9]+/, '_').toUpperCase()
}
}

Binary file not shown.

View File

@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

8
gradlew vendored
View File

@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -130,10 +131,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.

View File

@@ -1,2 +1,14 @@
rootProject.name = "architectury-loom"
rootProject.name = name
dependencyResolutionManagement {
versionCatalogs {
testLibs {
from(files("gradle/test.libs.versions.toml"))
}
runtimeLibs {
from(files("gradle/runtime.libs.versions.toml"))
}
}
}
include "bootstrap"

View File

@@ -45,7 +45,6 @@ import org.benf.cfr.reader.mapping.NullMapping;
import org.benf.cfr.reader.util.output.DelegatingDumper;
import org.benf.cfr.reader.util.output.Dumper;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.tree.MappingTree;
@@ -66,7 +65,7 @@ public class CFRObfuscationMapping extends NullMapping {
private static MappingTree readMappings(Path input) {
try (BufferedReader reader = Files.newBufferedReader(input)) {
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, MappingsNamespace.NAMED.toString());
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named");
MappingReader.read(reader, nsSwitch);
return mappingTree;

View File

@@ -25,6 +25,7 @@
package net.fabricmc.loom.decompilers.cfr;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -37,23 +38,18 @@ import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import com.google.common.base.Charsets;
import org.benf.cfr.reader.api.OutputSinkFactory;
import org.benf.cfr.reader.api.SinkReturns;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.util.IOStringConsumer;
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
public class CFRSinkFactory implements OutputSinkFactory {
private static final Logger ERROR_LOGGER = LoggerFactory.getLogger(CFRSinkFactory.class);
private final JarOutputStream outputStream;
private final IOStringConsumer logger;
private final LoomInternalDecompiler.Logger logger;
private final Set<String> addedDirectories = new HashSet<>();
private final Map<String, Map<Integer, Integer>> lineMap = new TreeMap<>();
public CFRSinkFactory(JarOutputStream outputStream, IOStringConsumer logger) {
public CFRSinkFactory(JarOutputStream outputStream, LoomInternalDecompiler.Logger logger) {
this.outputStream = outputStream;
this.logger = logger;
}
@@ -72,7 +68,7 @@ public class CFRSinkFactory implements OutputSinkFactory {
return switch (sinkType) {
case JAVA -> (Sink<T>) decompiledSink();
case LINENUMBER -> (Sink<T>) lineNumberMappingSink();
case EXCEPTION -> (e) -> ERROR_LOGGER.error((String) e);
case EXCEPTION -> (e) -> logger.error((String) e);
default -> null;
};
}
@@ -83,7 +79,7 @@ public class CFRSinkFactory implements OutputSinkFactory {
if (!filename.isEmpty()) filename += "/";
filename += sinkable.getClassName() + ".java";
byte[] data = sinkable.getJava().getBytes(Charsets.UTF_8);
byte[] data = sinkable.getJava().getBytes(StandardCharsets.UTF_8);
writeToJar(filename, data);
};

View File

@@ -45,10 +45,9 @@ import org.benf.cfr.reader.util.getopt.Options;
import org.benf.cfr.reader.util.getopt.OptionsImpl;
import org.benf.cfr.reader.util.output.SinkDumperFactory;
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
public final class LoomCFRDecompiler implements LoomDecompiler {
public final class LoomCFRDecompiler implements LoomInternalDecompiler {
private static final Map<String, String> DECOMPILE_OPTIONS = Map.of(
"renameillegalidents", "true",
"trackbytecodeloc", "true",
@@ -56,16 +55,18 @@ public final class LoomCFRDecompiler implements LoomDecompiler {
);
@Override
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
public void decompile(LoomInternalDecompiler.Context context) {
Path compiledJar = context.compiledJar();
final String path = compiledJar.toAbsolutePath().toString();
final Map<String, String> allOptions = new HashMap<>(DECOMPILE_OPTIONS);
allOptions.putAll(metaData.options());
allOptions.putAll(context.options());
final Options options = OptionsImpl.getFactory().create(allOptions);
ClassFileSourceImpl classFileSource = new ClassFileSourceImpl(options);
for (Path library : metaData.libraries()) {
for (Path library : context.libraries()) {
classFileSource.addJarContent(library.toAbsolutePath().toString(), AnalysisType.JAR);
}
@@ -73,8 +74,8 @@ public final class LoomCFRDecompiler implements LoomDecompiler {
DCCommonState state = new DCCommonState(options, classFileSource);
if (metaData.javaDocs() != null) {
state = new DCCommonState(state, new CFRObfuscationMapping(metaData.javaDocs()));
if (context.javaDocs() != null) {
state = new DCCommonState(state, new CFRObfuscationMapping(context.javaDocs()));
}
final Manifest manifest = new Manifest();
@@ -82,8 +83,8 @@ public final class LoomCFRDecompiler implements LoomDecompiler {
Map<String, Map<Integer, Integer>> lineMap;
try (JarOutputStream outputStream = new JarOutputStream(Files.newOutputStream(sourcesDestination), manifest)) {
CFRSinkFactory cfrSinkFactory = new CFRSinkFactory(outputStream, metaData.logger());
try (JarOutputStream outputStream = new JarOutputStream(Files.newOutputStream(context.sourcesDestination()), manifest)) {
CFRSinkFactory cfrSinkFactory = new CFRSinkFactory(outputStream, context.logger());
SinkDumperFactory dumperFactory = new SinkDumperFactory(cfrSinkFactory, options);
Driver.doJar(state, path, AnalysisType.JAR, dumperFactory);
@@ -93,7 +94,7 @@ public final class LoomCFRDecompiler implements LoomDecompiler {
throw new UncheckedIOException("Failed to decompile", e);
}
writeLineMap(linemapDestination, lineMap);
writeLineMap(context.linemapDestination(), lineMap);
}
private void writeLineMap(Path output, Map<String, Map<Integer, Integer>> lineMap) {

View File

@@ -0,0 +1,61 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.decompilers;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
// This is an internal interface to loom, DO NOT USE this in your own plugins.
public interface LoomInternalDecompiler {
void decompile(Context context);
interface Context {
Path compiledJar();
Path sourcesDestination();
Path linemapDestination();
int numberOfThreads();
Path javaDocs();
Collection<Path> libraries();
Logger logger();
Map<String, String> options();
byte[] unpackZip(Path zip, String path) throws IOException;
}
interface Logger {
void accept(String data) throws IOException;
void error(String msg);
}
}

View File

@@ -0,0 +1,86 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2019-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.decompilers.fernflower;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.java.decompiler.main.Fernflower;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
public final class FabricFernFlowerDecompiler implements LoomInternalDecompiler {
@Override
public void decompile(LoomInternalDecompiler.Context context) {
Path sourcesDestination = context.sourcesDestination();
Path linemapDestination = context.linemapDestination();
final Map<String, Object> options = new HashMap<>(
Map.of(
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1",
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1",
IFernflowerPreferences.REMOVE_SYNTHETIC, "1",
IFernflowerPreferences.LOG_LEVEL, "trace",
IFernflowerPreferences.THREADS, String.valueOf(context.numberOfThreads()),
IFernflowerPreferences.INDENT_STRING, "\t",
IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(context.javaDocs().toFile())
)
);
options.putAll(context.options());
IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile);
Fernflower ff = new Fernflower((externalPath, internalPath) -> FabricFernFlowerDecompiler.this.getBytecode(externalPath, internalPath, context), saver, options, new FernflowerLogger(context.logger()));
for (Path library : context.libraries()) {
ff.addLibrary(library.toFile());
}
ff.addSource(context.compiledJar().toFile());
try {
ff.decompileContext();
} finally {
ff.clearContext();
}
}
private byte[] getBytecode(String externalPath, String internalPath, LoomInternalDecompiler.Context context) throws IOException {
File file = new File(externalPath);
if (internalPath == null) {
return InterpreterUtil.getBytes(file);
} else {
return context.unpackZip(file.toPath(), internalPath);
}
}
}

View File

@@ -28,12 +28,12 @@ import java.io.IOException;
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
import net.fabricmc.loom.util.IOStringConsumer;
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
public class FernflowerLogger extends IFernflowerLogger {
private final IOStringConsumer logger;
private final LoomInternalDecompiler.Logger logger;
public FernflowerLogger(IOStringConsumer logger) {
public FernflowerLogger(LoomInternalDecompiler.Logger logger) {
this.logger = logger;
}

View File

@@ -35,16 +35,17 @@ import org.jetbrains.java.decompiler.struct.StructClass;
import org.jetbrains.java.decompiler.struct.StructField;
import org.jetbrains.java.decompiler.struct.StructMethod;
import org.jetbrains.java.decompiler.struct.StructRecordComponent;
import org.objectweb.asm.Opcodes;
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class TinyJavadocProvider implements IFabricJavadocProvider {
private static final int ACC_STATIC = 0x0008;
private static final int ACC_RECORD = 0x10000;
private final MappingTree mappingTree;
public TinyJavadocProvider(File tinyFile) {
@@ -93,7 +94,7 @@ public class TinyJavadocProvider implements IFabricJavadocProvider {
addedParam = true;
}
parts.add(String.format("@param %s %s", fieldMapping.getName(MappingsNamespace.NAMED.toString()), comment));
parts.add(String.format("@param %s %s", fieldMapping.getName("named"), comment));
}
}
@@ -151,7 +152,7 @@ public class TinyJavadocProvider implements IFabricJavadocProvider {
addedParam = true;
}
parts.add(String.format("@param %s %s", argMapping.getName(MappingsNamespace.NAMED.toString()), comment));
parts.add(String.format("@param %s %s", argMapping.getName("named"), comment));
}
}
@@ -168,7 +169,7 @@ public class TinyJavadocProvider implements IFabricJavadocProvider {
private static MappingTree readMappings(File input) {
try (BufferedReader reader = Files.newBufferedReader(input.toPath())) {
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, MappingsNamespace.NAMED.toString());
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named");
MappingReader.read(reader, nsSwitch);
return mappingTree;
@@ -178,10 +179,10 @@ public class TinyJavadocProvider implements IFabricJavadocProvider {
}
public static boolean isRecord(StructClass structClass) {
return (structClass.getAccessFlags() & Opcodes.ACC_RECORD) != 0;
return (structClass.getAccessFlags() & ACC_RECORD) != 0;
}
public static boolean isStatic(StructField structField) {
return (structField.getAccessFlags() & Opcodes.ACC_STATIC) != 0;
return (structField.getAccessFlags() & ACC_STATIC) != 0;
}
}

View File

@@ -0,0 +1,172 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2019-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.decompilers.vineflower;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.jetbrains.java.decompiler.main.DecompilerContext;
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
public class ThreadSafeResultSaver implements IResultSaver {
private final Supplier<File> output;
private final Supplier<File> lineMapFile;
public Map<String, ZipOutputStream> outputStreams = new HashMap<>();
public Map<String, ExecutorService> saveExecutors = new HashMap<>();
public PrintWriter lineMapWriter;
public ThreadSafeResultSaver(Supplier<File> output, Supplier<File> lineMapFile) {
this.output = output;
this.lineMapFile = lineMapFile;
}
@Override
public void createArchive(String path, String archiveName, Manifest manifest) {
String key = path + "/" + archiveName;
File file = output.get();
try {
FileOutputStream fos = new FileOutputStream(file);
ZipOutputStream zos = manifest == null ? new ZipOutputStream(fos) : new JarOutputStream(fos, manifest);
outputStreams.put(key, zos);
saveExecutors.put(key, Executors.newSingleThreadExecutor());
} catch (IOException e) {
throw new RuntimeException("Unable to create archive: " + file, e);
}
if (lineMapFile.get() != null) {
try {
lineMapWriter = new PrintWriter(new FileWriter(lineMapFile.get()));
} catch (IOException e) {
throw new RuntimeException("Unable to create line mapping file: " + lineMapFile.get(), e);
}
}
}
@Override
public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) {
this.saveClassEntry(path, archiveName, qualifiedName, entryName, content, null);
}
@Override
public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content, int[] mapping) {
String key = path + "/" + archiveName;
ExecutorService executor = saveExecutors.get(key);
executor.submit(() -> {
ZipOutputStream zos = outputStreams.get(key);
try {
zos.putNextEntry(new ZipEntry(entryName));
if (content != null) {
zos.write(content.getBytes(StandardCharsets.UTF_8));
}
} catch (IOException e) {
DecompilerContext.getLogger().writeMessage("Cannot write entry " + entryName, e);
}
if (mapping != null && lineMapWriter != null) {
int maxLine = 0;
int maxLineDest = 0;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < mapping.length; i += 2) {
maxLine = Math.max(maxLine, mapping[i]);
maxLineDest = Math.max(maxLineDest, mapping[i + 1]);
builder.append("\t").append(mapping[i]).append("\t").append(mapping[i + 1]).append("\n");
}
lineMapWriter.println(qualifiedName + "\t" + maxLine + "\t" + maxLineDest);
lineMapWriter.println(builder.toString());
}
});
}
@Override
public void closeArchive(String path, String archiveName) {
String key = path + "/" + archiveName;
ExecutorService executor = saveExecutors.get(key);
Future<?> closeFuture = executor.submit(() -> {
ZipOutputStream zos = outputStreams.get(key);
try {
zos.close();
} catch (IOException e) {
throw new RuntimeException("Unable to close zip. " + key, e);
}
});
executor.shutdown();
try {
closeFuture.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
outputStreams.remove(key);
saveExecutors.remove(key);
if (lineMapWriter != null) {
lineMapWriter.flush();
lineMapWriter.close();
}
}
@Override
public void saveFolder(String path) {
}
@Override
public void copyFile(String source, String path, String entryName) {
}
@Override
public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) {
}
@Override
public void saveDirEntry(String path, String archiveName, String entryName) {
}
@Override
public void copyEntry(String source, String path, String archiveName, String entry) {
}
}

View File

@@ -0,0 +1,188 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2019-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.decompilers.vineflower;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.java.decompiler.struct.StructClass;
import org.jetbrains.java.decompiler.struct.StructField;
import org.jetbrains.java.decompiler.struct.StructMethod;
import org.jetbrains.java.decompiler.struct.StructRecordComponent;
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class TinyJavadocProvider implements IFabricJavadocProvider {
private static final int ACC_STATIC = 0x0008;
private static final int ACC_RECORD = 0x10000;
private final MappingTree mappingTree;
public TinyJavadocProvider(File tinyFile) {
mappingTree = readMappings(tinyFile);
}
@Override
public String getClassDoc(StructClass structClass) {
MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
if (classMapping == null) {
return null;
}
if (!isRecord(structClass)) {
return classMapping.getComment();
}
/**
* Handle the record component docs here.
*
* Record components are mapped via the field name, thus take the docs from the fields and display them on then class.
*/
List<String> parts = new ArrayList<>();
if (classMapping.getComment() != null) {
parts.add(classMapping.getComment());
}
boolean addedParam = false;
for (StructRecordComponent component : structClass.getRecordComponents()) {
// The component will always match the field name and descriptor
MappingTree.FieldMapping fieldMapping = classMapping.getField(component.getName(), component.getDescriptor());
if (fieldMapping == null) {
continue;
}
String comment = fieldMapping.getComment();
if (comment != null) {
if (!addedParam && classMapping.getComment() != null) {
//Add a blank line before components when the class has a comment
parts.add("");
addedParam = true;
}
parts.add(String.format("@param %s %s", fieldMapping.getName("named"), comment));
}
}
if (parts.isEmpty()) {
return null;
}
return String.join("\n", parts);
}
@Override
public String getFieldDoc(StructClass structClass, StructField structField) {
// None static fields in records are handled in the class javadoc.
if (isRecord(structClass) && !isStatic(structField)) {
return null;
}
MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
if (classMapping == null) {
return null;
}
MappingTree.FieldMapping fieldMapping = classMapping.getField(structField.getName(), structField.getDescriptor());
return fieldMapping != null ? fieldMapping.getComment() : null;
}
@Override
public String getMethodDoc(StructClass structClass, StructMethod structMethod) {
MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
if (classMapping == null) {
return null;
}
MappingTree.MethodMapping methodMapping = classMapping.getMethod(structMethod.getName(), structMethod.getDescriptor());
if (methodMapping != null) {
List<String> parts = new ArrayList<>();
if (methodMapping.getComment() != null) {
parts.add(methodMapping.getComment());
}
boolean addedParam = false;
for (MappingTree.MethodArgMapping argMapping : methodMapping.getArgs()) {
String comment = argMapping.getComment();
if (comment != null) {
if (!addedParam && methodMapping.getComment() != null) {
//Add a blank line before params when the method has a comment
parts.add("");
addedParam = true;
}
parts.add(String.format("@param %s %s", argMapping.getName("named"), comment));
}
}
if (parts.isEmpty()) {
return null;
}
return String.join("\n", parts);
}
return null;
}
private static MappingTree readMappings(File input) {
try (BufferedReader reader = Files.newBufferedReader(input.toPath())) {
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named");
MappingReader.read(reader, nsSwitch);
return mappingTree;
} catch (IOException e) {
throw new RuntimeException("Failed to read mappings", e);
}
}
public static boolean isRecord(StructClass structClass) {
return (structClass.getAccessFlags() & ACC_RECORD) != 0;
}
public static boolean isStatic(StructField structField) {
return (structField.getAccessFlags() & ACC_STATIC) != 0;
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2019-2021 FabricMC
* Copyright (c) 2019-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
package net.fabricmc.loom.decompilers.fernflower;
package net.fabricmc.loom.decompilers.vineflower;
import java.nio.file.Path;
import java.util.HashMap;
@@ -33,34 +33,36 @@ import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
public final class FabricFernFlowerDecompiler implements LoomDecompiler {
public final class VineflowerDecompiler implements LoomInternalDecompiler {
@Override
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
public void decompile(Context context) {
Path sourcesDestination = context.sourcesDestination();
Path linemapDestination = context.linemapDestination();
final Map<String, Object> options = new HashMap<>(
Map.of(
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1",
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1",
IFernflowerPreferences.REMOVE_SYNTHETIC, "1",
IFernflowerPreferences.LOG_LEVEL, "trace",
IFernflowerPreferences.THREADS, String.valueOf(metaData.numberOfThreads()),
IFernflowerPreferences.THREADS, String.valueOf(context.numberOfThreads()),
IFernflowerPreferences.INDENT_STRING, "\t",
IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(metaData.javaDocs().toFile())
IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(context.javaDocs().toFile())
)
);
options.putAll(metaData.options());
options.putAll(context.options());
IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile);
Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, new FernflowerLogger(metaData.logger()));
Fernflower ff = new Fernflower(saver, options, new VineflowerLogger(context.logger()));
for (Path library : metaData.libraries()) {
for (Path library : context.libraries()) {
ff.addLibrary(library.toFile());
}
ff.addSource(compiledJar.toFile());
ff.addSource(context.compiledJar().toFile());
try {
ff.decompileContext();

View File

@@ -0,0 +1,87 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.decompilers.vineflower;
import java.io.IOException;
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
public class VineflowerLogger extends IFernflowerLogger {
private final LoomInternalDecompiler.Logger logger;
public VineflowerLogger(LoomInternalDecompiler.Logger logger) {
this.logger = logger;
}
@Override
public void writeMessage(String message, Severity severity) {
if (severity.ordinal() < Severity.ERROR.ordinal()) return;
System.err.println(message);
}
@Override
public void writeMessage(String message, Severity severity, Throwable t) {
if (severity.ordinal() < Severity.ERROR.ordinal()) return;
writeMessage(message, severity);
t.printStackTrace(System.err);
}
private void write(String data) {
try {
logger.accept(data);
} catch (IOException e) {
throw new RuntimeException("Failed to log", e);
}
}
@Override
public void startReadingClass(String className) {
write("Decompiling " + className);
}
@Override
public void startClass(String className) {
write("Decompiling " + className);
}
@Override
public void startWriteClass(String className) {
// Nope
}
@Override
public void startMethod(String methodName) {
// Nope
}
@Override
public void endMethod() {
// Nope
}
}

View File

@@ -99,7 +99,7 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
// Setup extensions
project.getExtensions().create(LoomGradleExtensionAPI.class, "loom", LoomGradleExtensionImpl.class, project, LoomFiles.create(project));
project.getExtensions().create("fabricApi", FabricApiExtension.class, project);
project.getExtensions().create("fabricApi", FabricApiExtension.class);
for (Class<? extends Runnable> jobClass : SETUP_JOBS) {
project.getObjects().newInstance(jobClass).run();

View File

@@ -37,6 +37,7 @@ import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.ApiStatus;
@@ -133,6 +134,8 @@ public interface LoomGradleExtensionAPI {
Property<String> getCustomMinecraftManifest();
SetProperty<String> getKnownIndyBsms();
/**
* Disables the deprecated POM generation for a publication.
* This is useful if you want to suppress deprecation warnings when you're not using software components.

View File

@@ -49,6 +49,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.PrepareJarRemapTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.LoomVersions;
/**
* Normally javac invokes annotation processors, but when the scala or kapt plugin are installed they will want to invoke
@@ -150,7 +151,7 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
// Add Mixin and mixin extensions (fabric-mixin-compile-extensions pulls mixin itself too)
project.getDependencies().add(processorConfig.getName(),
Constants.Dependencies.MIXIN_COMPILE_EXTENSIONS + Constants.Dependencies.Versions.MIXIN_COMPILE_EXTENSIONS);
LoomVersions.MIXIN_COMPILE_EXTENSIONS.mavenNotation());
}
}

View File

@@ -26,9 +26,11 @@ package net.fabricmc.loom.configuration;
import java.io.File;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -41,25 +43,29 @@ import org.w3c.dom.NodeList;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.download.DownloadException;
public class FabricApiExtension {
private final Project project;
public FabricApiExtension(Project project) {
this.project = project;
}
public abstract class FabricApiExtension {
@Inject
public abstract Project getProject();
private static final HashMap<String, Map<String, String>> moduleVersionCache = new HashMap<>();
private static final HashMap<String, Map<String, String>> deprecatedModuleVersionCache = new HashMap<>();
public Dependency module(String moduleName, String fabricApiVersion) {
return project.getDependencies()
return getProject().getDependencies()
.create(getDependencyNotation(moduleName, fabricApiVersion));
}
public String moduleVersion(String moduleName, String fabricApiVersion) {
String moduleVersion = moduleVersionCache
.computeIfAbsent(fabricApiVersion, this::populateModuleVersionMap)
.computeIfAbsent(fabricApiVersion, this::getApiModuleVersions)
.get(moduleName);
if (moduleVersion == null) {
moduleVersion = deprecatedModuleVersionCache
.computeIfAbsent(fabricApiVersion, this::getDeprecatedApiModuleVersions)
.get(moduleName);
}
if (moduleVersion == null) {
throw new RuntimeException("Failed to find module version for module: " + moduleName);
}
@@ -71,9 +77,24 @@ public class FabricApiExtension {
return String.format("net.fabricmc.fabric-api:%s:%s", moduleName, moduleVersion(moduleName, fabricApiVersion));
}
private Map<String, String> populateModuleVersionMap(String fabricApiVersion) {
File pomFile = getApiMavenPom(fabricApiVersion);
private Map<String, String> getApiModuleVersions(String fabricApiVersion) {
try {
return populateModuleVersionMap(getApiMavenPom(fabricApiVersion));
} catch (PomNotFoundException e) {
throw new RuntimeException("Could not find fabric-api version: " + fabricApiVersion);
}
}
private Map<String, String> getDeprecatedApiModuleVersions(String fabricApiVersion) {
try {
return populateModuleVersionMap(getDeprecatedApiMavenPom(fabricApiVersion));
} catch (PomNotFoundException e) {
// Not all fabric-api versions have deprecated modules, return an empty map to cache this fact.
return Collections.emptyMap();
}
}
private Map<String, String> populateModuleVersionMap(File pomFile) {
try {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
@@ -101,27 +122,36 @@ public class FabricApiExtension {
}
}
private File getApiMavenPom(String fabricApiVersion) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
private File getApiMavenPom(String fabricApiVersion) throws PomNotFoundException {
return getPom("fabric-api", fabricApiVersion);
}
File mavenPom = new File(extension.getFiles().getUserCache(), "fabric-api/" + fabricApiVersion + ".pom");
private File getDeprecatedApiMavenPom(String fabricApiVersion) throws PomNotFoundException {
return getPom("fabric-api-deprecated", fabricApiVersion);
}
if (project.getGradle().getStartParameter().isOffline()) {
if (!mavenPom.exists()) {
throw new RuntimeException("Cannot retrieve fabric-api pom due to being offline");
}
return mavenPom;
}
private File getPom(String name, String version) throws PomNotFoundException {
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
final var mavenPom = new File(extension.getFiles().getUserCache(), "fabric-api/%s-%s.pom".formatted(name, version));
try {
extension.download(String.format("https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/%1$s/fabric-api-%1$s.pom", fabricApiVersion))
extension.download(String.format("https://maven.fabricmc.net/net/fabricmc/fabric-api/%2$s/%1$s/%2$s-%1$s.pom", version, name))
.defaultCache()
.downloadPath(mavenPom.toPath());
} catch (DownloadException e) {
throw new UncheckedIOException("Failed to download maven info for " + fabricApiVersion, e);
if (e.getStatusCode() == 404) {
throw new PomNotFoundException(e);
}
throw new UncheckedIOException("Failed to download maven info to " + mavenPom.getName(), e);
}
return mavenPom;
}
private static class PomNotFoundException extends Exception {
PomNotFoundException(Throwable cause) {
super(cause);
}
}
}

View File

@@ -24,11 +24,16 @@
package net.fabricmc.loom.configuration;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ExternalModuleDependency;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import org.gradle.api.plugins.JavaPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomRepositoryPlugin;
@@ -36,6 +41,8 @@ import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.util.Constants;
public record InstallerData(String version, JsonObject installerJson) {
private static final Logger LOGGER = LoggerFactory.getLogger(InstallerData.class);
public void applyToProject(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
@@ -45,35 +52,61 @@ public record InstallerData(String version, JsonObject installerJson) {
extension.setInstallerData(this);
JsonObject libraries = installerJson.get("libraries").getAsJsonObject();
Configuration loaderDepsConfig = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES);
Configuration apDepsConfig = project.getConfigurations().getByName("annotationProcessor");
final JsonObject libraries = installerJson.get("libraries").getAsJsonObject();
libraries.get("common").getAsJsonArray().forEach(jsonElement -> {
String name = jsonElement.getAsJsonObject().get("name").getAsString();
project.getLogger().debug("Adding dependency ({}) from installer JSON", name);
applyDependendencies(libraries.get("common").getAsJsonArray(), project);
// Apply development dependencies if they exist.
if (libraries.has("development")) {
applyDependendencies(libraries.get("development").getAsJsonArray(), project);
}
}
private void applyDependendencies(JsonArray jsonArray, Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
Configuration loaderDepsConfig = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES);
Configuration annotationProcessor = project.getConfigurations().getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME);
for (JsonElement jsonElement : jsonArray) {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
final String name = jsonObject.get("name").getAsString();
LOGGER.debug("Adding dependency ({}) from installer JSON", name);
ExternalModuleDependency modDep = (ExternalModuleDependency) project.getDependencies().create(name);
modDep.setTransitive(false);
modDep.setTransitive(false); // Match the launcher in not being transitive
loaderDepsConfig.getDependencies().add(modDep);
// TODO: work around until https://github.com/FabricMC/Mixin/pull/60 and https://github.com/FabricMC/fabric-mixin-compile-extensions/issues/14 is fixed.
// Work around https://github.com/FabricMC/Mixin/pull/60 and https://github.com/FabricMC/fabric-mixin-compile-extensions/issues/14.
if (!IdeaUtils.isIdeaSync() && extension.getMixin().getUseLegacyMixinAp().get()) {
apDepsConfig.getDependencies().add(modDep);
annotationProcessor.getDependencies().add(modDep);
}
// If user choose to use dependencyResolutionManagement, then they should declare
// these repositories manually in the settings file.
if (jsonElement.getAsJsonObject().has("url") && !project.getGradle().getPlugins().hasPlugin(LoomRepositoryPlugin.class)) {
String url = jsonElement.getAsJsonObject().get("url").getAsString();
long count = project.getRepositories().stream().filter(artifactRepository -> artifactRepository instanceof MavenArtifactRepository)
.map(artifactRepository -> (MavenArtifactRepository) artifactRepository)
.filter(mavenArtifactRepository -> mavenArtifactRepository.getUrl().toString().equalsIgnoreCase(url)).count();
if (count == 0) {
project.getRepositories().maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(jsonElement.getAsJsonObject().get("url").getAsString()));
}
if (project.getGradle().getPlugins().hasPlugin(LoomRepositoryPlugin.class)) {
continue;
}
});
addRepository(jsonObject, project);
}
}
private void addRepository(JsonObject jsonObject, Project project) {
if (!jsonObject.has("url")) {
return;
}
final String url = jsonObject.get("url").getAsString();
final boolean isPresent = project.getRepositories().stream()
.filter(artifactRepository -> artifactRepository instanceof MavenArtifactRepository)
.map(artifactRepository -> (MavenArtifactRepository) artifactRepository)
.anyMatch(mavenArtifactRepository -> mavenArtifactRepository.getUrl().toString().equalsIgnoreCase(url));
if (isPresent) {
return;
}
project.getRepositories().maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(jsonObject.get("url").getAsString()));
}
}

View File

@@ -36,6 +36,7 @@ import org.gradle.api.plugins.JavaPlugin;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.LoomVersions;
import net.fabricmc.loom.util.gradle.GradleUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
@@ -106,10 +107,10 @@ public abstract class LoomConfigurations implements Runnable {
extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_RUNTIME_LIBRARIES);
// Add the dev time dependencies
getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR);
getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER);
getDependencies().add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS);
getDependencies().add(JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME, Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS);
getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, LoomVersions.DEV_LAUNCH_INJECTOR.mavenNotation());
getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, LoomVersions.TERMINAL_CONSOLE_APPENDER.mavenNotation());
getDependencies().add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation());
getDependencies().add(JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME, LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation());
GradleUtils.afterSuccessfulEvaluation(getProject(), () -> {
if (extension.shouldGenerateSrgTiny()) {

View File

@@ -170,9 +170,10 @@ public class ModProcessor {
MemoryMappingTree mappings = mappingConfiguration.getMappingsService(serviceManager, srg).getMappingTree();
LoggerFilter.replaceSystemOut();
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
.withKnownIndyBsm(extension.getKnownIndyBsms().get())
.withMappings(TinyRemapperHelper.create(mappingConfiguration.getMappingsService(serviceManager).getMappingTree(), fromM, toM, false))
.logger(project.getLogger()::lifecycle)
.logUnknownInvokeDynamic(false)
.withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false))
.renameInvalidLocals(false)
.extraAnalyzeVisitor(AccessWidenerAnalyzeVisitorProvider.createFromMods(fromM, remapList, extension.getPlatform().get()));

View File

@@ -30,7 +30,7 @@ import java.util.function.Predicate;
import net.fabricmc.loom.configuration.providers.minecraft.library.Library;
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryContext;
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessor;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.LoomVersions;
import net.fabricmc.loom.util.Platform;
public class LoomNativeSupportLibraryProcessor extends LibraryProcessor {
@@ -56,7 +56,7 @@ public class LoomNativeSupportLibraryProcessor extends LibraryProcessor {
@Override
public Predicate<Library> apply(Consumer<Library> dependencyConsumer) {
dependencyConsumer.accept(Library.fromMaven(Constants.Dependencies.NATIVE_SUPPORT + Constants.Dependencies.Versions.NATIVE_SUPPORT_VERSION, Library.Target.LOCAL_MOD));
dependencyConsumer.accept(Library.fromMaven(LoomVersions.NATIVE_SUPPORT.mavenNotation(), Library.Target.LOCAL_MOD));
return ALLOW_ALL;
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2018-2020 FabricMC
* Copyright (c) 2018-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,14 +24,27 @@
package net.fabricmc.loom.decompilers;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
import javax.inject.Inject;
import org.gradle.api.NamedDomainObjectProvider;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.decompilers.cfr.LoomCFRDecompiler;
import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler;
import net.fabricmc.loom.decompilers.vineflower.VineflowerDecompiler;
import net.fabricmc.loom.util.LoomVersions;
import net.fabricmc.loom.util.ZipUtils;
public abstract class DecompilerConfiguration implements Runnable {
@Inject
@@ -39,11 +52,118 @@ public abstract class DecompilerConfiguration implements Runnable {
@Override
public void run() {
registerDecompiler(getProject(), "fernFlower", FabricFernFlowerDecompiler.class);
registerDecompiler(getProject(), "cfr", LoomCFRDecompiler.class);
var fernflowerConfiguration = createConfiguration("fernflower", LoomVersions.FERNFLOWER);
var cfrConfiguration = createConfiguration("cfr", LoomVersions.CFR);
var vineflowerConfiguration = createConfiguration("vineflower", LoomVersions.VINEFLOWER);
registerDecompiler(getProject(), "fernFlower", BuiltinFernflower.class, fernflowerConfiguration);
registerDecompiler(getProject(), "cfr", BuiltinCfr.class, cfrConfiguration);
registerDecompiler(getProject(), "vineflower", BuiltinVineflower.class, vineflowerConfiguration);
}
private void registerDecompiler(Project project, String name, Class<? extends LoomDecompiler> decompilerClass) {
LoomGradleExtension.get(project).getDecompilerOptions().register(name, options -> options.getDecompilerClassName().set(decompilerClass.getName()));
private NamedDomainObjectProvider<Configuration> createConfiguration(String name, LoomVersions version) {
final String configurationName = name + "DecompilerClasspath";
NamedDomainObjectProvider<Configuration> configuration = getProject().getConfigurations().register(configurationName);
getProject().getDependencies().add(configurationName, version.mavenNotation());
return configuration;
}
private void registerDecompiler(Project project, String name, Class<? extends LoomDecompiler> decompilerClass, NamedDomainObjectProvider<Configuration> configuration) {
LoomGradleExtension.get(project).getDecompilerOptions().register(name, options -> {
options.getDecompilerClassName().set(decompilerClass.getName());
options.getClasspath().from(configuration);
});
}
// We need to wrap the internal API with the public API.
// This is needed as the sourceset containing fabric's decompilers do not have access to loom classes.
private abstract static sealed class BuiltinDecompiler implements LoomDecompiler permits BuiltinFernflower, BuiltinCfr, BuiltinVineflower {
private final LoomInternalDecompiler internalDecompiler;
BuiltinDecompiler(LoomInternalDecompiler internalDecompiler) {
this.internalDecompiler = internalDecompiler;
}
@Override
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
final Logger slf4jLogger = LoggerFactory.getLogger(internalDecompiler.getClass());
final var logger = new LoomInternalDecompiler.Logger() {
@Override
public void accept(String data) throws IOException {
metaData.logger().accept(data);
}
@Override
public void error(String msg) {
slf4jLogger.error(msg);
}
};
internalDecompiler.decompile(new LoomInternalDecompiler.Context() {
@Override
public Path compiledJar() {
return compiledJar;
}
@Override
public Path sourcesDestination() {
return sourcesDestination;
}
@Override
public Path linemapDestination() {
return linemapDestination;
}
@Override
public int numberOfThreads() {
return metaData.numberOfThreads();
}
@Override
public Path javaDocs() {
return metaData.javaDocs();
}
@Override
public Collection<Path> libraries() {
return metaData.libraries();
}
@Override
public LoomInternalDecompiler.Logger logger() {
return logger;
}
@Override
public Map<String, String> options() {
return metaData.options();
}
@Override
public byte[] unpackZip(Path zip, String path) throws IOException {
return ZipUtils.unpack(zip, path);
}
});
}
}
public static final class BuiltinFernflower extends BuiltinDecompiler {
public BuiltinFernflower() {
super(new FabricFernFlowerDecompiler());
}
}
public static final class BuiltinCfr extends BuiltinDecompiler {
public BuiltinCfr() {
super(new LoomCFRDecompiler());
}
}
public static final class BuiltinVineflower extends BuiltinDecompiler {
public BuiltinVineflower() {
super(new VineflowerDecompiler());
}
}
}

View File

@@ -30,6 +30,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import com.google.common.base.Suppliers;
@@ -43,6 +44,7 @@ import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.tasks.SourceSet;
@@ -85,6 +87,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
protected final ConfigurableFileCollection log4jConfigs;
protected final RegularFileProperty accessWidener;
protected final Property<String> customManifest;
protected final SetProperty<String> knownIndyBsms;
protected final Property<Boolean> transitiveAccessWideners;
protected final Property<Boolean> modProvidedJavadoc;
protected final Property<String> intermediary;
@@ -121,6 +124,12 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.log4jConfigs = project.files(directories.getDefaultLog4jConfigFile());
this.accessWidener = project.getObjects().fileProperty();
this.customManifest = project.getObjects().property(String.class);
this.knownIndyBsms = project.getObjects().setProperty(String.class).convention(Set.of(
"java/lang/invoke/StringConcatFactory",
"java/lang/runtime/ObjectMethods",
"org/codehaus/groovy/vmplugin/v8/IndyInterface"
));
this.knownIndyBsms.finalizeValueOnRead();
this.transitiveAccessWideners = project.getObjects().property(Boolean.class)
.convention(true);
this.transitiveAccessWideners.finalizeValueOnRead();
@@ -275,6 +284,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return customManifest;
}
@Override
public SetProperty<String> getKnownIndyBsms() {
return knownIndyBsms;
}
@Override
public String getModVersion() {
return versionParser.getModVersion();

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2022 FabricMC
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,23 +22,16 @@
* SOFTWARE.
*/
package net.fabricmc.loom.decompilers.fernflower;
package net.fabricmc.loom.kotlin.remapping;
import java.io.File;
import java.io.IOException;
import kotlin.Metadata;
import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import net.fabricmc.loom.util.ZipUtils;
public class FernFlowerUtils {
public static byte[] getBytecode(String externalPath, String internalPath) throws IOException {
File file = new File(externalPath);
if (internalPath == null) {
return InterpreterUtil.getBytes(file);
} else {
return ZipUtils.unpack(file.toPath(), internalPath);
}
/**
* Similar story to JvmExtensionWrapper, lets abuse the fact that Java can call "internal" Kotlin APIs without reflection :).
*/
public record KotlinClassMetadataWrapper(KotlinClassMetadata metadata) {
public Metadata getAnnotationData() {
return metadata.getAnnotationData$kotlinx_metadata_jvm();
}
}

View File

@@ -30,6 +30,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -53,6 +54,7 @@ import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.bundling.ZipEntryCompression;
import org.gradle.build.event.BuildEventsListenerRegistry;
import org.gradle.jvm.tasks.Jar;
import org.gradle.workers.WorkAction;
@@ -74,6 +76,7 @@ public abstract class AbstractRemapJarTask extends Jar {
public static final String MANIFEST_NAMESPACE_KEY = "Fabric-Mapping-Namespace";
public static final String MANIFEST_SPLIT_ENV_KEY = "Fabric-Loom-Split-Environment";
public static final String MANIFEST_CLIENT_ENTRIES_KEY = "Fabric-Loom-Client-Only-Entries";
public static final String MANIFEST_JAR_TYPE_KEY = "Fabric-Jar-Type";
public static final Attributes.Name MANIFEST_SPLIT_ENV_NAME = new Attributes.Name(MANIFEST_SPLIT_ENV_KEY);
public static final Attributes.Name MANIFEST_CLIENT_ENTRIES_NAME = new Attributes.Name(MANIFEST_CLIENT_ENTRIES_KEY);
@@ -111,6 +114,11 @@ public abstract class AbstractRemapJarTask extends Jar {
@Optional
public abstract Property<String> getClientOnlySourceSetName();
@Input
@Optional
@ApiStatus.Internal
public abstract Property<String> getJarType();
private final Provider<JarManifestService> jarManifestServiceProvider;
@Inject
@@ -119,6 +127,7 @@ public abstract class AbstractRemapJarTask extends Jar {
getTargetNamespace().convention(IntermediaryNamespaces.intermediary(getProject())).finalizeValueOnRead();
getRemapperIsolation().convention(true).finalizeValueOnRead();
getIncludesClientOnlyClasses().convention(false).finalizeValueOnRead();
getJarType().finalizeValueOnRead();
jarManifestServiceProvider = JarManifestService.get(getProject());
usesService(jarManifestServiceProvider);
@@ -138,14 +147,20 @@ public abstract class AbstractRemapJarTask extends Jar {
params.getArchiveReproducibleFileOrder().set(isReproducibleFileOrder());
params.getJarManifestService().set(jarManifestServiceProvider);
params.getEntryCompression().set(getEntryCompression());
if (getIncludesClientOnlyClasses().get()) {
final List<String> clientOnlyEntries = new ArrayList<>(getClientOnlyEntries(getClientSourceSet()));
clientOnlyEntries.addAll(getAdditionalClientOnlyEntries().get());
Collections.sort(clientOnlyEntries);
applyClientOnlyManifestAttributes(params, clientOnlyEntries);
params.getClientOnlyEntries().set(clientOnlyEntries.stream().filter(s -> s.endsWith(".class")).toList());
}
if (getJarType().isPresent()) {
params.getManifestAttributes().put(MANIFEST_JAR_TYPE_KEY, getJarType().get());
}
action.execute(params);
});
}
@@ -161,6 +176,7 @@ public abstract class AbstractRemapJarTask extends Jar {
Property<Boolean> getArchivePreserveFileTimestamps();
Property<Boolean> getArchiveReproducibleFileOrder();
Property<ZipEntryCompression> getEntryCompression();
Property<JarManifestService> getJarManifestService();
MapProperty<String, String> getManifestAttributes();
@@ -203,9 +219,10 @@ public abstract class AbstractRemapJarTask extends Jar {
protected void rewriteJar() throws IOException {
final boolean isReproducibleFileOrder = getParameters().getArchiveReproducibleFileOrder().get();
final boolean isPreserveFileTimestamps = getParameters().getArchivePreserveFileTimestamps().get();
final ZipEntryCompression compression = getParameters().getEntryCompression().get();
if (isReproducibleFileOrder || !isPreserveFileTimestamps) {
ZipReprocessorUtil.reprocessZip(outputFile.toFile(), isReproducibleFileOrder, isPreserveFileTimestamps);
if (isReproducibleFileOrder || !isPreserveFileTimestamps || compression != ZipEntryCompression.DEFLATED) {
ZipReprocessorUtil.reprocessZip(outputFile.toFile(), isReproducibleFileOrder, isPreserveFileTimestamps, compression);
}
}
}

View File

@@ -168,6 +168,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
// Make outputs reproducible by default
setReproducibleFileOrder(true);
setPreserveFileTimestamps(false);
getJarType().set("classes");
}
private void setupPreparationTask() {

View File

@@ -51,6 +51,7 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
serviceManagerProvider = BuildSharedServiceManager.createForTask(this, getBuildEventsListenerRegistry());
getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
getJarType().set("sources");
}
@TaskAction

View File

@@ -43,6 +43,7 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.LoomVersions;
public abstract class JarManifestService implements BuildService<JarManifestService.Params> {
interface Params extends BuildServiceParameters {
@@ -63,7 +64,7 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
params.getGradleVersion().set(GradleVersion.current().getVersion());
params.getLoomVersion().set(LoomGradlePlugin.LOOM_VERSION);
params.getMCEVersion().set(Constants.Dependencies.Versions.MIXIN_COMPILE_EXTENSIONS);
params.getMCEVersion().set(LoomVersions.MIXIN_COMPILE_EXTENSIONS.version());
params.getMinecraftVersion().set(project.provider(() -> extension.getMinecraftProvider().minecraftVersion()));
params.getTinyRemapperVersion().set(tinyRemapperVersion.orElse("unknown"));
params.getFabricLoaderVersion().set(project.provider(() -> Optional.ofNullable(extension.getInstallerData()).map(InstallerData::version).orElse("unknown")));

View File

@@ -34,6 +34,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import dev.architectury.tinyremapper.IMappingProvider;
@@ -78,6 +79,8 @@ public class TinyRemapperService implements SharedService {
joiner.add(project.getPath());
}
extension.getKnownIndyBsms().get().stream().sorted().forEach(joiner::add);
if (extension.isForge()) {
joiner.add("forge");
}
@@ -92,7 +95,7 @@ public class TinyRemapperService implements SharedService {
mappings.add(gradleMixinMappingProvider(serviceManager, project.getGradle(), extension.getMappingConfiguration().mappingsIdentifier, from, to));
}
return new TinyRemapperService(mappings, !legacyMixin, kotlinClasspathService);
return new TinyRemapperService(mappings, !legacyMixin, kotlinClasspathService, extension.getKnownIndyBsms().get());
});
service.readClasspath(remapJarTask.getClasspath().getFiles().stream().map(File::toPath).filter(Files::exists).toList());
@@ -132,8 +135,8 @@ public class TinyRemapperService implements SharedService {
// Set to true once remapping has started, once set no inputs can be read.
private boolean isRemapping = false;
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension, @Nullable KotlinClasspath kotlinClasspath) {
TinyRemapper.Builder builder = TinyRemapper.newRemapper();
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension, @Nullable KotlinClasspath kotlinClasspath, Set<String> knownIndyBsms) {
TinyRemapper.Builder builder = TinyRemapper.newRemapper().withKnownIndyBsm(knownIndyBsms);
for (IMappingProvider provider : mappings) {
builder.withMappings(provider);

View File

@@ -108,46 +108,6 @@ public class Constants {
}
}
/**
* Constants related to dependencies.
*/
public static final class Dependencies {
public static final String MIXIN_COMPILE_EXTENSIONS = "net.fabricmc:fabric-mixin-compile-extensions:";
public static final String DEV_LAUNCH_INJECTOR = "net.fabricmc:dev-launch-injector:";
public static final String TERMINAL_CONSOLE_APPENDER = "net.minecrell:terminalconsoleappender:";
public static final String JETBRAINS_ANNOTATIONS = "org.jetbrains:annotations:";
public static final String NATIVE_SUPPORT = "net.fabricmc:fabric-loom-native-support:";
public static final String JAVAX_ANNOTATIONS = "com.google.code.findbugs:jsr305:"; // I hate that I have to add these.
public static final String FORGE_RUNTIME = "dev.architectury:architectury-loom-runtime:";
public static final String ACCESS_TRANSFORMERS = "net.minecraftforge:accesstransformers:";
public static final String UNPROTECT = "io.github.juuxel:unprotect:";
// Used to upgrade the ASM version for the AT tool.
public static final String ASM = "org.ow2.asm:asm:";
private Dependencies() {
}
/**
* Constants for versions of dependencies.
*/
public static final class Versions {
public static final String MIXIN_COMPILE_EXTENSIONS = "0.6.0";
public static final String DEV_LAUNCH_INJECTOR = "0.2.1+build.8";
public static final String TERMINAL_CONSOLE_APPENDER = "1.2.0";
public static final String JETBRAINS_ANNOTATIONS = "24.0.1";
public static final String NATIVE_SUPPORT_VERSION = "1.0.1";
public static final String JAVAX_ANNOTATIONS = "3.0.2";
public static final String FORGE_RUNTIME = "1.1.8";
public static final String ACCESS_TRANSFORMERS = "3.0.1";
public static final String ACCESS_TRANSFORMERS_NEW = "8.0.5";
public static final String UNPROTECT = "1.2.0";
public static final String ASM = "9.3";
private Versions() {
}
}
}
public static final class MixinArguments {
public static final String IN_MAP_FILE_NAMED_INTERMEDIARY = "inMapFileNamedIntermediary";
public static final String OUT_MAP_FILE_NAMED_INTERMEDIARY = "outMapFileNamedIntermediary";

View File

@@ -206,7 +206,7 @@ public class SourceRemapper {
}
Set<File> files = project.getConfigurations()
.detachedConfiguration(project.getDependencies().create(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS))
.detachedConfiguration(project.getDependencies().create(LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation()))
.resolve();
for (File file : files) {

View File

@@ -90,6 +90,7 @@ public final class TinyRemapperHelper {
.rebuildSourceFilenames(true)
.invalidLvNamePattern(MC_LV_PATTERN)
.inferNameFromSameLvIndex(true)
.withKnownIndyBsm(extension.getKnownIndyBsms().get())
.extraPreApplyVisitor((cls, next) -> {
if (fixRecords && !cls.isRecord() && "java/lang/Record".equals(cls.getSuperName())) {
return new RecordComponentFixVisitor(next, mappingTree, intermediaryNsId);

View File

@@ -30,7 +30,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.Calendar;
import java.util.Comparator;
import java.util.GregorianCalendar;
@@ -38,12 +37,10 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class ZipReprocessorUtil {
/**
* See {@link org.gradle.api.internal.file.archive.ZipCopyAction} about this.
*/
private static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = new GregorianCalendar(1980, Calendar.FEBRUARY, 1, 0, 0, 0).getTimeInMillis();
import org.gradle.api.tasks.bundling.ZipEntryCompression;
import org.intellij.lang.annotations.MagicConstant;
public class ZipReprocessorUtil {
private ZipReprocessorUtil() { }
private static final String MANIFEST_LOCATION = "META-INF/MANIFEST.MF";
@@ -92,6 +89,10 @@ public class ZipReprocessorUtil {
}
public static void reprocessZip(File file, boolean reproducibleFileOrder, boolean preserveFileTimestamps) throws IOException {
reprocessZip(file, reproducibleFileOrder, preserveFileTimestamps, ZipEntryCompression.DEFLATED);
}
public static void reprocessZip(File file, boolean reproducibleFileOrder, boolean preserveFileTimestamps, ZipEntryCompression zipEntryCompression) throws IOException {
if (!reproducibleFileOrder && preserveFileTimestamps) {
return;
}
@@ -111,6 +112,8 @@ public class ZipReprocessorUtil {
final var outZip = new ByteArrayOutputStream(entries.length);
try (var zipOutputStream = new ZipOutputStream(outZip)) {
zipOutputStream.setMethod(zipOutputStreamCompressionMethod(zipEntryCompression));
for (ZipEntry entry : entries) {
ZipEntry newEntry = entry;
@@ -119,6 +122,7 @@ public class ZipReprocessorUtil {
setConstantFileTime(newEntry);
}
newEntry.setMethod(zipEntryCompressionMethod(zipEntryCompression));
copyZipEntry(zipOutputStream, newEntry, zipFile.getInputStream(entry));
}
}
@@ -173,8 +177,23 @@ public class ZipReprocessorUtil {
}
private static void setConstantFileTime(ZipEntry entry) {
entry.setTime(ZipReprocessorUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES);
entry.setLastModifiedTime(FileTime.fromMillis(ZipReprocessorUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES));
entry.setLastAccessTime(FileTime.fromMillis(ZipReprocessorUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES));
// See https://github.com/openjdk/jdk/blob/master/test/jdk/java/util/zip/ZipFile/ZipEntryTimeBounds.java
entry.setTime(new GregorianCalendar(1980, Calendar.JANUARY, 1, 0, 0, 0).getTimeInMillis());
}
@MagicConstant(valuesFromClass = ZipOutputStream.class)
private static int zipOutputStreamCompressionMethod(ZipEntryCompression compression) {
return switch (compression) {
case STORED -> ZipOutputStream.STORED;
case DEFLATED -> ZipOutputStream.DEFLATED;
};
}
@MagicConstant(valuesFromClass = ZipEntry.class)
private static int zipEntryCompressionMethod(ZipEntryCompression compression) {
return switch (compression) {
case STORED -> ZipEntry.STORED;
case DEFLATED -> ZipEntry.DEFLATED;
};
}
}

View File

@@ -62,9 +62,11 @@ import net.fabricmc.loom.util.Checksum;
public final class Download {
private static final String E_TAG = "ETag";
private static final Logger LOGGER = LoggerFactory.getLogger(Download.class);
private static final Duration TIMEOUT = Duration.ofMinutes(1);
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.proxy(ProxySelector.getDefault())
.connectTimeout(TIMEOUT)
.build();
public static DownloadBuilder create(String url) throws URISyntaxException {
@@ -93,17 +95,20 @@ public final class Download {
this.downloadAttempt = downloadAttempt;
}
private HttpRequest getRequest() {
private HttpRequest.Builder requestBuilder() {
return HttpRequest.newBuilder(url)
.timeout(TIMEOUT)
.version(httpVersion)
.GET()
.GET();
}
private HttpRequest getRequest() {
return requestBuilder()
.build();
}
private HttpRequest getETagRequest(String etag) {
return HttpRequest.newBuilder(url)
.version(httpVersion)
.GET()
return requestBuilder()
.header("If-None-Match", etag)
.build();
}
@@ -129,7 +134,7 @@ public final class Download {
if (!successful) {
progressListener.onEnd();
throw error("HTTP request to (%s) returned unsuccessful status (%d)", url, statusCode);
throw statusError("HTTP request to (%s) returned unsuccessful status".formatted(url) + "(%d)", statusCode);
}
try (InputStream inputStream = decodeOutput(response)) {
@@ -190,47 +195,12 @@ public final class Download {
return;
}
if (success) {
try {
Files.deleteIfExists(output);
} catch (IOException e) {
throw error(e, "Failed to delete existing file");
}
final long length = Long.parseLong(response.headers().firstValue("Content-Length").orElse("-1"));
AtomicLong totalBytes = new AtomicLong(0);
try (OutputStream outputStream = Files.newOutputStream(output, StandardOpenOption.CREATE_NEW)) {
copyWithCallback(decodeOutput(response), outputStream, value -> {
if (length < 0) {
return;
}
progressListener.onProgress(totalBytes.addAndGet(value), length);
});
} catch (IOException e) {
throw error(e, "Failed to decode and write download output");
}
if (Files.notExists(output)) {
throw error("No file was downloaded");
}
if (length > 0) {
try {
final long actualLength = Files.size(output);
if (actualLength != length) {
throw error("Unexpected file length of %d bytes, expected %d bytes".formatted(actualLength, length));
}
} catch (IOException e) {
throw error(e);
}
}
} else {
throw error("HTTP request returned unsuccessful status (%d)", statusCode);
if (!success) {
throw statusError("HTTP request returned unsuccessful status (%d)", statusCode);
}
downloadToPath(output, response);
if (useEtag) {
final HttpHeaders headers = response.headers();
final String responseETag = headers.firstValue(E_TAG.toLowerCase(Locale.ROOT)).orElse(null);
@@ -260,6 +230,58 @@ public final class Download {
}
}
private void downloadToPath(Path output, HttpResponse<InputStream> response) throws DownloadException {
// Download the file initially to a .part file
final Path partFile = getPartFile(output);
try {
Files.deleteIfExists(output);
Files.deleteIfExists(partFile);
} catch (IOException e) {
throw error(e, "Failed to delete existing file");
}
final long length = Long.parseLong(response.headers().firstValue("Content-Length").orElse("-1"));
AtomicLong totalBytes = new AtomicLong(0);
try (OutputStream outputStream = Files.newOutputStream(partFile, StandardOpenOption.CREATE_NEW)) {
copyWithCallback(decodeOutput(response), outputStream, value -> {
if (length < 0) {
return;
}
progressListener.onProgress(totalBytes.addAndGet(value), length);
});
} catch (IOException e) {
throw error(e, "Failed to decode and write download output");
}
if (Files.notExists(partFile)) {
throw error("No file was downloaded");
}
if (length > 0) {
try {
final long actualLength = Files.size(partFile);
if (actualLength != length) {
throw error("Unexpected file length of %d bytes, expected %d bytes".formatted(actualLength, length));
}
} catch (IOException e) {
throw error(e);
}
}
try {
// Once the file has been fully read, create a hard link to the destination file.
// And then remove the temporary file, this ensures that the output file only exists in fully populated state.
Files.createLink(output, partFile);
Files.delete(partFile);
} catch (IOException e) {
throw error(e, "Failed to complete download");
}
}
private void copyWithCallback(InputStream is, OutputStream os, IntConsumer consumer) throws IOException {
byte[] buffer = new byte[1024];
int length;
@@ -389,6 +411,18 @@ public final class Download {
} catch (IOException ignored) {
// ignored
}
try {
Files.deleteIfExists(getLockFile(output));
} catch (IOException ignored) {
// ignored
}
try {
Files.deleteIfExists(getPartFile(output));
} catch (IOException ignored) {
// ignored
}
}
// A faster exists check
@@ -405,6 +439,10 @@ public final class Download {
return output.resolveSibling(output.getFileName() + ".lock");
}
private Path getPartFile(Path output) {
return output.resolveSibling(output.getFileName() + ".part");
}
private boolean getAndResetLock(Path output) throws DownloadException {
final Path lock = getLockFile(output);
final boolean exists = exists(lock);
@@ -430,6 +468,10 @@ public final class Download {
}
}
private DownloadException statusError(String message, int statusCode) {
return new DownloadException(String.format(Locale.ENGLISH, message, statusCode), statusCode);
}
private DownloadException error(String message, Object... args) {
return new DownloadException(String.format(Locale.ENGLISH, message, args));
}

View File

@@ -158,6 +158,11 @@ public class DownloadBuilder {
return supplier.get(build(i));
} catch (DownloadException e) {
if (e.getStatusCode() == 404) {
// Don't retry on 404's
throw e;
}
if (i == maxRetries) {
throw new DownloadException(String.format(Locale.ENGLISH, "Failed download after %d attempts", maxRetries), e);
}

View File

@@ -27,15 +27,32 @@ package net.fabricmc.loom.util.download;
import java.io.IOException;
public class DownloadException extends IOException {
private final int statusCode;
public DownloadException(String message) {
super(message);
statusCode = -1;
}
public DownloadException(String message, int statusCode) {
super(message);
this.statusCode = statusCode;
}
public DownloadException(String message, Throwable cause) {
super(message, cause);
statusCode = cause instanceof DownloadException downloadException ? downloadException.getStatusCode() : -1;
}
public DownloadException(Throwable cause) {
super(cause);
statusCode = cause instanceof DownloadException downloadException ? downloadException.getStatusCode() : -1;
}
/**
* @return -1 when the status code is unknown.
*/
public int getStatusCode() {
return statusCode;
}
}

View File

@@ -58,18 +58,18 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp
when (val metadata = KotlinClassMetadata.read(header)) {
is KotlinClassMetadata.Class -> {
var klass = metadata.toKmClass()
var klass = metadata.kmClass
klass = KotlinClassRemapper(remapper).remap(klass)
val remapped = KotlinClassMetadata.writeClass(klass, header.metadataVersion, header.extraInt).annotationData
val remapped = KotlinClassMetadata.writeClass(klass, header.metadataVersion, header.extraInt)
writeClassHeader(remapped)
validateKotlinClassHeader(remapped, header)
}
is KotlinClassMetadata.SyntheticClass -> {
var klambda = metadata.toKmLambda()
var klambda = metadata.kmLambda
if (klambda != null) {
klambda = KotlinClassRemapper(remapper).remap(klambda)
val remapped = KotlinClassMetadata.writeLambda(klambda, header.metadataVersion, header.extraInt).annotationData
val remapped = KotlinClassMetadata.writeLambda(klambda, header.metadataVersion, header.extraInt)
writeClassHeader(remapped)
validateKotlinClassHeader(remapped, header)
} else {
@@ -77,20 +77,21 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp
}
}
is KotlinClassMetadata.FileFacade -> {
var kpackage = metadata.toKmPackage()
var kpackage = metadata.kmPackage
kpackage = KotlinClassRemapper(remapper).remap(kpackage)
val remapped = KotlinClassMetadata.writeFileFacade(kpackage, header.metadataVersion, header.extraInt).annotationData
val remapped = KotlinClassMetadata.writeFileFacade(kpackage, header.metadataVersion, header.extraInt)
writeClassHeader(remapped)
validateKotlinClassHeader(remapped, header)
}
is KotlinClassMetadata.MultiFileClassPart -> {
var kpackage = metadata.toKmPackage()
var kpackage = metadata.kmPackage
kpackage = KotlinClassRemapper(remapper).remap(kpackage)
val remapped = KotlinClassMetadata.writeMultiFileClassPart(kpackage, metadata.facadeClassName, metadata.annotationData.metadataVersion, metadata.annotationData.extraInt).annotationData
val wrapper = KotlinClassMetadataWrapper(metadata)
val remapped = KotlinClassMetadata.writeMultiFileClassPart(kpackage, metadata.facadeClassName, wrapper.annotationData.metadataVersion, wrapper.annotationData.extraInt)
writeClassHeader(remapped)
validateKotlinClassHeader(remapped, header)
}
is KotlinClassMetadata.MultiFileClassFacade, is KotlinClassMetadata.Unknown, null -> {
is KotlinClassMetadata.MultiFileClassFacade, is KotlinClassMetadata.Unknown -> {
// do nothing
accept(next)
}

View File

@@ -49,10 +49,10 @@ import kotlinx.metadata.internal.extensions.KmTypeAliasExtension
import kotlinx.metadata.internal.extensions.KmTypeExtension
import kotlinx.metadata.internal.extensions.KmTypeParameterExtension
import kotlinx.metadata.internal.extensions.KmValueParameterExtension
import kotlinx.metadata.isLocal
import kotlinx.metadata.isLocalClassName
import kotlinx.metadata.jvm.JvmFieldSignature
import kotlinx.metadata.jvm.JvmMethodSignature
import kotlinx.metadata.jvm.jvmInternalName
import kotlinx.metadata.jvm.toJvmInternalName
import org.objectweb.asm.commons.Remapper
@OptIn(ExperimentalContextReceivers::class)
@@ -86,8 +86,8 @@ class KotlinClassRemapper(private val remapper: Remapper) {
}
private fun remap(name: ClassName): ClassName {
val local = name.isLocal
val remapped = remapper.map(name.jvmInternalName).replace('$', '.')
val local = name.isLocalClassName()
val remapped = remapper.map(name.toJvmInternalName()).replace('$', '.')
if (local) {
return ".$remapped"
@@ -241,10 +241,10 @@ class KotlinClassRemapper(private val remapper: Remapper) {
}
private fun remap(signature: JvmMethodSignature): JvmMethodSignature {
return JvmMethodSignature(signature.name, remapper.mapMethodDesc(signature.desc))
return JvmMethodSignature(signature.name, remapper.mapMethodDesc(signature.descriptor))
}
private fun remap(signature: JvmFieldSignature): JvmFieldSignature {
return JvmFieldSignature(signature.name, remapper.mapDesc(signature.desc))
return JvmFieldSignature(signature.name, remapper.mapDesc(signature.descriptor))
}
}

View File

@@ -4,6 +4,8 @@
<!-- System out -->
<Console name="SysOut" target="SYSTEM_OUT">
<!-- Filter out the authentication error when starting in development -->
<RegexFilter regex="^Failed to verify authentication$" onMatch="DENY" onMismatch="ACCEPT"/>
<PatternLayout>
<LoggerNamePatternSelector defaultPattern="%style{[%d{HH:mm:ss}]}{blue} %highlight{[%t/%level]}{FATAL=red, ERROR=red, WARN=yellow, INFO=green, DEBUG=green, TRACE=blue} %style{(%logger{1})}{cyan} %highlight{%msg%n}{FATAL=red, ERROR=red, WARN=normal, INFO=normal, DEBUG=normal, TRACE=normal}" disableAnsi="${sys:fabric.log.disableAnsi:-true}">
<!-- Dont show the logger name for minecraft classes-->

View File

@@ -27,7 +27,7 @@ package net.fabricmc.loom.test
import org.gradle.util.GradleVersion
class LoomTestConstants {
private final static String NIGHTLY_VERSION = "8.3-20230702222859+0000"
private final static String NIGHTLY_VERSION = LoomTestVersions.GRADLE_NIGHTLY.version()
private final static boolean NIGHTLY_EXISTS = nightlyExists(NIGHTLY_VERSION)
// Test against the version of Gradle being used to build loom

View File

@@ -48,6 +48,7 @@ class DecompileTest extends Specification implements GradleProjectTestTrait {
decompiler | task | version
'fernflower' | "genSourcesWithFernFlower" | PRE_RELEASE_GRADLE
'cfr' | "genSourcesWithCfr" | PRE_RELEASE_GRADLE
'vineflower' | "genSourcesWithVineflower" | PRE_RELEASE_GRADLE
}
@Unroll

View File

@@ -45,7 +45,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
setup:
def gradle = gradleProject(
repo: "https://github.com/FabricMC/fabric.git",
commit: "1ac061308b9d70fa6aad5db3dcc5580cb6ac71cb",
commit: "f091af96c53963fadf9dbc391c67bb40e5678a96",
version: version,
patch: "fabric_api"
)
@@ -57,7 +57,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
.replace('id "fabric-loom" version "0.9.50"', 'id "dev.architectury.loom"')
.replace('"fabric-loom"', '"dev.architectury.loom"')
def server = ServerRunner.create(gradle.projectDir, "1.20.1")
def server = ServerRunner.create(gradle.projectDir, "23w33a")
.withMod(gradle.getOutputFile("fabric-api-${API_VERSION}.jar"))
when:
def result = gradle.run(tasks: [
@@ -79,8 +79,8 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
result.task(":build").outcome == SUCCESS
result.task(":prepareRemapJar").outcome == SUCCESS
new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/13.0.10/fabric-biome-api-v1-13.0.10.jar").exists()
new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/13.0.10/fabric-biome-api-v1-13.0.10-sources.jar").exists()
new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/13.0.11/fabric-biome-api-v1-13.0.11.jar").exists()
new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/13.0.11/fabric-biome-api-v1-13.0.11-sources.jar").exists()
serverResult.successful()
serverResult.output.contains("- fabric-api $API_VERSION")

View File

@@ -55,13 +55,13 @@ class ReproducibleBuildTest extends Specification implements GradleProjectTestTr
where:
version | modHash | sourceHash
DEFAULT_GRADLE | "174c9b52f4bc6d489548d11b42e853cf" | [
"5e6e56df303b4fbaaef372d6f143dbfc",
"92b6fbffd0bd14bf3c626750eb86c264"
DEFAULT_GRADLE | "97240b42385adfaa1952e9c4ea942f71" | [
"61438feb9bd548788bbc637637d202fc",
"185ad8396d89b726064682bf22572036"
]
PRE_RELEASE_GRADLE | "174c9b52f4bc6d489548d11b42e853cf" | [
"5e6e56df303b4fbaaef372d6f143dbfc",
"92b6fbffd0bd14bf3c626750eb86c264"
PRE_RELEASE_GRADLE | "97240b42385adfaa1952e9c4ea942f71" | [
"61438feb9bd548788bbc637637d202fc",
"185ad8396d89b726064682bf22572036"
]
}

View File

@@ -0,0 +1,69 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit
import org.gradle.api.Project
import spock.lang.Specification
import net.fabricmc.loom.configuration.FabricApiExtension
import net.fabricmc.loom.test.util.GradleTestUtil
class FabricApiExtensionTest extends Specification {
def "get module version"() {
when:
def fabricApi = new FabricApiExtension() {
Project project = GradleTestUtil.mockProject()
}
def version = fabricApi.moduleVersion(moduleName, apiVersion)
then:
version == expectedVersion
where:
moduleName | apiVersion | expectedVersion
"fabric-api-base" | "0.88.3+1.20.2" | "0.4.32+fce67b3299" // Normal module, new version
"fabric-api-base" | "0.13.1+build.257-1.14" | "0.1.2+28f8190f42" // Normal module, old version before deprecated modules.
"fabric-networking-v0" | "0.88.0+1.20.1" | "0.3.50+df3654b377" // Deprecated module, opt-out version
"fabric-networking-v0" | "0.85.0+1.20.1" | "0.3.48+df3654b377" // Deprecated module, opt-in version
}
def "unknown module"() {
when:
def fabricApi = new FabricApiExtension() {
Project project = GradleTestUtil.mockProject()
}
fabricApi.moduleVersion("fabric-api-unknown", apiVersion)
then:
def e = thrown RuntimeException
e.getMessage() == "Failed to find module version for module: fabric-api-unknown"
where:
apiVersion | _
"0.88.0+1.20.1" | _ // Deprecated opt-out
"0.85.0+1.20.1" | _ // Deprecated opt-int
"0.13.1+build.257-1.14" | _ // No deprecated modules
}
}

View File

@@ -26,6 +26,7 @@ package net.fabricmc.loom.test.unit
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.time.ZoneId
import spock.lang.Specification
@@ -155,6 +156,9 @@ class ZipUtilsTest extends Specification {
def "append zip entry"() {
given:
def currentTimezone = TimeZone.getDefault()
TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of(timezone)))
// Create a reproducible input zip
def dir = Files.createTempDirectory("loom-zip-test")
def zip = Files.createTempFile("loom-zip-test", ".zip")
@@ -167,9 +171,21 @@ class ZipUtilsTest extends Specification {
// Add an entry to it
ZipReprocessorUtil.appendZipEntry(zip.toFile(), "fabric.mod.json", "Some text".getBytes(StandardCharsets.UTF_8))
// Reset the timezone back
TimeZone.setDefault(currentTimezone)
then:
ZipUtils.unpack(zip, "text.txt") == "hello world".bytes
ZipUtils.unpack(zip, "fabric.mod.json") == "Some text".bytes
Checksum.sha1Hex(zip) == "232ecda4c770bde8ba618e7a194a4f7b57928dc5"
Checksum.sha1Hex(zip) == "1b06cc0aaa65ab2b0d423fe33431ff5bd14bf9c8"
where:
timezone | _
"UTC" | _
"US/Central" | _
"Europe/London" | _
"Australia/Sydney" | _
"Etc/GMT-6" | _
"Etc/GMT+9" | _
}
}

View File

@@ -76,16 +76,33 @@ class DownloadFileTest extends DownloadTest {
def "File: Not found"() {
setup:
server.get("/fileNotfound") {
it.status(404)
it.status(HttpStatus.NOT_FOUND)
}
def output = new File(File.createTempDir(), "file.txt").toPath()
when:
def result = Download.create("$PATH/stringNotFound").downloadPath(output)
def result = Download.create("$PATH/fileNotfound").downloadPath(output)
then:
thrown DownloadException
def e = thrown DownloadException
e.statusCode == 404
}
def "File: Server error"() {
setup:
server.get("/fileServerError") {
it.status(HttpStatus.INTERNAL_SERVER_ERROR)
}
def output = new File(File.createTempDir(), "file.txt").toPath()
when:
def result = Download.create("$PATH/fileServerError").downloadPath(output)
then:
def e = thrown DownloadException
e.statusCode == 500
}
def "Cache: Sha1"() {

View File

@@ -46,7 +46,7 @@ class DownloadStringTest extends DownloadTest {
def "String: Not found"() {
setup:
server.get("/stringNotFound") {
it.status(404)
it.status(HttpStatus.NOT_FOUND)
}
when:
@@ -55,7 +55,24 @@ class DownloadStringTest extends DownloadTest {
.downloadString()
then:
thrown DownloadException
def e = thrown DownloadException
e.statusCode == 404
}
def "String: Server error"() {
setup:
server.get("/stringNotFound") {
it.status(HttpStatus.INTERNAL_SERVER_ERROR)
}
when:
def result = Download.create("$PATH/stringNotFound")
.maxRetries(3) // Ensure we still error as expected when retrying
.downloadString()
then:
def e = thrown DownloadException
e.statusCode == 500
}
def "String: Redirect"() {
@@ -97,6 +114,25 @@ class DownloadStringTest extends DownloadTest {
result == "Hello World 3"
}
def "String: Retries 404"() {
setup:
int requests = 0
server.get("/retryString") {
requests ++
it.status(HttpStatus.NOT_FOUND)
}
when:
def result = Download.create("$PATH/retryString")
.maxRetries(3)
.downloadString()
then:
def e = thrown DownloadException
e.statusCode == 404
requests == 1
}
def "String: File cache"() {
setup:
server.get("/downloadString2") {

View File

@@ -36,12 +36,16 @@ import org.gradle.api.provider.Property
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.util.PatternFilterable
import org.jetbrains.annotations.Nullable
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer
import net.fabricmc.loom.LoomGradleExtension
import net.fabricmc.loom.extension.LoomFiles
import net.fabricmc.loom.test.LoomTestConstants
import net.fabricmc.loom.util.download.Download
import static org.mockito.ArgumentMatchers.any
import static org.mockito.Mockito.mock
import static org.mockito.Mockito.when
import static org.mockito.Mockito.*
class GradleTestUtil {
static <T> Property<T> mockProperty(T value) {
@@ -73,7 +77,18 @@ class GradleTestUtil {
static LoomGradleExtension mockLoomGradleExtension() {
def mock = mock(LoomGradleExtension.class)
def loomFiles = mockLoomFiles()
when(mock.refreshDeps()).thenReturn(false)
when(mock.getFiles()).thenReturn(loomFiles)
when(mock.download(any())).thenAnswer {
Download.create(it.getArgument(0))
}
return mock
}
static LoomFiles mockLoomFiles() {
def mock = mock(LoomFiles.class, new RequiresStubAnswer())
doReturn(LoomTestConstants.TEST_DIR).when(mock).getUserCache()
return mock
}
@@ -121,4 +136,10 @@ class GradleTestUtil {
def mock = mock(RepositoryHandler.class)
return mock
}
static class RequiresStubAnswer implements Answer<Object> {
Object answer(InvocationOnMock invocation) throws Throwable {
throw new RuntimeException("${invocation.getMethod().getName()} is not stubbed")
}
}
}

View File

@@ -28,11 +28,12 @@ import java.util.concurrent.TimeUnit
import groovy.transform.Immutable
import net.fabricmc.loom.test.LoomTestVersions
import net.fabricmc.loom.util.download.Download
class ServerRunner {
static final String LOADER_VERSION = "0.14.21"
static final String INSTALLER_VERSION = "0.11.1"
static final String LOADER_VERSION = LoomTestVersions.FABRIC_LOADER.version()
static final String INSTALLER_VERSION = LoomTestVersions.FABRIC_INSTALLER.version()
static final Map<String, String> FABRIC_API_URLS = [
"1.16.5": "https://github.com/FabricMC/fabric/releases/download/0.37.1%2B1.16/fabric-api-0.37.1+1.16.jar",
"1.17.1": "https://github.com/FabricMC/fabric/releases/download/0.37.1%2B1.17/fabric-api-0.37.1+1.17.jar"