mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Merge 1.7, part 1
This commit is contained in:
6
.github/workflows/test-push.yml
vendored
6
.github/workflows/test-push.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: [8.6.0-jdk17]
|
||||
version: [8.7.0-jdk17]
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: gradle:${{ matrix.version }}
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: gradle:8.6.0-jdk17
|
||||
image: gradle:8.7.0-jdk17
|
||||
options: --user root
|
||||
|
||||
steps:
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: [8.6.0-jdk17]
|
||||
version: [8.7.0-jdk17]
|
||||
test: ${{ fromJson(needs.prepare_test_matrix.outputs.matrix) }}
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.gradle.util.GradleVersion;
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class LoomGradlePluginBootstrap implements Plugin<PluginAware> {
|
||||
private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.6";
|
||||
private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.7";
|
||||
private static final int MIN_SUPPORTED_MAJOR_JAVA_VERSION = 17;
|
||||
private static final int MIN_SUPPORTED_MAJOR_IDEA_VERSION = 2021;
|
||||
|
||||
|
||||
17
build.gradle
17
build.gradle
@@ -6,7 +6,6 @@ plugins {
|
||||
id 'eclipse'
|
||||
id 'groovy'
|
||||
id 'checkstyle'
|
||||
id 'jacoco'
|
||||
id 'codenarc'
|
||||
alias(libs.plugins.kotlin) apply false // Delay this so we can perform magic 🪄 first.
|
||||
alias(libs.plugins.spotless)
|
||||
@@ -50,7 +49,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
}
|
||||
|
||||
group = "dev.architectury"
|
||||
def baseVersion = '1.6'
|
||||
def baseVersion = '1.7'
|
||||
|
||||
def ENV = System.getenv()
|
||||
def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999"
|
||||
@@ -299,20 +298,6 @@ gradlePlugin {
|
||||
}
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = libs.versions.jacoco.get()
|
||||
}
|
||||
|
||||
// Run to get test coverage.
|
||||
jacocoTestReport {
|
||||
dependsOn test
|
||||
reports {
|
||||
xml.required = false
|
||||
csv.required = false
|
||||
html.outputLocation = file("${layout.buildDirectory.get().asFile}/jacocoHtml")
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
maxHeapSize = "2560m"
|
||||
jvmArgs "-XX:+HeapDumpOnOutOfMemoryError"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
kotlin = "1.9.20"
|
||||
kotlin = "1.9.22"
|
||||
asm = "9.6"
|
||||
commons-io = "2.15.1"
|
||||
gson = "2.10.1"
|
||||
@@ -8,7 +8,7 @@ guava = "33.0.0-jre"
|
||||
stitch = "0.6.2"
|
||||
tiny-remapper = "0.10.1"
|
||||
access-widener = "2.1.0"
|
||||
mapping-io = "0.5.1"
|
||||
mapping-io = "0.6.1"
|
||||
lorenz-tiny = "4.0.2"
|
||||
mercury = "0.1.4.17"
|
||||
kotlinx-metadata = "0.9.0"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Decompilers
|
||||
fernflower = "2.0.0"
|
||||
cfr = "0.2.2"
|
||||
vineflower = "1.9.3"
|
||||
vineflower = "1.10.1"
|
||||
|
||||
# Runtime depedencies
|
||||
mixin-compile-extensions = "0.6.0"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[versions]
|
||||
spock = "2.3-groovy-3.0"
|
||||
junit = "5.10.2"
|
||||
javalin = "6.1.0"
|
||||
mockito = "5.10.0"
|
||||
java-debug = "0.51.0"
|
||||
javalin = "6.1.3"
|
||||
mockito = "5.11.0"
|
||||
java-debug = "0.52.0"
|
||||
mixin = "0.12.5+mixin.0.8.5"
|
||||
|
||||
gradle-nightly = "8.8-20240224001421+0000"
|
||||
fabric-loader = "0.15.6"
|
||||
fabric-installer = "1.0.0"
|
||||
gradle-nightly = "8.9-20240426001649+0000"
|
||||
fabric-loader = "0.15.10"
|
||||
fabric-installer = "1.0.1"
|
||||
|
||||
[libraries]
|
||||
spock = { module = "org.spockframework:spock-core", version.ref = "spock" }
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
14
gradlew
vendored
14
gradlew
vendored
@@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -202,11 +202,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
||||
20
gradlew.bat
vendored
20
gradlew.bat
vendored
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
@@ -153,6 +153,8 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
|
||||
|
||||
LoomProblemReporter getProblemReporter();
|
||||
|
||||
boolean isConfigurationCacheActive();
|
||||
|
||||
// ===================
|
||||
// Architectury Loom
|
||||
// ===================
|
||||
|
||||
@@ -131,7 +131,7 @@ public abstract class CompileConfiguration implements Runnable {
|
||||
extension.setDependencyManager(dependencyManager);
|
||||
dependencyManager.handleDependencies(getProject(), serviceManager);
|
||||
} catch (Exception e) {
|
||||
ExceptionUtil.printFileLocks(e, getProject());
|
||||
ExceptionUtil.processException(e, getProject());
|
||||
disownLock();
|
||||
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to setup Minecraft", e);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.artifacts.DependencySet;
|
||||
import org.gradle.api.artifacts.FileCollectionDependency;
|
||||
import org.gradle.api.artifacts.ResolvedDependency;
|
||||
import org.gradle.api.artifacts.component.ComponentIdentifier;
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.util.gradle.SelfResolvingDependencyUtils;
|
||||
@@ -104,8 +106,21 @@ public class DependencyInfo {
|
||||
return sourceConfiguration;
|
||||
}
|
||||
|
||||
private boolean matches(ComponentIdentifier identifier) {
|
||||
if (identifier instanceof ModuleComponentIdentifier moduleComponentIdentifier) {
|
||||
return moduleComponentIdentifier.getGroup().equals(dependency.getGroup())
|
||||
&& moduleComponentIdentifier.getModule().equals(dependency.getName())
|
||||
&& moduleComponentIdentifier.getVersion().equals(dependency.getVersion());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Set<File> resolve() {
|
||||
return sourceConfiguration.files(dependency);
|
||||
return sourceConfiguration.getIncoming()
|
||||
.artifactView(view -> view.componentFilter(this::matches))
|
||||
.getFiles()
|
||||
.getFiles();
|
||||
}
|
||||
|
||||
public Optional<File> resolveFile() {
|
||||
|
||||
@@ -37,6 +37,8 @@ import net.fabricmc.loom.task.GenerateSourcesTask;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
|
||||
public abstract class DecompileConfiguration<T extends MappedMinecraftProvider> {
|
||||
static final String DEFAULT_DECOMPILER = "Vineflower";
|
||||
|
||||
protected final Project project;
|
||||
protected final T minecraftProvider;
|
||||
protected final LoomGradleExtension extension;
|
||||
|
||||
@@ -78,7 +78,7 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration<Mapp
|
||||
task.setDescription("Decompile minecraft using the default decompiler.");
|
||||
task.setGroup(Constants.TaskGroup.FABRIC);
|
||||
|
||||
task.dependsOn(project.getTasks().named("genSourcesWithCfr"));
|
||||
task.dependsOn(project.getTasks().named("genSourcesWith" + DecompileConfiguration.DEFAULT_DECOMPILER));
|
||||
});
|
||||
|
||||
// TODO: Support for env-only jars?
|
||||
|
||||
@@ -114,7 +114,7 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration<Ma
|
||||
task.setDescription("Decompile minecraft (%s) using the default decompiler.".formatted(name));
|
||||
task.setGroup(Constants.TaskGroup.FABRIC);
|
||||
|
||||
task.dependsOn(project.getTasks().named("gen%sSourcesWithCfr".formatted(name)));
|
||||
task.dependsOn(project.getTasks().named("gen%sSourcesWith%s".formatted(name, DecompileConfiguration.DEFAULT_DECOMPILER)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.jar.Attributes;
|
||||
@@ -45,7 +48,7 @@ import net.fabricmc.loom.util.ModPlatform;
|
||||
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
|
||||
|
||||
// ARCH: isFabricMod means "is mod on current platform"
|
||||
public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequirements, @Nullable InstallerData installerData, MixinRemapType mixinRemapType) {
|
||||
public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequirements, @Nullable InstallerData installerData, MixinRemapType mixinRemapType, List<String> knownIdyBsms) {
|
||||
private static final String INSTALLER_PATH = "fabric-installer.json";
|
||||
|
||||
// ARCH: Quilt support
|
||||
@@ -60,6 +63,7 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
|
||||
RemapRequirements remapRequirements = RemapRequirements.DEFAULT;
|
||||
InstallerData installerData = null;
|
||||
MixinRemapType refmapRemapType = MixinRemapType.MIXIN;
|
||||
List<String> knownIndyBsms = new ArrayList<>();
|
||||
|
||||
// Force-remap all mods on Forge and NeoForge.
|
||||
if (platform.isForgeLike()) {
|
||||
@@ -76,6 +80,7 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
|
||||
final String remapValue = mainAttributes.getValue(Constants.Manifest.REMAP_KEY);
|
||||
final String loomVersion = mainAttributes.getValue(Constants.Manifest.LOOM_VERSION);
|
||||
final String mixinRemapType = mainAttributes.getValue(Constants.Manifest.MIXIN_REMAP_TYPE);
|
||||
final String knownIndyBsmsValue = mainAttributes.getValue(Constants.Manifest.KNOWN_IDY_BSMS);
|
||||
|
||||
if (remapValue != null) {
|
||||
// Support opting into and out of remapping with "Fabric-Loom-Remap" manifest entry
|
||||
@@ -98,6 +103,10 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
|
||||
if (loomVersion != null && refmapRemapType != MixinRemapType.STATIC) {
|
||||
validateLoomVersion(loomVersion, currentLoomVersion);
|
||||
}
|
||||
|
||||
if (knownIndyBsmsValue != null) {
|
||||
Collections.addAll(knownIndyBsms, knownIndyBsmsValue.split(","));
|
||||
}
|
||||
}
|
||||
|
||||
final String installerFile = platform == ModPlatform.QUILT ? QUILT_INSTALLER_PATH : INSTALLER_PATH;
|
||||
@@ -109,7 +118,7 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
|
||||
}
|
||||
}
|
||||
|
||||
return new ArtifactMetadata(isFabricMod, remapRequirements, installerData, refmapRemapType);
|
||||
return new ArtifactMetadata(isFabricMod, remapRequirements, installerData, refmapRemapType, Collections.unmodifiableList(knownIndyBsms));
|
||||
}
|
||||
|
||||
// Validates that the version matches or is less than the current loom version
|
||||
|
||||
@@ -166,14 +166,19 @@ public class ModProcessor {
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
final MappingConfiguration mappingConfiguration = extension.getMappingConfiguration();
|
||||
String fromM = IntermediaryNamespaces.intermediary(project);
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
Set<String> knownIndyBsms = new HashSet<>(extension.getKnownIndyBsms().get());
|
||||
|
||||
for (ModDependency modDependency : remapList) {
|
||||
knownIndyBsms.addAll(modDependency.getMetadata().knownIdyBsms());
|
||||
}
|
||||
|
||||
MappingOption mappingOption = MappingOption.forPlatform(extension).forNamespaces(fromM, toM);
|
||||
MemoryMappingTree mappings = mappingConfiguration.getMappingsService(serviceManager, mappingOption).getMappingTree();
|
||||
LoggerFilter.replaceSystemOut();
|
||||
|
||||
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
|
||||
.withKnownIndyBsm(extension.getKnownIndyBsms().get())
|
||||
.withKnownIndyBsm(knownIndyBsms)
|
||||
.withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false))
|
||||
.renameInvalidLocals(false)
|
||||
.extraAnalyzeVisitor(AccessWidenerAnalyzeVisitorProvider.createFromMods(fromM, remapList, extension.getPlatform().get()));
|
||||
|
||||
@@ -33,8 +33,11 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.configuration.BuildFeatures;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
@@ -66,7 +69,7 @@ import net.fabricmc.loom.util.download.Download;
|
||||
import net.fabricmc.loom.util.download.DownloadBuilder;
|
||||
import net.fabricmc.loom.util.gradle.GradleUtils;
|
||||
|
||||
public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implements LoomGradleExtension {
|
||||
public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implements LoomGradleExtension {
|
||||
private final Project project;
|
||||
private final MixinExtension mixinApExtension;
|
||||
private final LoomFiles loomFiles;
|
||||
@@ -83,9 +86,11 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
|
||||
private MojangMappedMinecraftProvider<?> mojangMappedMinecraftProvider;
|
||||
private InstallerData installerData;
|
||||
private boolean refreshDeps;
|
||||
private Provider<Boolean> multiProjectOptimisation;
|
||||
private final Provider<Boolean> multiProjectOptimisation;
|
||||
private final ListProperty<LibraryProcessorManager.LibraryProcessorFactory> libraryProcessorFactories;
|
||||
private final LoomProblemReporter problemReporter;
|
||||
private final boolean configurationCacheActive;
|
||||
private final boolean isolatedProjectsActive;
|
||||
|
||||
// +-------------------+
|
||||
// | Architectury Loom |
|
||||
@@ -95,6 +100,10 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
|
||||
private final Supplier<ForgeExtensionAPI> forgeExtension;
|
||||
private final Supplier<NeoForgeExtensionAPI> neoForgeExtension;
|
||||
|
||||
@Inject
|
||||
protected abstract BuildFeatures getBuildFeatures();
|
||||
|
||||
@Inject
|
||||
public LoomGradleExtensionImpl(Project project, LoomFiles files) {
|
||||
super(project, files);
|
||||
this.project = project;
|
||||
@@ -120,6 +129,22 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
|
||||
libraryProcessorFactories.addAll(LibraryProcessorManager.DEFAULT_LIBRARY_PROCESSORS);
|
||||
libraryProcessorFactories.finalizeValueOnRead();
|
||||
|
||||
configurationCacheActive = getBuildFeatures().getConfigurationCache().getActive().get();
|
||||
isolatedProjectsActive = getBuildFeatures().getIsolatedProjects().getActive().get();
|
||||
|
||||
// Fundamentally impossible to support multi-project optimisation with the configuration cache and/or isolated projects.
|
||||
if (multiProjectOptimisation.get() && configurationCacheActive) {
|
||||
throw new UnsupportedOperationException("Multi-project optimisation is not supported with the configuration cache");
|
||||
}
|
||||
|
||||
if (multiProjectOptimisation.get() && isolatedProjectsActive) {
|
||||
throw new UnsupportedOperationException("Isolated projects are not supported with multi-project optimisation");
|
||||
}
|
||||
|
||||
if (configurationCacheActive) {
|
||||
project.getLogger().warn("Loom support for the Gradle configuration cache is highly experimental and may not work as expected. Please report any issues you encounter.");
|
||||
}
|
||||
|
||||
if (refreshDeps) {
|
||||
project.getLogger().lifecycle("Refresh dependencies is in use, loom will be significantly slower.");
|
||||
}
|
||||
@@ -328,6 +353,11 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
|
||||
return problemReporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurationCacheActive() {
|
||||
return configurationCacheActive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForgeExtensionAPI getForge() {
|
||||
ModPlatform.assertPlatform(this, ModPlatform.FORGE);
|
||||
|
||||
@@ -34,17 +34,18 @@ 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.Nested;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.LoomGradlePlugin;
|
||||
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.assets.AssetIndex;
|
||||
import net.fabricmc.loom.util.MirrorUtil;
|
||||
import net.fabricmc.loom.util.download.DownloadExecutor;
|
||||
import net.fabricmc.loom.util.download.DownloadFactory;
|
||||
import net.fabricmc.loom.util.download.GradleDownloadProgressListener;
|
||||
import net.fabricmc.loom.util.gradle.ProgressGroup;
|
||||
|
||||
@@ -59,12 +60,24 @@ public abstract class DownloadAssetsTask extends AbstractLoomTask {
|
||||
@Input
|
||||
public abstract Property<String> getMinecraftVersion();
|
||||
|
||||
@Input
|
||||
public abstract Property<String> getResourcesBaseUrl();
|
||||
|
||||
@Input
|
||||
protected abstract Property<String> getAssetsIndexJson();
|
||||
|
||||
@OutputDirectory
|
||||
public abstract RegularFileProperty getAssetsDirectory();
|
||||
|
||||
@OutputDirectory
|
||||
public abstract RegularFileProperty getLegacyResourcesDirectory();
|
||||
|
||||
@Inject
|
||||
protected abstract ProgressLoggerFactory getProgressLoggerFactory();
|
||||
|
||||
@Nested
|
||||
protected abstract DownloadFactory getDownloadFactory();
|
||||
|
||||
@Inject
|
||||
public DownloadAssetsTask() {
|
||||
final MinecraftVersionMeta versionInfo = getExtension().getMinecraftProvider().getVersionInfo();
|
||||
@@ -84,6 +97,11 @@ public abstract class DownloadAssetsTask extends AbstractLoomTask {
|
||||
getLegacyResourcesDirectory().set(new File(getProject().getProjectDir(), client.getRunDir() + "/resources"));
|
||||
}
|
||||
|
||||
getResourcesBaseUrl().set(MirrorUtil.getResourcesBase(getProject()));
|
||||
getResourcesBaseUrl().finalizeValue();
|
||||
|
||||
getAssetsIndexJson().set(LoomGradlePlugin.GSON.toJson(getExtension().getMinecraftProvider().getVersionInfo().assetIndex()));
|
||||
|
||||
getAssetsHash().finalizeValue();
|
||||
getAssetsDirectory().finalizeValueOnRead();
|
||||
getLegacyResourcesDirectory().finalizeValueOnRead();
|
||||
@@ -93,13 +111,13 @@ public abstract class DownloadAssetsTask extends AbstractLoomTask {
|
||||
public void downloadAssets() throws IOException {
|
||||
final AssetIndex assetIndex = getAssetIndex();
|
||||
|
||||
try (ProgressGroup progressGroup = new ProgressGroup(getProject(), "Download Assets");
|
||||
try (ProgressGroup progressGroup = new ProgressGroup("Download Assets", getProgressLoggerFactory());
|
||||
DownloadExecutor executor = new DownloadExecutor(getDownloadThreads().get())) {
|
||||
for (AssetIndex.Object object : assetIndex.getObjects()) {
|
||||
final String sha1 = object.hash();
|
||||
final String url = MirrorUtil.getResourcesBase(getProject()) + sha1.substring(0, 2) + "/" + sha1;
|
||||
final String url = getResourcesBaseUrl().get() + sha1.substring(0, 2) + "/" + sha1;
|
||||
|
||||
getExtension()
|
||||
getDownloadFactory()
|
||||
.download(url)
|
||||
.sha1(sha1)
|
||||
.progress(new GradleDownloadProgressListener(object.name(), progressGroup::createProgressLogger))
|
||||
@@ -108,18 +126,11 @@ public abstract class DownloadAssetsTask extends AbstractLoomTask {
|
||||
}
|
||||
}
|
||||
|
||||
private MinecraftVersionMeta.AssetIndex getAssetIndexMeta() {
|
||||
MinecraftVersionMeta versionInfo = getExtension().getMinecraftProvider().getVersionInfo();
|
||||
return versionInfo.assetIndex();
|
||||
}
|
||||
|
||||
private AssetIndex getAssetIndex() throws IOException {
|
||||
final LoomGradleExtension extension = getExtension();
|
||||
final MinecraftProvider minecraftProvider = extension.getMinecraftProvider();
|
||||
final MinecraftVersionMeta.AssetIndex assetIndex = getAssetIndexMeta();
|
||||
final File indexFile = new File(getAssetsDirectory().get().getAsFile(), "indexes" + File.separator + assetIndex.fabricId(minecraftProvider.minecraftVersion()) + ".json");
|
||||
final MinecraftVersionMeta.AssetIndex assetIndex = LoomGradlePlugin.GSON.fromJson(getAssetsIndexJson().get(), MinecraftVersionMeta.AssetIndex.class);
|
||||
final File indexFile = new File(getAssetsDirectory().get().getAsFile(), "indexes" + File.separator + assetIndex.fabricId(getMinecraftVersion().get()) + ".json");
|
||||
|
||||
final String json = extension.download(assetIndex.url())
|
||||
final String json = getDownloadFactory().download(assetIndex.url())
|
||||
.sha1(assetIndex.sha1())
|
||||
.downloadString(indexFile.toPath());
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
||||
try (var timer = new Timer("Decompiled sources")) {
|
||||
runWithoutCache();
|
||||
} catch (Exception e) {
|
||||
ExceptionUtil.printFileLocks(e, getProject());
|
||||
ExceptionUtil.processException(e, getProject());
|
||||
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e);
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
||||
runWithCache(fs.getRoot());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ExceptionUtil.printFileLocks(e, getProject());
|
||||
ExceptionUtil.processException(e, getProject());
|
||||
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,18 +56,22 @@ public abstract class LoomTasks implements Runnable {
|
||||
t.setDescription("Migrates mappings to a new version.");
|
||||
t.getOutputs().upToDateWhen(o -> false);
|
||||
});
|
||||
|
||||
var generateLog4jConfig = getTasks().register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> {
|
||||
t.setDescription("Generate the log4j config file");
|
||||
});
|
||||
var generateRemapClasspath = getTasks().register("generateRemapClasspath", GenerateRemapClasspathTask.class, t -> {
|
||||
t.setDescription("Generate the remap classpath file");
|
||||
});
|
||||
getTasks().register("generateDLIConfig", GenerateDLIConfigTask.class, t -> {
|
||||
t.setDescription("Generate the DevLaunchInjector config file");
|
||||
|
||||
// Must allow these IDE files to be generated first
|
||||
t.mustRunAfter(getTasks().named("eclipse"));
|
||||
t.mustRunAfter(getTasks().named("idea"));
|
||||
});
|
||||
getTasks().register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> {
|
||||
t.setDescription("Generate the log4j config file");
|
||||
});
|
||||
getTasks().register("generateRemapClasspath", GenerateRemapClasspathTask.class, t -> {
|
||||
t.setDescription("Generate the remap classpath file");
|
||||
|
||||
t.dependsOn(generateLog4jConfig);
|
||||
t.getRemapClasspathFile().set(generateRemapClasspath.get().getRemapClasspathFile());
|
||||
});
|
||||
|
||||
getTasks().register("configureLaunch", task -> {
|
||||
|
||||
@@ -37,9 +37,18 @@ import java.util.StringJoiner;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.logging.configuration.ConsoleOutput;
|
||||
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.gradle.api.tasks.TaskAction;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.LoomGradlePlugin;
|
||||
import net.fabricmc.loom.build.IntermediaryNamespaces;
|
||||
import net.fabricmc.loom.configuration.providers.forge.ConfigValue;
|
||||
import net.fabricmc.loom.configuration.providers.forge.ForgeRunTemplate;
|
||||
@@ -52,10 +61,75 @@ import net.fabricmc.loom.util.PropertyUtil;
|
||||
import net.fabricmc.loom.util.gradle.SourceSetHelper;
|
||||
|
||||
public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
|
||||
@Input
|
||||
protected abstract Property<String> getVersionInfoJson();
|
||||
|
||||
@Input
|
||||
protected abstract Property<String> getMinecraftVersion();
|
||||
|
||||
@Input
|
||||
protected abstract Property<Boolean> getSplitSourceSets();
|
||||
|
||||
@Input
|
||||
protected abstract Property<Boolean> getPlainConsole();
|
||||
|
||||
@Input
|
||||
protected abstract Property<Boolean> getANSISupportedIDE();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
protected abstract Property<String> getClassPathGroups();
|
||||
|
||||
@Input
|
||||
protected abstract Property<String> getLog4jConfigPaths();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
protected abstract Property<String> getClientGameJarPath();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
protected abstract Property<String> getCommonGameJarPath();
|
||||
|
||||
@Input
|
||||
protected abstract Property<String> getAssetsDirectoryPath();
|
||||
|
||||
@Input
|
||||
protected abstract Property<String> getNativesDirectoryPath();
|
||||
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getRemapClasspathFile();
|
||||
|
||||
@OutputFile
|
||||
protected abstract RegularFileProperty getDevLauncherConfig();
|
||||
|
||||
public GenerateDLIConfigTask() {
|
||||
getVersionInfoJson().set(LoomGradlePlugin.GSON.toJson(getExtension().getMinecraftProvider().getVersionInfo()));
|
||||
getMinecraftVersion().set(getExtension().getMinecraftProvider().minecraftVersion());
|
||||
getSplitSourceSets().set(getExtension().areEnvironmentSourceSetsSplit());
|
||||
getANSISupportedIDE().set(ansiSupportedIde(getProject()));
|
||||
getPlainConsole().set(getProject().getGradle().getStartParameter().getConsoleOutput() == ConsoleOutput.Plain);
|
||||
|
||||
if (!getExtension().getMods().isEmpty()) {
|
||||
getClassPathGroups().set(buildClassPathGroups(getProject()));
|
||||
}
|
||||
|
||||
getLog4jConfigPaths().set(getAllLog4JConfigFiles(getProject()));
|
||||
|
||||
if (getSplitSourceSets().get()) {
|
||||
getClientGameJarPath().set(getGameJarPath("client"));
|
||||
getCommonGameJarPath().set(getGameJarPath("common"));
|
||||
}
|
||||
|
||||
getAssetsDirectoryPath().set(new File(getExtension().getFiles().getUserCache(), "assets").getAbsolutePath());
|
||||
getNativesDirectoryPath().set(getExtension().getFiles().getNativesDirectory(getProject()).getAbsolutePath());
|
||||
getDevLauncherConfig().set(getExtension().getFiles().getDevLauncherConfig());
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void run() throws IOException {
|
||||
final MinecraftVersionMeta versionInfo = getExtension().getMinecraftProvider().getVersionInfo();
|
||||
File assetsDirectory = new File(getExtension().getFiles().getUserCache(), "assets");
|
||||
final MinecraftVersionMeta versionInfo = LoomGradlePlugin.GSON.fromJson(getVersionInfoJson().get(), MinecraftVersionMeta.class);
|
||||
File assetsDirectory = new File(getAssetsDirectoryPath().get());
|
||||
|
||||
if (versionInfo.assets().equals("legacy")) {
|
||||
assetsDirectory = new File(assetsDirectory, "/legacy/" + versionInfo.id());
|
||||
@@ -64,12 +138,12 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
|
||||
boolean quilt = getExtension().isQuilt();
|
||||
final LaunchConfig launchConfig = new LaunchConfig()
|
||||
.property(!quilt ? "fabric.development" : "loader.development", "true")
|
||||
.property(!quilt ? "fabric.remapClasspathFile" : "loader.remapClasspathFile", getExtension().getFiles().getRemapClasspathFile().getAbsolutePath())
|
||||
.property("log4j.configurationFile", getAllLog4JConfigFiles())
|
||||
.property(!quilt ? "fabric.remapClasspathFile" : "loader.remapClasspathFile", getRemapClasspathFile().get().getAsFile().getAbsolutePath())
|
||||
.property("log4j.configurationFile", getLog4jConfigPaths().get())
|
||||
.property("log4j2.formatMsgNoLookups", "true");
|
||||
|
||||
if (versionInfo.hasNativesToExtract()) {
|
||||
String nativesPath = getExtension().getFiles().getNativesDirectory(getProject()).getAbsolutePath();
|
||||
String nativesPath = getNativesDirectoryPath().get();
|
||||
|
||||
launchConfig
|
||||
.property("client", "java.library.path", nativesPath)
|
||||
@@ -79,17 +153,17 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
|
||||
if (!getExtension().isForgeLike()) {
|
||||
launchConfig
|
||||
.argument("client", "--assetIndex")
|
||||
.argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion()))
|
||||
.argument("client", versionInfo.assetIndex().fabricId(getMinecraftVersion().get()))
|
||||
.argument("client", "--assetsDir")
|
||||
.argument("client", assetsDirectory.getAbsolutePath());
|
||||
|
||||
if (getExtension().areEnvironmentSourceSetsSplit()) {
|
||||
launchConfig.property("client", !quilt ? "fabric.gameJarPath.client" : "loader.gameJarPath.client", getGameJarPath("client"));
|
||||
launchConfig.property(!quilt ? "fabric.gameJarPath" : "loader.gameJarPath", getGameJarPath("common"));
|
||||
if (getSplitSourceSets().get()) {
|
||||
launchConfig.property("client", !quilt ? "fabric.gameJarPath.client" : "loader.gameJarPath.client", getClientGameJarPath().get());
|
||||
launchConfig.property(!quilt ? "fabric.gameJarPath" : "loader.gameJarPath", getCommonGameJarPath().get());
|
||||
}
|
||||
|
||||
if (!getExtension().getMods().isEmpty()) {
|
||||
launchConfig.property(!quilt ? "fabric.classPathGroups" : "loader.classPathGroups", getClassPathGroups());
|
||||
if (getClassPathGroups().isPresent()) {
|
||||
launchConfig.property(!quilt ? "fabric.classPathGroups" : "loader.classPathGroups", getClassPathGroups().get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,22 +240,16 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
|
||||
}
|
||||
}
|
||||
|
||||
final boolean plainConsole = getProject().getGradle().getStartParameter().getConsoleOutput() == ConsoleOutput.Plain;
|
||||
final boolean ansiSupportedIDE = new File(getProject().getRootDir(), ".vscode").exists()
|
||||
|| new File(getProject().getRootDir(), ".idea").exists()
|
||||
|| new File(getProject().getRootDir(), ".project").exists()
|
||||
|| (Arrays.stream(getProject().getRootDir().listFiles()).anyMatch(file -> file.getName().endsWith(".iws")));
|
||||
|
||||
//Enable ansi by default for idea and vscode when gradle is not ran with plain console.
|
||||
if (ansiSupportedIDE && !plainConsole) {
|
||||
if (getANSISupportedIDE().get() && !getPlainConsole().get()) {
|
||||
launchConfig.property("fabric.log.disableAnsi", "false");
|
||||
}
|
||||
|
||||
FileUtils.writeStringToFile(getExtension().getFiles().getDevLauncherConfig(), launchConfig.asString(), StandardCharsets.UTF_8);
|
||||
FileUtils.writeStringToFile(getDevLauncherConfig().getAsFile().get(), launchConfig.asString(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String getAllLog4JConfigFiles() {
|
||||
return getExtension().getLog4jConfigs().getFiles().stream()
|
||||
private static String getAllLog4JConfigFiles(Project project) {
|
||||
return LoomGradleExtension.get(project).getLog4jConfigs().getFiles().stream()
|
||||
.map(File::getAbsolutePath)
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
@@ -199,16 +267,24 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
|
||||
/**
|
||||
* See: https://github.com/FabricMC/fabric-loader/pull/585.
|
||||
*/
|
||||
private String getClassPathGroups() {
|
||||
return getExtension().getMods().stream()
|
||||
private static String buildClassPathGroups(Project project) {
|
||||
return LoomGradleExtension.get(project).getMods().stream()
|
||||
.map(modSettings ->
|
||||
SourceSetHelper.getClasspath(modSettings, getProject()).stream()
|
||||
SourceSetHelper.getClasspath(modSettings, project).stream()
|
||||
.map(File::getAbsolutePath)
|
||||
.collect(Collectors.joining(File.pathSeparator))
|
||||
)
|
||||
.collect(Collectors.joining(File.pathSeparator+File.pathSeparator));
|
||||
}
|
||||
|
||||
private static boolean ansiSupportedIde(Project project) {
|
||||
File rootDir = project.getRootDir();
|
||||
return new File(rootDir, ".vscode").exists()
|
||||
|| new File(rootDir, ".idea").exists()
|
||||
|| new File(rootDir, ".project").exists()
|
||||
|| (Arrays.stream(rootDir.listFiles()).anyMatch(file -> file.getName().endsWith(".iws")));
|
||||
}
|
||||
|
||||
public static class LaunchConfig {
|
||||
private final Map<String, List<String>> values = new HashMap<>();
|
||||
|
||||
|
||||
@@ -29,15 +29,27 @@ import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dev.architectury.loom.util.ForgeLoggerConfig;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import net.fabricmc.loom.task.AbstractLoomTask;
|
||||
|
||||
public abstract class GenerateLog4jConfigTask extends AbstractLoomTask {
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getOutputFile();
|
||||
|
||||
@Inject
|
||||
public GenerateLog4jConfigTask() {
|
||||
getOutputFile().set(getExtension().getFiles().getDefaultLog4jConfigFile());
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void run() {
|
||||
Path outputFile = getExtension().getFiles().getDefaultLog4jConfigFile().toPath();
|
||||
Path outputFile = getOutputFile().get().getAsFile().toPath();
|
||||
|
||||
if (getExtension().isForge() && getExtension().getForge().getUseForgeLoggerConfig().get()) {
|
||||
ForgeLoggerConfig.copyToPath(getProject(), outputFile);
|
||||
|
||||
@@ -60,6 +60,10 @@ public abstract class GenerateRemapClasspathTask extends AbstractLoomTask {
|
||||
.map(configurations::named)
|
||||
.forEach(getRemapClasspath()::from);
|
||||
|
||||
for (Path minecraftJar : getExtension().getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
|
||||
getRemapClasspath().from(minecraftJar.toFile());
|
||||
}
|
||||
|
||||
getRemapClasspathFile().set(getExtension().getFiles().getRemapClasspathFile());
|
||||
}
|
||||
|
||||
@@ -67,10 +71,6 @@ public abstract class GenerateRemapClasspathTask extends AbstractLoomTask {
|
||||
public void run() {
|
||||
final List<File> remapClasspath = new ArrayList<>(getRemapClasspath().getFiles());
|
||||
|
||||
for (Path minecraftJar : getExtension().getMinecraftJars(MappingsNamespace.INTERMEDIARY)) {
|
||||
remapClasspath.add(minecraftJar.toFile());
|
||||
}
|
||||
|
||||
String str = remapClasspath.stream()
|
||||
.map(File::getAbsolutePath)
|
||||
.collect(Collectors.joining(File.pathSeparator));
|
||||
|
||||
@@ -172,6 +172,7 @@ public class Constants {
|
||||
public static final String FABRIC_LOADER_VERSION = "Fabric-Loader-Version";
|
||||
public static final String MIXIN_VERSION = "Fabric-Mixin-Version";
|
||||
public static final String MIXIN_GROUP = "Fabric-Mixin-Group";
|
||||
public static final String KNOWN_IDY_BSMS = "Fabric-Loom-Known-Indy-BSMS";
|
||||
}
|
||||
|
||||
public static final class Forge {
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.nativeplatform.LoomNativePlatform;
|
||||
import net.fabricmc.loom.nativeplatform.LoomNativePlatformException;
|
||||
import net.fabricmc.loom.util.gradle.daemon.DaemonUtils;
|
||||
|
||||
public final class ExceptionUtil {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionUtil.class);
|
||||
@@ -59,17 +60,24 @@ public final class ExceptionUtil {
|
||||
return constructor.apply(descriptiveMessage, cause);
|
||||
}
|
||||
|
||||
public static void printFileLocks(Throwable e, Project project) {
|
||||
public static void processException(Throwable e, Project project) {
|
||||
Throwable cause = e;
|
||||
boolean unrecoverable = false;
|
||||
|
||||
while (cause != null) {
|
||||
if (cause instanceof FileSystemException fse) {
|
||||
if (cause instanceof FileSystemUtil.UnrecoverableZipException) {
|
||||
unrecoverable = true;
|
||||
} else if (cause instanceof FileSystemException fse) {
|
||||
printFileLocks(fse.getFile(), project);
|
||||
break;
|
||||
}
|
||||
|
||||
cause = cause.getCause();
|
||||
}
|
||||
|
||||
if (unrecoverable) {
|
||||
DaemonUtils.tryStopGradleDaemon(project);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printFileLocks(String filename, Project project) {
|
||||
|
||||
@@ -28,8 +28,11 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystemNotFoundException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
@@ -38,7 +41,7 @@ import java.util.function.Supplier;
|
||||
import net.fabricmc.tinyremapper.FileSystemReference;
|
||||
|
||||
public final class FileSystemUtil {
|
||||
public record Delegate(FileSystemReference reference) implements AutoCloseable, Supplier<FileSystem> {
|
||||
public record Delegate(FileSystemReference reference, URI uri) implements AutoCloseable, Supplier<FileSystem> {
|
||||
public Path getPath(String path, String... more) {
|
||||
return get().getPath(path, more);
|
||||
}
|
||||
@@ -69,7 +72,31 @@ public final class FileSystemUtil {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
reference.close();
|
||||
try {
|
||||
reference.close();
|
||||
} catch (IOException e) {
|
||||
// An IOException can only ever be thrown by the underlying FileSystem.close() call in tiny remapper
|
||||
// This means that this reference was the last open
|
||||
try {
|
||||
// We would then almost always expect this to throw a FileSystemNotFoundException
|
||||
FileSystem fileSystem = FileSystems.getFileSystem(uri);
|
||||
|
||||
if (fileSystem.isOpen()) {
|
||||
// Or the unlikely chance that another thread opened a new reference
|
||||
throw e;
|
||||
}
|
||||
|
||||
// However if we end up here, the closed FileSystem was not removed from ZipFileSystemProvider.filesystems
|
||||
// This leaves us in a broken state, preventing this JVM from ever being able to open a zip at this path.
|
||||
// See: https://bugs.openjdk.org/browse/JDK-8291712
|
||||
throw new UnrecoverableZipException(e.getMessage(), e);
|
||||
} catch (FileSystemNotFoundException ignored) {
|
||||
// This the "happy" case, where the zip FS failed to close but was
|
||||
}
|
||||
|
||||
// Throw the normal exception, we can recover from this
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,18 +114,34 @@ public final class FileSystemUtil {
|
||||
}
|
||||
|
||||
public static Delegate getJarFileSystem(File file, boolean create) throws IOException {
|
||||
return new Delegate(FileSystemReference.openJar(file.toPath(), create));
|
||||
return new Delegate(FileSystemReference.openJar(file.toPath(), create), toJarUri(file.toPath()));
|
||||
}
|
||||
|
||||
public static Delegate getJarFileSystem(Path path, boolean create) throws IOException {
|
||||
return new Delegate(FileSystemReference.openJar(path, create));
|
||||
return new Delegate(FileSystemReference.openJar(path, create), toJarUri(path));
|
||||
}
|
||||
|
||||
public static Delegate getJarFileSystem(Path path) throws IOException {
|
||||
return new Delegate(FileSystemReference.openJar(path));
|
||||
return new Delegate(FileSystemReference.openJar(path), toJarUri(path));
|
||||
}
|
||||
|
||||
public static Delegate getJarFileSystem(URI uri, boolean create) throws IOException {
|
||||
return new Delegate(FileSystemReference.open(uri, create));
|
||||
return new Delegate(FileSystemReference.open(uri, create), uri);
|
||||
}
|
||||
|
||||
private static URI toJarUri(Path path) {
|
||||
URI uri = path.toUri();
|
||||
|
||||
try {
|
||||
return new URI("jar:" + uri.getScheme(), uri.getHost(), uri.getPath(), uri.getFragment());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("can't convert path "+path+" to uri", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnrecoverableZipException extends RuntimeException {
|
||||
public UnrecoverableZipException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.util.download;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
|
||||
/**
|
||||
* Can be used to create a {@link DownloadBuilder} with the correct settings for the project within a task.
|
||||
*/
|
||||
public abstract class DownloadFactory {
|
||||
@Input
|
||||
protected abstract Property<Boolean> getIsOffline();
|
||||
|
||||
@Input
|
||||
protected abstract Property<Boolean> getIsManualRefreshDependencies();
|
||||
|
||||
@Inject
|
||||
public abstract Project getProject();
|
||||
|
||||
@Inject
|
||||
public DownloadFactory() {
|
||||
getIsOffline().set(getProject().getGradle().getStartParameter().isOffline());
|
||||
getIsManualRefreshDependencies().set(LoomGradleExtension.get(getProject()).refreshDeps());
|
||||
}
|
||||
|
||||
// Matches the logic in LoomGradleExtensionImpl
|
||||
public DownloadBuilder download(String url) {
|
||||
DownloadBuilder builder;
|
||||
|
||||
try {
|
||||
builder = Download.create(url);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("Failed to create downloader for: " + e);
|
||||
}
|
||||
|
||||
if (getIsOffline().get()) {
|
||||
builder.offline();
|
||||
}
|
||||
|
||||
if (getIsManualRefreshDependencies().get()) {
|
||||
builder.forceDownload();
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,8 @@ public final class FabricModJsonFactory {
|
||||
}
|
||||
|
||||
throw new UncheckedIOException("Failed to read fabric.mod.json file in zip: " + zipPath, e);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new JsonSyntaxException("Failed to parse fabric.mod.json in zip: " + zipPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +107,8 @@ public final class FabricModJsonFactory {
|
||||
jsonObject = ZipUtils.unpackGsonNullable(zipPath, FABRIC_MOD_JSON, JsonObject.class);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to read zip: " + zipPath, e);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new JsonSyntaxException("Failed to parse fabric.mod.json in zip: " + zipPath, e);
|
||||
}
|
||||
|
||||
if (jsonObject == null) {
|
||||
@@ -129,23 +133,6 @@ public final class FabricModJsonFactory {
|
||||
return Optional.ofNullable(createFromZipNullable(zipPath));
|
||||
}
|
||||
|
||||
public static FabricModJson createFromDirectory(Path directory) throws IOException {
|
||||
final Path path = directory.resolve(FABRIC_MOD_JSON);
|
||||
|
||||
// Try another mod metadata file if fabric.mod.json wasn't found.
|
||||
if (Files.notExists(path)) {
|
||||
final @Nullable ModMetadataFile modMetadata = ModMetadataFiles.fromDirectory(directory);
|
||||
|
||||
if (modMetadata != null) {
|
||||
return new ModMetadataFabricModJson(modMetadata, new FabricModJsonSource.DirectorySource(directory));
|
||||
}
|
||||
}
|
||||
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.DirectorySource(directory));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static FabricModJson createFromSourceSetsNullable(SourceSet... sourceSets) throws IOException {
|
||||
final File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, sourceSets);
|
||||
|
||||
@@ -43,6 +43,11 @@ public class ProgressGroup implements Closeable {
|
||||
this.progressLoggerFactory = ((ProjectInternal) project).getServices().get(ProgressLoggerFactory.class);
|
||||
}
|
||||
|
||||
public ProgressGroup(String name, ProgressLoggerFactory progressLoggerFactory) {
|
||||
this.name = name;
|
||||
this.progressLoggerFactory = progressLoggerFactory;
|
||||
}
|
||||
|
||||
private void start() {
|
||||
this.progressGroup = this.progressLoggerFactory.newOperation(name).setDescription(name);
|
||||
this.progressGroup.started();
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.api.ModSettings;
|
||||
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
|
||||
|
||||
@@ -236,6 +237,15 @@ public final class SourceSetHelper {
|
||||
Objects.requireNonNull(sourceSet);
|
||||
Objects.requireNonNull(path);
|
||||
|
||||
final Project project = getSourceSetProject(sourceSet);
|
||||
final LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
|
||||
if (extension.isConfigurationCacheActive()) {
|
||||
// TODO config cache, figure this out
|
||||
project.getLogger().warn("Unable to find resource ({}) in source set ({}) when configuration cache is active", path, sourceSet.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return sourceSet.getResources()
|
||||
.matching(patternFilterable -> patternFilterable.include(path))
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.util.gradle.daemon;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.cache.FileLockManager;
|
||||
import org.gradle.internal.file.Chmod;
|
||||
import org.gradle.internal.remote.internal.RemoteConnection;
|
||||
import org.gradle.internal.remote.internal.inet.TcpOutgoingConnector;
|
||||
import org.gradle.internal.serialize.Serializers;
|
||||
import org.gradle.internal.service.ServiceRegistry;
|
||||
import org.gradle.invocation.DefaultGradle;
|
||||
import org.gradle.launcher.daemon.client.DaemonClientConnection;
|
||||
import org.gradle.launcher.daemon.client.StopDispatcher;
|
||||
import org.gradle.launcher.daemon.protocol.DaemonMessageSerializer;
|
||||
import org.gradle.launcher.daemon.protocol.Message;
|
||||
import org.gradle.launcher.daemon.protocol.StopWhenIdle;
|
||||
import org.gradle.launcher.daemon.registry.DaemonInfo;
|
||||
import org.gradle.launcher.daemon.registry.DaemonRegistry;
|
||||
import org.gradle.launcher.daemon.registry.PersistentDaemonRegistry;
|
||||
import org.gradle.util.GradleVersion;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This uses a vast amount of Gradle internal APIs, however this is only used when the JVM is in an unrecoverable state.
|
||||
* The alternative is to kill the JVM, using System.exit, which is not ideal and leaves scary messages in the log.
|
||||
*/
|
||||
public final class DaemonUtils {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DaemonUtils.class);
|
||||
|
||||
private DaemonUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the Gradle daemon to stop when it becomes idle.
|
||||
*/
|
||||
public static void tryStopGradleDaemon(Project project) {
|
||||
try {
|
||||
stopWhenIdle(project);
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to request the Gradle demon to stop", t);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static boolean stopWhenIdle(Project project) {
|
||||
DaemonInfo daemonInfo = findCurrentDaemon(project);
|
||||
|
||||
if (daemonInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoteConnection<Message> connection = null;
|
||||
|
||||
try {
|
||||
// Gradle communicates with the daemon using a TCP connection, and a custom binary protocol.
|
||||
// We connect to the daemon using the daemon's address, and then send a StopWhenIdle message.
|
||||
connection = new TcpOutgoingConnector().connect(daemonInfo.getAddress()).create(Serializers.stateful(DaemonMessageSerializer.create(null)));
|
||||
DaemonClientConnection daemonClientConnection = new DaemonClientConnection(connection, daemonInfo, null);
|
||||
new StopDispatcher().dispatch(daemonClientConnection, new StopWhenIdle(UUID.randomUUID(), daemonInfo.getToken()));
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.stop();
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.warn("Requested Gradle daemon to stop on exit.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static DaemonInfo findCurrentDaemon(Project project) {
|
||||
// Gradle maintains a list of running daemons in a registry.bin file.
|
||||
final Path registryBin = project.getGradle().getGradleUserHomeDir().toPath().resolve("daemon").resolve(GradleVersion.current().getVersion()).resolve("registry.bin");
|
||||
project.getLogger().lifecycle("Looking for daemon in: " + registryBin);
|
||||
|
||||
// We can use a PersistentDaemonRegistry to read this
|
||||
final ServiceRegistry services = ((DefaultGradle) project.getGradle()).getServices();
|
||||
final DaemonRegistry registry = new PersistentDaemonRegistry(registryBin.toFile(), services.get(FileLockManager.class), services.get(Chmod.class));
|
||||
|
||||
final long pid = ProcessHandle.current().pid();
|
||||
final List<DaemonInfo> runningDaemons = registry.getAll();
|
||||
|
||||
LOGGER.info("Found {} running Gradle daemons in registry: {}", runningDaemons.size(), registryBin);
|
||||
|
||||
for (DaemonInfo daemonInfo : runningDaemons) {
|
||||
if (daemonInfo.getPid() == pid) {
|
||||
return daemonInfo;
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.warn("Could not find current process in daemon registry: {}", registryBin);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN" packages="com.mojang.util,net.minecrell.terminalconsole.util">
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
|
||||
<!-- System out -->
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.test.integration
|
||||
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
import net.fabricmc.loom.test.util.GradleProjectTestTrait
|
||||
|
||||
import static net.fabricmc.loom.test.LoomTestConstants.PRE_RELEASE_GRADLE
|
||||
import static org.gradle.testkit.runner.TaskOutcome.FAILED
|
||||
|
||||
class ConfigurationCacheTest extends Specification implements GradleProjectTestTrait {
|
||||
@Unroll
|
||||
def "Configuration cache (task #task)"() {
|
||||
setup:
|
||||
def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE)
|
||||
gradle.buildGradle << """
|
||||
dependencies {
|
||||
minecraft 'com.mojang:minecraft:1.20.4'
|
||||
mappings 'net.fabricmc:yarn:1.20.4+build.3:v2'
|
||||
modImplementation 'net.fabricmc:fabric-loader:0.15.6'
|
||||
modImplementation 'net.fabricmc.fabric-api:fabric-api:0.95.4+1.20.4'
|
||||
}
|
||||
""".stripIndent()
|
||||
when:
|
||||
def result = gradle.run(task: task, configurationCache: true, isloatedProjects: false)
|
||||
def result2 = gradle.run(task: task, configurationCache: true, isloatedProjects: false)
|
||||
|
||||
then:
|
||||
result.task(":${task}").outcome != FAILED
|
||||
result2.task(":${task}").outcome != FAILED
|
||||
|
||||
where:
|
||||
task | _
|
||||
"help" | _
|
||||
"configureClientLaunch" | _
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.test.integration
|
||||
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
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 DaemonShutdownTest extends Specification implements GradleProjectTestTrait {
|
||||
@Unroll
|
||||
def "custom decompiler (gradle #version)"() {
|
||||
setup:
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
gradle.buildSrc("stopDaemon")
|
||||
gradle.buildGradle << '''
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.20.4"
|
||||
mappings "net.fabricmc:yarn:1.20.4+build.3:v2"
|
||||
}
|
||||
'''
|
||||
when:
|
||||
def result = gradle.run(task: "help")
|
||||
|
||||
then:
|
||||
result.task(":help").outcome == SUCCESS
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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.test.integration.buildSrc.stopDaemon
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import org.gradle.api.JavaVersion
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.cache.FileLockManager
|
||||
import org.gradle.internal.concurrent.DefaultExecutorFactory
|
||||
import org.gradle.internal.concurrent.ExecutorFactory
|
||||
import org.gradle.internal.file.Chmod
|
||||
import org.gradle.internal.nativeintegration.services.NativeServices
|
||||
import org.gradle.internal.remote.internal.inet.InetAddressFactory
|
||||
import org.gradle.internal.service.ServiceRegistry
|
||||
import org.gradle.invocation.DefaultGradle
|
||||
import org.gradle.launcher.daemon.configuration.DaemonParameters
|
||||
import org.gradle.launcher.daemon.context.DefaultDaemonContext
|
||||
import org.gradle.launcher.daemon.protocol.DaemonMessageSerializer
|
||||
import org.gradle.launcher.daemon.protocol.Finished
|
||||
import org.gradle.launcher.daemon.protocol.Message
|
||||
import org.gradle.launcher.daemon.protocol.StopWhenIdle
|
||||
import org.gradle.launcher.daemon.protocol.Success
|
||||
import org.gradle.launcher.daemon.registry.DaemonInfo
|
||||
import org.gradle.launcher.daemon.registry.PersistentDaemonRegistry
|
||||
import org.gradle.launcher.daemon.server.DaemonTcpServerConnector
|
||||
import org.gradle.launcher.daemon.server.DefaultDaemonConnection
|
||||
import org.gradle.launcher.daemon.server.IncomingConnectionHandler
|
||||
import org.gradle.launcher.daemon.server.SynchronizedDispatchConnection
|
||||
import org.gradle.launcher.daemon.server.api.DaemonStateControl
|
||||
import org.gradle.util.GradleVersion
|
||||
|
||||
import net.fabricmc.loom.util.gradle.daemon.DaemonUtils
|
||||
|
||||
/**
|
||||
* An integration test that runs a dummy gradle daemon TCP server, to test the daemon shutdown mechanism.
|
||||
*/
|
||||
class TestPlugin implements Plugin<Project> {
|
||||
static ExecutorFactory executorFactory = new DefaultExecutorFactory()
|
||||
|
||||
@Override
|
||||
void apply(Project project) {
|
||||
final ServiceRegistry services = ((DefaultGradle) project.getGradle()).getServices()
|
||||
final Path registryBin = project.getGradle().getGradleUserHomeDir().toPath()
|
||||
.resolve("daemon")
|
||||
.resolve(GradleVersion.current().getVersion())
|
||||
.resolve("registry.bin")
|
||||
|
||||
// Start a dummy daemon process
|
||||
def handler = new TestIncomingConnectionHandler()
|
||||
def server = new DaemonTcpServerConnector(executorFactory, new InetAddressFactory(), DaemonMessageSerializer.create(null))
|
||||
def address = server.start(handler, handler)
|
||||
|
||||
// Write it in the registry
|
||||
def registry = new PersistentDaemonRegistry(registryBin.toFile(), services.get(FileLockManager.class), services.get(Chmod.class))
|
||||
def daemonInfo = new DaemonInfo(address, createDaemonContext(), "token".bytes, DaemonStateControl.State.Busy)
|
||||
registry.store(daemonInfo)
|
||||
|
||||
// When we get a connection, wait for a stop message and process it by responding with a success message
|
||||
def future = handler.daemonConnection.thenAccept { it.waitForAndProcessStop() }
|
||||
|
||||
// Stop the daemon
|
||||
def result = DaemonUtils.stopWhenIdle(project)
|
||||
|
||||
// Wait for the connection to be processed, this should have already happened, as the above call is blocking
|
||||
future.join()
|
||||
|
||||
// And clean up
|
||||
server.stop()
|
||||
registry.remove(address)
|
||||
|
||||
if (!result) {
|
||||
throw new IllegalStateException("Failed to stop daemon")
|
||||
}
|
||||
}
|
||||
|
||||
// Thanks groovy for allowing me to do this :D
|
||||
static DefaultDaemonContext createDaemonContext() {
|
||||
int constructorArgsCount = DefaultDaemonContext.class.getConstructors()[0].getParameterCount()
|
||||
|
||||
if (constructorArgsCount == 10) {
|
||||
// Gradle 8.9+ adds a JavaVersion and NativeServicesMode parameter to the constructor
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
return new DefaultDaemonContext(
|
||||
UUID.randomUUID().toString(),
|
||||
new File("."),
|
||||
JavaVersion.current(),
|
||||
new File("."),
|
||||
ProcessHandle.current().pid(),
|
||||
0,
|
||||
List.of(),
|
||||
false,
|
||||
NativeServices.NativeServicesMode.NOT_SET,
|
||||
DaemonParameters.Priority.NORMAL
|
||||
)
|
||||
}
|
||||
|
||||
return new DefaultDaemonContext(
|
||||
UUID.randomUUID().toString(),
|
||||
new File("."),
|
||||
new File("."),
|
||||
ProcessHandle.current().pid(),
|
||||
0,
|
||||
List.of(),
|
||||
false,
|
||||
DaemonParameters.Priority.NORMAL
|
||||
)
|
||||
}
|
||||
|
||||
class TestIncomingConnectionHandler implements IncomingConnectionHandler, Runnable, AutoCloseable {
|
||||
CompletableFuture<TestDaemonConnection> daemonConnection = new CompletableFuture<>()
|
||||
|
||||
@Override
|
||||
void handle(SynchronizedDispatchConnection<Message> connection) {
|
||||
if (daemonConnection.isDone()) {
|
||||
throw new IllegalStateException("Already have a connection?")
|
||||
}
|
||||
|
||||
daemonConnection.complete(new TestDaemonConnection(connection, executorFactory))
|
||||
}
|
||||
|
||||
@Override
|
||||
void run() {
|
||||
throw new IllegalStateException("Should not be called")
|
||||
}
|
||||
|
||||
@Override
|
||||
void close() throws Exception {
|
||||
if (daemonConnection.isDone()) {
|
||||
daemonConnection.get().stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestDaemonConnection extends DefaultDaemonConnection {
|
||||
SynchronizedDispatchConnection<Message> dispatchConnection
|
||||
|
||||
TestDaemonConnection(SynchronizedDispatchConnection<Message> connection, ExecutorFactory executorFactory) {
|
||||
super(connection, executorFactory)
|
||||
this.dispatchConnection = connection
|
||||
}
|
||||
|
||||
void waitForAndProcessStop() {
|
||||
def response = receive(1, TimeUnit.MINUTES)
|
||||
|
||||
if (!(response instanceof StopWhenIdle)) {
|
||||
throw new IllegalStateException("Expected StopWhenIdle, got ${response}")
|
||||
}
|
||||
println("Received stop message ${response}")
|
||||
|
||||
dispatchConnection.dispatchAndFlush(new Success("Ok"))
|
||||
response = receive(1, TimeUnit.MINUTES)
|
||||
|
||||
if (!(response instanceof Finished)) {
|
||||
throw new IllegalStateException("Expected Finished, got ${response}")
|
||||
}
|
||||
|
||||
println("Received finished message ${response}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,6 +184,23 @@ class ArtifactMetadataTest extends Specification {
|
||||
"1.4" | "2.4"
|
||||
}
|
||||
|
||||
def "known indy BSMs"() {
|
||||
given:
|
||||
def zip = createZip(entries)
|
||||
when:
|
||||
def metadata = createMetadata(zip)
|
||||
then:
|
||||
knownBSMs == metadata.knownIdyBsms()
|
||||
where:
|
||||
knownBSMs | entries
|
||||
[] | ["fabric.mod.json": "{}"] // Default
|
||||
["com/example/Class"] | ["META-INF/MANIFEST.MF": manifest("Fabric-Loom-Known-Indy-BSMS", "com/example/Class")] // single bsm
|
||||
[
|
||||
"com/example/Class",
|
||||
"com/example/Another"
|
||||
] | ["META-INF/MANIFEST.MF": manifest("Fabric-Loom-Known-Indy-BSMS", "com/example/Class,com/example/Another")] // two bsms
|
||||
}
|
||||
|
||||
private static Path createMod(String loomVersion, String remapType) {
|
||||
return createZip(["fabric.mod.json": "{}", "META-INF/MANIFEST.MF": manifest(["Fabric-Loom-Version": loomVersion, "Fabric-Loom-Mixin-Remap-Type": remapType])])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.test.unit
|
||||
|
||||
import java.nio.file.FileSystem
|
||||
import java.nio.file.FileSystemAlreadyExistsException
|
||||
import java.nio.file.FileSystemException
|
||||
import java.nio.file.FileSystemNotFoundException
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
// Test to prove https://bugs.openjdk.org/browse/JDK-8291712
|
||||
// If this test starts failing on a new JDK, it is likely that the bug has been fixed!
|
||||
class ClosedZipFSReproducer extends Specification {
|
||||
def "JDK-8291712"() {
|
||||
when:
|
||||
Path tempDir = Files.createTempDirectory("test")
|
||||
Path zipFile = tempDir.resolve("example.zip")
|
||||
|
||||
// Create a new ZipFileSystem, and prevent it from being written on close
|
||||
def fs = openZipFS(zipFile, true)
|
||||
Files.writeString(fs.getPath("test.txt"), "Hello, World!")
|
||||
|
||||
// Before we close the ZipFS do something to prevent the zip from being written on close
|
||||
// E.G lock the file
|
||||
Files.delete(zipFile)
|
||||
Files.createDirectories(zipFile)
|
||||
Files.createFile(zipFile.resolve("lock"))
|
||||
|
||||
try {
|
||||
fs.close()
|
||||
throw new IllegalStateException("Expected FileSystemException")
|
||||
} catch (FileSystemException ignored) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Remove the "lock"
|
||||
Files.delete(zipFile.resolve("lock"))
|
||||
|
||||
// We would expect a new FileSystem to be created, but instead we get the old one
|
||||
// That is in a broken state
|
||||
fs = openZipFS(zipFile, true)
|
||||
|
||||
then:
|
||||
!fs.isOpen()
|
||||
}
|
||||
|
||||
private static FileSystem openZipFS(Path path, boolean create) throws IOException {
|
||||
URI uri = toJarUri(path)
|
||||
try {
|
||||
return FileSystems.getFileSystem(uri)
|
||||
} catch (FileSystemNotFoundException e) {
|
||||
try {
|
||||
return FileSystems.newFileSystem(uri, create ? Collections.singletonMap("create", "true") : Collections.emptyMap())
|
||||
} catch (FileSystemAlreadyExistsException f) {
|
||||
return FileSystems.getFileSystem(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static URI toJarUri(Path path) {
|
||||
URI uri = path.toUri()
|
||||
|
||||
try {
|
||||
return new URI("jar:" + uri.getScheme(), uri.getHost(), uri.getPath(), uri.getFragment())
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("can't convert path " + path + " to uri", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import com.google.gson.JsonObject
|
||||
import spock.lang.Specification
|
||||
|
||||
import net.fabricmc.loom.util.Checksum
|
||||
import net.fabricmc.loom.util.FileSystemUtil
|
||||
import net.fabricmc.loom.util.Pair
|
||||
import net.fabricmc.loom.util.ZipReprocessorUtil
|
||||
import net.fabricmc.loom.util.ZipUtils
|
||||
@@ -213,4 +214,30 @@ class ZipUtilsTest extends Specification {
|
||||
then:
|
||||
transformed.get("test").asString == "THIS IS A TEST OF TRANSFORMING"
|
||||
}
|
||||
|
||||
// Also see: ClosedZipFSReproducer
|
||||
def "unrecoverable error"() {
|
||||
given:
|
||||
def dir = File.createTempDir()
|
||||
def zip = File.createTempFile("loom-zip-test", ".zip").toPath()
|
||||
new File(dir, "test.json").text = """
|
||||
{
|
||||
"test": "This is a test of transforming"
|
||||
}
|
||||
"""
|
||||
ZipUtils.pack(dir.toPath(), zip)
|
||||
|
||||
when:
|
||||
ZipUtils.transformJson(JsonObject.class, zip, "test.json") { json ->
|
||||
// Before we close the ZipFS do something to prevent the zip from being written on close
|
||||
// E.G lock the file
|
||||
Files.delete(zip)
|
||||
Files.createDirectories(zip)
|
||||
Files.createFile(zip.resolve("lock"))
|
||||
|
||||
json
|
||||
}
|
||||
then:
|
||||
thrown FileSystemUtil.UnrecoverableZipException
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ trait GradleProjectTestTrait {
|
||||
private String gradleHomeDir
|
||||
private String warningMode
|
||||
private boolean useBuildSrc
|
||||
private boolean enableDebugging = true
|
||||
|
||||
BuildResult run(Map options) {
|
||||
// Setup the system props to tell loom that its running in a test env
|
||||
@@ -165,6 +166,14 @@ trait GradleProjectTestTrait {
|
||||
args << options.task
|
||||
}
|
||||
|
||||
if (options.configurationCache || System.getenv("LOOM_TEST_CONFIGURATION_CACHE") != null) {
|
||||
args << "--configuration-cache"
|
||||
}
|
||||
|
||||
if (options.isloatedProjects) {
|
||||
args << "-Dorg.gradle.unsafe.isolated-projects=true"
|
||||
}
|
||||
|
||||
args.addAll(options.tasks ?: [])
|
||||
|
||||
args << "--stacktrace"
|
||||
@@ -179,6 +188,10 @@ trait GradleProjectTestTrait {
|
||||
writeBuildSrcDeps(runner)
|
||||
}
|
||||
|
||||
if (options.disableDebugging) {
|
||||
enableDebugging = false
|
||||
}
|
||||
|
||||
return options.expectFailure ? runner.buildAndFail() : runner.build()
|
||||
}
|
||||
|
||||
@@ -188,7 +201,7 @@ trait GradleProjectTestTrait {
|
||||
.withPluginClasspath()
|
||||
.withGradleVersion(gradleVersion)
|
||||
.forwardOutput()
|
||||
.withDebug(true)
|
||||
.withDebug(enableDebugging)
|
||||
}
|
||||
|
||||
File getProjectDir() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
plugins {
|
||||
id 'dev.architectury.loom'
|
||||
id 'com.github.johnrengelman.shadow' version '7.0.0'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
@@ -83,9 +82,8 @@ loom {
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
task shadowJar(type: Jar) {
|
||||
archiveClassifier.set("universal-dev")
|
||||
configurations = []
|
||||
|
||||
from(sourceSets["main"].output)
|
||||
from(sourceSets["mixin"].output)
|
||||
|
||||
Reference in New Issue
Block a user