Skip to content

Commit

Permalink
Initial project isolation support (#1215)
Browse files Browse the repository at this point in the history
* Enable project isolation

* Add test for COD

* Make spotless happy
  • Loading branch information
modmuss50 authored Nov 14, 2024
1 parent e387514 commit 1f28935
Show file tree
Hide file tree
Showing 20 changed files with 182 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,36 @@
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.configuration.BuildFeatures;
import org.gradle.api.plugins.PluginAware;
import org.gradle.util.GradleVersion;

/**
* This bootstrap is compiled against a minimal gradle API and java 8, this allows us to show a nice error to users who run on unsupported configurations.
*/
@SuppressWarnings("unused")
public 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;

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.");
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/net/fabricmc/loom/LoomGradleExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,6 @@ default List<Path> getMinecraftJars(MappingsNamespace mappingsNamespace) {
Collection<LayeredMappingsFactory> getLayeredMappingFactories();

boolean isConfigurationCacheActive();

boolean isProjectIsolationActive();
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public void configureDataGeneration(Action<DataGenerationSettings> action) {

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()");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ private static List<FabricModJson> getDependentMods(Project project, Map<String,
}
}

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(), $ -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
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;
Expand Down Expand Up @@ -124,7 +125,7 @@ private List<Library> processLibraries(List<Library> libraries) {
}

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ public SetProperty<String> getKnownIndyBsms() {
@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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ public LoomGradleExtensionImpl(Project project, LoomFiles files) {
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
Expand Down Expand Up @@ -297,4 +301,9 @@ protected <T extends IntermediateMappingsProvider> void configureIntermediateMap
public boolean isConfigurationCacheActive() {
return configurationCacheActive;
}

@Override
public boolean isProjectIsolationActive() {
return isolatedProjectsActive;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ public void run() {
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));
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@
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);
Expand Down Expand Up @@ -105,7 +103,8 @@ public static Provider<List<Options>> createOptions(Project thisProject, Provide
}
};

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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

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;
Expand Down Expand Up @@ -107,15 +108,15 @@ public static Optional<FabricModJson> createFromZipOptional(Path zipPath) {
}

@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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static List<FabricModJson> getModsInProject(Project project) {
}

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;
Expand All @@ -56,14 +57,14 @@ public byte[] read(String path) throws IOException {
}
}

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);
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import org.gradle.api.invocation.Gradle;
import org.gradle.api.provider.Provider;

import net.fabricmc.loom.LoomGradleExtension;

public final class GradleUtils {
private GradleUtils() {
}
Expand Down Expand Up @@ -61,6 +63,13 @@ public static boolean isLoomProject(Project project) {
}

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);
Expand All @@ -81,6 +90,17 @@ public static boolean getBooleanProperty(Project project, String key) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,11 @@ private static List<File> getBinDirClasspath(File projectDir, SourceSetReference
}

@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()) {
Expand All @@ -298,9 +298,9 @@ public static File findFileInResource(SourceSet sourceSet, String path) {
}

@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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,53 @@ 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
new File(gradle.projectDir, it).mkdir()
}

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
}

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
}
}
Original file line number Diff line number Diff line change
@@ -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")
}
}
Loading

0 comments on commit 1f28935

Please sign in to comment.