diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f204c462..5b3b2393 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,11 +1,11 @@ name: Run Tests -on: [ push, pull_request ] +on: [push, pull_request] jobs: build: strategy: fail-fast: false matrix: - version: [ 7.3.1-jdk17 ] + version: [7.4.0-jdk17] runs-on: ubuntu-20.04 container: image: gradle:${{ matrix.version }} @@ -22,13 +22,14 @@ jobs: runs-on: ubuntu-20.04 container: - image: gradle:7.3.1-jdk17 + image: gradle:7.4.0-jdk17 options: --user root steps: - uses: actions/checkout@v2 - run: gradle writeActionsTestMatrix --stacktrace --warning-mode fail - - id: set-matrix + - + id: set-matrix run: echo "::set-output name=matrix::$(cat build/test_matrix.json)" outputs: @@ -40,7 +41,7 @@ jobs: strategy: fail-fast: false matrix: - version: [7.3.1-jdk17] + version: [7.4.0-jdk17] test: ${{ fromJson(needs.prepare_test_matrix.outputs.matrix) }} runs-on: ubuntu-20.04 @@ -66,7 +67,7 @@ jobs: strategy: fail-fast: false matrix: - java: [ 17 ] + java: [17] test: ${{ fromJson(needs.prepare_test_matrix.outputs.matrix) }} runs-on: windows-2022 @@ -86,3 +87,28 @@ jobs: with: name: ${{ matrix.test }} (${{ matrix.java }}) Results path: build/reports/ + + # Special case this test to run across all os's + reproducible_build_test: + needs: build + + strategy: + fail-fast: false + matrix: + java: [ 17 ] + os: [ windows-2022, ubuntu-20.04, macos-11 ] + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + + - run: ./gradlew test --tests *ReproducibleBuildTest --stacktrace --warning-mode fail + + - uses: actions/upload-artifact@v2 + if: ${{ failure() }} + with: + name: Reproducible Build ${{ matrix.os }} (${{ matrix.java }}) Results + path: build/reports/ \ No newline at end of file diff --git a/bootstrap/build.gradle b/bootstrap/build.gradle index 54c094da..4083cf1f 100644 --- a/bootstrap/build.gradle +++ b/bootstrap/build.gradle @@ -3,12 +3,14 @@ plugins { id 'groovy' } -sourceCompatibility = 8 -targetCompatibility = 8 +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} tasks.withType(JavaCompile).configureEach { it.options.encoding = "UTF-8" - it.options.release = 8 } repositories { @@ -19,7 +21,7 @@ dependencies { implementation gradleApi() testImplementation(gradleTestKit()) - testImplementation('org.spockframework:spock-core:2.0-groovy-3.0') { + testImplementation('org.spockframework:spock-core:2.1-groovy-3.0') { exclude module: 'groovy-all' } } diff --git a/bootstrap/test-project/build.gradle b/bootstrap/test-project/build.gradle index 5e49c3db..de039d52 100644 --- a/bootstrap/test-project/build.gradle +++ b/bootstrap/test-project/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'dev.architectury.loom' version '0.11.local' + id 'dev.architectury.loom' version '0.12.local' } dependencies { diff --git a/build.gradle b/build.gradle index ff53d205..c9cc05c0 100644 --- a/build.gradle +++ b/build.gradle @@ -9,16 +9,18 @@ plugins { id 'jacoco' id 'codenarc' id "org.jetbrains.kotlin.jvm" version "1.5.31" // Must match the version included with gradle. - id "com.diffplug.spotless" version "5.14.1" + id "com.diffplug.spotless" version "6.3.0" id 'me.shedaniel.java-version-bridge' version '1.0-SNAPSHOT' } -sourceCompatibility = 17 -targetCompatibility = 17 +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} tasks.withType(JavaCompile).configureEach { it.options.encoding = "UTF-8" - it.options.release = 17 } tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { @@ -29,7 +31,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { group = "dev.architectury" archivesBaseName = project.name -def baseVersion = '0.11.0' +def baseVersion = '0.12.0' def runNumber = System.getenv("GITHUB_RUN_NUMBER") ?: "9999" def isSnapshot = System.getenv("PR_NUM") != null @@ -80,9 +82,9 @@ dependencies { // libraries implementation ('commons-io:commons-io:2.11.0') - implementation ('com.google.code.gson:gson:2.8.9') - implementation ('com.fasterxml.jackson.core:jackson-databind:2.13.0') - implementation ('com.google.guava:guava:31.0.1-jre') + implementation ('com.google.code.gson:gson:2.9.0') + implementation ('com.fasterxml.jackson.core:jackson-databind:2.13.2.2') + implementation ('com.google.guava:guava:31.1-jre') implementation ('org.ow2.asm:asm:9.3') implementation ('org.ow2.asm:asm-analysis:9.3') implementation ('org.ow2.asm:asm-commons:9.3') @@ -98,6 +100,7 @@ dependencies { // tinyfile management implementation ('dev.architectury:tiny-remapper:1.7.19') + // TODO: need TR update: implementation ('net.fabricmc:tiny-remapper:0.8.2') implementation 'net.fabricmc:access-widener:2.1.0' implementation 'net.fabricmc:mapping-io:0.2.1' @@ -107,20 +110,20 @@ dependencies { implementation "dev.architectury:refmap-remapper:1.0.5" // decompilers - implementation ('net.fabricmc:fabric-fernflower:1.4.1') - implementation ('net.fabricmc:cfr:0.0.9') + implementation ('net.fabricmc:fabric-fernflower:1.5.0') + implementation ('net.fabricmc:cfr:0.1.1') // source code remapping implementation ('dev.architectury:mercury:0.1.1.11') // Kotlin - implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.4.1") { + implementation('org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.4.2') { transitive = false } // Kapt integration - compileOnly('org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0') + compileOnly('org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31') // Must match the version included with gradle. // Forge patches implementation ('net.minecraftforge:installertools:1.2.0') @@ -132,16 +135,18 @@ dependencies { // Testing testImplementation(gradleTestKit()) - testImplementation('org.spockframework:spock-core:2.0-groovy-3.0') { + testImplementation('org.spockframework:spock-core:2.1-groovy-3.0') { exclude module: 'groovy-all' } - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.1' - testImplementation ('io.javalin:javalin:3.13.11') { + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testImplementation ('io.javalin:javalin:4.4.0') { exclude group: 'org.jetbrains.kotlin' } testImplementation 'net.fabricmc:fabric-installer:0.9.0' + testImplementation 'org.mockito:mockito-core:4.4.0' compileOnly 'org.jetbrains:annotations:23.0.0' + testCompileOnly 'org.jetbrains:annotations:23.0.0' } jar { @@ -191,7 +196,7 @@ spotless { checkstyle { configFile = file('checkstyle.xml') - toolVersion = '9.2' + toolVersion = '9.3' } codenarc { @@ -327,6 +332,10 @@ task writeActionsTestMatrix() { def className = it.path.toString().replace(".groovy", "") className = className.substring(className.lastIndexOf("integration/") + "integration/".length()).replace('/', '.') + + // Disabled for CI, as it fails too much. + if (className.endsWith("DecompileTest")) return + testMatrix.add("net.fabricmc.loom.test.integration.${className}") } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c0..41d9927a 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 ac0b842f..d7e66b5c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c..1b6c7873 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index 96d6535c..f73f2fa1 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -129,10 +129,6 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI { boolean isRootProject(); - default String getIntermediaryUrl(String minecraftVersion) { - return String.format(this.getIntermediaryUrl().get(), minecraftVersion); - } - @Override MixinExtension getMixin(); diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index de3b90b5..85df3e9e 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -48,6 +48,7 @@ import net.fabricmc.loom.decompilers.DecompilerConfiguration; import net.fabricmc.loom.extension.LoomFiles; import net.fabricmc.loom.extension.LoomGradleExtensionImpl; import net.fabricmc.loom.task.LoomTasks; +import net.fabricmc.loom.util.LibraryLocationLogger; public class LoomGradlePlugin implements BootstrappedPlugin { public static boolean refreshDeps; @@ -73,6 +74,8 @@ public class LoomGradlePlugin implements BootstrappedPlugin { project.getLogger().lifecycle("Architectury Loom: " + LOOM_VERSION); } + LibraryLocationLogger.logLibraryVersions(); + refreshDeps = project.getGradle().getStartParameter().isRefreshDependencies() || Boolean.getBoolean("loom.refresh"); if (refreshDeps) { diff --git a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java index 5004378b..995fffa0 100644 --- a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java @@ -26,7 +26,9 @@ package net.fabricmc.loom; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.artifacts.ArtifactRepositoryContainer; import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.artifacts.repositories.ArtifactRepository; import org.gradle.api.artifacts.repositories.IvyArtifactRepository; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.initialization.Settings; @@ -75,7 +77,8 @@ public class LoomRepositoryPlugin implements Plugin { repo.setName("Fabric"); repo.setUrl(MirrorUtil.getFabricRepository(target)); }); - repositories.maven(repo -> { + + MavenArtifactRepository mojangRepo = repositories.maven(repo -> { repo.setName("Mojang"); repo.setUrl(MirrorUtil.getLibrariesBase(target)); @@ -100,6 +103,16 @@ public class LoomRepositoryPlugin implements Plugin { sources.ignoreGradleMetadataRedirection(); }); }); + + // If a mavenCentral repo is already defined, remove the mojang repo and add it back before the mavenCentral repo so that it will be checked first. + // See: https://github.com/FabricMC/fabric-loom/issues/621 + ArtifactRepository mavenCentral = repositories.findByName(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME); + + if (mavenCentral != null) { + repositories.remove(mojangRepo); + repositories.add(repositories.indexOf(mavenCentral), mojangRepo); + } + repositories.mavenCentral(); repositories.ivy(repo -> { diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index d51843a5..2f2fa9f1 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -39,11 +39,13 @@ import org.gradle.api.publish.maven.MavenPublication; import org.jetbrains.annotations.ApiStatus; import net.fabricmc.loom.api.decompilers.DecompilerOptions; +import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.configuration.providers.mappings.NoOpIntermediateMappingsProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; import net.fabricmc.loom.util.DeprecationHelper; import net.fabricmc.loom.util.ModPlatform; @@ -75,9 +77,7 @@ public interface LoomGradleExtensionAPI { ConfigurableFileCollection getLog4jConfigs(); - default Dependency officialMojangMappings() { - return layered(LayeredMappingSpecBuilder::officialMojangMappings); - } + Dependency officialMojangMappings(); Dependency layered(Action action); @@ -89,6 +89,16 @@ public interface LoomGradleExtensionAPI { void mixin(Action action); + /** + * Optionally register and configure a {@link ModSettings} object. The name should match the modid. + * This is generally only required when the mod spans across multiple classpath directories, such as when using split sourcesets. + */ + @ApiStatus.Experimental + void mods(Action> action); + + @ApiStatus.Experimental + NamedDomainObjectContainer getMods(); + @ApiStatus.Experimental // TODO: move this from LoomGradleExtensionAPI to LoomGradleExtension once getRefmapName & setRefmapName is removed. MixinExtensionAPI getMixin(); @@ -137,6 +147,30 @@ public interface LoomGradleExtensionAPI { */ Property getEnableTransitiveAccessWideners(); + /** + * When true loom will apply mod provided javadoc from dependencies. + * + * @return the property controlling the mod provided javadoc + */ + Property getEnableModProvidedJavadoc(); + + @ApiStatus.Experimental + IntermediateMappingsProvider getIntermediateMappingsProvider(); + + @ApiStatus.Experimental + void setIntermediateMappingsProvider(IntermediateMappingsProvider intermediateMappingsProvider); + + @ApiStatus.Experimental + void setIntermediateMappingsProvider(Class clazz, Action action); + + /** + * An Experimental option to provide empty intermediate mappings, to be used for game versions without any intermediate mappings. + */ + @ApiStatus.Experimental + default void noIntermediateMappings() { + setIntermediateMappingsProvider(NoOpIntermediateMappingsProvider.class, p -> { }); + } + /** * Use "%1$s" as a placeholder for the minecraft version. * @@ -152,11 +186,22 @@ public interface LoomGradleExtensionAPI { getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SERVER_ONLY); } + @ApiStatus.Experimental + default void clientOnlyMinecraftJar() { + getMinecraftJarConfiguration().set(MinecraftJarConfiguration.CLIENT_ONLY); + } + @ApiStatus.Experimental default void splitMinecraftJar() { getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SPLIT); } + @ApiStatus.Experimental + void splitEnvironmentSourceSets(); + + @ApiStatus.Experimental + boolean areEnvironmentSourceSetsSplit(); + Property getRuntimeOnlyLog4j(); // =================== diff --git a/src/main/java/net/fabricmc/loom/api/MixinExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/MixinExtensionAPI.java index d33c7874..d7e23077 100644 --- a/src/main/java/net/fabricmc/loom/api/MixinExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/MixinExtensionAPI.java @@ -36,6 +36,8 @@ public interface MixinExtensionAPI { Property getDefaultRefmapName(); + Property getRefmapTargetNamespace(); + Property getLegacyRemapToNamespace(); /** diff --git a/src/main/java/net/fabricmc/loom/api/ModSettings.java b/src/main/java/net/fabricmc/loom/api/ModSettings.java new file mode 100644 index 00000000..af31a2cc --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/ModSettings.java @@ -0,0 +1,62 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.api; + +import javax.inject.Inject; + +import org.gradle.api.Named; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.ApiStatus; + +/** + * A {@link Named} object for setting mod-related values. The {@linkplain Named#getName() name} should match the mod id. + */ +@ApiStatus.Experimental +public abstract class ModSettings implements Named { + /** + * List of classpath directories, used to populate the `fabric.classPathGroups` Fabric Loader system property. + */ + public abstract ListProperty getModSourceSets(); + + /** + * List of classpath directories, or jar files used to populate the `fabric.classPathGroups` Fabric Loader system property. + */ + public abstract ConfigurableFileCollection getModFiles(); + + @Inject + public ModSettings() { + getModSourceSets().finalizeValueOnRead(); + getModFiles().finalizeValueOnRead(); + } + + /** + * Mark a {@link SourceSet} output directories part of the named mod. + */ + public void sourceSet(SourceSet sourceSet) { + getModSourceSets().add(sourceSet); + } +} diff --git a/src/main/java/net/fabricmc/loom/api/mappings/intermediate/IntermediateMappingsProvider.java b/src/main/java/net/fabricmc/loom/api/mappings/intermediate/IntermediateMappingsProvider.java new file mode 100644 index 00000000..f6b578a3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/mappings/intermediate/IntermediateMappingsProvider.java @@ -0,0 +1,47 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.api.mappings.intermediate; + +import java.io.IOException; +import java.nio.file.Path; + +import org.gradle.api.Named; +import org.gradle.api.provider.Property; +import org.jetbrains.annotations.ApiStatus; + +/** + * A simple API to allow 3rd party plugins. + * Implement by creating an abstract class overriding provide and getName + */ +@ApiStatus.Experimental +public abstract class IntermediateMappingsProvider implements Named { + public abstract Property getMinecraftVersion(); + + /** + * Generate or download a tinyv2 mapping file with intermediary and named namespaces. + * @throws IOException + */ + public abstract void provide(Path tinyMappings) throws IOException; +} 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 47ee2ab1..390a2161 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java @@ -42,6 +42,7 @@ import org.gradle.api.tasks.SourceSet; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.extension.MixinExtension; import net.fabricmc.loom.task.service.MixinMappingsService; import net.fabricmc.loom.util.Constants; @@ -90,7 +91,7 @@ public abstract class AnnotationProcessorInvoker { put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, mappings.toFile().getCanonicalPath()); put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, MixinMappingsService.getMixinMappingFile(project, sourceSet).getCanonicalPath()); put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, refmapName)); - put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary"); + put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:" + loom.getMixin().getRefmapTargetNamespace().get()); put(Constants.MixinArguments.QUIET, "true"); }}; @@ -104,15 +105,16 @@ public abstract class AnnotationProcessorInvoker { public void configureMixin() { LoomGradleExtension extension = LoomGradleExtension.get(project); ConfigurationContainer configs = project.getConfigurations(); + MinecraftSourceSets minecraftSourceSets = MinecraftSourceSets.get(project); if (!IdeaUtils.isIdeaSync()) { for (Configuration processorConfig : apConfigurations) { project.getLogger().info("Adding mixin to classpath of AP config: " + processorConfig.getName()); // Pass named MC classpath to mixin AP classpath processorConfig.extendsFrom( - configs.getByName(Constants.Configurations.MINECRAFT_NAMED), - configs.getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED), - configs.getByName(Constants.Configurations.MAPPINGS_FINAL) + configs.getByName(minecraftSourceSets.getCombinedSourceSetName()), + configs.getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED), + configs.getByName(Constants.Configurations.MAPPINGS_FINAL) ); if (extension.isForge()) { diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index d1e4ab81..dcbbc8d4 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -27,6 +27,7 @@ package net.fabricmc.loom.configuration; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Set; import org.gradle.api.NamedDomainObjectProvider; @@ -48,6 +49,7 @@ import net.fabricmc.loom.configuration.accesstransformer.AccessTransformerJarPro import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor; import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor; import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor; +import net.fabricmc.loom.configuration.mods.ModJavadocProcessor; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.providers.forge.DependencyProviders; import net.fabricmc.loom.configuration.providers.forge.ForgeProvider; @@ -60,6 +62,7 @@ import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider; @@ -89,7 +92,6 @@ public final class CompileConfiguration { extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH, configuration -> configuration.setTransitive(true)); extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED, configuration -> configuration.setTransitive(false)); - extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_NAMED, configuration -> configuration.setTransitive(false)); // The launchers do not recurse dependencies NamedDomainObjectProvider serverDeps = extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_SERVER_DEPENDENCIES, configuration -> configuration.setTransitive(false)); extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_RUNTIME_DEPENDENCIES, configuration -> configuration.setTransitive(false)); extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_DEPENDENCIES, configuration -> { @@ -177,13 +179,7 @@ public final class CompileConfiguration { } } - extendsFrom(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_NAMED, project); - extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_NAMED, project); - extendsFrom(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_NAMED, project); - extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_NAMED, project); - extendsFrom(Constants.Configurations.LOADER_DEPENDENCIES, Constants.Configurations.MINECRAFT_DEPENDENCIES, project); - extendsFrom(Constants.Configurations.MINECRAFT_NAMED, Constants.Configurations.LOADER_DEPENDENCIES, project); extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MAPPINGS_FINAL, project); extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MAPPINGS_FINAL, project); @@ -215,6 +211,8 @@ public final class CompileConfiguration { }); p.afterEvaluate(project -> { + MinecraftSourceSets.get(project).afterEvaluate(project); + try { setupMinecraft(project); } catch (Exception e) { @@ -339,6 +337,15 @@ public final class CompileConfiguration { } } + if (extension.getEnableModProvidedJavadoc().get()) { + // This doesn't do any processing on the compiled jar, but it does have an effect on the generated sources. + final ModJavadocProcessor javadocProcessor = ModJavadocProcessor.create(project); + + if (javadocProcessor != null) { + extension.getGameJarProcessors().add(javadocProcessor); + } + } + if (extension.isForge()) { Set atFiles = AccessTransformerJarProcessor.getAccessTransformerFiles(project); @@ -389,7 +396,13 @@ public final class CompileConfiguration { .apply(project, extension.getNamedMinecraftProvider()).afterEvaluation(); } - private static void extendsFrom(String a, String b, Project project) { + public static void extendsFrom(List parents, String b, Project project) { + for (String parent : parents) { + extendsFrom(parent, b, project); + } + } + + public static void extendsFrom(String a, String b, Project project) { project.getConfigurations().getByName(a, configuration -> configuration.extendsFrom(project.getConfigurations().getByName(b))); } diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java index 1f4a3bb2..6e9b7a63 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -53,6 +53,7 @@ import org.w3c.dom.Node; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.InstallerData; import net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask; +import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; import net.fabricmc.loom.configuration.providers.BundleMetadata; import net.fabricmc.loom.util.Constants; @@ -115,16 +116,6 @@ public class RunConfig { return e; } - private static String getIdeaModuleName(Project project, SourceSet srcs) { - String module = project.getName() + "." + srcs.getName(); - - while ((project = project.getParent()) != null) { - module = project.getName() + "." + module; - } - - return module; - } - private static void populate(Project project, LoomGradleExtension extension, RunConfig runConfig, String environment) { runConfig.configName += extension.isRootProject() ? "" : " (" + project.getPath() + ")"; runConfig.eclipseProjectName = project.getExtensions().getByType(EclipseModel.class).getProject().getName(); @@ -183,7 +174,7 @@ public class RunConfig { runConfig.envVariables.putAll(settings.envVariables); runConfig.configName = configName; populate(project, extension, runConfig, environment); - runConfig.ideaModuleName = getIdeaModuleName(project, sourceSet); + runConfig.ideaModuleName = IdeaUtils.getIdeaModuleName(project, sourceSet); runConfig.runDirIdeaUrl = "file://$PROJECT_DIR$/" + runDir; runConfig.runDir = runDir; runConfig.sourceSet = sourceSet; diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaUtils.java b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaUtils.java index e1aa3ea4..fd1e26ab 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaUtils.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaUtils.java @@ -26,6 +26,9 @@ package net.fabricmc.loom.configuration.ide.idea; import java.util.Objects; +import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; + public class IdeaUtils { public static boolean isIdeaSync() { return Boolean.parseBoolean(System.getProperty("idea.sync.active", "false")); @@ -42,4 +45,14 @@ public class IdeaUtils { final int minor = Integer.parseInt(split[1]); return major > 2021 || (major == 2021 && minor >= 3); } + + public static String getIdeaModuleName(Project project, SourceSet srcs) { + String module = project.getName() + "." + srcs.getName(); + + while ((project = project.getParent()) != null) { + module = project.getName() + "." + module; + } + + return module; + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java index 6ebad08a..93f90f95 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java @@ -63,6 +63,7 @@ import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.task.GenerateSourcesTask; import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ModUtils; import net.fabricmc.loom.util.Pair; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipUtils; @@ -314,18 +315,12 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource private record InjectedInterface(String modId, String className, String ifaceName) { /** - * Reads the injected interfaces contained in a mod jar, or returns null if there is none. + * Reads the injected interfaces contained in a mod jar, or returns empty if there is none. */ public static List fromModJar(Path modJarPath) { - final byte[] modJsonBytes; + final JsonObject jsonObject = ModUtils.getFabricModJson(modJarPath); - try { - modJsonBytes = ZipUtils.unpackNullable(modJarPath, "fabric.mod.json"); - } catch (IOException e) { - throw new RuntimeException("Failed to extract fabric.mod.json from " + modJarPath); - } - - if (modJsonBytes == null) { + if (jsonObject == null) { byte[] commonJsonBytes; try { @@ -353,12 +348,9 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource } } } - return Collections.emptyList(); } - final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class); - return fromJson(jsonObject, modJarPath.toString()); } @@ -375,11 +367,11 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource final JsonObject custom = jsonObject.getAsJsonObject("custom"); - if (!custom.has("loom:injected_interfaces")) { + if (!custom.has(Constants.CustomModJsonKeys.INJECTED_INTERFACE)) { return Collections.emptyList(); } - final JsonObject addedIfaces = custom.getAsJsonObject("loom:injected_interfaces"); + final JsonObject addedIfaces = custom.getAsJsonObject(Constants.CustomModJsonKeys.INJECTED_INTERFACE); final List result = new ArrayList<>(); diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModJavadocProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModJavadocProcessor.java new file mode 100644 index 00000000..e6f1d4c3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModJavadocProcessor.java @@ -0,0 +1,218 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.configuration.mods; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.google.gson.JsonObject; +import org.gradle.api.Project; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.RemappedConfigurationEntry; +import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.task.GenerateSourcesTask; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ModUtils; +import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public final class ModJavadocProcessor implements JarProcessor, GenerateSourcesTask.MappingsProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(ModJavadocProcessor.class); + + private final List javadocs; + + private ModJavadocProcessor(List javadocs) { + this.javadocs = javadocs; + } + + @Nullable + public static ModJavadocProcessor create(Project project) { + final LoomGradleExtension extension = LoomGradleExtension.get(project); + final List javadocs = new ArrayList<>(); + + for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) { + Set artifacts = extension.getLazyConfigurationProvider(entry.sourceConfiguration()) + .get() + .resolve(); + + for (File artifact : artifacts) { + if (!ModUtils.isMod(artifact.toPath())) { + continue; + } + + final ModJavadoc modJavadoc; + + try { + modJavadoc = ModJavadoc.fromModJar(artifact.toPath()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read mod jar (%s)".formatted(artifact), e); + } + + if (modJavadoc != null) { + javadocs.add(modJavadoc); + } + } + } + + if (javadocs.isEmpty()) { + return null; + } + + return new ModJavadocProcessor(javadocs); + } + + @Override + public boolean transform(MemoryMappingTree mappings) { + for (ModJavadoc javadoc : javadocs) { + javadoc.apply(mappings); + } + + return true; + } + + @Override + public String getId() { + return "loom:interface_injection:" + javadocs.hashCode(); + } + + @Override + public void setup() { + } + + @Override + public void process(File file) { + // No need to actually process anything, we need to be a JarProcessor to ensure that the jar is cached correctly. + } + + public record ModJavadoc(String modId, MemoryMappingTree mappingTree) { + @Nullable + public static ModJavadoc fromModJar(Path path) throws IOException { + JsonObject jsonObject = ModUtils.getFabricModJson(path); + + if (jsonObject == null || !jsonObject.has("custom")) { + return null; + } + + final String modId = jsonObject.get("id").getAsString(); + final JsonObject custom = jsonObject.getAsJsonObject("custom"); + + if (!custom.has(Constants.CustomModJsonKeys.PROVIDED_JAVADOC)) { + return null; + } + + final String javaDocPath = custom.getAsJsonPrimitive(Constants.CustomModJsonKeys.PROVIDED_JAVADOC).getAsString(); + final byte[] data = ZipUtils.unpack(path, javaDocPath); + final MemoryMappingTree mappings = new MemoryMappingTree(); + + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(data))) { + MappingReader.read(reader, mappings); + } + + if (!mappings.getSrcNamespace().equals(MappingsNamespace.INTERMEDIARY.toString())) { + throw new IllegalStateException("Javadoc provided by mod (%s) must be have an intermediary source namespace".formatted(modId)); + } + + if (!mappings.getDstNamespaces().isEmpty()) { + throw new IllegalStateException("Javadoc provided by mod (%s) must not contain any dst names".formatted(modId)); + } + + return new ModJavadoc(modId, mappings); + } + + public void apply(MemoryMappingTree target) { + if (!mappingTree.getSrcNamespace().equals(target.getSrcNamespace())) { + throw new IllegalStateException("Cannot apply mappings to differing namespaces. source: %s target: %s".formatted(mappingTree.getSrcNamespace(), target.getSrcNamespace())); + } + + for (MappingTree.ClassMapping sourceClass : mappingTree.getClasses()) { + final MappingTree.ClassMapping targetClass = target.getClass(sourceClass.getSrcName()); + + if (targetClass == null) { + LOGGER.warn("Could not find provided javadoc target class {} from mod {}", sourceClass.getSrcName(), modId); + continue; + } + + applyComment(sourceClass, targetClass); + + for (MappingTree.FieldMapping sourceField : sourceClass.getFields()) { + final MappingTree.FieldMapping targetField = targetClass.getField(sourceField.getSrcName(), sourceField.getSrcDesc()); + + if (targetField == null) { + LOGGER.warn("Could not find provided javadoc target field {}{} from mod {}", sourceField.getSrcName(), sourceField.getSrcDesc(), modId); + continue; + } + + applyComment(sourceField, targetField); + } + + for (MappingTree.MethodMapping sourceMethod : sourceClass.getMethods()) { + final MappingTree.MethodMapping targetMethod = targetClass.getMethod(sourceMethod.getSrcName(), sourceMethod.getSrcDesc()); + + if (targetMethod == null) { + LOGGER.warn("Could not find provided javadoc target method {}{} from mod {}", sourceMethod.getSrcName(), sourceMethod.getSrcDesc(), modId); + continue; + } + + applyComment(sourceMethod, targetMethod); + } + } + } + + private void applyComment(T source, T target) { + String sourceComment = source.getComment(); + + if (sourceComment == null) { + LOGGER.warn("Mod {} provided javadoc has mapping for {}, without comment", modId, source); + return; + } + + String targetComment = target.getComment(); + + if (targetComment == null) { + targetComment = ""; + } else { + targetComment += "\n"; + } + + targetComment += sourceComment; + target.setComment(targetComment); + } + } +} 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 2ec40aab..d12adad9 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -24,6 +24,8 @@ package net.fabricmc.loom.configuration.mods; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; @@ -33,6 +35,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.jar.Manifest; import com.google.common.base.Stopwatch; import com.google.gson.JsonObject; @@ -51,11 +54,13 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; -import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension; +import net.fabricmc.loom.task.RemapJarTask; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.LoggerFilter; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.kotlin.KotlinClasspathService; +import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader; import net.fabricmc.loom.util.srg.AtRemapper; import net.fabricmc.loom.util.srg.CoreModClassRemapper; import net.fabricmc.mappingio.tree.MemoryMappingTree; @@ -159,10 +164,8 @@ public class ModProcessor { private void remapJars(List remapList) throws IOException { final LoomGradleExtension extension = LoomGradleExtension.get(project); final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); - final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm"); String fromM = extension.isForge() ? MappingsNamespace.SRG.toString() : MappingsNamespace.INTERMEDIARY.toString(); String toM = MappingsNamespace.NAMED.toString(); - Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles() .stream().map(File::toPath).toArray(Path[]::new); @@ -177,8 +180,12 @@ public class ModProcessor { .withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false)) .renameInvalidLocals(false); - if (useKotlinExtension) { - builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE); + final KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(project); + KotlinRemapperClassloader kotlinRemapperClassloader = null; + + if (kotlinClasspathService != null) { + kotlinRemapperClassloader = KotlinRemapperClassloader.create(kotlinClasspathService); + builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension()); } final TinyRemapper remapper = builder.build(); @@ -236,6 +243,10 @@ public class ModProcessor { } } finally { remapper.finish(); + + if (kotlinRemapperClassloader != null) { + kotlinRemapperClassloader.close(); + } } project.getLogger().lifecycle(":remapped " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch.stop()); @@ -250,6 +261,7 @@ public class ModProcessor { } stripNestedJars(info.getRemappedOutput()); + remapJarManifestEntries(info.getRemappedOutput().toPath()); if (extension.isForge()) { AtRemapper.remap(project.getLogger(), info.getRemappedOutput().toPath(), mappings); @@ -259,4 +271,16 @@ public class ModProcessor { info.finaliseRemapping(); } } + + private void remapJarManifestEntries(Path jar) throws IOException { + ZipUtils.transform(jar, Map.of(RemapJarTask.MANIFEST_PATH, bytes -> { + var manifest = new Manifest(new ByteArrayInputStream(bytes)); + + manifest.getMainAttributes().putValue(RemapJarTask.MANIFEST_NAMESPACE_KEY, toM); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + manifest.write(out); + return out.toByteArray(); + })); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java index e2a83da6..39fe06db 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java @@ -65,7 +65,7 @@ public class GradleMappingContext implements MappingContext { @Override public Supplier intermediaryTree() { - return () -> IntermediaryService.getInstance(project, minecraftProvider()).getMemoryMappingTree(); + return () -> IntermediateMappingsService.getInstance(project, minecraftProvider()).getMemoryMappingTree(); } @Override @@ -82,4 +82,12 @@ public class GradleMappingContext implements MappingContext { public Logger getLogger() { return project.getLogger(); } + + public Project getProject() { + return project; + } + + public LoomGradleExtension getExtension() { + return extension; + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryMappingsProvider.java new file mode 100644 index 00000000..b29c70f3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryMappingsProvider.java @@ -0,0 +1,71 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.configuration.providers.mappings; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.google.common.net.UrlEscapers; +import org.gradle.api.provider.Property; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider; +import net.fabricmc.loom.util.DownloadUtil; + +public abstract class IntermediaryMappingsProvider extends IntermediateMappingsProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(IntermediateMappingsProvider.class); + + public abstract Property getIntermediaryUrl(); + + @Override + public void provide(Path tinyMappings) throws IOException { + if (Files.exists(tinyMappings) && !LoomGradlePlugin.refreshDeps) { + return; + } + + // Download and extract intermediary + final Path intermediaryJarPath = Files.createTempFile(getName(), ".jar"); + final String encodedMcVersion = UrlEscapers.urlFragmentEscaper().escape(getMinecraftVersion().get()); + final URL url = new URL(getIntermediaryUrl().get().formatted(encodedMcVersion)); + + LOGGER.info("Downloading intermediary from {}", url); + + Files.deleteIfExists(tinyMappings); + Files.deleteIfExists(intermediaryJarPath); + + DownloadUtil.downloadIfChanged(url, intermediaryJarPath.toFile(), LOGGER); + MappingsProviderImpl.extractMappings(intermediaryJarPath, tinyMappings); + } + + @Override + public @NotNull String getName() { + return "intermediary-v2"; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryService.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediateMappingsService.java similarity index 66% rename from src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryService.java rename to src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediateMappingsService.java index bd0c4e90..13b1427c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryService.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediateMappingsService.java @@ -25,10 +25,8 @@ package net.fabricmc.loom.configuration.providers.mappings; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -37,59 +35,52 @@ import java.util.Objects; import java.util.function.Supplier; import com.google.common.base.Suppliers; -import com.google.common.net.UrlEscapers; import org.gradle.api.Project; import org.jetbrains.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; -import net.fabricmc.loom.util.DownloadUtil; import net.fabricmc.loom.util.service.SharedService; import net.fabricmc.loom.util.service.SharedServiceManager; import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.format.Tiny2Reader; import net.fabricmc.mappingio.tree.MemoryMappingTree; -public final class IntermediaryService implements SharedService { - private static final Logger LOGGER = LoggerFactory.getLogger(IntermediaryService.class); - +public final class IntermediateMappingsService implements SharedService { private final Path intermediaryTiny; private final Supplier memoryMappingTree = Suppliers.memoize(this::createMemoryMappingTree); - private IntermediaryService(Path intermediaryTiny) { + private IntermediateMappingsService(Path intermediaryTiny) { this.intermediaryTiny = intermediaryTiny; } - public static synchronized IntermediaryService getInstance(Project project, MinecraftProvider minecraftProvider) { + public static synchronized IntermediateMappingsService getInstance(Project project, MinecraftProvider minecraftProvider) { final LoomGradleExtension extension = LoomGradleExtension.get(project); - final String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(minecraftProvider.minecraftVersion()); - final String intermediaryArtifactUrl = extension.getIntermediaryUrl(encodedMinecraftVersion); + final IntermediateMappingsProvider intermediateProvider = extension.getIntermediateMappingsProvider(); + final String id = "IntermediateMappingsService:%s:%s".formatted(intermediateProvider.getName(), intermediateProvider.getMinecraftVersion().get()); - return SharedServiceManager.get(project).getOrCreateService("IntermediaryService:" + intermediaryArtifactUrl, - () -> create(intermediaryArtifactUrl, minecraftProvider)); + return SharedServiceManager.get(project).getOrCreateService(id, () -> create(intermediateProvider, minecraftProvider)); } @VisibleForTesting - public static IntermediaryService create(String intermediaryUrl, MinecraftProvider minecraftProvider) { - final Path intermediaryTiny = minecraftProvider.file("intermediary-v2.tiny").toPath(); - - if (!Files.exists(intermediaryTiny) || LoomGradlePlugin.refreshDeps) { - // Download and extract intermediary - File intermediaryJar = minecraftProvider.file("intermediary-v2.jar"); + public static IntermediateMappingsService create(IntermediateMappingsProvider intermediateMappingsProvider, MinecraftProvider minecraftProvider) { + final Path intermediaryTiny = minecraftProvider.file(intermediateMappingsProvider.getName() + ".tiny").toPath(); + try { + intermediateMappingsProvider.provide(intermediaryTiny); + } catch (IOException e) { try { - DownloadUtil.downloadIfChanged(new URL(intermediaryUrl), intermediaryJar, LOGGER); - MappingsProviderImpl.extractMappings(intermediaryJar.toPath(), intermediaryTiny); - } catch (IOException e) { - throw new UncheckedIOException("Failed to download and extract intermediary", e); + Files.deleteIfExists(intermediaryTiny); + } catch (IOException ex) { + ex.printStackTrace(); } + + throw new UncheckedIOException("Failed to provide intermediate mappings", e); } - return new IntermediaryService(intermediaryTiny); + return new IntermediateMappingsService(intermediaryTiny); } private MemoryMappingTree createMemoryMappingTree() { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index d44772c5..03118c8a 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -102,9 +102,9 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService { private UnpickMetadata unpickMetadata; private Map signatureFixes; - private final Supplier intermediaryService; + private final Supplier intermediaryService; - protected MappingsProviderImpl(String mappingsIdentifier, Path mappingsWorkingDir, Supplier intermediaryService) { + protected MappingsProviderImpl(String mappingsIdentifier, Path mappingsWorkingDir, Supplier intermediaryService) { this.mappingsIdentifier = mappingsIdentifier; this.mappingsWorkingDir = mappingsWorkingDir; @@ -121,7 +121,7 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService { public static synchronized MappingsProviderImpl getInstance(Project project, DependencyInfo dependency, MinecraftProvider minecraftProvider) { return SharedServiceManager.get(project).getOrCreateService("MappingsProvider:%s:%s".formatted(dependency.getDepString(), minecraftProvider.minecraftVersion()), () -> { - Supplier intermediaryService = Suppliers.memoize(() -> IntermediaryService.getInstance(project, minecraftProvider)); + Supplier intermediaryService = Suppliers.memoize(() -> IntermediateMappingsService.getInstance(project, minecraftProvider)); return create(project, dependency, minecraftProvider, intermediaryService); }); } @@ -134,7 +134,7 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService { return Objects.requireNonNull(mappingTreeWithSrg, "Cannot get mappings before they have been read").get(); } - private static MappingsProviderImpl create(Project project, DependencyInfo dependency, MinecraftProvider minecraftProvider, Supplier intermediaryService) { + private static MappingsProviderImpl create(Project project, DependencyInfo dependency, MinecraftProvider minecraftProvider, Supplier intermediaryService) { final String version = dependency.getResolvedVersion(); final Path inputJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve mappings: " + dependency)).toPath(); final String mappingsName = StringUtils.removeSuffix(dependency.getDependency().getGroup() + "." + dependency.getDependency().getName(), "-unmerged"); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/NoOpIntermediateMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/NoOpIntermediateMappingsProvider.java new file mode 100644 index 00000000..28ddfc63 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/NoOpIntermediateMappingsProvider.java @@ -0,0 +1,51 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.configuration.providers.mappings; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.jetbrains.annotations.NotNull; + +import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider; + +/** + * A bit of a hack, creates an empty intermediary mapping file to be used for mc versions without any intermediate mappings. + */ +public abstract class NoOpIntermediateMappingsProvider extends IntermediateMappingsProvider { + private static final String HEADER = "tiny\t2\t0\tofficial\tintermediary"; + + @Override + public void provide(Path tinyMappings) throws IOException { + Files.writeString(tinyMappings, HEADER, StandardCharsets.UTF_8); + } + + @Override + public @NotNull String getName() { + return "empty-intermediate"; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java index 86aa7afa..5988f143 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java @@ -38,7 +38,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; -import net.fabricmc.loom.configuration.providers.mappings.IntermediaryService; +import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService; import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.format.Tiny2Reader; @@ -49,12 +49,12 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree; public final class MappingsMerger { private static final Logger LOGGER = LoggerFactory.getLogger(MappingsMerger.class); - public static void mergeAndSaveMappings(Path from, Path out, IntermediaryService intermediaryService) throws IOException { + public static void mergeAndSaveMappings(Path from, Path out, IntermediateMappingsService intermediateMappingsService) throws IOException { Stopwatch stopwatch = Stopwatch.createStarted(); LOGGER.info(":merging mappings"); MemoryMappingTree intermediaryTree = new MemoryMappingTree(); - intermediaryService.getMemoryMappingTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString())); + intermediateMappingsService.getMemoryMappingTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString())); try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) { Tiny2Reader.read(reader, intermediaryTree); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java index b4109f20..7cdcde94 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java @@ -37,7 +37,7 @@ import net.fabricmc.loom.util.Architecture; import net.fabricmc.loom.util.OperatingSystem; public class LWJGLVersionOverride { - public static final String LWJGL_VERSION = "3.3.0"; + public static final String LWJGL_VERSION = "3.3.1"; @Nullable public static final String NATIVE_CLASSIFIER = getNativesClassifier(); @@ -66,9 +66,15 @@ public class LWJGLVersionOverride { return false; } - return versionMeta.libraries().stream() - .map(MinecraftVersionMeta.Library::name) - .anyMatch(s -> s.startsWith("org.lwjgl:lwjgl:3")); + boolean supportedLwjglVersion = versionMeta.libraries().stream() + .anyMatch(library -> library.name().startsWith("org.lwjgl:lwjgl:3")); + + boolean hasExistingNatives = versionMeta.libraries().stream() + .filter(library -> library.name().startsWith("org.lwjgl:lwjgl")) + .anyMatch(MinecraftVersionMeta.Library::hasNativesForOS); + + // Is LWJGL 3, and doesn't have any existing compatible LWGL natives. + return supportedLwjglVersion && !hasExistingNatives; } public static boolean forceOverride(Project project) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java index 4d338902..3d386ec2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java @@ -58,6 +58,10 @@ public class MergedMinecraftProvider extends MinecraftProvider { public void provide() throws Exception { super.provide(); + if (!getVersionInfo().isVersionOrNewer("2012-07-25T22:00:00+00:00" /* 1.3 release date */)) { + throw new UnsupportedOperationException("Minecraft versions 1.2.5 and older cannot be merged. Please use `loom { server/clientOnlyMinecraftJar() }`"); + } + if (!Files.exists(minecraftMergedJar) || isRefreshDeps()) { try { mergeJars(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java index 9ea8b4aa..05cbde14 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java @@ -52,14 +52,22 @@ public enum MinecraftJarConfiguration { List.of("client", "server") ), SERVER_ONLY( - ServerOnlyMinecraftProvider::new, - IntermediaryMinecraftProvider.ServerOnlyImpl::new, - NamedMinecraftProvider.ServerOnlyImpl::new, + SingleJarMinecraftProvider::server, + IntermediaryMinecraftProvider.SingleJarImpl::server, + NamedMinecraftProvider.SingleJarImpl::server, SrgMinecraftProvider.ServerOnlyImpl::new, - ProcessedNamedMinecraftProvider.ServerOnlyImpl::new, + ProcessedNamedMinecraftProvider.SingleJarImpl::server, SingleJarDecompileConfiguration::new, List.of("server") ), + CLIENT_ONLY( + SingleJarMinecraftProvider::client, + IntermediaryMinecraftProvider.SingleJarImpl::client, + NamedMinecraftProvider.SingleJarImpl::client, + ProcessedNamedMinecraftProvider.SingleJarImpl::client, + SingleJarDecompileConfiguration::new, + List.of("client") + ), SPLIT( SplitMinecraftProvider::new, IntermediaryMinecraftProvider.SplitImpl::new, diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java index bcc1d148..260ec81f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java @@ -60,8 +60,11 @@ public class MinecraftLibraryProvider { if (library.isValidForOS() && !library.hasNatives() && library.artifact() != null) { // 1.4.7 contains an LWJGL version with an invalid maven pom, set the metadata sources to not use the pom for this version. - if ("org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3".equals(library.name())) { + if ("org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3".equals(library.name()) || "org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20131017".equals(library.name())) { LoomRepositoryPlugin.setupForLegacyVersions(project); + } else if (library.name().startsWith("org.ow2.asm:asm-all")) { + // Don't want asm-all, use the modern split version. + continue; } if (runtimeOnlyLog4j && library.name().startsWith("org.apache.logging.log4j")) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java index f66925e8..afd97e2d 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import com.google.common.base.Preconditions; import com.google.common.io.Files; import org.gradle.api.GradleException; import org.gradle.api.Project; @@ -75,6 +76,14 @@ public abstract class MinecraftProvider { this.project = project; } + protected boolean provideClient() { + return true; + } + + protected boolean provideServer() { + return true; + } + public void provide() throws Exception { final DependencyInfo dependency = DependencyInfo.create(getProject(), Constants.Configurations.MINECRAFT); minecraftVersion = dependency.getDependency().getVersion(); @@ -94,7 +103,17 @@ public abstract class MinecraftProvider { } if (offline) { - if (minecraftClientJar.exists() && minecraftServerJar.exists()) { + boolean exists = true; + + if (provideServer() && !minecraftServerJar.exists()) { + exists = false; + } + + if (provideClient() && !minecraftClientJar.exists()) { + exists = false; + } + + if (exists) { getProject().getLogger().debug("Found client and server jars, presuming up-to-date"); } else { throw new GradleException("Missing jar(s); Client: " + minecraftClientJar.exists() + ", Server: " + minecraftServerJar.exists()); @@ -103,7 +122,9 @@ public abstract class MinecraftProvider { downloadJars(getProject().getLogger()); } - serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath()); + if (provideServer()) { + serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath()); + } libraryProvider = new MinecraftLibraryProvider(); libraryProvider.provide(this, getProject()); @@ -119,11 +140,17 @@ public abstract class MinecraftProvider { workingDir = new File(getExtension().getFiles().getUserCache(), minecraftVersion); workingDir.mkdirs(); minecraftJson = file("minecraft-info.json"); - minecraftClientJar = file("minecraft-client.jar"); - minecraftServerJar = file("minecraft-server.jar"); - minecraftExtractedServerJar = file("minecraft-extracted_server.jar"); versionManifestJson = new File(getExtension().getFiles().getUserCache(), "version_manifest.json"); experimentalVersionsJson = new File(getExtension().getFiles().getUserCache(), "experimental_version_manifest.json"); + + if (provideClient()) { + minecraftClientJar = file("minecraft-client.jar"); + } + + if (provideServer()) { + minecraftServerJar = file("minecraft-server.jar"); + minecraftExtractedServerJar = file("minecraft-extracted_server.jar"); + } } private void downloadMcJson(boolean offline) throws IOException { @@ -243,14 +270,19 @@ public abstract class MinecraftProvider { return; } - MinecraftVersionMeta.Download client = versionInfo.download("client"); - MinecraftVersionMeta.Download server = versionInfo.download("server"); + if (provideClient()) { + MinecraftVersionMeta.Download client = versionInfo.download("client"); + HashedDownloadUtil.downloadIfInvalid(new URL(client.url()), minecraftClientJar, client.sha1(), logger, false); + } - HashedDownloadUtil.downloadIfInvalid(new URL(client.url()), minecraftClientJar, client.sha1(), logger, false); - HashedDownloadUtil.downloadIfInvalid(new URL(server.url()), minecraftServerJar, server.sha1(), logger, false); + if (provideServer()) { + MinecraftVersionMeta.Download server = versionInfo.download("server"); + HashedDownloadUtil.downloadIfInvalid(new URL(server.url()), minecraftServerJar, server.sha1(), logger, false); + } } protected final void extractBundledServerJar() throws IOException { + Preconditions.checkArgument(provideServer(), "Not configured to provide server jar"); Objects.requireNonNull(getServerBundleMetadata(), "Cannot bundled mc jar from none bundled server jar"); getLogger().info(":Extracting server jar from bootstrap"); @@ -281,17 +313,20 @@ public abstract class MinecraftProvider { } public File getMinecraftClientJar() { + Preconditions.checkArgument(provideClient(), "Not configured to provide client jar"); return minecraftClientJar; } // May be null on older versions @Nullable public File getMinecraftExtractedServerJar() { + Preconditions.checkArgument(provideServer(), "Not configured to provide server jar"); return minecraftExtractedServerJar; } // This may be the server bundler jar on newer versions prob not what you want. public File getMinecraftServerJar() { + Preconditions.checkArgument(provideServer(), "Not configured to provide server jar"); return minecraftServerJar; } @@ -303,10 +338,6 @@ public abstract class MinecraftProvider { return versionInfo; } - public MinecraftLibraryProvider getLibraryProvider() { - return libraryProvider; - } - public String getJarPrefix() { return jarPrefix; } @@ -315,10 +346,6 @@ public abstract class MinecraftProvider { this.jarPrefix = jarSuffix; } - public String getTargetConfig() { - return Constants.Configurations.MINECRAFT; - } - @Nullable public BundleMetadata getServerBundleMetadata() { return serverBundleMetadata; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java new file mode 100644 index 00000000..ef9427ae --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java @@ -0,0 +1,230 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.configuration.providers.minecraft; + +import static net.fabricmc.loom.configuration.CompileConfiguration.extendsFrom; + +import java.util.List; +import java.util.function.BiConsumer; + +import com.google.common.base.Preconditions; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.SourceSet; +import org.gradle.jvm.tasks.Jar; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.util.Constants; + +public abstract sealed class MinecraftSourceSets permits MinecraftSourceSets.Single, MinecraftSourceSets.Split { + public static MinecraftSourceSets get(Project project) { + return LoomGradleExtension.get(project).areEnvironmentSourceSetsSplit() ? Split.INSTANCE : Single.INSTANCE; + } + + public abstract void applyDependencies(BiConsumer consumer, List targets); + + public abstract String getCombinedSourceSetName(); + + public abstract String getSourceSetForEnv(String env); + + protected abstract List getAllSourceSetNames(); + + public void evaluateSplit(Project project) { + final LoomGradleExtension extension = LoomGradleExtension.get(project); + Preconditions.checkArgument(extension.areEnvironmentSourceSetsSplit()); + + Split.INSTANCE.evaluate(project); + } + + public abstract void afterEvaluate(Project project); + + protected void createSourceSets(Project project) { + final LoomGradleExtension extension = LoomGradleExtension.get(project); + + for (String name : getAllSourceSetNames()) { + extension.createLazyConfiguration(name, configuration -> configuration.setTransitive(false)); + + // All the configurations extend the loader deps. + extendsFrom(name, Constants.Configurations.LOADER_DEPENDENCIES, project); + } + } + + /** + * Used when we have a single source set, either with split or merged jars. + */ + public static final class Single extends MinecraftSourceSets { + private static final String MINECRAFT_NAMED = "minecraftNamed"; + + private static final Single INSTANCE = new Single(); + + @Override + public void applyDependencies(BiConsumer consumer, List targets) { + for (String target : targets) { + consumer.accept(MINECRAFT_NAMED, target); + } + } + + @Override + public String getCombinedSourceSetName() { + return MINECRAFT_NAMED; + } + + @Override + public String getSourceSetForEnv(String env) { + return SourceSet.MAIN_SOURCE_SET_NAME; + } + + @Override + protected List getAllSourceSetNames() { + return List.of(MINECRAFT_NAMED); + } + + @Override + public void afterEvaluate(Project project) { + // This is done in afterEvaluate as we need to be sure that split source sets was not enabled. + createSourceSets(project); + + // Default compile and runtime sourcesets. + extendsFrom(List.of( + JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME, + JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, + JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME, + JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME), + MINECRAFT_NAMED, project + ); + } + } + + /** + * Used when we have a split client/common source set and split jars. + */ + public static final class Split extends MinecraftSourceSets { + private static final String MINECRAFT_COMMON_NAMED = "minecraftCommonNamed"; + private static final String MINECRAFT_CLIENT_ONLY_NAMED = "minecraftClientOnlyNamed"; + private static final String MINECRAFT_COMBINED_NAMED = "minecraftCombinedNamed"; + + private static final String CLIENT_ONLY_SOURCE_SET_NAME = "client"; + + private static final Split INSTANCE = new Split(); + + @Override + public void applyDependencies(BiConsumer consumer, List targets) { + Preconditions.checkArgument(targets.size() == 2); + Preconditions.checkArgument(targets.contains("common")); + Preconditions.checkArgument(targets.contains("clientOnly")); + + consumer.accept(MINECRAFT_COMMON_NAMED, "common"); + consumer.accept(MINECRAFT_CLIENT_ONLY_NAMED, "clientOnly"); + } + + @Override + public String getCombinedSourceSetName() { + return MINECRAFT_COMBINED_NAMED; + } + + @Override + public String getSourceSetForEnv(String env) { + return env.equals("client") ? CLIENT_ONLY_SOURCE_SET_NAME : SourceSet.MAIN_SOURCE_SET_NAME; + } + + @Override + protected List getAllSourceSetNames() { + return List.of(MINECRAFT_COMMON_NAMED, MINECRAFT_CLIENT_ONLY_NAMED, MINECRAFT_COMBINED_NAMED); + } + + // Called during evaluation, when the loom extension method is called. + private void evaluate(Project project) { + createSourceSets(project); + + // Combined extends from the 2 environments. + extendsFrom(MINECRAFT_COMBINED_NAMED, MINECRAFT_COMMON_NAMED, project); + extendsFrom(MINECRAFT_COMBINED_NAMED, MINECRAFT_CLIENT_ONLY_NAMED, project); + + final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class); + final LoomGradleExtension loomExtension = LoomGradleExtension.get(project); + + // Register our new client only source set, main becomes common only, with their respective jars. + SourceSet mainSourceSet = javaExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); + SourceSet clientOnlySourceSet = javaExtension.getSourceSets().create(CLIENT_ONLY_SOURCE_SET_NAME); + + extendsFrom(List.of( + mainSourceSet.getCompileClasspathConfigurationName(), + mainSourceSet.getRuntimeClasspathConfigurationName() + ), MINECRAFT_COMMON_NAMED, project + ); + + extendsFrom(List.of( + clientOnlySourceSet.getCompileClasspathConfigurationName(), + clientOnlySourceSet.getRuntimeClasspathConfigurationName() + ), MINECRAFT_CLIENT_ONLY_NAMED, project + ); + + // Client depends on common. + extendsFrom(MINECRAFT_CLIENT_ONLY_NAMED, MINECRAFT_COMMON_NAMED, project); + clientOnlySourceSet.setCompileClasspath( + clientOnlySourceSet.getCompileClasspath() + .plus(mainSourceSet.getCompileClasspath()) + .plus(mainSourceSet.getOutput()) + ); + clientOnlySourceSet.setRuntimeClasspath( + clientOnlySourceSet.getRuntimeClasspath() + .plus(mainSourceSet.getRuntimeClasspath()) + .plus(mainSourceSet.getOutput()) + ); + + loomExtension.mixin(mixinExtension -> { + // Generate a refmap for mixins in the new source set. + mixinExtension.add(clientOnlySourceSet, "client-" + mixinExtension.getDefaultRefmapName().get(), (p) -> { }); + }); + + // Include the client only output in the jars + project.getTasks().named(mainSourceSet.getJarTaskName(), Jar.class).configure(jar -> { + jar.from(clientOnlySourceSet.getOutput().getClassesDirs()); + jar.from(clientOnlySourceSet.getOutput().getResourcesDir()); + }); + + if (project.getTasks().findByName(mainSourceSet.getSourcesJarTaskName()) == null) { + // No sources. + return; + } + + project.getTasks().named(mainSourceSet.getSourcesJarTaskName(), Jar.class).configure(jar -> { + jar.from(clientOnlySourceSet.getAllSource()); + }); + } + + @Override + public void afterEvaluate(Project project) { + } + + public static SourceSet getClientSourceSet(Project project) { + Preconditions.checkArgument(LoomGradleExtension.get(project).areEnvironmentSourceSetsSplit()); + + final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class); + return javaExtension.getSourceSets().getByName(CLIENT_ONLY_SOURCE_SET_NAME); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftVersionMeta.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftVersionMeta.java index 81009f46..26612d49 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftVersionMeta.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftVersionMeta.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import net.fabricmc.loom.util.Architecture; import net.fabricmc.loom.util.OperatingSystem; @SuppressWarnings("unused") @@ -85,7 +86,7 @@ public record MinecraftVersionMeta( return false; } - if (natives.get(OperatingSystem.CURRENT_OS) == null) { + if (classifierForOS() == null) { return false; } @@ -93,7 +94,13 @@ public record MinecraftVersionMeta( } public Download classifierForOS() { - return downloads().classifier(natives.get(OperatingSystem.CURRENT_OS)); + String classifier = natives.get(OperatingSystem.CURRENT_OS); + + if (Architecture.CURRENT.isArm()) { + classifier += "-arm64"; + } + + return downloads().classifier(classifier); } public Download artifact() { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ServerOnlyMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java similarity index 50% rename from src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ServerOnlyMinecraftProvider.java rename to src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java index 2cd8ff2f..edd237fd 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ServerOnlyMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java @@ -35,60 +35,64 @@ import org.gradle.api.Project; import net.fabricmc.loom.configuration.providers.BundleMetadata; -public final class ServerOnlyMinecraftProvider extends MinecraftProvider { - private Path minecraftServerOnlyJar; +public final class SingleJarMinecraftProvider extends MinecraftProvider { + private final Environment environment; - public ServerOnlyMinecraftProvider(Project project) { + private Path minecraftEnvOnlyJar; + + private SingleJarMinecraftProvider(Project project, Environment environment) { super(project); + this.environment = environment; + } + + public static SingleJarMinecraftProvider server(Project project) { + return new SingleJarMinecraftProvider(project, new Server()); + } + + public static SingleJarMinecraftProvider client(Project project) { + return new SingleJarMinecraftProvider(project, new Client()); } @Override protected void initFiles() { super.initFiles(); - minecraftServerOnlyJar = path("minecraft-server-only.jar"); + minecraftEnvOnlyJar = path("minecraft-%s-only.jar".formatted(environment.name())); } @Override public List getMinecraftJars() { - return List.of(minecraftServerOnlyJar); + return List.of(minecraftEnvOnlyJar); } @Override public void provide() throws Exception { super.provide(); - boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftServerOnlyJar); + boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftEnvOnlyJar); if (!requiresRefresh) { return; } - BundleMetadata serverBundleMetadata = getServerBundleMetadata(); - - if (serverBundleMetadata == null) { - throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar support server only configuration, please use a merged jar setup for this version of minecraft"); - } - - extractBundledServerJar(); - final Path serverJar = getMinecraftExtractedServerJar().toPath(); + final Path inputJar = environment.getInputJar(this); TinyRemapper remapper = null; try { remapper = TinyRemapper.newRemapper().build(); - Files.deleteIfExists(minecraftServerOnlyJar); + Files.deleteIfExists(minecraftEnvOnlyJar); // Pass through tiny remapper to fix the meta-inf - try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(minecraftServerOnlyJar).build()) { - outputConsumer.addNonClassFiles(serverJar, NonClassCopyMode.FIX_META_INF, remapper); - remapper.readInputs(serverJar); + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(minecraftEnvOnlyJar).build()) { + outputConsumer.addNonClassFiles(inputJar, NonClassCopyMode.FIX_META_INF, remapper); + remapper.readInputs(inputJar); remapper.apply(outputConsumer); } } catch (Exception e) { - Files.deleteIfExists(minecraftServerOnlyJar); - throw new RuntimeException("Failed to process server only jar", e); + Files.deleteIfExists(minecraftEnvOnlyJar); + throw new RuntimeException("Failed to process %s only jar".formatted(environment.name()), e); } finally { if (remapper != null) { remapper.finish(); @@ -96,7 +100,54 @@ public final class ServerOnlyMinecraftProvider extends MinecraftProvider { } } - public Path getMinecraftServerOnlyJar() { - return minecraftServerOnlyJar; + @Override + protected boolean provideClient() { + return environment instanceof Client; + } + + @Override + protected boolean provideServer() { + return environment instanceof Server; + } + + public Path getMinecraftEnvOnlyJar() { + return minecraftEnvOnlyJar; + } + + private interface Environment { + String name(); + + Path getInputJar(SingleJarMinecraftProvider provider) throws Exception; + } + + private static final class Server implements Environment { + @Override + public String name() { + return "server"; + } + + @Override + public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception { + BundleMetadata serverBundleMetadata = provider.getServerBundleMetadata(); + + if (serverBundleMetadata == null) { + return provider.getMinecraftServerJar().toPath(); + } + + provider.extractBundledServerJar(); + return provider.getMinecraftExtractedServerJar().toPath(); + } + } + + private static final class Client implements Environment { + @Override + public String name() { + return "client"; + } + + @Override + public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception { + return provider.getMinecraftClientJar().toPath(); + } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java index 319b33e2..b63f4796 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java @@ -77,7 +77,8 @@ public final class SplitMinecraftProvider extends MinecraftProvider { try (MinecraftJarSplitter jarSplitter = new MinecraftJarSplitter(clientJar, serverJar)) { // Required for loader to compute the version info also useful to have in both jars. jarSplitter.sharedEntry("version.json"); - jarSplitter.forcedClientEntry("assets/.mcassetsroot"); + jarSplitter.sharedEntry("assets/.mcassetsroot"); + jarSplitter.sharedEntry("assets/minecraft/lang/en_us.json"); jarSplitter.split(minecraftClientOnlyJar, minecraftCommonJar); } catch (Exception e) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/AssetIndex.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/AssetIndex.java index c87fc8c8..3dd6a3c5 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/AssetIndex.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/AssetIndex.java @@ -24,18 +24,38 @@ package net.fabricmc.loom.configuration.providers.minecraft.assets; -import java.util.HashSet; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonProperty; @SuppressWarnings("unused") -public record AssetIndex(Map objects, boolean virtual) { +public record AssetIndex(Map objects, boolean virtual, @JsonProperty("map_to_resources") boolean mapToResources) { public AssetIndex() { - this(new LinkedHashMap<>(), false); + this(new LinkedHashMap<>(), false, false); } - public Set getUniqueObjects() { - return new HashSet<>(this.objects.values()); + public Collection getObjects() { + return objects.entrySet().stream().map(Object::new).toList(); + } + + public record Entry(String hash, long size) { + } + + public record Object(String path, String hash, long size) { + private Object(Map.Entry entry) { + this(entry.getKey(), entry.getValue().hash(), entry.getValue().size()); + } + + public String name() { + int end = path().lastIndexOf("/") + 1; + + if (end > 0) { + return path().substring(end); + } + + return path(); + } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java deleted file mode 100644 index 5baec839..00000000 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2018-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.configuration.providers.minecraft.assets; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.net.URL; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import com.google.common.base.Stopwatch; -import me.tongfei.progressbar.DelegatingProgressBarConsumer; -import me.tongfei.progressbar.ProgressBar; -import me.tongfei.progressbar.ProgressBarBuilder; -import me.tongfei.progressbar.ProgressBarStyle; -import org.gradle.api.GradleException; -import org.gradle.api.Project; - -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.LoomGradlePlugin; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; -import net.fabricmc.loom.util.MirrorUtil; -import net.fabricmc.loom.util.HashedDownloadUtil; - -public class MinecraftAssetsProvider { - public static void provide(MinecraftProvider minecraftProvider, Project project) throws IOException { - LoomGradleExtension extension = LoomGradleExtension.get(project); - boolean offline = project.getGradle().getStartParameter().isOffline(); - - MinecraftVersionMeta versionInfo = minecraftProvider.getVersionInfo(); - MinecraftVersionMeta.AssetIndex assetIndex = versionInfo.assetIndex(); - - // get existing cache files - File assets = new File(extension.getFiles().getUserCache(), "assets"); - - if (!assets.exists()) { - assets.mkdirs(); - } - - File assetsInfo = new File(assets, "indexes" + File.separator + assetIndex.fabricId(minecraftProvider.minecraftVersion()) + ".json"); - - project.getLogger().info(":downloading asset index"); - - if (offline) { - if (assetsInfo.exists()) { - //We know it's outdated but can't do anything about it, oh well - project.getLogger().warn("Asset index outdated"); - } else { - //We don't know what assets we need, just that we don't have any - throw new GradleException("Asset index not found at " + assetsInfo.getAbsolutePath()); - } - } else { - HashedDownloadUtil.downloadIfInvalid(new URL(assetIndex.url()), assetsInfo, assetIndex.sha1(), project.getLogger(), false); - } - - ExecutorService executor = Executors.newFixedThreadPool(Math.min(16, Math.max(Runtime.getRuntime().availableProcessors() * 2, 1))); - int toDownload = 0; - - AssetIndex index; - - try (FileReader fileReader = new FileReader(assetsInfo)) { - index = LoomGradlePlugin.OBJECT_MAPPER.readValue(fileReader, AssetIndex.class); - } - - Stopwatch stopwatch = Stopwatch.createStarted(); - - Map parent = index.objects(); - - ProgressBar[] progressBar = {null}; - - try { - for (Map.Entry entry : parent.entrySet()) { - AssetObject object = entry.getValue(); - String sha1 = object.hash(); - String filename = "objects" + File.separator + sha1.substring(0, 2) + File.separator + sha1; - File file = new File(assets, filename); - - if (offline) { - if (file.exists()) { - project.getLogger().warn("Outdated asset " + entry.getKey()); - } else { - throw new GradleException("Asset " + entry.getKey() + " not found at " + file.getAbsolutePath()); - } - } else if (HashedDownloadUtil.requiresDownload(file, sha1, project.getLogger())) { - toDownload++; - - synchronized (progressBar) { - if (progressBar[0] == null) { - progressBar[0] = new ProgressBarBuilder() - .setConsumer(new DelegatingProgressBarConsumer(project.getLogger()::lifecycle)) - .setInitialMax(toDownload) - .setUpdateIntervalMillis(2000) - .setTaskName(":downloading assets") - .setStyle(ProgressBarStyle.ASCII) - .showSpeed() - .build(); - } - - progressBar[0].maxHint(toDownload); - } - - executor.execute(() -> { - String assetName = entry.getKey(); - int end = assetName.lastIndexOf("/") + 1; - - if (end > 0) { - assetName = assetName.substring(end); - } - - project.getLogger().debug(":downloading asset " + assetName); - - try { - HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, false); - } catch (IOException e) { - throw new RuntimeException("Failed to download: " + assetName, e); - } - - synchronized (progressBar) { - progressBar[0].step(); - } - }); - } - } - - project.getLogger().info("Took " + stopwatch.stop() + " to iterate " + parent.size() + " asset index."); - - //Wait for the assets to all download - executor.shutdown(); - - try { - if (executor.awaitTermination(2, TimeUnit.HOURS)) { - executor.shutdownNow(); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } finally { - if (progressBar[0] != null) { - progressBar[0].close(); - } - } - } -} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java index e169cc87..90d42d47 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -27,6 +27,7 @@ package net.fabricmc.loom.configuration.providers.minecraft.mapped; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -41,6 +42,7 @@ import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.configuration.providers.minecraft.SignatureFixerApplyVisitor; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.srg.InnerClassRemapper; @@ -60,8 +62,8 @@ public abstract class AbstractMappedMinecraftProvider getRemappedJars(); - protected void applyDependencies(BiConsumer consumer) { - // Override if needed + public List getDependencyTargets() { + return Collections.emptyList(); } public void provide(boolean applyDependencies) throws Exception { @@ -79,7 +81,16 @@ public abstract class AbstractMappedMinecraftProvider getProject().getDependencies().add(configuration, getDependencyNotation(name))); + final List dependencyTargets = getDependencyTargets(); + + if (dependencyTargets.isEmpty()) { + return; + } + + MinecraftSourceSets.get(getProject()).applyDependencies( + (configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)), + dependencyTargets + ); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/IntermediaryMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/IntermediaryMinecraftProvider.java index e31abde1..e69b57e7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/IntermediaryMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/IntermediaryMinecraftProvider.java @@ -33,11 +33,11 @@ import org.gradle.api.Project; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; -import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider; import net.fabricmc.loom.util.SidedClassVisitor; -public abstract sealed class IntermediaryMinecraftProvider extends AbstractMappedMinecraftProvider permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.ServerOnlyImpl, IntermediaryMinecraftProvider.SplitImpl { +public abstract sealed class IntermediaryMinecraftProvider extends AbstractMappedMinecraftProvider permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.SingleJarImpl, IntermediaryMinecraftProvider.SplitImpl { public IntermediaryMinecraftProvider(Project project, M minecraftProvider) { super(project, minecraftProvider); } @@ -86,16 +86,32 @@ public abstract sealed class IntermediaryMinecraftProvider implements ServerOnly { - public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) { + public static final class SingleJarImpl extends IntermediaryMinecraftProvider implements SingleJar { + private final String env; + + private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, String env) { super(project, minecraftProvider); + this.env = env; + } + + public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) { + return new SingleJarImpl(project, minecraftProvider, "server"); + } + + public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) { + return new SingleJarImpl(project, minecraftProvider, "client"); } @Override public List getRemappedJars() { return List.of( - new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL) + new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), MappingsNamespace.OFFICIAL) ); } + + @Override + public String env() { + return env; + } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/MappedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/MappedMinecraftProvider.java index 7bbd61be..6ec0f6c0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/MappedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/MappedMinecraftProvider.java @@ -65,16 +65,20 @@ public interface MappedMinecraftProvider { } } - interface ServerOnly extends ProviderImpl { - String SERVER_ONLY = "serverOnly"; + interface SingleJar extends ProviderImpl { + String env(); - default Path getServerOnlyJar() { - return getJar(SERVER_ONLY); + default String envName() { + return "%sOnly".formatted(env()); + } + + default Path getEnvOnlyJar() { + return getJar(envName()); } @Override default List getMinecraftJars() { - return List.of(getServerOnlyJar()); + return List.of(getEnvOnlyJar()); } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java index 1d9d317a..c30d4709 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -26,7 +26,6 @@ package net.fabricmc.loom.configuration.providers.minecraft.mapped; import java.nio.file.Path; import java.util.List; -import java.util.function.BiConsumer; import dev.architectury.tinyremapper.TinyRemapper; import org.gradle.api.Project; @@ -34,9 +33,8 @@ import org.gradle.api.Project; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; -import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider; -import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.SidedClassVisitor; public abstract class NamedMinecraftProvider extends AbstractMappedMinecraftProvider { @@ -67,8 +65,8 @@ public abstract class NamedMinecraftProvider extend } @Override - protected void applyDependencies(BiConsumer consumer) { - consumer.accept(Constants.Configurations.MINECRAFT_NAMED, MERGED); + public List getDependencyTargets() { + return List.of(MERGED); } } @@ -93,27 +91,42 @@ public abstract class NamedMinecraftProvider extend } @Override - protected void applyDependencies(BiConsumer consumer) { - consumer.accept(Constants.Configurations.MINECRAFT_NAMED, COMMON); - consumer.accept(Constants.Configurations.MINECRAFT_NAMED, CLIENT_ONLY); + public List getDependencyTargets() { + return List.of(CLIENT_ONLY, COMMON); } } - public static final class ServerOnlyImpl extends NamedMinecraftProvider implements ServerOnly { - public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) { + public static final class SingleJarImpl extends NamedMinecraftProvider implements SingleJar { + private final String env; + + private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, String env) { super(project, minecraftProvider); + this.env = env; + } + + public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) { + return new SingleJarImpl(project, minecraftProvider, "server"); + } + + public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) { + return new SingleJarImpl(project, minecraftProvider, "client"); } @Override public List getRemappedJars() { return List.of( - new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL) + new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), MappingsNamespace.OFFICIAL) ); } @Override - protected void applyDependencies(BiConsumer consumer) { - consumer.accept(Constants.Configurations.MINECRAFT_NAMED, SERVER_ONLY); + public List getDependencyTargets() { + return List.of(envName()); + } + + @Override + public String env() { + return env; } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java index 99c97f18..66ae93cc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -36,7 +36,8 @@ import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; -import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; +import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider; public abstract class ProcessedNamedMinecraftProvider> extends NamedMinecraftProvider { @@ -85,7 +86,16 @@ public abstract class ProcessedNamedMinecraftProvider getProject().getDependencies().add(configuration, getDependencyNotation(name))); + final List dependencyTargets = parentMinecraftProvider.getDependencyTargets(); + + if (dependencyTargets.isEmpty()) { + return; + } + + MinecraftSourceSets.get(getProject()).applyDependencies( + (configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)), + dependencyTargets + ); } } @@ -156,14 +166,30 @@ public abstract class ProcessedNamedMinecraftProvider implements ServerOnly { - public ServerOnlyImpl(NamedMinecraftProvider.ServerOnlyImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) { + public static final class SingleJarImpl extends ProcessedNamedMinecraftProvider implements SingleJar { + private final String env; + + private SingleJarImpl(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager, String env) { super(parentMinecraftProvide, jarProcessorManager); + this.env = env; + } + + public static ProcessedNamedMinecraftProvider.SingleJarImpl server(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) { + return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, "server"); + } + + public static ProcessedNamedMinecraftProvider.SingleJarImpl client(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) { + return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, "client"); } @Override - public Path getServerOnlyJar() { - return getProcessedPath(getParentMinecraftProvider().getServerOnlyJar()); + public Path getEnvOnlyJar() { + return getProcessedPath(getParentMinecraftProvider().getEnvOnlyJar()); + } + + @Override + public String env() { + return env; } } } diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java index a98060e9..3699e3a6 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java +++ b/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java @@ -40,6 +40,7 @@ public class FernflowerLogger extends IFernflowerLogger { @Override public void writeMessage(String message, Severity severity) { if (message.contains("Inconsistent inner class entries for")) return; + if (message.contains("Inconsistent generic signature in method")) return; System.err.println(message); } diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index b6a6fb42..3a1ab4c4 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -49,7 +49,9 @@ import net.fabricmc.loom.api.ForgeExtensionAPI; import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI; import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.api.MixinExtensionAPI; +import net.fabricmc.loom.api.ModSettings; import net.fabricmc.loom.api.decompilers.DecompilerOptions; +import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; @@ -61,6 +63,7 @@ import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.util.DeprecationHelper; import net.fabricmc.loom.util.ModPlatform; @@ -80,15 +83,22 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA protected final Property customManifest; protected final Property setupRemappedVariants; protected final Property transitiveAccessWideners; + protected final Property modProvidedJavadoc; protected final Property intermediary; + protected final Property intermediateMappingsProvider; private final Property runtimeOnlyLog4j; private final Property minecraftJarConfiguration; + private final Property splitEnvironmentalSourceSet; private final InterfaceInjectionExtensionAPI interfaceInjectionExtension; private final ModVersionParser versionParser; private final NamedDomainObjectContainer runConfigs; private final NamedDomainObjectContainer decompilers; + private final NamedDomainObjectContainer mods; + + // A common mistake with layered mappings is to call the wrong `officialMojangMappings` method, use this to keep track of when we are building a layered mapping spec. + protected final ThreadLocal layeredSpecBuilderScope = ThreadLocal.withInitial(() -> false); // =================== // Architectury Loom @@ -115,9 +125,15 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA this.transitiveAccessWideners = project.getObjects().property(Boolean.class) .convention(true); this.transitiveAccessWideners.finalizeValueOnRead(); + this.modProvidedJavadoc = project.getObjects().property(Boolean.class) + .convention(true); + this.modProvidedJavadoc.finalizeValueOnRead(); this.intermediary = project.getObjects().property(String.class) .convention("https://maven.fabricmc.net/net/fabricmc/intermediary/%1$s/intermediary-%1$s-v2.jar"); + this.intermediateMappingsProvider = project.getObjects().property(IntermediateMappingsProvider.class); + this.intermediateMappingsProvider.finalizeValueOnRead(); + this.versionParser = new ModVersionParser(project); this.deprecationHelper = new DeprecationHelper.ProjectBased(project); @@ -125,6 +141,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA this.runConfigs = project.container(RunConfigSettings.class, baseName -> new RunConfigSettings(project, baseName)); this.decompilers = project.getObjects().domainObjectContainer(DecompilerOptions.class); + this.mods = project.getObjects().domainObjectContainer(ModSettings.class); this.minecraftJarConfiguration = project.getObjects().property(MinecraftJarConfiguration.class).convention(MinecraftJarConfiguration.MERGED); this.minecraftJarConfiguration.finalizeValueOnRead(); @@ -137,6 +154,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA this.interfaceInjectionExtension = project.getObjects().newInstance(InterfaceInjectionExtensionAPI.class); + this.splitEnvironmentalSourceSet = project.getObjects().property(Boolean.class).convention(false); + this.splitEnvironmentalSourceSet.finalizeValueOnRead(); + // Add main source set by default interfaceInjection(interfaceInjection -> { final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); @@ -202,10 +222,23 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA return jarProcessors; } + @Override + public Dependency officialMojangMappings() { + if (layeredSpecBuilderScope.get()) { + throw new IllegalStateException("Use `officialMojangMappings()` when configuring layered mappings, not the extension method `loom.officialMojangMappings()`"); + } + + return layered(LayeredMappingSpecBuilder::officialMojangMappings); + } + @Override public Dependency layered(Action action) { LayeredMappingSpecBuilderImpl builder = new LayeredMappingSpecBuilderImpl(this); + + layeredSpecBuilderScope.set(true); action.execute(builder); + layeredSpecBuilderScope.set(false); + LayeredMappingSpec builtSpec = builder.build(); return new LayeredMappingsDependency(getProject(), new GradleMappingContext(getProject(), builtSpec.getVersion().replace("+", "_").replace(".", "_")), builtSpec, builtSpec.getVersion()); } @@ -257,6 +290,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA return transitiveAccessWideners; } + @Override + public Property getEnableModProvidedJavadoc() { + return modProvidedJavadoc; + } + protected abstract Project getProject(); protected abstract LoomFiles getFiles(); @@ -266,6 +304,26 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA return intermediary; } + @Override + public IntermediateMappingsProvider getIntermediateMappingsProvider() { + return intermediateMappingsProvider.get(); + } + + @Override + public void setIntermediateMappingsProvider(IntermediateMappingsProvider intermediateMappingsProvider) { + this.intermediateMappingsProvider.set(intermediateMappingsProvider); + } + + @Override + public void setIntermediateMappingsProvider(Class clazz, Action action) { + T provider = getProject().getObjects().newInstance(clazz); + configureIntermediateMappingsProviderInternal(provider); + action.execute(provider); + intermediateMappingsProvider.set(provider); + } + + protected abstract void configureIntermediateMappingsProviderInternal(T provider); + @Override public void disableDeprecatedPomGeneration(MavenPublication publication) { net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication); @@ -281,11 +339,39 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA return runtimeOnlyLog4j; } + @Override + public void splitEnvironmentSourceSets() { + splitMinecraftJar(); + + splitEnvironmentalSourceSet.set(true); + + // We need to lock these values, as we setup the new source sets right away. + splitEnvironmentalSourceSet.finalizeValue(); + minecraftJarConfiguration.finalizeValue(); + + MinecraftSourceSets.get(getProject()).evaluateSplit(getProject()); + } + + @Override + public boolean areEnvironmentSourceSetsSplit() { + return splitEnvironmentalSourceSet.get(); + } + @Override public InterfaceInjectionExtensionAPI getInterfaceInjection() { return interfaceInjectionExtension; } + @Override + public void mods(Action> action) { + action.execute(getMods()); + } + + @Override + public NamedDomainObjectContainer getMods() { + return mods; + } + @Override public void silentMojangMappingsLicense() { this.silentMojangMappingsLicense = true; @@ -362,6 +448,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA throw new RuntimeException("Yeah... something is really wrong"); } + @Override + protected void configureIntermediateMappingsProviderInternal(T provider) { + throw new RuntimeException("Yeah... something is really wrong"); + } + @Override public MixinExtension getMixin() { throw new RuntimeException("Yeah... something is really wrong"); diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java index f00f1918..58ed9d41 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java @@ -44,12 +44,14 @@ import org.gradle.api.file.FileCollection; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.ForgeExtensionAPI; +import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.InstallerData; import net.fabricmc.loom.configuration.LoomDependencyManager; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.providers.forge.DependencyProviders; +import net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; @@ -95,6 +97,13 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen this.unmappedMods = project.files(); this.forgeExtension = Suppliers.memoize(() -> isForge() ? project.getObjects().newInstance(ForgeExtensionImpl.class, project, this) : null); this.supportsInclude = new LazyBool(() -> Boolean.parseBoolean(Objects.toString(project.findProperty(INCLUDE_PROPERTY)))); + + // Setup the default intermediate mappings provider. + setIntermediateMappingsProvider(IntermediaryMappingsProvider.class, provider -> { + provider.getIntermediaryUrl() + .convention(getIntermediaryUrl()) + .finalizeValueOnRead(); + }); } @Override @@ -256,6 +265,12 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen transitiveAccessWideners.addAll(accessWidenerFiles); } + @Override + protected void configureIntermediateMappingsProviderInternal(T provider) { + provider.getMinecraftVersion().set(getProject().provider(() -> getMinecraftProvider().minecraftVersion())); + provider.getMinecraftVersion().disallowChanges(); + } + @Override protected String getMinecraftVersion() { return getMinecraftProvider().minecraftVersion(); diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java index 611b6f59..a0831dd9 100644 --- a/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -36,16 +36,22 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.util.PatternSet; import net.fabricmc.loom.api.MixinExtensionAPI; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; public abstract class MixinExtensionApiImpl implements MixinExtensionAPI { protected final Project project; protected final Property useMixinAp; + private final Property refmapTargetNamespace; public MixinExtensionApiImpl(Project project) { this.project = Objects.requireNonNull(project); this.useMixinAp = project.getObjects().property(Boolean.class) // .convention(project.provider(() -> LoomGradleExtension.get(project).isForge())); .convention(true); + + this.refmapTargetNamespace = project.getObjects().property(String.class) + .convention(MappingsNamespace.INTERMEDIARY.toString()); + this.refmapTargetNamespace.finalizeValueOnRead(); } protected final PatternSet add0(SourceSet sourceSet, String refmapName) { @@ -59,6 +65,13 @@ public abstract class MixinExtensionApiImpl implements MixinExtensionAPI { return useMixinAp; } + @Override + public Property getRefmapTargetNamespace() { + if (!getUseLegacyMixinAp().get()) throw new IllegalStateException("You need to set useLegacyMixinAp = true to configure Mixin annotation processor."); + + return refmapTargetNamespace; + } + @Override public void add(SourceSet sourceSet, String refmapName, Action action) { PatternSet pattern = add0(sourceSet, refmapName); diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java index f424a3b4..a815305e 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java @@ -52,7 +52,7 @@ public abstract class AbstractRunTask extends JavaExec { @Override public void exec() { - setWorkingDir(new File(getProject().getRootDir(), config.runDir)); + setWorkingDir(new File(getProject().getProjectDir(), config.runDir)); environment(config.envVariables); super.exec(); diff --git a/src/main/java/net/fabricmc/loom/task/DownloadAssetsTask.java b/src/main/java/net/fabricmc/loom/task/DownloadAssetsTask.java index f03a1079..c4c204b3 100644 --- a/src/main/java/net/fabricmc/loom/task/DownloadAssetsTask.java +++ b/src/main/java/net/fabricmc/loom/task/DownloadAssetsTask.java @@ -24,20 +24,179 @@ package net.fabricmc.loom.task; +import java.io.File; +import java.io.FileReader; import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.util.Deque; +import java.util.Objects; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import javax.inject.Inject; + +import org.gradle.api.GradleException; import org.gradle.api.Project; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.configuration.providers.minecraft.assets.MinecraftAssetsProvider; +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; +import net.fabricmc.loom.configuration.providers.minecraft.assets.AssetIndex; +import net.fabricmc.loom.util.HashedDownloadUtil; +import net.fabricmc.loom.util.MirrorUtil; +import net.fabricmc.loom.util.gradle.ProgressLoggerHelper; + +public abstract class DownloadAssetsTask extends AbstractLoomTask { + @Input + public abstract Property getAssetsHash(); + + @OutputDirectory + public abstract RegularFileProperty getAssetsDirectory(); + + @OutputDirectory + public abstract RegularFileProperty getLegacyResourcesDirectory(); + + @Inject + public DownloadAssetsTask() { + final MinecraftVersionMeta versionInfo = getExtension().getMinecraftProvider().getVersionInfo(); + final File assetsDir = new File(getExtension().getFiles().getUserCache(), "assets"); + + getAssetsDirectory().set(assetsDir); + getAssetsHash().set(versionInfo.assetIndex().sha1()); + + if (versionInfo.assets().equals("legacy")) { + getLegacyResourcesDirectory().set(new File(assetsDir, "/legacy/" + versionInfo.id())); + } else { + // pre-1.6 resources + RunConfigSettings client = Objects.requireNonNull(getExtension().getRunConfigs().findByName("client"), "Could not find client run config"); + getLegacyResourcesDirectory().set(new File(getProject().getProjectDir(), client.getRunDir() + "/resources")); + } + + getAssetsHash().finalizeValueOnRead(); + getAssetsDirectory().finalizeValueOnRead(); + getLegacyResourcesDirectory().finalizeValueOnRead(); + } -public class DownloadAssetsTask extends AbstractLoomTask { @TaskAction public void downloadAssets() throws IOException { - Project project = this.getProject(); - LoomGradleExtension extension = getExtension(); + final Project project = this.getProject(); + final File assetsDirectory = getAssetsDirectory().get().getAsFile(); + final Deque loggers = new ConcurrentLinkedDeque<>(); + final ExecutorService executor = Executors.newFixedThreadPool(Math.min(10, Math.max(Runtime.getRuntime().availableProcessors() / 2, 1))); + final AssetIndex assetIndex = getAssetIndex(); - MinecraftAssetsProvider.provide(extension.getMinecraftProvider(), project); + if (!assetsDirectory.exists()) { + assetsDirectory.mkdirs(); + } + + if (assetIndex.mapToResources()) { + getLegacyResourcesDirectory().get().getAsFile().mkdirs(); + } + + for (AssetIndex.Object object : assetIndex.getObjects()) { + final String path = object.path(); + final String sha1 = object.hash(); + final File file = getAssetsFile(object, assetIndex); + + if (getProject().getGradle().getStartParameter().isOffline()) { + if (!file.exists()) { + throw new GradleException("Asset " + path + " not found at " + file.getAbsolutePath()); + } + + continue; + } + + final Supplier getOrCreateLogger = () -> { + ProgressLoggerHelper logger = loggers.pollFirst(); + + if (logger == null) { + // No logger available, create a new one + logger = ProgressLoggerHelper.getProgressFactory(project, DownloadAssetsTask.class.getName()); + logger.start("Downloading assets...", "assets"); + } + + return logger; + }; + + executor.execute(() -> { + final ProgressLoggerHelper logger = getOrCreateLogger.get(); + + try { + HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, () -> { + project.getLogger().debug("downloading asset " + object.name()); + logger.progress(String.format("%-30.30s", object.name()) + " - " + sha1); + }); + } catch (IOException e) { + throw new UncheckedIOException("Failed to download: " + object.name(), e); + } + + // Give this logger back + loggers.add(logger); + }); + } + + // Wait for the assets to all download + try { + executor.shutdown(); + + if (executor.awaitTermination(2, TimeUnit.HOURS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + loggers.forEach(ProgressLoggerHelper::completed); + } + } + + private MinecraftVersionMeta.AssetIndex getAssetIndexMeta() { + MinecraftVersionMeta versionInfo = getExtension().getMinecraftProvider().getVersionInfo(); + return versionInfo.assetIndex(); + } + + private AssetIndex getAssetIndex() throws IOException { + final LoomGradleExtension extension = getExtension(); + final MinecraftProvider minecraftProvider = extension.getMinecraftProvider(); + + MinecraftVersionMeta.AssetIndex assetIndex = getAssetIndexMeta(); + File assetsInfo = new File(getAssetsDirectory().get().getAsFile(), "indexes" + File.separator + assetIndex.fabricId(minecraftProvider.minecraftVersion()) + ".json"); + + getProject().getLogger().info(":downloading asset index"); + + if (getProject().getGradle().getStartParameter().isOffline()) { + if (assetsInfo.exists()) { + // We know it's outdated but can't do anything about it, oh well + getProject().getLogger().warn("Asset index outdated"); + } else { + // We don't know what assets we need, just that we don't have any + throw new GradleException("Asset index not found at " + assetsInfo.getAbsolutePath()); + } + } else { + HashedDownloadUtil.downloadIfInvalid(new URL(assetIndex.url()), assetsInfo, assetIndex.sha1(), getProject().getLogger(), false); + } + + try (FileReader fileReader = new FileReader(assetsInfo)) { + return LoomGradlePlugin.OBJECT_MAPPER.readValue(fileReader, AssetIndex.class); + } + } + + private File getAssetsFile(AssetIndex.Object object, AssetIndex index) { + if (index.mapToResources() || index.virtual()) { + return new File(getLegacyResourcesDirectory().get().getAsFile(), object.path()); + } + + final String filename = "objects" + File.separator + object.hash().substring(0, 2) + File.separator + object.hash(); + return new File(getAssetsDirectory().get().getAsFile(), filename); } } diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 3f4599be..1d05f4d1 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -65,6 +65,7 @@ import net.fabricmc.loom.api.decompilers.LoomDecompiler; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMappingsProcessor; import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor; +import net.fabricmc.loom.configuration.mods.ModJavadocProcessor; import net.fabricmc.loom.decompilers.LineNumberRemapper; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.FileSystemUtil; @@ -342,6 +343,12 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { mappingsProcessors.add(new InterfaceInjectionProcessor(getProject())); } + final ModJavadocProcessor javadocProcessor = ModJavadocProcessor.create(getProject()); + + if (javadocProcessor != null) { + mappingsProcessors.add(javadocProcessor); + } + if (mappingsProcessors.isEmpty()) { return inputMappings; } diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index e43cba47..af9a16d3 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2021 FabricMC + * Copyright (c) 2016-2022 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 @@ -34,6 +34,7 @@ import org.gradle.api.tasks.TaskProvider; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.task.launch.GenerateDLIConfigTask; import net.fabricmc.loom.task.launch.GenerateLog4jConfigTask; import net.fabricmc.loom.task.launch.GenerateRemapClasspathTask; @@ -142,11 +143,30 @@ public final class LoomTasks { extension.getRunConfigs().create("client", RunConfigSettings::client); extension.getRunConfigs().create("server", RunConfigSettings::server); - // Remove the client run config when server only. Done by name to not remove any possible custom run configs + // Remove the client or server run config when not required. Done by name to not remove any possible custom run configs project.afterEvaluate(p -> { - if (extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SERVER_ONLY) { - extension.getRunConfigs().removeIf(settings -> settings.getName().equals("client")); + String taskName = switch (extension.getMinecraftJarConfiguration().get()) { + case SERVER_ONLY -> "client"; + case CLIENT_ONLY -> "server"; + default -> null; + }; + + if (taskName == null) { + return; } + + extension.getRunConfigs().removeIf(settings -> settings.getName().equals(taskName)); + }); + + // Configure the run config source sets. + project.afterEvaluate(p -> { + if (!extension.areEnvironmentSourceSetsSplit()) { + return; + } + + extension.getRunConfigs().configureEach(settings -> + settings.source(MinecraftSourceSets.get(project).getSourceSetForEnv(settings.getEnvironment())) + ); }); } diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index e4f1630c..ff8b0412 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -39,17 +39,20 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.inject.Inject; @@ -69,6 +72,7 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; @@ -89,6 +93,7 @@ import net.fabricmc.loom.build.MixinRefmapHelper; import net.fabricmc.loom.build.nesting.IncludedJarFactory; import net.fabricmc.loom.build.nesting.JarNester; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.extension.MixinExtension; import net.fabricmc.loom.task.service.JarManifestService; import net.fabricmc.loom.task.service.MappingsService; @@ -97,13 +102,16 @@ import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.LfWriter; import net.fabricmc.loom.util.ModPlatform; +import net.fabricmc.loom.util.Pair; +import net.fabricmc.loom.util.SidedClassVisitor; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.aw2at.Aw2At; import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper; import net.fabricmc.lorenztiny.TinyMappingsReader; public abstract class RemapJarTask extends AbstractRemapJarTask { - private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; + public static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; + public static final String MANIFEST_NAMESPACE_KEY = "Fabric-Mapping-Namespace"; @InputFiles public abstract ConfigurableFileCollection getNestedJars(); @@ -212,6 +220,16 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { if (!getAtAccessWideners().get().isEmpty()) { params.getMappingBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), MappingsService.createDefault(getProject(), getSourceNamespace().get(), getTargetNamespace().get()))); } + + if (extension.areEnvironmentSourceSetsSplit()) { + final List clientOnlyJarEntries = getClientOnlyJarEntries(); + params.getManifestAttributes().set(Map.of( + "Fabric-Loom-Split-Environment", "true", + "Fabric-Loom-Client-Only-Entries", String.join(";", clientOnlyJarEntries) + )); + + params.getClientOnlyClasses().set(clientOnlyJarEntries.stream().filter(s -> s.endsWith(".class")).toList()); + } }); } @@ -272,34 +290,14 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { MixinExtension.getMixinInformationContainer(sourceSet) ); - String[] rootPaths = sourceSet.getResources().getSrcDirs().stream() - .map(root -> { - String rootPath = root.getAbsolutePath().replace("\\", "/"); - - if (rootPath.charAt(rootPath.length() - 1) != '/') { - rootPath += '/'; - } - - return rootPath; - }) - .toArray(String[]::new); + final List rootPaths = getRootPaths(sourceSet.getResources().getSrcDirs()); final String refmapName = container.refmapNameProvider().get(); final List mixinConfigs = container.sourceSet().getResources() .matching(container.mixinConfigPattern()) .getFiles() .stream() - .map(file -> { - String s = file.getAbsolutePath().replace("\\", "/"); - - for (String rootPath : rootPaths) { - if (s.startsWith(rootPath)) { - s = s.substring(rootPath.length()); - } - } - - return s; - }) + .map(relativePath(rootPaths)) .filter(allMixinConfigs::contains) .toList(); @@ -346,6 +344,9 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { Property getJarManifestService(); Property getTinyRemapperBuildServiceUuid(); Property getMappingBuildServiceUuid(); + + MapProperty getManifestAttributes(); + ListProperty getClientOnlyClasses(); } public abstract static class RemapAction extends AbstractRemapAction { @@ -367,6 +368,10 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { remap(); + if (getParameters().getClientOnlyClasses().isPresent()) { + markClientOnlyClasses(); + } + if (!injectAccessWidener()) { remapAccessWidener(); } @@ -400,6 +405,15 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { } } + private void markClientOnlyClasses() throws IOException { + final Stream>> tranformers = getParameters().getClientOnlyClasses().get().stream() + .map(s -> new Pair<>(s, + (ZipUtils.AsmClassOperator) classVisitor -> SidedClassVisitor.CLIENT.insertApplyVisitor(null, classVisitor) + )); + + ZipUtils.transform(outputFile, tranformers); + } + private boolean injectAccessWidener() throws IOException { if (!getParameters().getInjectAccessWidener().isPresent()) return false; @@ -518,8 +532,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { int count = ZipUtils.transform(outputFile, Map.of(MANIFEST_PATH, bytes -> { var manifest = new Manifest(new ByteArrayInputStream(bytes)); - getParameters().getJarManifestService().get().apply(manifest); - manifest.getMainAttributes().putValue("Fabric-Mapping-Namespace", getParameters().getTargetNamespace().get()); + getParameters().getJarManifestService().get().apply(manifest, getParameters().getManifestAttributes().get()); + manifest.getMainAttributes().putValue(MANIFEST_NAMESPACE_KEY, getParameters().getTargetNamespace().get()); ByteArrayOutputStream out = new ByteArrayOutputStream(); manifest.write(out); @@ -546,6 +560,50 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { } } + private List getClientOnlyJarEntries() { + final SourceSet clientSourceSet = MinecraftSourceSets.Split.getClientSourceSet(getProject()); + + final ConfigurableFileCollection output = getProject().getObjects().fileCollection(); + output.from(clientSourceSet.getOutput().getClassesDirs()); + output.from(clientSourceSet.getOutput().getResourcesDir()); + + final List rootPaths = new ArrayList<>(); + + rootPaths.addAll(getRootPaths(clientSourceSet.getOutput().getClassesDirs().getFiles())); + rootPaths.addAll(getRootPaths(Set.of(Objects.requireNonNull(clientSourceSet.getOutput().getResourcesDir())))); + + return output.getAsFileTree().getFiles().stream() + .map(relativePath(rootPaths)) + .toList(); + } + + private static List getRootPaths(Set files) { + return files.stream() + .map(root -> { + String rootPath = root.getAbsolutePath().replace("\\", "/"); + + if (rootPath.charAt(rootPath.length() - 1) != '/') { + rootPath += '/'; + } + + return rootPath; + }).toList(); + } + + private static Function relativePath(List rootPaths) { + return file -> { + String s = file.getAbsolutePath().replace("\\", "/"); + + for (String rootPath : rootPaths) { + if (s.startsWith(rootPath)) { + s = s.substring(rootPath.length()); + } + } + + return s; + }; + } + @Internal public TinyRemapperService getTinyRemapperService() { return tinyRemapperService.get(); diff --git a/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java index 51f66a1a..176ae614 100644 --- a/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java +++ b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java @@ -41,14 +41,24 @@ import org.gradle.api.logging.configuration.ConsoleOutput; import org.gradle.api.tasks.TaskAction; import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; import net.fabricmc.loom.task.AbstractLoomTask; import net.fabricmc.loom.util.PropertyUtil; +import net.fabricmc.loom.util.gradle.SourceSetHelper; public abstract class GenerateDLIConfigTask extends AbstractLoomTask { @TaskAction public void run() throws IOException { final String nativesPath = getExtension().getFiles().getNativesDirectory(getProject()).getAbsolutePath(); + final MinecraftVersionMeta versionInfo = getExtension().getMinecraftProvider().getVersionInfo(); + File assetsDirectory = new File(getExtension().getFiles().getUserCache(), "assets"); + + if (versionInfo.assets().equals("legacy")) { + assetsDirectory = new File(assetsDirectory, "/legacy/" + versionInfo.id()); + } + final LaunchConfig launchConfig = new LaunchConfig() .property(!getExtension().isQuilt() ? "fabric.development" : "loader.development", "true") .property(!getExtension().isQuilt() ? "fabric.remapClasspathFile" : "loader.remapClasspathFile", getExtension().getFiles().getRemapClasspathFile().getAbsolutePath()) @@ -58,12 +68,21 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask { .property("client", "java.library.path", nativesPath) .property("client", "org.lwjgl.librarypath", nativesPath); - if (!getExtension().isForge()) { + if (!getExtension().isForge()) launchConfig .argument("client", "--assetIndex") .argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion())) .argument("client", "--assetsDir") - .argument("client", new File(getExtension().getFiles().getUserCache(), "assets").getAbsolutePath()); + .argument("client", assetsDirectory.getAbsolutePath()); + + if (getExtension().areEnvironmentSourceSetsSplit()) { + launchConfig.property("client", "fabric.gameJarPath.client", getGameJarPath("client")); + launchConfig.property("fabric.gameJarPath", getGameJarPath("common")); + } + + if (!getExtension().getMods().isEmpty()) { + launchConfig.property("fabric.classPathGroups", getClassPathGroups()); + } } if (getExtension().isQuilt()) { @@ -132,6 +151,29 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask { .collect(Collectors.joining(",")); } + private String getGameJarPath(String env) { + MappedMinecraftProvider.Split split = (MappedMinecraftProvider.Split) getExtension().getNamedMinecraftProvider(); + + return switch (env) { + case "client" -> split.getClientOnlyJar().toAbsolutePath().toString(); + case "common" -> split.getCommonJar().toAbsolutePath().toString(); + default -> throw new UnsupportedOperationException(); + }; + } + + /** + * See: https://github.com/FabricMC/fabric-loader/pull/585. + */ + private String getClassPathGroups() { + return getExtension().getMods().stream() + .map(modSettings -> + SourceSetHelper.getClasspath(modSettings, getProject()).stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining(File.pathSeparator)) + ) + .collect(Collectors.joining(File.pathSeparator+File.pathSeparator)); + } + public static class LaunchConfig { private final Map> values = new HashMap<>(); 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 f187e8bc..3942e26b 100644 --- a/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java +++ b/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -25,6 +25,7 @@ package net.fabricmc.loom.task.service; import java.io.Serializable; +import java.util.Map; import java.util.Optional; import java.util.jar.Attributes; import java.util.jar.Manifest; @@ -70,7 +71,7 @@ public abstract class JarManifestService implements BuildService extraValues) { // Don't set when running the reproducible build tests as it will break them when anything updates if (Boolean.getBoolean("loom.test.reproducible")) { return; @@ -91,6 +92,10 @@ public abstract class JarManifestService implements BuildService entry : extraValues.entrySet()) { + attributes.putValue(entry.getKey(), entry.getValue()); + } } private static Optional getLoaderVersion(Project project) { 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 eb43d877..887b6c1f 100644 --- a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java @@ -39,10 +39,13 @@ import dev.architectury.tinyremapper.IMappingProvider; import dev.architectury.tinyremapper.InputTag; import dev.architectury.tinyremapper.TinyRemapper; import org.gradle.api.Project; +import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension; import net.fabricmc.loom.task.AbstractRemapJarTask; +import net.fabricmc.loom.util.kotlin.KotlinClasspath; +import net.fabricmc.loom.util.kotlin.KotlinClasspathService; +import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader; import net.fabricmc.loom.util.service.SharedService; import net.fabricmc.loom.util.service.SharedServiceManager; @@ -54,15 +57,15 @@ public class TinyRemapperService implements SharedService { final LoomGradleExtension extension = LoomGradleExtension.get(project); final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project); final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get(); - final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm"); + final @Nullable KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(project); // Generates an id that is used to share the remapper across projects. This tasks in the remap jar task name to handle custom remap jar tasks separately. final var joiner = new StringJoiner(":"); joiner.add(extension.getMappingsProvider().getBuildServiceName("remapJarService", from, to)); joiner.add(remapJarTask.getName()); - if (useKotlinExtension) { - joiner.add("kotlin"); + if (kotlinClasspathService != null) { + joiner.add("kotlin-" + kotlinClasspathService.version()); } if (remapJarTask.getRemapperIsolation().get()) { @@ -76,10 +79,10 @@ public class TinyRemapperService implements SharedService { mappings.add(MappingsService.createDefault(project, from, to).getMappingsProvider()); if (legacyMixin) { - mappings.add(MixinMappingsService.getService(SharedServiceManager.get(project), extension.getMappingsProvider()).getMappingProvider("named", "intermediary")); + mappings.add(MixinMappingsService.getService(SharedServiceManager.get(project), extension.getMappingsProvider()).getMappingProvider(from, to)); } - return new TinyRemapperService(mappings, !legacyMixin, useKotlinExtension); + return new TinyRemapperService(mappings, !legacyMixin, kotlinClasspathService); }); service.readClasspath(remapJarTask.getClasspath().getFiles().stream().map(File::toPath).toList()); @@ -88,12 +91,14 @@ public class TinyRemapperService implements SharedService { } private TinyRemapper tinyRemapper; + @Nullable + private KotlinRemapperClassloader kotlinRemapperClassloader; private final Map inputTagMap = new HashMap<>(); private final HashSet classpath = new HashSet<>(); // Set to true once remapping has started, once set no inputs can be read. private boolean isRemapping = false; - public TinyRemapperService(List mappings, boolean useMixinExtension, boolean useKotlinExtension) { + public TinyRemapperService(List mappings, boolean useMixinExtension, @Nullable KotlinClasspath kotlinClasspath) { TinyRemapper.Builder builder = TinyRemapper.newRemapper(); for (IMappingProvider provider : mappings) { @@ -104,8 +109,9 @@ public class TinyRemapperService implements SharedService { builder.extension(new dev.architectury.tinyremapper.extension.mixin.MixinExtension()); } - if (useKotlinExtension) { - builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE); + if (kotlinClasspath != null) { + kotlinRemapperClassloader = KotlinRemapperClassloader.create(kotlinClasspath); + builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension()); } tinyRemapper = builder.build(); @@ -156,5 +162,9 @@ public class TinyRemapperService implements SharedService { tinyRemapper.finish(); tinyRemapper = null; } + + if (kotlinRemapperClassloader != null) { + kotlinRemapperClassloader.close(); + } } } diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index b36089a4..54b0b7ab 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -71,7 +71,6 @@ public class Constants { public static final String MINECRAFT_DEPENDENCIES = "minecraftLibraries"; public static final String MINECRAFT_RUNTIME_DEPENDENCIES = "minecraftRuntimeOnlyLibraries"; public static final String MINECRAFT_NATIVES = "minecraftNatives"; - public static final String MINECRAFT_NAMED = "minecraftNamed"; public static final String MAPPINGS = "mappings"; public static final String MAPPINGS_FINAL = "mappingsFinal"; public static final String LOADER_DEPENDENCIES = "loaderLibraries"; @@ -178,6 +177,11 @@ public class Constants { } } + public static final class CustomModJsonKeys { + public static final String INJECTED_INTERFACE = "loom:injected_interfaces"; + public static final String PROVIDED_JAVADOC = "loom:provided_javadoc"; + } + public static final class Forge { public static final String LAUNCH_TESTING = "net.minecraftforge.userdev.LaunchTesting"; public static final String ACCESS_TRANSFORMER_PATH = "META-INF/accesstransformer.cfg"; diff --git a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java index a5eb9c24..354c7238 100644 --- a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java +++ b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java @@ -35,7 +35,6 @@ import java.util.zip.GZIPInputStream; import javax.annotation.Nullable; -import com.google.common.hash.Hashing; import com.google.common.io.Files; import org.apache.commons.io.FileUtils; import org.gradle.api.logging.Logger; @@ -120,13 +119,6 @@ public class HashedDownloadUtil { throw e; } - if (!Checksum.equals(to, expectedHash)) { - String actualHash = Files.asByteSource(to).hash(Hashing.sha1()).toString(); - delete(to); - - throw new IOException(String.format("Downloaded file from %s to %s and got unexpected hash of %s expected %s", from, to, actualHash, expectedHash)); - } - saveSha1(to, expectedHash, logger); } diff --git a/src/main/java/net/fabricmc/loom/util/LibraryLocationLogger.java b/src/main/java/net/fabricmc/loom/util/LibraryLocationLogger.java new file mode 100644 index 00000000..2d27427f --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/LibraryLocationLogger.java @@ -0,0 +1,76 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.util; + +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Preconditions; +import com.google.gson.Gson; +import kotlinx.metadata.jvm.KotlinClassMetadata; +import org.apache.commons.io.FileUtils; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.util.ASMifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * It's quite common for other plugins to shade these libraries, thus the wrong version is used. + * This file logs out the version + location of each library as a debugging aid. + * + *

