diff --git a/.github/workflows/test-push.yml b/.github/workflows/test-push.yml index b9c6ee1f7..919565614 100644 --- a/.github/workflows/test-push.yml +++ b/.github/workflows/test-push.yml @@ -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 }} @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-22.04 container: - image: gradle:8.1.0-jdk17 + image: gradle:8.3.0-jdk17 options: --user root steps: @@ -46,7 +46,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 diff --git a/.gitignore b/.gitignore index 4354f60fd..664ed03e5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,6 @@ !/Jenkinsfile !/checkstyle.xml !/codenarc.groovy -!/bootstrap \ No newline at end of file +!/bootstrap + +/src/**/generated \ No newline at end of file diff --git a/bootstrap/build.gradle b/bootstrap/build.gradle index a92baf401..30b987a2e 100644 --- a/bootstrap/build.gradle +++ b/bootstrap/build.gradle @@ -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" diff --git a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java b/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java index c8a45de03..bd420dd00 100644 --- a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java +++ b/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java @@ -14,7 +14,7 @@ */ @SuppressWarnings("unused") public class LoomGradlePluginBootstrap implements Plugin { - 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; diff --git a/build.gradle b/build.gradle index c7ff152af..014f8da9a 100644 --- a/build.gradle +++ b/build.gradle @@ -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 = 'net.fabricmc' -archivesBaseName = project.name -def baseVersion = '1.3' +def baseVersion = '1.4' def ENV = System.getenv() if (ENV.BUILD_NUMBER) { @@ -63,45 +88,79 @@ 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 libs.commons.io + implementation libs.gson + implementation libs.jackson + implementation libs.guava + implementation libs.bundles.asm // game handling utils - implementation ('net.fabricmc:stitch:0.6.2') { + implementation (libs.fabric.stitch) { exclude module: 'enigma' } // tinyfile management - implementation ('net.fabricmc:tiny-remapper:0.8.7') - 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 } // 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 ('net.fabricmc:mercury:0.3.0') + implementation libs.fabric.mercury // Kotlin - implementation('org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.2') { + implementation(libs.kotlin.metadata) { transitive = false } @@ -110,20 +169,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 } } @@ -134,10 +194,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 @@ -145,6 +210,8 @@ tasks.withType(JavaCompile).configureEach { java { withSourcesJar() + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } spotless { @@ -174,11 +241,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") } @@ -192,7 +259,7 @@ gradlePlugin { } jacoco { - toolVersion = "0.8.8" + toolVersion = libs.versions.jacoco.get() } // Run to get test coverage. @@ -201,7 +268,7 @@ jacocoTestReport { reports { xml.required = false csv.required = false - html.outputLocation = file("${buildDir}/jacocoHtml") + html.outputLocation = file("${layout.buildDirectory.get().asFile}/jacocoHtml") } } @@ -223,6 +290,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 @@ -234,7 +303,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 @@ -360,3 +429,5 @@ class PrintActionsTestName extends DefaultTask { new File(System.getenv().GITHUB_OUTPUT) << "\ntest=$sanitised" } } + +apply from: rootProject.file('gradle/versions.gradle') \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a8f38ae50..08e91098d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } \ No newline at end of file +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"] diff --git a/gradle/runtime.libs.versions.toml b/gradle/runtime.libs.versions.toml new file mode 100644 index 000000000..fc707f6c9 --- /dev/null +++ b/gradle/runtime.libs.versions.toml @@ -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" } \ No newline at end of file diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml new file mode 100644 index 000000000..1c637f779 --- /dev/null +++ b/gradle/test.libs.versions.toml @@ -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" } \ No newline at end of file diff --git a/gradle/versions.gradle b/gradle/versions.gradle new file mode 100644 index 000000000..77f7890f5 --- /dev/null +++ b/gradle/versions.gradle @@ -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 getVersions() + + @Input + abstract Property 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() + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e..7f93135c4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a21c6ebe2..d11cdd907 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew index aeb74cbb4..0adc8e1a5 100755 --- a/gradlew +++ b/gradlew @@ -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. diff --git a/settings.gradle b/settings.gradle index 885070a6d..039a49da2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,14 @@ rootProject.name = name +dependencyResolutionManagement { + versionCatalogs { + testLibs { + from(files("gradle/test.libs.versions.toml")) + } + runtimeLibs { + from(files("gradle/runtime.libs.versions.toml")) + } + } +} + include "bootstrap" \ No newline at end of file diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java similarity index 98% rename from src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java rename to src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java index aae260196..34236a43f 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java +++ b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java @@ -45,7 +45,6 @@ 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 Dumper wrap(Dumper d) { 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; diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java similarity index 90% rename from src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java rename to src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java index bdc5a28a4..67a89473c 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java +++ b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java @@ -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.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 addedDirectories = new HashSet<>(); private final Map> 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 Sink getSink(SinkType sinkType, SinkClass sinkClass) { return switch (sinkType) { case JAVA -> (Sink) decompiledSink(); case LINENUMBER -> (Sink) lineNumberMappingSink(); - case EXCEPTION -> (e) -> ERROR_LOGGER.error((String) e); + case EXCEPTION -> (e) -> logger.error((String) e); default -> null; }; } @@ -83,7 +79,7 @@ private Sink decompiledSink() { 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); }; diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java similarity index 87% rename from src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java rename to src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java index de0b74d88..72f7b2e5b 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java +++ b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java @@ -45,10 +45,9 @@ 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 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 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 void decompile(Path compiledJar, Path sourcesDestination, Path linemapDes 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 void decompile(Path compiledJar, Path sourcesDestination, Path linemapDes Map> 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 void decompile(Path compiledJar, Path sourcesDestination, Path linemapDes throw new UncheckedIOException("Failed to decompile", e); } - writeLineMap(linemapDestination, lineMap); + writeLineMap(context.linemapDestination(), lineMap); } private void writeLineMap(Path output, Map> lineMap) { diff --git a/src/decompilers/common/net/fabricmc/loom/decompilers/LoomInternalDecompiler.java b/src/decompilers/common/net/fabricmc/loom/decompilers/LoomInternalDecompiler.java new file mode 100644 index 000000000..ad6870527 --- /dev/null +++ b/src/decompilers/common/net/fabricmc/loom/decompilers/LoomInternalDecompiler.java @@ -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 libraries(); + + Logger logger(); + + Map options(); + + byte[] unpackZip(Path zip, String path) throws IOException; + } + + interface Logger { + void accept(String data) throws IOException; + + void error(String msg); + } +} diff --git a/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java new file mode 100644 index 000000000..f0e33ee2c --- /dev/null +++ b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java @@ -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 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); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java similarity index 92% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java rename to src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java index 3699e3a6e..e43e0ecd3 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java +++ b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java @@ -28,12 +28,12 @@ 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; } diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ThreadSafeResultSaver.java b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/ThreadSafeResultSaver.java similarity index 100% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/ThreadSafeResultSaver.java rename to src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/ThreadSafeResultSaver.java diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java similarity index 93% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java rename to src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java index ced8161d1..c80608341 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java +++ b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java @@ -35,16 +35,17 @@ 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 String getClassDoc(StructClass structClass) { 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 String getMethodDoc(StructClass structClass, StructMethod structMethod) { 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 String getMethodDoc(StructClass structClass, StructMethod structMethod) { 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 @@ private static MappingTree readMappings(File input) { } 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; } } diff --git a/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/ThreadSafeResultSaver.java b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/ThreadSafeResultSaver.java new file mode 100644 index 000000000..bb77aa16b --- /dev/null +++ b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/ThreadSafeResultSaver.java @@ -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 output; + private final Supplier lineMapFile; + + public Map outputStreams = new HashMap<>(); + public Map saveExecutors = new HashMap<>(); + public PrintWriter lineMapWriter; + + public ThreadSafeResultSaver(Supplier output, Supplier 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) { + } +} diff --git a/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/TinyJavadocProvider.java b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/TinyJavadocProvider.java new file mode 100644 index 000000000..ba8ada9fc --- /dev/null +++ b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/TinyJavadocProvider.java @@ -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 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 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; + } +} diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java similarity index 73% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java rename to src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java index 2d2041b6f..ad865d735 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java +++ b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java @@ -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.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 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(); diff --git a/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerLogger.java b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerLogger.java new file mode 100644 index 000000000..675636f47 --- /dev/null +++ b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerLogger.java @@ -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 + } +} diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index 9c9c938e2..8231476d4 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -89,7 +89,7 @@ public void apply(Project project) { // 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 jobClass : SETUP_JOBS) { project.getObjects().newInstance(jobClass).run(); diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 45587d963..78584e110 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -35,6 +35,7 @@ import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.ApiStatus; @@ -129,6 +130,8 @@ default void interfaceInjection(Action action) { Property getCustomMinecraftManifest(); + SetProperty 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. diff --git a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java index 8cc57928f..f90f7e494 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java @@ -47,6 +47,7 @@ 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 @@ -147,7 +148,7 @@ public void configureMixin() { // 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()); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java b/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java index e043fef98..732bfa0f3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java +++ b/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java @@ -26,9 +26,11 @@ 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 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> moduleVersionCache = new HashMap<>(); + private static final HashMap> 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 @@ private String getDependencyNotation(String moduleName, String fabricApiVersion) return String.format("net.fabricmc.fabric-api:%s:%s", moduleName, moduleVersion(moduleName, fabricApiVersion)); } - private Map populateModuleVersionMap(String fabricApiVersion) { - File pomFile = getApiMavenPom(fabricApiVersion); + private Map getApiModuleVersions(String fabricApiVersion) { + try { + return populateModuleVersionMap(getApiMavenPom(fabricApiVersion)); + } catch (PomNotFoundException e) { + throw new RuntimeException("Could not find fabric-api version: " + fabricApiVersion); + } + } + + private Map 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 populateModuleVersionMap(File pomFile) { try { DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); @@ -101,27 +122,36 @@ private Map populateModuleVersionMap(String fabricApiVersion) { } } - private File getApiMavenPom(String fabricApiVersion) { - LoomGradleExtension extension = LoomGradleExtension.get(project); - - File mavenPom = new File(extension.getFiles().getUserCache(), "fabric-api/" + fabricApiVersion + ".pom"); + private File getApiMavenPom(String fabricApiVersion) throws PomNotFoundException { + return getPom("fabric-api", fabricApiVersion); + } - if (project.getGradle().getStartParameter().isOffline()) { - if (!mavenPom.exists()) { - throw new RuntimeException("Cannot retrieve fabric-api pom due to being offline"); - } + private File getDeprecatedApiMavenPom(String fabricApiVersion) throws PomNotFoundException { + return getPom("fabric-api-deprecated", fabricApiVersion); + } - 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); + } + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/InstallerData.java b/src/main/java/net/fabricmc/loom/configuration/InstallerData.java index 9496320b9..ce8da78d0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/InstallerData.java +++ b/src/main/java/net/fabricmc/loom/configuration/InstallerData.java @@ -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.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 void applyToProject(Project project) { extension.setInstallerData(this); - JsonObject libraries = installerJson.get("libraries").getAsJsonObject(); + final JsonObject libraries = installerJson.get("libraries").getAsJsonObject(); + + 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 apDepsConfig = project.getConfigurations().getByName("annotationProcessor"); + Configuration annotationProcessor = project.getConfigurations().getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME); - libraries.get("common").getAsJsonArray().forEach(jsonElement -> { - String name = jsonElement.getAsJsonObject().get("name").getAsString(); - project.getLogger().debug("Adding dependency ({}) from installer JSON", 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())); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java index d647c77a9..9d7008f35 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java @@ -35,6 +35,7 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.LoomVersions; import net.fabricmc.loom.util.gradle.SourceSetHelper; public abstract class LoomConfigurations implements Runnable { @@ -104,10 +105,10 @@ public void run() { 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()); } private NamedDomainObjectProvider register(String name, Role role) { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index f302600a4..3ed36eef9 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -133,6 +133,7 @@ private void remapJars(List remapList) throws IOException { .stream().map(File::toPath).toArray(Path[]::new); TinyRemapper.Builder builder = TinyRemapper.newRemapper() + .withKnownIndyBsm(extension.getKnownIndyBsms().get()) .withMappings(TinyRemapperHelper.create(mappingConfiguration.getMappingsService(serviceManager).getMappingTree(), fromM, toM, false)) .renameInvalidLocals(false) .extraAnalyzeVisitor(AccessWidenerAnalyzeVisitorProvider.createFromMods(fromM, remapList)); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/library/processors/LoomNativeSupportLibraryProcessor.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/library/processors/LoomNativeSupportLibraryProcessor.java index 89f85f82b..4baf86a4e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/library/processors/LoomNativeSupportLibraryProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/library/processors/LoomNativeSupportLibraryProcessor.java @@ -30,7 +30,7 @@ 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 ApplicationResult getApplicationResult() { @Override public Predicate apply(Consumer 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; } } diff --git a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java index adf8650d4..618629555 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java +++ b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java @@ -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 NamedDomainObjectProvider createConfiguration(String name, LoomVersions version) { + final String configurationName = name + "DecompilerClasspath"; + NamedDomainObjectProvider configuration = getProject().getConfigurations().register(configurationName); + getProject().getDependencies().add(configurationName, version.mavenNotation()); + return configuration; + } + + private void registerDecompiler(Project project, String name, Class decompilerClass, NamedDomainObjectProvider 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 libraries() { + return metaData.libraries(); + } + + @Override + public LoomInternalDecompiler.Logger logger() { + return logger; + } + + @Override + public Map 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()); + } } - private void registerDecompiler(Project project, String name, Class decompilerClass) { - LoomGradleExtension.get(project).getDecompilerOptions().register(name, options -> options.getDecompilerClassName().set(decompilerClass.getName())); + public static final class BuiltinVineflower extends BuiltinDecompiler { + public BuiltinVineflower() { + super(new VineflowerDecompiler()); + } } } diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index 3060d0ef1..f8d492ed1 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -25,6 +25,7 @@ package net.fabricmc.loom.extension; import java.io.File; +import java.util.Set; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; @@ -35,6 +36,7 @@ import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.tasks.SourceSet; @@ -71,6 +73,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA protected final ConfigurableFileCollection log4jConfigs; protected final RegularFileProperty accessWidener; protected final Property customManifest; + protected final SetProperty knownIndyBsms; protected final Property transitiveAccessWideners; protected final Property modProvidedJavadoc; protected final Property intermediary; @@ -98,6 +101,12 @@ protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) { 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(); @@ -228,6 +237,11 @@ public Property getCustomMinecraftManifest() { return customManifest; } + @Override + public SetProperty getKnownIndyBsms() { + return knownIndyBsms; + } + @Override public String getModVersion() { return versionParser.getModVersion(); diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernFlowerUtils.java b/src/main/java/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataWrapper.java similarity index 68% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/FernFlowerUtils.java rename to src/main/java/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataWrapper.java index ee07f045c..2a6238a97 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernFlowerUtils.java +++ b/src/main/java/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataWrapper.java @@ -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(); } } diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java index 3abc7c0a4..7da1ea1e5 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java @@ -30,6 +30,7 @@ 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.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; @@ -73,6 +75,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); @@ -110,6 +113,11 @@ public abstract class AbstractRemapJarTask extends Jar { @Optional public abstract Property getClientOnlySourceSetName(); + @Input + @Optional + @ApiStatus.Internal + public abstract Property getJarType(); + private final Provider jarManifestServiceProvider; @Inject @@ -118,6 +126,7 @@ public AbstractRemapJarTask() { getTargetNamespace().convention(MappingsNamespace.INTERMEDIARY.toString()).finalizeValueOnRead(); getRemapperIsolation().convention(false).finalizeValueOnRead(); getIncludesClientOnlyClasses().convention(false).finalizeValueOnRead(); + getJarType().finalizeValueOnRead(); jarManifestServiceProvider = JarManifestService.get(getProject()); usesService(jarManifestServiceProvider); @@ -137,14 +146,20 @@ public final

