mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Merge 1.11
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
[*.{gradle,java,kotlin}]
|
[*.{gradle,groovy,java,kotlin}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
ij_continuation_indent_size = 8
|
ij_continuation_indent_size = 8
|
||||||
ij_java_imports_layout = $*,|,java.**,|,javax.**,|,*,|,net.fabricmc.**
|
ij_java_imports_layout = $*,|,java.**,|,javax.**,|,*,|,net.fabricmc.**
|
||||||
ij_java_class_count_to_use_import_on_demand = 999
|
ij_java_class_count_to_use_import_on_demand = 999
|
||||||
|
ij_groovy_imports_layout = java.**,|,javax.**,|,*,|,net.fabricmc.**,|,$*
|
||||||
|
|||||||
25
.github/workflows/test-push.yml
vendored
25
.github/workflows/test-push.yml
vendored
@@ -1,5 +1,17 @@
|
|||||||
name: Run Tests
|
name: Run Tests
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
extended_tests:
|
||||||
|
description: 'Extended tests'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- 'false'
|
||||||
|
- 'true'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: build-${{ github.event.pull_request.number || github.ref }}
|
group: build-${{ github.event.pull_request.number || github.ref }}
|
||||||
@@ -24,6 +36,8 @@ jobs:
|
|||||||
build/reports/checkstyle/*.xml
|
build/reports/checkstyle/*.xml
|
||||||
|
|
||||||
build_windows:
|
build_windows:
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.extended_tests == 'true' }}
|
||||||
|
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -77,6 +91,7 @@ jobs:
|
|||||||
- run: ./gradlew printActionsTestName --name="${{ matrix.test }}" test --tests ${{ matrix.test }} --stacktrace --warning-mode fail
|
- run: ./gradlew printActionsTestName --name="${{ matrix.test }}" test --tests ${{ matrix.test }} --stacktrace --warning-mode fail
|
||||||
env:
|
env:
|
||||||
TEST_WARNING_MODE: fail
|
TEST_WARNING_MODE: fail
|
||||||
|
EXTENDED_TESTS: ${{ github.event.inputs.extended_tests }}
|
||||||
id: test
|
id: test
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
@@ -91,6 +106,8 @@ jobs:
|
|||||||
path: "*.hprof"
|
path: "*.hprof"
|
||||||
|
|
||||||
run_tests_windows:
|
run_tests_windows:
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.extended_tests == 'true' }}
|
||||||
|
|
||||||
needs: prepare_test_matrix
|
needs: prepare_test_matrix
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@@ -98,7 +115,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
test: ${{ fromJson(needs.prepare_test_matrix.outputs.matrix) }}
|
test: ${{ fromJson(needs.prepare_test_matrix.outputs.matrix) }}
|
||||||
|
|
||||||
runs-on: windows-2022
|
runs-on: windows-2025
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -130,8 +147,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
java: [ 17, 21 ]
|
java: [ 21 ]
|
||||||
os: [ windows-2022, ubuntu-24.04, macos-14 ]
|
os: [ windows-2025, ubuntu-24.04, macos-15 ]
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
22
build.gradle
22
build.gradle
@@ -18,12 +18,12 @@ tasks.withType(JavaCompile).configureEach {
|
|||||||
|
|
||||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "17"
|
jvmTarget = "21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "dev.architectury"
|
group = "dev.architectury"
|
||||||
def baseVersion = '1.10'
|
def baseVersion = '1.11'
|
||||||
|
|
||||||
def ENV = System.getenv()
|
def ENV = System.getenv()
|
||||||
def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999"
|
def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999"
|
||||||
@@ -144,6 +144,9 @@ dependencies {
|
|||||||
// source code remapping
|
// source code remapping
|
||||||
implementation libs.fabric.mercury
|
implementation libs.fabric.mercury
|
||||||
|
|
||||||
|
implementation libs.fabric.unpick
|
||||||
|
implementation libs.fabric.unpick.utils
|
||||||
|
|
||||||
// Kotlin
|
// Kotlin
|
||||||
implementation(libs.kotlin.metadata) {
|
implementation(libs.kotlin.metadata) {
|
||||||
transitive = false
|
transitive = false
|
||||||
@@ -176,6 +179,9 @@ dependencies {
|
|||||||
}
|
}
|
||||||
testImplementation testLibs.mockito
|
testImplementation testLibs.mockito
|
||||||
testImplementation testLibs.java.debug
|
testImplementation testLibs.java.debug
|
||||||
|
testImplementation testLibs.bcprov
|
||||||
|
testImplementation testLibs.bcutil
|
||||||
|
testImplementation testLibs.bcpkix
|
||||||
|
|
||||||
compileOnly runtimeLibs.jetbrains.annotations
|
compileOnly runtimeLibs.jetbrains.annotations
|
||||||
testCompileOnly runtimeLibs.jetbrains.annotations
|
testCompileOnly runtimeLibs.jetbrains.annotations
|
||||||
@@ -201,13 +207,13 @@ base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
tasks.withType(JavaCompile).configureEach {
|
||||||
it.options.release = 17
|
it.options.release = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
|
|
||||||
spotless {
|
spotless {
|
||||||
@@ -362,6 +368,7 @@ publishing {
|
|||||||
tasks.register('writeActionsTestMatrix') {
|
tasks.register('writeActionsTestMatrix') {
|
||||||
doLast {
|
doLast {
|
||||||
def testMatrix = []
|
def testMatrix = []
|
||||||
|
def extendedTests = Boolean.parseBoolean(System.getenv('EXTENDED_TESTS') ?: 'false')
|
||||||
file('src/test/groovy/net/fabricmc/loom/test/integration').traverse {
|
file('src/test/groovy/net/fabricmc/loom/test/integration').traverse {
|
||||||
if (it.name.endsWith("Test.groovy")) {
|
if (it.name.endsWith("Test.groovy")) {
|
||||||
if (it.name.endsWith("ReproducibleBuildTest.groovy")) {
|
if (it.name.endsWith("ReproducibleBuildTest.groovy")) {
|
||||||
@@ -369,7 +376,7 @@ tasks.register('writeActionsTestMatrix') {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it.name.endsWith("DebugLineNumbersTest.groovy")) {
|
if (it.name.endsWith("DebugLineNumbersTest.groovy") && !extendedTests) {
|
||||||
// Known flakey test
|
// Known flakey test
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -389,9 +396,6 @@ tasks.register('writeActionsTestMatrix') {
|
|||||||
// Run all the unit tests together
|
// Run all the unit tests together
|
||||||
testMatrix.add("net.fabricmc.loom.test.unit.*")
|
testMatrix.add("net.fabricmc.loom.test.unit.*")
|
||||||
|
|
||||||
// Kotlin tests
|
|
||||||
testMatrix.add("net.fabricmc.loom.test.kotlin.*")
|
|
||||||
|
|
||||||
def json = groovy.json.JsonOutput.toJson(testMatrix)
|
def json = groovy.json.JsonOutput.toJson(testMatrix)
|
||||||
def output = file("build/test_matrix.json")
|
def output = file("build/test_matrix.json")
|
||||||
output.parentFile.mkdir()
|
output.parentFile.mkdir()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.0.21"
|
||||||
asm = "9.7.1"
|
asm = "9.8"
|
||||||
commons-io = "2.15.1"
|
commons-io = "2.15.1"
|
||||||
gson = "2.10.1"
|
gson = "2.10.1"
|
||||||
guava = "33.0.0-jre"
|
guava = "33.0.0-jre"
|
||||||
@@ -12,6 +12,7 @@ mapping-io = "0.7.1"
|
|||||||
lorenz-tiny = "4.0.2"
|
lorenz-tiny = "4.0.2"
|
||||||
mercury = "0.1.4.17"
|
mercury = "0.1.4.17"
|
||||||
loom-native = "0.2.0"
|
loom-native = "0.2.0"
|
||||||
|
unpick = "3.0.0-beta.9"
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
spotless = "6.25.0"
|
spotless = "6.25.0"
|
||||||
@@ -48,6 +49,8 @@ fabric-mapping-io = { module = "net.fabricmc:mapping-io", version.ref = "mapping
|
|||||||
fabric-lorenz-tiny = { module = "net.fabricmc:lorenz-tiny", version.ref = "lorenz-tiny" }
|
fabric-lorenz-tiny = { module = "net.fabricmc:lorenz-tiny", version.ref = "lorenz-tiny" }
|
||||||
fabric-mercury = { module = "dev.architectury:mercury", version.ref = "mercury" }
|
fabric-mercury = { module = "dev.architectury:mercury", version.ref = "mercury" }
|
||||||
fabric-loom-nativelib = { module = "net.fabricmc:fabric-loom-native", version.ref = "loom-native" }
|
fabric-loom-nativelib = { module = "net.fabricmc:fabric-loom-native", version.ref = "loom-native" }
|
||||||
|
fabric-unpick = { module = "net.fabricmc.unpick:unpick", version.ref = "unpick" }
|
||||||
|
fabric-unpick-utils = { module = "net.fabricmc.unpick:unpick-format-utils", version.ref = "unpick" }
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ vineflower = "1.11.1"
|
|||||||
mixin-compile-extensions = "0.6.0"
|
mixin-compile-extensions = "0.6.0"
|
||||||
dev-launch-injector = "0.2.1+build.8"
|
dev-launch-injector = "0.2.1+build.8"
|
||||||
terminal-console-appender = "1.3.0"
|
terminal-console-appender = "1.3.0"
|
||||||
jetbrains-annotations = "25.0.0"
|
jetbrains-annotations = "26.0.2"
|
||||||
native-support = "1.0.1"
|
native-support = "1.0.1"
|
||||||
fabric-installer = "1.0.1"
|
fabric-installer = "1.0.3"
|
||||||
|
|
||||||
|
# Debug tools
|
||||||
|
renderdoc = "1.37"
|
||||||
|
|
||||||
# Forge Runtime depedencies
|
# Forge Runtime depedencies
|
||||||
javax-annotations = "3.0.2"
|
javax-annotations = "3.0.2"
|
||||||
@@ -36,6 +39,9 @@ jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "j
|
|||||||
native-support = { module = "net.fabricmc:fabric-loom-native-support", version.ref = "native-support" }
|
native-support = { module = "net.fabricmc:fabric-loom-native-support", version.ref = "native-support" }
|
||||||
fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" }
|
fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" }
|
||||||
|
|
||||||
|
# Debug tools
|
||||||
|
renderdoc = { module = "org.renderdoc:renderdoc", version.ref = "renderdoc" } # Not a maven dependency
|
||||||
|
|
||||||
# Forge Runtime depedencies
|
# Forge Runtime depedencies
|
||||||
javax-annotations = { module = "com.google.code.findbugs:jsr305", version.ref = "javax-annotations" }
|
javax-annotations = { module = "com.google.code.findbugs:jsr305", version.ref = "javax-annotations" }
|
||||||
mixin-remapper-service = { module = "dev.architectury:architectury-mixin-remapper-service", version.ref = "forge-runtime" }
|
mixin-remapper-service = { module = "dev.architectury:architectury-mixin-remapper-service", version.ref = "forge-runtime" }
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
[versions]
|
[versions]
|
||||||
spock = "2.3-groovy-3.0"
|
spock = "2.3-groovy-3.0"
|
||||||
junit = "5.11.3"
|
junit = "5.12.2"
|
||||||
javalin = "6.3.0"
|
javalin = "6.6.0"
|
||||||
mockito = "5.14.2"
|
mockito = "5.17.0"
|
||||||
java-debug = "0.52.0"
|
java-debug = "0.53.1"
|
||||||
mixin = "0.15.3+mixin.0.8.7"
|
mixin = "0.15.3+mixin.0.8.7"
|
||||||
|
bouncycastle = "1.80"
|
||||||
|
|
||||||
gradle-nightly = "8.14-20250208001853+0000"
|
gradle-latest = "9.0.0-rc-1"
|
||||||
fabric-loader = "0.16.9"
|
gradle-nightly = "9.1.0-20250620001442+0000"
|
||||||
|
fabric-loader = "0.16.14"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
spock = { module = "org.spockframework:spock-core", version.ref = "spock" }
|
spock = { module = "org.spockframework:spock-core", version.ref = "spock" }
|
||||||
@@ -17,5 +19,9 @@ javalin = { module = "io.javalin:javalin", version.ref = "javalin" }
|
|||||||
mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
|
mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
|
||||||
java-debug = { module = "com.microsoft.java:com.microsoft.java.debug.core", version.ref = "java-debug" }
|
java-debug = { module = "com.microsoft.java:com.microsoft.java.debug.core", version.ref = "java-debug" }
|
||||||
mixin = { module = "net.fabricmc:sponge-mixin", version.ref = "mixin" }
|
mixin = { module = "net.fabricmc:sponge-mixin", version.ref = "mixin" }
|
||||||
|
gradle-latest = { module = "org.gradle:dummy", version.ref = "gradle-latest" }
|
||||||
gradle-nightly = { module = "org.gradle:dummy", version.ref = "gradle-nightly" }
|
gradle-nightly = { module = "org.gradle:dummy", version.ref = "gradle-nightly" }
|
||||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||||
|
bcprov = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncycastle" }
|
||||||
|
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bouncycastle" }
|
||||||
|
bcutil = { module = "org.bouncycastle:bcutil-jdk18on", version.ref = "bouncycastle" }
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
6
gradlew
vendored
6
gradlew
vendored
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH="\\\"\\\""
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -205,7 +205,7 @@ fi
|
|||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
# Collect all arguments for the java command:
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# and any embedded shellness will be escaped.
|
# and any embedded shellness will be escaped.
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# treated as '${Hostname}' itself on the command line.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-classpath "$CLASSPATH" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|||||||
4
gradlew.bat
vendored
4
gradlew.bat
vendored
@@ -70,11 +70,11 @@ goto fail
|
|||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
@@ -155,6 +155,11 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
|
|||||||
|
|
||||||
boolean isProjectIsolationActive();
|
boolean isProjectIsolationActive();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true when '--write-verification-metadata` is set
|
||||||
|
*/
|
||||||
|
boolean isCollectingDependencyVerificationMetadata();
|
||||||
|
|
||||||
// ===================
|
// ===================
|
||||||
// Architectury Loom
|
// Architectury Loom
|
||||||
// ===================
|
// ===================
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ package net.fabricmc.loom;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import org.gradle.api.Plugin;
|
import org.gradle.api.Plugin;
|
||||||
@@ -98,8 +98,8 @@ public class LoomGradlePlugin implements Plugin<PluginAware> {
|
|||||||
LibraryLocationLogger.logLibraryVersions();
|
LibraryLocationLogger.logLibraryVersions();
|
||||||
|
|
||||||
// Apply default plugins
|
// Apply default plugins
|
||||||
project.apply(ImmutableMap.of("plugin", "java-library"));
|
project.apply(Map.of("plugin", "java-library"));
|
||||||
project.apply(ImmutableMap.of("plugin", "eclipse"));
|
project.apply(Map.of("plugin", "eclipse"));
|
||||||
|
|
||||||
// Setup extensions
|
// Setup extensions
|
||||||
project.getExtensions().create(LoomGradleExtensionAPI.class, "loom", LoomGradleExtensionImpl.class, project, LoomFiles.create(project));
|
project.getExtensions().create(LoomGradleExtensionAPI.class, "loom", LoomGradleExtensionImpl.class, project, LoomFiles.create(project));
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import org.gradle.api.tasks.SourceSet;
|
|||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Named} object for configuring "proxy" configurations that remap artifacts.
|
* A {@link Named} object for configuring "proxy" configurations that remap artifacts.
|
||||||
*/
|
*/
|
||||||
@@ -140,7 +142,7 @@ public abstract class RemapConfigurationSettings implements Named {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Provider<Boolean> defaultDependencyTransforms() {
|
private Provider<Boolean> defaultDependencyTransforms() {
|
||||||
return getSourceSet().map(sourceSet -> sourceSet.getName().equals(SourceSet.MAIN_SOURCE_SET_NAME) || sourceSet.getName().equals("client"));
|
return getSourceSet().map(sourceSet -> sourceSet.getName().equals(SourceSet.MAIN_SOURCE_SET_NAME) || sourceSet.getName().equals(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
*
|
*
|
||||||
* Copyright (c) 2016-2021 FabricMC
|
* Copyright (c) 2016-2025 FabricMC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -46,6 +46,8 @@ public interface MappingContext {
|
|||||||
|
|
||||||
Supplier<MemoryMappingTree> intermediaryTree();
|
Supplier<MemoryMappingTree> intermediaryTree();
|
||||||
|
|
||||||
|
boolean isUsingIntermediateMappings();
|
||||||
|
|
||||||
MinecraftProvider minecraftProvider();
|
MinecraftProvider minecraftProvider();
|
||||||
|
|
||||||
default String minecraftVersion() {
|
default String minecraftVersion() {
|
||||||
@@ -62,4 +64,6 @@ public interface MappingContext {
|
|||||||
DownloadBuilder download(String url);
|
DownloadBuilder download(String url);
|
||||||
|
|
||||||
boolean refreshDeps();
|
boolean refreshDeps();
|
||||||
|
|
||||||
|
boolean hasProperty(String property);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,16 @@ public interface SpecContext {
|
|||||||
|
|
||||||
List<FabricModJson> localMods();
|
List<FabricModJson> localMods();
|
||||||
|
|
||||||
// Returns mods that are both on the compile and runtime classpath
|
/**
|
||||||
|
* Return a set of mods that should be used for transforms, that target EITHER the common or client.
|
||||||
|
*/
|
||||||
List<FabricModJson> modDependenciesCompileRuntime();
|
List<FabricModJson> modDependenciesCompileRuntime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a set of mods that should be used for transforms, that target ONLY the client.
|
||||||
|
*/
|
||||||
|
List<FabricModJson> modDependenciesCompileRuntimeClient();
|
||||||
|
|
||||||
default List<FabricModJson> allMods() {
|
default List<FabricModJson> allMods() {
|
||||||
return Stream.concat(modDependencies().stream(), localMods().stream()).toList();
|
return Stream.concat(modDependencies().stream(), localMods().stream()).toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,10 @@
|
|||||||
package net.fabricmc.loom.build.mixin;
|
package net.fabricmc.loom.build.mixin;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.tasks.SourceSet;
|
import org.gradle.api.tasks.SourceSet;
|
||||||
import org.gradle.api.tasks.TaskProvider;
|
import org.gradle.api.tasks.TaskProvider;
|
||||||
@@ -41,7 +41,7 @@ public class GroovyApInvoker extends AnnotationProcessorInvoker<GroovyCompile> {
|
|||||||
public GroovyApInvoker(Project project) {
|
public GroovyApInvoker(Project project) {
|
||||||
super(
|
super(
|
||||||
project,
|
project,
|
||||||
ImmutableList.of(),
|
List.of(),
|
||||||
getInvokerTasks(project),
|
getInvokerTasks(project),
|
||||||
AnnotationProcessorInvoker.GROOVY);
|
AnnotationProcessorInvoker.GROOVY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,10 @@
|
|||||||
package net.fabricmc.loom.build.mixin;
|
package net.fabricmc.loom.build.mixin;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.tasks.SourceSet;
|
import org.gradle.api.tasks.SourceSet;
|
||||||
import org.gradle.api.tasks.TaskProvider;
|
import org.gradle.api.tasks.TaskProvider;
|
||||||
@@ -42,7 +42,7 @@ public class ScalaApInvoker extends AnnotationProcessorInvoker<ScalaCompile> {
|
|||||||
super(
|
super(
|
||||||
project,
|
project,
|
||||||
// Scala just uses the java AP configuration afaik. This of course assumes the java AP also gets configured.
|
// Scala just uses the java AP configuration afaik. This of course assumes the java AP also gets configured.
|
||||||
ImmutableList.of(),
|
List.of(),
|
||||||
getInvokerTasks(project),
|
getInvokerTasks(project),
|
||||||
AnnotationProcessorInvoker.SCALA);
|
AnnotationProcessorInvoker.SCALA);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import com.google.common.hash.Hashing;
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.gradle.api.artifacts.ArtifactView;
|
import org.gradle.api.artifacts.ArtifactView;
|
||||||
@@ -63,6 +62,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import net.fabricmc.loom.LoomGradlePlugin;
|
import net.fabricmc.loom.LoomGradlePlugin;
|
||||||
import net.fabricmc.loom.task.AbstractLoomTask;
|
import net.fabricmc.loom.task.AbstractLoomTask;
|
||||||
|
import net.fabricmc.loom.util.Checksum;
|
||||||
import net.fabricmc.loom.util.ModPlatform;
|
import net.fabricmc.loom.util.ModPlatform;
|
||||||
import net.fabricmc.loom.util.ZipReprocessorUtil;
|
import net.fabricmc.loom.util.ZipReprocessorUtil;
|
||||||
import net.fabricmc.loom.util.ZipUtils;
|
import net.fabricmc.loom.util.ZipUtils;
|
||||||
@@ -194,9 +194,7 @@ public abstract class NestableJarGenerationTask extends AbstractLoomTask {
|
|||||||
|
|
||||||
// Fabric Loader can't handle modIds longer than 64 characters
|
// Fabric Loader can't handle modIds longer than 64 characters
|
||||||
if (modId.length() > 64) {
|
if (modId.length() > 64) {
|
||||||
String hash = Hashing.sha256()
|
String hash = Checksum.of(modId).sha256().hex();
|
||||||
.hashString(modId, StandardCharsets.UTF_8)
|
|
||||||
.toString();
|
|
||||||
modId = modId.substring(0, 50) + hash.substring(0, 14);
|
modId = modId.substring(0, 50) + hash.substring(0, 14);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ public abstract class CompileConfiguration implements Runnable {
|
|||||||
private LockFile getLockFile() {
|
private LockFile getLockFile() {
|
||||||
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||||
final Path cacheDirectory = extension.getFiles().getUserCache().toPath();
|
final Path cacheDirectory = extension.getFiles().getUserCache().toPath();
|
||||||
final String pathHash = Checksum.projectHash(getProject());
|
final String pathHash = Checksum.of(getProject()).sha1().hex();
|
||||||
return new LockFile(
|
return new LockFile(
|
||||||
cacheDirectory.resolve("." + pathHash + ".lock"),
|
cacheDirectory.resolve("." + pathHash + ".lock"),
|
||||||
"Lock for cache='%s', project='%s'".formatted(
|
"Lock for cache='%s', project='%s'".formatted(
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ public abstract class LoomConfigurations implements Runnable {
|
|||||||
register(Constants.Configurations.MAPPINGS, Role.RESOLVABLE);
|
register(Constants.Configurations.MAPPINGS, Role.RESOLVABLE);
|
||||||
register(Constants.Configurations.MAPPINGS_FINAL, Role.RESOLVABLE);
|
register(Constants.Configurations.MAPPINGS_FINAL, Role.RESOLVABLE);
|
||||||
register(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Role.RESOLVABLE);
|
register(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Role.RESOLVABLE);
|
||||||
register(Constants.Configurations.UNPICK_CLASSPATH, Role.RESOLVABLE);
|
|
||||||
register(Constants.Configurations.LOCAL_RUNTIME, Role.RESOLVABLE);
|
register(Constants.Configurations.LOCAL_RUNTIME, Role.RESOLVABLE);
|
||||||
extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.LOCAL_RUNTIME);
|
extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.LOCAL_RUNTIME);
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import groovy.util.Node;
|
import groovy.util.Node;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.artifacts.Configuration;
|
import org.gradle.api.artifacts.Configuration;
|
||||||
@@ -54,7 +53,7 @@ import net.fabricmc.loom.util.gradle.GradleUtils;
|
|||||||
public abstract class MavenPublication implements Runnable {
|
public abstract class MavenPublication implements Runnable {
|
||||||
// ImmutableMap is needed since it guarantees ordering
|
// ImmutableMap is needed since it guarantees ordering
|
||||||
// (compile must go before runtime, or otherwise dependencies might get the "weaker" runtime scope).
|
// (compile must go before runtime, or otherwise dependencies might get the "weaker" runtime scope).
|
||||||
private static final Map<String, String> CONFIGURATION_TO_SCOPE = ImmutableMap.of(
|
private static final Map<String, String> CONFIGURATION_TO_SCOPE = Map.of(
|
||||||
JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, "compile",
|
JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, "compile",
|
||||||
JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME, "runtime"
|
JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME, "runtime"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
package net.fabricmc.loom.configuration.accesswidener;
|
package net.fabricmc.loom.configuration.accesswidener;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
@@ -40,11 +39,7 @@ import net.fabricmc.tinyremapper.TinyRemapper;
|
|||||||
|
|
||||||
public record LocalAccessWidenerEntry(Path path, String hash) implements AccessWidenerEntry {
|
public record LocalAccessWidenerEntry(Path path, String hash) implements AccessWidenerEntry {
|
||||||
public static LocalAccessWidenerEntry create(Path path) {
|
public static LocalAccessWidenerEntry create(Path path) {
|
||||||
try {
|
return new LocalAccessWidenerEntry(path, Checksum.of(path).sha1().hex());
|
||||||
return new LocalAccessWidenerEntry(path, Checksum.sha1Hex(path));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Failed to create LocalAccessWidenerEntry", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -24,18 +24,12 @@
|
|||||||
|
|
||||||
package net.fabricmc.loom.configuration.decompile;
|
package net.fabricmc.loom.configuration.decompile;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
|
||||||
import net.fabricmc.loom.task.GenerateSourcesTask;
|
|
||||||
import net.fabricmc.loom.util.Constants;
|
|
||||||
|
|
||||||
public abstract class DecompileConfiguration<T extends MappedMinecraftProvider> {
|
public abstract class DecompileConfiguration<T extends MappedMinecraftProvider> {
|
||||||
static final String DEFAULT_DECOMPILER = "Vineflower";
|
static final String DEFAULT_DECOMPILER = "Vineflower";
|
||||||
@@ -55,15 +49,4 @@ public abstract class DecompileConfiguration<T extends MappedMinecraftProvider>
|
|||||||
public abstract String getTaskName(MinecraftJar.Type type);
|
public abstract String getTaskName(MinecraftJar.Type type);
|
||||||
|
|
||||||
public abstract void afterEvaluation();
|
public abstract void afterEvaluation();
|
||||||
|
|
||||||
protected final void configureUnpick(GenerateSourcesTask task, File unpickOutputJar) {
|
|
||||||
final ConfigurationContainer configurations = task.getProject().getConfigurations();
|
|
||||||
|
|
||||||
task.getUnpickDefinitions().set(mappingConfiguration.getUnpickDefinitionsFile());
|
|
||||||
task.getUnpickOutputJar().set(unpickOutputJar);
|
|
||||||
task.getUnpickConstantJar().setFrom(configurations.getByName(Constants.Configurations.MAPPING_CONSTANTS));
|
|
||||||
task.getUnpickClasspath().setFrom(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
|
|
||||||
task.getUnpickClasspath().from(configurations.getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED));
|
|
||||||
extension.getMinecraftJars(MappingsNamespace.NAMED).forEach(task.getUnpickClasspath()::from);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
package net.fabricmc.loom.configuration.decompile;
|
package net.fabricmc.loom.configuration.decompile;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
@@ -66,11 +65,6 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration<Mapp
|
|||||||
task.dependsOn(project.getTasks().named("validateAccessWidener"));
|
task.dependsOn(project.getTasks().named("validateAccessWidener"));
|
||||||
task.setDescription("Decompile minecraft using %s.".formatted(decompilerName));
|
task.setDescription("Decompile minecraft using %s.".formatted(decompilerName));
|
||||||
task.setGroup(Constants.TaskGroup.FABRIC);
|
task.setGroup(Constants.TaskGroup.FABRIC);
|
||||||
|
|
||||||
if (mappingConfiguration.hasUnpickDefinitions()) {
|
|
||||||
final File outputJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar");
|
|
||||||
configureUnpick(task, outputJar);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
package net.fabricmc.loom.configuration.decompile;
|
package net.fabricmc.loom.configuration.decompile;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import org.gradle.api.Action;
|
import org.gradle.api.Action;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.Task;
|
import org.gradle.api.Task;
|
||||||
@@ -56,22 +54,12 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration<Ma
|
|||||||
final TaskProvider<Task> commonDecompileTask = createDecompileTasks("Common", task -> {
|
final TaskProvider<Task> commonDecompileTask = createDecompileTasks("Common", task -> {
|
||||||
task.getInputJarName().set(commonJar.getName());
|
task.getInputJarName().set(commonJar.getName());
|
||||||
task.getSourcesOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", commonJar.getPath()));
|
task.getSourcesOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", commonJar.getPath()));
|
||||||
|
|
||||||
if (mappingConfiguration.hasUnpickDefinitions()) {
|
|
||||||
File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar");
|
|
||||||
configureUnpick(task, unpickJar);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final TaskProvider<Task> clientOnlyDecompileTask = createDecompileTasks("ClientOnly", task -> {
|
final TaskProvider<Task> clientOnlyDecompileTask = createDecompileTasks("ClientOnly", task -> {
|
||||||
task.getInputJarName().set(clientOnlyJar.getName());
|
task.getInputJarName().set(clientOnlyJar.getName());
|
||||||
task.getSourcesOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", clientOnlyJar.getPath()));
|
task.getSourcesOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", clientOnlyJar.getPath()));
|
||||||
|
|
||||||
if (mappingConfiguration.hasUnpickDefinitions()) {
|
|
||||||
File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar");
|
|
||||||
configureUnpick(task, unpickJar);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't allow them to run at the same time.
|
// Don't allow them to run at the same time.
|
||||||
task.mustRunAfter(commonDecompileTask);
|
task.mustRunAfter(commonDecompileTask);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ import java.util.Set;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import groovy.xml.XmlUtil;
|
||||||
import org.gradle.api.JavaVersion;
|
import org.gradle.api.JavaVersion;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.artifacts.ModuleVersionIdentifier;
|
import org.gradle.api.artifacts.ModuleVersionIdentifier;
|
||||||
@@ -51,9 +51,6 @@ import org.gradle.api.artifacts.ResolvedArtifact;
|
|||||||
import org.gradle.api.artifacts.ResolvedModuleVersion;
|
import org.gradle.api.artifacts.ResolvedModuleVersion;
|
||||||
import org.gradle.api.tasks.SourceSet;
|
import org.gradle.api.tasks.SourceSet;
|
||||||
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
|
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.configuration.InstallerData;
|
import net.fabricmc.loom.configuration.InstallerData;
|
||||||
@@ -78,42 +75,7 @@ public class RunConfig {
|
|||||||
public transient SourceSet sourceSet;
|
public transient SourceSet sourceSet;
|
||||||
public Map<String, Object> environmentVariables;
|
public Map<String, Object> environmentVariables;
|
||||||
public String projectName;
|
public String projectName;
|
||||||
|
public String folderName;
|
||||||
public Element genRuns(Element doc) {
|
|
||||||
Element root = this.addXml(doc, "component", ImmutableMap.of("name", "ProjectRunConfigurationManager"));
|
|
||||||
root = addXml(root, "configuration", ImmutableMap.of("default", "false", "name", configName, "type", "Application", "factoryName", "Application"));
|
|
||||||
|
|
||||||
this.addXml(root, "module", ImmutableMap.of("name", ideaModuleName));
|
|
||||||
this.addXml(root, "option", ImmutableMap.of("name", "MAIN_CLASS_NAME", "value", mainClass));
|
|
||||||
this.addXml(root, "option", ImmutableMap.of("name", "WORKING_DIRECTORY", "value", runDirIdeaUrl));
|
|
||||||
|
|
||||||
if (!vmArgs.isEmpty()) {
|
|
||||||
this.addXml(root, "option", ImmutableMap.of("name", "VM_PARAMETERS", "value", joinArguments(vmArgs)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!programArgs.isEmpty()) {
|
|
||||||
this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", joinArguments(programArgs)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Element addXml(Node parent, String name, Map<String, String> values) {
|
|
||||||
Document doc = parent.getOwnerDocument();
|
|
||||||
|
|
||||||
if (doc == null) {
|
|
||||||
doc = (Document) parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
Element e = doc.createElement(name);
|
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : values.entrySet()) {
|
|
||||||
e.setAttribute(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.appendChild(e);
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turns camelCase/PascalCase into Capital Case
|
// Turns camelCase/PascalCase into Capital Case
|
||||||
// caseConversionExample -> Case Conversion Example
|
// caseConversionExample -> Case Conversion Example
|
||||||
@@ -194,6 +156,7 @@ public class RunConfig {
|
|||||||
runConfig.environmentVariables = new HashMap<>();
|
runConfig.environmentVariables = new HashMap<>();
|
||||||
runConfig.environmentVariables.putAll(settings.getEnvironmentVariables());
|
runConfig.environmentVariables.putAll(settings.getEnvironmentVariables());
|
||||||
runConfig.projectName = project.getName();
|
runConfig.projectName = project.getName();
|
||||||
|
runConfig.folderName = settings.getIdeConfigFolder().getOrNull();
|
||||||
|
|
||||||
for (Consumer<RunConfig> consumer : extension.getSettingsPostEdit()) {
|
for (Consumer<RunConfig> consumer : extension.getSettingsPostEdit()) {
|
||||||
consumer.accept(runConfig);
|
consumer.accept(runConfig);
|
||||||
@@ -228,6 +191,7 @@ public class RunConfig {
|
|||||||
dummyConfig = dummyConfig.replace("%VM_ARGS%", joinArguments(vmArgs).replaceAll("\"", """));
|
dummyConfig = dummyConfig.replace("%VM_ARGS%", joinArguments(vmArgs).replaceAll("\"", """));
|
||||||
dummyConfig = dummyConfig.replace("%IDEA_ENV_VARS%", getEnvVars("<env name=\"%s\" value=\"%s\"/>"));
|
dummyConfig = dummyConfig.replace("%IDEA_ENV_VARS%", getEnvVars("<env name=\"%s\" value=\"%s\"/>"));
|
||||||
dummyConfig = dummyConfig.replace("%ECLIPSE_ENV_VARS%", getEnvVars("<mapEntry key=\"%s\" value=\"%s\"/>"));
|
dummyConfig = dummyConfig.replace("%ECLIPSE_ENV_VARS%", getEnvVars("<mapEntry key=\"%s\" value=\"%s\"/>"));
|
||||||
|
dummyConfig = dummyConfig.replace("%IDEA_FOLDER_NAME%", folderName == null ? "" : "folderName=\"" + XmlUtil.escapeXml(folderName) + "\"");
|
||||||
|
|
||||||
return dummyConfig;
|
return dummyConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ import net.fabricmc.loom.util.ModPlatform;
|
|||||||
import net.fabricmc.loom.util.Platform;
|
import net.fabricmc.loom.util.Platform;
|
||||||
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
||||||
|
|
||||||
public class RunConfigSettings implements Named {
|
public abstract class RunConfigSettings implements Named {
|
||||||
/**
|
/**
|
||||||
* Arguments for the JVM, such as system properties.
|
* Arguments for the JVM, such as system properties.
|
||||||
*/
|
*/
|
||||||
@@ -465,6 +465,7 @@ public class RunConfigSettings implements Named {
|
|||||||
defaultMainClass = parent.defaultMainClass;
|
defaultMainClass = parent.defaultMainClass;
|
||||||
source = parent.source;
|
source = parent.source;
|
||||||
ideConfigGenerated = parent.ideConfigGenerated;
|
ideConfigGenerated = parent.ideConfigGenerated;
|
||||||
|
getIdeConfigFolder().set(parent.getIdeConfigFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void makeRunDir() {
|
public void makeRunDir() {
|
||||||
@@ -483,6 +484,15 @@ public class RunConfigSettings implements Named {
|
|||||||
this.ideConfigGenerated = ideConfigGenerated;
|
this.ideConfigGenerated = ideConfigGenerated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group this run config under the given folder.
|
||||||
|
*
|
||||||
|
* <p>This is currently only supported on IntelliJ IDEA.
|
||||||
|
*
|
||||||
|
* @return The property used to set the config folder.
|
||||||
|
*/
|
||||||
|
public abstract Property<String> getIdeConfigFolder();
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
public Property<String> devLaunchMainClass() {
|
public Property<String> devLaunchMainClass() {
|
||||||
|
|||||||
@@ -100,10 +100,14 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Spec(injectedInterfaces);
|
Set<String> clientOnlyModIds = context.modDependenciesCompileRuntimeClient().stream()
|
||||||
|
.map(FabricModJson::getId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
return new Spec(injectedInterfaces, clientOnlyModIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Spec(List<InjectedInterface> injectedInterfaces) implements MinecraftJarProcessor.Spec {
|
public record Spec(List<InjectedInterface> injectedInterfaces, Set<String> clientOnlyModIds) implements MinecraftJarProcessor.Spec {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -115,6 +119,10 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess
|
|||||||
|
|
||||||
try (LazyCloseable<TinyRemapper> tinyRemapper = context.createRemapper(MappingsNamespace.INTERMEDIARY, MappingsNamespace.NAMED)) {
|
try (LazyCloseable<TinyRemapper> tinyRemapper = context.createRemapper(MappingsNamespace.INTERMEDIARY, MappingsNamespace.NAMED)) {
|
||||||
final List<InjectedInterface> remappedInjectedInterfaces = spec.injectedInterfaces().stream()
|
final List<InjectedInterface> remappedInjectedInterfaces = spec.injectedInterfaces().stream()
|
||||||
|
.filter(injectedInterface -> {
|
||||||
|
return context.includesClient() // The client jar depends on the server, so always apply all to it
|
||||||
|
|| !spec.clientOnlyModIds.contains(injectedInterface.modId()); // Or the mod is NOT only found on the client classpath, so we can apply it to the server jar
|
||||||
|
})
|
||||||
.map(injectedInterface -> remap(
|
.map(injectedInterface -> remap(
|
||||||
injectedInterface,
|
injectedInterface,
|
||||||
s -> mappings.mapClassName(s, intermediaryIndex, namedIndex),
|
s -> mappings.mapClassName(s, intermediaryIndex, namedIndex),
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public interface ArtifactRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String version() {
|
public String version() {
|
||||||
return replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.truncatedSha256(artifact.getFile()));
|
return replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.of(artifact.getFile()).sha256().hex(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String classifier() {
|
public String classifier() {
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.artifacts.Configuration;
|
import org.gradle.api.artifacts.Configuration;
|
||||||
import org.gradle.api.artifacts.FileCollectionDependency;
|
import org.gradle.api.artifacts.FileCollectionDependency;
|
||||||
@@ -58,6 +57,8 @@ import org.gradle.api.tasks.SourceSet;
|
|||||||
import org.gradle.jvm.JvmLibrary;
|
import org.gradle.jvm.JvmLibrary;
|
||||||
import org.gradle.language.base.artifact.SourcesArtifact;
|
import org.gradle.language.base.artifact.SourcesArtifact;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.LoomGradlePlugin;
|
import net.fabricmc.loom.LoomGradlePlugin;
|
||||||
@@ -65,6 +66,7 @@ import net.fabricmc.loom.api.RemapConfigurationSettings;
|
|||||||
import net.fabricmc.loom.configuration.RemapConfigurations;
|
import net.fabricmc.loom.configuration.RemapConfigurations;
|
||||||
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
|
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
|
||||||
import net.fabricmc.loom.configuration.mods.dependency.ModDependencyFactory;
|
import net.fabricmc.loom.configuration.mods.dependency.ModDependencyFactory;
|
||||||
|
import net.fabricmc.loom.configuration.mods.dependency.ModDependencyOptions;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
||||||
import net.fabricmc.loom.util.Checksum;
|
import net.fabricmc.loom.util.Checksum;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
@@ -79,6 +81,8 @@ public class ModConfigurationRemapper {
|
|||||||
// This can happen when the dependency is a FileCollectionDependency or from a flatDir repository.
|
// This can happen when the dependency is a FileCollectionDependency or from a flatDir repository.
|
||||||
public static final String MISSING_GROUP = "unspecified";
|
public static final String MISSING_GROUP = "unspecified";
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ModConfigurationRemapper.class);
|
||||||
|
|
||||||
public static void supplyModConfigurations(Project project, ServiceFactory serviceFactory, String mappingsSuffix, LoomGradleExtension extension, SourceRemapper sourceRemapper) {
|
public static void supplyModConfigurations(Project project, ServiceFactory serviceFactory, String mappingsSuffix, LoomGradleExtension extension, SourceRemapper sourceRemapper) {
|
||||||
final DependencyHandler dependencies = project.getDependencies();
|
final DependencyHandler dependencies = project.getDependencies();
|
||||||
// The configurations where the source and remapped artifacts go.
|
// The configurations where the source and remapped artifacts go.
|
||||||
@@ -98,7 +102,7 @@ public class ModConfigurationRemapper {
|
|||||||
|
|
||||||
for (RemapConfigurationSettings entry : remapConfigurationSettings) {
|
for (RemapConfigurationSettings entry : remapConfigurationSettings) {
|
||||||
// key: true if runtime, false if compile
|
// key: true if runtime, false if compile
|
||||||
final Map<Boolean, Boolean> envToEnabled = ImmutableMap.of(
|
final Map<Boolean, Boolean> envToEnabled = Map.of(
|
||||||
false, entry.getOnCompileClasspath().get(),
|
false, entry.getOnCompileClasspath().get(),
|
||||||
true, entry.getOnRuntimeClasspath().get()
|
true, entry.getOnRuntimeClasspath().get()
|
||||||
);
|
);
|
||||||
@@ -135,6 +139,15 @@ public class ModConfigurationRemapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ModDependencyOptions modDependencyOptions = ModDependencyOptions.create(project, ModDependencyOptions.class, options -> {
|
||||||
|
options.getMappings().set(mappingsSuffix);
|
||||||
|
options.getInlineRefmap().set(extension.getMixin().getInlineDependencyRefmaps());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (LOGGER.isInfoEnabled()) {
|
||||||
|
LOGGER.info("Mod dependency options: {}", modDependencyOptions.getJson());
|
||||||
|
}
|
||||||
|
|
||||||
// Round 1: Discovery
|
// Round 1: Discovery
|
||||||
// Go through all the configs to find artifacts to remap and
|
// Go through all the configs to find artifacts to remap and
|
||||||
// the installer data. The installer data has to be added before
|
// the installer data. The installer data has to be added before
|
||||||
@@ -177,7 +190,7 @@ public class ModConfigurationRemapper {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ModDependency modDependency = ModDependencyFactory.create(artifact, artifactMetadata, remappedConfig, clientRemappedConfig, mappingsSuffix, project);
|
final ModDependency modDependency = ModDependencyFactory.create(artifact, artifactMetadata, remappedConfig, clientRemappedConfig, modDependencyOptions, project);
|
||||||
scheduleSourcesRemapping(project, sourceRemapper, modDependency);
|
scheduleSourcesRemapping(project, sourceRemapper, modDependency);
|
||||||
modDependencies.add(modDependency);
|
modDependencies.add(modDependency);
|
||||||
}
|
}
|
||||||
@@ -263,7 +276,7 @@ public class ModConfigurationRemapper {
|
|||||||
|
|
||||||
for (File artifact : files) {
|
for (File artifact : files) {
|
||||||
final String name = getNameWithoutExtension(artifact.toPath());
|
final String name = getNameWithoutExtension(artifact.toPath());
|
||||||
final String version = replaceIfNullOrEmpty(dependency.getVersion(), () -> Checksum.truncatedSha256(artifact));
|
final String version = replaceIfNullOrEmpty(dependency.getVersion(), () -> Checksum.of(artifact).sha256().hex(10));
|
||||||
artifacts.add(new ArtifactRef.FileArtifactRef(artifact.toPath(), group, name, version));
|
artifacts.add(new ArtifactRef.FileArtifactRef(artifact.toPath(), group, name, version));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +346,7 @@ public class ModConfigurationRemapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dependency.isCacheInvalid(project, "sources")) {
|
if (dependency.isCacheInvalid(project, "sources")) {
|
||||||
final Path output = dependency.getWorkingFile("sources");
|
final Path output = dependency.getWorkingFile(project, "sources");
|
||||||
|
|
||||||
sourceRemapper.scheduleRemapSources(sourcesInput.toFile(), output.toFile(), false, true, () -> {
|
sourceRemapper.scheduleRemapSources(sourcesInput.toFile(), output.toFile(), false, true, () -> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -48,15 +49,19 @@ import dev.architectury.loom.util.MappingOption;
|
|||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.artifacts.Configuration;
|
import org.gradle.api.artifacts.Configuration;
|
||||||
import org.gradle.api.attributes.Usage;
|
import org.gradle.api.attributes.Usage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.api.RemapConfigurationSettings;
|
import net.fabricmc.loom.api.RemapConfigurationSettings;
|
||||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.build.IntermediaryNamespaces;
|
import net.fabricmc.loom.build.IntermediaryNamespaces;
|
||||||
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
|
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
|
||||||
|
import net.fabricmc.loom.configuration.mods.extension.ModProcessorExtension;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
||||||
import net.fabricmc.loom.extension.RemapperExtensionHolder;
|
import net.fabricmc.loom.extension.RemapperExtensionHolder;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
|
import net.fabricmc.loom.util.IdentityBiMap;
|
||||||
import net.fabricmc.loom.util.LoggerFilter;
|
import net.fabricmc.loom.util.LoggerFilter;
|
||||||
import net.fabricmc.loom.util.ModPlatform;
|
import net.fabricmc.loom.util.ModPlatform;
|
||||||
import net.fabricmc.loom.util.Pair;
|
import net.fabricmc.loom.util.Pair;
|
||||||
@@ -73,11 +78,12 @@ import net.fabricmc.tinyremapper.InputTag;
|
|||||||
import net.fabricmc.tinyremapper.NonClassCopyMode;
|
import net.fabricmc.tinyremapper.NonClassCopyMode;
|
||||||
import net.fabricmc.tinyremapper.OutputConsumerPath;
|
import net.fabricmc.tinyremapper.OutputConsumerPath;
|
||||||
import net.fabricmc.tinyremapper.TinyRemapper;
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
import net.fabricmc.tinyremapper.extension.mixin.MixinExtension;
|
|
||||||
|
|
||||||
public class ModProcessor {
|
public class ModProcessor {
|
||||||
private static final String toM = MappingsNamespace.NAMED.toString();
|
private static final String toM = MappingsNamespace.NAMED.toString();
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ModProcessor.class);
|
||||||
|
|
||||||
private static final Pattern COPY_CONFIGURATION_PATTERN = Pattern.compile("^(.+)Copy[0-9]*$");
|
private static final Pattern COPY_CONFIGURATION_PATTERN = Pattern.compile("^(.+)Copy[0-9]*$");
|
||||||
|
|
||||||
private final Project project;
|
private final Project project;
|
||||||
@@ -92,7 +98,7 @@ public class ModProcessor {
|
|||||||
|
|
||||||
public void processMods(List<ModDependency> remapList) throws IOException {
|
public void processMods(List<ModDependency> remapList) throws IOException {
|
||||||
try {
|
try {
|
||||||
project.getLogger().lifecycle(":remapping {} mods from {}", remapList.size(), describeConfiguration(sourceConfiguration));
|
LOGGER.info(":remapping {} mods from {}", remapList.size(), describeConfiguration(sourceConfiguration));
|
||||||
remapJars(remapList);
|
remapJars(remapList);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(String.format(Locale.ENGLISH, "Failed to remap %d mods", remapList.size()), e);
|
throw new RuntimeException(String.format(Locale.ENGLISH, "Failed to remap %d mods", remapList.size()), e);
|
||||||
@@ -192,13 +198,21 @@ public class ModProcessor {
|
|||||||
builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension());
|
builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension());
|
||||||
}
|
}
|
||||||
|
|
||||||
final Set<InputTag> remapMixins = new HashSet<>();
|
final IdentityBiMap<InputTag, ModDependency> inputTags = new IdentityBiMap<>();
|
||||||
final boolean requiresStaticMixinRemap = remapList.stream()
|
final List<ModProcessorExtension> activeExtensions = ModProcessorExtension.EXTENSIONS.stream()
|
||||||
.anyMatch(modDependency -> modDependency.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.STATIC);
|
.filter(e -> remapList.stream().anyMatch(e::appliesTo))
|
||||||
|
.toList();
|
||||||
|
final ModProcessorExtension.Context context = new ModProcessorExtension.Context(fromM, toM, remapList);
|
||||||
|
|
||||||
if (requiresStaticMixinRemap) {
|
for (ModProcessorExtension modProcessorExtension : activeExtensions) {
|
||||||
// Configure the mixin extension to remap mixins from mod jars that were remapped with the mixin extension.
|
LOGGER.info("Applying mod processor extension: {}", modProcessorExtension.getClass().getSimpleName());
|
||||||
builder.extension(new MixinExtension(remapMixins::contains));
|
|
||||||
|
final Predicate<InputTag> applyPredicate = inputTag -> {
|
||||||
|
ModDependency mod = inputTags.getByKey(inputTag);
|
||||||
|
return mod != null && modProcessorExtension.appliesTo(mod);
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.extension(modProcessorExtension.createExtension(context, applyPredicate));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (RemapperExtensionHolder holder : extension.getRemapperExtensions().get()) {
|
for (RemapperExtensionHolder holder : extension.getRemapperExtensions().get()) {
|
||||||
@@ -209,14 +223,13 @@ public class ModProcessor {
|
|||||||
|
|
||||||
remapper.readClassPath(extension.getMinecraftJars(IntermediaryNamespaces.runtimeIntermediaryNamespace(project)).toArray(Path[]::new));
|
remapper.readClassPath(extension.getMinecraftJars(IntermediaryNamespaces.runtimeIntermediaryNamespace(project)).toArray(Path[]::new));
|
||||||
|
|
||||||
final Map<ModDependency, InputTag> tagMap = new HashMap<>();
|
|
||||||
final Map<ModDependency, OutputConsumerPath> outputConsumerMap = new HashMap<>();
|
final Map<ModDependency, OutputConsumerPath> outputConsumerMap = new HashMap<>();
|
||||||
final Map<ModDependency, Pair<byte[], String>> accessWidenerMap = new HashMap<>();
|
final Map<ModDependency, Pair<byte[], String>> accessWidenerMap = new HashMap<>();
|
||||||
|
|
||||||
for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) {
|
for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) {
|
||||||
for (File inputFile : entry.getSourceConfiguration().get().getFiles()) {
|
for (File inputFile : entry.getSourceConfiguration().get().getFiles()) {
|
||||||
if (remapList.stream().noneMatch(info -> info.getInputFile().toFile().equals(inputFile))) {
|
if (remapList.stream().noneMatch(info -> info.getInputFile().toFile().equals(inputFile))) {
|
||||||
project.getLogger().debug("Adding " + inputFile + " onto the remap classpath");
|
LOGGER.debug("Adding " + inputFile + " onto the remap classpath");
|
||||||
remapper.readClassPathAsync(inputFile.toPath());
|
remapper.readClassPathAsync(inputFile.toPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,23 +238,10 @@ public class ModProcessor {
|
|||||||
for (ModDependency info : remapList) {
|
for (ModDependency info : remapList) {
|
||||||
InputTag tag = remapper.createInputTag();
|
InputTag tag = remapper.createInputTag();
|
||||||
|
|
||||||
project.getLogger().debug("Adding " + info.getInputFile() + " as a remap input");
|
LOGGER.debug("Adding " + info.getInputFile() + " as a remap input");
|
||||||
|
inputTags.put(tag, info);
|
||||||
// Note: this is done at a jar level, not at the level of an individual mixin config.
|
|
||||||
// If a mod has multiple mixin configs, it's assumed that either all or none of them have refmaps.
|
|
||||||
if (info.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.STATIC) {
|
|
||||||
if (!requiresStaticMixinRemap) {
|
|
||||||
// Should be impossible but stranger things have happened.
|
|
||||||
throw new IllegalStateException("Was not configured for static remap, but a mod required it?!");
|
|
||||||
}
|
|
||||||
|
|
||||||
project.getLogger().info("Remapping mixins in {} statically", info.getInputFile());
|
|
||||||
remapMixins.add(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
remapper.readInputsAsync(tag, info.getInputFile());
|
remapper.readInputsAsync(tag, info.getInputFile());
|
||||||
tagMap.put(info, tag);
|
|
||||||
|
|
||||||
Files.deleteIfExists(getRemappedOutput(info));
|
Files.deleteIfExists(getRemappedOutput(info));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,12 +258,12 @@ public class ModProcessor {
|
|||||||
final AccessWidenerUtils.AccessWidenerData accessWidenerData = AccessWidenerUtils.readAccessWidenerData(dependency.getInputFile(), platform);
|
final AccessWidenerUtils.AccessWidenerData accessWidenerData = AccessWidenerUtils.readAccessWidenerData(dependency.getInputFile(), platform);
|
||||||
|
|
||||||
if (accessWidenerData != null) {
|
if (accessWidenerData != null) {
|
||||||
project.getLogger().debug("Remapping access widener in {}", dependency.getInputFile());
|
LOGGER.debug("Remapping access widener in {}", dependency.getInputFile());
|
||||||
byte[] remappedAw = AccessWidenerUtils.remapAccessWidener(accessWidenerData.content(), remapper.getEnvironment().getRemapper());
|
byte[] remappedAw = AccessWidenerUtils.remapAccessWidener(accessWidenerData.content(), remapper.getEnvironment().getRemapper());
|
||||||
accessWidenerMap.put(dependency, new Pair<>(remappedAw, accessWidenerData.path()));
|
accessWidenerMap.put(dependency, new Pair<>(remappedAw, accessWidenerData.path()));
|
||||||
}
|
}
|
||||||
|
|
||||||
remapper.apply(outputConsumer, tagMap.get(dependency));
|
remapper.apply(outputConsumer, inputTags.getByValue(dependency));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to remap: " + dependency, e);
|
throw new RuntimeException("Failed to remap: " + dependency, e);
|
||||||
}
|
}
|
||||||
@@ -288,6 +288,12 @@ public class ModProcessor {
|
|||||||
ZipUtils.replace(output, accessWidener.right(), accessWidener.left());
|
ZipUtils.replace(output, accessWidener.right(), accessWidener.left());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (ModProcessorExtension modProcessorExtension : activeExtensions) {
|
||||||
|
if (modProcessorExtension.appliesTo(dependency)) {
|
||||||
|
modProcessorExtension.finalise(dependency, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stripNestedJars(output);
|
stripNestedJars(output);
|
||||||
remapJarManifestEntries(output);
|
remapJarManifestEntries(output);
|
||||||
|
|
||||||
@@ -307,8 +313,8 @@ public class ModProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path getRemappedOutput(ModDependency dependency) {
|
private Path getRemappedOutput(ModDependency dependency) {
|
||||||
return dependency.getWorkingFile(null);
|
return dependency.getWorkingFile(project, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void remapJarManifestEntries(Path jar) throws IOException {
|
private void remapJarManifestEntries(Path jar) throws IOException {
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ import java.nio.file.StandardCopyOption;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public record LocalMavenHelper(String group, String name, String version, @Nullable String baseClassifier, Path root) {
|
public record LocalMavenHelper(String group, String name, String version, @Nullable String baseClassifier, Path root, @Nullable String snapshotVersion) {
|
||||||
|
public LocalMavenHelper(String group, String name, String version, @Nullable String baseClassifier, Path root) {
|
||||||
|
this(group, name, version, baseClassifier, root, null);
|
||||||
|
}
|
||||||
|
|
||||||
public Path copyToMaven(Path artifact, @Nullable String classifier) throws IOException {
|
public Path copyToMaven(Path artifact, @Nullable String classifier) throws IOException {
|
||||||
if (!artifact.getFileName().toString().endsWith(".jar")) {
|
if (!artifact.getFileName().toString().endsWith(".jar")) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
@@ -77,6 +81,13 @@ public record LocalMavenHelper(String group, String name, String version, @Nulla
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Path getDirectory() {
|
private Path getDirectory() {
|
||||||
|
String version = this.version();
|
||||||
|
|
||||||
|
// When using a specific snapshot version the directory name should be the 1.0.0-SNAPSHOT version
|
||||||
|
if (this.snapshotVersion() != null) {
|
||||||
|
version = this.snapshotVersion();
|
||||||
|
}
|
||||||
|
|
||||||
return root.resolve("%s/%s/%s".formatted(group.replace(".", "/"), name, version));
|
return root.resolve("%s/%s/%s".formatted(group.replace(".", "/"), name, version));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.api.artifacts.component.ComponentIdentifier;
|
||||||
|
import org.gradle.api.internal.artifacts.repositories.resolver.MavenUniqueSnapshotComponentIdentifier;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
@@ -37,23 +39,21 @@ import net.fabricmc.loom.configuration.mods.ArtifactRef;
|
|||||||
public abstract sealed class ModDependency permits SplitModDependency, SimpleModDependency {
|
public abstract sealed class ModDependency permits SplitModDependency, SimpleModDependency {
|
||||||
private final ArtifactRef artifact;
|
private final ArtifactRef artifact;
|
||||||
private final ArtifactMetadata metadata;
|
private final ArtifactMetadata metadata;
|
||||||
protected final String group;
|
private final String group;
|
||||||
protected final String name;
|
private final String name;
|
||||||
protected final String version;
|
private final String version;
|
||||||
@Nullable
|
@Nullable
|
||||||
protected final String classifier;
|
private final String classifier;
|
||||||
protected final String mappingsSuffix;
|
private final ModDependencyOptions options;
|
||||||
protected final Project project;
|
|
||||||
|
|
||||||
public ModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Project project) {
|
public ModDependency(ArtifactRef artifact, ArtifactMetadata metadata, ModDependencyOptions options) {
|
||||||
this.artifact = artifact;
|
this.artifact = artifact;
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
this.group = artifact.group();
|
this.group = artifact.group();
|
||||||
this.name = artifact.name();
|
this.name = artifact.name();
|
||||||
this.version = artifact.version();
|
this.version = artifact.version();
|
||||||
this.classifier = artifact.classifier();
|
this.classifier = artifact.classifier();
|
||||||
this.mappingsSuffix = mappingsSuffix;
|
this.options = options;
|
||||||
this.project = project;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,10 +71,27 @@ public abstract sealed class ModDependency permits SplitModDependency, SimpleMod
|
|||||||
*/
|
*/
|
||||||
public abstract void applyToProject(Project project);
|
public abstract void applyToProject(Project project);
|
||||||
|
|
||||||
protected LocalMavenHelper createMaven(String name) {
|
/**
|
||||||
|
* Create a maven helper for the local cache.
|
||||||
|
* @param type The jar type, e.g "common" or "client" for split dependencies.
|
||||||
|
*/
|
||||||
|
protected LocalMavenHelper createMavenHelper(Project project, @Nullable String type) {
|
||||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
final Path root = extension.getFiles().getRemappedModCache().toPath();
|
final Path root = extension.getFiles().getRemappedModCache().toPath();
|
||||||
return new LocalMavenHelper(getRemappedGroup(), name, this.version, this.classifier, root);
|
final String fullName = getName() + (type != null ? "-" + type : "");
|
||||||
|
return new LocalMavenHelper(getGroup(), fullName, this.version, this.classifier, root, getSnapshotVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable String getSnapshotVersion() {
|
||||||
|
if (artifact instanceof ArtifactRef.ResolvedArtifactRef resolvedArtifactRef) {
|
||||||
|
ComponentIdentifier componentIdentifier = resolvedArtifactRef.artifact().getId().getComponentIdentifier();
|
||||||
|
|
||||||
|
if (componentIdentifier instanceof MavenUniqueSnapshotComponentIdentifier mavenUniqueId) {
|
||||||
|
return mavenUniqueId.getSnapshotVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArtifactRef getInputArtifact() {
|
public ArtifactRef getInputArtifact() {
|
||||||
@@ -85,26 +102,34 @@ public abstract sealed class ModDependency permits SplitModDependency, SimpleMod
|
|||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getRemappedGroup() {
|
protected String getName() {
|
||||||
return getMappingsPrefix() + "." + group;
|
return "%s-%s".formatted(name, options.getCacheKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMappingsPrefix() {
|
protected String getGroup() {
|
||||||
return mappingsSuffix.replace(".", "_").replace("-", "_").replace("+", "_");
|
return "remapped.%s".formatted(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getVersion() {
|
||||||
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getInputFile() {
|
public Path getInputFile() {
|
||||||
return artifact.path();
|
return artifact.path();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getWorkingFile(@Nullable String classifier) {
|
public Path getWorkingFile(Project project, @Nullable String classifier) {
|
||||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
final String fileName = classifier == null ? String.format("%s-%s-%s.jar", getRemappedGroup(), name, version)
|
final String fileName = classifier == null ? String.format("%s-%s-%s.jar", getGroup(), getName(), version)
|
||||||
: String.format("%s-%s-%s-%s.jar", getRemappedGroup(), name, version, classifier);
|
: String.format("%s-%s-%s-%s.jar", getGroup(), getName(), version, classifier);
|
||||||
|
|
||||||
return extension.getFiles().getProjectBuildCache().toPath().resolve("remapped_working").resolve(fileName);
|
return extension.getFiles().getProjectBuildCache().toPath().resolve("remapped_working").resolve(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ModDependencyOptions getOptions() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ModDependency{" + "group='" + group + '\'' + ", name='" + name + '\'' + ", version='" + version + '\'' + ", classifier='" + classifier + '\'' + '}';
|
return "ModDependency{" + "group='" + group + '\'' + ", name='" + name + '\'' + ", version='" + version + '\'' + ", classifier='" + classifier + '\'' + '}';
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import net.fabricmc.loom.util.AttributeHelper;
|
|||||||
public class ModDependencyFactory {
|
public class ModDependencyFactory {
|
||||||
private static final String TARGET_ATTRIBUTE_KEY = "loom-target";
|
private static final String TARGET_ATTRIBUTE_KEY = "loom-target";
|
||||||
|
|
||||||
public static ModDependency create(ArtifactRef artifact, ArtifactMetadata metadata, Configuration targetConfig, @Nullable Configuration targetClientConfig, String mappingsSuffix, Project project) {
|
public static ModDependency create(ArtifactRef artifact, ArtifactMetadata metadata, Configuration targetConfig, @Nullable Configuration targetClientConfig, ModDependencyOptions options, Project project) {
|
||||||
if (targetClientConfig != null && LoomGradleExtension.get(project).getSplitModDependencies().get()) {
|
if (targetClientConfig != null && LoomGradleExtension.get(project).getSplitModDependencies().get()) {
|
||||||
final Optional<JarSplitter.Target> cachedTarget = readTarget(artifact);
|
final Optional<JarSplitter.Target> cachedTarget = readTarget(artifact);
|
||||||
JarSplitter.Target target;
|
JarSplitter.Target target;
|
||||||
@@ -54,11 +54,11 @@ public class ModDependencyFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
return new SplitModDependency(artifact, metadata, mappingsSuffix, targetConfig, targetClientConfig, target, project);
|
return new SplitModDependency(artifact, metadata, options, targetConfig, targetClientConfig, target, project);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SimpleModDependency(artifact, metadata, mappingsSuffix, targetConfig, project);
|
return new SimpleModDependency(artifact, metadata, options, targetConfig, project);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Optional<JarSplitter.Target> readTarget(ArtifactRef artifact) {
|
private static Optional<JarSplitter.Target> readTarget(ArtifactRef artifact) {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.dependency;
|
||||||
|
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.CacheKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inputs used to process a mod dependency. The output jar is cached based on these properties.
|
||||||
|
*/
|
||||||
|
public abstract class ModDependencyOptions extends CacheKey {
|
||||||
|
public abstract Property<String> getMappings();
|
||||||
|
|
||||||
|
public abstract Property<Boolean> getInlineRefmap();
|
||||||
|
}
|
||||||
@@ -40,10 +40,10 @@ public final class SimpleModDependency extends ModDependency {
|
|||||||
private final Configuration targetConfig;
|
private final Configuration targetConfig;
|
||||||
private final LocalMavenHelper maven;
|
private final LocalMavenHelper maven;
|
||||||
|
|
||||||
public SimpleModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Configuration targetConfig, Project project) {
|
public SimpleModDependency(ArtifactRef artifact, ArtifactMetadata metadata, ModDependencyOptions options, Configuration targetConfig, Project project) {
|
||||||
super(artifact, metadata, mappingsSuffix, project);
|
super(artifact, metadata, options);
|
||||||
this.targetConfig = Objects.requireNonNull(targetConfig);
|
this.targetConfig = Objects.requireNonNull(targetConfig);
|
||||||
this.maven = createMaven(name);
|
this.maven = createMavenHelper(project, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -48,13 +48,13 @@ public final class SplitModDependency extends ModDependency {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private final LocalMavenHelper clientMaven;
|
private final LocalMavenHelper clientMaven;
|
||||||
|
|
||||||
public SplitModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Configuration targetCommonConfig, Configuration targetClientConfig, JarSplitter.Target target, Project project) {
|
public SplitModDependency(ArtifactRef artifact, ArtifactMetadata metadata, ModDependencyOptions options, Configuration targetCommonConfig, Configuration targetClientConfig, JarSplitter.Target target, Project project) {
|
||||||
super(artifact, metadata, mappingsSuffix, project);
|
super(artifact, metadata, options);
|
||||||
this.targetCommonConfig = Objects.requireNonNull(targetCommonConfig);
|
this.targetCommonConfig = Objects.requireNonNull(targetCommonConfig);
|
||||||
this.targetClientConfig = Objects.requireNonNull(targetClientConfig);
|
this.targetClientConfig = Objects.requireNonNull(targetClientConfig);
|
||||||
this.target = Objects.requireNonNull(target);
|
this.target = Objects.requireNonNull(target);
|
||||||
this.commonMaven = target.common() ? createMaven(name + "-common") : null;
|
this.commonMaven = target.common() ? createMavenHelper(project, "common") : null;
|
||||||
this.clientMaven = target.client() ? createMaven(name + "-client") : null;
|
this.clientMaven = target.client() ? createMavenHelper(project, "client") : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -86,8 +86,8 @@ public final class SplitModDependency extends ModDependency {
|
|||||||
// Split the jar into 2
|
// Split the jar into 2
|
||||||
case SPLIT -> {
|
case SPLIT -> {
|
||||||
final String suffix = variant == null ? "" : "-" + variant;
|
final String suffix = variant == null ? "" : "-" + variant;
|
||||||
final Path commonTempJar = getWorkingFile("common" + suffix);
|
final Path commonTempJar = getWorkingFile(project, "common" + suffix);
|
||||||
final Path clientTempJar = getWorkingFile("client" + suffix);
|
final Path clientTempJar = getWorkingFile(project, "client" + suffix);
|
||||||
|
|
||||||
final JarSplitter splitter = new JarSplitter(path);
|
final JarSplitter splitter = new JarSplitter(path);
|
||||||
splitter.split(commonTempJar, clientTempJar);
|
splitter.split(commonTempJar, clientTempJar);
|
||||||
@@ -114,15 +114,16 @@ public final class SplitModDependency extends ModDependency {
|
|||||||
|
|
||||||
if (target == JarSplitter.Target.SPLIT) {
|
if (target == JarSplitter.Target.SPLIT) {
|
||||||
createModGroup(
|
createModGroup(
|
||||||
|
project,
|
||||||
getCommonMaven().getOutputFile(null),
|
getCommonMaven().getOutputFile(null),
|
||||||
getClientMaven().getOutputFile(null)
|
getClientMaven().getOutputFile(null)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createModGroup(Path commonJar, Path clientJar) {
|
private void createModGroup(Project project, Path commonJar, Path clientJar) {
|
||||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
final ModSettings modSettings = extension.getMods().maybeCreate(String.format("%s-%s-%s", getRemappedGroup(), name, version));
|
final ModSettings modSettings = extension.getMods().maybeCreate(String.format("%s-%s-%s", getGroup(), getName(), getVersion()));
|
||||||
modSettings.getModFiles().from(
|
modSettings.getModFiles().from(
|
||||||
commonJar.toFile(),
|
commonJar.toFile(),
|
||||||
clientJar.toFile()
|
clientJar.toFile()
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.dependency.refmap;
|
||||||
|
|
||||||
|
public interface MixinReferenceRemapper {
|
||||||
|
String remapReference(String mixinClassName, String reference);
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.dependency.refmap;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.fmj.mixin.MixinRefmap;
|
||||||
|
|
||||||
|
public record MixinReferenceRemapperImpl(Map<String, MixinRefmap.ReferenceMappingData> data) implements MixinReferenceRemapper {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(MixinReferenceRemapperImpl.class);
|
||||||
|
|
||||||
|
public static MixinReferenceRemapper createFromRefmaps(String from, String to, Stream<MixinRefmap> refmaps) {
|
||||||
|
MixinRefmap.NamespacePair namespaces = new MixinRefmap.NamespacePair(from, to);
|
||||||
|
|
||||||
|
Map<String, MixinRefmap.ReferenceMappingData> data = refmaps
|
||||||
|
.map(refmap -> refmap.getData(namespaces))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(MixinRefmap.MixinMappingData::data)
|
||||||
|
.flatMap(map -> map.entrySet().stream())
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
|
||||||
|
(existing, replacement) -> {
|
||||||
|
// TODO we could merge this, but it should never happen in practice
|
||||||
|
LOGGER.warn("Duplicate mixin reference mapping for {} in refmaps, using the first one", existing);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
return new MixinReferenceRemapperImpl(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String remapReference(String mixinClassName, String reference) {
|
||||||
|
final MixinRefmap.ReferenceMappingData data = data().get(mixinClassName);
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
return data.remap(reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.dependency.refmap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.configuration.mods.ArtifactMetadata;
|
||||||
|
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
|
||||||
|
import net.fabricmc.loom.util.ExceptionUtil;
|
||||||
|
import net.fabricmc.loom.util.fmj.FabricModJson;
|
||||||
|
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
|
||||||
|
import net.fabricmc.loom.util.fmj.mixin.MixinConfiguration;
|
||||||
|
|
||||||
|
public class MixinRefmapInliner {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(MixinRefmapInliner.class);
|
||||||
|
|
||||||
|
public static MixinReferenceRemapper createRemapper(String from, String to, List<ModDependency> mods) throws IOException {
|
||||||
|
List<MixinConfiguration> mixinConfigurations = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ModDependency mod : mods) {
|
||||||
|
if (mod.getMetadata().mixinRemapType() != ArtifactMetadata.MixinRemapType.MIXIN) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FabricModJson fabricModJson = FabricModJsonFactory.createFromZipNullable(mod.getInputFile());
|
||||||
|
|
||||||
|
if (fabricModJson == null) {
|
||||||
|
LOGGER.warn("Failed to read fabric.mod.json from {}", mod.getInputFile());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mixinConfigurations.addAll(MixinConfiguration.fromMod(fabricModJson));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw ExceptionUtil.createDescriptiveWrapper(IOException::new, "Failed to read mixin configuration from " + mod.getInputFile(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MixinReferenceRemapperImpl.createFromRefmaps(from, to, mixinConfigurations.stream().map(MixinConfiguration::refmap));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeRefmap(ModDependency modDependency, Path ouputPath) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.dependency.refmap;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
|
||||||
|
import net.fabricmc.tinyremapper.InputTag;
|
||||||
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
|
import net.fabricmc.tinyremapper.api.TrClass;
|
||||||
|
|
||||||
|
public record MixinRefmapInlinerApplyVisitorProvider(
|
||||||
|
MixinReferenceRemapper remapper,
|
||||||
|
// A set of input tags that do NOT need their refmaps inlined
|
||||||
|
Predicate<InputTag> staticRemappedMixins) implements TinyRemapper.ApplyVisitorProvider, TinyRemapper.Extension {
|
||||||
|
@Override
|
||||||
|
public ClassVisitor insertApplyVisitor(TrClass cls, ClassVisitor next) {
|
||||||
|
return new MixinRefmapInlinerClassVisitor(remapper, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClassVisitor insertApplyVisitor(TrClass cls, ClassVisitor next, InputTag[] inputTags) {
|
||||||
|
for (InputTag tag : inputTags) {
|
||||||
|
if (staticRemappedMixins.test(tag)) {
|
||||||
|
// No need to inline the refmaps for this tag, as we know this was originally a statically remapped mixin with no refmap
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MixinRefmapInlinerClassVisitor(remapper, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attach(TinyRemapper.Builder builder) {
|
||||||
|
builder.extraPreApplyVisitor(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.dependency.refmap;
|
||||||
|
|
||||||
|
import org.objectweb.asm.AnnotationVisitor;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.Constants;
|
||||||
|
|
||||||
|
public class MixinRefmapInlinerClassVisitor extends ClassVisitor {
|
||||||
|
private final MixinReferenceRemapper remapper;
|
||||||
|
|
||||||
|
private String className = null;
|
||||||
|
|
||||||
|
public MixinRefmapInlinerClassVisitor(MixinReferenceRemapper remapper, ClassVisitor classVisitor) {
|
||||||
|
super(Constants.ASM_VERSION, classVisitor);
|
||||||
|
this.remapper = remapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||||
|
this.className = name;
|
||||||
|
super.visit(version, access, name, signature, superName, interfaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||||
|
AnnotationVisitor annotationVisitor = super.visitAnnotation(descriptor, visible);
|
||||||
|
return new RefmapInlinerAnnotationVisitor(annotationVisitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||||
|
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
|
return new RefmapInlinerMethodVisitor(methodVisitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RefmapInlinerMethodVisitor extends MethodVisitor {
|
||||||
|
private RefmapInlinerMethodVisitor(MethodVisitor methodVisitor) {
|
||||||
|
super(MixinRefmapInlinerClassVisitor.super.api, methodVisitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||||
|
AnnotationVisitor annotationVisitor = super.visitAnnotation(descriptor, visible);
|
||||||
|
return new RefmapInlinerAnnotationVisitor(annotationVisitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RefmapInlinerAnnotationVisitor extends AnnotationVisitor {
|
||||||
|
private RefmapInlinerAnnotationVisitor(AnnotationVisitor annotationVisitor) {
|
||||||
|
super(MixinRefmapInlinerClassVisitor.super.api, annotationVisitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(String name, Object value) {
|
||||||
|
if (value instanceof String strValue) {
|
||||||
|
value = remapper.remapReference(className, strValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visit(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitArray(String name) {
|
||||||
|
AnnotationVisitor annotationVisitor = super.visitArray(name);
|
||||||
|
return new RefmapInlinerAnnotationVisitor(annotationVisitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
|
||||||
|
AnnotationVisitor annotationVisitor = super.visitAnnotation(name, descriptor);
|
||||||
|
return new RefmapInlinerAnnotationVisitor(annotationVisitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.extension;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.configuration.mods.ArtifactMetadata;
|
||||||
|
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
|
||||||
|
import net.fabricmc.loom.configuration.mods.dependency.refmap.MixinReferenceRemapper;
|
||||||
|
import net.fabricmc.loom.configuration.mods.dependency.refmap.MixinRefmapInliner;
|
||||||
|
import net.fabricmc.loom.configuration.mods.dependency.refmap.MixinRefmapInlinerApplyVisitorProvider;
|
||||||
|
import net.fabricmc.tinyremapper.InputTag;
|
||||||
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
|
|
||||||
|
final class InlineRefmap implements ModProcessorExtension {
|
||||||
|
static final InlineRefmap INSTANCE = new InlineRefmap();
|
||||||
|
|
||||||
|
private InlineRefmap() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean appliesTo(ModDependency modDependency) {
|
||||||
|
return modDependency.getOptions().getInlineRefmap().get()
|
||||||
|
&& modDependency.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.MIXIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TinyRemapper.Extension createExtension(Context ctx, Predicate<InputTag> applyPredicate) throws IOException {
|
||||||
|
MixinReferenceRemapper refmapRemapper = MixinRefmapInliner.createRemapper(ctx.from(), ctx.to(), ctx.mods());
|
||||||
|
return new MixinRefmapInlinerApplyVisitorProvider(refmapRemapper, applyPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finalise(ModDependency modDependency, Path path) throws IOException {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.extension;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.configuration.mods.ArtifactMetadata;
|
||||||
|
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
|
||||||
|
import net.fabricmc.loom.configuration.mods.dependency.refmap.MixinRefmapInliner;
|
||||||
|
import net.fabricmc.tinyremapper.InputTag;
|
||||||
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
|
import net.fabricmc.tinyremapper.extension.mixin.MixinExtension;
|
||||||
|
|
||||||
|
final class MixinRemap implements ModProcessorExtension {
|
||||||
|
static final MixinRemap INSTANCE = new MixinRemap();
|
||||||
|
|
||||||
|
private MixinRemap() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean appliesTo(ModDependency modDependency) {
|
||||||
|
return modDependency.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.STATIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TinyRemapper.Extension createExtension(Context ctx, Predicate<InputTag> applyPredicate) {
|
||||||
|
return new MixinExtension(applyPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finalise(ModDependency modDependency, Path path) throws IOException {
|
||||||
|
MixinRefmapInliner.removeRefmap(modDependency, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.extension;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
|
||||||
|
import net.fabricmc.tinyremapper.InputTag;
|
||||||
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface to aid with applying mod-specific remapping extensions.
|
||||||
|
*/
|
||||||
|
public interface ModProcessorExtension {
|
||||||
|
List<ModProcessorExtension> EXTENSIONS = List.of(
|
||||||
|
MixinRemap.INSTANCE,
|
||||||
|
InlineRefmap.INSTANCE
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the extension applies to the given mod dependency.
|
||||||
|
*/
|
||||||
|
boolean appliesTo(ModDependency modDependency);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a TinyRemapper extension that uses the predicate to only apply to mods that match appliesTo.
|
||||||
|
*/
|
||||||
|
TinyRemapper.Extension createExtension(Context ctx, Predicate<InputTag> applyPredicate) throws IOException;
|
||||||
|
|
||||||
|
void finalise(ModDependency modDependency, Path path) throws IOException;
|
||||||
|
|
||||||
|
record Context(
|
||||||
|
String from,
|
||||||
|
String to,
|
||||||
|
List<ModDependency> mods) { }
|
||||||
|
}
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
package net.fabricmc.loom.configuration.processors;
|
package net.fabricmc.loom.configuration.processors;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -113,11 +112,11 @@ public final class MinecraftJarProcessorManager {
|
|||||||
|
|
||||||
public String getJarHash() {
|
public String getJarHash() {
|
||||||
//fabric-loom:mod-javadoc:-1289977000
|
//fabric-loom:mod-javadoc:-1289977000
|
||||||
return Checksum.sha1Hex(getCacheValue().getBytes(StandardCharsets.UTF_8)).substring(0, 10);
|
return Checksum.of(getCacheValue()).sha1().hex(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSourceMappingsHash() {
|
public String getSourceMappingsHash() {
|
||||||
return Checksum.sha1Hex(getCacheValue().getBytes(StandardCharsets.UTF_8));
|
return Checksum.of(getCacheValue()).sha1().hex();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean requiresProcessingJar(Path jar) {
|
public boolean requiresProcessingJar(Path jar) {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public abstract class ModJavadocProcessor implements MinecraftJarProcessor<ModJa
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final byte[] data = fabricModJson.getSource().read(javaDocPath);
|
final byte[] data = fabricModJson.getSource().read(javaDocPath);
|
||||||
mappingsHash = Checksum.sha1Hex(data);
|
mappingsHash = Checksum.of(data).sha1().hex();
|
||||||
|
|
||||||
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(data))) {
|
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(data))) {
|
||||||
MappingReader.read(reader, mappings);
|
MappingReader.read(reader, mappings);
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.api.RemapConfigurationSettings;
|
import net.fabricmc.loom.api.RemapConfigurationSettings;
|
||||||
import net.fabricmc.loom.api.processor.SpecContext;
|
import net.fabricmc.loom.api.processor.SpecContext;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.fmj.FabricModJson;
|
import net.fabricmc.loom.util.fmj.FabricModJson;
|
||||||
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
|
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
|
||||||
@@ -59,10 +60,17 @@ import net.fabricmc.loom.util.gradle.GradleUtils;
|
|||||||
* @param localMods Mods found in the current project.
|
* @param localMods Mods found in the current project.
|
||||||
* @param compileRuntimeMods Dependent mods found in both the compile and runtime classpath.
|
* @param compileRuntimeMods Dependent mods found in both the compile and runtime classpath.
|
||||||
*/
|
*/
|
||||||
public record SpecContextImpl(List<FabricModJson> modDependencies, List<FabricModJson> localMods, List<FabricModJson> compileRuntimeMods) implements SpecContext {
|
public record SpecContextImpl(
|
||||||
|
List<FabricModJson> modDependencies,
|
||||||
|
List<FabricModJson> localMods,
|
||||||
|
List<ModHolder> compileRuntimeMods) implements SpecContext {
|
||||||
public static SpecContextImpl create(Project project) {
|
public static SpecContextImpl create(Project project) {
|
||||||
final Map<String, List<FabricModJson>> fmjCache = new HashMap<>();
|
final Map<String, List<FabricModJson>> fmjCache = new HashMap<>();
|
||||||
return new SpecContextImpl(getDependentMods(project, fmjCache), FabricModJsonHelpers.getModsInProject(project), getCompileRuntimeMods(project, fmjCache));
|
return new SpecContextImpl(
|
||||||
|
getDependentMods(project, fmjCache),
|
||||||
|
FabricModJsonHelpers.getModsInProject(project),
|
||||||
|
getCompileRuntimeMods(project, fmjCache)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reruns a list of mods found on both the compile and/or runtime classpaths
|
// Reruns a list of mods found on both the compile and/or runtime classpaths
|
||||||
@@ -108,39 +116,68 @@ public record SpecContextImpl(List<FabricModJson> modDependencies, List<FabricMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns a list of mods that are on both to compile and runtime classpath
|
// Returns a list of mods that are on both to compile and runtime classpath
|
||||||
private static List<FabricModJson> getCompileRuntimeMods(Project project, Map<String, List<FabricModJson>> fmjCache) {
|
private static List<ModHolder> getCompileRuntimeMods(Project project, Map<String, List<FabricModJson>> fmjCache) {
|
||||||
var mods = new ArrayList<>(getCompileRuntimeModsFromRemapConfigs(project, fmjCache).toList());
|
var mods = new ArrayList<>(getCompileRuntimeModsFromRemapConfigs(project, fmjCache));
|
||||||
|
|
||||||
for (Project dependentProject : getCompileRuntimeProjectDependencies(project).toList()) {
|
for (Project dependentProject : getCompileRuntimeProjectDependencies(project).toList()) {
|
||||||
mods.addAll(fmjCache.computeIfAbsent(dependentProject.getPath(), $ -> {
|
List<FabricModJson> projectMods = fmjCache.computeIfAbsent(dependentProject.getPath(), $ -> {
|
||||||
return FabricModJsonHelpers.getModsInProject(dependentProject);
|
return FabricModJsonHelpers.getModsInProject(dependentProject);
|
||||||
}));
|
});
|
||||||
|
|
||||||
|
for (FabricModJson mod : projectMods) {
|
||||||
|
mods.add(new ModHolder(mod));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableList(mods);
|
return Collections.unmodifiableList(mods);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a list of jar mods that are found on the compile and runtime remapping configurations
|
// Returns a list of jar mods that are found on the compile and runtime remapping configurations
|
||||||
private static Stream<FabricModJson> getCompileRuntimeModsFromRemapConfigs(Project project, Map<String, List<FabricModJson>> fmjCache) {
|
private static List<ModHolder> getCompileRuntimeModsFromRemapConfigs(Project project, Map<String, List<FabricModJson>> fmjCache) {
|
||||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
final Set<String> runtimeModIds = extension.getRuntimeRemapConfigurations().stream()
|
|
||||||
.filter(settings -> settings.getApplyDependencyTransforms().get())
|
|
||||||
.flatMap(resolveArtifacts(project, true))
|
|
||||||
.map(modFromZip(fmjCache))
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.map(FabricModJson::getId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
return extension.getCompileRemapConfigurations().stream()
|
// A set of mod ids from all remap configurations that are considered for dependency transforms.
|
||||||
.filter(settings -> settings.getApplyDependencyTransforms().get())
|
final Set<String> runtimeModIds = getModIds(
|
||||||
.flatMap(resolveArtifacts(project, false))// Use the intersection of the two configurations.
|
project,
|
||||||
.map(modFromZip(fmjCache))
|
fmjCache,
|
||||||
.filter(Objects::nonNull)
|
extension.getRuntimeRemapConfigurations().stream()
|
||||||
|
.filter(settings -> settings.getApplyDependencyTransforms().get())
|
||||||
|
);
|
||||||
|
|
||||||
|
// A set of mod ids that are found on one or more remap configurations that target the common source set.
|
||||||
|
// Null when split source sets are not enabled, meaning all mods are common.
|
||||||
|
final Set<String> commonModIds = extension.areEnvironmentSourceSetsSplit() ? getModIds(
|
||||||
|
project,
|
||||||
|
fmjCache,
|
||||||
|
extension.getRuntimeRemapConfigurations().stream()
|
||||||
|
.filter(settings -> settings.getSourceSet().map(sourceSet -> !sourceSet.getName().equals(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME)).get())
|
||||||
|
.filter(settings -> settings.getApplyDependencyTransforms().get()))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return getMods(
|
||||||
|
project,
|
||||||
|
fmjCache,
|
||||||
|
extension.getCompileRemapConfigurations().stream()
|
||||||
|
.filter(settings -> settings.getApplyDependencyTransforms().get()))
|
||||||
// Only check based on the modid, as there may be differing versions used between the compile and runtime classpath.
|
// Only check based on the modid, as there may be differing versions used between the compile and runtime classpath.
|
||||||
// We assume that the version used at runtime will be binary compatible with the version used to compile against.
|
// We assume that the version used at runtime will be binary compatible with the version used to compile against.
|
||||||
// It's not perfect but better than silently not supplying the mod, and this could happen with regular API that you compile against anyway.
|
// It's not perfect but better than silently not supplying the mod, and this could happen with regular API that you compile against anyway.
|
||||||
.filter(fabricModJson -> runtimeModIds.contains(fabricModJson.getId()))
|
.filter(fabricModJson -> runtimeModIds.contains(fabricModJson.getId()))
|
||||||
.sorted(Comparator.comparing(FabricModJson::getId));
|
.sorted(Comparator.comparing(FabricModJson::getId))
|
||||||
|
.map(fabricModJson -> new ModHolder(fabricModJson, commonModIds == null || commonModIds.contains(fabricModJson.getId())))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<FabricModJson> getMods(Project project, Map<String, List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
|
||||||
|
return stream.flatMap(resolveArtifacts(project, true))
|
||||||
|
.map(modFromZip(fmjCache))
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> getModIds(Project project, Map<String, List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
|
||||||
|
return getMods(project, fmjCache, stream)
|
||||||
|
.map(FabricModJson::getId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Function<Path, @Nullable FabricModJson> modFromZip(Map<String, List<FabricModJson>> fmjCache) {
|
private static Function<Path, @Nullable FabricModJson> modFromZip(Map<String, List<FabricModJson>> fmjCache) {
|
||||||
@@ -190,6 +227,22 @@ public record SpecContextImpl(List<FabricModJson> modDependencies, List<FabricMo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<FabricModJson> modDependenciesCompileRuntime() {
|
public List<FabricModJson> modDependenciesCompileRuntime() {
|
||||||
return compileRuntimeMods;
|
return compileRuntimeMods.stream()
|
||||||
|
.map(ModHolder::mod)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FabricModJson> modDependenciesCompileRuntimeClient() {
|
||||||
|
return compileRuntimeMods.stream()
|
||||||
|
.filter(modHolder -> !modHolder.common())
|
||||||
|
.map(ModHolder::mod)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ModHolder(FabricModJson mod, boolean common) {
|
||||||
|
ModHolder(FabricModJson mod) {
|
||||||
|
this(mod, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
*
|
*
|
||||||
* Copyright (c) 2018-2021 FabricMC
|
* Copyright (c) 2018-2025 FabricMC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -41,6 +41,7 @@ import net.fabricmc.loom.LoomGradleExtension;
|
|||||||
import net.fabricmc.loom.api.mappings.layered.MappingContext;
|
import net.fabricmc.loom.api.mappings.layered.MappingContext;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||||
import net.fabricmc.loom.util.download.DownloadBuilder;
|
import net.fabricmc.loom.util.download.DownloadBuilder;
|
||||||
|
import net.fabricmc.loom.util.gradle.GradleUtils;
|
||||||
import net.fabricmc.loom.util.service.ScopedServiceFactory;
|
import net.fabricmc.loom.util.service.ScopedServiceFactory;
|
||||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||||
|
|
||||||
@@ -85,6 +86,11 @@ public class GradleMappingContext implements MappingContext {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUsingIntermediateMappings() {
|
||||||
|
return !(extension.getIntermediateMappingsProvider() instanceof NoOpIntermediateMappingsProvider);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MinecraftProvider minecraftProvider() {
|
public MinecraftProvider minecraftProvider() {
|
||||||
return extension.getMinecraftProvider();
|
return extension.getMinecraftProvider();
|
||||||
@@ -110,6 +116,11 @@ public class GradleMappingContext implements MappingContext {
|
|||||||
return extension.refreshDeps();
|
return extension.refreshDeps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasProperty(String property) {
|
||||||
|
return GradleUtils.getBooleanProperty(project, property);
|
||||||
|
}
|
||||||
|
|
||||||
public Project getProject() {
|
public Project getProject() {
|
||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
package net.fabricmc.loom.configuration.providers.mappings;
|
package net.fabricmc.loom.configuration.providers.mappings;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
@@ -115,7 +114,7 @@ public abstract class IntermediaryMappingsProvider extends IntermediateMappingsP
|
|||||||
if (!LoomGradleExtensionApiImpl.DEFAULT_INTERMEDIARY_URL.equals(urlRaw)) {
|
if (!LoomGradleExtensionApiImpl.DEFAULT_INTERMEDIARY_URL.equals(urlRaw)) {
|
||||||
final String url = getIntermediaryUrl().get().formatted(encodedMcVersion);
|
final String url = getIntermediaryUrl().get().formatted(encodedMcVersion);
|
||||||
|
|
||||||
return NAME + "-" + Checksum.sha1Hex(url.getBytes(StandardCharsets.UTF_8));
|
return NAME + "-" + Checksum.of(url).sha1().hex();
|
||||||
}
|
}
|
||||||
|
|
||||||
return NAME;
|
return NAME;
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
|||||||
import net.fabricmc.loom.configuration.ConfigContext;
|
import net.fabricmc.loom.configuration.ConfigContext;
|
||||||
import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper;
|
import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.extras.unpick.UnpickLayer;
|
import net.fabricmc.loom.configuration.providers.mappings.extras.unpick.UnpickLayer;
|
||||||
|
import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.utils.AddConstructorMappingVisitor;
|
import net.fabricmc.loom.configuration.providers.mappings.utils.AddConstructorMappingVisitor;
|
||||||
import net.fabricmc.loom.util.ZipUtils;
|
import net.fabricmc.loom.util.ZipUtils;
|
||||||
import net.fabricmc.mappingio.adapter.MappingDstNsReorder;
|
import net.fabricmc.mappingio.adapter.MappingDstNsReorder;
|
||||||
@@ -148,7 +149,7 @@ public record LayeredMappingsFactory(LayeredMappingSpec spec) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipUtils.add(mappingsFile, "extras/definitions.unpick", unpickData.definitions());
|
ZipUtils.add(mappingsFile, UnpickMetadata.UNPICK_DEFINITIONS_PATH, unpickData.definitions());
|
||||||
ZipUtils.add(mappingsFile, "extras/unpick.json", unpickData.metadata().asJson());
|
ZipUtils.add(mappingsFile, UnpickMetadata.UNPICK_METADATA_PATH, unpickData.rawMetadata());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,14 +45,12 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import com.google.common.base.Stopwatch;
|
import com.google.common.base.Stopwatch;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import dev.architectury.loom.util.MappingOption;
|
import dev.architectury.loom.util.MappingOption;
|
||||||
import org.apache.tools.ant.util.StringUtils;
|
import org.apache.tools.ant.util.StringUtils;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.artifacts.Configuration;
|
import org.gradle.api.artifacts.Configuration;
|
||||||
import org.gradle.api.provider.Provider;
|
import org.gradle.api.provider.Provider;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -64,6 +62,7 @@ import net.fabricmc.loom.configuration.providers.forge.ForgeMigratedMappingConfi
|
|||||||
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
|
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.tiny.MappingsMerger;
|
import net.fabricmc.loom.configuration.providers.mappings.tiny.MappingsMerger;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.tiny.TinyJarInfo;
|
import net.fabricmc.loom.configuration.providers.mappings.tiny.TinyJarInfo;
|
||||||
|
import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.DeletingFileVisitor;
|
import net.fabricmc.loom.util.DeletingFileVisitor;
|
||||||
@@ -102,7 +101,7 @@ public class MappingConfiguration {
|
|||||||
private final Map<MappingOption, Supplier<Path>> mappingOptions;
|
private final Map<MappingOption, Supplier<Path>> mappingOptions;
|
||||||
private final Path unpickDefinitions;
|
private final Path unpickDefinitions;
|
||||||
|
|
||||||
private boolean hasUnpickDefinitions;
|
@Nullable
|
||||||
private UnpickMetadata unpickMetadata;
|
private UnpickMetadata unpickMetadata;
|
||||||
private Map<String, String> signatureFixes;
|
private Map<String, String> signatureFixes;
|
||||||
|
|
||||||
@@ -250,15 +249,19 @@ public class MappingConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void applyToProject(Project project, DependencyInfo dependency) throws IOException {
|
public void applyToProject(Project project, DependencyInfo dependency) throws IOException {
|
||||||
if (hasUnpickDefinitions()) {
|
if (unpickMetadata != null) {
|
||||||
String notation = String.format("%s:%s:%s:constants",
|
if (unpickMetadata.hasConstants()) {
|
||||||
dependency.getDependency().getGroup(),
|
String notation = switch (unpickMetadata) {
|
||||||
dependency.getDependency().getName(),
|
case UnpickMetadata.V1 v1 -> String.format("%s:%s:%s:constants",
|
||||||
dependency.getDependency().getVersion()
|
dependency.getDependency().getGroup(),
|
||||||
);
|
dependency.getDependency().getName(),
|
||||||
|
dependency.getDependency().getVersion()
|
||||||
|
);
|
||||||
|
case UnpickMetadata.V2 v2 -> Objects.requireNonNull(v2.constants());
|
||||||
|
};
|
||||||
|
|
||||||
project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation);
|
project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation);
|
||||||
populateUnpickClasspath(project);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
@@ -441,8 +444,8 @@ public class MappingConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void extractUnpickDefinitions(FileSystem jar) throws IOException {
|
private void extractUnpickDefinitions(FileSystem jar) throws IOException {
|
||||||
Path unpickPath = jar.getPath("extras/definitions.unpick");
|
Path unpickPath = jar.getPath(UnpickMetadata.UNPICK_DEFINITIONS_PATH);
|
||||||
Path unpickMetadataPath = jar.getPath("extras/unpick.json");
|
Path unpickMetadataPath = jar.getPath(UnpickMetadata.UNPICK_METADATA_PATH);
|
||||||
|
|
||||||
if (!Files.exists(unpickPath) || !Files.exists(unpickMetadataPath)) {
|
if (!Files.exists(unpickPath) || !Files.exists(unpickMetadataPath)) {
|
||||||
return;
|
return;
|
||||||
@@ -450,8 +453,7 @@ public class MappingConfiguration {
|
|||||||
|
|
||||||
Files.copy(unpickPath, unpickDefinitions, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(unpickPath, unpickDefinitions, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
unpickMetadata = parseUnpickMetadata(unpickMetadataPath);
|
unpickMetadata = UnpickMetadata.parse(unpickMetadataPath);
|
||||||
hasUnpickDefinitions = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void extractSignatureFixes(FileSystem jar) throws IOException {
|
private void extractSignatureFixes(FileSystem jar) throws IOException {
|
||||||
@@ -467,40 +469,6 @@ public class MappingConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnpickMetadata parseUnpickMetadata(Path input) throws IOException {
|
|
||||||
JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(Files.readString(input, StandardCharsets.UTF_8), JsonObject.class);
|
|
||||||
|
|
||||||
if (!jsonObject.has("version") || jsonObject.get("version").getAsInt() != 1) {
|
|
||||||
throw new UnsupportedOperationException("Unsupported unpick version");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UnpickMetadata(
|
|
||||||
jsonObject.get("unpickGroup").getAsString(),
|
|
||||||
jsonObject.get("unpickVersion").getAsString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void populateUnpickClasspath(Project project) {
|
|
||||||
String unpickCliName = "unpick-cli";
|
|
||||||
project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH,
|
|
||||||
String.format("%s:%s:%s", unpickMetadata.unpickGroup, unpickCliName, unpickMetadata.unpickVersion)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Unpick ships with a slightly older version of asm, ensure it runs with at least the same version as loom.
|
|
||||||
String[] asmDeps = new String[] {
|
|
||||||
"org.ow2.asm:asm:%s",
|
|
||||||
"org.ow2.asm:asm-tree:%s",
|
|
||||||
"org.ow2.asm:asm-commons:%s",
|
|
||||||
"org.ow2.asm:asm-util:%s"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (String asm : asmDeps) {
|
|
||||||
project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH,
|
|
||||||
asm.formatted(Opcodes.class.getPackage().getImplementationVersion())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void suggestFieldNames(Path inputJar, Path oldMappings, Path newMappings) {
|
private void suggestFieldNames(Path inputJar, Path oldMappings, Path newMappings) {
|
||||||
Command command = new CommandProposeFieldNames();
|
Command command = new CommandProposeFieldNames();
|
||||||
runCommand(command, inputJar.toFile().getAbsolutePath(),
|
runCommand(command, inputJar.toFile().getAbsolutePath(),
|
||||||
@@ -554,7 +522,11 @@ public class MappingConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasUnpickDefinitions() {
|
public boolean hasUnpickDefinitions() {
|
||||||
return hasUnpickDefinitions;
|
return unpickMetadata != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnpickMetadata getUnpickMetadata() {
|
||||||
|
return Objects.requireNonNull(unpickMetadata, "Unpick metadata is not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -562,10 +534,6 @@ public class MappingConfiguration {
|
|||||||
return signatureFixes;
|
return signatureFixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBuildServiceName(String name, String from, String to) {
|
|
||||||
return "%s:%s:%s>%S".formatted(name, mappingsIdentifier(), from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getReplacedTarget(LoomGradleExtension loom, String namespace) {
|
public Path getReplacedTarget(LoomGradleExtension loom, String namespace) {
|
||||||
if (namespace.equals("intermediary")) return getPlatformMappingFile(loom);
|
if (namespace.equals("intermediary")) return getPlatformMappingFile(loom);
|
||||||
|
|
||||||
@@ -602,7 +570,4 @@ public class MappingConfiguration {
|
|||||||
return tinyMappings;
|
return tinyMappings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record UnpickMetadata(String unpickGroup, String unpickVersion) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,37 +25,24 @@
|
|||||||
package net.fabricmc.loom.configuration.providers.mappings.extras.unpick;
|
package net.fabricmc.loom.configuration.providers.mappings.extras.unpick;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradlePlugin;
|
import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata;
|
||||||
|
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
public interface UnpickLayer {
|
public interface UnpickLayer {
|
||||||
@Nullable
|
@Nullable
|
||||||
UnpickData getUnpickData() throws IOException;
|
UnpickData getUnpickData() throws IOException;
|
||||||
|
|
||||||
record UnpickData(Metadata metadata, byte[] definitions) {
|
record UnpickData(UnpickMetadata metadata, byte[] rawMetadata, byte[] definitions) {
|
||||||
public static UnpickData read(Path metadataPath, Path definitionPath) throws IOException {
|
public static UnpickData read(Path metadataPath, Path definitionPath) throws IOException {
|
||||||
final byte[] definitions = Files.readAllBytes(definitionPath);
|
final byte[] definitions = Files.readAllBytes(definitionPath);
|
||||||
final Metadata metadata;
|
final byte[] metadata = Files.readAllBytes(metadataPath);
|
||||||
|
return new UnpickData(UnpickMetadata.parse(metadataPath), metadata, definitions);
|
||||||
try (Reader reader = Files.newBufferedReader(metadataPath, StandardCharsets.UTF_8)) {
|
|
||||||
metadata = LoomGradlePlugin.GSON.fromJson(reader, Metadata.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UnpickData(metadata, definitions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public record Metadata(int version, String unpickGroup, String unpickVersion) {
|
|
||||||
public String asJson() {
|
|
||||||
return LoomGradlePlugin.GSON.toJson(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingLayer;
|
|||||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.extras.unpick.UnpickLayer;
|
import net.fabricmc.loom.configuration.providers.mappings.extras.unpick.UnpickLayer;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingLayer;
|
import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingLayer;
|
||||||
|
import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata;
|
||||||
import net.fabricmc.loom.util.FileSystemUtil;
|
import net.fabricmc.loom.util.FileSystemUtil;
|
||||||
import net.fabricmc.loom.util.ZipUtils;
|
import net.fabricmc.loom.util.ZipUtils;
|
||||||
import net.fabricmc.mappingio.MappingReader;
|
import net.fabricmc.mappingio.MappingReader;
|
||||||
@@ -52,9 +53,6 @@ public record FileMappingsLayer(
|
|||||||
boolean unpick,
|
boolean unpick,
|
||||||
String mergeNamespace
|
String mergeNamespace
|
||||||
) implements MappingLayer, UnpickLayer {
|
) implements MappingLayer, UnpickLayer {
|
||||||
private static final String UNPICK_METADATA_PATH = "extras/unpick.json";
|
|
||||||
private static final String UNPICK_DEFINITIONS_PATH = "extras/definitions.unpick";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(MappingVisitor mappingVisitor) throws IOException {
|
public void visit(MappingVisitor mappingVisitor) throws IOException {
|
||||||
// Bare file
|
// Bare file
|
||||||
@@ -102,8 +100,8 @@ public record FileMappingsLayer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (FileSystemUtil.Delegate fileSystem = FileSystemUtil.getJarFileSystem(path)) {
|
try (FileSystemUtil.Delegate fileSystem = FileSystemUtil.getJarFileSystem(path)) {
|
||||||
final Path unpickMetadata = fileSystem.get().getPath(UNPICK_METADATA_PATH);
|
final Path unpickMetadata = fileSystem.get().getPath(UnpickMetadata.UNPICK_METADATA_PATH);
|
||||||
final Path unpickDefinitions = fileSystem.get().getPath(UNPICK_DEFINITIONS_PATH);
|
final Path unpickDefinitions = fileSystem.get().getPath(UnpickMetadata.UNPICK_DEFINITIONS_PATH);
|
||||||
|
|
||||||
if (!Files.exists(unpickMetadata)) {
|
if (!Files.exists(unpickMetadata)) {
|
||||||
// No unpick in this zip
|
// No unpick in this zip
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
*
|
*
|
||||||
* Copyright (c) 2021 FabricMC
|
* Copyright (c) 2021-2025 FabricMC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -30,9 +30,11 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.gradle.api.logging.Logger;
|
import org.gradle.api.logging.Logger;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import net.fabricmc.loom.api.mappings.layered.MappingLayer;
|
import net.fabricmc.loom.api.mappings.layered.MappingLayer;
|
||||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
@@ -41,11 +43,14 @@ import net.fabricmc.loom.configuration.providers.mappings.utils.DstNameFilterMap
|
|||||||
import net.fabricmc.mappingio.MappingVisitor;
|
import net.fabricmc.mappingio.MappingVisitor;
|
||||||
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
|
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
|
||||||
import net.fabricmc.mappingio.format.proguard.ProGuardFileReader;
|
import net.fabricmc.mappingio.format.proguard.ProGuardFileReader;
|
||||||
|
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||||
|
|
||||||
public record MojangMappingLayer(String minecraftVersion,
|
public record MojangMappingLayer(String minecraftVersion,
|
||||||
Path clientMappings,
|
Path clientMappings,
|
||||||
Path serverMappings,
|
Path serverMappings,
|
||||||
boolean nameSyntheticMembers,
|
boolean nameSyntheticMembers,
|
||||||
|
boolean dropNoneIntermediaryRoots,
|
||||||
|
@Nullable Supplier<MemoryMappingTree> intermediarySupplier,
|
||||||
Logger logger,
|
Logger logger,
|
||||||
MojangMappingsSpec.SilenceLicenseOption silenceLicense) implements MappingLayer {
|
MojangMappingsSpec.SilenceLicenseOption silenceLicense) implements MappingLayer {
|
||||||
private static final Pattern SYNTHETIC_NAME_PATTERN = Pattern.compile("^(access|this|val\\$this|lambda\\$.*)\\$[0-9]+$");
|
private static final Pattern SYNTHETIC_NAME_PATTERN = Pattern.compile("^(access|this|val\\$this|lambda\\$.*)\\$[0-9]+$");
|
||||||
@@ -55,12 +60,41 @@ public record MojangMappingLayer(String minecraftVersion,
|
|||||||
printMappingsLicense(clientMappings);
|
printMappingsLicense(clientMappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!dropNoneIntermediaryRoots) {
|
||||||
|
logger().debug("Not attempting to drop none intermediary roots");
|
||||||
|
|
||||||
|
readMappings(mappingVisitor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger().info("Attempting to drop none intermediary roots");
|
||||||
|
|
||||||
|
if (intermediarySupplier == null) {
|
||||||
|
// Using no-op intermediary mappings
|
||||||
|
readMappings(mappingVisitor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a mapping tree with src: official dst: named, intermediary
|
||||||
|
MemoryMappingTree mappingTree = new MemoryMappingTree();
|
||||||
|
intermediarySupplier.get().accept(mappingTree);
|
||||||
|
readMappings(mappingTree);
|
||||||
|
|
||||||
|
// The following code first switches the src namespace to intermediary dropping any entries that don't have an intermediary name
|
||||||
|
// This removes any none root methods before switching it back to official
|
||||||
|
var officialSwitch = new MappingSourceNsSwitch(mappingVisitor, getSourceNamespace().toString(), false);
|
||||||
|
var intermediarySwitch = new MappingSourceNsSwitch(officialSwitch, MappingsNamespace.INTERMEDIARY.toString(), true);
|
||||||
|
mappingTree.accept(intermediarySwitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readMappings(MappingVisitor mappingVisitor) throws IOException {
|
||||||
// Filter out field names matching the pattern
|
// Filter out field names matching the pattern
|
||||||
DstNameFilterMappingVisitor nameFilter = new DstNameFilterMappingVisitor(mappingVisitor, SYNTHETIC_NAME_PATTERN);
|
var nameFilter = new DstNameFilterMappingVisitor(mappingVisitor, SYNTHETIC_NAME_PATTERN);
|
||||||
|
|
||||||
// Make official the source namespace
|
// Make official the source namespace
|
||||||
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nameSyntheticMembers() ? mappingVisitor : nameFilter, MappingsNamespace.OFFICIAL.toString());
|
var nsSwitch = new MappingSourceNsSwitch(nameSyntheticMembers() ? mappingVisitor : nameFilter, MappingsNamespace.OFFICIAL.toString());
|
||||||
|
|
||||||
|
// Read both server and client mappings
|
||||||
try (BufferedReader clientBufferedReader = Files.newBufferedReader(clientMappings, StandardCharsets.UTF_8);
|
try (BufferedReader clientBufferedReader = Files.newBufferedReader(clientMappings, StandardCharsets.UTF_8);
|
||||||
BufferedReader serverBufferedReader = Files.newBufferedReader(serverMappings, StandardCharsets.UTF_8)) {
|
BufferedReader serverBufferedReader = Files.newBufferedReader(serverMappings, StandardCharsets.UTF_8)) {
|
||||||
ProGuardFileReader.read(clientBufferedReader, MappingsNamespace.NAMED.toString(), MappingsNamespace.OFFICIAL.toString(), nsSwitch);
|
ProGuardFileReader.read(clientBufferedReader, MappingsNamespace.NAMED.toString(), MappingsNamespace.OFFICIAL.toString(), nsSwitch);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
*
|
*
|
||||||
* Copyright (c) 2016-2021 FabricMC
|
* Copyright (c) 2016-2025 FabricMC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -30,6 +30,7 @@ import java.nio.file.Path;
|
|||||||
import net.fabricmc.loom.api.mappings.layered.MappingContext;
|
import net.fabricmc.loom.api.mappings.layered.MappingContext;
|
||||||
import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec;
|
import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
||||||
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.download.DownloadException;
|
import net.fabricmc.loom.util.download.DownloadException;
|
||||||
|
|
||||||
public record MojangMappingsSpec(SilenceLicenseOption silenceLicense, boolean nameSyntheticMembers) implements MappingsSpec<MojangMappingLayer> {
|
public record MojangMappingsSpec(SilenceLicenseOption silenceLicense, boolean nameSyntheticMembers) implements MappingsSpec<MojangMappingLayer> {
|
||||||
@@ -103,6 +104,8 @@ public record MojangMappingsSpec(SilenceLicenseOption silenceLicense, boolean na
|
|||||||
clientMappings,
|
clientMappings,
|
||||||
serverMappings,
|
serverMappings,
|
||||||
nameSyntheticMembers(),
|
nameSyntheticMembers(),
|
||||||
|
context.hasProperty(Constants.Properties.DROP_NON_INTERMEDIATE_ROOT_METHODS),
|
||||||
|
context.isUsingIntermediateMappings() ? context.intermediaryTree() : null,
|
||||||
context.getLogger(),
|
context.getLogger(),
|
||||||
silenceLicense()
|
silenceLicense()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.unpick;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.LoomGradlePlugin;
|
||||||
|
|
||||||
|
public sealed interface UnpickMetadata permits UnpickMetadata.V1, UnpickMetadata.V2 {
|
||||||
|
String UNPICK_METADATA_PATH = "extras/unpick.json";
|
||||||
|
String UNPICK_DEFINITIONS_PATH = "extras/definitions.unpick";
|
||||||
|
|
||||||
|
boolean hasConstants();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param unpickGroup Deprecated, always uses the version of unpick loom depends on.
|
||||||
|
* @param unpickVersion Deprecated, always uses the version of unpick loom depends on.
|
||||||
|
*/
|
||||||
|
record V1(@Deprecated String unpickGroup, @Deprecated String unpickVersion) implements UnpickMetadata {
|
||||||
|
@Override
|
||||||
|
public boolean hasConstants() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpick metadata v2.
|
||||||
|
*
|
||||||
|
* @param namespace the mapping namespace of the unpick definitions
|
||||||
|
* @param constants An optional maven notation of the constants jar.
|
||||||
|
*/
|
||||||
|
record V2(String namespace, @Nullable String constants) implements UnpickMetadata {
|
||||||
|
@Override
|
||||||
|
public boolean hasConstants() {
|
||||||
|
return constants != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static UnpickMetadata parse(Path path) throws IOException {
|
||||||
|
JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(Files.readString(path, StandardCharsets.UTF_8), JsonObject.class);
|
||||||
|
|
||||||
|
if (!jsonObject.has("version")) {
|
||||||
|
throw new UnsupportedOperationException("Missing unpick metadata version");
|
||||||
|
}
|
||||||
|
|
||||||
|
int version = jsonObject.get("version").getAsInt();
|
||||||
|
|
||||||
|
switch (version) {
|
||||||
|
case 1 -> {
|
||||||
|
return new V1(
|
||||||
|
getString(jsonObject, "unpickGroup"),
|
||||||
|
getString(jsonObject, "unpickVersion")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 2 -> {
|
||||||
|
return new V2(
|
||||||
|
getString(jsonObject, "namespace"),
|
||||||
|
getOptionalString(jsonObject, "constants")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default -> throw new UnsupportedOperationException("Unsupported unpick metadata version: %s. Please update loom.".formatted(version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(JsonObject jsonObject, String key) {
|
||||||
|
if (!jsonObject.has(key)) {
|
||||||
|
throw new UnsupportedOperationException("Missing unpick metadata %s".formatted(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonObject.get(key).getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String getOptionalString(JsonObject jsonObject, String key) {
|
||||||
|
return jsonObject.has(key) ? jsonObject.get(key).getAsString() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,8 +29,8 @@ import java.nio.file.Path;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import net.fabricmc.loom.api.mappings.layered.spec.FileSpec;
|
|
||||||
import net.fabricmc.loom.api.mappings.layered.MappingContext;
|
import net.fabricmc.loom.api.mappings.layered.MappingContext;
|
||||||
|
import net.fabricmc.loom.api.mappings.layered.spec.FileSpec;
|
||||||
import net.fabricmc.loom.util.Checksum;
|
import net.fabricmc.loom.util.Checksum;
|
||||||
|
|
||||||
public class LocalFileSpec implements FileSpec {
|
public class LocalFileSpec implements FileSpec {
|
||||||
@@ -48,7 +48,7 @@ public class LocalFileSpec implements FileSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use the file hash as part of the spec, this means if the input file changes the mappings will be re-generated.
|
// Use the file hash as part of the spec, this means if the input file changes the mappings will be re-generated.
|
||||||
return Objects.hash(Arrays.hashCode(Checksum.sha256(file)), file.getAbsolutePath());
|
return Objects.hash(Arrays.hashCode(Checksum.of(file).sha256().digest()), file.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ public class MergedMinecraftProvider extends MinecraftProvider {
|
|||||||
File minecraftServerJar = getMinecraftServerJar();
|
File minecraftServerJar = getMinecraftServerJar();
|
||||||
|
|
||||||
if (getServerBundleMetadata() != null) {
|
if (getServerBundleMetadata() != null) {
|
||||||
extractBundledServerJar();
|
|
||||||
minecraftServerJar = getMinecraftExtractedServerJar();
|
minecraftServerJar = getMinecraftExtractedServerJar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ import java.util.jar.Attributes;
|
|||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
|
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.FileSystemUtil;
|
import net.fabricmc.loom.util.FileSystemUtil;
|
||||||
|
|
||||||
@@ -79,7 +77,7 @@ public class MinecraftJarSplitter implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Set<String> getJarEntries(Path input) throws IOException {
|
public static Set<String> getJarEntries(Path input) throws IOException {
|
||||||
Set<String> entries = Sets.newHashSet();
|
Set<String> entries = new HashSet<>();
|
||||||
|
|
||||||
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(input);
|
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(input);
|
||||||
Stream<Path> walk = Files.walk(fs.get().getPath("/"))) {
|
Stream<Path> walk = Files.walk(fs.get().getPath("/"))) {
|
||||||
@@ -154,17 +152,17 @@ public class MinecraftJarSplitter implements AutoCloseable {
|
|||||||
this.clientEntries = clientEntries;
|
this.clientEntries = clientEntries;
|
||||||
this.serverEntries = serverEntries;
|
this.serverEntries = serverEntries;
|
||||||
|
|
||||||
this.commonEntries = Sets.newHashSet(clientEntries);
|
this.commonEntries = new HashSet<>(clientEntries);
|
||||||
this.commonEntries.retainAll(serverEntries);
|
this.commonEntries.retainAll(serverEntries);
|
||||||
this.commonEntries.addAll(sharedEntries);
|
this.commonEntries.addAll(sharedEntries);
|
||||||
this.commonEntries.removeAll(forcedClientEntries);
|
this.commonEntries.removeAll(forcedClientEntries);
|
||||||
|
|
||||||
this.clientOnlyEntries = Sets.newHashSet(clientEntries);
|
this.clientOnlyEntries = new HashSet<>(clientEntries);
|
||||||
this.clientOnlyEntries.removeAll(serverEntries);
|
this.clientOnlyEntries.removeAll(serverEntries);
|
||||||
this.clientOnlyEntries.addAll(sharedEntries);
|
this.clientOnlyEntries.addAll(sharedEntries);
|
||||||
this.clientOnlyEntries.addAll(forcedClientEntries);
|
this.clientOnlyEntries.addAll(forcedClientEntries);
|
||||||
|
|
||||||
this.serverOnlyEntries = Sets.newHashSet(serverEntries);
|
this.serverOnlyEntries = new HashSet<>(serverEntries);
|
||||||
this.serverOnlyEntries.removeAll(clientEntries);
|
this.serverOnlyEntries.removeAll(clientEntries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.gradle.api.JavaVersion;
|
import org.gradle.api.JavaVersion;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.api.artifacts.Configuration;
|
||||||
import org.gradle.api.artifacts.Dependency;
|
import org.gradle.api.artifacts.Dependency;
|
||||||
import org.gradle.api.artifacts.ExternalModuleDependency;
|
import org.gradle.api.artifacts.ExternalModuleDependency;
|
||||||
import org.gradle.api.artifacts.ModuleDependency;
|
import org.gradle.api.artifacts.ModuleDependency;
|
||||||
@@ -94,6 +95,10 @@ public class MinecraftLibraryProvider {
|
|||||||
if (provideServer) {
|
if (provideServer) {
|
||||||
provideServerLibraries();
|
provideServerLibraries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extension.isCollectingDependencyVerificationMetadata()) {
|
||||||
|
resolveAllLibraries();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void provideClientLibraries() {
|
private void provideClientLibraries() {
|
||||||
@@ -114,6 +119,22 @@ public class MinecraftLibraryProvider {
|
|||||||
processLibraries.forEach(this::applyServerLibrary);
|
processLibraries.forEach(this::applyServerLibrary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When Gradle is writing dependency verification metadata, we need to resolve all libraries across all platforms,
|
||||||
|
* to ensure that they are captured.
|
||||||
|
*/
|
||||||
|
private void resolveAllLibraries() {
|
||||||
|
project.getLogger().info("Resolving all libraries for dependency verification metadata generation");
|
||||||
|
|
||||||
|
final List<Library> libraries = MinecraftLibraryHelper.getAllLibraries(minecraftProvider.getVersionInfo());
|
||||||
|
Configuration detachedConfiguration = project.getConfigurations().detachedConfiguration(
|
||||||
|
libraries.stream()
|
||||||
|
.map(library -> project.getDependencies().create(library.mavenNotation()))
|
||||||
|
.toArray(Dependency[]::new)
|
||||||
|
);
|
||||||
|
detachedConfiguration.getFiles();
|
||||||
|
}
|
||||||
|
|
||||||
private List<Library> processLibraries(List<Library> libraries) {
|
private List<Library> processLibraries(List<Library> libraries) {
|
||||||
final LibraryContext libraryContext = new LibraryContext(minecraftProvider.getVersionInfo(), getTargetRuntimeJavaVersion());
|
final LibraryContext libraryContext = new LibraryContext(minecraftProvider.getVersionInfo(), getTargetRuntimeJavaVersion());
|
||||||
return processorManager.processLibraries(libraries, libraryContext);
|
return processorManager.processLibraries(libraries, libraryContext);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import org.gradle.api.JavaVersion;
|
import org.gradle.api.JavaVersion;
|
||||||
@@ -41,9 +42,12 @@ import net.fabricmc.loom.LoomGradleExtension;
|
|||||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.configuration.ConfigContext;
|
import net.fabricmc.loom.configuration.ConfigContext;
|
||||||
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.verify.MinecraftJarVerification;
|
||||||
|
import net.fabricmc.loom.configuration.providers.minecraft.verify.SignatureVerificationFailure;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.download.DownloadExecutor;
|
import net.fabricmc.loom.util.download.DownloadExecutor;
|
||||||
import net.fabricmc.loom.util.download.GradleDownloadProgressListener;
|
import net.fabricmc.loom.util.download.GradleDownloadProgressListener;
|
||||||
|
import net.fabricmc.loom.util.gradle.GradleUtils;
|
||||||
import net.fabricmc.loom.util.gradle.ProgressGroup;
|
import net.fabricmc.loom.util.gradle.ProgressGroup;
|
||||||
|
|
||||||
public abstract class MinecraftProvider {
|
public abstract class MinecraftProvider {
|
||||||
@@ -93,10 +97,18 @@ public abstract class MinecraftProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadJars();
|
boolean didDownload = downloadJars();
|
||||||
|
|
||||||
if (provideServer()) {
|
if (provideServer()) {
|
||||||
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
|
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
|
||||||
|
|
||||||
|
if (serverBundleMetadata != null) {
|
||||||
|
extractBundledServerJar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didDownload) {
|
||||||
|
verifyJars();
|
||||||
}
|
}
|
||||||
|
|
||||||
final MinecraftLibraryProvider libraryProvider = new MinecraftLibraryProvider(this, configContext.project());
|
final MinecraftLibraryProvider libraryProvider = new MinecraftLibraryProvider(this, configContext.project());
|
||||||
@@ -114,7 +126,35 @@ public abstract class MinecraftProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadJars() throws IOException {
|
private void verifyJars() throws IOException, SignatureVerificationFailure {
|
||||||
|
if (GradleUtils.getBooleanProperty(getProject(), Constants.Properties.DISABLE_MINECRAFT_VERIFICATION)) {
|
||||||
|
LOGGER.info("Skipping Minecraft jar verification!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Verifying Minecraft jars");
|
||||||
|
|
||||||
|
MinecraftJarVerification verification = getProject().getObjects().newInstance(MinecraftJarVerification.class, minecraftVersion());
|
||||||
|
|
||||||
|
if (provideClient()) {
|
||||||
|
verification.verifyClientJar(minecraftClientJar.toPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provideServer()) {
|
||||||
|
if (serverBundleMetadata == null) {
|
||||||
|
verification.verifyServerJar(minecraftServerJar.toPath());
|
||||||
|
} else {
|
||||||
|
verification.verifyServerJar(getMinecraftExtractedServerJar().toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Jar verification complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true when a file was downloaded
|
||||||
|
private boolean downloadJars() throws IOException {
|
||||||
|
AtomicBoolean didDownload = new AtomicBoolean(false);
|
||||||
|
|
||||||
try (ProgressGroup progressGroup = new ProgressGroup(getProject(), "Download Minecraft jars");
|
try (ProgressGroup progressGroup = new ProgressGroup(getProject(), "Download Minecraft jars");
|
||||||
DownloadExecutor executor = new DownloadExecutor(2)) {
|
DownloadExecutor executor = new DownloadExecutor(2)) {
|
||||||
if (provideClient()) {
|
if (provideClient()) {
|
||||||
@@ -122,7 +162,12 @@ public abstract class MinecraftProvider {
|
|||||||
getExtension().download(client.url())
|
getExtension().download(client.url())
|
||||||
.sha1(client.sha1())
|
.sha1(client.sha1())
|
||||||
.progress(new GradleDownloadProgressListener("Minecraft client", progressGroup::createProgressLogger))
|
.progress(new GradleDownloadProgressListener("Minecraft client", progressGroup::createProgressLogger))
|
||||||
.downloadPathAsync(minecraftClientJar.toPath(), executor);
|
.downloadPathAsync(minecraftClientJar.toPath(), executor)
|
||||||
|
.thenAccept(downloadResult -> {
|
||||||
|
if (downloadResult.didDownload()) {
|
||||||
|
didDownload.set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provideServer()) {
|
if (provideServer()) {
|
||||||
@@ -130,9 +175,22 @@ public abstract class MinecraftProvider {
|
|||||||
getExtension().download(server.url())
|
getExtension().download(server.url())
|
||||||
.sha1(server.sha1())
|
.sha1(server.sha1())
|
||||||
.progress(new GradleDownloadProgressListener("Minecraft server", progressGroup::createProgressLogger))
|
.progress(new GradleDownloadProgressListener("Minecraft server", progressGroup::createProgressLogger))
|
||||||
.downloadPathAsync(minecraftServerJar.toPath(), executor);
|
.downloadPathAsync(minecraftServerJar.toPath(), executor)
|
||||||
|
.thenAccept(downloadResult -> {
|
||||||
|
if (downloadResult.didDownload()) {
|
||||||
|
didDownload.set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (didDownload.get()) {
|
||||||
|
LOGGER.info("Downloaded new Minecraft jars");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Using cached Minecraft jars");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void extractBundledServerJar() throws IOException {
|
public final void extractBundledServerJar() throws IOException {
|
||||||
|
|||||||
@@ -143,14 +143,13 @@ public abstract class SingleJarMinecraftProvider extends MinecraftProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception {
|
public Path getInputJar(SingleJarMinecraftProvider provider) {
|
||||||
BundleMetadata serverBundleMetadata = provider.getServerBundleMetadata();
|
BundleMetadata serverBundleMetadata = provider.getServerBundleMetadata();
|
||||||
|
|
||||||
if (serverBundleMetadata == null) {
|
if (serverBundleMetadata == null) {
|
||||||
return provider.getMinecraftServerJar().toPath();
|
return provider.getMinecraftServerJar().toPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.extractBundledServerJar();
|
|
||||||
return provider.getMinecraftExtractedServerJar().toPath();
|
return provider.getMinecraftExtractedServerJar().toPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,8 +74,6 @@ public final class SplitMinecraftProvider extends MinecraftProvider {
|
|||||||
throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar can be split, please use a merged jar setup for this version of minecraft");
|
throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar can be split, please use a merged jar setup for this version of minecraft");
|
||||||
}
|
}
|
||||||
|
|
||||||
extractBundledServerJar();
|
|
||||||
|
|
||||||
final Path clientJar = getMinecraftClientJar().toPath();
|
final Path clientJar = getMinecraftClientJar().toPath();
|
||||||
final Path serverJar = getMinecraftExtractedServerJar().toPath();
|
final Path serverJar = getMinecraftExtractedServerJar().toPath();
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ package net.fabricmc.loom.configuration.providers.minecraft.library;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -72,6 +73,35 @@ public class MinecraftLibraryHelper {
|
|||||||
return Collections.unmodifiableList(libraries);
|
return Collections.unmodifiableList(libraries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<Library> getAllLibraries(MinecraftVersionMeta versionMeta) {
|
||||||
|
var libraries = new ArrayList<Library>();
|
||||||
|
|
||||||
|
for (MinecraftVersionMeta.Library library : versionMeta.libraries()) {
|
||||||
|
if (library.artifact() != null) {
|
||||||
|
Library mavenLib = Library.fromMaven(library.name(), Library.Target.COMPILE);
|
||||||
|
|
||||||
|
// Versions that have the natives on the classpath, attempt to target them as natives.
|
||||||
|
if (mavenLib.classifier() != null && mavenLib.classifier().startsWith("natives-")) {
|
||||||
|
mavenLib = mavenLib.withTarget(Library.Target.NATIVES);
|
||||||
|
}
|
||||||
|
|
||||||
|
libraries.add(mavenLib);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, MinecraftVersionMeta.Download> classifiers = library.downloads().classifiers();
|
||||||
|
|
||||||
|
if (classifiers == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MinecraftVersionMeta.Download download : classifiers.values()) {
|
||||||
|
libraries.add(downloadToLibrary(download));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(libraries);
|
||||||
|
}
|
||||||
|
|
||||||
private static Library downloadToLibrary(MinecraftVersionMeta.Download download) {
|
private static Library downloadToLibrary(MinecraftVersionMeta.Download download) {
|
||||||
final String path = download.path();
|
final String path = download.path();
|
||||||
final Matcher matcher = NATIVES_PATTERN.matcher(path);
|
final Matcher matcher = NATIVES_PATTERN.matcher(path);
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.verify;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node in the certificate chain.
|
||||||
|
*/
|
||||||
|
public interface CertificateChain {
|
||||||
|
/**
|
||||||
|
* The certificate itself.
|
||||||
|
*/
|
||||||
|
X509Certificate certificate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The issuer of this certificate, or null if this is a root certificate.
|
||||||
|
*/
|
||||||
|
@Nullable CertificateChain issuer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The children of this certificate, or an empty list if this is a leaf certificate.
|
||||||
|
*/
|
||||||
|
List<CertificateChain> children();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that this certificate chain matches exactly with another one.
|
||||||
|
* @param other the other certificate chain
|
||||||
|
*/
|
||||||
|
void verifyChainMatches(CertificateChain other) throws SignatureVerificationFailure;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively visit all certificates in the chain, including this one.
|
||||||
|
*/
|
||||||
|
static void visitAll(CertificateChain chain, CertificateConsumer consumer) throws SignatureVerificationFailure {
|
||||||
|
consumer.accept(chain.certificate());
|
||||||
|
|
||||||
|
for (CertificateChain child : chain.children()) {
|
||||||
|
visitAll(child, consumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load certificate chain from the classpath, returning the root certificate.
|
||||||
|
*/
|
||||||
|
static CertificateChain getRoot(String name) throws IOException {
|
||||||
|
try (InputStream is = JarVerifier.class.getClassLoader().getResourceAsStream("certs/" + name + ".cer")) {
|
||||||
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
|
Collection<X509Certificate> certificates = cf.generateCertificates(is).stream()
|
||||||
|
.map(c -> (X509Certificate) c)
|
||||||
|
.toList();
|
||||||
|
return getRoot(certificates);
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
throw new RuntimeException("Failed to load certificate: " + name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an unordered collection of certificates and builds a tree structure.
|
||||||
|
*/
|
||||||
|
static CertificateChain getRoot(Collection<X509Certificate> certificates) {
|
||||||
|
Map<String, Impl> certificateNodes = new HashMap<>();
|
||||||
|
|
||||||
|
for (X509Certificate certificate : certificates) {
|
||||||
|
Impl node = new Impl();
|
||||||
|
node.certificate = certificate;
|
||||||
|
certificateNodes.put(certificate.getSubjectX500Principal().getName(), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (X509Certificate certificate : certificates) {
|
||||||
|
String subject = certificate.getSubjectX500Principal().getName();
|
||||||
|
String issuer = certificate.getIssuerX500Principal().getName();
|
||||||
|
|
||||||
|
if (subject.equals(issuer)) {
|
||||||
|
continue; // self-signed
|
||||||
|
}
|
||||||
|
|
||||||
|
Impl parent = certificateNodes.get(issuer);
|
||||||
|
Impl self = certificateNodes.get(subject);
|
||||||
|
|
||||||
|
if (parent == self) {
|
||||||
|
throw new IllegalStateException("Certificate " + subject + " is its own issuer");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent == null) {
|
||||||
|
throw new IllegalStateException("Certificate " + subject + " defines issuer " + issuer + " which is not in the chain");
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.children.add(self);
|
||||||
|
self.issuer = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Impl> roots = certificateNodes.values()
|
||||||
|
.stream()
|
||||||
|
.filter(node -> node.issuer == null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (roots.size() != 1) {
|
||||||
|
throw new IllegalStateException("Expected exactly one root certificate, but found " + roots.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
return roots.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface CertificateConsumer {
|
||||||
|
void accept(X509Certificate certificate) throws SignatureVerificationFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Impl implements CertificateChain {
|
||||||
|
X509Certificate certificate;
|
||||||
|
@Nullable CertificateChain.Impl issuer;
|
||||||
|
List<CertificateChain> children = new ArrayList<>();
|
||||||
|
|
||||||
|
private Impl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate certificate() {
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable CertificateChain issuer() {
|
||||||
|
return issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CertificateChain> children() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verifyChainMatches(CertificateChain other) throws SignatureVerificationFailure {
|
||||||
|
if (!this.certificate().equals(other.certificate())) {
|
||||||
|
throw new SignatureVerificationFailure("Certificate mismatch: " + this + " != " + other);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.children().size() != other.children().size()) {
|
||||||
|
throw new SignatureVerificationFailure("Certificate mismatch: " + this + " has " + this.children().size() + " children, but " + other + " has " + other.children().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.children.isEmpty()) {
|
||||||
|
// Fine, leaf certificate
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.children.size() != 1) {
|
||||||
|
// TODO support this, not needed currently
|
||||||
|
throw new UnsupportedOperationException("Validating Certificate chain with multiple children is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.children.get(0).verifyChainMatches(other.children().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return certificate.getSubjectX500Principal().getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.verify;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.cert.CRLException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509CRL;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
|
import net.fabricmc.loom.util.download.DownloadException;
|
||||||
|
|
||||||
|
public record CertificateRevocationList(Collection<X509CRL> crls, boolean downloadFailure) {
|
||||||
|
/**
|
||||||
|
* Hardcoded CRLs for Mojang's certificate, we don't want to add a large dependency just to parse this each time.
|
||||||
|
*/
|
||||||
|
public static final List<String> CSC3_2010 = List.of(
|
||||||
|
"http://crl.verisign.com/pca3-g5.crl",
|
||||||
|
"http://crl.verisign.com/pca3.crl",
|
||||||
|
"http://csc3-2010-crl.verisign.com/CSC3-2010.crl"
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(CertificateRevocationList.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to download the CRL from the given URL, if we fail to get it its not the end of the world.
|
||||||
|
*/
|
||||||
|
public static CertificateRevocationList create(Project project, List<String> urls) throws IOException {
|
||||||
|
List<X509CRL> crls = new ArrayList<>();
|
||||||
|
|
||||||
|
boolean downloadFailure = false;
|
||||||
|
|
||||||
|
for (String url : urls) {
|
||||||
|
try {
|
||||||
|
crls.add(download(project, url));
|
||||||
|
} catch (DownloadException e) {
|
||||||
|
LOGGER.info("Failed to download CRL from {}: {}", url, e.getMessage());
|
||||||
|
LOGGER.info("Loom will not be able to verify the integrity of the minecraft jar signature");
|
||||||
|
downloadFailure = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CertificateRevocationList(crls, downloadFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509CRL download(Project project, String url) throws IOException {
|
||||||
|
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
|
final String name = url.substring(url.lastIndexOf('/') + 1);
|
||||||
|
final Path path = extension.getFiles().getUserCache().toPath()
|
||||||
|
.resolve("crl")
|
||||||
|
.resolve(name);
|
||||||
|
|
||||||
|
LOGGER.info("Downloading CRL from {} to {}", url, path);
|
||||||
|
|
||||||
|
extension.download(url)
|
||||||
|
.allowInsecureProtocol()
|
||||||
|
.maxAge(Duration.ofDays(7)) // Cache the CRL for a week
|
||||||
|
.downloadPath(path);
|
||||||
|
|
||||||
|
return parse(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509CRL parse(Path path) throws IOException {
|
||||||
|
try (InputStream inStream = Files.newInputStream(path)) {
|
||||||
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
|
return (X509CRL) cf.generateCRL(inStream);
|
||||||
|
} catch (CRLException | CertificateException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that none of the certs in the chain are revoked.
|
||||||
|
* @throws SignatureVerificationFailure if the certificate is revoked
|
||||||
|
*/
|
||||||
|
public void verify(CertificateChain certificateChain) throws SignatureVerificationFailure {
|
||||||
|
CertificateChain.visitAll(certificateChain, this::verify);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verify(X509Certificate certificate) throws SignatureVerificationFailure {
|
||||||
|
for (X509CRL crl : crls) {
|
||||||
|
if (crl.isRevoked(certificate)) {
|
||||||
|
throw new SignatureVerificationFailure("Certificate " + certificate.getSubjectX500Principal().getName() + " is revoked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.verify;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.ZipReprocessorUtil;
|
||||||
|
|
||||||
|
public final class JarVerifier {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JarVerifier.class);
|
||||||
|
|
||||||
|
private JarVerifier() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void verify(Path jarPath, CertificateChain certificateChain) throws IOException, SignatureVerificationFailure {
|
||||||
|
Objects.requireNonNull(jarPath, "jarPath");
|
||||||
|
Objects.requireNonNull(certificateChain, "certificateChain");
|
||||||
|
|
||||||
|
if (certificateChain.issuer() != null) {
|
||||||
|
throw new IllegalStateException("Can only verify jars from a root certificate");
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<X509Certificate> jarCertificates = new HashSet<>();
|
||||||
|
|
||||||
|
try (JarFile jarFile = new JarFile(jarPath.toFile(), true)) {
|
||||||
|
for (JarEntry jarEntry : Collections.list(jarFile.entries())) {
|
||||||
|
if (ZipReprocessorUtil.isSpecialFile(jarEntry.getName())
|
||||||
|
|| jarEntry.getName().equals("META-INF/MANIFEST.MF")
|
||||||
|
|| jarEntry.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Must read the entire entry to trigger the signature verification
|
||||||
|
byte[] bytes = jarFile.getInputStream(jarEntry).readAllBytes();
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new SignatureVerificationFailure("Jar entry " + jarEntry.getName() + " failed signature verification", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Certificate[] entryCertificates = jarEntry.getCertificates();
|
||||||
|
|
||||||
|
if (entryCertificates == null) {
|
||||||
|
throw new SignatureVerificationFailure("Jar entry " + jarEntry.getName() + " does not have a signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrays.stream(entryCertificates)
|
||||||
|
.map(c -> (X509Certificate) c)
|
||||||
|
.forEach(jarCertificates::add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CertificateChain jarCertificateChain = CertificateChain.getRoot(jarCertificates);
|
||||||
|
|
||||||
|
jarCertificateChain.verifyChainMatches(certificateChain);
|
||||||
|
LOGGER.debug("Jar {} is signed by the expected certificate", jarPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.verify;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.LoomGradlePlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The know versions keep track of the versions that are signed using SHA1 or not signature at all.
|
||||||
|
* The maps are the Minecraft version to sha256 hash of the jar file.
|
||||||
|
*/
|
||||||
|
public record KnownVersions(
|
||||||
|
Map<String, String> client,
|
||||||
|
Map<String, String> server) {
|
||||||
|
public static final Supplier<KnownVersions> INSTANCE = Suppliers.memoize(KnownVersions::load);
|
||||||
|
|
||||||
|
private static KnownVersions load() {
|
||||||
|
try (InputStream is = KnownVersions.class.getClassLoader().getResourceAsStream("certs/known_versions.json");
|
||||||
|
Reader reader = new InputStreamReader(Objects.requireNonNull(is))) {
|
||||||
|
return LoomGradlePlugin.GSON.fromJson(reader, KnownVersions.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException("Failed to load known versions", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.verify;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.Checksum;
|
||||||
|
|
||||||
|
public abstract class MinecraftJarVerification {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(MinecraftJarVerification.class);
|
||||||
|
|
||||||
|
private final String minecraftVersion;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected abstract Project getProject();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MinecraftJarVerification(String minecraftVersion) {
|
||||||
|
this.minecraftVersion = minecraftVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verifyClientJar(Path path) throws IOException, SignatureVerificationFailure {
|
||||||
|
verifyJarSignature(path, KnownJarType.CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verifyServerJar(Path path) throws IOException, SignatureVerificationFailure {
|
||||||
|
verifyJarSignature(path, KnownJarType.SERVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyJarSignature(Path path, KnownJarType type) throws IOException, SignatureVerificationFailure {
|
||||||
|
CertificateChain chain = CertificateChain.getRoot("mojangcs");
|
||||||
|
CertificateRevocationList revocationList = CertificateRevocationList.create(getProject(), CertificateRevocationList.CSC3_2010);
|
||||||
|
|
||||||
|
try {
|
||||||
|
revocationList.verify(chain);
|
||||||
|
JarVerifier.verify(path, chain);
|
||||||
|
} catch (SignatureVerificationFailure e) {
|
||||||
|
if (isValidKnownVersion(path, minecraftVersion, type)) {
|
||||||
|
LOGGER.info("Minecraft {} signature verification failed, but is a known version", path.getFileName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.error("Verification of Minecraft {} signature failed: {}", path.getFileName(), e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidKnownVersion(Path path, String version, KnownJarType type) throws IOException, SignatureVerificationFailure {
|
||||||
|
Map<String, String> knownVersions = type.getKnownVersions();
|
||||||
|
String expectedHash = knownVersions.get(version);
|
||||||
|
|
||||||
|
if (expectedHash == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Found executed hash ({}) for known version: {}", expectedHash, version);
|
||||||
|
Checksum.Result hash = Checksum.of(path).sha256();
|
||||||
|
|
||||||
|
if (hash.matchesStr(expectedHash)) {
|
||||||
|
LOGGER.info("Minecraft {} hash matches known version", path.getFileName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SignatureVerificationFailure("Hash mismatch for known Minecraft version " + version + ": expected " + expectedHash + ", got " + hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum KnownJarType {
|
||||||
|
CLIENT(KnownVersions::client),
|
||||||
|
SERVER(KnownVersions::server),;
|
||||||
|
|
||||||
|
private final Function<KnownVersions, Map<String, String>> knownVersions;
|
||||||
|
|
||||||
|
KnownJarType(Function<KnownVersions, Map<String, String>> knownVersions) {
|
||||||
|
this.knownVersions = knownVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> getKnownVersions() {
|
||||||
|
return knownVersions.apply(KnownVersions.INSTANCE.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.verify;
|
||||||
|
|
||||||
|
public final class SignatureVerificationFailure extends Exception {
|
||||||
|
public SignatureVerificationFailure(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureVerificationFailure(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,13 +105,13 @@ public record ClassEntry(String name, List<String> innerClasses, List<String> su
|
|||||||
public String hash(Path root) throws IOException {
|
public String hash(Path root) throws IOException {
|
||||||
StringJoiner joiner = new StringJoiner(",");
|
StringJoiner joiner = new StringJoiner(",");
|
||||||
|
|
||||||
joiner.add(Checksum.sha256Hex(Files.readAllBytes(root.resolve(name))));
|
joiner.add(Checksum.of(root.resolve(name)).sha256().hex());
|
||||||
|
|
||||||
for (String innerClass : innerClasses) {
|
for (String innerClass : innerClasses) {
|
||||||
joiner.add(Checksum.sha256Hex(Files.readAllBytes(root.resolve(innerClass))));
|
joiner.add(Checksum.of(root.resolve(innerClass)).sha256().hex());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Checksum.sha256Hex(joiner.toString().getBytes());
|
return Checksum.of(joiner.toString()).sha256().hex();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,7 +138,7 @@ public record ClassEntry(String name, List<String> innerClasses, List<String> su
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Checksum.sha256Hex(joiner.toString().getBytes());
|
return Checksum.of(joiner.toString()).sha256().hex();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String sourcesFileName() {
|
public String sourcesFileName() {
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ public interface LoomFiles {
|
|||||||
File getNativesDirectory(Project project);
|
File getNativesDirectory(Project project);
|
||||||
File getDefaultLog4jConfigFile();
|
File getDefaultLog4jConfigFile();
|
||||||
File getDevLauncherConfig();
|
File getDevLauncherConfig();
|
||||||
File getUnpickLoggingConfigFile();
|
|
||||||
File getRemapClasspathFile();
|
File getRemapClasspathFile();
|
||||||
File getGlobalMinecraftRepo();
|
File getGlobalMinecraftRepo();
|
||||||
File getLocalMinecraftRepo();
|
File getLocalMinecraftRepo();
|
||||||
|
|||||||
@@ -84,11 +84,6 @@ public abstract class LoomFilesBaseImpl implements LoomFiles {
|
|||||||
return new File(getProjectPersistentCache(), "launch.cfg");
|
return new File(getProjectPersistentCache(), "launch.cfg");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public File getUnpickLoggingConfigFile() {
|
|
||||||
return new File(getProjectPersistentCache(), "unpick-logging.properties");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getRemapClasspathFile() {
|
public File getRemapClasspathFile() {
|
||||||
return new File(getProjectPersistentCache(), "remapClasspath.txt");
|
return new File(getProjectPersistentCache(), "remapClasspath.txt");
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
|
|||||||
private final ListProperty<LibraryProcessorManager.LibraryProcessorFactory> libraryProcessorFactories;
|
private final ListProperty<LibraryProcessorManager.LibraryProcessorFactory> libraryProcessorFactories;
|
||||||
private final boolean configurationCacheActive;
|
private final boolean configurationCacheActive;
|
||||||
private final boolean isolatedProjectsActive;
|
private final boolean isolatedProjectsActive;
|
||||||
|
private final boolean isCollectingDependencyVerificationMetadata;
|
||||||
|
|
||||||
// +-------------------+
|
// +-------------------+
|
||||||
// | Architectury Loom |
|
// | Architectury Loom |
|
||||||
@@ -127,6 +128,7 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
|
|||||||
|
|
||||||
configurationCacheActive = getBuildFeatures().getConfigurationCache().getActive().get();
|
configurationCacheActive = getBuildFeatures().getConfigurationCache().getActive().get();
|
||||||
isolatedProjectsActive = getBuildFeatures().getIsolatedProjects().getActive().get();
|
isolatedProjectsActive = getBuildFeatures().getIsolatedProjects().getActive().get();
|
||||||
|
isCollectingDependencyVerificationMetadata = !project.getGradle().getStartParameter().getWriteDependencyVerifications().isEmpty();
|
||||||
|
|
||||||
if (refreshDeps) {
|
if (refreshDeps) {
|
||||||
project.getLogger().lifecycle("Refresh dependencies is in use, loom will be significantly slower.");
|
project.getLogger().lifecycle("Refresh dependencies is in use, loom will be significantly slower.");
|
||||||
@@ -348,6 +350,11 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
|
|||||||
return isolatedProjectsActive;
|
return isolatedProjectsActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCollectingDependencyVerificationMetadata() {
|
||||||
|
return isCollectingDependencyVerificationMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ForgeExtensionAPI getForge() {
|
public ForgeExtensionAPI getForge() {
|
||||||
ModPlatform.assertPlatform(this, ModPlatform.FORGE);
|
ModPlatform.assertPlatform(this, ModPlatform.FORGE);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.gradle.api.InvalidUserDataException;
|
|||||||
import org.gradle.api.Task;
|
import org.gradle.api.Task;
|
||||||
import org.gradle.api.artifacts.Configuration;
|
import org.gradle.api.artifacts.Configuration;
|
||||||
import org.gradle.api.plugins.ExtraPropertiesExtension;
|
import org.gradle.api.plugins.ExtraPropertiesExtension;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
import org.gradle.api.provider.Provider;
|
import org.gradle.api.provider.Provider;
|
||||||
import org.gradle.api.tasks.Input;
|
import org.gradle.api.tasks.Input;
|
||||||
import org.gradle.api.tasks.SourceSet;
|
import org.gradle.api.tasks.SourceSet;
|
||||||
@@ -89,4 +90,6 @@ public interface MixinExtension extends MixinExtensionAPI {
|
|||||||
Collection<SourceSet> getMixinSourceSets();
|
Collection<SourceSet> getMixinSourceSets();
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
|
Property<Boolean> getInlineDependencyRefmaps();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,13 @@ import org.gradle.api.tasks.Input;
|
|||||||
import org.gradle.api.tasks.SourceSet;
|
import org.gradle.api.tasks.SourceSet;
|
||||||
import org.gradle.api.tasks.TaskProvider;
|
import org.gradle.api.tasks.TaskProvider;
|
||||||
import org.gradle.api.tasks.util.PatternSet;
|
import org.gradle.api.tasks.util.PatternSet;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinExtension {
|
public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinExtension {
|
||||||
private boolean isDefault;
|
private boolean isDefault;
|
||||||
private final Property<String> defaultRefmapName;
|
private final Property<String> defaultRefmapName;
|
||||||
|
private final Property<Boolean> inlineDependencyRefmaps;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MixinExtensionImpl(Project project) {
|
public MixinExtensionImpl(Project project) {
|
||||||
@@ -59,6 +61,9 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
|
|||||||
this.defaultRefmapName = project.getObjects().property(String.class)
|
this.defaultRefmapName = project.getObjects().property(String.class)
|
||||||
.convention(project.provider(this::getDefaultMixinRefmapName));
|
.convention(project.provider(this::getDefaultMixinRefmapName));
|
||||||
this.defaultRefmapName.finalizeValueOnRead();
|
this.defaultRefmapName.finalizeValueOnRead();
|
||||||
|
this.inlineDependencyRefmaps = project.getObjects().property(Boolean.class)
|
||||||
|
.convention(false);
|
||||||
|
this.inlineDependencyRefmaps.finalizeValueOnRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -146,4 +151,10 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
@Override
|
||||||
|
public Property<Boolean> getInlineDependencyRefmaps() {
|
||||||
|
return inlineDependencyRefmaps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,14 @@ public abstract class AbstractRemapJarTask extends Jar {
|
|||||||
@Optional
|
@Optional
|
||||||
public abstract Property<String> getClientOnlySourceSetName();
|
public abstract Property<String> getClientOnlySourceSetName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally supply a single mapping file or jar file containing mappings to be used for remapping.
|
||||||
|
*/
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
@InputFiles
|
||||||
|
@Optional
|
||||||
|
public abstract ConfigurableFileCollection getCustomMappings();
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
@Optional
|
@Optional
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import java.io.BufferedReader;
|
|||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@@ -37,9 +36,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -59,7 +56,6 @@ import org.gradle.api.file.RegularFileProperty;
|
|||||||
import org.gradle.api.provider.Property;
|
import org.gradle.api.provider.Property;
|
||||||
import org.gradle.api.services.ServiceReference;
|
import org.gradle.api.services.ServiceReference;
|
||||||
import org.gradle.api.tasks.Input;
|
import org.gradle.api.tasks.Input;
|
||||||
import org.gradle.api.tasks.InputFile;
|
|
||||||
import org.gradle.api.tasks.InputFiles;
|
import org.gradle.api.tasks.InputFiles;
|
||||||
import org.gradle.api.tasks.Internal;
|
import org.gradle.api.tasks.Internal;
|
||||||
import org.gradle.api.tasks.Nested;
|
import org.gradle.api.tasks.Nested;
|
||||||
@@ -70,7 +66,6 @@ import org.gradle.api.tasks.UntrackedTask;
|
|||||||
import org.gradle.api.tasks.options.Option;
|
import org.gradle.api.tasks.options.Option;
|
||||||
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
|
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
|
||||||
import org.gradle.process.ExecOperations;
|
import org.gradle.process.ExecOperations;
|
||||||
import org.gradle.process.ExecResult;
|
|
||||||
import org.gradle.workers.WorkAction;
|
import org.gradle.workers.WorkAction;
|
||||||
import org.gradle.workers.WorkParameters;
|
import org.gradle.workers.WorkParameters;
|
||||||
import org.gradle.workers.WorkQueue;
|
import org.gradle.workers.WorkQueue;
|
||||||
@@ -91,6 +86,7 @@ import net.fabricmc.loom.decompilers.cache.CachedData;
|
|||||||
import net.fabricmc.loom.decompilers.cache.CachedFileStoreImpl;
|
import net.fabricmc.loom.decompilers.cache.CachedFileStoreImpl;
|
||||||
import net.fabricmc.loom.decompilers.cache.CachedJarProcessor;
|
import net.fabricmc.loom.decompilers.cache.CachedJarProcessor;
|
||||||
import net.fabricmc.loom.task.service.SourceMappingsService;
|
import net.fabricmc.loom.task.service.SourceMappingsService;
|
||||||
|
import net.fabricmc.loom.task.service.UnpickService;
|
||||||
import net.fabricmc.loom.util.Checksum;
|
import net.fabricmc.loom.util.Checksum;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
import net.fabricmc.loom.util.ExceptionUtil;
|
import net.fabricmc.loom.util.ExceptionUtil;
|
||||||
@@ -106,6 +102,7 @@ import net.fabricmc.loom.util.gradle.daemon.DaemonUtils;
|
|||||||
import net.fabricmc.loom.util.ipc.IPCClient;
|
import net.fabricmc.loom.util.ipc.IPCClient;
|
||||||
import net.fabricmc.loom.util.ipc.IPCServer;
|
import net.fabricmc.loom.util.ipc.IPCServer;
|
||||||
import net.fabricmc.loom.util.service.ScopedServiceFactory;
|
import net.fabricmc.loom.util.service.ScopedServiceFactory;
|
||||||
|
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||||
|
|
||||||
@UntrackedTask(because = "Manually invoked, has internal caching")
|
@UntrackedTask(because = "Manually invoked, has internal caching")
|
||||||
@@ -135,31 +132,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
@OutputFile
|
@OutputFile
|
||||||
protected abstract ConfigurableFileCollection getClassesOutputJar(); // Single jar
|
protected abstract ConfigurableFileCollection getClassesOutputJar(); // Single jar
|
||||||
|
|
||||||
// Unpick
|
|
||||||
@InputFile
|
|
||||||
@Optional
|
|
||||||
public abstract RegularFileProperty getUnpickDefinitions();
|
|
||||||
|
|
||||||
@InputFiles
|
|
||||||
@Optional
|
|
||||||
public abstract ConfigurableFileCollection getUnpickConstantJar();
|
|
||||||
|
|
||||||
@InputFiles
|
|
||||||
@Optional
|
|
||||||
public abstract ConfigurableFileCollection getUnpickClasspath();
|
|
||||||
|
|
||||||
@InputFiles
|
|
||||||
@Optional
|
|
||||||
@ApiStatus.Internal
|
|
||||||
public abstract ConfigurableFileCollection getUnpickRuntimeClasspath();
|
|
||||||
|
|
||||||
@OutputFile
|
|
||||||
@Optional
|
|
||||||
public abstract RegularFileProperty getUnpickOutputJar();
|
|
||||||
|
|
||||||
@OutputFile
|
|
||||||
protected abstract RegularFileProperty getUnpickLogConfig();
|
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
@Option(option = "use-cache", description = "Use the decompile cache")
|
@Option(option = "use-cache", description = "Use the decompile cache")
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
@@ -204,6 +176,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
@Nested
|
@Nested
|
||||||
protected abstract Property<DaemonUtils.Context> getDaemonUtilsContext();
|
protected abstract Property<DaemonUtils.Context> getDaemonUtilsContext();
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@Optional
|
||||||
|
protected abstract Property<UnpickService.Options> getUnpickOptions();
|
||||||
|
|
||||||
// Prevent Gradle from running two gen sources tasks in parallel
|
// Prevent Gradle from running two gen sources tasks in parallel
|
||||||
@ServiceReference(SyncTaskBuildService.NAME)
|
@ServiceReference(SyncTaskBuildService.NAME)
|
||||||
abstract Property<SyncTaskBuildService> getSyncTask();
|
abstract Property<SyncTaskBuildService> getSyncTask();
|
||||||
@@ -246,8 +222,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
|
|
||||||
getMinecraftCompileLibraries().from(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
|
getMinecraftCompileLibraries().from(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
|
||||||
getDecompileCacheFile().set(getExtension().getFiles().getDecompileCache(CACHE_VERSION));
|
getDecompileCacheFile().set(getExtension().getFiles().getDecompileCache(CACHE_VERSION));
|
||||||
getUnpickRuntimeClasspath().from(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH));
|
|
||||||
getUnpickLogConfig().set(getExtension().getFiles().getUnpickLoggingConfigFile());
|
|
||||||
|
|
||||||
getUseCache().convention(true);
|
getUseCache().convention(true);
|
||||||
getResetCache().convention(getExtension().refreshDeps());
|
getResetCache().convention(getExtension().refreshDeps());
|
||||||
@@ -259,6 +233,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
|
|
||||||
getDaemonUtilsContext().set(getProject().getObjects().newInstance(DaemonUtils.Context.class, getProject()));
|
getDaemonUtilsContext().set(getProject().getObjects().newInstance(DaemonUtils.Context.class, getProject()));
|
||||||
|
|
||||||
|
getUnpickOptions().set(UnpickService.createOptions(this));
|
||||||
|
|
||||||
mustRunAfter(getProject().getTasks().withType(AbstractRemapJarTask.class));
|
mustRunAfter(getProject().getTasks().withType(AbstractRemapJarTask.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,57 +246,59 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements.");
|
throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getUseCache().get()) {
|
try (ScopedServiceFactory serviceFactory = new ScopedServiceFactory()) {
|
||||||
getLogger().info("Not using decompile cache.");
|
if (!getUseCache().get()) {
|
||||||
|
getLogger().info("Not using decompile cache.");
|
||||||
|
|
||||||
try (var timer = new Timer("Decompiled sources")) {
|
try (var timer = new Timer("Decompiled sources")) {
|
||||||
runWithoutCache();
|
runWithoutCache(serviceFactory);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ExceptionUtil.processException(e, getDaemonUtilsContext().get());
|
||||||
|
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogger().info("Using decompile cache.");
|
||||||
|
|
||||||
|
try (var timer = new Timer("Decompiled sources with cache")) {
|
||||||
|
final Path cacheFile = getDecompileCacheFile().getAsFile().get().toPath();
|
||||||
|
|
||||||
|
if (getResetCache().get()) {
|
||||||
|
getLogger().warn("Resetting decompile cache");
|
||||||
|
Files.deleteIfExists(cacheFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO ensure we have a lock on this file to prevent multiple tasks from running at the same time
|
||||||
|
Files.createDirectories(cacheFile.getParent());
|
||||||
|
|
||||||
|
if (Files.exists(cacheFile)) {
|
||||||
|
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) {
|
||||||
|
// Success, cache exists and can be read
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().warn("Discarding invalid decompile cache file: {}", cacheFile, e);
|
||||||
|
Files.delete(cacheFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) {
|
||||||
|
runWithCache(serviceFactory, fs.getRoot());
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ExceptionUtil.processException(e, getDaemonUtilsContext().get());
|
ExceptionUtil.processException(e, getDaemonUtilsContext().get());
|
||||||
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e);
|
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLogger().info("Using decompile cache.");
|
|
||||||
|
|
||||||
try (var timer = new Timer("Decompiled sources with cache")) {
|
|
||||||
final Path cacheFile = getDecompileCacheFile().getAsFile().get().toPath();
|
|
||||||
|
|
||||||
if (getResetCache().get()) {
|
|
||||||
getLogger().warn("Resetting decompile cache");
|
|
||||||
Files.deleteIfExists(cacheFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO ensure we have a lock on this file to prevent multiple tasks from running at the same time
|
|
||||||
Files.createDirectories(cacheFile.getParent());
|
|
||||||
|
|
||||||
if (Files.exists(cacheFile)) {
|
|
||||||
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) {
|
|
||||||
// Success, cache exists and can be read
|
|
||||||
} catch (IOException e) {
|
|
||||||
getLogger().warn("Discarding invalid decompile cache file: {}", cacheFile, e);
|
|
||||||
Files.delete(cacheFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) {
|
|
||||||
runWithCache(fs.getRoot());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
ExceptionUtil.processException(e, getDaemonUtilsContext().get());
|
|
||||||
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runWithCache(Path cacheRoot) throws IOException {
|
private void runWithCache(ServiceFactory serviceFactory, Path cacheRoot) throws IOException {
|
||||||
final Path classesInputJar = getClassesInputJar().getSingleFile().toPath();
|
final Path classesInputJar = getClassesInputJar().getSingleFile().toPath();
|
||||||
final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath();
|
final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath();
|
||||||
final Path classesOutputJar = getClassesOutputJar().getSingleFile().toPath();
|
final Path classesOutputJar = getClassesOutputJar().getSingleFile().toPath();
|
||||||
final var cacheRules = new CachedFileStoreImpl.CacheRules(getMaxCachedFiles().get(), Duration.ofDays(getMaxCacheFileAge().get()));
|
final var cacheRules = new CachedFileStoreImpl.CacheRules(getMaxCachedFiles().get(), Duration.ofDays(getMaxCacheFileAge().get()));
|
||||||
final var decompileCache = new CachedFileStoreImpl<>(cacheRoot, CachedData.SERIALIZER, cacheRules);
|
final var decompileCache = new CachedFileStoreImpl<>(cacheRoot, CachedData.SERIALIZER, cacheRules);
|
||||||
final String cacheKey = getCacheKey();
|
final String cacheKey = getCacheKey(serviceFactory);
|
||||||
final CachedJarProcessor cachedJarProcessor = new CachedJarProcessor(decompileCache, cacheKey);
|
final CachedJarProcessor cachedJarProcessor = new CachedJarProcessor(decompileCache, cacheKey);
|
||||||
final CachedJarProcessor.WorkRequest workRequest;
|
final CachedJarProcessor.WorkRequest workRequest;
|
||||||
|
|
||||||
@@ -342,9 +320,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
Path workInputJar = workToDoJob.incomplete();
|
Path workInputJar = workToDoJob.incomplete();
|
||||||
@Nullable Path existingClasses = (job instanceof CachedJarProcessor.PartialWorkJob partialWorkJob) ? partialWorkJob.existingClasses() : null;
|
@Nullable Path existingClasses = (job instanceof CachedJarProcessor.PartialWorkJob partialWorkJob) ? partialWorkJob.existingClasses() : null;
|
||||||
|
|
||||||
if (getUnpickDefinitions().isPresent()) {
|
if (usingUnpick()) {
|
||||||
try (var timer = new Timer("Unpick")) {
|
try (var timer = new Timer("Unpick")) {
|
||||||
workInputJar = unpickJar(workInputJar, existingClasses);
|
UnpickService unpick = serviceFactory.get(getUnpickOptions());
|
||||||
|
workInputJar = unpick.unpickJar(workInputJar, existingClasses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,16 +360,17 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runWithoutCache() throws IOException {
|
private void runWithoutCache(ServiceFactory serviceFactory) throws IOException {
|
||||||
final Path classesInputJar = getClassesInputJar().getSingleFile().toPath();
|
final Path classesInputJar = getClassesInputJar().getSingleFile().toPath();
|
||||||
final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath();
|
final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath();
|
||||||
final Path classesOutputJar = getClassesOutputJar().getSingleFile().toPath();
|
final Path classesOutputJar = getClassesOutputJar().getSingleFile().toPath();
|
||||||
|
|
||||||
Path workClassesJar = classesInputJar;
|
Path workClassesJar = classesInputJar;
|
||||||
|
|
||||||
if (getUnpickDefinitions().isPresent()) {
|
if (usingUnpick()) {
|
||||||
try (var timer = new Timer("Unpick")) {
|
try (var timer = new Timer("Unpick")) {
|
||||||
workClassesJar = unpickJar(workClassesJar, null);
|
UnpickService unpick = serviceFactory.get(getUnpickOptions());
|
||||||
|
workClassesJar = unpick.unpickJar(workClassesJar, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,24 +407,24 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
Files.move(tempJar, classesOutputJar, StandardCopyOption.REPLACE_EXISTING);
|
Files.move(tempJar, classesOutputJar, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getCacheKey() {
|
private String getCacheKey(ServiceFactory serviceFactory) {
|
||||||
var sj = new StringJoiner(",");
|
var sj = new StringJoiner(",");
|
||||||
sj.add(getDecompilerCheckKey());
|
sj.add(getDecompilerCheckKey());
|
||||||
sj.add(getUnpickCacheKey());
|
|
||||||
|
if (usingUnpick()) {
|
||||||
|
UnpickService unpick = serviceFactory.get(getUnpickOptions());
|
||||||
|
sj.add(unpick.getUnpickCacheKey());
|
||||||
|
}
|
||||||
|
|
||||||
getLogger().info("Decompile cache data: {}", sj);
|
getLogger().info("Decompile cache data: {}", sj);
|
||||||
|
|
||||||
try {
|
return Checksum.of(sj.toString()).sha256().hex();
|
||||||
return Checksum.sha256Hex(sj.toString().getBytes(StandardCharsets.UTF_8));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDecompilerCheckKey() {
|
private String getDecompilerCheckKey() {
|
||||||
var sj = new StringJoiner(",");
|
var sj = new StringJoiner(",");
|
||||||
sj.add(decompilerOptions.getDecompilerClassName().get());
|
sj.add(decompilerOptions.getDecompilerClassName().get());
|
||||||
sj.add(fileCollectionHash(decompilerOptions.getClasspath()));
|
sj.add(Checksum.of(decompilerOptions.getClasspath()).sha256().hex());
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : decompilerOptions.getOptions().get().entrySet()) {
|
for (Map.Entry<String, String> entry : decompilerOptions.getOptions().get().entrySet()) {
|
||||||
sj.add(entry.getKey() + "=" + entry.getValue());
|
sj.add(entry.getKey() + "=" + entry.getValue());
|
||||||
@@ -453,19 +433,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
return sj.toString();
|
return sj.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUnpickCacheKey() {
|
|
||||||
if (!getUnpickDefinitions().isPresent()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
var sj = new StringJoiner(",");
|
|
||||||
sj.add(fileHash(getUnpickDefinitions().getAsFile().get()));
|
|
||||||
sj.add(fileCollectionHash(getUnpickConstantJar()));
|
|
||||||
sj.add(fileCollectionHash(getUnpickRuntimeClasspath()));
|
|
||||||
|
|
||||||
return sj.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ClassLineNumbers runDecompileJob(Path inputJar, Path outputJar, @Nullable Path existingJar) throws IOException {
|
private ClassLineNumbers runDecompileJob(Path inputJar, Path outputJar, @Nullable Path existingJar) throws IOException {
|
||||||
final Platform platform = Platform.CURRENT;
|
final Platform platform = Platform.CURRENT;
|
||||||
@@ -565,55 +532,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path unpickJar(Path inputJar, @Nullable Path existingClasses) {
|
|
||||||
final Path outputJar = getUnpickOutputJar().get().getAsFile().toPath();
|
|
||||||
final List<String> args = getUnpickArgs(inputJar, outputJar, existingClasses);
|
|
||||||
|
|
||||||
ExecResult result = getExecOperations().javaexec(spec -> {
|
|
||||||
spec.getMainClass().set("daomephsta.unpick.cli.Main");
|
|
||||||
spec.classpath(getUnpickRuntimeClasspath());
|
|
||||||
spec.args(args);
|
|
||||||
spec.systemProperty("java.util.logging.config.file", writeUnpickLogConfig().getAbsolutePath());
|
|
||||||
});
|
|
||||||
|
|
||||||
result.rethrowFailure();
|
|
||||||
|
|
||||||
return outputJar;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getUnpickArgs(Path inputJar, Path outputJar, @Nullable Path existingClasses) {
|
|
||||||
var fileArgs = new ArrayList<File>();
|
|
||||||
|
|
||||||
fileArgs.add(inputJar.toFile());
|
|
||||||
fileArgs.add(outputJar.toFile());
|
|
||||||
fileArgs.add(getUnpickDefinitions().get().getAsFile());
|
|
||||||
fileArgs.add(getUnpickConstantJar().getSingleFile());
|
|
||||||
|
|
||||||
for (File file : getUnpickClasspath()) {
|
|
||||||
fileArgs.add(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingClasses != null) {
|
|
||||||
fileArgs.add(existingClasses.toFile());
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileArgs.stream()
|
|
||||||
.map(File::getAbsolutePath)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private File writeUnpickLogConfig() {
|
|
||||||
final File unpickLoggingConfigFile = getUnpickLogConfig().getAsFile().get();
|
|
||||||
|
|
||||||
try (InputStream is = GenerateSourcesTask.class.getClassLoader().getResourceAsStream("unpick-logging.properties")) {
|
|
||||||
Files.copy(Objects.requireNonNull(is), unpickLoggingConfigFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Failed to copy unpick logging config", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return unpickLoggingConfigFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void remapLineNumbers(ClassLineNumbers lineNumbers, Path inputJar, Path outputJar) throws IOException {
|
private void remapLineNumbers(ClassLineNumbers lineNumbers, Path inputJar, Path outputJar) throws IOException {
|
||||||
Objects.requireNonNull(lineNumbers, "lineNumbers");
|
Objects.requireNonNull(lineNumbers, "lineNumbers");
|
||||||
final var remapper = new LineNumberRemapper(lineNumbers);
|
final var remapper = new LineNumberRemapper(lineNumbers);
|
||||||
@@ -689,6 +607,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
return !Boolean.getBoolean("fabric.loom.genSources.debug");
|
return !Boolean.getBoolean("fabric.loom.genSources.debug");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean usingUnpick() {
|
||||||
|
return getUnpickOptions().isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
public interface DecompileParams extends WorkParameters {
|
public interface DecompileParams extends WorkParameters {
|
||||||
Property<DecompilerOptions.Dto> getDecompilerOptions();
|
Property<DecompilerOptions.Dto> getDecompilerOptions();
|
||||||
|
|
||||||
@@ -816,26 +738,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String fileHash(File file) {
|
|
||||||
try {
|
|
||||||
return Checksum.sha256Hex(Files.readAllBytes(file.toPath()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String fileCollectionHash(FileCollection files) {
|
|
||||||
var sj = new StringJoiner(",");
|
|
||||||
|
|
||||||
files.getFiles()
|
|
||||||
.stream()
|
|
||||||
.sorted(Comparator.comparing(File::getAbsolutePath))
|
|
||||||
.map(GenerateSourcesTask::fileHash)
|
|
||||||
.forEach(sj::add);
|
|
||||||
|
|
||||||
return sj.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface MappingsProcessor {
|
public interface MappingsProcessor {
|
||||||
boolean transform(MemoryMappingTree mappings);
|
boolean transform(MemoryMappingTree mappings);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,18 @@
|
|||||||
|
|
||||||
package net.fabricmc.loom.task;
|
package net.fabricmc.loom.task;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.Task;
|
import org.gradle.api.Task;
|
||||||
|
import org.gradle.api.file.FileCollection;
|
||||||
import org.gradle.api.provider.Provider;
|
import org.gradle.api.provider.Provider;
|
||||||
|
import org.gradle.api.tasks.Sync;
|
||||||
import org.gradle.api.tasks.TaskContainer;
|
import org.gradle.api.tasks.TaskContainer;
|
||||||
|
import org.gradle.api.tasks.TaskOutputs;
|
||||||
import org.gradle.api.tasks.TaskProvider;
|
import org.gradle.api.tasks.TaskProvider;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
@@ -41,6 +46,8 @@ import net.fabricmc.loom.task.launch.GenerateDLIConfigTask;
|
|||||||
import net.fabricmc.loom.task.launch.GenerateLog4jConfigTask;
|
import net.fabricmc.loom.task.launch.GenerateLog4jConfigTask;
|
||||||
import net.fabricmc.loom.task.launch.GenerateRemapClasspathTask;
|
import net.fabricmc.loom.task.launch.GenerateRemapClasspathTask;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
|
import net.fabricmc.loom.util.LoomVersions;
|
||||||
|
import net.fabricmc.loom.util.Platform;
|
||||||
import net.fabricmc.loom.util.gradle.GradleUtils;
|
import net.fabricmc.loom.util.gradle.GradleUtils;
|
||||||
|
|
||||||
public abstract class LoomTasks implements Runnable {
|
public abstract class LoomTasks implements Runnable {
|
||||||
@@ -132,17 +139,28 @@ public abstract class LoomTasks implements Runnable {
|
|||||||
|
|
||||||
private void registerRunTasks() {
|
private void registerRunTasks() {
|
||||||
LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||||
|
final boolean renderDocSupported = RenderDocRunTask.isSupported(Platform.CURRENT);
|
||||||
|
|
||||||
Preconditions.checkArgument(extension.getRunConfigs().size() == 0, "Run configurations must not be registered before loom");
|
Preconditions.checkArgument(extension.getRunConfigs().size() == 0, "Run configurations must not be registered before loom");
|
||||||
|
|
||||||
extension.getRunConfigs().whenObjectAdded(config -> {
|
extension.getRunConfigs().whenObjectAdded(config -> {
|
||||||
getTasks().register(getRunConfigTaskName(config), RunGameTask.class, config).configure(t -> {
|
var runTask = getTasks().register(getRunConfigTaskName(config), RunGameTask.class, config);
|
||||||
|
|
||||||
|
runTask.configure(t -> {
|
||||||
t.setDescription("Starts the '" + config.getConfigName() + "' run configuration");
|
t.setDescription("Starts the '" + config.getConfigName() + "' run configuration");
|
||||||
|
|
||||||
t.dependsOn(config.getEnvironment().equals("client") ? "configureClientLaunch" : "configureLaunch");
|
t.dependsOn(config.getEnvironment().equals("client") ? "configureClientLaunch" : "configureLaunch");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (config.getName().equals("client") && renderDocSupported) {
|
||||||
|
getTasks().register("runClientRenderDoc", RenderDocRunTask.class, config);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (renderDocSupported) {
|
||||||
|
configureRenderDocTasks();
|
||||||
|
}
|
||||||
|
|
||||||
extension.getRunConfigs().whenObjectRemoved(runConfigSettings -> {
|
extension.getRunConfigs().whenObjectRemoved(runConfigSettings -> {
|
||||||
getTasks().named(getRunConfigTaskName(runConfigSettings), task -> {
|
getTasks().named(getRunConfigTaskName(runConfigSettings), task -> {
|
||||||
// Disable the task so it can't be run
|
// Disable the task so it can't be run
|
||||||
@@ -170,7 +188,58 @@ public abstract class LoomTasks implements Runnable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension.getRunConfigs().removeIf(settings -> settings.getName().equals(taskName));
|
extension.getRunConfigs().removeIf(settings -> settings.getName().equals(taskName)
|
||||||
|
|| settings.getName().equals(taskName + "RenderDoc"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureRenderDocTasks() {
|
||||||
|
final Platform.OperatingSystem operatingSystem = Platform.CURRENT.getOperatingSystem();
|
||||||
|
final String renderDocVersion = LoomVersions.RENDERDOC.version();
|
||||||
|
final String renderDocBaseName = operatingSystem.isWindows()
|
||||||
|
? "RenderDoc_%s_64".formatted(renderDocVersion)
|
||||||
|
: "renderdoc_%s".formatted(renderDocVersion);
|
||||||
|
final String renderDocFilename = operatingSystem.isWindows()
|
||||||
|
? "%s.zip".formatted(renderDocBaseName)
|
||||||
|
: "%s.tar.gz".formatted(renderDocBaseName);
|
||||||
|
final String renderDocUrl = "https://maven.fabricmc.net/org/renderdoc/%s".formatted(renderDocFilename);
|
||||||
|
final String executableExt = operatingSystem.isWindows() ? ".exe" : "";
|
||||||
|
|
||||||
|
var downloadRenderDoc = getTasks().register("downloadRenderDoc", DownloadTask.class, task -> {
|
||||||
|
task.setGroup(Constants.TaskGroup.FABRIC);
|
||||||
|
|
||||||
|
task.getUrl().set(renderDocUrl);
|
||||||
|
task.getOutput().set(getProject().getLayout().getBuildDirectory().file(renderDocFilename));
|
||||||
|
});
|
||||||
|
|
||||||
|
var extractRenderDoc = getTasks().register("extractRenderDoc", Sync.class, task -> {
|
||||||
|
task.setGroup(Constants.TaskGroup.FABRIC);
|
||||||
|
|
||||||
|
if (operatingSystem.isWindows()) {
|
||||||
|
task.from(getProject().zipTree(downloadRenderDoc.map(DownloadTask::getOutput)));
|
||||||
|
} else {
|
||||||
|
task.from(getProject().tarTree(downloadRenderDoc.map(DownloadTask::getOutput)));
|
||||||
|
}
|
||||||
|
|
||||||
|
task.into(getProject().getLayout().getBuildDirectory().dir("renderdoc"));
|
||||||
|
});
|
||||||
|
|
||||||
|
Provider<File> renderDocDir = extractRenderDoc.map(Sync::getOutputs)
|
||||||
|
.map(TaskOutputs::getFiles)
|
||||||
|
.map(FileCollection::getSingleFile)
|
||||||
|
.map(dir -> new File(dir, renderDocBaseName));
|
||||||
|
|
||||||
|
if (operatingSystem.isLinux()) {
|
||||||
|
renderDocDir = renderDocDir.map(dir -> new File(dir, "bin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider<File> renderDocCMD = renderDocDir.map(dir -> new File(dir, "renderdoccmd" + executableExt));
|
||||||
|
Provider<File> renderDocUI = renderDocDir.map(dir -> new File(dir, "qrenderdoc" + executableExt));
|
||||||
|
|
||||||
|
getTasks().register("startRenderDocUI", RenderDocRunUITask.class, task -> task.getRenderDocExecutable().fileProvider(renderDocUI));
|
||||||
|
|
||||||
|
getTasks().withType(RenderDocRunTask.class).configureEach(task -> {
|
||||||
|
task.getRenderDocExecutable().fileProvider(renderDocCMD);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
93
src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java
Normal file
93
src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.task;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.gradle.api.file.RegularFileProperty;
|
||||||
|
import org.gradle.api.provider.ListProperty;
|
||||||
|
import org.gradle.api.tasks.Input;
|
||||||
|
import org.gradle.api.tasks.InputFile;
|
||||||
|
import org.gradle.process.CommandLineArgumentProvider;
|
||||||
|
import org.gradle.process.ExecOperations;
|
||||||
|
import org.gradle.process.ExecResult;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
|
||||||
|
import net.fabricmc.loom.util.Constants;
|
||||||
|
import net.fabricmc.loom.util.Platform;
|
||||||
|
|
||||||
|
public abstract class RenderDocRunTask extends RunGameTask {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(RenderDocRunTask.class);
|
||||||
|
|
||||||
|
@InputFile
|
||||||
|
public abstract RegularFileProperty getRenderDocExecutable();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public abstract ListProperty<String> getRenderDocArgs();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected abstract ExecOperations getExecOperations();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RenderDocRunTask(RunConfigSettings settings) {
|
||||||
|
super(settings);
|
||||||
|
setGroup(Constants.TaskGroup.FABRIC);
|
||||||
|
dependsOn("configureClientLaunch");
|
||||||
|
getRenderDocArgs().addAll("capture", "--wait-for-exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exec() {
|
||||||
|
ExecResult result = getExecOperations().exec(exec -> {
|
||||||
|
exec.workingDir(new File(getProjectDir().get(), getInternalRunDir().get()));
|
||||||
|
exec.environment(getInternalEnvironmentVars().get());
|
||||||
|
|
||||||
|
exec.commandLine(getRenderDocExecutable().get().getAsFile());
|
||||||
|
exec.args(getRenderDocArgs().get());
|
||||||
|
exec.args("--working-dir", new File(getProjectDir().get(), getInternalRunDir().get()));
|
||||||
|
exec.args(getJavaLauncher().get().getExecutablePath());
|
||||||
|
exec.args(getJvmArgs());
|
||||||
|
exec.args(getMainClass().get());
|
||||||
|
|
||||||
|
for (CommandLineArgumentProvider provider : getArgumentProviders()) {
|
||||||
|
exec.args(provider.asArguments());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Running command: {}", exec.getCommandLine());
|
||||||
|
});
|
||||||
|
result.assertNormalExitValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSupported(Platform platform) {
|
||||||
|
final Platform.OperatingSystem os = platform.getOperatingSystem();
|
||||||
|
final Platform.Architecture arch = platform.getArchitecture();
|
||||||
|
// RenderDoc does support 32-bit Windows, but I cannot be bothered to test/maintain it
|
||||||
|
return (os.isLinux() || os.isWindows()) && arch.isX64();
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/main/java/net/fabricmc/loom/task/RenderDocRunUITask.java
Normal file
51
src/main/java/net/fabricmc/loom/task/RenderDocRunUITask.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.task;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask;
|
||||||
|
import org.gradle.api.file.RegularFileProperty;
|
||||||
|
import org.gradle.api.tasks.InputFile;
|
||||||
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.Constants;
|
||||||
|
|
||||||
|
public abstract class RenderDocRunUITask extends DefaultTask {
|
||||||
|
@InputFile
|
||||||
|
public abstract RegularFileProperty getRenderDocExecutable();
|
||||||
|
|
||||||
|
public RenderDocRunUITask() {
|
||||||
|
setGroup(Constants.TaskGroup.FABRIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
public void run() throws IOException {
|
||||||
|
ProcessBuilder builder = new ProcessBuilder()
|
||||||
|
.command(getRenderDocExecutable().getAsFile().get().getAbsolutePath());
|
||||||
|
builder.start();
|
||||||
|
// Allow to run in the background.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
package net.fabricmc.loom.task.service;
|
package net.fabricmc.loom.task.service;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -35,9 +36,12 @@ import org.gradle.api.provider.Property;
|
|||||||
import org.gradle.api.provider.Provider;
|
import org.gradle.api.provider.Provider;
|
||||||
import org.gradle.api.tasks.Input;
|
import org.gradle.api.tasks.Input;
|
||||||
import org.gradle.api.tasks.InputFile;
|
import org.gradle.api.tasks.InputFile;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
|
||||||
|
import net.fabricmc.loom.task.AbstractRemapJarTask;
|
||||||
import net.fabricmc.loom.util.TinyRemapperHelper;
|
import net.fabricmc.loom.util.TinyRemapperHelper;
|
||||||
import net.fabricmc.loom.util.service.Service;
|
import net.fabricmc.loom.util.service.Service;
|
||||||
import net.fabricmc.loom.util.service.ServiceFactory;
|
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||||
@@ -52,6 +56,8 @@ import net.fabricmc.tinyremapper.IMappingProvider;
|
|||||||
public final class MappingsService extends Service<MappingsService.Options> implements Closeable {
|
public final class MappingsService extends Service<MappingsService.Options> implements Closeable {
|
||||||
public static ServiceType<Options, MappingsService> TYPE = new ServiceType<>(Options.class, MappingsService.class);
|
public static ServiceType<Options, MappingsService> TYPE = new ServiceType<>(Options.class, MappingsService.class);
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(TinyRemapperService.class);
|
||||||
|
|
||||||
// TODO use a nested TinyMappingsService instead of duplicating it
|
// TODO use a nested TinyMappingsService instead of duplicating it
|
||||||
public interface Options extends Service.Options {
|
public interface Options extends Service.Options {
|
||||||
@InputFile
|
@InputFile
|
||||||
@@ -91,6 +97,36 @@ public final class MappingsService extends Service<MappingsService.Options> impl
|
|||||||
return createOptions(project, LoomGradleExtension.get(project).getPlatformMappingFile(), from, to, false);
|
return createOptions(project, LoomGradleExtension.get(project).getPlatformMappingFile(), from, to, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Provider<MappingsService.Options> createForRemapTask(AbstractRemapJarTask remapJarTask) {
|
||||||
|
final Project project = remapJarTask.getProject();
|
||||||
|
|
||||||
|
return project.provider(() -> {
|
||||||
|
if (remapJarTask.getCustomMappings().isEmpty()) {
|
||||||
|
LOGGER.debug("Using default project mappings for remapping");
|
||||||
|
return MappingsService.createOptionsWithProjectMappings(
|
||||||
|
project,
|
||||||
|
remapJarTask.getSourceNamespace(),
|
||||||
|
remapJarTask.getTargetNamespace()
|
||||||
|
).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom mappings:
|
||||||
|
File mappingsFile = remapJarTask.getCustomMappings().getSingleFile();
|
||||||
|
|
||||||
|
if (mappingsFile.getName().endsWith(".zip") || mappingsFile.getName().endsWith(".jar")) {
|
||||||
|
mappingsFile = project.zipTree(mappingsFile).matching(patternFilterable -> patternFilterable.include("mappings/mappings.tiny")).getSingleFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Using custom mappings for remap task: {}", mappingsFile);
|
||||||
|
return MappingsService.createOptions(
|
||||||
|
project,
|
||||||
|
mappingsFile.toPath(),
|
||||||
|
remapJarTask.getSourceNamespace(), remapJarTask.getTargetNamespace(),
|
||||||
|
false)
|
||||||
|
.get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public MappingsService(Options options, ServiceFactory serviceFactory) {
|
public MappingsService(Options options, ServiceFactory serviceFactory) {
|
||||||
super(options, serviceFactory);
|
super(options, serviceFactory);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ import java.io.IOException;
|
|||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import org.cadixdev.lorenz.MappingSet;
|
import org.cadixdev.lorenz.MappingSet;
|
||||||
import org.cadixdev.mercury.Mercury;
|
import org.cadixdev.mercury.Mercury;
|
||||||
import org.cadixdev.mercury.remapper.MercuryRemapper;
|
import org.cadixdev.mercury.remapper.MercuryRemapper;
|
||||||
@@ -181,7 +181,7 @@ public class MigrateMappingsService extends Service<MigrateMappingsService.Optio
|
|||||||
}
|
}
|
||||||
} catch (IllegalDependencyNotation ignored) {
|
} catch (IllegalDependencyNotation ignored) {
|
||||||
LOGGER.info("Could not locate mappings, presuming V2 Yarn");
|
LOGGER.info("Could not locate mappings, presuming V2 Yarn");
|
||||||
return project.getConfigurations().detachedConfiguration(project.getDependencies().create(ImmutableMap.of("group", "net.fabricmc", "name", "yarn", "version", mappings, "classifier", "v2")));
|
return project.getConfigurations().detachedConfiguration(project.getDependencies().create(Map.of("group", "net.fabricmc", "name", "yarn", "version", mappings, "classifier", "v2")));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException("Failed to resolve mappings", e);
|
throw new UncheckedIOException("Failed to resolve mappings", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,11 +68,7 @@ public final class SourceRemapperService extends Service<SourceRemapperService.O
|
|||||||
|
|
||||||
public static Provider<Options> createOptions(RemapSourcesJarTask task) {
|
public static Provider<Options> createOptions(RemapSourcesJarTask task) {
|
||||||
return TYPE.create(task.getProject(), o -> {
|
return TYPE.create(task.getProject(), o -> {
|
||||||
o.getMappings().set(MappingsService.createOptionsWithProjectMappings(
|
o.getMappings().set(MappingsService.createForRemapTask(task));
|
||||||
task.getProject(),
|
|
||||||
task.getSourceNamespace(),
|
|
||||||
task.getTargetNamespace()
|
|
||||||
));
|
|
||||||
o.getJavaCompileRelease().set(getJavaCompileRelease(task.getProject()));
|
o.getJavaCompileRelease().set(getJavaCompileRelease(task.getProject()));
|
||||||
o.getClasspath().from(task.getClasspath());
|
o.getClasspath().from(task.getClasspath());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import org.gradle.api.Project;
|
|||||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||||
import org.gradle.api.file.ConfigurableFileCollection;
|
import org.gradle.api.file.ConfigurableFileCollection;
|
||||||
import org.gradle.api.file.FileCollection;
|
import org.gradle.api.file.FileCollection;
|
||||||
|
import org.gradle.api.plugins.JavaPlugin;
|
||||||
import org.gradle.api.provider.ListProperty;
|
import org.gradle.api.provider.ListProperty;
|
||||||
import org.gradle.api.provider.Property;
|
import org.gradle.api.provider.Property;
|
||||||
import org.gradle.api.provider.Provider;
|
import org.gradle.api.provider.Provider;
|
||||||
@@ -51,6 +52,7 @@ import org.gradle.api.tasks.Optional;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
import net.fabricmc.loom.LoomGradleExtension;
|
||||||
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.build.IntermediaryNamespaces;
|
import net.fabricmc.loom.build.IntermediaryNamespaces;
|
||||||
import net.fabricmc.loom.extension.RemapperExtensionHolder;
|
import net.fabricmc.loom.extension.RemapperExtensionHolder;
|
||||||
import net.fabricmc.loom.task.AbstractRemapJarTask;
|
import net.fabricmc.loom.task.AbstractRemapJarTask;
|
||||||
@@ -66,7 +68,7 @@ import net.fabricmc.tinyremapper.InputTag;
|
|||||||
import net.fabricmc.tinyremapper.TinyRemapper;
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
import net.fabricmc.tinyremapper.extension.mixin.MixinExtension;
|
import net.fabricmc.tinyremapper.extension.mixin.MixinExtension;
|
||||||
|
|
||||||
public class TinyRemapperService extends Service<TinyRemapperService.Options> implements Closeable {
|
public class TinyRemapperService extends Service<TinyRemapperService.Options> implements TinyRemapperServiceInterface, Closeable {
|
||||||
public static final ServiceType<Options, TinyRemapperService> TYPE = new ServiceType<>(Options.class, TinyRemapperService.class);
|
public static final ServiceType<Options, TinyRemapperService> TYPE = new ServiceType<>(Options.class, TinyRemapperService.class);
|
||||||
|
|
||||||
public interface Options extends Service.Options {
|
public interface Options extends Service.Options {
|
||||||
@@ -103,7 +105,7 @@ public class TinyRemapperService extends Service<TinyRemapperService.Options> im
|
|||||||
|
|
||||||
options.getFrom().set(remapJarTask.getSourceNamespace());
|
options.getFrom().set(remapJarTask.getSourceNamespace());
|
||||||
options.getTo().set(remapJarTask.getTargetNamespace());
|
options.getTo().set(remapJarTask.getTargetNamespace());
|
||||||
options.getMappings().add(MappingsService.createOptionsWithProjectMappings(project, options.getFrom(), options.getTo()));
|
options.getMappings().add(MappingsService.createForRemapTask(remapJarTask));
|
||||||
|
|
||||||
if (legacyMixin) {
|
if (legacyMixin) {
|
||||||
options.getMixinApMappings().set(MixinAPMappingService.createOptions(project, options.getFrom(), options.getTo().map(to -> IntermediaryNamespaces.replaceMixinIntermediaryNamespace(project, to))));
|
options.getMixinApMappings().set(MixinAPMappingService.createOptions(project, options.getFrom(), options.getTo().map(to -> IntermediaryNamespaces.replaceMixinIntermediaryNamespace(project, to))));
|
||||||
@@ -117,6 +119,56 @@ public class TinyRemapperService extends Service<TinyRemapperService.Options> im
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Provider<Options> createSimple(Project project, Provider<String> from, Provider<String> to, ClasspathLibraries classpathLibraries) {
|
||||||
|
return TYPE.create(project, options -> {
|
||||||
|
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
|
final FileCollection classpath = getRemapClasspath(project, from, classpathLibraries);
|
||||||
|
|
||||||
|
options.getFrom().set(from);
|
||||||
|
options.getTo().set(to);
|
||||||
|
options.getMappings().add(MappingsService.createOptionsWithProjectMappings(project, options.getFrom(), options.getTo()));
|
||||||
|
options.getUselegacyMixinAP().set(true);
|
||||||
|
options.getClasspath().from(classpath);
|
||||||
|
options.getKnownIndyBsms().set(extension.getKnownIndyBsms().get().stream().sorted().toList());
|
||||||
|
options.getRemapperExtensions().set(extension.getRemapperExtensions());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileCollection getRemapClasspath(Project project, Provider<String> from, ClasspathLibraries classpathLibraries) {
|
||||||
|
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
|
final ConfigurationContainer configurations = project.getConfigurations();
|
||||||
|
|
||||||
|
if (from.get().equals(MappingsNamespace.INTERMEDIARY.toString())) {
|
||||||
|
ConfigurableFileCollection files = project.files(extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY));
|
||||||
|
|
||||||
|
if (classpathLibraries == ClasspathLibraries.INCLUDE) {
|
||||||
|
files = files.from(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (classpathLibraries == ClasspathLibraries.INCLUDE) {
|
||||||
|
return configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
return configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)
|
||||||
|
.minus(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES))
|
||||||
|
.minus(configurations.getByName(Constants.Configurations.MINECRAFT_RUNTIME_LIBRARIES));
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ClasspathLibraries {
|
||||||
|
/**
|
||||||
|
* Default, in most cases the Minecraft libraries are not required as they are not obfuscated and do not need to be queried.
|
||||||
|
*/
|
||||||
|
EXCLUDE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses more memory, but provides a complete index of all the classes within the libraries.
|
||||||
|
*/
|
||||||
|
INCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
private TinyRemapper tinyRemapper;
|
private TinyRemapper tinyRemapper;
|
||||||
@Nullable
|
@Nullable
|
||||||
private KotlinRemapperClassloader kotlinRemapperClassloader;
|
private KotlinRemapperClassloader kotlinRemapperClassloader;
|
||||||
@@ -179,11 +231,13 @@ public class TinyRemapperService extends Service<TinyRemapperService.Options> im
|
|||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public TinyRemapper getTinyRemapperForRemapping() {
|
public TinyRemapper getTinyRemapperForRemapping() {
|
||||||
isRemapping = true;
|
isRemapping = true;
|
||||||
return Objects.requireNonNull(tinyRemapper, "Tiny remapper has not been setup");
|
return Objects.requireNonNull(tinyRemapper, "Tiny remapper has not been setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public TinyRemapper getTinyRemapperForInputs() {
|
public TinyRemapper getTinyRemapperForInputs() {
|
||||||
if (isRemapping) {
|
if (isRemapping) {
|
||||||
throw new IllegalStateException("Cannot read inputs as remapping has already started");
|
throw new IllegalStateException("Cannot read inputs as remapping has already started");
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.task.service;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public interface TinyRemapperServiceInterface {
|
||||||
|
TinyRemapper getTinyRemapperForRemapping();
|
||||||
|
TinyRemapper getTinyRemapperForInputs();
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.task.service;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import daomephsta.unpick.constantmappers.datadriven.parser.v3.UnpickV3Reader;
|
||||||
|
import daomephsta.unpick.constantmappers.datadriven.parser.v3.UnpickV3Remapper;
|
||||||
|
import daomephsta.unpick.constantmappers.datadriven.parser.v3.UnpickV3Writer;
|
||||||
|
import daomephsta.unpick.constantmappers.datadriven.tree.UnpickV3Visitor;
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.provider.Provider;
|
||||||
|
import org.gradle.api.tasks.Nested;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
|
import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata;
|
||||||
|
import net.fabricmc.loom.util.JarPackageIndex;
|
||||||
|
import net.fabricmc.loom.util.service.Service;
|
||||||
|
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||||
|
import net.fabricmc.loom.util.service.ServiceType;
|
||||||
|
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||||
|
import net.fabricmc.tinyremapper.api.TrClass;
|
||||||
|
import net.fabricmc.tinyremapper.api.TrField;
|
||||||
|
|
||||||
|
public class UnpickRemapperService extends Service<UnpickRemapperService.Options> {
|
||||||
|
public static final ServiceType<Options, UnpickRemapperService> TYPE = new ServiceType<>(Options.class, UnpickRemapperService.class);
|
||||||
|
|
||||||
|
public interface Options extends Service.Options {
|
||||||
|
@Nested
|
||||||
|
Property<TinyRemapperService.Options> getTinyRemapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Provider<Options> createOptions(Project project, UnpickMetadata.V2 metadata) {
|
||||||
|
return TYPE.create(project, options -> {
|
||||||
|
options.getTinyRemapper().set(TinyRemapperService.createSimple(project,
|
||||||
|
project.provider(metadata::namespace),
|
||||||
|
project.provider(MappingsNamespace.NAMED::toString),
|
||||||
|
TinyRemapperService.ClasspathLibraries.INCLUDE // Must include the full set of libraries on classpath so fields can be looked up. This does use a lot of memory however...
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnpickRemapperService(Options options, ServiceFactory serviceFactory) {
|
||||||
|
super(options, serviceFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the remapped definitions.
|
||||||
|
*/
|
||||||
|
public String remap(File input) throws IOException {
|
||||||
|
TinyRemapperServiceInterface tinyRemapperService = getServiceFactory().get(getOptions().getTinyRemapper());
|
||||||
|
TinyRemapper tinyRemapper = tinyRemapperService.getTinyRemapperForRemapping();
|
||||||
|
|
||||||
|
List<Path> classpath = getOptions().getTinyRemapper().get().getClasspath().getFiles().stream().map(File::toPath).toList();
|
||||||
|
JarPackageIndex packageIndex = JarPackageIndex.create(classpath);
|
||||||
|
|
||||||
|
return doRemap(input, tinyRemapper, packageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String doRemap(File input, TinyRemapper remapper, JarPackageIndex packageIndex) throws IOException {
|
||||||
|
try (Reader fileReader = new BufferedReader(new FileReader(input));
|
||||||
|
var reader = new UnpickV3Reader(fileReader)) {
|
||||||
|
var writer = new UnpickV3Writer();
|
||||||
|
reader.accept(new UnpickRemapper(writer, remapper, packageIndex));
|
||||||
|
return writer.getOutput().replace(System.lineSeparator(), "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class UnpickRemapper extends UnpickV3Remapper {
|
||||||
|
private final TinyRemapper tinyRemapper;
|
||||||
|
private final Remapper remapper;
|
||||||
|
private final JarPackageIndex jarPackageIndex;
|
||||||
|
|
||||||
|
private UnpickRemapper(UnpickV3Visitor downstream, TinyRemapper tinyRemapper, JarPackageIndex jarPackageIndex) {
|
||||||
|
super(downstream);
|
||||||
|
this.tinyRemapper = tinyRemapper;
|
||||||
|
this.remapper = tinyRemapper.getEnvironment().getRemapper();
|
||||||
|
this.jarPackageIndex = jarPackageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String mapClassName(String className) {
|
||||||
|
return remapper.map(className.replace('.', '/')).replace('/', '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String mapFieldName(String className, String fieldName, String fieldDesc) {
|
||||||
|
return remapper.mapFieldName(className.replace('.', '/'), fieldName, fieldDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String mapMethodName(String className, String methodName, String methodDesc) {
|
||||||
|
return remapper.mapMethodName(className.replace('.', '/'), methodName, methodDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all classes in the given package, not recursively.
|
||||||
|
@Override
|
||||||
|
protected List<String> getClassesInPackage(String pkg) {
|
||||||
|
return jarPackageIndex.packages().getOrDefault(pkg, Collections.emptyList())
|
||||||
|
.stream()
|
||||||
|
.map(className -> pkg + "." + className)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getFieldDesc(String className, String fieldName) {
|
||||||
|
TrClass trClass = tinyRemapper.getEnvironment().getClass(className.replace('.', '/'));
|
||||||
|
|
||||||
|
if (trClass != null) {
|
||||||
|
for (TrField trField : trClass.getFields()) {
|
||||||
|
if (trField.getName().equals(fieldName)) {
|
||||||
|
return trField.getDesc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String fieldDesc = getFieldDescFromReflection(className, fieldName);
|
||||||
|
|
||||||
|
if (fieldDesc == null) {
|
||||||
|
throw new IllegalStateException("Could not find field " + fieldName + " in class " + className);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getFieldDescFromReflection(String className, String fieldName) {
|
||||||
|
try {
|
||||||
|
// Use the bootstrap class loader, which should only resolve classes from the JDK.
|
||||||
|
// Don't run the static initializer.
|
||||||
|
Class<?> clazz = Class.forName(className, false, null);
|
||||||
|
Field field = clazz.getDeclaredField(fieldName);
|
||||||
|
return Type.getDescriptor(field.getType());
|
||||||
|
} catch (ClassNotFoundException | NoSuchFieldException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
270
src/main/java/net/fabricmc/loom/task/service/UnpickService.java
Normal file
270
src/main/java/net/fabricmc/loom/task/service/UnpickService.java
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.task.service;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import daomephsta.unpick.api.ConstantUninliner;
|
||||||
|
import daomephsta.unpick.api.classresolvers.ClassResolvers;
|
||||||
|
import daomephsta.unpick.api.classresolvers.IClassResolver;
|
||||||
|
import daomephsta.unpick.api.constantgroupers.ConstantGroupers;
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection;
|
||||||
|
import org.gradle.api.file.RegularFileProperty;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.provider.Provider;
|
||||||
|
import org.gradle.api.tasks.Input;
|
||||||
|
import org.gradle.api.tasks.InputFile;
|
||||||
|
import org.gradle.api.tasks.InputFiles;
|
||||||
|
import org.gradle.api.tasks.Nested;
|
||||||
|
import org.gradle.api.tasks.Optional;
|
||||||
|
import org.gradle.api.tasks.OutputFile;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
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.providers.mappings.MappingConfiguration;
|
||||||
|
import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata;
|
||||||
|
import net.fabricmc.loom.task.GenerateSourcesTask;
|
||||||
|
import net.fabricmc.loom.util.AsyncZipProcessor;
|
||||||
|
import net.fabricmc.loom.util.Checksum;
|
||||||
|
import net.fabricmc.loom.util.Constants;
|
||||||
|
import net.fabricmc.loom.util.FileSystemUtil;
|
||||||
|
import net.fabricmc.loom.util.SLF4JAdapterHandler;
|
||||||
|
import net.fabricmc.loom.util.service.Service;
|
||||||
|
import net.fabricmc.loom.util.service.ServiceFactory;
|
||||||
|
import net.fabricmc.loom.util.service.ServiceType;
|
||||||
|
|
||||||
|
public class UnpickService extends Service<UnpickService.Options> {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(UnpickService.class);
|
||||||
|
private static final java.util.logging.Logger JAVA_LOGGER = java.util.logging.Logger.getLogger("loom-unpick-service");
|
||||||
|
|
||||||
|
static {
|
||||||
|
JAVA_LOGGER.setUseParentHandlers(false);
|
||||||
|
JAVA_LOGGER.addHandler(new SLF4JAdapterHandler(LOGGER, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final ServiceType<Options, UnpickService> TYPE = new ServiceType<>(Options.class, UnpickService.class);
|
||||||
|
|
||||||
|
public interface Options extends Service.Options {
|
||||||
|
@InputFile
|
||||||
|
RegularFileProperty getUnpickDefinitions();
|
||||||
|
|
||||||
|
@Optional
|
||||||
|
@Nested
|
||||||
|
Property<UnpickRemapperService.Options> getUnpickRemapperService();
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
ConfigurableFileCollection getUnpickConstantJar();
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
ConfigurableFileCollection getUnpickClasspath();
|
||||||
|
|
||||||
|
@OutputFile
|
||||||
|
RegularFileProperty getUnpickOutputJar();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
Property<Boolean> getLenient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Provider<Options> createOptions(GenerateSourcesTask task) {
|
||||||
|
final Project project = task.getProject();
|
||||||
|
return TYPE.maybeCreate(project, options -> {
|
||||||
|
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
|
MappingConfiguration mappingConfiguration = extension.getMappingConfiguration();
|
||||||
|
|
||||||
|
if (!mappingConfiguration.hasUnpickDefinitions()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnpickMetadata unpickMetadata = mappingConfiguration.getUnpickMetadata();
|
||||||
|
|
||||||
|
if (unpickMetadata instanceof UnpickMetadata.V2 v2) {
|
||||||
|
if (!Objects.equals(v2.namespace(), MappingsNamespace.NAMED.toString())) {
|
||||||
|
options.getUnpickRemapperService().set(UnpickRemapperService.createOptions(project, v2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationContainer configurations = project.getConfigurations();
|
||||||
|
File mappingsWorkingDir = mappingConfiguration.mappingsWorkingDir().toFile();
|
||||||
|
|
||||||
|
options.getUnpickDefinitions().set(mappingConfiguration.getUnpickDefinitionsFile());
|
||||||
|
options.getUnpickOutputJar().set(task.getInputJarName().map(s -> project.getLayout()
|
||||||
|
.dir(project.provider(() -> mappingsWorkingDir)).get().file(s + "-unpicked.jar")));
|
||||||
|
options.getUnpickConstantJar().setFrom(configurations.getByName(Constants.Configurations.MAPPING_CONSTANTS));
|
||||||
|
options.getUnpickClasspath().setFrom(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
|
||||||
|
options.getUnpickClasspath().from(configurations.getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED));
|
||||||
|
options.getLenient().set(unpickMetadata instanceof UnpickMetadata.V1);
|
||||||
|
extension.getMinecraftJars(MappingsNamespace.NAMED).forEach(options.getUnpickClasspath()::from);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnpickService(Options options, ServiceFactory serviceFactory) {
|
||||||
|
super(options, serviceFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path unpickJar(Path inputJar, @Nullable Path existingClasses) throws IOException {
|
||||||
|
final List<Path> classpath = Stream.of(
|
||||||
|
getOptions().getUnpickClasspath().getFiles().stream().map(File::toPath),
|
||||||
|
getOptions().getUnpickConstantJar().getFiles().stream().map(File::toPath),
|
||||||
|
Stream.of(inputJar),
|
||||||
|
Stream.ofNullable(existingClasses)
|
||||||
|
).flatMap(Function.identity()).toList();
|
||||||
|
final Path outputJar = getOptions().getUnpickOutputJar().get().getAsFile().toPath();
|
||||||
|
Files.deleteIfExists(outputJar);
|
||||||
|
|
||||||
|
try (ZipFsClasspath zipFsClasspath = ZipFsClasspath.create(classpath);
|
||||||
|
InputStream unpickDefinitions = getUnpickDefinitionsInputStream()) {
|
||||||
|
IClassResolver classResolver = zipFsClasspath.createClassResolver().chain(ClassResolvers.classpath());
|
||||||
|
ConstantUninliner uninliner = ConstantUninliner.builder()
|
||||||
|
.logger(JAVA_LOGGER)
|
||||||
|
.classResolver(classResolver)
|
||||||
|
.grouper(ConstantGroupers.dataDriven()
|
||||||
|
.logger(JAVA_LOGGER)
|
||||||
|
.lenient(getOptions().getLenient().get())
|
||||||
|
.classResolver(classResolver)
|
||||||
|
.mappingSource(unpickDefinitions)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AsyncZipProcessor.processEntries(inputJar, outputJar, new UnpickZipProcessor(uninliner));
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputJar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getUnpickDefinitionsInputStream() throws IOException {
|
||||||
|
final Path unpickDefinitionsPath = getOptions().getUnpickDefinitions().getAsFile().get().toPath();
|
||||||
|
|
||||||
|
if (getOptions().getUnpickRemapperService().isPresent()) {
|
||||||
|
LOGGER.info("Remapping unpick definitions: {}", unpickDefinitionsPath);
|
||||||
|
|
||||||
|
UnpickRemapperService unpickRemapperService = getServiceFactory().get(getOptions().getUnpickRemapperService());
|
||||||
|
String remapped = unpickRemapperService.remap(unpickDefinitionsPath.toFile());
|
||||||
|
|
||||||
|
return new ByteArrayInputStream(remapped.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("Using unpick definitions: {}", unpickDefinitionsPath);
|
||||||
|
|
||||||
|
return Files.newInputStream(unpickDefinitionsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnpickCacheKey() {
|
||||||
|
return Checksum.of(List.of(
|
||||||
|
Checksum.of(getOptions().getUnpickDefinitions().getAsFile().get()),
|
||||||
|
Checksum.of(getOptions().getUnpickConstantJar()),
|
||||||
|
Checksum.of(getOptions().getUnpickRemapperService()
|
||||||
|
.flatMap(options -> options.getTinyRemapper()
|
||||||
|
.flatMap(TinyRemapperService.Options::getFrom))
|
||||||
|
.getOrElse("named"))
|
||||||
|
)).sha256().hex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record UnpickZipProcessor(ConstantUninliner uninliner) implements AsyncZipProcessor {
|
||||||
|
@Override
|
||||||
|
public void processEntryAsync(Path input, Path output) throws IOException {
|
||||||
|
Files.createDirectories(output.getParent());
|
||||||
|
|
||||||
|
String fileName = input.toAbsolutePath().toString();
|
||||||
|
|
||||||
|
if (!fileName.endsWith(".class")) {
|
||||||
|
// Copy non-class files
|
||||||
|
Files.copy(input, output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassNode classNode = new ClassNode();
|
||||||
|
|
||||||
|
try (InputStream is = Files.newInputStream(input)) {
|
||||||
|
ClassReader reader = new ClassReader(is);
|
||||||
|
reader.accept(classNode, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("Unpick class: {}", classNode.name);
|
||||||
|
uninliner.transform(classNode);
|
||||||
|
|
||||||
|
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||||
|
classNode.accept(writer);
|
||||||
|
|
||||||
|
Files.write(output, writer.toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ZipFsClasspath(List<FileSystemUtil.Delegate> fileSystems) implements Closeable {
|
||||||
|
private ZipFsClasspath {
|
||||||
|
if (fileSystems.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("No resolvers provided");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ZipFsClasspath create(List<Path> classpath) throws IOException {
|
||||||
|
var fileSystems = new ArrayList<FileSystemUtil.Delegate>();
|
||||||
|
|
||||||
|
for (Path path : classpath) {
|
||||||
|
FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(path, false);
|
||||||
|
fileSystems.add(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ZipFsClasspath(fileSystems);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IClassResolver createClassResolver() {
|
||||||
|
IClassResolver resolver = ClassResolvers.fromDirectory(fileSystems.getFirst().getRoot());
|
||||||
|
|
||||||
|
for (int i = 1; i < fileSystems.size(); i++) {
|
||||||
|
resolver = resolver.chain(ClassResolvers.fromDirectory(fileSystems.get(i).getRoot()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
for (FileSystemUtil.Delegate fileSystem : fileSystems) {
|
||||||
|
fileSystem.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,17 +40,17 @@ import java.util.concurrent.Executors;
|
|||||||
public interface AsyncZipProcessor {
|
public interface AsyncZipProcessor {
|
||||||
static void processEntries(Path inputZip, Path outputZip, AsyncZipProcessor processor) throws IOException {
|
static void processEntries(Path inputZip, Path outputZip, AsyncZipProcessor processor) throws IOException {
|
||||||
try (FileSystemUtil.Delegate inFs = FileSystemUtil.getJarFileSystem(inputZip, false);
|
try (FileSystemUtil.Delegate inFs = FileSystemUtil.getJarFileSystem(inputZip, false);
|
||||||
FileSystemUtil.Delegate outFs = FileSystemUtil.getJarFileSystem(outputZip, true)) {
|
FileSystemUtil.Delegate outFs = FileSystemUtil.getJarFileSystem(outputZip, true);
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())) {
|
||||||
final Path inRoot = inFs.get().getPath("/");
|
final Path inRoot = inFs.get().getPath("/");
|
||||||
final Path outRoot = outFs.get().getPath("/");
|
final Path outRoot = outFs.get().getPath("/");
|
||||||
|
|
||||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||||
final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
|
||||||
|
|
||||||
Files.walkFileTree(inRoot, new SimpleFileVisitor<>() {
|
Files.walkFileTree(inRoot, new SimpleFileVisitor<>() {
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path inputFile, BasicFileAttributes attrs) throws IOException {
|
public FileVisitResult visitFile(Path inputFile, BasicFileAttributes attrs) {
|
||||||
final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
|
final CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||||
try {
|
try {
|
||||||
final String rel = inRoot.relativize(inputFile).toString();
|
final String rel = inRoot.relativize(inputFile).toString();
|
||||||
final Path outputFile = outRoot.resolve(rel);
|
final Path outputFile = outRoot.resolve(rel);
|
||||||
@@ -58,8 +58,6 @@ public interface AsyncZipProcessor {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new CompletionException(e);
|
throw new CompletionException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}, executor);
|
}, executor);
|
||||||
|
|
||||||
futures.add(future);
|
futures.add(future);
|
||||||
@@ -67,7 +65,7 @@ public interface AsyncZipProcessor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for all futures to complete
|
// Wait for all futures to complete, throwing the first exception if any
|
||||||
for (CompletableFuture<Void> future : futures) {
|
for (CompletableFuture<Void> future : futures) {
|
||||||
try {
|
try {
|
||||||
future.join();
|
future.join();
|
||||||
@@ -79,8 +77,6 @@ public interface AsyncZipProcessor {
|
|||||||
throw new RuntimeException("Failed to process zip", e.getCause());
|
throw new RuntimeException("Failed to process zip", e.getCause());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executor.shutdown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
src/main/java/net/fabricmc/loom/util/CacheKey.java
Normal file
59
src/main/java/net/fabricmc/loom/util/CacheKey.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.function.Supplier;
|
||||||
|
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import org.gradle.api.Action;
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.api.tasks.Internal;
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.gradle.GradleTypeAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple base class for creating cache keys. Extend this class and create abstract properties to be included in the cache key.
|
||||||
|
*/
|
||||||
|
public abstract class CacheKey {
|
||||||
|
private static final int CHECKSUM_LENGTH = 8;
|
||||||
|
private final transient Supplier<String> jsonSupplier = Suppliers.memoize(() -> GradleTypeAdapter.GSON.toJson(this));
|
||||||
|
private final transient Supplier<String> cacheKeySupplier = Suppliers.memoize(() -> Checksum.of(jsonSupplier.get()).sha1().hex(CHECKSUM_LENGTH));
|
||||||
|
|
||||||
|
public static <T> T create(Project project, Class<T> clazz, Action<T> action) {
|
||||||
|
T instance = project.getObjects().newInstance(clazz);
|
||||||
|
action.execute(instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public final String getJson() {
|
||||||
|
return jsonSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public final String getCacheKey() {
|
||||||
|
return cacheKeySupplier.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
*
|
*
|
||||||
* Copyright (c) 2016-2020 FabricMC
|
* Copyright (c) 2025 FabricMC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -26,88 +26,138 @@ package net.fabricmc.loom.util;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
|
||||||
import com.google.common.hash.Hashing;
|
|
||||||
import com.google.common.io.BaseEncoding;
|
|
||||||
import com.google.common.io.ByteSource;
|
|
||||||
import com.google.common.io.Files;
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.logging.Logger;
|
import org.gradle.api.file.FileCollection;
|
||||||
import org.gradle.api.logging.Logging;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class Checksum {
|
public final class Checksum {
|
||||||
private static final Logger log = Logging.getLogger(Checksum.class);
|
public static Checksum of(byte[] data) {
|
||||||
|
return new Checksum(digest -> digest.write(data));
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean equals(File file, String checksum) {
|
public static Checksum of(String str) {
|
||||||
if (file == null || !file.exists()) {
|
return new Checksum(digest -> digest.write(str));
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
public static Checksum of(File file) {
|
||||||
|
return of(file.toPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Checksum of(Path file) {
|
||||||
|
return new Checksum(digest -> {
|
||||||
|
try (InputStream is = Files.newInputStream(file)) {
|
||||||
|
is.transferTo(digest);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Checksum of(Project project) {
|
||||||
|
return of(project.getProjectDir().getAbsolutePath() + ":" + project.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Checksum of(FileCollection files) {
|
||||||
|
return new Checksum(os -> {
|
||||||
|
for (File file : files) {
|
||||||
|
try (InputStream is = Files.newInputStream(file.toPath())) {
|
||||||
|
is.transferTo(os);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Checksum of(List<Checksum> others) {
|
||||||
|
return new Checksum(os -> {
|
||||||
|
for (Checksum other : others) {
|
||||||
|
other.consumer.accept(os);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DataConsumer consumer;
|
||||||
|
|
||||||
|
private Checksum(DataConsumer consumer) {
|
||||||
|
this.consumer = consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result sha1() {
|
||||||
|
return computeResult("SHA-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result sha256() {
|
||||||
|
return computeResult("SHA-256");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result md5() {
|
||||||
|
return computeResult("MD5");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result computeResult(String algorithm) {
|
||||||
|
MessageDigest digest;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HashCode hash = Files.asByteSource(file).hash(Hashing.sha1());
|
digest = MessageDigest.getInstance(algorithm);
|
||||||
String hashString = hash.toString();
|
} catch (NoSuchAlgorithmException e) {
|
||||||
log.debug("Checksum check: '" + hashString + "' == '" + checksum + "'?");
|
throw new RuntimeException(e);
|
||||||
return hashString.equals(checksum);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
try (MessageDigestOutputStream os = new MessageDigestOutputStream(digest)) {
|
||||||
|
consumer.accept(os);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException("Failed to compute checksum", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Result(digest.digest());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] sha256(File file) {
|
public record Result(byte[] digest) {
|
||||||
try {
|
public String hex() {
|
||||||
HashCode hash = Files.asByteSource(file).hash(Hashing.sha256());
|
return HexFormat.of().formatHex(digest());
|
||||||
return hash.asBytes();
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Failed to get file hash", e);
|
public String hex(int length) {
|
||||||
|
return hex().substring(0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matchesStr(String other) {
|
||||||
|
return hex().equalsIgnoreCase(other);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String sha256Hex(byte[] input) throws IOException {
|
@FunctionalInterface
|
||||||
HashCode hash = ByteSource.wrap(input).hash(Hashing.sha256());
|
private interface DataConsumer {
|
||||||
return Checksum.toHex(hash.asBytes());
|
void accept(MessageDigestOutputStream os) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String sha1Hex(Path path) throws IOException {
|
private static class MessageDigestOutputStream extends OutputStream {
|
||||||
HashCode hash = Files.asByteSource(path.toFile()).hash(Hashing.sha1());
|
private final MessageDigest digest;
|
||||||
return toHex(hash.asBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String sha1Hex(byte[] input) {
|
private MessageDigestOutputStream(MessageDigest digest) {
|
||||||
try {
|
this.digest = digest;
|
||||||
HashCode hash = ByteSource.wrap(input).hash(Hashing.sha1());
|
|
||||||
return toHex(hash.asBytes());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Failed to hash", e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static String truncatedSha256(File file) {
|
@Override
|
||||||
try {
|
public void write(int b) {
|
||||||
HashCode hash = Files.asByteSource(file).hash(Hashing.sha256());
|
digest.update((byte) b);
|
||||||
return hash.toString().substring(0, 12);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Failed to get file hash of " + file, e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] sha256(String string) {
|
@Override
|
||||||
HashCode hash = Hashing.sha256().hashString(string, StandardCharsets.UTF_8);
|
public void write(byte @NotNull[] b, int off, int len) {
|
||||||
return hash.asBytes();
|
digest.update(b, off, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String toHex(byte[] bytes) {
|
public void write(String string) throws IOException {
|
||||||
return BaseEncoding.base16().lowerCase().encode(bytes);
|
write(string.getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String projectHash(Project project) {
|
|
||||||
String str = project.getProjectDir().getAbsolutePath() + ":" + project.getPath();
|
|
||||||
String hex = sha1Hex(str.getBytes(StandardCharsets.UTF_8));
|
|
||||||
return hex.substring(hex.length() - 16);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ public class Constants {
|
|||||||
*/
|
*/
|
||||||
public static final String FORGE_RUNTIME_LIBRARY = "forgeRuntimeLibrary";
|
public static final String FORGE_RUNTIME_LIBRARY = "forgeRuntimeLibrary";
|
||||||
public static final String MAPPING_CONSTANTS = "mappingsConstants";
|
public static final String MAPPING_CONSTANTS = "mappingsConstants";
|
||||||
public static final String UNPICK_CLASSPATH = "unpick";
|
|
||||||
/**
|
/**
|
||||||
* A configuration that behaves like {@code runtimeOnly} but is not
|
* A configuration that behaves like {@code runtimeOnly} but is not
|
||||||
* exposed in {@code runtimeElements} to dependents. A bit like
|
* exposed in {@code runtimeElements} to dependents. A bit like
|
||||||
@@ -176,6 +175,14 @@ public class Constants {
|
|||||||
public static final String RUNTIME_JAVA_COMPATIBILITY_VERSION = "fabric.loom.runtimeJavaCompatibilityVersion";
|
public static final String RUNTIME_JAVA_COMPATIBILITY_VERSION = "fabric.loom.runtimeJavaCompatibilityVersion";
|
||||||
public static final String DECOMPILE_CACHE_MAX_FILES = "fabric.loom.decompileCacheMaxFiles";
|
public static final String DECOMPILE_CACHE_MAX_FILES = "fabric.loom.decompileCacheMaxFiles";
|
||||||
public static final String DECOMPILE_CACHE_MAX_AGE = "fabric.loom.decompileCacheMaxAge";
|
public static final String DECOMPILE_CACHE_MAX_AGE = "fabric.loom.decompileCacheMaxAge";
|
||||||
|
/**
|
||||||
|
* Skip the signature verification of the Minecraft jar after downloading it.
|
||||||
|
*/
|
||||||
|
public static final String DISABLE_MINECRAFT_VERIFICATION = "fabric.loom.disableMinecraftVerification";
|
||||||
|
/**
|
||||||
|
* When using the MojangMappingLayer this will remove names for non root methods by using the intermediary mappings.
|
||||||
|
*/
|
||||||
|
public static final String DROP_NON_INTERMEDIATE_ROOT_METHODS = "fabric.loom.dropNonIntermediateRootMethods";
|
||||||
public static final String ALLOW_MISMATCHED_PLATFORM_VERSION = "loom.allowMismatchedPlatformVersion";
|
public static final String ALLOW_MISMATCHED_PLATFORM_VERSION = "loom.allowMismatchedPlatformVersion";
|
||||||
public static final String IGNORE_DEPENDENCY_LOOM_VERSION_VALIDATION = "loom.ignoreDependencyLoomVersionValidation";
|
public static final String IGNORE_DEPENDENCY_LOOM_VERSION_VALIDATION = "loom.ignoreDependencyLoomVersionValidation";
|
||||||
}
|
}
|
||||||
|
|||||||
49
src/main/java/net/fabricmc/loom/util/IdentityBiMap.java
Normal file
49
src/main/java/net/fabricmc/loom/util/IdentityBiMap.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.IdentityHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class IdentityBiMap<K, V> {
|
||||||
|
private final Map<K, V> keyToValue = new IdentityHashMap<>();
|
||||||
|
private final Map<V, K> valueToKey = new IdentityHashMap<>();
|
||||||
|
|
||||||
|
public IdentityBiMap() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(K key, V value) {
|
||||||
|
keyToValue.put(key, value);
|
||||||
|
valueToKey.put(value, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getByKey(K key) {
|
||||||
|
return keyToValue.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public K getByValue(V value) {
|
||||||
|
return valueToKey.get(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/main/java/net/fabricmc/loom/util/JarPackageIndex.java
Normal file
100
src/main/java/net/fabricmc/loom/util/JarPackageIndex.java
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An index of all packages and the classes directly contained within.
|
||||||
|
*/
|
||||||
|
public record JarPackageIndex(Map<String, List<String>> packages) {
|
||||||
|
public static JarPackageIndex create(List<Path> jars) {
|
||||||
|
Map<String, List<String>> packages = jars.stream()
|
||||||
|
.map(jar -> CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
List<String> classes = getClasses(jar);
|
||||||
|
return groupClassesByPackage(classes);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, Executors.newVirtualThreadPerTaskExecutor()))
|
||||||
|
.map(CompletableFuture::join)
|
||||||
|
.flatMap(map -> map.entrySet().stream())
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
Map.Entry::getKey,
|
||||||
|
Map.Entry::getValue,
|
||||||
|
(existing, newValues) -> {
|
||||||
|
existing.addAll(newValues);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
return new JarPackageIndex(packages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> getClasses(Path jar) throws IOException {
|
||||||
|
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar, false);
|
||||||
|
Stream<Path> walk = Files.walk(fs.getRoot())) {
|
||||||
|
return walk
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.map(Path::toString)
|
||||||
|
.filter(className -> className.endsWith(".class"))
|
||||||
|
.map(className -> className.startsWith("/") ? className.substring(1) : className)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, List<String>> groupClassesByPackage(List<String> classes) {
|
||||||
|
return classes.stream()
|
||||||
|
.filter(className -> className.endsWith(".class")) // Ensure it's a class file
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
JarPackageIndex::extractPackageName,
|
||||||
|
Collectors.mapping(
|
||||||
|
JarPackageIndex::extractClassName,
|
||||||
|
Collectors.toList()
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the package name from a class name, e.g., "com/example/MyClass.class" -> "com.example"
|
||||||
|
private static String extractPackageName(String className) {
|
||||||
|
int lastSlashIndex = className.lastIndexOf('/');
|
||||||
|
return lastSlashIndex == -1 ? "" : className.substring(0, lastSlashIndex).replace("/", ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractClassName(String className) {
|
||||||
|
int lastSlashIndex = className.lastIndexOf('/');
|
||||||
|
String simpleName = lastSlashIndex == -1 ? className : className.substring(lastSlashIndex + 1);
|
||||||
|
return simpleName.endsWith(".class") ? simpleName.substring(0, simpleName.length() - 6) : simpleName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,10 @@ public interface Platform {
|
|||||||
boolean isArm();
|
boolean isArm();
|
||||||
|
|
||||||
boolean isRiscV();
|
boolean isRiscV();
|
||||||
|
|
||||||
|
default boolean isX64() {
|
||||||
|
return is64Bit() && !isArm() && !isRiscV();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Architecture getArchitecture();
|
Architecture getArchitecture();
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 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.logging.Handler;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
public class SLF4JAdapterHandler extends Handler {
|
||||||
|
private final Logger logger;
|
||||||
|
private final boolean suppressWarnings;
|
||||||
|
|
||||||
|
public SLF4JAdapterHandler(Logger logger, boolean suppressWarnings) {
|
||||||
|
this.logger = logger;
|
||||||
|
this.suppressWarnings = suppressWarnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void publish(LogRecord record) {
|
||||||
|
if (record.getLevel().intValue() >= Level.SEVERE.intValue()) {
|
||||||
|
logger.error(record.getMessage(), record.getThrown());
|
||||||
|
} else if (record.getLevel().intValue() >= Level.WARNING.intValue() && !suppressWarnings) {
|
||||||
|
logger.warn(record.getMessage(), record.getThrown());
|
||||||
|
} else if (record.getLevel().intValue() >= Level.INFO.intValue()) {
|
||||||
|
logger.info(record.getMessage(), record.getThrown());
|
||||||
|
} else if (record.getLevel().intValue() >= Level.FINER.intValue()) {
|
||||||
|
logger.debug(record.getMessage(), record.getThrown());
|
||||||
|
} else {
|
||||||
|
logger.trace(record.getMessage(), record.getThrown());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SecurityException {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -120,7 +120,7 @@ public class SourceRemapper {
|
|||||||
source = new File(destination.getAbsolutePath().substring(0, destination.getAbsolutePath().lastIndexOf('.')) + "-dev.jar");
|
source = new File(destination.getAbsolutePath().substring(0, destination.getAbsolutePath().lastIndexOf('.')) + "-dev.jar");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
com.google.common.io.Files.move(destination, source);
|
Files.move(destination.toPath(), source.toPath());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("Could not rename " + destination.getName() + "!", e);
|
throw new RuntimeException("Could not rename " + destination.getName() + "!", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import java.util.Set;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import dev.architectury.loom.util.MappingOption;
|
import dev.architectury.loom.util.MappingOption;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
|
|
||||||
@@ -50,11 +49,11 @@ import net.fabricmc.tinyremapper.TinyRemapper;
|
|||||||
* Contains shortcuts to create tiny remappers using the mappings accessibly to the project.
|
* Contains shortcuts to create tiny remappers using the mappings accessibly to the project.
|
||||||
*/
|
*/
|
||||||
public final class TinyRemapperHelper {
|
public final class TinyRemapperHelper {
|
||||||
public static final Map<String, String> JSR_TO_JETBRAINS = new ImmutableMap.Builder<String, String>()
|
public static final Map<String, String> JSR_TO_JETBRAINS = Map.of(
|
||||||
.put("javax/annotation/Nullable", "org/jetbrains/annotations/Nullable")
|
"javax/annotation/Nullable", "org/jetbrains/annotations/Nullable",
|
||||||
.put("javax/annotation/Nonnull", "org/jetbrains/annotations/NotNull")
|
"javax/annotation/Nonnull", "org/jetbrains/annotations/NotNull",
|
||||||
.put("javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable")
|
"javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable"
|
||||||
.build();
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches the new local variable naming format introduced in 21w37a.
|
* Matches the new local variable naming format introduced in 21w37a.
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class ZipReprocessorUtil {
|
|||||||
private static final String META_INF = "META-INF/";
|
private static final String META_INF = "META-INF/";
|
||||||
|
|
||||||
// See https://docs.oracle.com/en/java/javase/20/docs/specs/jar/jar.html#signed-jar-file
|
// See https://docs.oracle.com/en/java/javase/20/docs/specs/jar/jar.html#signed-jar-file
|
||||||
private static boolean isSpecialFile(String zipEntryName) {
|
public static boolean isSpecialFile(String zipEntryName) {
|
||||||
if (!zipEntryName.startsWith(META_INF)) {
|
if (!zipEntryName.startsWith(META_INF)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user