gradlew buildEnvironment is a useful command to run alongside this. + */ +public final class LibraryLocationLogger { + private static final List> libraryClasses = List.of( + KotlinClassMetadata.class, + ClassVisitor.class, + Analyzer.class, + ClassRemapper.class, + ClassNode.class, + ASMifier.class, + ObjectMapper.class, + Gson.class, + Preconditions.class, + FileUtils.class + ); + + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryLocationLogger.class); + + public static void logLibraryVersions() { + for (Class clazz : libraryClasses) { + LOGGER.info("({}) with version ({}) was loaded from ({})", + clazz.getName(), + clazz.getPackage().getImplementationVersion(), + clazz.getProtectionDomain().getCodeSource().getLocation().getPath() + ); + } + } + + private LibraryLocationLogger() { + } +} diff --git a/src/main/java/net/fabricmc/loom/util/ModUtils.java b/src/main/java/net/fabricmc/loom/util/ModUtils.java index b98ca67c..4aebbb37 100644 --- a/src/main/java/net/fabricmc/loom/util/ModUtils.java +++ b/src/main/java/net/fabricmc/loom/util/ModUtils.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2021 FabricMC + * Copyright (c) 2016-2022 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 @@ -25,21 +25,50 @@ package net.fabricmc.loom.util; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import com.google.gson.JsonObject; import org.gradle.api.logging.Logger; +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.LoomGradlePlugin; public final class ModUtils { private ModUtils() { } - public static boolean isMod(File input, ModPlatform platform) { + public static boolean isMod(File file, ModPlatform platform) { + return isMod(file.toPath()); + } + + public static boolean isMod(Path input, ModPlatform platform) { if (platform == ModPlatform.FORGE) { return ZipUtils.contains(input.toPath(), "META-INF/mods.toml"); } else if (platform == ModPlatform.QUILT) { return ZipUtils.contains(input.toPath(), "quilt.mod.json"); } - return ZipUtils.contains(input.toPath(), "fabric.mod.json"); + return ZipUtils.contains(input, "fabric.mod.json"); + } + + @Nullable + public static JsonObject getFabricModJson(Path path) { + final byte[] modJsonBytes; + + try { + modJsonBytes = ZipUtils.unpackNullable(path, "fabric.mod.json"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to extract fabric.mod.json from " + path, e); + } + + if (modJsonBytes == null) { + return null; + } + + return LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class); } public static boolean shouldRemapMod(Logger logger, File input, Object id, ModPlatform platform, String config) { diff --git a/src/main/java/net/fabricmc/loom/util/SidedClassVisitor.java b/src/main/java/net/fabricmc/loom/util/SidedClassVisitor.java index d5e4e730..44c190c5 100644 --- a/src/main/java/net/fabricmc/loom/util/SidedClassVisitor.java +++ b/src/main/java/net/fabricmc/loom/util/SidedClassVisitor.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -41,6 +41,7 @@ public final class SidedClassVisitor extends ClassVisitor { private static final String SIDE_DESCRIPTOR = "Lnet/fabricmc/api/EnvType;"; private final String side; + private boolean hasExisting = false; private SidedClassVisitor(String side, ClassVisitor next) { super(Constants.ASM_VERSION, next); @@ -48,11 +49,22 @@ public final class SidedClassVisitor extends ClassVisitor { } @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, access, name, signature, superName, interfaces); + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + if (ENVIRONMENT_DESCRIPTOR.equals(descriptor)) { + hasExisting = true; + } - final AnnotationVisitor annotationVisitor = visitAnnotation(ENVIRONMENT_DESCRIPTOR, true); - annotationVisitor.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT)); - annotationVisitor.visitEnd(); + return super.visitAnnotation(descriptor, visible); + } + + @Override + public void visitEnd() { + if (!hasExisting) { + final AnnotationVisitor annotationVisitor = visitAnnotation(ENVIRONMENT_DESCRIPTOR, true); + annotationVisitor.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT)); + annotationVisitor.visitEnd(); + } + + super.visitEnd(); } } diff --git a/src/main/java/net/fabricmc/loom/util/ZipUtils.java b/src/main/java/net/fabricmc/loom/util/ZipUtils.java index f3a875f1..da6b1003 100644 --- a/src/main/java/net/fabricmc/loom/util/ZipUtils.java +++ b/src/main/java/net/fabricmc/loom/util/ZipUtils.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -44,6 +44,9 @@ import java.util.function.Function; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; import net.fabricmc.loom.LoomGradlePlugin; @@ -231,6 +234,20 @@ public class ZipUtils { T apply(T arg) throws IOException; } + public interface AsmClassOperator extends UnsafeUnaryOperator { + ClassVisitor visit(ClassVisitor classVisitor); + + @Override + default byte[] apply(byte[] arg) throws IOException { + final ClassReader reader = new ClassReader(arg); + final ClassWriter writer = new ClassWriter(0); + + reader.accept(visit(writer), 0); + + return writer.toByteArray(); + } + } + private static Map> collectTransformersStream(Stream>> transforms) { Map> map = new HashMap<>(); Iterator>> iterator = transforms.iterator(); diff --git a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java new file mode 100644 index 00000000..4950be4f --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java @@ -0,0 +1,172 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.util.gradle; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetOutput; +import org.intellij.lang.annotations.Language; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.VisibleForTesting; +import org.xml.sax.InputSource; + +import net.fabricmc.loom.api.ModSettings; +import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; + +public final class SourceSetHelper { + @VisibleForTesting + @Language("xpath") + public static final String IDEA_OUTPUT_XPATH = "/project/component[@name='ProjectRootManager']/output/@url"; + + private SourceSetHelper() { + } + + public static List getClasspath(ModSettings modSettings, Project project) { + final List files = new ArrayList<>(); + + files.addAll(modSettings.getModSourceSets().get().stream() + .flatMap(sourceSet -> getClasspath(sourceSet, project).stream()) + .toList()); + files.addAll(modSettings.getModFiles().getFiles()); + + return Collections.unmodifiableList(files); + } + + public static List getClasspath(SourceSet sourceSet, Project project) { + final List classpath = getGradleClasspath(sourceSet); + + classpath.addAll(getIdeaClasspath(sourceSet, project)); + classpath.addAll(getEclipseClasspath(sourceSet, project)); + classpath.addAll(getVscodeClasspath(sourceSet, project)); + + return classpath; + } + + private static List getGradleClasspath(SourceSet sourceSet) { + final SourceSetOutput output = sourceSet.getOutput(); + final File resources = output.getResourcesDir(); + + final List classpath = new ArrayList<>(); + + classpath.addAll(output.getClassesDirs().getFiles()); + + if (resources != null) { + classpath.add(resources); + } + + return classpath; + } + + @VisibleForTesting + public static List getIdeaClasspath(SourceSet sourceSet, Project project) { + final File projectDir = project.getRootDir(); + final File dotIdea = new File(projectDir, ".idea"); + + if (!dotIdea.exists()) { + return Collections.emptyList(); + } + + final File miscXml = new File(dotIdea, "misc.xml"); + + if (!miscXml.exists()) { + return Collections.emptyList(); + } + + String outputDirUrl = evaluateXpath(miscXml, IDEA_OUTPUT_XPATH); + + if (outputDirUrl == null) { + return Collections.emptyList(); + } + + outputDirUrl = outputDirUrl.replace("$PROJECT_DIR$", projectDir.getAbsolutePath()); + outputDirUrl = outputDirUrl.replaceAll("^file:", ""); + + final File productionDir = new File(outputDirUrl, "production"); + final File outputDir = new File(productionDir, IdeaUtils.getIdeaModuleName(project, sourceSet)); + + return Collections.singletonList(outputDir); + } + + @Nullable + private static String evaluateXpath(File file, @Language("xpath") String expression) { + final XPath xpath = XPathFactory.newInstance().newXPath(); + + try (FileInputStream fis = new FileInputStream(file)) { + return xpath.evaluate(expression, new InputSource(fis)); + } catch (XPathExpressionException e) { + return null; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @VisibleForTesting + public static List getEclipseClasspath(SourceSet sourceSet, Project project) { + // Somewhat of a guess, I'm unsure if this is correct for multi-project builds + final File projectDir = project.getProjectDir(); + final File classpath = new File(projectDir, ".classpath"); + + if (!classpath.exists()) { + return Collections.emptyList(); + } + + return getBinDirClasspath(projectDir, sourceSet); + } + + @VisibleForTesting + public static List getVscodeClasspath(SourceSet sourceSet, Project project) { + // Somewhat of a guess, I'm unsure if this is correct for multi-project builds + final File projectDir = project.getProjectDir(); + final File dotVscode = new File(projectDir, ".vscode"); + + if (!dotVscode.exists()) { + return Collections.emptyList(); + } + + return getBinDirClasspath(projectDir, sourceSet); + } + + private static List getBinDirClasspath(File projectDir, SourceSet sourceSet) { + final File binDir = new File(projectDir, "bin"); + + if (!binDir.exists()) { + return Collections.emptyList(); + } + + return Collections.singletonList(new File(binDir, sourceSet.getName())); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/AssetObject.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspath.java similarity index 85% rename from src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/AssetObject.java rename to src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspath.java index b7e63b12..f7461f08 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/AssetObject.java +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspath.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2021 FabricMC + * Copyright (c) 2022 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,8 +22,13 @@ * SOFTWARE. */ -package net.fabricmc.loom.configuration.providers.minecraft.assets; +package net.fabricmc.loom.util.kotlin; -@SuppressWarnings("unused") -public record AssetObject(String hash, long size) { +import java.net.URL; +import java.util.Set; + +public interface KotlinClasspath { + String version(); + + Set classpath(); } diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java new file mode 100644 index 00000000..05b9cada --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java @@ -0,0 +1,75 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.util.kotlin; + +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Set; +import java.util.stream.Collectors; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.util.service.SharedService; +import net.fabricmc.loom.util.service.SharedServiceManager; + +public record KotlinClasspathService(Set classpath, String version) implements KotlinClasspath, SharedService { + @Nullable + public static KotlinClasspathService getOrCreateIfRequired(Project project) { + if (!KotlinPluginUtils.hasKotlinPlugin(project)) { + return null; + } + + return getOrCreate(project, KotlinPluginUtils.getKotlinPluginVersion(project), KotlinPluginUtils.getKotlinMetadataVersion()); + } + + public static synchronized KotlinClasspathService getOrCreate(Project project, String kotlinVersion, String kotlinMetadataVersion) { + final String id = "kotlinclasspath:%s:%s".formatted(kotlinVersion, kotlinMetadataVersion); + final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project); + return sharedServiceManager.getOrCreateService(id, () -> create(project, kotlinVersion, kotlinMetadataVersion)); + } + + private static KotlinClasspathService create(Project project, String kotlinVersion, String kotlinMetadataVersion) { + // Create a detached config to resolve the kotlin std lib for the provided version. + Configuration detachedConfiguration = project.getConfigurations().detachedConfiguration( + project.getDependencies().create("org.jetbrains.kotlin:kotlin-stdlib:" + kotlinVersion), + // Load kotlinx-metadata-jvm like this to work around: https://github.com/gradle/gradle/issues/14727 + project.getDependencies().create("org.jetbrains.kotlinx:kotlinx-metadata-jvm:" + kotlinMetadataVersion) + ); + + Set classpath = detachedConfiguration.getFiles().stream() + .map(file -> { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new UncheckedIOException(e); + } + }).collect(Collectors.toSet());; + + return new KotlinClasspathService(classpath, kotlinVersion); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinMetadataTinyRemapperExtension.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinMetadataTinyRemapperExtension.java new file mode 100644 index 00000000..1e13a350 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinMetadataTinyRemapperExtension.java @@ -0,0 +1,30 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.util.kotlin; + +import net.fabricmc.tinyremapper.TinyRemapper; + +public interface KotlinMetadataTinyRemapperExtension extends TinyRemapper.ApplyVisitorProvider, TinyRemapper.Extension { +} diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java new file mode 100644 index 00000000..b0e7aaf8 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java @@ -0,0 +1,45 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.util.kotlin; + +import kotlinx.metadata.jvm.KotlinClassMetadata; +import org.gradle.api.Project; + +public class KotlinPluginUtils { + private static final String KOTLIN_PLUGIN_ID = "org.jetbrains.kotlin.jvm"; + + public static boolean hasKotlinPlugin(Project project) { + return project.getPluginManager().hasPlugin(KOTLIN_PLUGIN_ID); + } + + public static String getKotlinPluginVersion(Project project) { + Class koltinPluginClass = project.getPlugins().getPlugin(KOTLIN_PLUGIN_ID).getClass(); + return koltinPluginClass.getPackage().getImplementationVersion().split("-")[0]; + } + + public static String getKotlinMetadataVersion() { + return KotlinClassMetadata.class.getPackage().getImplementationVersion(); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinRemapperClassloader.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinRemapperClassloader.java new file mode 100644 index 00000000..fb3ee13b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinRemapperClassloader.java @@ -0,0 +1,90 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.util.kotlin; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtensionImpl; + +/** + * Used to run the Kotlin remapper with a specific version of Koltin that may not match the kotlin version included with gradle. + */ +public class KotlinRemapperClassloader extends URLClassLoader { + // Packages that should be loaded from the gradle plugin classloader. + private static final List PARENT_PACKAGES = List.of( + "net.fabricmc.tinyremapper", + "net.fabricmc.loom.util.kotlin", + "org.objectweb.asm", + "org.slf4j" + ); + + private KotlinRemapperClassloader(URL[] urls) { + super(urls, null); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (PARENT_PACKAGES.stream().anyMatch(name::startsWith)) { + return LoomGradlePlugin.class.getClassLoader().loadClass(name); + } + + return super.loadClass(name, resolve); + } + + public static KotlinRemapperClassloader create(KotlinClasspath classpathProvider) { + // Include the libraries that are not on the kotlin classpath. + final Stream loomUrls = getClassUrls( + KotlinMetadataTinyRemapperExtensionImpl.class // Loom + ); + + final URL[] urls = Stream.concat( + loomUrls, + classpathProvider.classpath().stream() + ).toArray(URL[]::new); + + return new KotlinRemapperClassloader(urls); + } + + private static Stream getClassUrls(Class... classes) { + return Arrays.stream(classes).map(klass -> klass.getProtectionDomain().getCodeSource().getLocation()); + } + + /** + * Load the {@link KotlinMetadataTinyRemapperExtensionImpl} class on the new classloader. + */ + public KotlinMetadataTinyRemapperExtension getTinyRemapperExtension() { + try { + Class klass = this.loadClass(KotlinMetadataTinyRemapperExtensionImpl.class.getCanonicalName()); + return (KotlinMetadataTinyRemapperExtension) klass.getField("INSTANCE").get(null); + } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException("Failed to create instance", e); + } + } +} 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 f847fc19..57fca33b 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt @@ -30,10 +30,13 @@ import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.Opcodes import org.objectweb.asm.commons.Remapper import org.objectweb.asm.tree.AnnotationNode +import org.slf4j.LoggerFactory -class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapper, val next: AnnotationVisitor) : +class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapper, val next: AnnotationVisitor, val className: String?) : AnnotationNode(Opcodes.ASM9, KotlinMetadataRemappingClassVisitor.ANNOTATION_DESCRIPTOR) { + private val logger = LoggerFactory.getLogger(javaClass) + private var _name: String? = null override fun visit(name: String?, value: Any?) { @@ -43,12 +46,24 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp override fun visitEnd() { super.visitEnd() - when (val metadata = readMetadata()) { + + val header = readHeader() ?: return + + val headerVersion = KotlinVersion(header.metadataVersion[0], header.metadataVersion[1], 0) + val currentMinorVersion = KotlinVersion(KotlinVersion.CURRENT.major, KotlinVersion.CURRENT.minor, 0) + + if (headerVersion != currentMinorVersion) { + logger.info("Kotlin metadata for class ($className) as it was built using a different major Kotlin version (${header.metadataVersion[0]}.${header.metadataVersion[1]}.x) while the remapper is using (${KotlinVersion.CURRENT}).") + } + + when (val metadata = KotlinClassMetadata.read(header)) { is KotlinClassMetadata.Class -> { val klass = metadata.toKmClass() val writer = KotlinClassMetadata.Class.Writer() klass.accept(RemappingKmVisitors(remapper).RemappingKmClassVisitor(writer)) - writeClassHeader(writer.write().header) + val remapped = writer.write(header.metadataVersion).header + writeClassHeader(remapped) + validateKotlinClassHeader(remapped, header) } is KotlinClassMetadata.SyntheticClass -> { val klambda = metadata.toKmLambda() @@ -56,14 +71,29 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp if (klambda != null) { val writer = KotlinClassMetadata.SyntheticClass.Writer() klambda.accept(RemappingKmVisitors(remapper).RemappingKmLambdaVisitor(writer)) - writeClassHeader(writer.write().header) + val remapped = writer.write(header.metadataVersion).header + writeClassHeader(remapped) + validateKotlinClassHeader(remapped, header) } else { accept(next) } } - // Can only be turned into KmPackage which is useless data - is KotlinClassMetadata.FileFacade, is KotlinClassMetadata.MultiFileClassPart, - // Can't be turned into data + is KotlinClassMetadata.FileFacade -> { + val kpackage = metadata.toKmPackage() + val writer = KotlinClassMetadata.FileFacade.Writer() + kpackage.accept(RemappingKmVisitors(remapper).RemappingKmPackageVisitor(writer)) + val remapped = writer.write(header.metadataVersion).header + writeClassHeader(remapped) + validateKotlinClassHeader(remapped, header) + } + is KotlinClassMetadata.MultiFileClassPart -> { + val kpackage = metadata.toKmPackage() + val writer = KotlinClassMetadata.MultiFileClassPart.Writer() + kpackage.accept(RemappingKmVisitors(remapper).RemappingKmPackageVisitor(writer)) + val remapped = writer.write(metadata.facadeClassName, metadata.header.metadataVersion, metadata.header.extraInt).header + writeClassHeader(remapped) + validateKotlinClassHeader(remapped, header) + } is KotlinClassMetadata.MultiFileClassFacade, is KotlinClassMetadata.Unknown, null -> { // do nothing accept(next) @@ -72,7 +102,7 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp } @Suppress("UNCHECKED_CAST") - private fun readMetadata(): KotlinClassMetadata? { + private fun readHeader(): KotlinClassHeader? { var kind: Int? = null var metadataVersion: IntArray? = null var data1: Array? = null @@ -97,8 +127,7 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp } } - val header = KotlinClassHeader(kind, metadataVersion, data1, data2, extraString, packageName, extraInt) - return KotlinClassMetadata.read(header) + return KotlinClassHeader(kind, metadataVersion, data1, data2, extraString, packageName, extraInt) } private fun writeClassHeader(header: KotlinClassHeader) { @@ -121,4 +150,11 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp newNode.accept(next) } + + private fun validateKotlinClassHeader(remapped: KotlinClassHeader, original: KotlinClassHeader) { + // This can happen when the remapper is ran on a kotlin version that does not match the version the class was compiled with. + if (remapped.data2.size != original.data2.size) { + logger.info("Kotlin class metadata size mismatch: data2 size does not match original in class $className. New: ${remapped.data2.size} Old: ${original.data2.size}") + } + } } diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt index f6716a89..e22b05fb 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt @@ -24,6 +24,7 @@ package net.fabricmc.loom.kotlin.remapping +import org.jetbrains.annotations.VisibleForTesting import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.ClassVisitor import org.objectweb.asm.Opcodes @@ -35,13 +36,32 @@ class KotlinMetadataRemappingClassVisitor(private val remapper: Remapper, next: val ANNOTATION_DESCRIPTOR: String = Type.getDescriptor(Metadata::class.java) } + var className: String? = null + + override fun visit( + version: Int, + access: Int, + name: String?, + signature: String?, + superName: String?, + interfaces: Array? + ) { + this.className = name + super.visit(version, access, name, signature, superName, interfaces) + } + override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { var result: AnnotationVisitor? = super.visitAnnotation(descriptor, visible) if (descriptor == ANNOTATION_DESCRIPTOR && result != null) { - result = KotlinClassMetadataRemappingAnnotationVisitor(remapper, result) + result = KotlinClassMetadataRemappingAnnotationVisitor(remapper, result, className) } return result } + + @VisibleForTesting + fun getRuntimeKotlinVersion(): String { + return KotlinVersion.CURRENT.toString() + } } diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtension.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtensionImpl.kt similarity index 89% rename from src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtension.kt rename to src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtensionImpl.kt index ca43eea6..5ab3c6e7 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtension.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtensionImpl.kt @@ -26,10 +26,11 @@ package net.fabricmc.loom.kotlin.remapping import dev.architectury.tinyremapper.TinyRemapper import dev.architectury.tinyremapper.api.TrClass +import net.fabricmc.loom.util.kotlin.KotlinMetadataTinyRemapperExtension import org.objectweb.asm.ClassVisitor -object KotlinMetadataTinyRemapperExtension : TinyRemapper.ApplyVisitorProvider, TinyRemapper.Extension { - override fun insertApplyVisitor(cls: TrClass, next: ClassVisitor): ClassVisitor { +object KotlinMetadataTinyRemapperExtensionImpl : KotlinMetadataTinyRemapperExtension { + override fun insertApplyVisitor(cls: TrClass, next: ClassVisitor?): ClassVisitor { return KotlinMetadataRemappingClassVisitor(cls.environment.remapper, next) } diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/RemappingKmVisitors.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/RemappingKmVisitors.kt index 3acbd24a..7cb9ed04 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/RemappingKmVisitors.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/RemappingKmVisitors.kt @@ -40,6 +40,8 @@ import kotlinx.metadata.KmExtensionType import kotlinx.metadata.KmFunctionExtensionVisitor import kotlinx.metadata.KmFunctionVisitor import kotlinx.metadata.KmLambdaVisitor +import kotlinx.metadata.KmPackageExtensionVisitor +import kotlinx.metadata.KmPackageVisitor import kotlinx.metadata.KmPropertyExtensionVisitor import kotlinx.metadata.KmPropertyVisitor import kotlinx.metadata.KmTypeAliasVisitor @@ -54,6 +56,7 @@ import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor import kotlinx.metadata.jvm.JvmFieldSignature import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor import kotlinx.metadata.jvm.JvmMethodSignature +import kotlinx.metadata.jvm.JvmPackageExtensionVisitor import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor import kotlinx.metadata.jvm.JvmTypeExtensionVisitor import kotlinx.metadata.jvm.JvmTypeParameterExtensionVisitor @@ -189,6 +192,15 @@ class RemappingKmVisitors(private val remapper: Remapper) { override fun visitContract(): KmContractVisitor { return RemappingKmContractVisitor(super.visitContract()) } + + override fun visitTypeParameter( + flags: Flags, + name: String, + id: Int, + variance: KmVariance + ): KmTypeParameterVisitor { + return RemappingKmTypeParameterVisitor(super.visitTypeParameter(flags, name, id, variance)) + } } inner class RemappingKmContractVisitor(delegate: KmContractVisitor?) : KmContractVisitor(delegate) { @@ -356,4 +368,38 @@ class RemappingKmVisitors(private val remapper: Remapper) { super.visit(remapJvmMethodSignature(signature)) } } + + inner class RemappingKmPackageVisitor(delegate: KmPackageVisitor?) : KmPackageVisitor(delegate) { + override fun visitExtensions(type: KmExtensionType): KmPackageExtensionVisitor { + return RemappingJvmPackageExtensionVisitor(super.visitExtensions(type) as JvmPackageExtensionVisitor?) + } + + override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor { + return RemappingKmFunctionVisitor(super.visitFunction(flags, name)) + } + + override fun visitProperty( + flags: Flags, + name: String, + getterFlags: Flags, + setterFlags: Flags + ): KmPropertyVisitor { + return RemappingKmPropertyVisitor(super.visitProperty(flags, name, getterFlags, setterFlags)) + } + + override fun visitTypeAlias(flags: Flags, name: String): KmTypeAliasVisitor { + return RemappingKmTypeAliasVisitor(super.visitTypeAlias(flags, name)) + } + } + + inner class RemappingJvmPackageExtensionVisitor(delegate: JvmPackageExtensionVisitor?) : JvmPackageExtensionVisitor(delegate) { + override fun visitLocalDelegatedProperty( + flags: Flags, + name: String, + getterFlags: Flags, + setterFlags: Flags + ): KmPropertyVisitor { + return RemappingKmPropertyVisitor(super.visitLocalDelegatedProperty(flags, name, getterFlags, setterFlags)) + } + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy index 6f852317..45642ec9 100644 --- a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy @@ -28,7 +28,7 @@ import org.gradle.util.GradleVersion class LoomTestConstants { public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion() - public final static String PRE_RELEASE_GRADLE = "7.5-20220129032445+0000" + public final static String PRE_RELEASE_GRADLE = "7.5-20220415181004+0000" public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE] } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/KotlinTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/KotlinTest.groovy index afe06b03..abf67bb3 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/KotlinTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/KotlinTest.groovy @@ -39,7 +39,7 @@ class KotlinTest extends Specification implements GradleProjectTestTrait { def gradle = gradleProject(project: "kotlin", version: version) def server = ServerRunner.create(gradle.projectDir, "1.16.5") .withMod(gradle.getOutputFile("fabric-example-mod-0.0.1.jar")) - .downloadMod(ServerRunner.FABRIC_LANG_KOTLIN, "fabric-language-kotlin-1.7.1+kotlin.1.6.10.jar") + .downloadMod(ServerRunner.FABRIC_LANG_KOTLIN, "fabric-language-kotlin-1.7.3+kotlin.1.6.20.jar") when: def result = gradle.run(task: "build") diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/LegacyProjectTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/LegacyProjectTest.groovy index 75259a17..7461566c 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/LegacyProjectTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/LegacyProjectTest.groovy @@ -32,7 +32,6 @@ import static net.fabricmc.loom.test.LoomTestConstants.PRE_RELEASE_GRADLE import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS import static org.gradle.testkit.runner.TaskOutcome.SUCCESS -// This test uses gradle 4.9 and 1.14.4 v1 mappings class LegacyProjectTest extends Specification implements GradleProjectTestTrait { @Unroll def "legacy build (gradle #version)"() { @@ -55,7 +54,7 @@ class LegacyProjectTest extends Specification implements GradleProjectTestTrait def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE) gradle.buildGradle << """ loom { - intermediaryUrl = 'https://s.modm.us/intermediary-empty-v2.jar' + noIntermediateMappings() } dependencies { @@ -80,8 +79,42 @@ class LegacyProjectTest extends Specification implements GradleProjectTestTrait '1.12.2' | _ '1.8.9' | _ '1.7.10' | _ + '1.7' | _ '1.6.4' | _ '1.4.7' | _ '1.3.2' | _ } + + @Unroll + def "Ancient minecraft (minecraft #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE) + gradle.buildGradle << """ + loom { + noIntermediateMappings() + clientOnlyMinecraftJar() + } + + dependencies { + minecraft "com.mojang:minecraft:${version}" + mappings loom.layered() { + // No names + } + + modImplementation "net.fabricmc:fabric-loader:0.12.12" + } + """ + + when: + def result = gradle.run(task: "configureClientLaunch") + + then: + result.task(":configureClientLaunch").outcome == SUCCESS + + where: + version | _ + '1.2.5' | _ + 'b1.8.1' | _ + 'a1.2.5' | _ + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/MCJarConfigTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/MCJarConfigTest.groovy index 5df77f3e..d58d0741 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/MCJarConfigTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/MCJarConfigTest.groovy @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -59,6 +59,33 @@ class MCJarConfigTest extends Specification implements GradleProjectTestTrait { version << STANDARD_TEST_VERSIONS } + @Unroll + def "client only (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + + gradle.buildGradle << ''' + loom { + clientOnlyMinecraftJar() + } + + dependencies { + minecraft "com.mojang:minecraft:1.18.1" + mappings "net.fabricmc:yarn:1.18.1+build.18:v2" + modImplementation "net.fabricmc:fabric-loader:0.12.12" + } + ''' + + when: + def result = gradle.run(task: "build") + + then: + result.task(":build").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } + @Unroll def "split (gradle #version)"() { setup: @@ -85,4 +112,31 @@ class MCJarConfigTest extends Specification implements GradleProjectTestTrait { where: version << STANDARD_TEST_VERSIONS } + + @Unroll + def "split env (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + + gradle.buildGradle << ''' + loom { + splitEnvironmentSourceSets() + } + + dependencies { + minecraft "com.mojang:minecraft:1.18.1" + mappings "net.fabricmc:yarn:1.18.1+build.18:v2" + modImplementation "net.fabricmc:fabric-loader:0.12.12" + } + ''' + + when: + def result = gradle.run(task: "build") + + then: + result.task(":build").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/ModJavadocTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/ModJavadocTest.groovy new file mode 100644 index 00000000..51397634 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/ModJavadocTest.groovy @@ -0,0 +1,62 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.integration + +import net.fabricmc.loom.test.util.GradleProjectTestTrait +import net.fabricmc.loom.util.ZipUtils +import spock.lang.Specification +import spock.lang.Unroll + +import java.nio.charset.StandardCharsets + +import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class ModJavadocTest extends Specification implements GradleProjectTestTrait { + @Unroll + def "mod javadoc (gradle #version)"() { + setup: + def gradle = gradleProject(project: "modJavadoc", version: version) + ZipUtils.pack(new File(gradle.projectDir, "dummyDependency").toPath(), new File(gradle.projectDir, "dummy.jar").toPath()) + + when: + def result = gradle.run(task: "genSources") + def blocks = getClassSource(gradle, "net/minecraft/block/Blocks.java") + + then: + result.task(":genSources").outcome == SUCCESS + blocks.contains("An example of a mod added class javadoc") + blocks.contains("An example of a mod added field javadoc") + blocks.contains("An example of a mod added method javadoc") + + where: + version << STANDARD_TEST_VERSIONS + } + + private static String getClassSource(GradleProject gradle, String classname) { + File sourcesJar = gradle.getGeneratedLocalSources("1.17.1/net.fabricmc.yarn.1_17_1.1.17.1+build.59-v2") + return new String(ZipUtils.unpack(sourcesJar.toPath(), classname), StandardCharsets.UTF_8) + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/MojangMappingsProjectTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/MojangMappingsProjectTest.groovy index 6afba527..2e1db92c 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/MojangMappingsProjectTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/MojangMappingsProjectTest.groovy @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2018-2021 FabricMC + * Copyright (c) 2018-2022 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 @@ -74,4 +74,29 @@ class MojangMappingsProjectTest extends Specification implements GradleProjectTe where: version << STANDARD_TEST_VERSIONS } + + @Unroll + def "fail with wrong officialMojangMappings usage (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + + gradle.buildGradle << ''' + dependencies { + minecraft "com.mojang:minecraft:1.18.2" + mappings loom.layered { + // This is the wrong method to call! + loom.officialMojangMappings() + } + } + ''' + + when: + def result = gradle.run(task: "build", expectFailure: true) + + then: + result.output.contains("Use `officialMojangMappings()` when configuring layered mappings, not the extension method `loom.officialMojangMappings()`") + + where: + version << STANDARD_TEST_VERSIONS + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/SplitProjectTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/SplitProjectTest.groovy new file mode 100644 index 00000000..b7440833 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/SplitProjectTest.groovy @@ -0,0 +1,49 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.integration + +import net.fabricmc.loom.test.util.GradleProjectTestTrait +import spock.lang.Specification +import spock.lang.Unroll + +import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class SplitProjectTest extends Specification implements GradleProjectTestTrait { + @Unroll + def "build (gradle #version)"() { + setup: + def gradle = gradleProject(project: "splitSources", version: version) + + when: + def result = gradle.run(task: "build") + + then: + result.task(":build").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy new file mode 100644 index 00000000..1586abdb --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy @@ -0,0 +1,45 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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 net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider +import net.fabricmc.loom.test.util.GradleTestUtil + +import static org.mockito.Mockito.spy +import static org.mockito.Mockito.when + +class LoomMocks { + static IntermediaryMappingsProvider intermediaryMappingsProviderMock(String minecraftVersion, String intermediaryUrl) { + def minecraftVersionProperty = GradleTestUtil.mockProperty(minecraftVersion) + def intermediaryUrlProperty = GradleTestUtil.mockProperty(intermediaryUrl) + + Objects.requireNonNull(minecraftVersionProperty.get()) + + def mock = spy(IntermediaryMappingsProvider.class) + when(mock.getMinecraftVersion()).thenReturn(minecraftVersionProperty) + when(mock.getIntermediaryUrl()).thenReturn(intermediaryUrlProperty) + return mock + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/SourceSetHelperTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/SourceSetHelperTest.groovy new file mode 100644 index 00000000..22499db5 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/SourceSetHelperTest.groovy @@ -0,0 +1,115 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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 net.fabricmc.loom.util.gradle.SourceSetHelper +import org.gradle.api.Project +import org.gradle.api.tasks.SourceSet +import org.intellij.lang.annotations.Language +import spock.lang.Shared +import spock.lang.Specification + +class SourceSetHelperTest extends Specification { + @Shared + private static File projectDir = File.createTempDir() + + def "idea classpath"() { + given: + def miscXml = new File(projectDir, ".idea/misc.xml") + miscXml.parentFile.mkdirs() + miscXml.text = MISC_XML + + def mockProject = Mock(Project) + def mockSourceSet = Mock(SourceSet) + + mockProject.getName() >> "UnitTest" + mockProject.getRootDir() >> projectDir + mockSourceSet.getName() >> "main" + + when: + def result = SourceSetHelper.getIdeaClasspath(mockSourceSet, mockProject) + + then: + result.size() == 1 + !result[0].toString().startsWith("file:") + + println(result[0].toString()) + } + + def "eclipse classpath"() { + given: + def classpath = new File(projectDir, ".classpath") + classpath.createNewFile() + + def binDir = new File(projectDir, "bin") + binDir.mkdirs() + + def mockProject = Mock(Project) + def mockSourceSet = Mock(SourceSet) + + mockProject.getName() >> "UnitTest" + mockProject.getProjectDir() >> projectDir + mockSourceSet.getName() >> "main" + + when: + def result = SourceSetHelper.getEclipseClasspath(mockSourceSet, mockProject) + + then: + result.size() == 1 + println(result[0].toString()) + } + + def "vscode classpath"() { + given: + def dotVscode = new File(projectDir, ".vscode") + dotVscode.mkdirs() + + def binDir = new File(projectDir, "bin") + binDir.mkdirs() + + def mockProject = Mock(Project) + def mockSourceSet = Mock(SourceSet) + + mockProject.getName() >> "UnitTest" + mockProject.getProjectDir() >> projectDir + mockSourceSet.getName() >> "main" + + when: + def result = SourceSetHelper.getVscodeClasspath(mockSourceSet, mockProject) + + then: + result.size() == 1 + println(result[0].toString()) + } + + @Language("xml") + private static String MISC_XML = """ + + + + + +""" +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy new file mode 100644 index 00000000..fc7df3fc --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy @@ -0,0 +1,101 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.kotlin + +import net.fabricmc.loom.util.kotlin.KotlinClasspath +import net.fabricmc.loom.util.kotlin.KotlinPluginUtils +import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader +import net.fabricmc.tinyremapper.api.TrClass +import net.fabricmc.tinyremapper.api.TrEnvironment +import net.fabricmc.tinyremapper.api.TrRemapper +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode +import spock.lang.Specification + +class KotlinRemapperClassloaderTest extends Specification { + private static String KOTLIN_VERSION = "1.6.10" + private static String KOTLIN_METADATA_VERSION = KotlinPluginUtils.kotlinMetadataVersion + private static String KOTLIN_URL = "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/${KOTLIN_VERSION}/kotlin-stdlib-${KOTLIN_VERSION}.jar" + private static String KOTLIN_METADATA_URL = "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-metadata-jvm/${KOTLIN_METADATA_VERSION}/kotlinx-metadata-jvm-${KOTLIN_METADATA_VERSION}.jar" + + def "Test Koltin Remapper Classloader"() { + given: + def classLoader = KotlinRemapperClassloader.create(new TestKotlinClasspath()) + def mockTrClass = Mock(TrClass) + def mockEnv = Mock(TrEnvironment) + def mockRemapper = Mock(TrRemapper) + + mockRemapper.map(_) >> { args -> args[0] } + mockRemapper.mapMethodDesc(_) >> { args -> args[0] } + + mockEnv.remapper >> mockRemapper + mockTrClass.environment >> mockEnv + + def classReader = new ClassReader(getClassBytes("TestExtensionKt")) + + when: + def extension = classLoader.tinyRemapperExtension + def visitor = extension.insertApplyVisitor(mockTrClass, new ClassNode()) + + classReader.accept(visitor, 0) + + then: + extension != null + visitor != null + + // Ensure that the visitor is using the kotlin version specified on the classpath. + visitor.runtimeKotlinVersion == KOTLIN_VERSION + } + + private class TestKotlinClasspath implements KotlinClasspath { + @Override + String version() { + return KOTLIN_VERSION + } + + @Override + Set classpath() { + def kotlin = downloadFile(KOTLIN_URL, "kotlin-stdlib.jar") + def metadata = downloadFile(KOTLIN_METADATA_URL, "kotlin-metadata.jar") + + return Set.of( + kotlin.toURI().toURL(), + metadata.toURI().toURL() + ) + } + } + + File tempDir = File.createTempDir() + File downloadFile(String url, String name) { + File dst = new File(tempDir, name) + dst.parentFile.mkdirs() + dst << new URL(url).newInputStream() + return dst + } + + def getClassBytes(String name) { + return new File("src/test/resources/classes/${name}.class").bytes + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsSpecification.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsSpecification.groovy index 13a94047..7cd139ff 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsSpecification.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsSpecification.groovy @@ -28,11 +28,12 @@ import net.fabricmc.loom.api.mappings.layered.MappingContext import net.fabricmc.loom.api.mappings.layered.MappingLayer import net.fabricmc.loom.api.mappings.layered.MappingsNamespace import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec -import net.fabricmc.loom.configuration.providers.mappings.IntermediaryService +import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsProcessor import net.fabricmc.loom.configuration.providers.mappings.extras.unpick.UnpickLayer import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider +import net.fabricmc.loom.test.unit.LoomMocks import net.fabricmc.mappingio.adapter.MappingDstNsReorder import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch import net.fabricmc.mappingio.format.Tiny2Writer @@ -129,7 +130,7 @@ abstract class LayeredMappingsSpecification extends Specification implements Lay @Override Supplier intermediaryTree() { return { - IntermediaryService.create(intermediaryUrl, minecraftProvider()).memoryMappingTree + IntermediateMappingsService.create(LoomMocks.intermediaryMappingsProviderMock("test", intermediaryUrl), minecraftProvider()).memoryMappingTree } } diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy index d47ffda7..c5893a12 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -234,6 +234,10 @@ trait GradleProjectTestTrait { return new File(getGradleHomeDir(), "caches/fabric-loom/${mappings}/minecraft-merged-named-sources.jar") } + File getGeneratedLocalSources(String mappings) { + return new File(getProjectDir(), ".gradle/loom-cache/${mappings}/minecraft-project-@-merged-named-sources.jar") + } + void buildSrc(String name) { useBuildSrc = true diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy new file mode 100644 index 00000000..e19660c1 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy @@ -0,0 +1,38 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.util + +import org.gradle.api.provider.Property + +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.when + +class GradleTestUtil { + static Property mockProperty(T value) { + def mock = mock(Property.class) + when(mock.get()).thenReturn(Objects.requireNonNull(value)) + return mock + } +} 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 0a95fd7d..351a6869 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy @@ -34,7 +34,7 @@ class ServerRunner { "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" ] - static final String FABRIC_LANG_KOTLIN = "https://maven.fabricmc.net/net/fabricmc/fabric-language-kotlin/1.7.1%2Bkotlin.1.6.10/fabric-language-kotlin-1.7.1%2Bkotlin.1.6.10.jar" + static final String FABRIC_LANG_KOTLIN = "https://maven.fabricmc.net/net/fabricmc/fabric-language-kotlin/1.7.3%2Bkotlin.1.6.20/fabric-language-kotlin-1.7.3%2Bkotlin.1.6.20.jar" final File serverDir final String minecraftVersion diff --git a/src/test/kotlin/net/fabricmc/loom/test/kotlin/KotlinClassMetadataRemappingAnnotationVisitorTest.kt b/src/test/kotlin/net/fabricmc/loom/test/kotlin/KotlinClassMetadataRemappingAnnotationVisitorTest.kt index 6b3848f4..cac05210 100644 --- a/src/test/kotlin/net/fabricmc/loom/test/kotlin/KotlinClassMetadataRemappingAnnotationVisitorTest.kt +++ b/src/test/kotlin/net/fabricmc/loom/test/kotlin/KotlinClassMetadataRemappingAnnotationVisitorTest.kt @@ -26,19 +26,21 @@ package net.fabricmc.loom.test.kotlin import dev.architectury.tinyremapper.IMappingProvider import dev.architectury.tinyremapper.TinyRemapper -import java.io.File -import java.io.PrintWriter -import java.io.StringWriter -import java.nio.file.Paths import net.fabricmc.loom.kotlin.remapping.KotlinMetadataRemappingClassVisitor import net.fabricmc.loom.util.TinyRemapperHelper import net.fabricmc.mappingio.MappingReader import net.fabricmc.mappingio.tree.MemoryMappingTree +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor import org.objectweb.asm.Opcodes import org.objectweb.asm.util.Textifier import org.objectweb.asm.util.TraceClassVisitor +import java.io.File +import java.io.PrintWriter +import java.io.StringWriter +import java.nio.file.Paths // See: https://github.com/JetBrains/kotlin/blob/master/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt#L67 class KotlinClassMetadataRemappingAnnotationVisitorTest { @@ -58,14 +60,43 @@ class KotlinClassMetadataRemappingAnnotationVisitorTest { .withMappings(readMappings("PosInChunk")) .build() - val stringWriter = StringWriter() - val traceClassVisitor = TraceClassVisitor(null, TextifierImpl(), PrintWriter(stringWriter)) + val inputWriter = StringWriter() + classReader.accept(stringWriterVisitor(inputWriter), 0) - classReader.accept(KotlinMetadataRemappingClassVisitor(tinyRemapper.environment.remapper, traceClassVisitor), 0) + val remappedWriter = StringWriter() + classReader.accept(KotlinMetadataRemappingClassVisitor(tinyRemapper.environment.remapper, stringWriterVisitor(remappedWriter)), 0) - val d2Regex = Regex("(d2=)(.*)") - val asm = stringWriter.toString() - println(d2Regex.find(asm)?.value) + val d2In = d2(inputWriter.toString()) + val d2Out = d2(remappedWriter.toString()) + + println(d2In) + println(d2Out) + + assertEquals(d2In.size, d2Out.size) + } + + @Test + fun extensionTest() { + val input = getClassBytes("TestExtensionKt") + val classReader = ClassReader(input) + + val tinyRemapper = TinyRemapper.newRemapper() + .withMappings(readMappings("TestExtensionKt")) + .build() + + val inputWriter = StringWriter() + classReader.accept(stringWriterVisitor(inputWriter), 0) + + val remappedWriter = StringWriter() + classReader.accept(KotlinMetadataRemappingClassVisitor(tinyRemapper.environment.remapper, stringWriterVisitor(remappedWriter)), 0) + + val d2In = d2(inputWriter.toString()) + val d2Out = d2(remappedWriter.toString()) + + println(d2In) + println(d2Out) + + assertEquals(d2In.size, d2Out.size) } private fun getClassBytes(name: String): ByteArray { @@ -78,5 +109,14 @@ class KotlinClassMetadataRemappingAnnotationVisitorTest { return TinyRemapperHelper.create(mappingTree, "named", "intermediary", false) } + private fun stringWriterVisitor(writer: StringWriter): ClassVisitor { + return TraceClassVisitor(null, TextifierImpl(), PrintWriter(writer)) + } + + private fun d2(bytecode: String): List { + val d2Regex = Regex("d2=\\{(.*)}") + return d2Regex.find(bytecode)!!.groupValues[1].split(",") + } + private class TextifierImpl : Textifier(Opcodes.ASM9) } diff --git a/src/test/resources/classes/TestExtensionKt.class b/src/test/resources/classes/TestExtensionKt.class new file mode 100644 index 00000000..8ed6cd7e Binary files /dev/null and b/src/test/resources/classes/TestExtensionKt.class differ diff --git a/src/test/resources/mappings/TestExtensionKt.mappings b/src/test/resources/mappings/TestExtensionKt.mappings new file mode 100644 index 00000000..ca345906 --- /dev/null +++ b/src/test/resources/mappings/TestExtensionKt.mappings @@ -0,0 +1,2 @@ +tiny 2 0 intermediary named +c net/minecraft/class_1297 net/minecraft/entity/Entity \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/build.gradle.kts b/src/test/resources/projects/kotlin/build.gradle.kts index fca3185a..a2ef63b8 100644 --- a/src/test/resources/projects/kotlin/build.gradle.kts +++ b/src/test/resources/projects/kotlin/build.gradle.kts @@ -1,8 +1,8 @@ import java.util.Properties plugins { - kotlin("jvm") version "1.6.10" - kotlin("plugin.serialization") version "1.6.10" + kotlin("jvm") version "1.6.20" + kotlin("plugin.serialization") version "1.6.20" id("dev.architectury.loom") } @@ -17,5 +17,5 @@ dependencies { minecraft(group = "com.mojang", name = "minecraft", version = "1.16.5") mappings(group = "net.fabricmc", name = "yarn", version = "1.16.5+build.5", classifier = "v2") modImplementation("net.fabricmc:fabric-loader:0.12.12") - modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.7.1+kotlin.1.6.10") + modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.7.3+kotlin.1.6.20") } \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestExtension.kt b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestExtension.kt new file mode 100644 index 00000000..281850f5 --- /dev/null +++ b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestExtension.kt @@ -0,0 +1,19 @@ +package net.fabricmc.language.kotlin + +import net.minecraft.entity.Entity +import net.minecraft.util.Identifier + +class TestExtension { + fun testExtCompile() { + val entity: Entity? = null + entity!!.testExt() + } +} + +fun Entity.testExt() { + velocityDirty = true +} + +fun Identifier.testExt(): String { + return "Hello ext" +} \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt index 456ce7a9..a82609a1 100644 --- a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt +++ b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt @@ -11,13 +11,15 @@ class TestModClass : ModInitializer { val logger = LogManager.getFormatterLogger("KotlinLanguageTest") override fun onInitialize() { - val json = Json.encodeToString(ExampleSerializable(Identifier("kotlin:hello"), 12.0)) + val ident = Identifier("kotlin:hello") + val json = Json.encodeToString(ExampleSerializable(ident, 12.0)) val obj = Json.decodeFromString(json) logger.info("**************************") logger.info("Hello from Kotlin TestModClass") logger.info(json) logger.info(obj) + logger.info(ident.testExt()) logger.info("**************************") } } \ No newline at end of file diff --git a/src/test/resources/projects/modJavadoc/build.gradle b/src/test/resources/projects/modJavadoc/build.gradle new file mode 100644 index 00000000..52f1fc39 --- /dev/null +++ b/src/test/resources/projects/modJavadoc/build.gradle @@ -0,0 +1,18 @@ +// This is used by a range of tests that append to this file before running the gradle tasks. +// Can be used for tests that require minimal custom setup +plugins { + id 'fabric-loom' + id 'maven-publish' +} + +archivesBaseName = "fabric-example-mod" +version = "1.0.0" +group = "com.example" + +dependencies { + minecraft "com.mojang:minecraft:1.17.1" + mappings "net.fabricmc:yarn:1.17.1+build.59:v2" + modImplementation "net.fabricmc:fabric-loader:0.11.6" + + modImplementation files("dummy.jar") +} \ No newline at end of file diff --git a/src/test/resources/projects/modJavadoc/dummyDependency/fabric.mod.json b/src/test/resources/projects/modJavadoc/dummyDependency/fabric.mod.json new file mode 100644 index 00000000..aeefb996 --- /dev/null +++ b/src/test/resources/projects/modJavadoc/dummyDependency/fabric.mod.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "id": "dummy", + "version": "1", + "name": "Dummy Mod", + "custom": { + "loom:provided_javadoc": "javadoc.tiny" + } +} diff --git a/src/test/resources/projects/modJavadoc/dummyDependency/javadoc.tiny b/src/test/resources/projects/modJavadoc/dummyDependency/javadoc.tiny new file mode 100644 index 00000000..4a9212e5 --- /dev/null +++ b/src/test/resources/projects/modJavadoc/dummyDependency/javadoc.tiny @@ -0,0 +1,7 @@ +tiny 2 0 intermediary +c net/minecraft/class_2246 + c An example of a mod added class javadoc + f Lnet/minecraft/class_2248; field_10382 + c An example of a mod added field javadoc + m (I)Ljava/util/function/ToIntFunction; method_26107 + c An example of a mod added method javadoc \ No newline at end of file diff --git a/src/test/resources/projects/splitSources/build.gradle b/src/test/resources/projects/splitSources/build.gradle new file mode 100644 index 00000000..ea40b502 --- /dev/null +++ b/src/test/resources/projects/splitSources/build.gradle @@ -0,0 +1,27 @@ +plugins { + id 'fabric-loom' version '0.12.local' + id 'maven-publish' +} + +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + +loom { + splitEnvironmentSourceSets() +} + +dependencies { + minecraft "com.mojang:minecraft:1.18.2" + mappings "net.fabricmc:yarn:1.18.2+build.1:v2" + modImplementation "net.fabricmc:fabric-loader:0.13.3" + + modImplementation "net.fabricmc.fabric-api:fabric-api:0.47.8+1.18.2" +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 17 +} + +java { + withSourcesJar() +} \ No newline at end of file diff --git a/src/test/resources/projects/splitSources/src/client/java/net/fabricmc/example/client/ExampleModClient.java b/src/test/resources/projects/splitSources/src/client/java/net/fabricmc/example/client/ExampleModClient.java new file mode 100644 index 00000000..9047ed62 --- /dev/null +++ b/src/test/resources/projects/splitSources/src/client/java/net/fabricmc/example/client/ExampleModClient.java @@ -0,0 +1,19 @@ +package net.fabricmc.example.client; + +import net.fabricmc.api.ClientModInitializer; + +import net.minecraft.client.MinecraftClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ExampleModClient implements ClientModInitializer { + public static final Logger LOGGER = LoggerFactory.getLogger(ExampleModClient.class); + + @Override + public void onInitializeClient() { + LOGGER.info("Hello Client"); + + // Check we can compile against the client. + MinecraftClient client; + } +} diff --git a/src/test/resources/projects/splitSources/src/client/java/net/fabricmc/example/client/TestClientClass.java b/src/test/resources/projects/splitSources/src/client/java/net/fabricmc/example/client/TestClientClass.java new file mode 100644 index 00000000..46dbdecc --- /dev/null +++ b/src/test/resources/projects/splitSources/src/client/java/net/fabricmc/example/client/TestClientClass.java @@ -0,0 +1,8 @@ +package net.fabricmc.example.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class TestClientClass { +} diff --git a/src/test/resources/projects/splitSources/src/client/java/net/fabricmc/example/client/mixin/ExampleMixin.java b/src/test/resources/projects/splitSources/src/client/java/net/fabricmc/example/client/mixin/ExampleMixin.java new file mode 100644 index 00000000..afd4a3bc --- /dev/null +++ b/src/test/resources/projects/splitSources/src/client/java/net/fabricmc/example/client/mixin/ExampleMixin.java @@ -0,0 +1,16 @@ +package net.fabricmc.example.client.mixin; + +import net.fabricmc.example.ExampleMod; +import net.minecraft.client.gui.screen.TitleScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(TitleScreen.class) +public class ExampleMixin { + @Inject(at = @At("HEAD"), method = "init()V") + private void init(CallbackInfo info) { + ExampleMod.LOGGER.info("This line is printed by an example mod mixin!"); + } +} diff --git a/src/test/resources/projects/splitSources/src/client/resources/modid.client.mixins.json b/src/test/resources/projects/splitSources/src/client/resources/modid.client.mixins.json new file mode 100644 index 00000000..7c42cb48 --- /dev/null +++ b/src/test/resources/projects/splitSources/src/client/resources/modid.client.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.fabricmc.example.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ExampleMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/test/resources/projects/splitSources/src/main/java/net/fabricmc/example/ExampleMod.java b/src/test/resources/projects/splitSources/src/main/java/net/fabricmc/example/ExampleMod.java new file mode 100644 index 00000000..72df3bf7 --- /dev/null +++ b/src/test/resources/projects/splitSources/src/main/java/net/fabricmc/example/ExampleMod.java @@ -0,0 +1,19 @@ +package net.fabricmc.example; + +import net.fabricmc.api.ModInitializer; + +import net.minecraft.block.Block; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ExampleMod implements ModInitializer { + public static final Logger LOGGER = LoggerFactory.getLogger("modid"); + + @Override + public void onInitialize() { + LOGGER.info("Hello Fabric world!"); + + // Check we can compile against common code. + Block block; + } +} diff --git a/src/test/resources/projects/splitSources/src/main/resources/assets/modid/icon.png b/src/test/resources/projects/splitSources/src/main/resources/assets/modid/icon.png new file mode 100644 index 00000000..047b91f2 Binary files /dev/null and b/src/test/resources/projects/splitSources/src/main/resources/assets/modid/icon.png differ diff --git a/src/test/resources/projects/splitSources/src/main/resources/fabric.mod.json b/src/test/resources/projects/splitSources/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..f68569fa --- /dev/null +++ b/src/test/resources/projects/splitSources/src/main/resources/fabric.mod.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": 1, + "id": "modid", + "version": "${version}", + + "name": "Example Mod", + "description": "This is an example description! Tell everyone what your mod is about!", + "icon": "assets/modid/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "net.fabricmc.example.ExampleMod" + ], + "client": [ + "net.fabricmc.example.client.ExampleModClient" + ] + }, + "mixins": [ + "modid.client.mixins.json" + ] +}