mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Merge 1.10
This commit is contained in:
7
bootstrap/.gitignore
vendored
7
bootstrap/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
# Ignore everything
|
||||
/*
|
||||
|
||||
!/src
|
||||
!/build.gradle
|
||||
!/.gitignore
|
||||
!/test-project
|
||||
@@ -1,32 +0,0 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'groovy'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
it.options.encoding = "UTF-8"
|
||||
it.options.release = 8
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation gradleApi()
|
||||
|
||||
testImplementation(gradleTestKit())
|
||||
testImplementation('org.spockframework:spock-core:2.3-groovy-3.0') {
|
||||
exclude module: 'groovy-all'
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
maxHeapSize = "4096m"
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package net.fabricmc.loom.bootstrap;
|
||||
|
||||
import org.gradle.api.plugins.PluginAware;
|
||||
|
||||
public interface BootstrappedPlugin {
|
||||
void apply(PluginAware project);
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package net.fabricmc.loom.bootstrap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.configuration.BuildFeatures;
|
||||
import org.gradle.api.plugins.PluginAware;
|
||||
import org.gradle.util.GradleVersion;
|
||||
|
||||
/**
|
||||
* This bootstrap is compiled against a minimal gradle API and java 8, this allows us to show a nice error to users who run on unsupported configurations.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class LoomGradlePluginBootstrap implements Plugin<PluginAware> {
|
||||
private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.11";
|
||||
private static final int MIN_SUPPORTED_MAJOR_JAVA_VERSION = 17;
|
||||
private static final int MIN_SUPPORTED_MAJOR_IDEA_VERSION = 2022;
|
||||
|
||||
private static final String PLUGIN_CLASS_NAME = "net.fabricmc.loom.LoomGradlePlugin";
|
||||
private static final String IDEA_VERSION_PROP_KEY = "idea.version";
|
||||
|
||||
@Inject
|
||||
protected abstract BuildFeatures getBuildFeatures();
|
||||
|
||||
@Override
|
||||
public void apply(PluginAware pluginAware) {
|
||||
if (pluginAware instanceof Project) {
|
||||
Project project = (Project) pluginAware;
|
||||
|
||||
if (getBuildFeatures().getIsolatedProjects().getActive().get() || project.findProperty("fabric.loom.skip-env-validation") == null) {
|
||||
validateEnvironment();
|
||||
} else {
|
||||
project.getLogger().lifecycle("Loom environment validation disabled. Please re-enable before reporting any issues.");
|
||||
}
|
||||
}
|
||||
|
||||
getActivePlugin().apply(pluginAware);
|
||||
}
|
||||
|
||||
private void validateEnvironment() {
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
if (!isValidGradleRuntime()) {
|
||||
errors.add(String.format("You are using an outdated version of Gradle (%s). Gradle %s or higher is required.", GradleVersion.current().getVersion(), MIN_SUPPORTED_GRADLE_VERSION));
|
||||
}
|
||||
|
||||
if (!isValidJavaRuntime()) {
|
||||
errors.add(String.format("You are using an outdated version of Java (%s). Java %d or higher is required.", JavaVersion.current().getMajorVersion(), MIN_SUPPORTED_MAJOR_JAVA_VERSION));
|
||||
|
||||
if (Boolean.getBoolean("idea.active")) {
|
||||
// Idea specific error
|
||||
errors.add("You can change the Java version in the Gradle settings dialog.");
|
||||
} else {
|
||||
String javaHome = System.getenv("JAVA_HOME");
|
||||
|
||||
if (javaHome != null) {
|
||||
errors.add(String.format("The JAVA_HOME environment variable is currently set to (%s).", javaHome));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidIdeaRuntime()) {
|
||||
errors.add(String.format("You are using an outdated version of intellij idea (%s). Intellij idea %d or higher is required.", System.getProperty(IDEA_VERSION_PROP_KEY), MIN_SUPPORTED_MAJOR_IDEA_VERSION));
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
throw new UnsupportedOperationException(String.join("\n", errors));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidJavaRuntime() {
|
||||
// Note use compareTo to ensure compatibility with gradle < 6.0
|
||||
return JavaVersion.current().compareTo(JavaVersion.toVersion(MIN_SUPPORTED_MAJOR_JAVA_VERSION)) >= 0;
|
||||
}
|
||||
|
||||
private static boolean isValidGradleRuntime() {
|
||||
return GradleVersion.current().compareTo(GradleVersion.version(MIN_SUPPORTED_GRADLE_VERSION)) >= 0;
|
||||
}
|
||||
|
||||
private static boolean isValidIdeaRuntime() {
|
||||
String version = System.getProperty(IDEA_VERSION_PROP_KEY);
|
||||
|
||||
if (version == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int ideaYear = Integer.parseInt(version.substring(0, version.indexOf(".")));
|
||||
return ideaYear >= MIN_SUPPORTED_MAJOR_IDEA_VERSION;
|
||||
}
|
||||
|
||||
BootstrappedPlugin getActivePlugin() {
|
||||
try {
|
||||
return (BootstrappedPlugin) Class.forName(PLUGIN_CLASS_NAME).getConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to bootstrap loom", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
plugins {
|
||||
id 'dev.architectury.loom' version '0.13.local'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.16.5"
|
||||
mappings loom.officialMojangMappings()
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
name = 'Fabric'
|
||||
url = 'https://maven.fabricmc.net/'
|
||||
}
|
||||
gradlePluginPortal()
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
50
build.gradle
50
build.gradle
@@ -23,7 +23,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
}
|
||||
|
||||
group = "dev.architectury"
|
||||
def baseVersion = '1.9'
|
||||
def baseVersion = '1.10'
|
||||
|
||||
def ENV = System.getenv()
|
||||
def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999"
|
||||
@@ -60,15 +60,6 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
bootstrap {
|
||||
transitive false
|
||||
}
|
||||
compileClasspath.extendsFrom bootstrap
|
||||
runtimeClasspath.extendsFrom bootstrap
|
||||
testRuntimeClasspath.extendsFrom bootstrap
|
||||
}
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy {
|
||||
// I am sorry, for now
|
||||
@@ -108,8 +99,6 @@ sourceSets {
|
||||
dependencies {
|
||||
implementation gradleApi()
|
||||
|
||||
bootstrap project(":bootstrap")
|
||||
|
||||
// libraries
|
||||
implementation libs.commons.io
|
||||
implementation libs.gson
|
||||
@@ -201,7 +190,6 @@ jar {
|
||||
attributes 'Implementation-Version': project.version
|
||||
}
|
||||
|
||||
from configurations.bootstrap.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
from sourceSets.commonDecompiler.output.classesDirs
|
||||
from sourceSets.cfr.output.classesDirs
|
||||
from sourceSets.fernflower.output.classesDirs
|
||||
@@ -277,7 +265,7 @@ gradlePlugin {
|
||||
plugins {
|
||||
fabricLoom {
|
||||
id = 'dev.architectury.loom'
|
||||
implementationClass = 'net.fabricmc.loom.bootstrap.LoomGradlePluginBootstrap'
|
||||
implementationClass = 'net.fabricmc.loom.LoomGradlePlugin'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,6 +286,11 @@ test {
|
||||
maxRetries = 3
|
||||
}
|
||||
}
|
||||
|
||||
testLogging {
|
||||
// Log everything to the console
|
||||
setEvents(TestLogEvent.values().toList())
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround https://github.com/gradle/gradle/issues/25898
|
||||
@@ -311,7 +304,7 @@ tasks.withType(Test).configureEach {
|
||||
}
|
||||
|
||||
|
||||
import org.gradle.api.internal.artifacts.configurations.ConfigurationRoles
|
||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
@@ -322,18 +315,18 @@ publishing {
|
||||
if (!isSnapshot && !ENV.EXPERIMENTAL) {
|
||||
// Also publish a snapshot so people can use the latest version if they wish
|
||||
snapshot(MavenPublication) { publication ->
|
||||
groupId project.group
|
||||
artifactId project.base.archivesName.get()
|
||||
version baseVersion + '-SNAPSHOT'
|
||||
groupId = project.group
|
||||
artifactId = project.base.archivesName.get()
|
||||
version = baseVersion + '-SNAPSHOT'
|
||||
|
||||
from components.java
|
||||
}
|
||||
|
||||
// Manually crate the plugin marker for snapshot versions
|
||||
snapshotPlugin(MavenPublication) {
|
||||
groupId 'dev.architectury.loom'
|
||||
artifactId 'dev.architectury.loom.gradle.plugin'
|
||||
version baseVersion + '-SNAPSHOT'
|
||||
groupId = 'dev.architectury.loom'
|
||||
artifactId = 'dev.architectury.loom.gradle.plugin'
|
||||
version = baseVersion + '-SNAPSHOT'
|
||||
|
||||
pom.withXml({
|
||||
// Based off org.gradle.plugin.devel.plugins.MavenPluginPublishPlugin
|
||||
@@ -365,21 +358,6 @@ publishing {
|
||||
}
|
||||
}
|
||||
|
||||
// Need to tweak this file to pretend we are compatible with j8 so the bootstrap will run.
|
||||
tasks.withType(GenerateModuleMetadata).configureEach {
|
||||
doLast {
|
||||
def file = outputFile.get().asFile
|
||||
|
||||
def metadata = new groovy.json.JsonSlurper().parseText(file.text)
|
||||
|
||||
metadata.variants.each {
|
||||
it.attributes["org.gradle.jvm.version"] = 8
|
||||
}
|
||||
|
||||
file.text = groovy.json.JsonOutput.toJson(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
// A task to output a json file with a list of all the test to run
|
||||
tasks.register('writeActionsTestMatrix') {
|
||||
doLast {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[versions]
|
||||
kotlin = "2.0.20"
|
||||
kotlin = "2.0.21"
|
||||
asm = "9.7.1"
|
||||
commons-io = "2.15.1"
|
||||
gson = "2.10.1"
|
||||
guava = "33.0.0-jre"
|
||||
|
||||
stitch = "0.6.2"
|
||||
tiny-remapper = "0.10.4"
|
||||
tiny-remapper = "0.11.0"
|
||||
access-widener = "2.1.0"
|
||||
mapping-io = "0.6.1"
|
||||
mapping-io = "0.7.1"
|
||||
lorenz-tiny = "4.0.2"
|
||||
mercury = "0.1.4.17"
|
||||
loom-native = "0.2.0"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Decompilers
|
||||
fernflower = "2.0.0"
|
||||
cfr = "0.2.2"
|
||||
vineflower = "1.10.1"
|
||||
vineflower = "1.11.0"
|
||||
|
||||
# Runtime depedencies
|
||||
mixin-compile-extensions = "0.6.0"
|
||||
@@ -10,6 +10,7 @@ dev-launch-injector = "0.2.1+build.8"
|
||||
terminal-console-appender = "1.3.0"
|
||||
jetbrains-annotations = "25.0.0"
|
||||
native-support = "1.0.1"
|
||||
fabric-installer = "1.0.1"
|
||||
|
||||
# Forge Runtime depedencies
|
||||
javax-annotations = "3.0.2"
|
||||
@@ -33,6 +34,7 @@ dev-launch-injector = { module = "net.fabricmc:dev-launch-injector", version.ref
|
||||
terminal-console-appender = { module = "net.minecrell:terminalconsoleappender", version.ref = "terminal-console-appender" }
|
||||
jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" }
|
||||
native-support = { module = "net.fabricmc:fabric-loom-native-support", version.ref = "native-support" }
|
||||
fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" }
|
||||
|
||||
# Forge Runtime depedencies
|
||||
javax-annotations = { module = "com.google.code.findbugs:jsr305", version.ref = "javax-annotations" }
|
||||
|
||||
@@ -6,9 +6,8 @@ mockito = "5.14.2"
|
||||
java-debug = "0.52.0"
|
||||
mixin = "0.15.3+mixin.0.8.7"
|
||||
|
||||
gradle-nightly = "8.12-20241110002642+0000"
|
||||
gradle-nightly = "8.14-20250208001853+0000"
|
||||
fabric-loader = "0.16.9"
|
||||
fabric-installer = "1.0.1"
|
||||
|
||||
[libraries]
|
||||
spock = { module = "org.spockframework:spock-core", version.ref = "spock" }
|
||||
@@ -19,5 +18,4 @@ mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
|
||||
java-debug = { module = "com.microsoft.java:com.microsoft.java.debug.core", version.ref = "java-debug" }
|
||||
mixin = { module = "net.fabricmc:sponge-mixin", version.ref = "mixin" }
|
||||
gradle-nightly = { module = "org.gradle:dummy", version.ref = "gradle-nightly" }
|
||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||
fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" }
|
||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
3
gradlew
vendored
3
gradlew
vendored
@@ -86,8 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
@@ -9,6 +9,4 @@ dependencyResolutionManagement {
|
||||
from(files("gradle/runtime.libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include "bootstrap"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2016-2023 FabricMC
|
||||
* Copyright (c) 2016-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
|
||||
@@ -33,15 +33,16 @@ import java.util.Set;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.PluginAware;
|
||||
|
||||
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
|
||||
import net.fabricmc.loom.bootstrap.BootstrappedPlugin;
|
||||
import net.fabricmc.loom.api.fabricapi.FabricApiExtension;
|
||||
import net.fabricmc.loom.configuration.CompileConfiguration;
|
||||
import net.fabricmc.loom.configuration.FabricApiExtension;
|
||||
import net.fabricmc.loom.configuration.LoomConfigurations;
|
||||
import net.fabricmc.loom.configuration.MavenPublication;
|
||||
import net.fabricmc.loom.configuration.fabricapi.FabricApiExtensionImpl;
|
||||
import net.fabricmc.loom.configuration.ide.idea.IdeaConfiguration;
|
||||
import net.fabricmc.loom.configuration.sandbox.SandboxConfiguration;
|
||||
import net.fabricmc.loom.decompilers.DecompilerConfiguration;
|
||||
@@ -52,7 +53,7 @@ import net.fabricmc.loom.task.RemapTaskConfiguration;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.LibraryLocationLogger;
|
||||
|
||||
public class LoomGradlePlugin implements BootstrappedPlugin {
|
||||
public class LoomGradlePlugin implements Plugin<PluginAware> {
|
||||
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
public static final String LOOM_VERSION = Objects.requireNonNullElse(LoomGradlePlugin.class.getPackage().getImplementationVersion(), "0.0.0+unknown");
|
||||
|
||||
@@ -79,7 +80,7 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
private void apply(Project project) {
|
||||
Set<String> loggedVersions = new HashSet<>(Arrays.asList(System.getProperty("loom.printed.logged", "").split(",")));
|
||||
|
||||
if (!loggedVersions.contains(LOOM_VERSION)) {
|
||||
@@ -93,7 +94,6 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
|
||||
project.getLogger().lifecycle("You are using an outdated version of Architectury Loom! This version will not receive any support, please consider updating!");
|
||||
}
|
||||
}
|
||||
|
||||
LibraryLocationLogger.logLibraryVersions();
|
||||
|
||||
// Apply default plugins
|
||||
@@ -102,7 +102,7 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
|
||||
|
||||
// Setup extensions
|
||||
project.getExtensions().create(LoomGradleExtensionAPI.class, "loom", LoomGradleExtensionImpl.class, project, LoomFiles.create(project));
|
||||
project.getExtensions().create("fabricApi", FabricApiExtension.class);
|
||||
project.getExtensions().create(FabricApiExtension.class, "fabricApi", FabricApiExtensionImpl.class);
|
||||
|
||||
for (Class<? extends Runnable> jobClass : SETUP_JOBS) {
|
||||
project.getObjects().newInstance(jobClass).run();
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2024 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.api.fabricapi;
|
||||
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
/**
|
||||
* Represents the settings for data generation.
|
||||
*/
|
||||
public interface DataGenerationSettings {
|
||||
/**
|
||||
* Contains the output directory where generated data files will be stored.
|
||||
*/
|
||||
RegularFileProperty getOutputDirectory();
|
||||
|
||||
/**
|
||||
* Contains a boolean indicating whether a run configuration should be created for the data generation process.
|
||||
*/
|
||||
Property<Boolean> getCreateRunConfiguration();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether a new source set should be created for the data generation process.
|
||||
*/
|
||||
Property<Boolean> getCreateSourceSet();
|
||||
|
||||
/**
|
||||
* Contains a string property representing the mod ID associated with the data generation process.
|
||||
*
|
||||
* <p>This must be set when {@link #getCreateRunConfiguration()} is set.
|
||||
*/
|
||||
Property<String> getModId();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether strict validation is enabled.
|
||||
*/
|
||||
Property<Boolean> getStrictValidation();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether the generated resources will be automatically added to the main sourceset.
|
||||
*/
|
||||
Property<Boolean> getAddToResources();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether data generation will be compiled and ran with the client.
|
||||
*/
|
||||
Property<Boolean> getClient();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2024-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.api.fabricapi;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* A gradle extension with specific functionality related to Fabric API.
|
||||
*/
|
||||
public interface FabricApiExtension {
|
||||
/**
|
||||
* Get a {@link Dependency} for a given Fabric API module.
|
||||
*
|
||||
* @param moduleName The name of the module.
|
||||
* @param fabricApiVersion The main Fabric API version.
|
||||
* @return A {@link Dependency} for the module.
|
||||
*/
|
||||
Dependency module(String moduleName, String fabricApiVersion);
|
||||
|
||||
/**
|
||||
* Get the version of a Fabric API module.
|
||||
* @param moduleName The name of the module.
|
||||
* @param fabricApiVersion The main Fabric API version.
|
||||
* @return The version of the module.
|
||||
*/
|
||||
String moduleVersion(String moduleName, String fabricApiVersion);
|
||||
|
||||
/**
|
||||
* Configuration data generation using the default settings.
|
||||
*/
|
||||
void configureDataGeneration();
|
||||
|
||||
/**
|
||||
* Configuration data generation using the specified settings.
|
||||
* @param action An action to configure specific data generation settings. See {@link DataGenerationSettings} for more information.
|
||||
*/
|
||||
void configureDataGeneration(Action<DataGenerationSettings> action);
|
||||
|
||||
/**
|
||||
* Configuration of game and client tests using the default settings.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
void configureTests();
|
||||
|
||||
/**
|
||||
* Configuration of game and/or client tests using the specified settings.
|
||||
* @param action An action to configure specific game test settings. See {@link GameTestSettings} for more information.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
void configureTests(Action<GameTestSettings> action);
|
||||
}
|
||||
@@ -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.api.fabricapi;
|
||||
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Represents the settings for game and/or client tests.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public interface GameTestSettings {
|
||||
/**
|
||||
* Contains a boolean property indicating whether a new source set should be created for the tests.
|
||||
*
|
||||
* <p>Default: false
|
||||
*/
|
||||
Property<Boolean> getCreateSourceSet();
|
||||
|
||||
/**
|
||||
* Contains a string property representing the mod ID associated with the tests.
|
||||
*
|
||||
* <p>This must be set when {@link #getCreateSourceSet()} is set.
|
||||
*/
|
||||
@Optional
|
||||
Property<String> getModId();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether a run configuration will be created for the server side game tests, using Vanilla Game Test framework.
|
||||
*
|
||||
* <p>Default: true
|
||||
*/
|
||||
Property<Boolean> getEnableGameTests();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether a run configuration will be created for the client side game tests, using the Fabric API Client Test framework.
|
||||
*
|
||||
* <p>Default: true
|
||||
*/
|
||||
Property<Boolean> getEnableClientGameTests();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether the eula has been accepted. By enabling this you agree to the Minecraft EULA located at <a href="https://aka.ms/MinecraftEULA">https://aka.ms/MinecraftEULA</a>.
|
||||
*
|
||||
* <p>This only works when {@link #getEnableClientGameTests()} is enabled.
|
||||
*
|
||||
* <p>Default: false
|
||||
*/
|
||||
Property<Boolean> getEula();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether the run directories should be cleared before running the tests.
|
||||
*
|
||||
* <p>This only works when {@link #getEnableClientGameTests()} is enabled.
|
||||
*
|
||||
* <p>Default: true
|
||||
*/
|
||||
Property<Boolean> getClearRunDirectory();
|
||||
|
||||
/**
|
||||
* Contains a string property representing the username to use for the client side game tests.
|
||||
*
|
||||
* <p>This only works when {@link #getEnableClientGameTests()} is enabled.
|
||||
*
|
||||
* <p>Default: Player0
|
||||
*/
|
||||
@Optional
|
||||
Property<String> getUsername();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2020-2022 FabricMC
|
||||
* Copyright (c) 2020-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
|
||||
@@ -41,6 +41,7 @@ import org.gradle.api.Task;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.build.IntermediaryNamespaces;
|
||||
@@ -66,13 +67,13 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
|
||||
protected final Project project;
|
||||
private final LoomGradleExtension loomExtension;
|
||||
protected final MixinExtension mixinExtension;
|
||||
protected final Map<SourceSet, T> invokerTasks;
|
||||
protected final Map<SourceSet, TaskProvider<T>> invokerTasks;
|
||||
private final String name;
|
||||
private final Collection<Configuration> apConfigurations;
|
||||
|
||||
protected AnnotationProcessorInvoker(Project project,
|
||||
Collection<Configuration> apConfigurations,
|
||||
Map<SourceSet, T> invokerTasks, String name) {
|
||||
Map<SourceSet, TaskProvider<T>> invokerTasks, String name) {
|
||||
this.project = project;
|
||||
this.loomExtension = LoomGradleExtension.get(project);
|
||||
this.mixinExtension = loomExtension.getMixin();
|
||||
@@ -149,8 +150,8 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<SourceSet, T> entry : invokerTasks.entrySet()) {
|
||||
passMixinArguments(entry.getValue(), entry.getKey());
|
||||
for (Map.Entry<SourceSet, TaskProvider<T>> entry : invokerTasks.entrySet()) {
|
||||
entry.getValue().configure(t -> passMixinArguments(t, entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 FabricMC
|
||||
* Copyright (c) 2022-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
|
||||
@@ -26,12 +26,12 @@ package net.fabricmc.loom.build.mixin;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.compile.GroovyCompile;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
@@ -46,11 +46,10 @@ public class GroovyApInvoker extends AnnotationProcessorInvoker<GroovyCompile> {
|
||||
AnnotationProcessorInvoker.GROOVY);
|
||||
}
|
||||
|
||||
private static Map<SourceSet, GroovyCompile> getInvokerTasks(Project project) {
|
||||
private static Map<SourceSet, TaskProvider<GroovyCompile>> getInvokerTasks(Project project) {
|
||||
MixinExtension mixin = LoomGradleExtension.get(project).getMixin();
|
||||
return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.GROOVY).collect(
|
||||
Collectors.toMap(Map.Entry::getKey,
|
||||
entry -> Objects.requireNonNull((GroovyCompile) entry.getValue())));
|
||||
return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.GROOVY, GroovyCompile.class).collect(
|
||||
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2016-2022 FabricMC
|
||||
* Copyright (c) 2016-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
|
||||
@@ -26,11 +26,11 @@ package net.fabricmc.loom.build.mixin;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
@@ -45,10 +45,10 @@ public class JavaApInvoker extends AnnotationProcessorInvoker<JavaCompile> {
|
||||
AnnotationProcessorInvoker.JAVA);
|
||||
}
|
||||
|
||||
private static Map<SourceSet, JavaCompile> getInvokerTasks(Project project) {
|
||||
private static Map<SourceSet, TaskProvider<JavaCompile>> getInvokerTasks(Project project) {
|
||||
MixinExtension mixin = LoomGradleExtension.get(project).getMixin();
|
||||
return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.JAVA)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> Objects.requireNonNull((JavaCompile) entry.getValue())));
|
||||
return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.JAVA, JavaCompile.class)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2020-2022 FabricMC
|
||||
* Copyright (c) 2020-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
|
||||
@@ -36,6 +36,7 @@ import java.util.stream.Collectors;
|
||||
import kotlin.Unit;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.jetbrains.kotlin.gradle.plugin.KaptExtension;
|
||||
|
||||
@@ -66,35 +67,36 @@ public class KaptApInvoker extends AnnotationProcessorInvoker<JavaCompile> {
|
||||
kaptExtension.setIncludeCompileClasspath(false);
|
||||
}
|
||||
|
||||
private static Map<SourceSet, JavaCompile> getInvokerTasks(Project project) {
|
||||
private static Map<SourceSet, TaskProvider<JavaCompile>> getInvokerTasks(Project project) {
|
||||
MixinExtension mixin = LoomGradleExtension.get(project).getMixin();
|
||||
return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.JAVA)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> Objects.requireNonNull((JavaCompile) entry.getValue())));
|
||||
return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.JAVA, JavaCompile.class)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMixin() {
|
||||
super.configureMixin();
|
||||
|
||||
for (Map.Entry<SourceSet, JavaCompile> entry : invokerTasks.entrySet()) {
|
||||
for (Map.Entry<SourceSet, TaskProvider<JavaCompile>> entry : invokerTasks.entrySet()) {
|
||||
// Kapt only allows specifying javac args to all annotation processors at once. So we need to specify some dummy
|
||||
// target location for the refmap and then move it to the correct place for each sourceset
|
||||
JavaCompile task = entry.getValue();
|
||||
SourceSet sourceSet = entry.getKey();
|
||||
task.doLast(t -> {
|
||||
try {
|
||||
String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get();
|
||||
Path src = Paths.get(getRefmapDestination(task, refmapName));
|
||||
Path dest = Paths.get(task.getDestinationDirectory().get().getAsFile().toString(), refmapName);
|
||||
entry.getValue().configure(task -> {
|
||||
SourceSet sourceSet = entry.getKey();
|
||||
task.doLast(t -> {
|
||||
try {
|
||||
String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get();
|
||||
Path src = Paths.get(getRefmapDestination(task, refmapName));
|
||||
Path dest = Paths.get(task.getDestinationDirectory().get().getAsFile().toString(), refmapName);
|
||||
|
||||
// Possible that no mixin annotations exist
|
||||
if (Files.exists(src)) {
|
||||
project.getLogger().info("Copying refmap from " + src + " to " + dest);
|
||||
Files.move(src, dest);
|
||||
// Possible that no mixin annotations exist
|
||||
if (Files.exists(src)) {
|
||||
project.getLogger().info("Copying refmap from " + src + " to " + dest);
|
||||
Files.move(src, dest);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
project.getLogger().warn("Could not move refmap generated by kapt for task " + task, e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
project.getLogger().warn("Could not move refmap generated by kapt for task " + task, e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2016-2020 FabricMC
|
||||
* Copyright (c) 2016-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
|
||||
@@ -26,12 +26,12 @@ package net.fabricmc.loom.build.mixin;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.scala.ScalaCompile;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
@@ -47,10 +47,10 @@ public class ScalaApInvoker extends AnnotationProcessorInvoker<ScalaCompile> {
|
||||
AnnotationProcessorInvoker.SCALA);
|
||||
}
|
||||
|
||||
private static Map<SourceSet, ScalaCompile> getInvokerTasks(Project project) {
|
||||
private static Map<SourceSet, TaskProvider<ScalaCompile>> getInvokerTasks(Project project) {
|
||||
MixinExtension mixin = LoomGradleExtension.get(project).getMixin();
|
||||
return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.SCALA)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> Objects.requireNonNull((ScalaCompile) entry.getValue())));
|
||||
return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.SCALA, ScalaCompile.class)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -215,6 +215,9 @@ public abstract class CompileConfiguration implements Runnable {
|
||||
extension.setMinecraftProvider(minecraftProvider);
|
||||
minecraftProvider.provide();
|
||||
|
||||
// Realise the dependencies without actually resolving them, this forces any lazy providers to be created, populating the layered mapping factories.
|
||||
project.getConfigurations().getByName(Configurations.MAPPINGS).getDependencies().toArray();
|
||||
|
||||
// Created any layered mapping files.
|
||||
LayeredMappingsFactory.afterEvaluate(configContext);
|
||||
|
||||
@@ -222,6 +225,7 @@ public abstract class CompileConfiguration implements Runnable {
|
||||
// but before MinecraftPatchedProvider.provide.
|
||||
setupDependencyProviders(project, extension);
|
||||
|
||||
// Resolve the mapping files from the configuration
|
||||
final DependencyInfo mappingsDep = DependencyInfo.create(getProject(), Configurations.MAPPINGS);
|
||||
final MappingConfiguration mappingConfiguration = MappingConfiguration.create(getProject(), configContext.serviceFactory(), mappingsDep, minecraftProvider);
|
||||
extension.setMappingConfiguration(mappingConfiguration);
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2020-2023 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
||||
import net.fabricmc.loom.util.download.DownloadException;
|
||||
import net.fabricmc.loom.util.fmj.FabricModJson;
|
||||
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
|
||||
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
||||
|
||||
public abstract class FabricApiExtension {
|
||||
@Inject
|
||||
public abstract Project getProject();
|
||||
|
||||
private static final String DATAGEN_SOURCESET_NAME = "datagen";
|
||||
|
||||
private static final HashMap<String, Map<String, String>> moduleVersionCache = new HashMap<>();
|
||||
private static final HashMap<String, Map<String, String>> deprecatedModuleVersionCache = new HashMap<>();
|
||||
|
||||
public Dependency module(String moduleName, String fabricApiVersion) {
|
||||
return getProject().getDependencies()
|
||||
.create(getDependencyNotation(moduleName, fabricApiVersion));
|
||||
}
|
||||
|
||||
public String moduleVersion(String moduleName, String fabricApiVersion) {
|
||||
String moduleVersion = moduleVersionCache
|
||||
.computeIfAbsent(fabricApiVersion, this::getApiModuleVersions)
|
||||
.get(moduleName);
|
||||
|
||||
if (moduleVersion == null) {
|
||||
moduleVersion = deprecatedModuleVersionCache
|
||||
.computeIfAbsent(fabricApiVersion, this::getDeprecatedApiModuleVersions)
|
||||
.get(moduleName);
|
||||
}
|
||||
|
||||
if (moduleVersion == null) {
|
||||
throw new RuntimeException("Failed to find module version for module: " + moduleName);
|
||||
}
|
||||
|
||||
return moduleVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure data generation with the default options.
|
||||
*/
|
||||
public void configureDataGeneration() {
|
||||
configureDataGeneration(dataGenerationSettings -> { });
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure data generation with custom options.
|
||||
*/
|
||||
public void configureDataGeneration(Action<DataGenerationSettings> action) {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||
final TaskContainer taskContainer = getProject().getTasks();
|
||||
|
||||
DataGenerationSettings settings = getProject().getObjects().newInstance(DataGenerationSettings.class);
|
||||
settings.getOutputDirectory().set(getProject().file("src/main/generated"));
|
||||
settings.getCreateRunConfiguration().convention(true);
|
||||
settings.getCreateSourceSet().convention(false);
|
||||
settings.getStrictValidation().convention(false);
|
||||
settings.getAddToResources().convention(true);
|
||||
settings.getClient().convention(false);
|
||||
|
||||
action.execute(settings);
|
||||
|
||||
final SourceSet mainSourceSet = SourceSetHelper.getMainSourceSet(getProject());
|
||||
final File outputDirectory = settings.getOutputDirectory().getAsFile().get();
|
||||
|
||||
if (settings.getAddToResources().get()) {
|
||||
mainSourceSet.resources(files -> {
|
||||
// Add the src/main/generated to the main sourceset's resources.
|
||||
Set<File> srcDirs = new HashSet<>(files.getSrcDirs());
|
||||
srcDirs.add(outputDirectory);
|
||||
files.setSrcDirs(srcDirs);
|
||||
});
|
||||
}
|
||||
|
||||
// Exclude the cache dir from the output jar to ensure reproducibility.
|
||||
taskContainer.getByName(JavaPlugin.JAR_TASK_NAME, task -> {
|
||||
Jar jar = (Jar) task;
|
||||
jar.exclude(".cache/**");
|
||||
});
|
||||
|
||||
if (settings.getCreateSourceSet().get()) {
|
||||
final boolean isClientAndSplit = extension.areEnvironmentSourceSetsSplit() && settings.getClient().get();
|
||||
|
||||
SourceSetContainer sourceSets = SourceSetHelper.getSourceSets(getProject());
|
||||
|
||||
// Create the new datagen sourceset, depend on the main or client sourceset.
|
||||
SourceSet dataGenSourceSet = sourceSets.create(DATAGEN_SOURCESET_NAME, sourceSet -> {
|
||||
dependsOn(sourceSet, mainSourceSet);
|
||||
|
||||
if (isClientAndSplit) {
|
||||
dependsOn(sourceSet, SourceSetHelper.getSourceSetByName(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME, getProject()));
|
||||
}
|
||||
});
|
||||
|
||||
settings.getModId().convention(getProject().provider(() -> {
|
||||
try {
|
||||
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), dataGenSourceSet);
|
||||
|
||||
if (fabricModJson == null) {
|
||||
throw new RuntimeException("Could not find a fabric.mod.json file in the data source set or a value for DataGenerationSettings.getModId()");
|
||||
}
|
||||
|
||||
return fabricModJson.getId();
|
||||
} catch (IOException e) {
|
||||
throw new org.gradle.api.UncheckedIOException("Failed to read mod id from the datagen source set.", e);
|
||||
}
|
||||
}));
|
||||
|
||||
extension.getMods().create(settings.getModId().get(), mod -> {
|
||||
// Create a classpath group for this mod. Assume that the main sourceset is already in a group.
|
||||
mod.sourceSet(DATAGEN_SOURCESET_NAME);
|
||||
});
|
||||
|
||||
extension.createRemapConfigurations(sourceSets.getByName(DATAGEN_SOURCESET_NAME));
|
||||
}
|
||||
|
||||
if (settings.getCreateRunConfiguration().get()) {
|
||||
extension.getRunConfigs().create("datagen", run -> {
|
||||
run.inherit(extension.getRunConfigs().getByName(settings.getClient().get() ? "client" : "server"));
|
||||
run.setConfigName("Data Generation");
|
||||
|
||||
run.property("fabric-api.datagen");
|
||||
run.property("fabric-api.datagen.output-dir", outputDirectory.getAbsolutePath());
|
||||
run.runDir("build/datagen");
|
||||
|
||||
if (settings.getModId().isPresent()) {
|
||||
run.property("fabric-api.datagen.modid", settings.getModId().get());
|
||||
}
|
||||
|
||||
if (settings.getStrictValidation().get()) {
|
||||
run.property("fabric-api.datagen.strict-validation", "true");
|
||||
}
|
||||
|
||||
if (settings.getCreateSourceSet().get()) {
|
||||
run.source(DATAGEN_SOURCESET_NAME);
|
||||
}
|
||||
});
|
||||
|
||||
// Add the output directory as an output allowing the task to be skipped.
|
||||
getProject().getTasks().named("runDatagen", task -> {
|
||||
task.getOutputs().dir(outputDirectory);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public interface DataGenerationSettings {
|
||||
/**
|
||||
* Contains the output directory where generated data files will be stored.
|
||||
*/
|
||||
RegularFileProperty getOutputDirectory();
|
||||
|
||||
/**
|
||||
* Contains a boolean indicating whether a run configuration should be created for the data generation process.
|
||||
*/
|
||||
Property<Boolean> getCreateRunConfiguration();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether a new source set should be created for the data generation process.
|
||||
*/
|
||||
Property<Boolean> getCreateSourceSet();
|
||||
|
||||
/**
|
||||
* Contains a string property representing the mod ID associated with the data generation process.
|
||||
*
|
||||
* <p>This must be set when {@link #getCreateRunConfiguration()} is set.
|
||||
*/
|
||||
Property<String> getModId();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether strict validation is enabled.
|
||||
*/
|
||||
Property<Boolean> getStrictValidation();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether the generated resources will be automatically added to the main sourceset.
|
||||
*/
|
||||
Property<Boolean> getAddToResources();
|
||||
|
||||
/**
|
||||
* Contains a boolean property indicating whether data generation will be compiled and ran with the client.
|
||||
*/
|
||||
Property<Boolean> getClient();
|
||||
}
|
||||
|
||||
private String getDependencyNotation(String moduleName, String fabricApiVersion) {
|
||||
return String.format("net.fabricmc.fabric-api:%s:%s", moduleName, moduleVersion(moduleName, fabricApiVersion));
|
||||
}
|
||||
|
||||
private Map<String, String> getApiModuleVersions(String fabricApiVersion) {
|
||||
try {
|
||||
return populateModuleVersionMap(getApiMavenPom(fabricApiVersion));
|
||||
} catch (PomNotFoundException e) {
|
||||
throw new RuntimeException("Could not find fabric-api version: " + fabricApiVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> getDeprecatedApiModuleVersions(String fabricApiVersion) {
|
||||
try {
|
||||
return populateModuleVersionMap(getDeprecatedApiMavenPom(fabricApiVersion));
|
||||
} catch (PomNotFoundException e) {
|
||||
// Not all fabric-api versions have deprecated modules, return an empty map to cache this fact.
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> populateModuleVersionMap(File pomFile) {
|
||||
try {
|
||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
||||
Document pom = docBuilder.parse(pomFile);
|
||||
|
||||
Map<String, String> versionMap = new HashMap<>();
|
||||
|
||||
NodeList dependencies = ((Element) pom.getElementsByTagName("dependencies").item(0)).getElementsByTagName("dependency");
|
||||
|
||||
for (int i = 0; i < dependencies.getLength(); i++) {
|
||||
Element dep = (Element) dependencies.item(i);
|
||||
Element artifact = (Element) dep.getElementsByTagName("artifactId").item(0);
|
||||
Element version = (Element) dep.getElementsByTagName("version").item(0);
|
||||
|
||||
if (artifact == null || version == null) {
|
||||
throw new RuntimeException("Failed to find artifact or version");
|
||||
}
|
||||
|
||||
versionMap.put(artifact.getTextContent(), version.getTextContent());
|
||||
}
|
||||
|
||||
return versionMap;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to parse " + pomFile.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private File getApiMavenPom(String fabricApiVersion) throws PomNotFoundException {
|
||||
return getPom("fabric-api", fabricApiVersion);
|
||||
}
|
||||
|
||||
private File getDeprecatedApiMavenPom(String fabricApiVersion) throws PomNotFoundException {
|
||||
return getPom("fabric-api-deprecated", fabricApiVersion);
|
||||
}
|
||||
|
||||
private File getPom(String name, String version) throws PomNotFoundException {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||
final var mavenPom = new File(extension.getFiles().getUserCache(), "fabric-api/%s-%s.pom".formatted(name, version));
|
||||
|
||||
try {
|
||||
extension.download(String.format("https://maven.fabricmc.net/net/fabricmc/fabric-api/%2$s/%1$s/%2$s-%1$s.pom", version, name))
|
||||
.defaultCache()
|
||||
.downloadPath(mavenPom.toPath());
|
||||
} catch (DownloadException e) {
|
||||
if (e.getStatusCode() == 404) {
|
||||
throw new PomNotFoundException(e);
|
||||
}
|
||||
|
||||
throw new UncheckedIOException("Failed to download maven info to " + mavenPom.getName(), e);
|
||||
}
|
||||
|
||||
return mavenPom;
|
||||
}
|
||||
|
||||
private static class PomNotFoundException extends Exception {
|
||||
PomNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
private static void extendsFrom(Project project, String name, String extendsFrom) {
|
||||
final ConfigurationContainer configurations = project.getConfigurations();
|
||||
|
||||
configurations.named(name, configuration -> {
|
||||
configuration.extendsFrom(configurations.getByName(extendsFrom));
|
||||
});
|
||||
}
|
||||
|
||||
private void dependsOn(SourceSet sourceSet, SourceSet other) {
|
||||
sourceSet.setCompileClasspath(
|
||||
sourceSet.getCompileClasspath()
|
||||
.plus(other.getOutput())
|
||||
);
|
||||
|
||||
sourceSet.setRuntimeClasspath(
|
||||
sourceSet.getRuntimeClasspath()
|
||||
.plus(other.getOutput())
|
||||
);
|
||||
|
||||
extendsFrom(getProject(), sourceSet.getCompileClasspathConfigurationName(), other.getCompileClasspathConfigurationName());
|
||||
extendsFrom(getProject(), sourceSet.getRuntimeClasspathConfigurationName(), other.getRuntimeClasspathConfigurationName());
|
||||
}
|
||||
}
|
||||
@@ -155,6 +155,13 @@ public abstract class LoomConfigurations implements Runnable {
|
||||
getDependencies().add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation());
|
||||
getDependencies().add(JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME, LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation());
|
||||
|
||||
register(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Role.RESOLVABLE);
|
||||
extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.MINECRAFT_NATIVES);
|
||||
extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.MINECRAFT_CLIENT_RUNTIME_LIBRARIES);
|
||||
extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.LOADER_DEPENDENCIES);
|
||||
|
||||
register(Constants.Configurations.PRODUCTION_RUNTIME_MODS, Role.RESOLVABLE);
|
||||
|
||||
GradleUtils.afterSuccessfulEvaluation(getProject(), () -> {
|
||||
if (extension.shouldGenerateSrgTiny()) {
|
||||
registerNonTransitive(Constants.Configurations.SRG, Role.RESOLVABLE);
|
||||
|
||||
@@ -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.fabricapi;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
|
||||
import net.fabricmc.loom.util.fmj.FabricModJson;
|
||||
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
|
||||
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
||||
|
||||
abstract class FabricApiAbstractSourceSet {
|
||||
@Inject
|
||||
protected abstract Project getProject();
|
||||
|
||||
protected abstract String getSourceSetName();
|
||||
|
||||
protected SourceSet configureSourceSet(Property<String> modId, boolean isClient) {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||
final SourceSet mainSourceSet = SourceSetHelper.getMainSourceSet(getProject());
|
||||
|
||||
final boolean isClientAndSplit = extension.areEnvironmentSourceSetsSplit() && isClient;
|
||||
|
||||
SourceSetContainer sourceSets = SourceSetHelper.getSourceSets(getProject());
|
||||
|
||||
// Create the new sourceset, depend on the main or client sourceset.
|
||||
SourceSet sourceSet = sourceSets.create(getSourceSetName(), ss -> {
|
||||
dependsOn(ss, mainSourceSet);
|
||||
|
||||
if (isClientAndSplit) {
|
||||
dependsOn(ss, SourceSetHelper.getSourceSetByName(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME, getProject()));
|
||||
}
|
||||
});
|
||||
|
||||
modId.convention(getProject().provider(() -> {
|
||||
try {
|
||||
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), sourceSet);
|
||||
|
||||
if (fabricModJson == null) {
|
||||
throw new RuntimeException("Could not find a fabric.mod.json file in the data source set or a value for DataGenerationSettings.getModId()");
|
||||
}
|
||||
|
||||
return fabricModJson.getId();
|
||||
} catch (IOException e) {
|
||||
throw new org.gradle.api.UncheckedIOException("Failed to read mod id from the datagen source set.", e);
|
||||
}
|
||||
}));
|
||||
|
||||
extension.getMods().create(modId.get(), mod -> {
|
||||
// Create a classpath group for this mod. Assume that the main sourceset is already in a group.
|
||||
mod.sourceSet(getSourceSetName());
|
||||
});
|
||||
|
||||
extension.createRemapConfigurations(sourceSets.getByName(getSourceSetName()));
|
||||
|
||||
return sourceSet;
|
||||
}
|
||||
|
||||
private static void extendsFrom(Project project, String name, String extendsFrom) {
|
||||
final ConfigurationContainer configurations = project.getConfigurations();
|
||||
|
||||
configurations.named(name, configuration -> {
|
||||
configuration.extendsFrom(configurations.getByName(extendsFrom));
|
||||
});
|
||||
}
|
||||
|
||||
private void dependsOn(SourceSet sourceSet, SourceSet other) {
|
||||
sourceSet.setCompileClasspath(
|
||||
sourceSet.getCompileClasspath()
|
||||
.plus(other.getOutput())
|
||||
);
|
||||
|
||||
sourceSet.setRuntimeClasspath(
|
||||
sourceSet.getRuntimeClasspath()
|
||||
.plus(other.getOutput())
|
||||
);
|
||||
|
||||
extendsFrom(getProject(), sourceSet.getCompileClasspathConfigurationName(), other.getCompileClasspathConfigurationName());
|
||||
extendsFrom(getProject(), sourceSet.getRuntimeClasspathConfigurationName(), other.getRuntimeClasspathConfigurationName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2024 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.fabricapi;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.api.fabricapi.DataGenerationSettings;
|
||||
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
||||
|
||||
public abstract class FabricApiDataGeneration extends FabricApiAbstractSourceSet {
|
||||
@Inject
|
||||
protected abstract Project getProject();
|
||||
|
||||
@Inject
|
||||
public FabricApiDataGeneration() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSourceSetName() {
|
||||
return "datagen";
|
||||
}
|
||||
|
||||
void configureDataGeneration(Action<DataGenerationSettings> action) {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||
final TaskContainer taskContainer = getProject().getTasks();
|
||||
|
||||
DataGenerationSettings settings = getProject().getObjects().newInstance(DataGenerationSettings.class);
|
||||
settings.getOutputDirectory().set(getProject().file("src/main/generated"));
|
||||
settings.getCreateRunConfiguration().convention(true);
|
||||
settings.getCreateSourceSet().convention(false);
|
||||
settings.getStrictValidation().convention(false);
|
||||
settings.getAddToResources().convention(true);
|
||||
settings.getClient().convention(false);
|
||||
|
||||
action.execute(settings);
|
||||
|
||||
final SourceSet mainSourceSet = SourceSetHelper.getMainSourceSet(getProject());
|
||||
final File outputDirectory = settings.getOutputDirectory().getAsFile().get();
|
||||
|
||||
if (settings.getAddToResources().get()) {
|
||||
mainSourceSet.resources(files -> {
|
||||
// Add the src/main/generated to the main sourceset's resources.
|
||||
Set<File> srcDirs = new HashSet<>(files.getSrcDirs());
|
||||
srcDirs.add(outputDirectory);
|
||||
files.setSrcDirs(srcDirs);
|
||||
});
|
||||
}
|
||||
|
||||
// Exclude the cache dir from the output jar to ensure reproducibility.
|
||||
taskContainer.getByName(JavaPlugin.JAR_TASK_NAME, task -> {
|
||||
Jar jar = (Jar) task;
|
||||
jar.exclude(".cache/**");
|
||||
});
|
||||
|
||||
if (settings.getCreateSourceSet().get()) {
|
||||
configureSourceSet(settings.getModId(), settings.getClient().get());
|
||||
}
|
||||
|
||||
if (settings.getCreateRunConfiguration().get()) {
|
||||
extension.getRunConfigs().create("datagen", run -> {
|
||||
run.inherit(extension.getRunConfigs().getByName(settings.getClient().get() ? "client" : "server"));
|
||||
run.setConfigName("Data Generation");
|
||||
|
||||
run.property("fabric-api.datagen");
|
||||
run.property("fabric-api.datagen.output-dir", outputDirectory.getAbsolutePath());
|
||||
run.runDir("build/datagen");
|
||||
|
||||
if (settings.getModId().isPresent()) {
|
||||
run.property("fabric-api.datagen.modid", settings.getModId().get());
|
||||
}
|
||||
|
||||
if (settings.getStrictValidation().get()) {
|
||||
run.property("fabric-api.datagen.strict-validation", "true");
|
||||
}
|
||||
|
||||
if (settings.getCreateSourceSet().get()) {
|
||||
run.source(getSourceSetName());
|
||||
}
|
||||
});
|
||||
|
||||
// Add the output directory as an output allowing the task to be skipped.
|
||||
getProject().getTasks().named("runDatagen", task -> {
|
||||
task.getOutputs().dir(outputDirectory);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void extendsFrom(Project project, String name, String extendsFrom) {
|
||||
final ConfigurationContainer configurations = project.getConfigurations();
|
||||
|
||||
configurations.named(name, configuration -> {
|
||||
configuration.extendsFrom(configurations.getByName(extendsFrom));
|
||||
});
|
||||
}
|
||||
|
||||
private void dependsOn(SourceSet sourceSet, SourceSet other) {
|
||||
sourceSet.setCompileClasspath(
|
||||
sourceSet.getCompileClasspath()
|
||||
.plus(other.getOutput())
|
||||
);
|
||||
|
||||
sourceSet.setRuntimeClasspath(
|
||||
sourceSet.getRuntimeClasspath()
|
||||
.plus(other.getOutput())
|
||||
);
|
||||
|
||||
extendsFrom(getProject(), sourceSet.getCompileClasspathConfigurationName(), other.getCompileClasspathConfigurationName());
|
||||
extendsFrom(getProject(), sourceSet.getRuntimeClasspathConfigurationName(), other.getRuntimeClasspathConfigurationName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2020-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.fabricapi;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
|
||||
import net.fabricmc.loom.api.fabricapi.DataGenerationSettings;
|
||||
import net.fabricmc.loom.api.fabricapi.FabricApiExtension;
|
||||
import net.fabricmc.loom.api.fabricapi.GameTestSettings;
|
||||
|
||||
public abstract class FabricApiExtensionImpl implements FabricApiExtension {
|
||||
@Inject
|
||||
protected abstract ObjectFactory getObjectFactory();
|
||||
|
||||
private final FabricApiVersions versions;
|
||||
private final FabricApiDataGeneration dataGeneration;
|
||||
private final FabricApiTesting testing;
|
||||
|
||||
public FabricApiExtensionImpl() {
|
||||
versions = getObjectFactory().newInstance(FabricApiVersions.class);
|
||||
dataGeneration = getObjectFactory().newInstance(FabricApiDataGeneration.class);
|
||||
testing = getObjectFactory().newInstance(FabricApiTesting.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dependency module(String moduleName, String fabricApiVersion) {
|
||||
return versions.module(moduleName, fabricApiVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String moduleVersion(String moduleName, String fabricApiVersion) {
|
||||
return versions.moduleVersion(moduleName, fabricApiVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureDataGeneration() {
|
||||
configureDataGeneration(dataGenerationSettings -> { });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureDataGeneration(Action<DataGenerationSettings> action) {
|
||||
dataGeneration.configureDataGeneration(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTests() {
|
||||
configureTests(gameTestSettings -> { });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTests(Action<GameTestSettings> action) {
|
||||
testing.configureTests(action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.fabricapi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.Delete;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.api.fabricapi.GameTestSettings;
|
||||
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
|
||||
import net.fabricmc.loom.task.AbstractLoomTask;
|
||||
import net.fabricmc.loom.task.LoomTasks;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
||||
|
||||
public abstract class FabricApiTesting extends FabricApiAbstractSourceSet {
|
||||
@Inject
|
||||
protected abstract Project getProject();
|
||||
|
||||
@Inject
|
||||
public FabricApiTesting() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSourceSetName() {
|
||||
return "gametest";
|
||||
}
|
||||
|
||||
void configureTests(Action<GameTestSettings> action) {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||
final TaskContainer tasks = getProject().getTasks();
|
||||
|
||||
GameTestSettings settings = getProject().getObjects().newInstance(GameTestSettings.class);
|
||||
settings.getCreateSourceSet().convention(false);
|
||||
settings.getEnableGameTests().convention(true);
|
||||
settings.getEnableClientGameTests().convention(true);
|
||||
settings.getEula().convention(false);
|
||||
settings.getClearRunDirectory().convention(true);
|
||||
settings.getUsername().convention("Player0");
|
||||
|
||||
action.execute(settings);
|
||||
|
||||
final SourceSet testSourceSet;
|
||||
|
||||
if (settings.getCreateSourceSet().get()) {
|
||||
testSourceSet = configureSourceSet(settings.getModId(), true);
|
||||
} else {
|
||||
testSourceSet = SourceSetHelper.getMainSourceSet(getProject());
|
||||
}
|
||||
|
||||
Consumer<RunConfigSettings> configureBase = run -> {
|
||||
if (settings.getCreateSourceSet().get()) {
|
||||
run.source(getSourceSetName());
|
||||
}
|
||||
};
|
||||
|
||||
if (settings.getEnableGameTests().get()) {
|
||||
RunConfigSettings gameTest = extension.getRunConfigs().create("gameTest", run -> {
|
||||
run.inherit(extension.getRunConfigs().getByName("server"));
|
||||
run.property("fabric-api.gametest");
|
||||
run.runDir("build/run/gameTest");
|
||||
configureBase.accept(run);
|
||||
});
|
||||
|
||||
tasks.named("test", task -> task.dependsOn(LoomTasks.getRunConfigTaskName(gameTest)));
|
||||
}
|
||||
|
||||
if (settings.getEnableClientGameTests().get()) {
|
||||
// Not ideal as there may be multiple resources directories, if this isnt correct the mod will need to override this.
|
||||
final File resourcesDir = testSourceSet.getResources().getSrcDirs().stream().findFirst().orElse(null);
|
||||
|
||||
RunConfigSettings clientGameTest = extension.getRunConfigs().create("clientGameTest", run -> {
|
||||
run.inherit(extension.getRunConfigs().getByName("client"));
|
||||
run.property("fabric.client.gametest");
|
||||
|
||||
if (resourcesDir != null) {
|
||||
run.property("fabric.client.gametest.testModResourcesPath", resourcesDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
run.runDir("build/run/clientGameTest");
|
||||
|
||||
if (settings.getUsername().isPresent()) {
|
||||
run.programArgs("--username", settings.getUsername().get());
|
||||
}
|
||||
|
||||
configureBase.accept(run);
|
||||
});
|
||||
|
||||
if (settings.getClearRunDirectory().get()) {
|
||||
var deleteGameTestRunDir = tasks.register("deleteGameTestRunDir", Delete.class, task -> {
|
||||
task.setGroup(Constants.TaskGroup.FABRIC);
|
||||
task.delete(clientGameTest.getRunDir());
|
||||
});
|
||||
|
||||
tasks.named(LoomTasks.getRunConfigTaskName(clientGameTest), task -> task.dependsOn(deleteGameTestRunDir));
|
||||
}
|
||||
|
||||
if (settings.getEula().get()) {
|
||||
var acceptEula = tasks.register("acceptGameTestEula", AcceptEulaTask.class, task -> {
|
||||
task.getEulaFile().set(getProject().file(clientGameTest.getRunDir() + "/eula.txt"));
|
||||
|
||||
if (settings.getClearRunDirectory().get()) {
|
||||
// Ensure that the eula is accepted after the run directory is cleared
|
||||
task.dependsOn(tasks.named("deleteGameTestRunDir"));
|
||||
}
|
||||
});
|
||||
|
||||
tasks.named("configureLaunch", task -> task.dependsOn(acceptEula));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class AcceptEulaTask extends AbstractLoomTask {
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getEulaFile();
|
||||
|
||||
@TaskAction
|
||||
public void acceptEula() throws IOException {
|
||||
final Path eula = getEulaFile().get().getAsFile().toPath();
|
||||
|
||||
if (Files.notExists(eula)) {
|
||||
Files.writeString(eula, """
|
||||
#This file was generated by the Fabric Loom Gradle plugin. As the user opted into accepting the EULA.
|
||||
eula=true
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2024 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.fabricapi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.util.download.DownloadException;
|
||||
|
||||
public abstract class FabricApiVersions {
|
||||
@Inject
|
||||
protected abstract Project getProject();
|
||||
|
||||
private final HashMap<String, Map<String, String>> moduleVersionCache = new HashMap<>();
|
||||
private final HashMap<String, Map<String, String>> deprecatedModuleVersionCache = new HashMap<>();
|
||||
|
||||
public Dependency module(String moduleName, String fabricApiVersion) {
|
||||
return getProject().getDependencies()
|
||||
.create(getDependencyNotation(moduleName, fabricApiVersion));
|
||||
}
|
||||
|
||||
public String moduleVersion(String moduleName, String fabricApiVersion) {
|
||||
String moduleVersion = moduleVersionCache
|
||||
.computeIfAbsent(fabricApiVersion, this::getApiModuleVersions)
|
||||
.get(moduleName);
|
||||
|
||||
if (moduleVersion == null) {
|
||||
moduleVersion = deprecatedModuleVersionCache
|
||||
.computeIfAbsent(fabricApiVersion, this::getDeprecatedApiModuleVersions)
|
||||
.get(moduleName);
|
||||
}
|
||||
|
||||
if (moduleVersion == null) {
|
||||
throw new RuntimeException("Failed to find module version for module: " + moduleName);
|
||||
}
|
||||
|
||||
return moduleVersion;
|
||||
}
|
||||
|
||||
private String getDependencyNotation(String moduleName, String fabricApiVersion) {
|
||||
return String.format("net.fabricmc.fabric-api:%s:%s", moduleName, moduleVersion(moduleName, fabricApiVersion));
|
||||
}
|
||||
|
||||
private Map<String, String> getApiModuleVersions(String fabricApiVersion) {
|
||||
try {
|
||||
return populateModuleVersionMap(getApiMavenPom(fabricApiVersion));
|
||||
} catch (PomNotFoundException e) {
|
||||
throw new RuntimeException("Could not find fabric-api version: " + fabricApiVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> getDeprecatedApiModuleVersions(String fabricApiVersion) {
|
||||
try {
|
||||
return populateModuleVersionMap(getDeprecatedApiMavenPom(fabricApiVersion));
|
||||
} catch (PomNotFoundException e) {
|
||||
// Not all fabric-api versions have deprecated modules, return an empty map to cache this fact.
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> populateModuleVersionMap(File pomFile) {
|
||||
try {
|
||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
||||
Document pom = docBuilder.parse(pomFile);
|
||||
|
||||
Map<String, String> versionMap = new HashMap<>();
|
||||
|
||||
NodeList dependencies = ((Element) pom.getElementsByTagName("dependencies").item(0)).getElementsByTagName("dependency");
|
||||
|
||||
for (int i = 0; i < dependencies.getLength(); i++) {
|
||||
Element dep = (Element) dependencies.item(i);
|
||||
Element artifact = (Element) dep.getElementsByTagName("artifactId").item(0);
|
||||
Element version = (Element) dep.getElementsByTagName("version").item(0);
|
||||
|
||||
if (artifact == null || version == null) {
|
||||
throw new RuntimeException("Failed to find artifact or version");
|
||||
}
|
||||
|
||||
versionMap.put(artifact.getTextContent(), version.getTextContent());
|
||||
}
|
||||
|
||||
return versionMap;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to parse " + pomFile.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private File getApiMavenPom(String fabricApiVersion) throws PomNotFoundException {
|
||||
return getPom("fabric-api", fabricApiVersion);
|
||||
}
|
||||
|
||||
private File getDeprecatedApiMavenPom(String fabricApiVersion) throws PomNotFoundException {
|
||||
return getPom("fabric-api-deprecated", fabricApiVersion);
|
||||
}
|
||||
|
||||
private File getPom(String name, String version) throws PomNotFoundException {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
|
||||
final var mavenPom = new File(extension.getFiles().getUserCache(), "fabric-api/%s-%s.pom".formatted(name, version));
|
||||
|
||||
try {
|
||||
extension.download(String.format("https://maven.fabricmc.net/net/fabricmc/fabric-api/%2$s/%1$s/%2$s-%1$s.pom", version, name))
|
||||
.defaultCache()
|
||||
.downloadPath(mavenPom.toPath());
|
||||
} catch (DownloadException e) {
|
||||
if (e.getStatusCode() == 404) {
|
||||
throw new PomNotFoundException(e);
|
||||
}
|
||||
|
||||
throw new UncheckedIOException("Failed to download maven info to " + mavenPom.getName(), e);
|
||||
}
|
||||
|
||||
return mavenPom;
|
||||
}
|
||||
|
||||
private static class PomNotFoundException extends Exception {
|
||||
PomNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,8 @@ public abstract class IdeaSyncTask extends AbstractLoomTask {
|
||||
irc.getExcludedLibraryPaths().set(excludedLibraryPaths);
|
||||
irc.getLaunchFile().set(runConfigFile);
|
||||
configs.add(irc);
|
||||
|
||||
settings.makeRunDir();
|
||||
}
|
||||
|
||||
return configs;
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -43,6 +44,8 @@ import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.FileCollectionDependency;
|
||||
import org.gradle.api.artifacts.MutableVersionConstraint;
|
||||
import org.gradle.api.artifacts.ResolvedArtifact;
|
||||
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier;
|
||||
import org.gradle.api.artifacts.component.ComponentIdentifier;
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||
import org.gradle.api.artifacts.query.ArtifactResolutionQuery;
|
||||
import org.gradle.api.artifacts.result.ArtifactResult;
|
||||
@@ -244,7 +247,10 @@ public class ModConfigurationRemapper {
|
||||
private static List<ArtifactRef> resolveArtifacts(Project project, Configuration configuration) {
|
||||
final List<ArtifactRef> artifacts = new ArrayList<>();
|
||||
|
||||
for (ResolvedArtifact artifact : configuration.getResolvedConfiguration().getResolvedArtifacts()) {
|
||||
final Set<ResolvedArtifact> resolvedArtifacts = configuration.getResolvedConfiguration().getResolvedArtifacts();
|
||||
downloadAllSources(project, resolvedArtifacts);
|
||||
|
||||
for (ResolvedArtifact artifact : resolvedArtifacts) {
|
||||
final Path sources = findSources(project, artifact);
|
||||
artifacts.add(new ArtifactRef.ResolvedArtifactRef(artifact, sources));
|
||||
}
|
||||
@@ -271,6 +277,27 @@ public class ModConfigurationRemapper {
|
||||
return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
|
||||
}
|
||||
|
||||
private static void downloadAllSources(Project project, Set<ResolvedArtifact> resolvedArtifacts) {
|
||||
if (isCIBuild()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final DependencyHandler dependencies = project.getDependencies();
|
||||
|
||||
List<ComponentIdentifier> componentIdentifiers = resolvedArtifacts.stream()
|
||||
.map(ResolvedArtifact::getId)
|
||||
.map(ComponentArtifactIdentifier::getComponentIdentifier)
|
||||
.toList();
|
||||
|
||||
//noinspection unchecked
|
||||
ArtifactResolutionQuery query = dependencies.createArtifactResolutionQuery()
|
||||
.forComponents(componentIdentifiers)
|
||||
.withArtifacts(JvmLibrary.class, SourcesArtifact.class);
|
||||
|
||||
// Run a single query for all of the artifacts, this will allow them to be resolved in parallel before they are queried individually
|
||||
query.execute();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Path findSources(Project project, ResolvedArtifact artifact) {
|
||||
if (isCIBuild()) {
|
||||
|
||||
@@ -61,6 +61,7 @@ import net.fabricmc.loom.util.LoggerFilter;
|
||||
import net.fabricmc.loom.util.ModPlatform;
|
||||
import net.fabricmc.loom.util.Pair;
|
||||
import net.fabricmc.loom.util.TinyRemapperHelper;
|
||||
import net.fabricmc.loom.util.TinyRemapperLoggerAdapter;
|
||||
import net.fabricmc.loom.util.ZipUtils;
|
||||
import net.fabricmc.loom.util.kotlin.KotlinClasspathService;
|
||||
import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader;
|
||||
@@ -177,7 +178,7 @@ public class ModProcessor {
|
||||
MemoryMappingTree mappings = mappingConfiguration.getMappingsService(project, serviceFactory, mappingOption).getMappingTree();
|
||||
LoggerFilter.replaceSystemOut();
|
||||
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE)
|
||||
.withKnownIndyBsm(knownIndyBsms)
|
||||
.withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false))
|
||||
.renameInvalidLocals(false)
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
@@ -42,6 +43,7 @@ import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ProjectDependency;
|
||||
import org.gradle.api.attributes.Usage;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.api.RemapConfigurationSettings;
|
||||
@@ -121,27 +123,37 @@ public record SpecContextImpl(List<FabricModJson> modDependencies, List<FabricMo
|
||||
// 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) {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
final List<Path> runtimeEntries = extension.getRuntimeRemapConfigurations().stream()
|
||||
final Set<String> runtimeModIds = extension.getRuntimeRemapConfigurations().stream()
|
||||
.filter(settings -> settings.getApplyDependencyTransforms().get())
|
||||
.flatMap(resolveArtifacts(project, true))
|
||||
.toList();
|
||||
.map(modFromZip(fmjCache))
|
||||
.filter(Objects::nonNull)
|
||||
.map(FabricModJson::getId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return extension.getCompileRemapConfigurations().stream()
|
||||
.filter(settings -> settings.getApplyDependencyTransforms().get())
|
||||
.flatMap(resolveArtifacts(project, false))
|
||||
.filter(runtimeEntries::contains) // Use the intersection of the two configurations.
|
||||
.map(zipPath -> {
|
||||
final List<FabricModJson> list = fmjCache.computeIfAbsent(zipPath.toAbsolutePath().toString(), $ -> {
|
||||
return FabricModJsonFactory.createFromZipOptional(zipPath)
|
||||
.map(List::of)
|
||||
.orElseGet(List::of);
|
||||
});
|
||||
return list.isEmpty() ? null : list.get(0);
|
||||
})
|
||||
.flatMap(resolveArtifacts(project, false))// Use the intersection of the two configurations.
|
||||
.map(modFromZip(fmjCache))
|
||||
.filter(Objects::nonNull)
|
||||
// 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.
|
||||
// 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()))
|
||||
.sorted(Comparator.comparing(FabricModJson::getId));
|
||||
}
|
||||
|
||||
private static Function<Path, @Nullable FabricModJson> modFromZip(Map<String, List<FabricModJson>> fmjCache) {
|
||||
return zipPath -> {
|
||||
final List<FabricModJson> list = fmjCache.computeIfAbsent(zipPath.toAbsolutePath().toString(), $ -> {
|
||||
return FabricModJsonFactory.createFromZipOptional(zipPath)
|
||||
.map(List::of)
|
||||
.orElseGet(List::of);
|
||||
});
|
||||
return list.isEmpty() ? null : list.get(0);
|
||||
};
|
||||
}
|
||||
|
||||
private static Function<RemapConfigurationSettings, Stream<Path>> resolveArtifacts(Project project, boolean runtime) {
|
||||
final Usage usage = project.getObjects().named(Usage.class, runtime ? Usage.JAVA_RUNTIME : Usage.JAVA_API);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
|
||||
*/
|
||||
public abstract class NoOpIntermediateMappingsProvider extends IntermediateMappingsProvider {
|
||||
private static final String HEADER_OFFICIAL_MERGED = "tiny\t2\t0\tofficial\tintermediary";
|
||||
private static final String HEADER_OFFICIAL_LEGACY_MERGED = "tiny\t2\t0\tintermediary\tclientOfficial\tserverOfficial\t";
|
||||
private static final String HEADER_OFFICIAL_LEGACY_MERGED = "tiny\t2\t0\tintermediary\tclientOfficial\tserverOfficial";
|
||||
|
||||
@Override
|
||||
public void provide(Path tinyMappings) throws IOException {
|
||||
|
||||
@@ -109,12 +109,7 @@ public class MinecraftLibraryProvider {
|
||||
|
||||
private void provideServerLibraries() {
|
||||
final BundleMetadata serverBundleMetadata = minecraftProvider.getServerBundleMetadata();
|
||||
|
||||
if (serverBundleMetadata == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Library> libraries = MinecraftLibraryHelper.getServerLibraries(serverBundleMetadata);
|
||||
final List<Library> libraries = serverBundleMetadata != null ? MinecraftLibraryHelper.getServerLibraries(serverBundleMetadata) : Collections.emptyList();
|
||||
final List<Library> processLibraries = processLibraries(libraries);
|
||||
processLibraries.forEach(this::applyServerLibrary);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||
import net.fabricmc.loom.configuration.ConfigContext;
|
||||
import net.fabricmc.loom.configuration.providers.BundleMetadata;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.TinyRemapperLoggerAdapter;
|
||||
import net.fabricmc.tinyremapper.NonClassCopyMode;
|
||||
import net.fabricmc.tinyremapper.OutputConsumerPath;
|
||||
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||
@@ -98,7 +99,7 @@ public abstract class SingleJarMinecraftProvider extends MinecraftProvider {
|
||||
TinyRemapper remapper = null;
|
||||
|
||||
try {
|
||||
remapper = TinyRemapper.newRemapper().build();
|
||||
remapper = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE).build();
|
||||
|
||||
Files.deleteIfExists(minecraftEnvOnlyJar);
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ import java.util.function.Function;
|
||||
|
||||
import dev.architectury.loom.util.MappingOption;
|
||||
import org.gradle.api.Project;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||
@@ -65,6 +67,8 @@ import net.fabricmc.tinyremapper.OutputConsumerPath;
|
||||
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||
|
||||
public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvider> implements MappedMinecraftProvider.ProviderImpl {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMappedMinecraftProvider.class);
|
||||
|
||||
protected final M minecraftProvider;
|
||||
private final Project project;
|
||||
protected final LoomGradleExtension extension;
|
||||
@@ -77,8 +81,18 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
|
||||
|
||||
public abstract MappingsNamespace getTargetNamespace();
|
||||
|
||||
/**
|
||||
* @return A list of jars that should be remapped
|
||||
*/
|
||||
public abstract List<RemappedJars> getRemappedJars();
|
||||
|
||||
/**
|
||||
* @return A list of output jars that this provider generates
|
||||
*/
|
||||
public List<? extends OutputJar> getOutputJars() {
|
||||
return getRemappedJars();
|
||||
}
|
||||
|
||||
// Returns a list of MinecraftJar.Type's that this provider exports to be used as a dependency
|
||||
public List<MinecraftJar.Type> getDependencyTypes() {
|
||||
return Collections.emptyList();
|
||||
@@ -94,7 +108,7 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
|
||||
throw new IllegalStateException("No remapped jars provided");
|
||||
}
|
||||
|
||||
if (!areOutputsValid(remappedJars) || context.refreshOutputs() || !hasBackupJars(minecraftJars)) {
|
||||
if (shouldRefreshOutputs(context)) {
|
||||
try {
|
||||
remapInputs(remappedJars, context.configContext());
|
||||
createBackupJars(minecraftJars);
|
||||
@@ -125,16 +139,6 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
|
||||
return outputJarPath.resolveSibling(outputJarPath.getFileName() + ".backup");
|
||||
}
|
||||
|
||||
protected boolean hasBackupJars(List<MinecraftJar> minecraftJars) {
|
||||
for (MinecraftJar minecraftJar : minecraftJars) {
|
||||
if (!Files.exists(getBackupJarPath(minecraftJar))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void createBackupJars(List<MinecraftJar> minecraftJars) throws IOException {
|
||||
for (MinecraftJar minecraftJar : minecraftJars) {
|
||||
Files.copy(minecraftJar.getPath(), getBackupJarPath(minecraftJar), StandardCopyOption.REPLACE_EXISTING);
|
||||
@@ -202,11 +206,16 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
|
||||
return "net.minecraft:%s:%s".formatted(getName(type), getVersion());
|
||||
}
|
||||
|
||||
private boolean areOutputsValid(List<RemappedJars> remappedJars) {
|
||||
for (RemappedJars remappedJar : remappedJars) {
|
||||
if (!getMavenHelper(remappedJar.type()).exists(null)) {
|
||||
return false;
|
||||
}
|
||||
protected boolean shouldRefreshOutputs(ProvideContext context) {
|
||||
if (context.refreshOutputs()) {
|
||||
LOGGER.info("Refreshing outputs for mapped jar, as refresh outputs was requested");
|
||||
return true;
|
||||
}
|
||||
|
||||
final List<? extends OutputJar> outputJars = getOutputJars();
|
||||
|
||||
if (outputJars.isEmpty()) {
|
||||
throw new IllegalStateException("No output jars provided");
|
||||
}
|
||||
|
||||
// Architectury: regenerate jars if patches have changed.
|
||||
@@ -214,7 +223,22 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
for (OutputJar outputJar : outputJars) {
|
||||
if (!getMavenHelper(outputJar.type()).exists(null)) {
|
||||
LOGGER.info("Refreshing outputs for mapped jar, as {} does not exist", outputJar.outputJar());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (OutputJar outputJar : outputJars) {
|
||||
if (!Files.exists(getBackupJarPath(outputJar.outputJar()))) {
|
||||
LOGGER.info("Refreshing outputs for mapped jar, as backup jar does not exist for {}", outputJar.outputJar());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debug("All outputs are up to date");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void remapInputs(List<RemappedJars> remappedJars, ConfigContext configContext) throws IOException {
|
||||
@@ -306,7 +330,15 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
|
||||
return minecraftProvider;
|
||||
}
|
||||
|
||||
public record RemappedJars(Path inputJar, MinecraftJar outputJar, MappingsNamespace sourceNamespace, Path... remapClasspath) {
|
||||
public sealed interface OutputJar permits RemappedJars, SimpleOutputJar {
|
||||
MinecraftJar outputJar();
|
||||
|
||||
default MinecraftJar.Type type() {
|
||||
return outputJar().getType();
|
||||
}
|
||||
}
|
||||
|
||||
public record RemappedJars(Path inputJar, MinecraftJar outputJar, MappingsNamespace sourceNamespace, Path... remapClasspath) implements OutputJar {
|
||||
public Path outputJarPath() {
|
||||
return outputJar().getPath();
|
||||
}
|
||||
@@ -314,9 +346,8 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
|
||||
public String name() {
|
||||
return outputJar().getName();
|
||||
}
|
||||
}
|
||||
|
||||
public MinecraftJar.Type type() {
|
||||
return outputJar().getType();
|
||||
}
|
||||
public record SimpleOutputJar(MinecraftJar outputJar) implements OutputJar {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,18 +78,30 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
|
||||
|
||||
@Override
|
||||
public List<MinecraftJar> provide(ProvideContext context) throws Exception {
|
||||
final List<MinecraftJar> minecraftJars = List.of(getMergedJar());
|
||||
|
||||
// this check must be done before the client and server impls are provided
|
||||
// because the merging only needs to happen if the remapping step is run
|
||||
final boolean refreshOutputs = client.shouldRefreshOutputs(context)
|
||||
|| server.shouldRefreshOutputs(context)
|
||||
|| this.shouldRefreshOutputs(context);
|
||||
|
||||
// Map the client and server jars separately
|
||||
server.provide(context);
|
||||
client.provide(context);
|
||||
|
||||
// then merge them
|
||||
MergedMinecraftProvider.mergeJars(
|
||||
client.getEnvOnlyJar().toFile(),
|
||||
server.getEnvOnlyJar().toFile(),
|
||||
getMergedJar().toFile()
|
||||
);
|
||||
if (refreshOutputs) {
|
||||
// then merge them
|
||||
MergedMinecraftProvider.mergeJars(
|
||||
client.getEnvOnlyJar().toFile(),
|
||||
server.getEnvOnlyJar().toFile(),
|
||||
getMergedJar().toFile()
|
||||
);
|
||||
|
||||
return List.of(getMergedJar());
|
||||
createBackupJars(minecraftJars);
|
||||
}
|
||||
|
||||
return minecraftJars;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,6 +110,13 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
|
||||
throw new UnsupportedOperationException("LegacyMergedImpl does not support getRemappedJars");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends OutputJar> getOutputJars() {
|
||||
return List.of(
|
||||
new SimpleOutputJar(getMergedJar())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MinecraftJar.Type> getDependencyTypes() {
|
||||
return List.of(MinecraftJar.Type.MERGED);
|
||||
|
||||
@@ -85,17 +85,28 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
|
||||
@Override
|
||||
public List<MinecraftJar> provide(ProvideContext context) throws Exception {
|
||||
final ProvideContext childContext = context.withApplyDependencies(false);
|
||||
final List<MinecraftJar> minecraftJars = List.of(getMergedJar());
|
||||
|
||||
// this check must be done before the client and server impls are provided
|
||||
// because the merging only needs to happen if the remapping step is run
|
||||
final boolean refreshOutputs = client.shouldRefreshOutputs(childContext)
|
||||
|| server.shouldRefreshOutputs(childContext)
|
||||
|| this.shouldRefreshOutputs(childContext);
|
||||
|
||||
// Map the client and server jars separately
|
||||
server.provide(childContext);
|
||||
client.provide(childContext);
|
||||
|
||||
// then merge them
|
||||
MergedMinecraftProvider.mergeJars(
|
||||
client.getEnvOnlyJar().toFile(),
|
||||
server.getEnvOnlyJar().toFile(),
|
||||
getMergedJar().toFile()
|
||||
);
|
||||
if (refreshOutputs) {
|
||||
// then merge them
|
||||
MergedMinecraftProvider.mergeJars(
|
||||
client.getEnvOnlyJar().toFile(),
|
||||
server.getEnvOnlyJar().toFile(),
|
||||
getMergedJar().toFile()
|
||||
);
|
||||
|
||||
createBackupJars(minecraftJars);
|
||||
}
|
||||
|
||||
getMavenHelper(MinecraftJar.Type.MERGED).savePom();
|
||||
|
||||
@@ -106,7 +117,7 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
|
||||
);
|
||||
}
|
||||
|
||||
return List.of(getMergedJar());
|
||||
return minecraftJars;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,6 +126,13 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
|
||||
throw new UnsupportedOperationException("LegacyMergedImpl does not support getRemappedJars");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends OutputJar> getOutputJars() {
|
||||
return List.of(
|
||||
new SimpleOutputJar(getMergedJar())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MinecraftJar.Type> getDependencyTypes() {
|
||||
return List.of(MinecraftJar.Type.MERGED);
|
||||
|
||||
@@ -65,7 +65,7 @@ public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvide
|
||||
|
||||
parentMinecraftProvider.provide(context.withApplyDependencies(false));
|
||||
|
||||
boolean requiresProcessing = context.refreshOutputs() || !hasBackupJars(minecraftJars) || parentMinecraftJars.stream()
|
||||
boolean requiresProcessing = shouldRefreshOutputs(context) || parentMinecraftJars.stream()
|
||||
.map(this::getProcessedPath)
|
||||
.anyMatch(jarProcessorManager::requiresProcessingJar);
|
||||
|
||||
@@ -81,6 +81,14 @@ public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvide
|
||||
return List.copyOf(minecraftJarOutputMap.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends OutputJar> getOutputJars() {
|
||||
return parentMinecraftProvider.getMinecraftJars().stream()
|
||||
.map(this::getProcessedJar)
|
||||
.map(SimpleOutputJar::new)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MavenScope getMavenScope() {
|
||||
return MavenScope.LOCAL;
|
||||
|
||||
@@ -106,20 +106,16 @@ public record LineNumberRemapper(ClassLineNumbers lineNumbers) {
|
||||
return new MethodVisitor(api, super.visitMethod(access, name, descriptor, signature, exceptions)) {
|
||||
@Override
|
||||
public void visitLineNumber(int line, Label start) {
|
||||
int tLine = line;
|
||||
|
||||
if (tLine <= 0) {
|
||||
if (line <= 0) {
|
||||
super.visitLineNumber(line, start);
|
||||
} else if (tLine >= lineNumbers.maxLine()) {
|
||||
} else if (line >= lineNumbers.maxLine()) {
|
||||
super.visitLineNumber(lineNumbers.maxLineDest(), start);
|
||||
} else {
|
||||
Integer matchedLine = null;
|
||||
Integer matchedLine = lineNumbers.lineMap().get(line);
|
||||
|
||||
while (tLine <= lineNumbers.maxLine() && ((matchedLine = lineNumbers.lineMap().get(tLine)) == null)) {
|
||||
tLine++;
|
||||
if (matchedLine != null) {
|
||||
super.visitLineNumber(matchedLine, start);
|
||||
}
|
||||
|
||||
super.visitLineNumber(matchedLine != null ? matchedLine : lineNumbers.maxLineDest(), start);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2024 FabricMC
|
||||
* Copyright (c) 2024-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
|
||||
@@ -45,6 +45,34 @@ import net.fabricmc.loom.util.Checksum;
|
||||
public record ClassEntry(String name, List<String> innerClasses, List<String> superClasses) {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ClassEntry.class);
|
||||
|
||||
public ClassEntry {
|
||||
if (!name.endsWith(".class")) {
|
||||
throw new IllegalArgumentException("Class name must end with '.class': " + name);
|
||||
}
|
||||
|
||||
if (!name.contains("/")) {
|
||||
throw new IllegalArgumentException("Class name must be in a package: " + name);
|
||||
}
|
||||
|
||||
String className = name.replace(".class", "");
|
||||
|
||||
for (String innerClass : innerClasses) {
|
||||
if (!innerClass.endsWith(".class")) {
|
||||
throw new IllegalArgumentException("Inner class name must end with '.class': " + name);
|
||||
}
|
||||
|
||||
if (!innerClass.startsWith(className)) {
|
||||
throw new IllegalArgumentException("Inner class (" + innerClass + ") does not have the parent class name as a prefix: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
for (String superClass : superClasses) {
|
||||
if (!superClass.endsWith(".class")) {
|
||||
throw new IllegalArgumentException("Super class name must end with '.class': " + superClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the class and its inner classes to the target root.
|
||||
* @param sourceRoot The root of the source jar
|
||||
@@ -55,13 +83,18 @@ public record ClassEntry(String name, List<String> innerClasses, List<String> su
|
||||
public void copyTo(Path sourceRoot, Path targetRoot) throws IOException {
|
||||
Path targetPath = targetRoot.resolve(name);
|
||||
Files.createDirectories(targetPath.getParent());
|
||||
Files.copy(sourceRoot.resolve(name), targetPath);
|
||||
copy(sourceRoot.resolve(name), targetPath);
|
||||
|
||||
for (String innerClass : innerClasses) {
|
||||
Files.copy(sourceRoot.resolve(innerClass), targetRoot.resolve(innerClass));
|
||||
copy(sourceRoot.resolve(innerClass), targetRoot.resolve(innerClass));
|
||||
}
|
||||
}
|
||||
|
||||
private void copy(Path source, Path target) throws IOException {
|
||||
LOGGER.debug("Copying class entry `{}` from `{}` to `{}`", name, source, target);
|
||||
Files.copy(source, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the class and its inner classes using sha256.
|
||||
* @param root The root of the jar
|
||||
@@ -95,7 +128,7 @@ public record ClassEntry(String name, List<String> innerClasses, List<String> su
|
||||
joiner.add(selfHash);
|
||||
|
||||
for (String superClass : superClasses) {
|
||||
final String superHash = hashes.get(superClass + ".class");
|
||||
final String superHash = hashes.get(superClass);
|
||||
|
||||
if (superHash != null) {
|
||||
joiner.add(superHash);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2024 FabricMC
|
||||
* Copyright (c) 2024-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
|
||||
@@ -194,11 +194,14 @@ public final class JarWalker {
|
||||
List<String> parentClasses = new ArrayList<>();
|
||||
String superName = reader.getSuperName();
|
||||
|
||||
if (superName != null) {
|
||||
parentClasses.add(superName);
|
||||
if (superName != null && !superName.equals("java/lang/Object")) {
|
||||
parentClasses.add(superName + ".class");
|
||||
}
|
||||
|
||||
for (String iface : reader.getInterfaces()) {
|
||||
parentClasses.add(iface + ".class");
|
||||
}
|
||||
|
||||
Collections.addAll(parentClasses, reader.getInterfaces());
|
||||
return Collections.unmodifiableList(parentClasses);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to read class file: " + classFile, e);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2016-2012 FabricMC
|
||||
* Copyright (c) 2016-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
|
||||
@@ -36,6 +36,7 @@ import org.gradle.api.plugins.ExtraPropertiesExtension;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.util.PatternSet;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -81,7 +82,7 @@ public interface MixinExtension extends MixinExtensionAPI {
|
||||
Stream<Configuration> getApConfigurationsStream(Function<SourceSet, String> getApConfigNameFunc);
|
||||
|
||||
@NotNull
|
||||
Stream<Map.Entry<SourceSet, Task>> getInvokerTasksStream(String compileTaskLanguage);
|
||||
<T extends Task> Stream<Map.Entry<SourceSet, TaskProvider<T>>> getInvokerTasksStream(String compileTaskLanguage, Class<T> taskType);
|
||||
|
||||
@NotNull
|
||||
@Input
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2021-2022 FabricMC
|
||||
* Copyright (c) 2021-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
|
||||
@@ -44,6 +44,7 @@ import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.util.PatternSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -57,6 +58,7 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
|
||||
this.isDefault = true;
|
||||
this.defaultRefmapName = project.getObjects().property(String.class)
|
||||
.convention(project.provider(this::getDefaultMixinRefmapName));
|
||||
this.defaultRefmapName.finalizeValueOnRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,11 +109,11 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Stream<Map.Entry<SourceSet, Task>> getInvokerTasksStream(String compileTaskLanguage) {
|
||||
public <T extends Task> Stream<Map.Entry<SourceSet, TaskProvider<T>>> getInvokerTasksStream(String compileTaskLanguage, Class<T> taskType) {
|
||||
return getMixinSourceSetsStream()
|
||||
.flatMap(sourceSet -> {
|
||||
try {
|
||||
Task task = project.getTasks().getByName(sourceSet.getCompileTaskName(compileTaskLanguage));
|
||||
TaskProvider<T> task = project.getTasks().named(sourceSet.getCompileTaskName(compileTaskLanguage), taskType);
|
||||
return Stream.of(new AbstractMap.SimpleEntry<>(sourceSet, task));
|
||||
} catch (UnknownTaskException ignored) {
|
||||
return Stream.empty();
|
||||
@@ -140,7 +142,7 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
|
||||
if (sourceSet.getName().equals("main")) {
|
||||
add(sourceSet);
|
||||
} else {
|
||||
add(sourceSet, sourceSet.getName() + "-" + getDefaultRefmapName().get());
|
||||
add(sourceSet, getDefaultRefmapName().map(defaultRefmapName -> "%s-%s".formatted(sourceSet.getName(), defaultRefmapName)), x -> { });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
145
src/main/java/net/fabricmc/loom/task/DownloadTask.java
Normal file
145
src/main/java/net/fabricmc/loom/task/DownloadTask.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.net.URISyntaxException;
|
||||
import java.time.Duration;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.workers.WorkAction;
|
||||
import org.gradle.workers.WorkParameters;
|
||||
import org.gradle.workers.WorkQueue;
|
||||
import org.gradle.workers.WorkerExecutor;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.loom.util.ExceptionUtil;
|
||||
import net.fabricmc.loom.util.download.Download;
|
||||
import net.fabricmc.loom.util.download.DownloadBuilder;
|
||||
import net.fabricmc.loom.util.download.DownloadException;
|
||||
|
||||
/**
|
||||
* A general purpose task for downloading files from a URL, using the loom {@link Download} utility.
|
||||
*/
|
||||
public abstract class DownloadTask extends DefaultTask {
|
||||
/**
|
||||
* The URL to download the file from.
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getUrl();
|
||||
|
||||
/**
|
||||
* The expected SHA-1 hash of the downloaded file.
|
||||
*/
|
||||
@Optional
|
||||
@Input
|
||||
public abstract Property<String> getSha1();
|
||||
|
||||
/**
|
||||
* The maximum age of the downloaded file in days. When not provided the downloaded file will never be considered stale.
|
||||
*/
|
||||
@Optional
|
||||
@Input
|
||||
public abstract Property<Duration> getMaxAge();
|
||||
|
||||
/**
|
||||
* The file to download to.
|
||||
*/
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getOutput();
|
||||
|
||||
// Internal stuff:
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Input
|
||||
protected abstract Property<Boolean> getIsOffline();
|
||||
|
||||
@Inject
|
||||
protected abstract WorkerExecutor getWorkerExecutor();
|
||||
|
||||
@Inject
|
||||
public DownloadTask() {
|
||||
getIsOffline().set(getProject().getGradle().getStartParameter().isOffline());
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void run() {
|
||||
final WorkQueue workQueue = getWorkerExecutor().noIsolation();
|
||||
|
||||
workQueue.submit(DownloadAction.class, params -> {
|
||||
params.getUrl().set(getUrl());
|
||||
params.getSha1().set(getSha1());
|
||||
params.getMaxAge().set(getMaxAge());
|
||||
params.getOutputFile().set(getOutput());
|
||||
params.getIsOffline().set(getIsOffline());
|
||||
});
|
||||
}
|
||||
|
||||
public interface DownloadWorkParameters extends WorkParameters {
|
||||
Property<String> getUrl();
|
||||
Property<String> getSha1();
|
||||
Property<Duration> getMaxAge();
|
||||
RegularFileProperty getOutputFile();
|
||||
Property<Boolean> getIsOffline();
|
||||
}
|
||||
|
||||
public abstract static class DownloadAction implements WorkAction<DownloadWorkParameters> {
|
||||
@Override
|
||||
public void execute() {
|
||||
DownloadBuilder builder;
|
||||
|
||||
try {
|
||||
builder = Download.create(getParameters().getUrl().get()).defaultCache();
|
||||
} catch (URISyntaxException e) {
|
||||
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Invalid URL", e);
|
||||
}
|
||||
|
||||
if (getParameters().getMaxAge().isPresent()) {
|
||||
builder.maxAge(getParameters().getMaxAge().get());
|
||||
}
|
||||
|
||||
if (getParameters().getSha1().isPresent()) {
|
||||
builder.sha1(getParameters().getSha1().get());
|
||||
}
|
||||
|
||||
if (getParameters().getIsOffline().get()) {
|
||||
builder.offline();
|
||||
}
|
||||
|
||||
try {
|
||||
builder.downloadPath(getParameters().getOutputFile().get().getAsFile().toPath());
|
||||
} catch (DownloadException e) {
|
||||
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to download file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,8 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask {
|
||||
}
|
||||
|
||||
for (VsCodeConfiguration configuration : getLaunchConfigurations().get()) {
|
||||
final JsonElement configurationJson = LoomGradlePlugin.GSON.toJsonTree(configuration);
|
||||
JsonObject configurationJson = LoomGradlePlugin.GSON.toJsonTree(configuration).getAsJsonObject();
|
||||
configurationJson.remove("runDir");
|
||||
|
||||
final List<JsonElement> toRemove = new LinkedList<>();
|
||||
|
||||
@@ -161,11 +162,14 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask {
|
||||
String projectName,
|
||||
String runDir) implements Serializable {
|
||||
public static VsCodeConfiguration fromRunConfig(Project project, RunConfig runConfig) {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
Path projectPath = project.getProjectDir().toPath();
|
||||
String relativeRunDir = rootPath.relativize(projectPath).resolve(runConfig.runDir).toString();
|
||||
return new VsCodeConfiguration(
|
||||
"java",
|
||||
runConfig.configName,
|
||||
"launch",
|
||||
"${workspaceFolder}/" + runConfig.runDir,
|
||||
"${workspaceFolder}/" + relativeRunDir,
|
||||
"integratedTerminal",
|
||||
false,
|
||||
runConfig.mainClass,
|
||||
@@ -173,7 +177,7 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask {
|
||||
RunConfig.joinArguments(runConfig.programArgs),
|
||||
new HashMap<>(runConfig.environmentVariables),
|
||||
runConfig.projectName,
|
||||
project.getProjectDir().toPath().resolve(runConfig.runDir).toAbsolutePath().toString()
|
||||
rootPath.resolve(relativeRunDir).toAbsolutePath().toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,11 +66,11 @@ import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.api.tasks.UntrackedTask;
|
||||
import org.gradle.api.tasks.options.Option;
|
||||
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
|
||||
import org.gradle.process.ExecOperations;
|
||||
import org.gradle.process.ExecResult;
|
||||
import org.gradle.work.DisableCachingByDefault;
|
||||
import org.gradle.workers.WorkAction;
|
||||
import org.gradle.workers.WorkParameters;
|
||||
import org.gradle.workers.WorkQueue;
|
||||
@@ -108,7 +108,7 @@ import net.fabricmc.loom.util.ipc.IPCServer;
|
||||
import net.fabricmc.loom.util.service.ScopedServiceFactory;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
@DisableCachingByDefault
|
||||
@UntrackedTask(because = "Manually invoked, has internal caching")
|
||||
public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
||||
private static final String CACHE_VERSION = "v1";
|
||||
private final DecompilerOptions decompilerOptions;
|
||||
@@ -241,7 +241,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
||||
throw new IllegalStateException("Input minecraft jar not found: " + getInputJarName().get());
|
||||
}));
|
||||
|
||||
getOutputs().upToDateWhen((o) -> false);
|
||||
getClasspath().from(decompilerOptions.getClasspath()).finalizeValueOnRead();
|
||||
dependsOn(decompilerOptions.getClasspath().getBuiltBy());
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ public abstract class LoomTasks implements Runnable {
|
||||
public void run() {
|
||||
getTasks().register("migrateMappings", MigrateMappingsTask.class, t -> {
|
||||
t.setDescription("Migrates mappings to a new version.");
|
||||
t.getOutputs().upToDateWhen(o -> false);
|
||||
});
|
||||
|
||||
var generateLog4jConfig = getTasks().register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> {
|
||||
@@ -126,7 +125,7 @@ public abstract class LoomTasks implements Runnable {
|
||||
});
|
||||
}
|
||||
|
||||
private static String getRunConfigTaskName(RunConfigSettings config) {
|
||||
public static String getRunConfigTaskName(RunConfigSettings config) {
|
||||
String configName = config.getName();
|
||||
return "run" + configName.substring(0, 1).toUpperCase() + configName.substring(1);
|
||||
}
|
||||
|
||||
@@ -31,13 +31,13 @@ import org.gradle.api.tasks.InputDirectory;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.api.tasks.UntrackedTask;
|
||||
import org.gradle.api.tasks.options.Option;
|
||||
import org.gradle.work.DisableCachingByDefault;
|
||||
|
||||
import net.fabricmc.loom.task.service.MigrateMappingsService;
|
||||
import net.fabricmc.loom.util.service.ScopedServiceFactory;
|
||||
|
||||
@DisableCachingByDefault(because = "Always rerun this task.")
|
||||
@UntrackedTask(because = "Always rerun this task.")
|
||||
public abstract class MigrateMappingsTask extends AbstractLoomTask {
|
||||
@Input
|
||||
@Option(option = "mappings", description = "Target mappings")
|
||||
|
||||
@@ -159,7 +159,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
||||
getInjectAccessWidener().convention(false);
|
||||
|
||||
TaskProvider<NestableJarGenerationTask> processIncludeJars = getProject().getTasks().named(Constants.Task.PROCESS_INCLUDE_JARS, NestableJarGenerationTask.class);
|
||||
getNestedJars().from(getProject().fileTree(processIncludeJars.get().getOutputDirectory()));
|
||||
getNestedJars().from(processIncludeJars.map(task -> getProject().fileTree(task.getOutputDirectory())));
|
||||
getNestedJars().builtBy(processIncludeJars);
|
||||
|
||||
getUseMixinAP().set(LoomGradleExtension.get(getProject()).getMixin().getUseLegacyMixinAp());
|
||||
|
||||
@@ -84,7 +84,7 @@ public abstract class RemapTaskConfiguration implements Runnable {
|
||||
});
|
||||
|
||||
Action<RemapJarTask> remapJarTaskAction = task -> {
|
||||
final AbstractArchiveTask jarTask = getTasks().named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class).get();
|
||||
final TaskProvider<AbstractArchiveTask> jarTask = getTasks().named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class);
|
||||
|
||||
// Basic task setup
|
||||
task.dependsOn(jarTask);
|
||||
@@ -94,7 +94,7 @@ public abstract class RemapTaskConfiguration implements Runnable {
|
||||
getArtifacts().add(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME, task);
|
||||
|
||||
// Setup the input file and the nested deps
|
||||
task.getInputFile().convention(jarTask.getArchiveFile());
|
||||
task.getInputFile().convention(jarTask.flatMap(AbstractArchiveTask::getArchiveFile));
|
||||
task.dependsOn(getTasks().named(JavaPlugin.JAR_TASK_NAME));
|
||||
task.getIncludesClientOnlyClasses().set(getProject().provider(extension::areEnvironmentSourceSetsSplit));
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ import net.fabricmc.accesswidener.AccessWidenerReader;
|
||||
import net.fabricmc.accesswidener.AccessWidenerVisitor;
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||
import net.fabricmc.loom.util.TinyRemapperLoggerAdapter;
|
||||
import net.fabricmc.tinyremapper.TinyRemapper;
|
||||
import net.fabricmc.tinyremapper.api.TrEnvironment;
|
||||
|
||||
@@ -70,7 +71,7 @@ public abstract class ValidateAccessWidenerTask extends DefaultTask {
|
||||
|
||||
@TaskAction
|
||||
public void run() {
|
||||
final TinyRemapper tinyRemapper = TinyRemapper.newRemapper().build();
|
||||
final TinyRemapper tinyRemapper = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE).build();
|
||||
|
||||
for (File file : getTargetJars().getFiles()) {
|
||||
tinyRemapper.readClassPath(file.toPath());
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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.prod;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.api.tasks.UntrackedTask;
|
||||
import org.gradle.jvm.toolchain.JavaLauncher;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainSpec;
|
||||
import org.gradle.process.ExecOperations;
|
||||
import org.gradle.process.ExecResult;
|
||||
import org.gradle.process.ExecSpec;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.configuration.InstallerData;
|
||||
import net.fabricmc.loom.task.AbstractLoomTask;
|
||||
import net.fabricmc.loom.task.RemapTaskConfiguration;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.gradle.GradleUtils;
|
||||
|
||||
/**
|
||||
* This is the base task for running the game in a "production" like environment. Using intermediary names, and not enabling development only features.
|
||||
*
|
||||
* <p>Do not use this task directly, use {@link ClientProductionRunTask} or {@link ServerProductionRunTask} instead.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@UntrackedTask(because = "Always rerun this task.")
|
||||
public abstract sealed class AbstractProductionRunTask extends AbstractLoomTask permits ClientProductionRunTask, ServerProductionRunTask {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractProductionRunTask.class);
|
||||
|
||||
/**
|
||||
* A collection of mods that will be used when running the game. The mods must be remapped to run with intermediary names.
|
||||
*
|
||||
* <p>By default this includes the remapped jar.
|
||||
*/
|
||||
@Classpath
|
||||
public abstract ConfigurableFileCollection getMods();
|
||||
|
||||
/**
|
||||
* A list of additional JVM arguments to pass to the game.
|
||||
*/
|
||||
@Input
|
||||
public abstract ListProperty<String> getJvmArgs();
|
||||
|
||||
/**
|
||||
* A list of additional program arguments to pass to the game.
|
||||
*/
|
||||
@Input
|
||||
public abstract ListProperty<String> getProgramArgs();
|
||||
|
||||
/**
|
||||
* The directory to run the game in.
|
||||
*/
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getRunDir();
|
||||
|
||||
/**
|
||||
* The {@link JavaLauncher} to use when running the game, this can be used to specify a specific Java version to use.
|
||||
*
|
||||
* <p>See: <a href="https://docs.gradle.org/current/userguide/toolchains.html#sec:plugins_toolchains">Java Toolchains</a>
|
||||
* @return
|
||||
*/
|
||||
@Nested
|
||||
public abstract Property<JavaLauncher> getJavaLauncher();
|
||||
|
||||
// Internal options
|
||||
@ApiStatus.Internal
|
||||
@Classpath
|
||||
protected abstract ConfigurableFileCollection getClasspath();
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Input
|
||||
protected abstract Property<String> getMainClass();
|
||||
|
||||
@Inject
|
||||
protected abstract ExecOperations getExecOperations();
|
||||
|
||||
@Inject
|
||||
protected abstract JavaToolchainService getJavaToolchainService();
|
||||
|
||||
@Inject
|
||||
public AbstractProductionRunTask() {
|
||||
JavaToolchainSpec defaultToolchain = getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain();
|
||||
getJavaLauncher().convention(getJavaToolchainService().launcherFor(defaultToolchain));
|
||||
getRunDir().convention(getProject().getLayout().getProjectDirectory().dir("run"));
|
||||
|
||||
if (!GradleUtils.getBooleanProperty(getProject(), Constants.Properties.DONT_REMAP)) {
|
||||
getMods().from(getProject().getTasks().named(RemapTaskConfiguration.REMAP_JAR_TASK_NAME));
|
||||
}
|
||||
|
||||
getMods().from(getProject().getConfigurations().named(Constants.Configurations.PRODUCTION_RUNTIME_MODS));
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void run() throws IOException {
|
||||
Files.createDirectories(getRunDir().get().getAsFile().toPath());
|
||||
|
||||
ExecResult result = getExecOperations().exec(exec -> {
|
||||
configureCommand(exec);
|
||||
configureJvmArgs(exec);
|
||||
configureClasspath(exec);
|
||||
configureMainClass(exec);
|
||||
configureProgramArgs(exec);
|
||||
|
||||
exec.setWorkingDir(getRunDir());
|
||||
|
||||
LOGGER.debug("Running command: {}", exec.getCommandLine());
|
||||
});
|
||||
result.assertNormalExitValue();
|
||||
}
|
||||
|
||||
protected void configureCommand(ExecSpec exec) {
|
||||
exec.commandLine(getJavaLauncher().get().getExecutablePath());
|
||||
}
|
||||
|
||||
protected void configureJvmArgs(ExecSpec exec) {
|
||||
exec.args(getJvmArgs().get());
|
||||
exec.args("-Dfabric.addMods=" + joinFiles(getMods().getFiles().stream()));
|
||||
}
|
||||
|
||||
protected Stream<File> streamClasspath() {
|
||||
return getClasspath().getFiles().stream();
|
||||
}
|
||||
|
||||
protected void configureClasspath(ExecSpec exec) {
|
||||
exec.args("-cp");
|
||||
exec.args(joinFiles(streamClasspath()));
|
||||
}
|
||||
|
||||
protected void configureMainClass(ExecSpec exec) {
|
||||
exec.args(getMainClass().get());
|
||||
}
|
||||
|
||||
protected void configureProgramArgs(ExecSpec exec) {
|
||||
exec.args(getProgramArgs().get());
|
||||
}
|
||||
|
||||
@Internal
|
||||
protected Provider<String> getProjectLoaderVersion() {
|
||||
return getProject().provider(() -> {
|
||||
InstallerData installerData = getExtension().getInstallerData();
|
||||
|
||||
if (installerData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return installerData.version();
|
||||
});
|
||||
}
|
||||
|
||||
protected Provider<Configuration> detachedConfigurationProvider(String mavenNotation, Provider<String> versionProvider) {
|
||||
return versionProvider.map(version -> {
|
||||
Dependency serverLauncher = getProject().getDependencies().create(mavenNotation.formatted(version));
|
||||
return getProject().getConfigurations().detachedConfiguration(serverLauncher);
|
||||
});
|
||||
}
|
||||
|
||||
private static String joinFiles(Stream<File> stream) {
|
||||
return stream.map(File::getAbsolutePath)
|
||||
.collect(Collectors.joining(File.pathSeparator));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.prod;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.process.ExecSpec;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.Platform;
|
||||
|
||||
/**
|
||||
* A task that runs the Minecraft client in a similar way to a production launcher. You must manually register a task of this type to use it.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public abstract non-sealed class ClientProductionRunTask extends AbstractProductionRunTask {
|
||||
/**
|
||||
* Whether to use XVFB to run the game, using a virtual framebuffer. This is useful for CI environments that don't have a display server.
|
||||
*
|
||||
* <p>Defaults to true only on Linux and when the "CI" environment variable is set.
|
||||
*
|
||||
* <p>XVFB must be installed, on Debian-based systems you can install it with: <code>apt install -y xvfb</code>
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<Boolean> getUseXVFB();
|
||||
|
||||
@Nested
|
||||
@Optional
|
||||
public abstract Property<TracyCapture> getTracyCapture();
|
||||
|
||||
/**
|
||||
* Configures the tracy profiler to run alongside the game. See @{@link TracyCapture} for more information.
|
||||
*
|
||||
* @param action The configuration action.
|
||||
*/
|
||||
public void tracy(Action<? super TracyCapture> action) {
|
||||
getTracyCapture().set(getProject().getObjects().newInstance(TracyCapture.class));
|
||||
getTracyCapture().finalizeValue();
|
||||
action.execute(getTracyCapture().get());
|
||||
}
|
||||
|
||||
// Internal options
|
||||
@Input
|
||||
protected abstract Property<String> getAssetsIndex();
|
||||
|
||||
@InputFiles
|
||||
protected abstract DirectoryProperty getAssetsDir();
|
||||
|
||||
@Inject
|
||||
public ClientProductionRunTask() {
|
||||
getUseXVFB().convention(getProject().getProviders().environmentVariable("CI")
|
||||
.map(value -> Platform.CURRENT.getOperatingSystem().isLinux())
|
||||
.orElse(false)
|
||||
);
|
||||
|
||||
getAssetsIndex().set(getExtension().getMinecraftVersion()
|
||||
.map(minecraftVersion -> getExtension()
|
||||
.getMinecraftProvider()
|
||||
.getVersionInfo()
|
||||
.assetIndex()
|
||||
.fabricId(minecraftVersion)
|
||||
)
|
||||
);
|
||||
getAssetsDir().set(new File(getExtension().getFiles().getUserCache(), "assets"));
|
||||
getMainClass().convention("net.fabricmc.loader.impl.launch.knot.KnotClient");
|
||||
|
||||
getClasspath().from(getExtension().getMinecraftProvider().getMinecraftClientJar());
|
||||
getClasspath().from(detachedConfigurationProvider("net.fabricmc:fabric-loader:%s", getProjectLoaderVersion()));
|
||||
getClasspath().from(detachedConfigurationProvider("net.fabricmc:intermediary:%s", getExtension().getMinecraftVersion()));
|
||||
getClasspath().from(getProject().getConfigurations().named(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES));
|
||||
|
||||
dependsOn("downloadAssets");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
if (getTracyCapture().isPresent()) {
|
||||
getTracyCapture().get().runWithTracy(super::run);
|
||||
return;
|
||||
}
|
||||
|
||||
super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureCommand(ExecSpec exec) {
|
||||
if (getUseXVFB().get()) {
|
||||
if (!Platform.CURRENT.getOperatingSystem().isLinux()) {
|
||||
throw new UnsupportedOperationException("XVFB is only supported on Linux");
|
||||
}
|
||||
|
||||
exec.commandLine("/usr/bin/xvfb-run");
|
||||
exec.args("-a", getJavaLauncher().get().getExecutablePath());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
super.configureCommand(exec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureJvmArgs(ExecSpec exec) {
|
||||
super.configureJvmArgs(exec);
|
||||
|
||||
if (Platform.CURRENT.getOperatingSystem().isMacOS()) {
|
||||
exec.args("-XstartOnFirstThread");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureProgramArgs(ExecSpec exec) {
|
||||
super.configureProgramArgs(exec);
|
||||
|
||||
exec.args(
|
||||
"--assetIndex", getAssetsIndex().get(),
|
||||
"--assetsDir", getAssetsDir().get().getAsFile().getAbsolutePath(),
|
||||
"--gameDir", getRunDir().get().getAsFile().getAbsolutePath()
|
||||
);
|
||||
|
||||
if (getTracyCapture().isPresent()) {
|
||||
exec.args("--tracy");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.prod;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.fabricmc.loom.util.LoomVersions;
|
||||
import net.fabricmc.loom.util.ZipUtils;
|
||||
|
||||
/**
|
||||
* A task that runs the server using the production server launcher. You must manually register a task of this type to use it.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public abstract non-sealed class ServerProductionRunTask extends AbstractProductionRunTask {
|
||||
/**
|
||||
* The version of Fabric Loader to use.
|
||||
*
|
||||
* <p>Defaults to the version of Fabric Loader that the project is using.
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getLoaderVersion();
|
||||
|
||||
/**
|
||||
* The version of Minecraft to use.
|
||||
*
|
||||
* <p>Defaults to the version of Minecraft that the project is using.
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getMinecraftVersion();
|
||||
|
||||
/**
|
||||
* The version of the Fabric Installer to use.
|
||||
*
|
||||
* <p>Defaults to a version provided by Loom.
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getInstallerVersion();
|
||||
|
||||
// Internal options
|
||||
|
||||
@ApiStatus.Internal
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getInstallPropertiesJar();
|
||||
|
||||
@Inject
|
||||
public ServerProductionRunTask() {
|
||||
getLoaderVersion().convention(getProjectLoaderVersion());
|
||||
getMinecraftVersion().convention(getExtension().getMinecraftVersion());
|
||||
getInstallPropertiesJar().convention(getProject().getLayout().getBuildDirectory().file("server_properties.jar"));
|
||||
getInstallerVersion().convention(LoomVersions.FABRIC_INSTALLER.version());
|
||||
|
||||
getMainClass().convention("net.fabricmc.installer.ServerLauncher");
|
||||
getClasspath().from(detachedConfigurationProvider("net.fabricmc:fabric-installer:%s:server", getInstallerVersion()));
|
||||
|
||||
getProgramArgs().add("nogui");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
ZipUtils.add(
|
||||
getInstallPropertiesJar().get().getAsFile().toPath(),
|
||||
"install.properties",
|
||||
"fabric-loader-version=%s\ngame-version=%s".formatted(getLoaderVersion().get(), getMinecraftVersion().get())
|
||||
);
|
||||
|
||||
super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Stream<File> streamClasspath() {
|
||||
return Stream.concat(
|
||||
super.streamClasspath(),
|
||||
Stream.of(getInstallPropertiesJar().get().getAsFile())
|
||||
);
|
||||
}
|
||||
}
|
||||
159
src/main/java/net/fabricmc/loom/task/prod/TracyCapture.java
Normal file
159
src/main/java/net/fabricmc/loom/task/prod/TracyCapture.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.prod;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.util.ExceptionUtil;
|
||||
|
||||
public abstract class TracyCapture {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TracyCapture.class);
|
||||
|
||||
/**
|
||||
* The path to the tracy-capture executable.
|
||||
*/
|
||||
@InputFile
|
||||
@Optional
|
||||
public abstract RegularFileProperty getTracyCapture();
|
||||
|
||||
/**
|
||||
* The maximum number of seconds to wait for tracy-capture to stop on its own before killing it.
|
||||
*
|
||||
* <p>Defaults to 10 seconds.
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<Integer> getMaxShutdownWaitSeconds();
|
||||
|
||||
/**
|
||||
* The path to the output file.
|
||||
*/
|
||||
@OutputFile
|
||||
@Optional
|
||||
public abstract RegularFileProperty getOutput();
|
||||
|
||||
@Inject
|
||||
public TracyCapture() {
|
||||
getMaxShutdownWaitSeconds().convention(10);
|
||||
}
|
||||
|
||||
void runWithTracy(IORunnable runnable) throws IOException {
|
||||
TracyCaptureRunner tracyCaptureRunner = createRunner();
|
||||
|
||||
boolean success = false;
|
||||
|
||||
try {
|
||||
runnable.run();
|
||||
success = true;
|
||||
} finally {
|
||||
try {
|
||||
tracyCaptureRunner.close();
|
||||
} catch (Exception e) {
|
||||
if (success) {
|
||||
//noinspection ThrowFromFinallyBlock
|
||||
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to stop tracy capture", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TracyCaptureRunner createRunner() throws IOException {
|
||||
File tracyCapture = getTracyCapture().getAsFile().get();
|
||||
File output = getOutput().getAsFile().get();
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder()
|
||||
.command(tracyCapture.getAbsolutePath(), "-a", "127.0.0.1", "-f", "-o", output.getAbsolutePath());
|
||||
Process process = builder.start();
|
||||
|
||||
captureLog(process.getInputStream(), LOGGER::info);
|
||||
captureLog(process.getErrorStream(), LOGGER::error);
|
||||
|
||||
LOGGER.info("Tracy capture started");
|
||||
|
||||
return new TracyCaptureRunner(process, getMaxShutdownWaitSeconds().get());
|
||||
}
|
||||
|
||||
private record TracyCaptureRunner(Process process, int shutdownWait) implements AutoCloseable {
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
// Wait x seconds for tracy to stop on its own
|
||||
// This allows time for tracy to save the profile to disk
|
||||
for (int i = 0; i < shutdownWait; i++) {
|
||||
if (!process.isAlive()) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// If it's still running, kill it
|
||||
if (process.isAlive()) {
|
||||
LOGGER.error("Tracy capture did not stop on its own, killing it");
|
||||
process.destroy();
|
||||
process.waitFor();
|
||||
}
|
||||
|
||||
int exitCode = process.exitValue();
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw new RuntimeException("Tracy capture failed with exit code " + exitCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void captureLog(InputStream inputStream, Consumer<String> lineConsumer) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(lineConsumer);
|
||||
} catch (Exception e) {
|
||||
// Don't really care, this will happen when the stream is closed
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface IORunnable {
|
||||
void run() throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -28,15 +28,19 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.cadixdev.mercury.Mercury;
|
||||
import org.cadixdev.mercury.remapper.MercuryRemapper;
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -69,7 +73,7 @@ public final class SourceRemapperService extends Service<SourceRemapperService.O
|
||||
task.getSourceNamespace(),
|
||||
task.getTargetNamespace()
|
||||
));
|
||||
o.getJavaCompileRelease().set(SourceRemapper.getJavaCompileRelease(task.getProject()));
|
||||
o.getJavaCompileRelease().set(getJavaCompileRelease(task.getProject()));
|
||||
o.getClasspath().from(task.getClasspath());
|
||||
});
|
||||
}
|
||||
@@ -135,4 +139,28 @@ public final class SourceRemapperService extends Service<SourceRemapperService.O
|
||||
|
||||
return mercury;
|
||||
}
|
||||
|
||||
public static int getJavaCompileRelease(Project project) {
|
||||
AtomicInteger release = new AtomicInteger(-1);
|
||||
|
||||
project.getTasks().withType(JavaCompile.class, javaCompile -> {
|
||||
Property<Integer> releaseProperty = javaCompile.getOptions().getRelease();
|
||||
|
||||
if (!releaseProperty.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int compileRelease = releaseProperty.get();
|
||||
release.set(Math.max(release.get(), compileRelease));
|
||||
});
|
||||
|
||||
final int i = release.get();
|
||||
|
||||
if (i < 0) {
|
||||
// Unable to find the release used to compile with, default to the current version
|
||||
return Integer.parseInt(JavaVersion.current().getMajorVersion());
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ import net.fabricmc.loom.build.IntermediaryNamespaces;
|
||||
import net.fabricmc.loom.extension.RemapperExtensionHolder;
|
||||
import net.fabricmc.loom.task.AbstractRemapJarTask;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.TinyRemapperLoggerAdapter;
|
||||
import net.fabricmc.loom.util.kotlin.KotlinClasspathService;
|
||||
import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader;
|
||||
import net.fabricmc.loom.util.service.Service;
|
||||
@@ -131,7 +132,7 @@ public class TinyRemapperService extends Service<TinyRemapperService.Options> im
|
||||
}
|
||||
|
||||
private TinyRemapper createTinyRemapper() {
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE)
|
||||
.withKnownIndyBsm(Set.copyOf(getOptions().getKnownIndyBsms().get()));
|
||||
|
||||
for (MappingsService.Options options : getOptions().getMappings().get()) {
|
||||
|
||||
@@ -109,6 +109,14 @@ public class Constants {
|
||||
*/
|
||||
public static final String LOCAL_RUNTIME = "localRuntime";
|
||||
public static final String NAMED_ELEMENTS = "namedElements";
|
||||
/**
|
||||
* The configuration that contains the Minecraft client and loader runtime libraries, as used by the production run tasks.
|
||||
*/
|
||||
public static final String MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES = "minecraftTestClientRuntimeLibraries";
|
||||
/**
|
||||
* Mods to be used by {@link net.fabricmc.loom.task.prod.AbstractProductionRunTask} tasks by default.
|
||||
*/
|
||||
public static final String PRODUCTION_RUNTIME_MODS = "productionRuntimeMods";
|
||||
|
||||
private Configurations() {
|
||||
}
|
||||
|
||||
@@ -32,17 +32,13 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.cadixdev.lorenz.MappingSet;
|
||||
import org.cadixdev.mercury.Mercury;
|
||||
import org.cadixdev.mercury.remapper.MercuryRemapper;
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.internal.project.ProjectInternal;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.internal.logging.progress.ProgressLogger;
|
||||
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
@@ -182,7 +178,8 @@ public class SourceRemapper {
|
||||
MappingSet mappings = lorenzMappingService.getMappings();
|
||||
|
||||
Mercury mercury = createMercuryWithClassPath(project, MappingsNamespace.of(to) == MappingsNamespace.NAMED);
|
||||
mercury.setSourceCompatibilityFromRelease(getJavaCompileRelease(project));
|
||||
// Always use the latest version
|
||||
mercury.setSourceCompatibilityFromRelease(Integer.MAX_VALUE);
|
||||
|
||||
for (File file : extension.getUnmappedModCollection()) {
|
||||
Path path = file.toPath();
|
||||
@@ -220,30 +217,6 @@ public class SourceRemapper {
|
||||
return this.mercury;
|
||||
}
|
||||
|
||||
public static int getJavaCompileRelease(Project project) {
|
||||
AtomicInteger release = new AtomicInteger(-1);
|
||||
|
||||
project.getTasks().withType(JavaCompile.class, javaCompile -> {
|
||||
Property<Integer> releaseProperty = javaCompile.getOptions().getRelease();
|
||||
|
||||
if (!releaseProperty.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int compileRelease = releaseProperty.get();
|
||||
release.set(Math.max(release.get(), compileRelease));
|
||||
});
|
||||
|
||||
final int i = release.get();
|
||||
|
||||
if (i < 0) {
|
||||
// Unable to find the release used to compile with, default to the current version
|
||||
return Integer.parseInt(JavaVersion.current().getMajorVersion());
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
public static void copyNonJavaFiles(Path from, Path to, Logger logger, Path source) throws IOException {
|
||||
Files.walk(from).forEach(path -> {
|
||||
Path targetPath = to.resolve(from.relativize(path).toString());
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class TinyRemapperHelper {
|
||||
|
||||
int intermediaryNsId = mappingTree.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString());
|
||||
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE)
|
||||
.ignoreConflicts(extension.isForgeLike())
|
||||
.threads(Runtime.getRuntime().availableProcessors())
|
||||
.withMappings(create(mappingTree, fromM, toM, true))
|
||||
|
||||
@@ -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.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.tinyremapper.api.TrLogger;
|
||||
|
||||
public final class TinyRemapperLoggerAdapter implements TrLogger {
|
||||
public static final TinyRemapperLoggerAdapter INSTANCE = new TinyRemapperLoggerAdapter();
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("TinyRemapper");
|
||||
|
||||
private TinyRemapperLoggerAdapter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(Level level, String message) {
|
||||
switch (level) {
|
||||
case ERROR:
|
||||
LOGGER.error(message);
|
||||
break;
|
||||
case WARN:
|
||||
LOGGER.warn(message);
|
||||
break;
|
||||
case INFO:
|
||||
LOGGER.info(message);
|
||||
break;
|
||||
case DEBUG:
|
||||
LOGGER.debug(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2023 FabricMC
|
||||
* Copyright (c) 2023-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
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
package net.fabricmc.loom.test.integration
|
||||
|
||||
import spock.lang.IgnoreIf
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
@@ -31,15 +32,16 @@ import net.fabricmc.loom.test.util.GradleProjectTestTrait
|
||||
|
||||
import static net.fabricmc.loom.test.LoomTestConstants.PRE_RELEASE_GRADLE
|
||||
import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
|
||||
import static org.gradle.testkit.runner.TaskOutcome.FAILED
|
||||
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
|
||||
|
||||
class DataGenerationTest extends Specification implements GradleProjectTestTrait {
|
||||
private static String DEPENDENCIES = """
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.20.2"
|
||||
mappings "net.fabricmc:yarn:1.20.2+build.4:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:0.14.23"
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:0.90.0+1.20.2"
|
||||
minecraft "com.mojang:minecraft:1.21.4"
|
||||
mappings "net.fabricmc:yarn:1.21.4+build.4:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:0.16.9"
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -202,4 +204,52 @@ class DataGenerationTest extends Specification implements GradleProjectTestTrait
|
||||
then:
|
||||
result.task(":runDatagen").outcome == SUCCESS
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "game tests (gradle #version)"() {
|
||||
setup:
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
gradle.buildGradle << '''
|
||||
fabricApi {
|
||||
configureTests()
|
||||
}
|
||||
''' + DEPENDENCIES
|
||||
when:
|
||||
def result = gradle.run(task: "runGameTest", expectFailure: true)
|
||||
|
||||
then:
|
||||
// We expect this to fail because there is nothing to test
|
||||
// At least we know that Fabric API is attempting to run the tests
|
||||
result.task(":runGameTest").outcome == FAILED
|
||||
result.output.contains("No test functions were given!")
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
|
||||
@Unroll
|
||||
@IgnoreIf({ System.getenv("CI") != null }) // This test is disabled on CI because it launches a real client and cannot run headless.
|
||||
def "client game tests (gradle #version)"() {
|
||||
setup:
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
gradle.buildGradle << '''
|
||||
fabricApi {
|
||||
configureTests {
|
||||
createSourceSet = true
|
||||
modId = "example-test"
|
||||
eula = true
|
||||
}
|
||||
}
|
||||
''' + DEPENDENCIES
|
||||
when:
|
||||
def result = gradle.run(task: "runClientGameTest")
|
||||
def eula = new File(gradle.projectDir, "build/run/clientGameTest/eula.txt")
|
||||
|
||||
then:
|
||||
result.task(":runClientGameTest").outcome == SUCCESS
|
||||
eula.text.contains("eula=true")
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.test.integration
|
||||
|
||||
import spock.lang.Unroll
|
||||
|
||||
import net.fabricmc.loom.test.unit.download.DownloadTest
|
||||
import net.fabricmc.loom.test.util.GradleProjectTestTrait
|
||||
|
||||
import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
|
||||
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
|
||||
|
||||
class DownloadTaskTest extends DownloadTest implements GradleProjectTestTrait {
|
||||
@Unroll
|
||||
def "download (gradle #version)"() {
|
||||
setup:
|
||||
server.get("/simpleFile") {
|
||||
it.result("Hello World")
|
||||
}
|
||||
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
gradle.buildGradle << """
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.21.4"
|
||||
mappings "net.fabricmc:yarn:1.21.4+build.8:v2"
|
||||
}
|
||||
|
||||
tasks.register("download", net.fabricmc.loom.task.DownloadTask) {
|
||||
url = "${PATH}/simpleFile"
|
||||
output = file("out.txt")
|
||||
}
|
||||
"""
|
||||
when:
|
||||
def result = gradle.run(task: "download")
|
||||
def output = new File(gradle.projectDir, "out.txt")
|
||||
|
||||
then:
|
||||
result.task(":download").outcome == SUCCESS
|
||||
output.text == "Hello World"
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "download sha1 (gradle #version)"() {
|
||||
setup:
|
||||
server.get("/simpleFile") {
|
||||
it.result("Hello World")
|
||||
}
|
||||
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
gradle.buildGradle << """
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.21.4"
|
||||
mappings "net.fabricmc:yarn:1.21.4+build.8:v2"
|
||||
}
|
||||
|
||||
tasks.register("download", net.fabricmc.loom.task.DownloadTask) {
|
||||
url = "${PATH}/simpleFile"
|
||||
sha1 = "0a4d55a8d778e5022fab701977c5d840bbc486d0"
|
||||
output = file("out.txt")
|
||||
}
|
||||
"""
|
||||
when:
|
||||
def result = gradle.run(task: "download")
|
||||
def output = new File(gradle.projectDir, "out.txt")
|
||||
|
||||
then:
|
||||
result.task(":download").outcome == SUCCESS
|
||||
output.text == "Hello World"
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "download max age (gradle #version)"() {
|
||||
setup:
|
||||
server.get("/simpleFile") {
|
||||
it.result("Hello World")
|
||||
}
|
||||
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
gradle.buildGradle << """
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.21.4"
|
||||
mappings "net.fabricmc:yarn:1.21.4+build.8:v2"
|
||||
}
|
||||
|
||||
tasks.register("download", net.fabricmc.loom.task.DownloadTask) {
|
||||
url = "${PATH}/simpleFile"
|
||||
maxAge = Duration.ofDays(1)
|
||||
output = file("out.txt")
|
||||
}
|
||||
"""
|
||||
when:
|
||||
def result = gradle.run(task: "download")
|
||||
def output = new File(gradle.projectDir, "out.txt")
|
||||
|
||||
then:
|
||||
result.task(":download").outcome == SUCCESS
|
||||
output.text == "Hello World"
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
|
||||
setup:
|
||||
def gradle = gradleProject(
|
||||
repo: "https://github.com/FabricMC/fabric.git",
|
||||
commit: "70277babddfaf52ee30013af94764da19473b3b1",
|
||||
commit: "d70d2c06bb8fafdb72c6778b29fb050618015ab3",
|
||||
version: version,
|
||||
patch: "fabric_api"
|
||||
)
|
||||
@@ -63,7 +63,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
|
||||
.replace('id "fabric-loom" version "1.6.11"', 'id "dev.architectury.loom"')
|
||||
.replace('"fabric-loom"', '"dev.architectury.loom"') + mixinApPatch
|
||||
|
||||
def minecraftVersion = "1.21.4-pre3"
|
||||
def minecraftVersion = "1.21.4"
|
||||
def server = ServerRunner.create(gradle.projectDir, minecraftVersion)
|
||||
.withMod(gradle.getOutputFile("fabric-api-999.0.0.jar"))
|
||||
|
||||
|
||||
@@ -120,4 +120,30 @@ class MojangMappingsProjectTest extends Specification implements GradleProjectTe
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "mojang mappings via lazy provider (gradle #version)"() {
|
||||
setup:
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
|
||||
gradle.buildGradle << '''
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.18-pre5"
|
||||
mappings project.provider {
|
||||
loom.layered() {
|
||||
officialMojangMappings()
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
when:
|
||||
def result = gradle.run(task: "build")
|
||||
|
||||
then:
|
||||
result.task(":build").outcome == SUCCESS
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2018-2023 FabricMC
|
||||
* Copyright (c) 2018-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
|
||||
@@ -24,24 +24,32 @@
|
||||
|
||||
package net.fabricmc.loom.test.integration
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import spock.lang.IgnoreIf
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Timeout
|
||||
import spock.lang.Unroll
|
||||
import spock.util.environment.RestoreSystemProperties
|
||||
|
||||
import net.fabricmc.loom.test.LoomTestConstants
|
||||
import net.fabricmc.loom.test.util.GradleProjectTestTrait
|
||||
import net.fabricmc.loom.util.download.Download
|
||||
|
||||
import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
|
||||
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
|
||||
|
||||
// This test runs a mod that exits on mod init
|
||||
class RunConfigTest extends Specification implements GradleProjectTestTrait {
|
||||
private static List<String> tasks = [
|
||||
private static final List<String> tasks = [
|
||||
"runClient",
|
||||
"runServer",
|
||||
"runTestmodClient",
|
||||
"runTestmodServer",
|
||||
"runAutoTestServer"
|
||||
]
|
||||
private static final String TRACY_CAPTURE_LINUX = "https://github.com/modmuss50/tracy-utils/releases/download/0.0.2/linux-x86_64-tracy-capture"
|
||||
|
||||
@Unroll
|
||||
def "Run config #task (gradle #version)"() {
|
||||
setup:
|
||||
@@ -130,4 +138,74 @@ class RunConfigTest extends Specification implements GradleProjectTestTrait {
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "prod server (gradle #version)"() {
|
||||
setup:
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
gradle.buildGradle << '''
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.21.4"
|
||||
mappings "net.fabricmc:yarn:1.21.4+build.4:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:0.16.9"
|
||||
}
|
||||
|
||||
tasks.register("prodServer", net.fabricmc.loom.task.prod.ServerProductionRunTask) {
|
||||
installerVersion = "1.0.1"
|
||||
}
|
||||
'''
|
||||
when:
|
||||
def result = gradle.run(task: "prodServer")
|
||||
|
||||
then:
|
||||
result.task(":prodServer").outcome == SUCCESS
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
|
||||
@Timeout(value = 10, unit = TimeUnit.MINUTES)
|
||||
@Unroll
|
||||
@IgnoreIf({ !os.linux }) // XVFB is installed on the CI for this test
|
||||
def "prod client (gradle #version)"() {
|
||||
setup:
|
||||
def tracyCapture = new File(LoomTestConstants.TEST_DIR, "tracy-capture")
|
||||
Download.create(TRACY_CAPTURE_LINUX).defaultCache().downloadPath(tracyCapture.toPath())
|
||||
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
gradle.buildGradle << '''
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.21.4"
|
||||
mappings "net.fabricmc:yarn:1.21.4+build.4:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:0.16.9"
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4"
|
||||
|
||||
productionRuntimeMods "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4"
|
||||
}
|
||||
|
||||
tasks.register("prodClient", net.fabricmc.loom.task.prod.ClientProductionRunTask) {
|
||||
jvmArgs.add("-Dfabric.client.gametest")
|
||||
|
||||
tracy {
|
||||
tracyCapture = file("tracy-capture")
|
||||
output = file("profile.tracy")
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
// Copy tracy into the project
|
||||
def projectTracyCapture = new File(gradle.projectDir, "tracy-capture")
|
||||
projectTracyCapture.bytes = tracyCapture.bytes
|
||||
projectTracyCapture.setExecutable(true)
|
||||
|
||||
when:
|
||||
def result = gradle.run(task: "prodClient")
|
||||
|
||||
then:
|
||||
result.task(":prodClient").outcome == SUCCESS
|
||||
new File(gradle.projectDir, "profile.tracy").exists()
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,13 @@ package net.fabricmc.loom.test.unit
|
||||
import org.gradle.api.Project
|
||||
import spock.lang.Specification
|
||||
|
||||
import net.fabricmc.loom.configuration.FabricApiExtension
|
||||
import net.fabricmc.loom.configuration.fabricapi.FabricApiVersions
|
||||
import net.fabricmc.loom.test.util.GradleTestUtil
|
||||
|
||||
class FabricApiExtensionTest extends Specification {
|
||||
def "get module version"() {
|
||||
when:
|
||||
def fabricApi = new FabricApiExtension() {
|
||||
def fabricApi = new FabricApiVersions() {
|
||||
Project project = GradleTestUtil.mockProject()
|
||||
}
|
||||
def version = fabricApi.moduleVersion(moduleName, apiVersion)
|
||||
@@ -51,7 +51,7 @@ class FabricApiExtensionTest extends Specification {
|
||||
|
||||
def "unknown module"() {
|
||||
when:
|
||||
def fabricApi = new FabricApiExtension() {
|
||||
def fabricApi = new FabricApiVersions() {
|
||||
Project project = GradleTestUtil.mockProject()
|
||||
}
|
||||
fabricApi.moduleVersion("fabric-api-unknown", apiVersion)
|
||||
|
||||
@@ -65,6 +65,31 @@ class LineNumberRemapperTests extends Specification {
|
||||
readLineNumbers(unpacked) == [37, 39, 40]
|
||||
}
|
||||
|
||||
def "remapLinenumbersExclude"() {
|
||||
given:
|
||||
def className = LineNumberSource.class.name.replace('.', '/')
|
||||
def input = ZipTestUtils.createZipFromBytes([(className + ".class"): getClassBytes(LineNumberSource.class)])
|
||||
|
||||
// + 10 to each line number
|
||||
def entry = new ClassLineNumbers.Entry(className, 30, 40, [
|
||||
27: 37,
|
||||
30: 40
|
||||
])
|
||||
def lineNumbers = new ClassLineNumbers([(className): entry])
|
||||
|
||||
def outputJar = Files.createTempDirectory("loom").resolve("output.jar")
|
||||
|
||||
when:
|
||||
def remapper = new LineNumberRemapper(lineNumbers)
|
||||
remapper.process(input, outputJar)
|
||||
|
||||
def unpacked = ZipUtils.unpack(outputJar, className + ".class")
|
||||
|
||||
then:
|
||||
readLineNumbers(getClassBytes(LineNumberSource.class)) == [27, 29, 30]
|
||||
readLineNumbers(unpacked) == [37, 40]
|
||||
}
|
||||
|
||||
static byte[] getClassBytes(Class<?> clazz) {
|
||||
return clazz.classLoader.getResourceAsStream(clazz.name.replace('.', '/') + ".class").withCloseable {
|
||||
it.bytes
|
||||
|
||||
63
src/test/groovy/net/fabricmc/loom/test/unit/cache/ClassEntryTest.groovy
vendored
Normal file
63
src/test/groovy/net/fabricmc/loom/test/unit/cache/ClassEntryTest.groovy
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.test.unit.cache
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
import net.fabricmc.loom.decompilers.cache.ClassEntry
|
||||
|
||||
class ClassEntryTest extends Specification {
|
||||
def "valid class entry"() {
|
||||
when:
|
||||
def classEntry = new ClassEntry(name, innerClasses, superClasses)
|
||||
then:
|
||||
// Just make sure the constructor doesn't throw an exception
|
||||
classEntry != null
|
||||
where:
|
||||
name | innerClasses | superClasses
|
||||
"net/fabricmc/Test.class" | [] | []
|
||||
"net/fabricmc/Test.class" | [
|
||||
"net/fabricmc/Test\$Inner.class"
|
||||
] | ["java/lang/List.class"]
|
||||
}
|
||||
|
||||
def "invalid class entry"() {
|
||||
when:
|
||||
new ClassEntry(name, innerClasses, superClasses)
|
||||
then:
|
||||
thrown IllegalArgumentException
|
||||
where:
|
||||
name | innerClasses | superClasses
|
||||
"net/fabricmc/Test" | [] | []
|
||||
"net/fabricmc/Test.class" | ["net/fabricmc/Test\$Inner"] | ["java/lang/List.class"]
|
||||
"net/fabricmc/Test.class" | [
|
||||
"net/fabricmc/Test\$Inner.class"
|
||||
] | ["java/lang/List"]
|
||||
"net/fabricmc/Test.class" | ["net/Test\$Inner.class"] | ["java/lang/List.class"]
|
||||
"net/fabricmc/Test.class" | [
|
||||
"net/fabricmc/Bar\$Inner.class"
|
||||
] | []
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ class JarWalkerTest extends Specification {
|
||||
classes.size() == 1
|
||||
classes[0].name() == "net/fabricmc/Example.class"
|
||||
classes[0].innerClasses() == []
|
||||
classes[0].superClasses() == ["java/lang/Runnable"]
|
||||
classes[0].superClasses() == ["java/lang/Runnable.class"]
|
||||
}
|
||||
|
||||
def "inner classes"() {
|
||||
@@ -146,8 +146,8 @@ class JarWalkerTest extends Specification {
|
||||
"net/fabricmc/other/Test\$Inner.class"
|
||||
]
|
||||
classes[0].superClasses() == [
|
||||
"java/lang/Runnable",
|
||||
"net/fabricmc/other/Super"
|
||||
"java/lang/Runnable.class",
|
||||
"net/fabricmc/other/Super.class"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,12 @@ import java.util.concurrent.TimeUnit
|
||||
import groovy.transform.Immutable
|
||||
|
||||
import net.fabricmc.loom.test.LoomTestVersions
|
||||
import net.fabricmc.loom.util.LoomVersions
|
||||
import net.fabricmc.loom.util.download.Download
|
||||
|
||||
class ServerRunner {
|
||||
static final String LOADER_VERSION = LoomTestVersions.FABRIC_LOADER.version()
|
||||
static final String INSTALLER_VERSION = LoomTestVersions.FABRIC_INSTALLER.version()
|
||||
static final String INSTALLER_VERSION = LoomVersions.FABRIC_INSTALLER.version()
|
||||
static final Map<String, String> FABRIC_API_URLS = [
|
||||
"1.16.5": "https://github.com/FabricMC/fabric/releases/download/0.37.1%2B1.16/fabric-api-0.37.1+1.16.jar",
|
||||
"1.17.1": "https://github.com/FabricMC/fabric/releases/download/0.37.1%2B1.17/fabric-api-0.37.1+1.17.jar"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
diff --git a/build.gradle b/build.gradle
|
||||
--- a/build.gradle (revision 70277babddfaf52ee30013af94764da19473b3b1)
|
||||
+++ b/build.gradle (date 1732875235843)
|
||||
--- a/build.gradle (revision d70d2c06bb8fafdb72c6778b29fb050618015ab3)
|
||||
+++ b/build.gradle (date 1734958436644)
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
def ENV = System.getenv()
|
||||
@@ -36,23 +36,17 @@ diff --git a/build.gradle b/build.gradle
|
||||
}
|
||||
|
||||
def getBranch() {
|
||||
@@ -247,19 +230,6 @@
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
-
|
||||
- afterEvaluate {
|
||||
- // See: https://github.com/FabricMC/fabric-loader/pull/585
|
||||
- def classPathGroups = loom.mods.stream()
|
||||
- .map { modSettings ->
|
||||
- SourceSetHelper.getClasspath(modSettings, getProject()).stream()
|
||||
- .map(File.&getAbsolutePath)
|
||||
- .collect(Collectors.joining(File.pathSeparator))
|
||||
- }
|
||||
- .collect(Collectors.joining(File.pathSeparator+File.pathSeparator))
|
||||
-
|
||||
- systemProperty("fabric.classPathGroups", classPathGroups)
|
||||
- }
|
||||
@@ -250,10 +233,11 @@
|
||||
}
|
||||
|
||||
tasks.withType(ProcessResources).configureEach {
|
||||
- inputs.property "version", project.version
|
||||
+ def version = project.version
|
||||
+ inputs.property "version", version
|
||||
|
||||
filesMatching("fabric.mod.json") {
|
||||
- expand "version": project.version
|
||||
+ expand "version": version
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,14 @@ publishing {
|
||||
from components.java
|
||||
|
||||
artifact(remapJar) {
|
||||
classifier "classifier"
|
||||
classifier = "classifier"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "http://localhost:${System.getProperty("loom.test.mavenPort")}/"
|
||||
url = "http://localhost:${System.getProperty("loom.test.mavenPort")}/"
|
||||
allowInsecureProtocol = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,10 @@ loom {
|
||||
mixin {
|
||||
useLegacyMixinAp = true
|
||||
|
||||
defaultRefmapName = "default-refmap0000.json"
|
||||
// After evaluate block only to test for https://github.com/FabricMC/fabric-loom/issues/1249
|
||||
afterEvaluate {
|
||||
defaultRefmapName = "default-refmap0000.json"
|
||||
}
|
||||
add(sourceSets["main"], "main-refmap0000.json")
|
||||
add(sourceSets["mixin"])
|
||||
|
||||
|
||||
@@ -36,14 +36,14 @@ publishing {
|
||||
from components.java
|
||||
artifact(remapJar) {
|
||||
builtBy remapJar
|
||||
classifier "classifier"
|
||||
classifier = "classifier"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "http://localhost:${System.getProperty("loom.test.mavenPort")}/"
|
||||
url = "http://localhost:${System.getProperty("loom.test.mavenPort")}/"
|
||||
allowInsecureProtocol = true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user