void submitWork(Class 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); }); } @@ -160,6 +175,7 @@ public interface AbstractRemapParams extends WorkParameters { Property getArchivePreserveFileTimestamps(); Property getArchiveReproducibleFileOrder(); + Property getEntryCompression(); Property getJarManifestService(); MapProperty getManifestAttributes(); @@ -202,9 +218,10 @@ protected void modifyJarManifest() throws IOException { 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); } } } diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index e680792c2..9639f3d18 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -109,6 +109,8 @@ public RemapJarTask() { // Make outputs reproducible by default setReproducibleFileOrder(true); setPreserveFileTimestamps(false); + + getJarType().set("classes"); } private void setupPreparationTask() { diff --git a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java index cca9638ed..2a9424acc 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java @@ -51,6 +51,7 @@ public RemapSourcesJarTask() { serviceManagerProvider = BuildSharedServiceManager.createForTask(this, getBuildEventsListenerRegistry()); getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)); + getJarType().set("sources"); } @TaskAction diff --git a/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java b/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java index f1c3d9b24..4ca2791c0 100644 --- a/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java +++ b/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java @@ -42,6 +42,7 @@ import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.configuration.InstallerData; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.LoomVersions; import net.fabricmc.tinyremapper.TinyRemapper; public abstract class JarManifestService implements BuildService { @@ -63,7 +64,7 @@ public static synchronized Provider get(Project project) { 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"))); diff --git a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java index 38f7ad7d1..3e1eee877 100644 --- a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.StringJoiner; import org.gradle.api.Project; @@ -77,6 +78,8 @@ public static synchronized TinyRemapperService getOrCreate(SharedServiceManager joiner.add(project.getPath()); } + extension.getKnownIndyBsms().get().stream().sorted().forEach(joiner::add); + final String id = joiner.toString(); TinyRemapperService service = serviceManager.getOrCreateService(id, () -> { @@ -87,7 +90,7 @@ public static synchronized TinyRemapperService getOrCreate(SharedServiceManager 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()); @@ -126,8 +129,8 @@ private static IMappingProvider gradleMixinMappingProvider(SharedServiceManager // Set to true once remapping has started, once set no inputs can be read. private boolean isRemapping = false; - public TinyRemapperService(List mappings, boolean useMixinExtension, @Nullable KotlinClasspath kotlinClasspath) { - TinyRemapper.Builder builder = TinyRemapper.newRemapper(); + public TinyRemapperService(List mappings, boolean useMixinExtension, @Nullable KotlinClasspath kotlinClasspath, Set knownIndyBsms) { + TinyRemapper.Builder builder = TinyRemapper.newRemapper().withKnownIndyBsm(knownIndyBsms); for (IMappingProvider provider : mappings) { builder.withMappings(provider); diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index eebfeee5b..42692aabc 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -84,34 +84,6 @@ private Configurations() { } } - /** - * 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:"; - - 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"; - - 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"; diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java index 17fa8a52e..1f4fca35e 100644 --- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java @@ -192,7 +192,7 @@ private Mercury getMercuryInstance() { } Set 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) { diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index 0b862108e..648ba105a 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -82,6 +82,7 @@ public static TinyRemapper getTinyRemapper(Project project, SharedServiceManager .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); diff --git a/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java b/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java index f1c8c3645..d962be8c4 100644 --- a/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java +++ b/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java @@ -30,7 +30,6 @@ 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.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 @@ private static int specialOrdering(String name1, String name2) { } 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 static void reprocessZip(File file, boolean reproducibleFileOrder, boolea 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 static void reprocessZip(File file, boolean reproducibleFileOrder, boolea setConstantFileTime(newEntry); } + newEntry.setMethod(zipEntryCompressionMethod(zipEntryCompression)); copyZipEntry(zipOutputStream, newEntry, zipFile.getInputStream(entry)); } } @@ -173,8 +177,23 @@ private static void copyZipEntry(ZipOutputStream zipOutputStream, ZipEntry entry } 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; + }; } } diff --git a/src/main/java/net/fabricmc/loom/util/download/Download.java b/src/main/java/net/fabricmc/loom/util/download/Download.java index a21d4ce44..79c200cc0 100644 --- a/src/main/java/net/fabricmc/loom/util/download/Download.java +++ b/src/main/java/net/fabricmc/loom/util/download/Download.java @@ -62,9 +62,11 @@ 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 static DownloadBuilder create(String url) throws URISyntaxException { 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 @@ String downloadString() throws DownloadException { 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 @@ private void doDownload(Path output) throws DownloadException { 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 @@ private void doDownload(Path output) throws DownloadException { } } + private void downloadToPath(Path output, HttpResponse 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 @@ private void tryCleanup(Path output) { } 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 @@ private Path getLockFile(Path output) { 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 @@ private void createLock(Path output) throws DownloadException { } } + 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)); } diff --git a/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java b/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java index c2fd6679a..ab787bfb1 100644 --- a/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java +++ b/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java @@ -158,6 +158,11 @@ private T withRetries(DownloadFunction supplier) throws DownloadException 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); } diff --git a/src/main/java/net/fabricmc/loom/util/download/DownloadException.java b/src/main/java/net/fabricmc/loom/util/download/DownloadException.java index 993fa2573..36db54cc3 100644 --- a/src/main/java/net/fabricmc/loom/util/download/DownloadException.java +++ b/src/main/java/net/fabricmc/loom/util/download/DownloadException.java @@ -27,15 +27,32 @@ 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; } } diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt index c20820fa3..56ce489e6 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt @@ -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) } diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassRemapper.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassRemapper.kt index 416a0bde1..e3d00bb61 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassRemapper.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassRemapper.kt @@ -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)) } } diff --git a/src/main/resources/log4j2.fabric.xml b/src/main/resources/log4j2.fabric.xml index 69c9c0da4..cb8c84e22 100644 --- a/src/main/resources/log4j2.fabric.xml +++ b/src/main/resources/log4j2.fabric.xml @@ -4,6 +4,8 @@ + + diff --git a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy index 9aedb1bef..6e128956d 100644 --- a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy @@ -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 diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy index 5b2f0e105..7431aae09 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy @@ -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 diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy index 0b20f0a69..f46cc9900 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy @@ -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" ) @@ -55,7 +55,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait { // Set the version to something constant gradle.buildGradle.text = gradle.buildGradle.text.replace('project.version + "+" + (ENV.GITHUB_RUN_NUMBER ? "" : "local-") + getBranch()', "\"$API_VERSION\"") - 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: [ @@ -77,8 +77,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") diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/ReproducibleBuildTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/ReproducibleBuildTest.groovy index f40a65e0d..0936f58f7 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/ReproducibleBuildTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/ReproducibleBuildTest.groovy @@ -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" ] } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/FabricApiExtensionTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/FabricApiExtensionTest.groovy new file mode 100644 index 000000000..860e65ebd --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/FabricApiExtensionTest.groovy @@ -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 + } +} \ No newline at end of file diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy index 95c469166..1b40e3f3d 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy @@ -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" | _ } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadFileTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadFileTest.groovy index 820647f93..1d9772035 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadFileTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadFileTest.groovy @@ -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"() { diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadStringTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadStringTest.groovy index f6397a63f..48ed5fce3 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadStringTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/download/DownloadStringTest.groovy @@ -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") { diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy index 423201cfd..49448cf00 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy @@ -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 Property 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 answer(InvocationOnMock invocation) throws Throwable { + throw new RuntimeException("${invocation.getMethod().getName()} is not stubbed") + } + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy b/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy index 2cc9d6f58..805c2094d 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy @@ -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 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"