Initial project isolation support (#1215)

* Enable project isolation

* Add test for COD

* Make spotless happy
This commit is contained in:
modmuss
2024-11-14 21:56:20 +00:00
committed by GitHub
parent e387514ff7
commit 1f28935221
20 changed files with 182 additions and 101 deletions

View File

@@ -3,9 +3,12 @@ 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;
@@ -13,7 +16,7 @@ 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 class LoomGradlePluginBootstrap implements Plugin<PluginAware> {
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;
@@ -21,12 +24,15 @@ public class LoomGradlePluginBootstrap implements Plugin<PluginAware> {
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 (project.findProperty("fabric.loom.skip-env-validation") == null) {
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.");

View File

@@ -121,4 +121,6 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
Collection<LayeredMappingsFactory> getLayeredMappingFactories();
boolean isConfigurationCacheActive();
boolean isProjectIsolationActive();
}

View File

@@ -159,7 +159,7 @@ public abstract class FabricApiExtension {
settings.getModId().convention(getProject().provider(() -> {
try {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(dataGenSourceSet);
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()");

View File

@@ -84,7 +84,8 @@ public record SpecContextImpl(List<FabricModJson> modDependencies, List<FabricMo
}
}
if (!GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS)) {
// TODO provide a project isolated way of doing this.
if (!extension.isProjectIsolationActive() && !GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS)) {
// Add all the dependent projects
for (Project dependentProject : getDependentProjects(project).toList()) {
mods.addAll(fmjCache.computeIfAbsent(dependentProject.getPath(), $ -> {

View File

@@ -45,6 +45,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.library.MinecraftLibr
import net.fabricmc.loom.configuration.providers.minecraft.library.processors.RuntimeLog4jLibraryProcessor;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Platform;
import net.fabricmc.loom.util.gradle.GradleUtils;
public class MinecraftLibraryProvider {
private static final Platform platform = Platform.CURRENT;
@@ -124,7 +125,7 @@ public class MinecraftLibraryProvider {
}
private JavaVersion getTargetRuntimeJavaVersion() {
final Object property = project.findProperty(Constants.Properties.RUNTIME_JAVA_COMPATIBILITY_VERSION);
final Object property = GradleUtils.getProperty(project, Constants.Properties.RUNTIME_JAVA_COMPATIBILITY_VERSION);
if (property != null) {
// This is very much a last ditch effort to allow users to set the runtime java version

View File

@@ -59,6 +59,13 @@ public abstract class SandboxConfiguration implements Runnable {
@Override
public void run() {
LoomGradleExtension extension = LoomGradleExtension.get(getProject());
if (extension.isProjectIsolationActive()) {
LOGGER.debug("Skipping sandbox configuration as project isolation is enabled.");
return;
}
if (getProject().findProperty(Constants.Properties.SANDBOX) == null) {
LOGGER.debug("No fabric sandbox property set");
return;

View File

@@ -295,7 +295,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
@Override
public String getModVersion() {
try {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(SourceSetHelper.getMainSourceSet(getProject()));
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), SourceSetHelper.getMainSourceSet(getProject()));
if (fabricModJson == null) {
throw new RuntimeException("Could not find a fabric.mod.json file in the main sourceset");

View File

@@ -110,6 +110,10 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
if (refreshDeps) {
project.getLogger().lifecycle("Refresh dependencies is in use, loom will be significantly slower.");
}
if (isolatedProjectsActive) {
project.getLogger().lifecycle("Isolated projects is enabled, Loom support is highly experimental, not all features will be enabled.");
}
}
@Override
@@ -297,4 +301,9 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
public boolean isConfigurationCacheActive() {
return configurationCacheActive;
}
@Override
public boolean isProjectIsolationActive() {
return isolatedProjectsActive;
}
}

View File

@@ -119,7 +119,8 @@ public abstract class RemapTaskConfiguration implements Runnable {
final Jar jarTask = (Jar) getTasks().getByName(JavaPlugin.JAR_TASK_NAME);
configuration.getArtifacts().removeIf(artifact -> {
// if the artifact is built by the jar task, and has the same output path.
return artifact.getFile().getAbsolutePath().equals(jarTask.getArchiveFile().get().getAsFile().getAbsolutePath()) && artifact.getBuildDependencies().getDependencies(null).contains(jarTask);
return artifact.getFile().getAbsolutePath().equals(jarTask.getArchiveFile().get().getAsFile().getAbsolutePath())
&& (extension.isProjectIsolationActive() || artifact.getBuildDependencies().getDependencies(null).contains(jarTask));
});
}
});

View File

@@ -59,8 +59,6 @@ import net.fabricmc.tinyremapper.IMappingProvider;
public class MixinAPMappingService extends Service<MixinAPMappingService.Options> {
public static final ServiceType<Options, MixinAPMappingService> TYPE = new ServiceType<>(Options.class, MixinAPMappingService.class);
// TODO look into seeing if we can make this an option, it likely breaks project isolation.
private static final boolean INCLUDE_CROSS_PROJECT_MAPPINGS = true;
// Again look into what the result of changing this would be.
private static final boolean USE_ALL_SOURCE_SETS = true;
private static final Logger LOGGER = LoggerFactory.getLogger(MixinAPMappingService.class);
@@ -105,7 +103,8 @@ public class MixinAPMappingService extends Service<MixinAPMappingService.Options
}
};
if (!INCLUDE_CROSS_PROJECT_MAPPINGS) {
if (thisExtension.isProjectIsolationActive()) {
// TODO provide a project isolated way of remapping with dependency mixin mapping
processProject.accept(thisProject);
} else {
GradleUtils.allLoomProjects(thisProject.getGradle(), project -> {

View File

@@ -37,6 +37,7 @@ import java.util.Optional;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
@@ -107,15 +108,15 @@ public final class FabricModJsonFactory {
}
@Nullable
public static FabricModJson createFromSourceSetsNullable(SourceSet... sourceSets) throws IOException {
final File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, sourceSets);
public static FabricModJson createFromSourceSetsNullable(Project project, SourceSet... sourceSets) throws IOException {
final File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, project, sourceSets);
if (file == null) {
return null;
}
try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.SourceSetSource(sourceSets));
return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.SourceSetSource(project, sourceSets));
} catch (JsonSyntaxException e) {
LOGGER.warn("Failed to parse fabric.mod.json: {}", file.getAbsolutePath());
return null;

View File

@@ -48,7 +48,7 @@ public class FabricModJsonHelpers {
}
try {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(sourceSets.toArray(SourceSet[]::new));
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(project, sourceSets.toArray(SourceSet[]::new));
if (fabricModJson != null) {
return List.of(fabricModJson);

View File

@@ -30,6 +30,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.util.ZipUtils;
@@ -56,14 +57,14 @@ public interface FabricModJsonSource {
}
}
record SourceSetSource(SourceSet... sourceSets) implements FabricModJsonSource {
record SourceSetSource(Project project, SourceSet... sourceSets) implements FabricModJsonSource {
@Override
public byte[] read(String path) throws IOException {
return Files.readAllBytes(findFile(path).toPath());
}
private File findFile(String path) throws IOException {
final File file = SourceSetHelper.findFirstFileInResource(path, sourceSets);
final File file = SourceSetHelper.findFirstFileInResource(path, project, sourceSets);
if (file == null) {
throw new FileNotFoundException("Could not find: " + path);

View File

@@ -32,6 +32,8 @@ import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.invocation.Gradle;
import org.gradle.api.provider.Provider;
import net.fabricmc.loom.LoomGradleExtension;
public final class GradleUtils {
private GradleUtils() {
}
@@ -61,6 +63,13 @@ public final class GradleUtils {
}
public static Provider<Boolean> getBooleanPropertyProvider(Project project, String key) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.isProjectIsolationActive()) {
// TODO write a custom property parser for isolated projects
return project.provider(() -> false);
}
// Works around https://github.com/gradle/gradle/issues/23572
return project.provider(() -> {
final Object value = project.findProperty(key);
@@ -81,6 +90,17 @@ public final class GradleUtils {
return getBooleanPropertyProvider(project, key).getOrElse(false);
}
public static Object getProperty(Project project, String key) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.isProjectIsolationActive()) {
// TODO write a custom property parser for isolated projects
return null;
}
return project.findProperty(key);
}
// A hack to include the given file in the configuration cache input
// this ensures that configuration cache is invalidated when the file changes
public static File configurationInputFile(Project project, File file) {

View File

@@ -268,11 +268,11 @@ public final class SourceSetHelper {
}
@Nullable
public static File findFileInResource(SourceSet sourceSet, String path) {
public static File findFileInResource(Project project, SourceSet sourceSet, String path) {
Objects.requireNonNull(project);
Objects.requireNonNull(sourceSet);
Objects.requireNonNull(path);
final Project project = getSourceSetProject(sourceSet);
final LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.isConfigurationCacheActive()) {
@@ -298,9 +298,9 @@ public final class SourceSetHelper {
}
@Nullable
public static File findFirstFileInResource(String path, SourceSet... sourceSets) {
public static File findFirstFileInResource(String path, Project project, SourceSet... sourceSets) {
for (SourceSet sourceSet : sourceSets) {
File file = findFileInResource(sourceSet, path);
File file = findFileInResource(project, sourceSet, path);
if (file != null) {
return file;

View File

@@ -51,6 +51,7 @@ class MultiMcVersionTest extends Specification implements GradleProjectTestTrait
def "build (gradle #version)"() {
setup:
def gradle = gradleProject(project: "multi-mc-versions", version: version)
gradle.buildSrc("multiMcVersions", false)
versions.forEach {
// Make dir as its now required by Gradle
@@ -58,10 +59,9 @@ class MultiMcVersionTest extends Specification implements GradleProjectTestTrait
}
when:
def result = gradle.run(tasks: "build")
def result = gradle.run(tasks: "build", isloatedProjects: true, configureOnDemand: true)
then:
result.task(":build").outcome == SUCCESS
versions.forEach {
result.task(":$it:build").outcome == SUCCESS
}
@@ -69,4 +69,35 @@ class MultiMcVersionTest extends Specification implements GradleProjectTestTrait
where:
version << STANDARD_TEST_VERSIONS
}
@Unroll
def "configure on demand (gradle #version)"() {
setup:
def gradle = gradleProject(project: "multi-mc-versions", version: version)
gradle.buildSrc("multiMcVersions", false)
versions.forEach {
// Make dir as its now required by Gradle
new File(gradle.projectDir, it).mkdir()
}
when:
def result = gradle.run(
tasks: ":fabric-1.19.3:build",
isloatedProjects: true,
configureOnDemand: true,
// See: https://github.com/gradle/gradle/issues/30401
// By default parallel configuration of all projects is preferred.
args: [
"-Dorg.gradle.internal.isolated-projects.configure-on-demand.tasks=true"
])
then:
result.task(":fabric-1.19.3:build").outcome == SUCCESS
// Ensure that loom is only loaded once.
result.output.count("Fabric Loom:") == 1
where:
version << STANDARD_TEST_VERSIONS
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.multiMcVersions
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.BasePluginExtension
class TestPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.group = "com.example"
project.version = "1.0.0"
project.getExtensions().configure(BasePluginExtension.class) {
it.archivesName = project.rootProject.isolated.name + "-" + project.name
}
def minecraftVersion = project.name.substring(7)
project.getDependencies().add("minecraft", "com.mojang:minecraft:$minecraftVersion")
project.getDependencies().add("mappings", "net.fabricmc:yarn:$minecraftVersion+build.1:v2")
project.getDependencies().add("modImplementation", "net.fabricmc:fabric-loader:0.16.9")
}
}

View File

@@ -180,6 +180,10 @@ trait GradleProjectTestTrait {
args << "-Dorg.gradle.unsafe.isolated-projects=true"
}
if (options.configureOnDemand) {
args << "--configure-on-demand"
}
args.addAll(options.tasks ?: [])
args << "--stacktrace"
@@ -278,7 +282,7 @@ trait GradleProjectTestTrait {
return file
}
void buildSrc(String name) {
void buildSrc(String name, boolean apply = true) {
useBuildSrc = true
def buildSrcDir = new File(projectDir, "buildSrc")
@@ -305,24 +309,26 @@ trait GradleProjectTestTrait {
rootProject.name='loom-test-plugin'
'''
// Patch the new plugin into the end of the plugins block
def matcher = buildGradle.text =~ /(?s)plugins \{(?<ids>.*?)}/
assert matcher.find()
def ids = matcher.group("ids")
def pluginBlock = """
plugins {
${ids}
id 'loom-test-plugin'
}
"""
buildGradle.text = buildGradle.text.replaceAll("(?s)(plugins \\{.*?})", pluginBlock)
def sourceSrc = new File("src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/" + name)
def targetSrc = new File(buildSrcDir, "src/main/groovy/net/fabricmc/loom/test/integration/buildSrc/" + name)
FileUtils.copyDirectory(sourceSrc, targetSrc)
if (apply) {
// Patch the new plugin into the end of the plugins block
def matcher = buildGradle.text =~ /(?s)plugins \{(?<ids>.*?)}/
assert matcher.find()
def ids = matcher.group("ids")
def pluginBlock = """
plugins {
${ids}
id 'loom-test-plugin'
}
"""
buildGradle.text = buildGradle.text.replaceAll("(?s)(plugins \\{.*?})", pluginBlock)
}
}
void writeBuildSrcDeps(GradleRunner runner) {

View File

@@ -1,64 +0,0 @@
import groovy.json.JsonSlurper
plugins {
id "java"
id 'fabric-loom' apply false
}
allprojects {
group = project.maven_group
version = project.mod_version
}
ext {
yarnMeta = new JsonSlurper().parse(new URL("https://meta.fabricmc.net/v2/versions/yarn"))
}
def getMappingVersion(String mcVersion) {
return rootProject.yarnMeta.find { it.gameVersion == mcVersion }.version
}
subprojects {
apply plugin: "fabric-loom"
base {
archivesName = rootProject.name + "-" + project.name
}
def minecraft_version = project.name.substring(7)
def yarn_mappings = getMappingVersion(minecraft_version)
dependencies {
// To change the versions see the gradle.properties files
minecraft "com.mojang:minecraft:$minecraft_version"
mappings "net.fabricmc:yarn:$yarn_mappings:v2"
modImplementation "net.fabricmc:fabric-loader:$loader_version"
}
jar {
archiveClassifier.set "dev"
}
// Just use the source from the root project
compileJava {
source(sourceSets.main.java.srcDirs)
}
processResources {
from(rootProject.sourceSets.main.resources)
def version = project.version
def mcVersion = minecraft_version
def loaderVersion = project.loader_version
inputs.property 'version', version
inputs.property 'minecraft_version', mcVersion
inputs.property 'loader_version', loaderVersion
filesMatching("fabric.mod.json") {
expand 'version': version, 'minecraft_version': mcVersion, 'loader_version': loaderVersion
}
}
}
compileJava.enabled = false
processResources.enabled = false

View File

@@ -1,3 +1,16 @@
plugins {
id 'fabric-loom' apply false
}
gradle.lifecycle.beforeProject {
if (it.rootProject == it) {
return
}
apply plugin: 'fabric-loom'
apply plugin: 'loom-test-plugin'
}
rootProject.name = "multi-mc-versions"
// Yes lot of mc version