diff --git a/.gitignore b/.gitignore
index 66ea833ed..456456b36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ snapshots
/examples/*/run/ # this is where the run files are stored
/neoform/tests
+**/version.neogradle
\ No newline at end of file
diff --git a/README.md b/README.md
index ac566f134..1966294ed 100644
--- a/README.md
+++ b/README.md
@@ -77,11 +77,319 @@ runs {
}
```
You do not need to create all five of the different runs, only the ones you need.
+This is because at the point where gradle actually adds the dependency, we can not create any further runs, yet we can still configure them for you when the dependency is added.
+
+### Common Plugin
+#### Available Dependency Management
+##### Extra Jar
+The common plugin provides dependency management for extra-jar of minecraft.
+This is a special jar containing just the resources and assets of minecraft, no code.
+This is useful for mods that want to depend on the resources of minecraft, but not the code.
+```gradle
+plugins {
+ id 'net.neoforged.gradle.common' version ''
+}
-This is because at the point where gradle actually adds the dependency, we can not create any further runs.
+dependencies {
+ implementation "net.minecraft:client::client-extra"
+}
+```
-### NeoForm Runtime Plugin
+#### Jar-In-Jar support
+The common plugin provides support for jar-in-jar dependencies, both for publishing and when you consume them.
+##### Consuming Jar-In-Jar dependencies
+If you want to depend on a project that uses jar-in-jar dependencies, you can do so by adding the following to your build.gradle:
+```groovy
+dependencies {
+ implementation "project.with:jar-in-jar:1.2.3"
+}
+```
+Obviously you need to replace `project.with:jar-in-jar:1.2.3` with the actual coordinates of the project you want to depend on,
+and any configuration can be used to determine the scope of your dependency.
+
+##### Publishing
+If you want to publish a jar-in-jar dependency, you can do so by adding the following to your build.gradle:
+```groovy
+dependencies {
+ jarJar("project.you.want.to:include-as-jar-in-jar:[my.lowe.supported.version,potentially.my.upper.supported.version)") {
+ version {
+ prefer "the.version.you.want.to.use"
+ }
+ }
+}
+```
+Important here to note is that specifying a version range is needed. Jar-in-jar dependencies are not supported with a single version, directly.
+If you need to specify a single version, you can do so by specifying the same version for both the lower and upper bounds of the version range: `[the.version.you.want.to.use]`.
+
+###### Handling of moved Jar-In-Jar dependencies
+When dependency gets moved from one GAV to another, generally a transfer coordinate gets published, either via maven-metadata.xml, a seperate pom file, or via gradles available-at metadata.
+This can cause the version of the dependency to be different from the version you specified.
+It is best that you update your dependency to the new GAV to prevent problems and confusion in the future.
+
+#### Managing runs
+The common plugin provides a way to manage runs in your project.
+Its main purpose is to ensure that whether you use the vanilla, neoform, platform or userdev modules, you can always manage your runs in the same way.
+```groovy
+plugins {
+ id 'net.neoforged.gradle.common' version ''
+}
+
+runs {
+ //...Run configuration
+}
+```
+
+##### Configuring runs
+When you create a run in your project, it will initially be empty.
+If you do not use a run type, or clone the configuration from another run, as described below, you will have to configure the run yourself.
+```groovy
+runs {
+ someRun {
+ isIsSingleInstance true //This will make the run a single instance run, meaning that only one instance of the run can be run at a time
+ mainClass 'com.example.Main' //This will set the main class of the run
+ arguments 'arg1', 'arg2' //This will set the arguments of the run
+ jvmArguments '-Xmx4G' //This will set the jvm arguments of the run
+ isClient true //This will set the run to be a client run
+ isServer true //This will set the run to be a server run
+ isDataGenerator true //This will set the run to be a data gen run
+ isGameTest true //This will set the run to be a game test run
+ isJUnit true //This will set the run to be a junit run, indicating that a Unit Test environment should be used and not a normal run
+ environmentVariables 'key1': 'value1', 'key2': 'value2' //This will set the environment variables of the run
+ systemProperties 'key1': 'value1', 'key2': 'value2' //This will set the system properties of the run
+ classPath.from project.configurations.runtimeClasspath //This will add an element to just the classpath of this run
+
+ shouldBuildAllProjects true //This will set the run to build all projects before running
+ workingDirectory file('some/path') //This will set the working directory of the run
+ }
+}
+```
+Beyond these basic configurations you can always look at the [Runs DSL object](dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/Run.groovy) to see what other options are available.
+
+##### Configuring run types
+If you are using run types that do not come from an SDK (like those that the userdev plugin), you can configure them as follows:
+```groovy
+runs {
+ someRun {
+ isIsSingleInstance true //This will make the runs of this type a single instance run, meaning that only one instance of the run can be run at a time
+ mainClass 'com.example.Main' //This will set the main class of the runs of this type
+ arguments 'arg1', 'arg2' //This will set the arguments of the runs of this type
+ jvmArguments '-Xmx4G' //This will set the jvm arguments of the runs of this type
+ isClient true //This will set the run to be a client runs of this type
+ isServer true //This will set the run to be a server runs of this type
+ isDataGenerator true //This will set the run to be a data gen runs of this type
+ isGameTest true //This will set the run to be a game test runs of this type
+ isJUnit true //This will set the run to be a junit runs of this type, indicating that a Unit Test environment should be used and not a normal run
+ environmentVariables 'key1': 'value1', 'key2': 'value2' //This will set the environment variables of the runs of this type
+ systemProperties 'key1': 'value1', 'key2': 'value2' //This will set the system properties of the runs of this type
+ classPath.from project.configurations.runtimeClasspath //This will add an element to just the classpath of this runs of this type
+ }
+}
+```
+
+##### Types of runs
+The common plugin manages the existence of runs, but it on its own does not create or configure a run.
+It just ensures that when you create a run tasks, and IDE runs, are created for it, based on the configuration of the run itself.
+
+To configure a run based on a given template, run types exist.
+And if you use the userdev plugin for example then it will load the run types from the SDKs you depend on and allow you to configure runs based on those types.
+
+###### Configuring runs by name
+First and foremost by default the common plugin will configure any run that is named identical to a run type with that run type. You can disable that for a run by setting:
+```groovy
+runs {
+ someRun {
+ configureFromTypeWithName false
+ }
+}
+```
+
+###### Configuring run using types
+If you want to configure a run based on a run type, you can do so by setting:
+```groovy
+runs {
+ someRun {
+ runType 'client' //In case you want to configure the run type based on its name
+ configure runTypes.client //In case you want to configure the run type based on the run type object, this might not always be possible, due to the way gradle works
+
+ //The following method is deprecated and will be removed in a future release
+ configure 'client'
+ }
+}
+```
+Both of these will throw an exception if the run type could not be found during realization of the run to a task or ide run.
+
+##### Configuration of runs
+When a run is added the common plugin will also inspect it and figure out if any SDK or MDK was added to any of its sourcesets,
+if so, it gives that SDK/MDK a chance to add its own settings to the run.
+This allows provides like NeoForge to preconfigure runs for you.
+
+However, if you set up the run as follows:
+```groovy
+runs {
+ someRun {
+ modSource sourceSets.some_version_a_sourceset
+ modSource sourceSets.some_version_b_sourceset
+ }
+}
+```
+where the relevant sourcesets have a dependency in their compile classpath on the given sdk version, and those differ, and error will be raised.
+This is because the common plugin can not determine which version of the sdk to use for the run.
+To fix this, choose one of the versions and add it to the run:
+```groovy
+runs {
+ someRun {
+ modSource sourceSets.some_version_a_sourceset
+ }
+}
+```
+or
+```groovy
+runs {
+ someRun {
+ modSource sourceSets.some_version_b_sourceset
+ }
+}
+```
+
+###### Configuring run using another run
+Additionally, you can also clone a run into another run:
+```groovy
+runs {
+ someRun {
+ run 'client' //In case you want to clone the client run into the someRun
+ configure runTypes.client //In case you want to clone the client run type into the someRun
+ }
+}
+```
+
+##### Using DevLogin in runs
+The DevLogin tool is a tool that allows you to log in to a Minecraft account without having to use the Minecraft launcher, during development.
+During first start it will show you a link to log in to your Minecraft account, and then you can use the tool to log in to your account.
+The credentials are cached on your local machine, and then reused for future logins, so that re-logging is only needed when the tokens expire.
+This tool is used by the runs subsystem to enable logged in plays on all client runs.
+The tool can be configured using the following properties:
+```properties
+neogradle.subsystems.devLogin.enabled=
+```
+By default, the subsystem is enabled, and it will prepare everything for you to log in to a Minecraft account, however you will still need to enable it on the runs you want.
+If you want to disable this you can set the property to false, and then you will not be asked to log in, but use a random non-signed in dev account.
+
+###### Per run configuration
+If you want to configure the dev login tool per run, you can do so by setting the following properties in your run configuration:
+```groovy
+runs {
+ someRun {
+ devLogin {
+ enabled true
+ }
+ }
+}
+```
+This will enable the dev login tool for this run.
+By default, the dev login tool is disabled, and can only be enabled for client runs.
+
+> [!WARNING]
+> If you enable the dev login tool for a non-client run, you will get an error message.
+
+Additionally, it is possible to use a different user profile for the dev login tool, by setting the following property in your run configuration:
+```groovy
+runs {
+ someRun {
+ devLogin {
+ profile ''
+ }
+ }
+}
+```
+If it is not set then the default profile will be used. See the DevLogin documentation for more information on profiles: [DevLogin by Covers1624](https://github.com/covers1624/DevLogin/blob/main/README.md#multiple-accounts)
+
+###### Configurations
+To add the dev login tool to your run we create a custom configuration to which we add the dev login tool.
+This configuration is created for the first source-set you register with the run as a mod source set.
+The suffix for the configuration can be configured to your personal preference, by setting the following property in your gradle.properties:
+```properties
+neogradle.subsystems.devLogin.configurationSuffix=
+```
+By default, the suffix is set to "DevLoginLocalOnly".
+We use this approach to create a runtime only configuration, that does not leak to other consumers of your project.
+
+##### Using RenderDoc in runs
+The RenderDoc tool is a tool that allows you to capture frames from your game, and inspect them in its frame debugger.
+Our connector implementation can be optionally injected to start RenderDoc with your game.
+
+###### Per run configuration
+If you want to configure the render doc tool per run, you can do so by setting the following properties in your run configuration:
+```groovy
+runs {
+ someRun {
+ renderDoc {
+ enabled true
+ }
+ }
+}
+```
+This will enable the render doc tool for this run.
+By default, the render doc tool is disabled, and can only be enabled for client runs.
+
+> [!WARNING]
+> If you enable the render doc tool for a non-client run, you will get an error message.
+
+##### Resource processing
+Gradle supports resource processing out of the box, using the `processResources` task (or equivalent for none main sourcesets).
+However, no IDE supports this out of the box. To provide you with the best experience NeoGradle, will run the `processResources` task for you, before you run the game.
+
+If you are using IDEA and have enabled the "Build and Run using IntelliJ IDEA" option, then NeoGradle will additionally modify its runs,
+to ensure that the `processResources` task is run before the game is started, and redirects its output to a separate location in your build directory, and these processed
+resources are used during your run.
+
+> [!WARNING]
+> This means that the resource files created by ideas compile process are not used, and you should not rely on them being up-to-date.
+
+##### IDEA Compatibility
+Due to the way IDEA starts Unit Tests from the gutter, it is not possible to reconfigure these kinds of tests, if you are running with the IDEA testing engine.
+To support this scenario, by default NeoGradle will reconfigure IDEAs testing defaults to support running these tests within a unit test environment.
+
+However, due to the many constructs it is not possible to configure the defaults correctly for everybody, if you have a none standard testing setup, you can configure the defaults like so:
+```groovy
+idea {
+ unitTests {
+ //Normal run properties, and sourceset configuration as if you are configuring a run:
+ modSource sourceSets.anotherTestSourceSet
+ }
+}
+```
+
+If you want to disable this feature, you can disable the relevant conventions, see [Disabling conventions](#disabling-conventions).
+
+#### SourceSet Management
+The common plugin provides a way to manage sourcesets in your project a bit easier.
+In particular, it allows you to easily depend on a different sourceset, or inherit its dependencies.
+
+> [!WARNING]
+> However, it is important to know that you can only do this for sourcesets from the same project.
+> NeoGradle will throw an exception if you try to depend on a sourceset from another project.
+
+##### Inheriting dependencies
+If you want to inherit the dependencies of another sourceset, you can do so by adding the following to your build.gradle:
+```groovy
+sourceSets {
+ someSourceSet {
+ inherit.from sourceSets.someOtherSourceSet
+ }
+}
+```
+
+##### Depending on another sourceset
+If you want to depend on another sourceset, you can do so by adding the following to your build.gradle:
+```groovy
+sourceSets {
+ someSourceSet {
+ depends.on sourceSets.someOtherSourceSet
+ }
+}
+```
+### NeoForm Runtime Plugin
This plugin enables use of the NeoForm runtime and allows projects to depend directly on deobfuscated but otherwise
unmodified Minecraft artifacts.
@@ -314,7 +622,12 @@ neogradle.subsystems.conventions.sourcesets.enabled=false
```
#### Automatic inclusion of the current project in its runs
-By default, the current project is automatically included in its runs.
+By default, the current projects main sourceset is automatically included in its runs.
+If you want to disable this, you can set the following property in your gradle.properties:
+```properties
+neogradle.subsystems.conventions.sourcesets.automatic-inclusion=false
+```
+
If you want to disable this, you can set the following property in your gradle.properties:
```properties
neogradle.subsystems.conventions.sourcesets.automatic-inclusion=false
@@ -383,13 +696,20 @@ idea {
}
```
-#### Post Sync Task Usage
+##### Post Sync Task Usage
By default, the import in IDEA is run during the sync task.
If you want to disable this, and use a post sync task, you can set the following property in your gradle.properties:
```properties
neogradle.subsystems.conventions.ide.idea.use-post-sync-task=true
```
+##### Reconfiguration of IDEA Unit Test Templates
+By default, the IDEA unit test templates are not reconfigured to support running unit tests from the gutter.
+You can enable this behavior by setting the following property in your gradle.properties:
+```properties
+neogradle.subsystems.conventions.ide.idea.reconfigure-unit-test-templates=true
+```
+
### Runs
To disable the runs conventions, you can set the following property in your gradle.properties:
```properties
@@ -403,6 +723,21 @@ If you want to disable this, you can set the following property in your gradle.p
neogradle.subsystems.conventions.runs.create-default-run-per-type=false
```
+#### DevLogin Conventions
+If you want to enable the dev login tool for all client runs, you can set the following property in your gradle.properties:
+```properties
+neogradle.subsystems.conventions.runs.devlogin.conventionForRun=true
+```
+This will enable the dev login tool for all client runs, unless explicitly disabled.
+
+#### RenderDoc Conventions
+If you want to enable the render doc tool for all client runs, you can set the following property in your gradle.properties:
+```properties
+neogradle.subsystems.conventions.runs.renderdoc.conventionForRun=true
+```
+This will enable the render doc tool for all client runs, unless explicitly disabled.
+
+
## Tool overrides
To configure tools used by different subsystems of NG, the subsystems dsl and properties can be used to configure the following tools:
### JST
@@ -412,69 +747,21 @@ The following properties can be used to configure the JST tool:
neogradle.subsystems.tools.jst=
```
### DevLogin
-This tool is used by the runs subsystem to enable Minecraft authentication in client runs.
+This tool is used by the dev login subsystem in runs to enable Minecraft authentication in client runs.
The following properties can be used to configure the DevLogin tool:
```properties
neogradle.subsystems.tools.devLogin=
```
More information on the relevant tool, its released version and documentation can be found here: [DevLogin by Covers1624](https://github.com/covers1624/DevLogin)
-
-## DevLogin
-The DevLogin tool is a tool that allows you to log in to a Minecraft account without having to use the Minecraft launcher, during development.
-During first start it will show you a link to log in to your Minecraft account, and then you can use the tool to log in to your account.
-The credentials are cached on your local machine, and then reused for future logins, so that re-logging is only needed when the tokens expire.
-This tool is used by the runs subsystem to enable logged in plays on all client runs.
-The tool can be configured using the following properties:
+### RenderDoc
+This tool is used by the RenderDoc subsystem in runs to allow capturing frames from the game in client runs.
+The following properties can be used to configure the RenderDoc tool:
```properties
-neogradle.subsystems.devLogin.enabled=
+neogradle.subsystems.tools.renderDoc.path=
+neogradle.subsystems.tools.renderDoc.version=
+neogradle.subsystems.tools.renderDoc.renderNurse=
```
-By default, the subsystem is enabled, and it will prepare everything for you to log in to a Minecraft account, however you will still need to enable it on the runs you want.
-If you want to disable this you can set the property to false, and then you will not be asked to log in, but use a random non-signed in dev account.
-
-### Per run configuration
-If you want to configure the dev login tool per run, you can do so by setting the following properties in your run configuration:
-```groovy
-runs {
- someRun {
- devLogin {
- enabled true
- }
- }
-}
-```
-This will enable the dev login tool for this run.
-By default, the dev login tool is disabled, and can only be enabled for client runs.
-
-> [!WARNING]
-> If you enable the dev login tool for a non-client run, you will get an error message.
-
-If you want to enable the dev login tool for all client runs, you can set the following property in your gradle.properties:
-```properties
-neogradle.subsystems.devLogin.conventionForRun=true
-```
-This will enable the dev login tool for all client runs, unless explicitly disabled.
-
-Additionally, it is possible to use a different user profile for the dev login tool, by setting the following property in your run configuration:
-```groovy
-runs {
- someRun {
- devLogin {
- profile ''
- }
- }
-}
-```
-If it is not set then the default profile will be used. See the DevLogin documentation for more information on profiles: [DevLogin by Covers1624](https://github.com/covers1624/DevLogin/blob/main/README.md#multiple-accounts)
-
-### Configurations
-To add the dev login tool to your run we create a custom configuration to which we add the dev login tool.
-This configuration is created for the first source-set you register with the run as a mod source set.
-The suffix for the configuration can be configured to your personal preference, by setting the following property in your gradle.properties:
-```properties
-neogradle.subsystems.devLogin.configurationSuffix=
-```
-By default, the suffix is set to "DevLoginLocalOnly".
-We use this approach to create a runtime only configuration, that does not leak to other consumers of your project.
+More information on the relevant tool, its released version and documentation can be found here: [RenderDoc](https://renderdoc.org/) and [RenderNurse](https://github.com/neoforged/RenderNurse)
## Centralized Cache
NeoGradle has a centralized cache that can be used to store the decompiled Minecraft sources, the recompiled Minecraft sources, and other task outputs of complex tasks.
diff --git a/common/build.gradle b/common/build.gradle
index 495be60d8..ad81b802b 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -3,6 +3,16 @@ plugins {
id 'java-gradle-plugin'
}
+sourceSets {
+ main {
+ java {
+ resources {
+ srcDir 'src/main/generated'
+ }
+ }
+ }
+}
+
dependencies {
api project(':utils')
api project(':dsl-common')
@@ -29,3 +39,10 @@ dependencies {
api "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext:${project.gradle_idea_extension_version}"
}
+def versionFile = file('src/main/generated/version.neogradle')
+if (versionFile.exists()) {
+ versionFile.delete()
+} else {
+ versionFile.parentFile.mkdirs()
+}
+versionFile << project.version.toString()
\ No newline at end of file
diff --git a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java
index 7eebbe36a..6fab4c9f1 100644
--- a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java
+++ b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java
@@ -1,87 +1,74 @@
package net.neoforged.gradle.common;
-import net.neoforged.gradle.common.caching.CentralCacheService;
+import net.neoforged.gradle.common.accesstransformers.AccessTransformerPublishing;
+import net.neoforged.gradle.common.conventions.ConventionConfigurator;
import net.neoforged.gradle.common.dependency.ExtraJarDependencyManager;
import net.neoforged.gradle.common.extensions.*;
import net.neoforged.gradle.common.extensions.dependency.replacement.ReplacementLogic;
import net.neoforged.gradle.common.extensions.repository.IvyRepository;
+import net.neoforged.gradle.common.extensions.sourcesets.SourceSetDependencyExtensionImpl;
+import net.neoforged.gradle.common.extensions.sourcesets.SourceSetInheritanceExtensionImpl;
import net.neoforged.gradle.common.extensions.subsystems.SubsystemsExtension;
import net.neoforged.gradle.common.rules.LaterAddedReplacedDependencyRule;
import net.neoforged.gradle.common.runs.ide.IdeRunIntegrationManager;
-import net.neoforged.gradle.common.runs.run.RunImpl;
+import net.neoforged.gradle.common.runs.run.RunManagerImpl;
import net.neoforged.gradle.common.runs.run.RunTypeManagerImpl;
import net.neoforged.gradle.common.runs.tasks.RunsReport;
+import net.neoforged.gradle.common.runs.unittest.UnitTestConfigurator;
import net.neoforged.gradle.common.runtime.definition.CommonRuntimeDefinition;
import net.neoforged.gradle.common.runtime.extensions.RuntimesExtension;
import net.neoforged.gradle.common.runtime.naming.OfficialNamingChannelConfigurator;
+import net.neoforged.gradle.common.services.caching.CachedExecutionService;
import net.neoforged.gradle.common.tasks.CleanCache;
import net.neoforged.gradle.common.tasks.DisplayMappingsLicenseTask;
-import net.neoforged.gradle.common.util.ProjectUtils;
-import net.neoforged.gradle.common.util.SourceSetUtils;
-import net.neoforged.gradle.common.util.TaskDependencyUtils;
-import net.neoforged.gradle.common.util.constants.RunsConstants;
-import net.neoforged.gradle.common.util.exceptions.MultipleDefinitionsFoundException;
+import net.neoforged.gradle.common.util.ConfigurationUtils;
import net.neoforged.gradle.common.util.run.RunsUtil;
import net.neoforged.gradle.dsl.common.extensions.*;
import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.DependencyReplacement;
import net.neoforged.gradle.dsl.common.extensions.repository.Repository;
-import net.neoforged.gradle.dsl.common.extensions.subsystems.Conventions;
-import net.neoforged.gradle.dsl.common.extensions.subsystems.DevLogin;
+import net.neoforged.gradle.dsl.common.extensions.sourceset.RunnableSourceSet;
+import net.neoforged.gradle.dsl.common.extensions.sourceset.SourceSetDependencyExtension;
+import net.neoforged.gradle.dsl.common.extensions.sourceset.SourceSetInheritanceExtension;
import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems;
-import net.neoforged.gradle.dsl.common.extensions.subsystems.Tools;
-import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.Configurations;
-import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.IDE;
-import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.Runs;
-import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.SourceSets;
-import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.ide.IDEA;
-import net.neoforged.gradle.dsl.common.runs.run.Run;
-import net.neoforged.gradle.dsl.common.runs.run.RunDevLogin;
-import net.neoforged.gradle.dsl.common.runs.type.RunType;
+import net.neoforged.gradle.dsl.common.runs.run.RunManager;
import net.neoforged.gradle.dsl.common.runs.type.RunTypeManager;
-import net.neoforged.gradle.dsl.common.util.ConfigurationUtils;
import net.neoforged.gradle.dsl.common.util.NamingConstants;
import net.neoforged.gradle.util.UrlConstants;
-import org.gradle.StartParameter;
-import org.gradle.TaskExecutionRequest;
-import org.gradle.api.*;
-import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.attributes.AttributeContainer;
-import org.gradle.api.attributes.Category;
-import org.gradle.api.component.AdhocComponentWithVariants;
import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.problems.Problems;
import org.gradle.api.tasks.Delete;
-import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
-import org.gradle.api.tasks.TaskProvider;
-import org.gradle.internal.DefaultTaskExecutionRequest;
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
import org.gradle.plugins.ide.idea.IdeaPlugin;
import org.jetbrains.gradle.ext.IdeaExtPlugin;
-import java.util.*;
+import javax.inject.Inject;
public class CommonProjectPlugin implements Plugin {
- public static final String ASSETS_SERVICE = "ng_assets";
- public static final String LIBRARIES_SERVICE = "ng_libraries";
- public static final String EXECUTE_SERVICE = "ng_execute";
- public static final String ACCESS_TRANSFORMER_ELEMENTS_CONFIGURATION = "accessTransformerElements";
- public static final String ACCESS_TRANSFORMER_API_CONFIGURATION = "accessTransformerApi";
- public static final String ACCESS_TRANSFORMER_CONFIGURATION = "accessTransformer";
- static final String ACCESS_TRANSFORMER_CATEGORY = "accesstransformer";
+ public static final String PROBLEM_NAMESPACE = "neoforged.gradle";
+ public static final String PROBLEM_REPORTER_EXTENSION_NAME = "neogradleProblems";
+
+ private final Problems problems;
+
+ @Inject
+ public CommonProjectPlugin(Problems problems) {
+ this.problems = problems;
+ }
@Override
public void apply(Project project) {
//Apply the evaluation extension to monitor immediate execution of indirect tasks when evaluation already happened.
project.getExtensions().create(NamingConstants.Extension.EVALUATION, ProjectEvaluationExtension.class, project);
+ //We need the java plugin
project.getPluginManager().apply(JavaPlugin.class);
//Register the services
- CentralCacheService.register(project, ASSETS_SERVICE, true);
- CentralCacheService.register(project, LIBRARIES_SERVICE, true);
- CentralCacheService.register(project, EXECUTE_SERVICE, false);
+ CachedExecutionService.register(project);
// Apply both the idea and eclipse IDE plugins
project.getPluginManager().apply(IdeaPlugin.class);
@@ -89,25 +76,21 @@ public void apply(Project project) {
project.getPluginManager().apply(IdeaExtPlugin.class);
project.getPluginManager().apply(EclipsePlugin.class);
- project.getExtensions().create("extensionManager", ExtensionManager.class, project);
-
- final ExtensionManager extensionManager = project.getExtensions().getByType(ExtensionManager.class);
- extensionManager.registerExtension("subsystems", Subsystems.class, (p) -> p.getObjects().newInstance(SubsystemsExtension.class, p));
-
+ project.getExtensions().create(Subsystems.class,"subsystems", SubsystemsExtension.class, project);
project.getExtensions().create(IdeManagementExtension.class, "ideManager", IdeManagementExtension.class, project);
project.getExtensions().create("allRuntimes", RuntimesExtension.class);
project.getExtensions().create(Repository.class, "ivyDummyRepository", IvyRepository.class, project);
project.getExtensions().create(MinecraftArtifactCache.class, "minecraftArtifactCache", MinecraftArtifactCacheExtension.class, project);
project.getExtensions().create(DependencyReplacement.class, "dependencyReplacements", ReplacementLogic.class, project);
- AccessTransformers accessTransformers = project.getExtensions().create(AccessTransformers.class, "accessTransformers", AccessTransformersExtension.class, project);
+ project.getExtensions().create(NeoGradleProblemReporter.class, PROBLEM_REPORTER_EXTENSION_NAME, NeoGradleProblemReporter.class, problems.forNamespace(PROBLEM_NAMESPACE));
+ project.getExtensions().create(AccessTransformers.class, "accessTransformers", AccessTransformersExtension.class, project);
- extensionManager.registerExtension("minecraft", Minecraft.class, (p) -> p.getObjects().newInstance(MinecraftExtension.class, p));
- extensionManager.registerExtension("mappings", Mappings.class, (p) -> p.getObjects().newInstance(MappingsExtension.class, p));
+ project.getExtensions().create(Minecraft.class, "minecraft", MinecraftExtension.class, project);
+ project.getExtensions().create(Mappings.class,"mappings", MappingsExtension.class, project);
+ project.getExtensions().create(RunTypeManager.class, "runTypeManager", RunTypeManagerImpl.class, project);
+ project.getExtensions().create(ExtraJarDependencyManager.class, "clientExtraJarDependencyManager", ExtraJarDependencyManager.class, project);
+ project.getExtensions().create(RunManager.class, "runManager", RunManagerImpl.class, project);
- extensionManager.registerExtension("runTypes", RunTypeManager.class, (p) -> p.getObjects().newInstance(RunTypeManagerImpl.class, p));
-
-
- project.getExtensions().create("clientExtraJarDependencyManager", ExtraJarDependencyManager.class, project);
final ConfigurationData configurationData = project.getExtensions().create(ConfigurationData.class, "configurationData", ConfigurationDataExtension.class, project);
OfficialNamingChannelConfigurator.getInstance().configure(project);
@@ -130,272 +113,52 @@ public void apply(Project project) {
project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> {
sourceSet.getExtensions().create(ProjectHolder.class, ProjectHolderExtension.NAME, ProjectHolderExtension.class, project);
sourceSet.getExtensions().create(RunnableSourceSet.NAME, RunnableSourceSet.class, project);
+
+ sourceSet.getExtensions().create(SourceSetDependencyExtension.class, "depends", SourceSetDependencyExtensionImpl.class, sourceSet);
+ sourceSet.getExtensions().create(SourceSetInheritanceExtension.class, "inherits", SourceSetInheritanceExtensionImpl.class, sourceSet);
+
sourceSet.getExtensions().add("runtimeDefinition", project.getObjects().property(CommonRuntimeDefinition.class));
});
- final NamedDomainObjectContainer runs = project.getObjects().domainObjectContainer(Run.class, name -> RunsUtil.create(project, name));
- project.getExtensions().add(
- RunsConstants.Extensions.RUNS,
- runs
- );
+ ConfigurationUtils.ensureReplacementConfigurationExists(project);
+
+ //Setup IDE specific unit test handling.
+ UnitTestConfigurator.configureIdeUnitTests(project);
+
//Register a task creation rule that checks for runs.
project.getTasks().addRule(new LaterAddedReplacedDependencyRule(project));
- runs.configureEach(run -> {
- RunsUtil.configureModClasses(run);
- RunsUtil.createTasks(project, run);
- });
-
- setupAccessTransformerConfigurations(project, accessTransformers);
+ //Set up publishing for access transformer elements
+ AccessTransformerPublishing.setup(project);
+ //Set up the IDE run integration manager
IdeRunIntegrationManager.getInstance().setup(project);
- final TaskProvider> cleanCache = project.getTasks().register("cleanCache", CleanCache.class);
+ //Clean the shared cache
+ project.getTasks().register("cleanCache", CleanCache.class);
+ //Clean the configuration data location.
project.getTasks().named("clean", Delete.class, delete -> {
delete.delete(configurationData.getLocation());
- delete.dependsOn(cleanCache);
});
- //Needs to be before after evaluate
- configureConventions(project);
-
//Set up reporting tasks
project.getTasks().register("runs", RunsReport.class);
- final DevLogin devLogin = project.getExtensions().getByType(Subsystems.class).getDevLogin();
- if (devLogin.getEnabled().get()) {
- runs.configureEach(run -> {
- final RunDevLogin runsDevLogin = run.getExtensions().create("devLogin", RunDevLogin.class);
- runsDevLogin.getIsEnabled().convention(devLogin.getConventionForRun().zip(run.getIsClient(), (conventionForRun, isClient) -> conventionForRun && isClient));
- });
- }
+ //Needs to be before after evaluate
+ ConventionConfigurator.configureConventions(project);
project.afterEvaluate(this::applyAfterEvaluate);
}
- private void configureConventions(Project project) {
- final Conventions conventions = project.getExtensions().getByType(Subsystems.class).getConventions();
- if (!conventions.getIsEnabled().get())
- return;
-
- configureRunConventions(project, conventions);
- configureSourceSetConventions(project, conventions);
- configureIDEConventions(project, conventions);
- }
-
- private void configureSourceSetConventions(Project project, Conventions conventions) {
- final SourceSets sourceSets = conventions.getSourceSets();
- final Configurations configurations = conventions.getConfigurations();
-
- if (!sourceSets.getIsEnabled().get())
- return;
-
- if (configurations.getIsEnabled().get()) {
- project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> {
- final Configuration sourceSetLocalRuntimeConfiguration = project.getConfigurations().maybeCreate(ConfigurationUtils.getSourceSetName(sourceSet, configurations.getLocalRuntimeConfigurationPostFix().get()));
- project.getConfigurations().maybeCreate(ConfigurationUtils.getSourceSetName(sourceSet, configurations.getRunRuntimeConfigurationPostFix().get()));
-
- final Configuration sourceSetRuntimeClasspath = project.getConfigurations().maybeCreate(sourceSet.getRuntimeClasspathConfigurationName());
- sourceSetRuntimeClasspath.extendsFrom(sourceSetLocalRuntimeConfiguration);
- });
- }
-
- ProjectUtils.afterEvaluate(project, () -> {
- project.getExtensions().configure(RunsConstants.Extensions.RUNS, (Action>) runs -> {
- runs.configureEach(run -> {
- if (sourceSets.getShouldMainSourceSetBeAutomaticallyAddedToRuns().get()) {
- //We always register main
- run.getModSources().add(project.getExtensions().getByType(SourceSetContainer.class).getByName("main"));
- }
-
- if (sourceSets.getShouldSourceSetsLocalRunRuntimesBeAutomaticallyAddedToRuns().get() && configurations.getIsEnabled().get()) {
- run.getModSources().all().get().values().forEach(sourceSet -> {
- if (project.getConfigurations().findByName(ConfigurationUtils.getSourceSetName(sourceSet, configurations.getRunRuntimeConfigurationPostFix().get())) != null) {
- run.getDependencies().get().getRuntime().add(project.getConfigurations().getByName(ConfigurationUtils.getSourceSetName(sourceSet, configurations.getRunRuntimeConfigurationPostFix().get())));
- }
- });
- }
- });
- });
- });
-
- }
-
- private void configureRunConventions(Project project, Conventions conventions) {
- final Configurations configurations = conventions.getConfigurations();
- final Runs runs = conventions.getRuns();
-
- if (!runs.getIsEnabled().get())
- return;
-
- if (!configurations.getIsEnabled().get())
- return;
-
- final Configuration runRuntimeConfiguration = project.getConfigurations().maybeCreate(configurations.getRunRuntimeConfigurationName().get());
-
- project.getExtensions().configure(RunsConstants.Extensions.RUNS, (Action>) runContainer -> runContainer.configureEach(run -> {
- final Configuration runSpecificRuntimeConfiguration = project.getConfigurations().maybeCreate(ConfigurationUtils.getRunName(run, configurations.getPerRunRuntimeConfigurationPostFix().get()));
-
- run.getDependencies().get().getRuntime().add(runRuntimeConfiguration);
- run.getDependencies().get().getRuntime().add(runSpecificRuntimeConfiguration);
- }));
- }
-
- private void configureIDEConventions(Project project, Conventions conventions) {
- final IDE ideConventions = conventions.getIde();
- if (!ideConventions.getIsEnabled().get())
- return;
-
- configureIDEAIDEConventions(project, ideConventions);
- }
-
- private void configureIDEAIDEConventions(Project project, IDE ideConventions) {
- final IDEA ideaConventions = ideConventions.getIdea();
- if (!ideaConventions.getIsEnabled().get())
- return;
-
- //We need to configure the tasks to run during sync.
- final IdeManagementExtension ideManagementExtension = project.getExtensions().getByType(IdeManagementExtension.class);
- ideManagementExtension
- .onIdea((innerProject, idea, ideaExtension) -> {
- if (!ideaConventions.getIsEnabled().get())
- return;
-
- if (ideaConventions.getShouldUsePostSyncTask().get())
- return;
-
- if (!ideManagementExtension.isIdeaSyncing())
- return;
-
- final StartParameter startParameter = innerProject.getGradle().getStartParameter();
- final List taskRequests = new ArrayList<>(startParameter.getTaskRequests());
-
- final TaskProvider> ideImportTask = ideManagementExtension.getOrCreateIdeImportTask();
- final List taskPaths = new ArrayList<>();
-
- final String ideImportTaskName = ideImportTask.getName();
- final String projectPath = innerProject.getPath();
-
- String taskPath;
- if (ideImportTaskName.startsWith(":")) {
- if (projectPath.equals(":")) {
- taskPath = ideImportTaskName;
- } else {
- taskPath = String.format("%s%s", projectPath, ideImportTaskName);
- }
- } else {
- if (projectPath.equals(":")) {
- taskPath = String.format(":%s", ideImportTaskName);
- } else {
- taskPath = String.format("%s:%s", projectPath, ideImportTaskName);
- }
- }
-
- taskPaths.add(taskPath);
-
- taskRequests.add(new DefaultTaskExecutionRequest(taskPaths));
- startParameter.setTaskRequests(taskRequests);
- });
-
- IdeRunIntegrationManager.getInstance().configureIdeaConventions(project, ideaConventions);
- }
-
- @SuppressWarnings("UnstableApiUsage")
- private void setupAccessTransformerConfigurations(Project project, AccessTransformers accessTransformersExtension) {
- Configuration accessTransformerElements = project.getConfigurations().maybeCreate(ACCESS_TRANSFORMER_ELEMENTS_CONFIGURATION);
- Configuration accessTransformerApi = project.getConfigurations().maybeCreate(ACCESS_TRANSFORMER_API_CONFIGURATION);
- Configuration accessTransformer = project.getConfigurations().maybeCreate(ACCESS_TRANSFORMER_CONFIGURATION);
-
- accessTransformerApi.setCanBeConsumed(false);
- accessTransformerApi.setCanBeResolved(false);
-
- accessTransformer.setCanBeConsumed(false);
- accessTransformer.setCanBeResolved(true);
-
- accessTransformerElements.setCanBeConsumed(true);
- accessTransformerElements.setCanBeResolved(false);
- accessTransformerElements.setCanBeDeclared(false);
-
- Action action = attributes -> {
- attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, ACCESS_TRANSFORMER_CATEGORY));
- };
-
- accessTransformerElements.attributes(action);
- accessTransformer.attributes(action);
-
- accessTransformer.extendsFrom(accessTransformerApi);
- accessTransformerElements.extendsFrom(accessTransformerApi);
-
- // Now we set up the component, conditionally
- AdhocComponentWithVariants java = (AdhocComponentWithVariants) project.getComponents().getByName("java");
- Runnable enable = () -> java.addVariantsFromConfiguration(accessTransformerElements, variant -> {
- });
-
- accessTransformerElements.getAllDependencies().configureEach(dep -> enable.run());
- accessTransformerElements.getArtifacts().configureEach(artifact -> enable.run());
-
- // And add resolved ATs to the extension
- accessTransformersExtension.getFiles().from(accessTransformer);
- }
-
- @SuppressWarnings("unchecked")
private void applyAfterEvaluate(final Project project) {
//We now eagerly get all runs and configure them.
- final NamedDomainObjectContainer runs = (NamedDomainObjectContainer) project.getExtensions().getByName(RunsConstants.Extensions.RUNS);
- runs.configureEach(run -> {
- if (run instanceof RunImpl runImpl) {
- run.configure();
-
- // We add default junit sourcesets here because we need to know the type of the run first
- final Conventions conventions = project.getExtensions().getByType(Subsystems.class).getConventions();
- if (conventions.getIsEnabled().get() && conventions.getSourceSets().getIsEnabled().get() && conventions.getSourceSets().getShouldMainSourceSetBeAutomaticallyAddedToRuns().get()) {
- if (run.getIsJUnit().get()) {
- run.getUnitTestSources().add(project.getExtensions().getByType(SourceSetContainer.class).getByName("test"));
- }
- }
-
- if (run.getModSources().all().get().isEmpty()) {
- throw new InvalidUserDataException("Run: " + run.getName() + " has no source sets configured. Please configure at least one source set.");
- }
-
- //Handle dev login.
- final DevLogin devLogin = project.getExtensions().getByType(Subsystems.class).getDevLogin();
- final Tools tools = project.getExtensions().getByType(Subsystems.class).getTools();
- if (devLogin.getEnabled().get()) {
- final RunDevLogin runsDevLogin = runImpl.getExtensions().getByType(RunDevLogin.class);
-
- //Dev login is only supported on the client side
- if (runImpl.getIsClient().get() && runsDevLogin.getIsEnabled().get()) {
- final String mainClass = runImpl.getMainClass().get();
-
- //We add the dev login tool to a custom configuration which runtime classpath extends from the default runtime classpath
- final SourceSet defaultSourceSet = runImpl.getModSources().all().get().entries().iterator().next().getValue();
- final String runtimeOnlyDevLoginConfigurationName = ConfigurationUtils.getSourceSetName(defaultSourceSet, devLogin.getConfigurationSuffix().get());
- final Configuration sourceSetRuntimeOnlyDevLoginConfiguration = project.getConfigurations().maybeCreate(runtimeOnlyDevLoginConfigurationName);
- final Configuration sourceSetRuntimeClasspathConfiguration = project.getConfigurations().maybeCreate(defaultSourceSet.getRuntimeClasspathConfigurationName());
-
- sourceSetRuntimeClasspathConfiguration.extendsFrom(sourceSetRuntimeOnlyDevLoginConfiguration);
- sourceSetRuntimeOnlyDevLoginConfiguration.getDependencies().add(project.getDependencies().create(tools.getDevLogin().get()));
-
- //Update the program arguments to properly launch the dev login tool
- run.getProgramArguments().add("--launch_target");
- run.getProgramArguments().add(mainClass);
-
- if (runsDevLogin.getProfile().isPresent()) {
- run.getProgramArguments().add("--launch_profile");
- run.getProgramArguments().add(runsDevLogin.getProfile().get());
- }
-
- //Set the main class to the dev login tool
- run.getMainClass().set(devLogin.getMainClass());
- } else if (!runImpl.getIsClient().get() && runsDevLogin.getIsEnabled().get()) {
- throw new InvalidUserDataException("Dev login is only supported on runs which are marked as clients! The run: " + runImpl.getName() + " is not a client run.");
- }
- }
- }
- });
-
+ final RunManager runs = project.getExtensions().getByType(RunManager.class);
+ runs.realizeAll(run -> RunsUtil.configure(
+ project,
+ run,
+ !runs.getNames().contains(run.getName()) //Internal runs are not directly registered, so they don't show up in the name list.
+ ));
IdeRunIntegrationManager.getInstance().apply(project);
}
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/accesstransformers/AccessTransformerPublishing.java b/common/src/main/java/net/neoforged/gradle/common/accesstransformers/AccessTransformerPublishing.java
new file mode 100644
index 000000000..1b0b527b1
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/accesstransformers/AccessTransformerPublishing.java
@@ -0,0 +1,57 @@
+package net.neoforged.gradle.common.accesstransformers;
+
+import net.neoforged.gradle.common.extensions.AccessTransformersExtension;
+import net.neoforged.gradle.dsl.common.extensions.AccessTransformers;
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.attributes.AttributeContainer;
+import org.gradle.api.attributes.Category;
+import org.gradle.api.component.AdhocComponentWithVariants;
+
+public class AccessTransformerPublishing {
+
+ public static final String ACCESS_TRANSFORMER_ELEMENTS_CONFIGURATION = "accessTransformerElements";
+ public static final String ACCESS_TRANSFORMER_API_CONFIGURATION = "accessTransformerApi";
+ public static final String ACCESS_TRANSFORMER_CONFIGURATION = "accessTransformer";
+ public static final String ACCESS_TRANSFORMER_CATEGORY = "accesstransformer";
+
+ public static void setup(Project project) {
+ AccessTransformers accessTransformersExtension = project.getExtensions().getByType(AccessTransformers.class);
+
+ Configuration accessTransformerElements = project.getConfigurations().maybeCreate(ACCESS_TRANSFORMER_ELEMENTS_CONFIGURATION);
+ Configuration accessTransformerApi = project.getConfigurations().maybeCreate(ACCESS_TRANSFORMER_API_CONFIGURATION);
+ Configuration accessTransformer = project.getConfigurations().maybeCreate(ACCESS_TRANSFORMER_CONFIGURATION);
+
+ accessTransformerApi.setCanBeConsumed(false);
+ accessTransformerApi.setCanBeResolved(false);
+
+ accessTransformer.setCanBeConsumed(false);
+ accessTransformer.setCanBeResolved(true);
+
+ accessTransformerElements.setCanBeConsumed(true);
+ accessTransformerElements.setCanBeResolved(false);
+ accessTransformerElements.setCanBeDeclared(false);
+
+ Action action = attributes -> {
+ attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, ACCESS_TRANSFORMER_CATEGORY));
+ };
+
+ accessTransformerElements.attributes(action);
+ accessTransformer.attributes(action);
+
+ accessTransformer.extendsFrom(accessTransformerApi);
+ accessTransformerElements.extendsFrom(accessTransformerApi);
+
+ // Now we set up the component, conditionally
+ AdhocComponentWithVariants java = (AdhocComponentWithVariants) project.getComponents().getByName("java");
+ Runnable enable = () -> java.addVariantsFromConfiguration(accessTransformerElements, variant -> {
+ });
+
+ accessTransformerElements.getAllDependencies().configureEach(dep -> enable.run());
+ accessTransformerElements.getArtifacts().configureEach(artifact -> enable.run());
+
+ // And add resolved ATs to the extension
+ accessTransformersExtension.getFiles().from(accessTransformer);
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/caching/CentralCacheService.java b/common/src/main/java/net/neoforged/gradle/common/caching/CentralCacheService.java
deleted file mode 100644
index 1599f57aa..000000000
--- a/common/src/main/java/net/neoforged/gradle/common/caching/CentralCacheService.java
+++ /dev/null
@@ -1,562 +0,0 @@
-package net.neoforged.gradle.common.caching;
-
-import com.google.common.collect.Lists;
-import net.neoforged.gradle.common.util.hash.HashCode;
-import net.neoforged.gradle.common.util.hash.HashFunction;
-import net.neoforged.gradle.common.util.hash.Hasher;
-import net.neoforged.gradle.common.util.hash.Hashing;
-import net.neoforged.gradle.util.FileUtils;
-import org.apache.commons.io.filefilter.AbstractFileFilter;
-import org.apache.commons.lang3.SystemUtils;
-import org.gradle.api.GradleException;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.file.Directory;
-import org.gradle.api.file.DirectoryProperty;
-import org.gradle.api.file.RegularFile;
-import org.gradle.api.file.RegularFileProperty;
-import org.gradle.api.provider.Property;
-import org.gradle.api.provider.Provider;
-import org.gradle.api.services.BuildService;
-import org.gradle.api.services.BuildServiceParameters;
-import org.gradle.api.tasks.TaskInputs;
-import org.gradle.internal.impldep.org.joda.time.DateTime;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.file.*;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.text.DateFormat;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * This is a cache service, which holds no directory information, yet.
- * I tried different ways of adding a directory property, but it always failed during isolation of the params.
- * For now please make sure that consuming tasks have a directory property, which is set to their cache directory.
- */
-public abstract class CentralCacheService implements BuildService {
-
- public static final String CACHING_PROPERTY_PREFIX = "net.neoforged.gradle.caching.";
- public static final String CACHE_DIRECTORY_PROPERTY = CACHING_PROPERTY_PREFIX + "cacheDirectory";
- public static final String LOG_CACHE_HITS_PROPERTY = CACHING_PROPERTY_PREFIX + "logCacheHits";
- public static final String MAX_CACHE_SIZE_PROPERTY = CACHING_PROPERTY_PREFIX + "maxCacheSize";
- public static final String DEBUG_CACHE_PROPERTY = CACHING_PROPERTY_PREFIX + "debug";
- public static final String IS_ENABLED_PROPERTY = CACHING_PROPERTY_PREFIX + "enabled";
- public static final String HEALTHY_FILE_NAME = "healthy";
-
- public static void register(Project project, String name, boolean isolated) {
- project.getGradle().getSharedServices().registerIfAbsent(
- name,
- CentralCacheService.class,
- spec -> {
- if (isolated) {
- spec.getMaxParallelUsages().set(1);
- }
-
- spec.getParameters().getCacheDirectory()
- .fileProvider(project.getProviders().gradleProperty(CACHE_DIRECTORY_PROPERTY)
- .map(File::new)
- .orElse(new File(new File(project.getGradle().getGradleUserHomeDir(), "caches"), name)));
- spec.getParameters().getLogCacheHits().set(project.getProviders().gradleProperty(LOG_CACHE_HITS_PROPERTY).map(Boolean::parseBoolean).orElse(false));
- spec.getParameters().getMaxCacheSize().set(project.getProviders().gradleProperty(MAX_CACHE_SIZE_PROPERTY).map(Integer::parseInt).orElse(100));
- spec.getParameters().getDebugCache().set(project.getProviders().gradleProperty(DEBUG_CACHE_PROPERTY).map(Boolean::parseBoolean).orElse(false));
- spec.getParameters().getIsEnabled().set(project.getProviders().gradleProperty(IS_ENABLED_PROPERTY).map(Boolean::parseBoolean).orElse(true));
- }
- );
- }
-
- private final LockManager lockManager = new LockManager();
-
- public void cleanCache(Task cleaningTask) {
- if (!getParameters().getIsEnabled().get()) {
- debugLog(cleaningTask, "Cache is disabled, skipping clean");
- return;
- }
-
- final File cacheDirectory = getParameters().getCacheDirectory().get().getAsFile();
-
- if (cacheDirectory.exists()) {
- try (Stream stream = Files.walk(cacheDirectory.toPath())) {
- final Set lockFilesToDelete = stream.filter(path -> path.getFileName().toString().equals("lock"))
- .sorted(Comparator.comparingLong(p -> p.toFile().lastModified()).reversed())
- .skip(getParameters().getMaxCacheSize().get())
- .collect(Collectors.toSet());
-
- for (Path path : lockFilesToDelete) {
- final File cacheFileDirectory = path.getParent().toFile();
- debugLog(cleaningTask, "Deleting cache directory for clean: " + cacheFileDirectory.getAbsolutePath());
- FileUtils.delete(cacheFileDirectory.toPath());
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- public void doCached(Task task, DoCreate onCreate, Provider target) throws Throwable {
- if (!getParameters().getIsEnabled().get()) {
- debugLog(task, "Cache is disabled, skipping cache");
- onCreate.create();
- return;
- }
-
- final TaskHasher hasher = new TaskHasher(task);
- final String hashDirectoryName = hasher.create().toString();
- final Directory cacheDirectory = getParameters().getCacheDirectory().get().dir(hashDirectoryName);
- final RegularFile targetFile = target.get();
-
- debugLog(task, "Cache directory: " + cacheDirectory.getAsFile().getAbsolutePath());
- debugLog(task, "Target file: " + targetFile.getAsFile().getAbsolutePath());
-
- final File lockFile = new File(cacheDirectory.getAsFile(), "lock");
- debugLog(task, "Lock file: " + lockFile.getAbsolutePath());
- executeCacheLookupOrCreation(task, onCreate, lockFile, cacheDirectory, targetFile);
- debugLog(task, "Cached task: " + task.getPath() + " finished");
- }
-
- public void doCachedDirectory(Task task, DoCreate onCreate, DirectoryProperty output) throws Throwable {
- if (!getParameters().getIsEnabled().get()) {
- debugLog(task, "Cache is disabled, skipping cache");
- onCreate.create();
- return;
- }
-
- final TaskHasher hasher = new TaskHasher(task);
- final String hashDirectoryName = hasher.create().toString();
- final Directory cacheDirectory = getParameters().getCacheDirectory().get().dir(hashDirectoryName);
- final Directory targetDirectory = output.get();
-
- debugLog(task, "Cache directory: " + cacheDirectory.getAsFile().getAbsolutePath());
- debugLog(task, "Target directory: " + targetDirectory.getAsFile().getAbsolutePath());
-
- final File lockFile = new File(cacheDirectory.getAsFile(), "lock");
- debugLog(task, "Lock file: " + lockFile.getAbsolutePath());
- executeCacheLookupOrCreation(task, onCreate, lockFile, cacheDirectory, targetDirectory);
- debugLog(task, "Cached task: " + task.getPath() + " finished");
- }
-
- @SuppressWarnings("ResultOfMethodCallIgnored")
- private void executeCacheLookupOrCreation(Task task, DoCreate onCreate, File lockFile, Directory cacheDirectory, RegularFile targetFile) throws Throwable {
- if (!lockFile.exists()) {
- debugLog(task, "Lock file does not exist: " + lockFile.getAbsolutePath());
- try {
- lockFile.getParentFile().mkdirs();
- lockFile.createNewFile();
- } catch (IOException e) {
- throw new GradleException("Failed to create lock file: " + lockFile.getAbsolutePath(), e);
- }
- }
-
- // Acquiring an exclusive lock on the file
- debugLog(task, "Acquiring lock on file: " + lockFile.getAbsolutePath());
- try (FileBasedLock fileBasedLock = lockManager.createLock(task, lockFile)) {
- try {
- fileBasedLock.updateAccessTime();
- debugLog(task, "Lock acquired on file: " + lockFile.getAbsolutePath());
-
- executeCacheLookupOrCreationLocked(task, onCreate, cacheDirectory, targetFile.getAsFile(), fileBasedLock.hasPreviousFailure(), false);
-
- // Release the lock when done
- debugLog(task, "Releasing lock on file: " + lockFile.getAbsolutePath());
- } catch (Exception ex) {
- debugLog(task, "Exception occurred while executing cached task: " + targetFile.getAsFile().getAbsolutePath(), ex);
- fileBasedLock.markAsFailed();
- throw new GradleException("Cached execution failed for: " + task.getName(), ex);
- }
- }
- }
-
-
- @SuppressWarnings("ResultOfMethodCallIgnored")
- private void executeCacheLookupOrCreation(Task task, DoCreate onCreate, File lockFile, Directory cacheDirectory, Directory targetDirectory) throws Throwable {
- if (!lockFile.exists()) {
- debugLog(task, "Lock file does not exist: " + lockFile.getAbsolutePath());
- try {
- lockFile.getParentFile().mkdirs();
- lockFile.createNewFile();
- } catch (IOException e) {
- throw new GradleException("Failed to create lock file: " + lockFile.getAbsolutePath(), e);
- }
- }
-
- // Acquiring an exclusive lock on the file
- debugLog(task, "Acquiring lock on file: " + lockFile.getAbsolutePath());
- try (FileBasedLock fileBasedLock = lockManager.createLock(task, lockFile)) {
- try {
- fileBasedLock.updateAccessTime();
- debugLog(task, "Lock acquired on file: " + lockFile.getAbsolutePath());
-
- executeCacheLookupOrCreationLocked(task, onCreate, cacheDirectory, targetDirectory.getAsFile(), fileBasedLock.hasPreviousFailure(), true);
-
- // Release the lock when done
- debugLog(task, "Releasing lock on file: " + lockFile.getAbsolutePath());
- } catch (Exception ex) {
- debugLog(task, "Exception occurred while executing cached task: " + targetDirectory.getAsFile().getAbsolutePath(), ex);
- fileBasedLock.markAsFailed();
- throw new GradleException("Cached execution failed for: " + task.getName(), ex);
- }
- }
- }
-
- private void executeCacheLookupOrCreationLocked(Task task, DoCreate onCreate, Directory cacheDirectory, File targetFile, boolean failedPreviously, boolean isDirectory) throws Throwable {
- final File cacheFile = new File(cacheDirectory.getAsFile(), targetFile.getName());
- final File noCacheFile = new File(cacheDirectory.getAsFile(), "nocache");
-
- if (failedPreviously) {
- //Previous execution failed
- debugLog(task, "Last cache run failed: " + cacheFile.getAbsolutePath());
- FileUtils.delete(cacheFile.toPath());
- FileUtils.delete(noCacheFile.toPath());
- }
-
- if (noCacheFile.exists()) {
- //Previous execution indicated no output
- debugLog(task, "Last cache run indicated no output: " + noCacheFile.getAbsolutePath());
- logCacheHit(task, noCacheFile);
- task.setDidWork(false);
- FileUtils.delete(targetFile.toPath());
- if (isDirectory) {
- //Create the directory, we always ensure the empty directory will get created!
- if (!targetFile.mkdirs()) {
- throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
- }
- }
- return;
- }
-
- if (cacheFile.exists()) {
- debugLog(task, "Cached file exists: " + cacheFile.getAbsolutePath());
- if (targetFile.exists() && Hashing.hashFile(cacheFile).equals(Hashing.hashFile(targetFile))) {
- debugLog(task, "Cached file equals target file");
- task.setDidWork(false);
- logCacheEquals(task, cacheFile);
- return;
- }
-
- debugLog(task, "Cached file does not equal target file");
- logCacheHit(task, cacheFile);
- FileUtils.delete(targetFile.toPath());
- if (!isDirectory) {
- Files.copy(cacheFile.toPath(), targetFile.toPath());
- } else {
- //Copy the directory
- Files.walkFileTree(cacheFile.toPath(), new SimpleFileVisitor<>() {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
- final Path relativePath = cacheFile.toPath().relativize(file);
- final Path targetPath = targetFile.toPath().resolve(relativePath);
- Files.createDirectories(targetPath.getParent());
- Files.copy(file, targetPath);
- return FileVisitResult.CONTINUE;
- }
- });
- }
- task.setDidWork(false);
- } else {
- debugLog(task, "Cached file does not exist: " + cacheFile.getAbsolutePath());
- logCacheMiss(task);
-
- debugLog(task, "Creating output: " + targetFile.getAbsolutePath());
- File createdFile = onCreate.create();
- if (isDirectory && !createdFile.isDirectory())
- throw new IllegalStateException("Expected a directory, but got a file: " + createdFile.getAbsolutePath());
-
- debugLog(task, "Created output: " + createdFile.getAbsolutePath());
-
- if (!createdFile.exists() || (isDirectory && Objects.requireNonNull(createdFile.listFiles()).length == 0)) {
- //No output was created
- debugLog(task, "No output was created: " + createdFile.getAbsolutePath());
- Files.createFile(noCacheFile.toPath());
- Files.deleteIfExists(cacheFile.toPath());
- } else {
- //Output was created
- debugLog(task, "Output was created: " + createdFile.getAbsolutePath());
- Files.deleteIfExists(noCacheFile.toPath());
- if (!isDirectory) {
- Files.copy(createdFile.toPath(), cacheFile.toPath());
- } else {
- //Copy the directory
- Files.walkFileTree(createdFile.toPath(), new SimpleFileVisitor<>() {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
- final Path relativePath = createdFile.toPath().relativize(file);
- final Path targetPath = cacheFile.toPath().resolve(relativePath);
- Files.createDirectories(targetPath.getParent());
- Files.copy(file, targetPath);
- return FileVisitResult.CONTINUE;
- }
- });
- }
- }
- task.setDidWork(true);
- }
- }
-
- private void logCacheEquals(Task task, File cacheFile) {
- if (getParameters().getLogCacheHits().get()) {
- task.getLogger().lifecycle("Cache equal for task {} from {}", task.getPath(), cacheFile.getAbsolutePath());
- }
- }
-
- private void logCacheHit(Task task, File cacheFile) {
- if (getParameters().getLogCacheHits().get()) {
- task.getLogger().lifecycle("Cache hit for task {} from {}", task.getPath(), cacheFile.getAbsolutePath());
- }
- }
-
- private void logCacheMiss(Task task) {
- if (getParameters().getLogCacheHits().get()) {
- task.getLogger().lifecycle("Cache miss for task {}", task.getPath());
- }
- }
-
- private void debugLog(Task task, String message) {
- if (getParameters().getDebugCache().get()) {
- task.getLogger().lifecycle(" > [" + System.currentTimeMillis() + "] (" + ProcessHandle.current().pid() + "): " + message);
- }
- }
-
- private void debugLog(Task task, String message, Exception e) {
- if (getParameters().getDebugCache().get()) {
- task.getLogger().lifecycle(" > [" + System.currentTimeMillis() + "] (" + ProcessHandle.current().pid() + "): " + message, e);
- }
- }
-
- public interface DoCreate {
- File create() throws Throwable;
- }
-
- public interface Parameters extends BuildServiceParameters {
-
- DirectoryProperty getCacheDirectory();
-
- Property getLogCacheHits();
-
- Property getMaxCacheSize();
-
- Property getDebugCache();
-
- Property getIsEnabled();
- }
-
- private final class TaskHasher {
- private final HashFunction hashFunction = Hashing.sha256();
- private final Hasher hasher = hashFunction.newHasher();
-
- private final Task task;
-
- public TaskHasher(Task task) {
- this.task = task;
- }
-
- public void hash() throws IOException {
- debugLog(task, "Hashing task: " + task.getPath());
- hasher.putString(task.getClass().getName());
-
- final TaskInputs taskInputs = task.getInputs();
- hash(taskInputs);
- }
-
- public void hash(TaskInputs inputs) throws IOException {
- debugLog(task, "Hashing task inputs: " + task.getPath());
- inputs.getProperties().forEach((key, value) -> {
- debugLog(task, "Hashing task input property: " + key);
- hasher.putString(key);
- debugLog(task, "Hashing task input property value: " + value);
- hasher.put(value, false); //We skin unknown types (mostly file collections)
- });
-
- for (File file : inputs.getFiles()) {
- try (Stream pathStream = Files.walk(file.toPath())) {
- for (Path path : pathStream.filter(Files::isRegularFile).toList()) {
- debugLog(task, "Hashing task input file: " + path.toAbsolutePath());
- hasher.putString(path.getFileName().toString());
- final HashCode code = hashFunction.hashFile(path.toFile());
- debugLog(task, "Hashing task input file hash: " + code);
- hasher.putHash(code);
- }
- }
- }
- }
-
- public HashCode create() throws IOException {
- hash();
- return hasher.hash();
- }
- }
-
- private interface FileBasedLock extends AutoCloseable {
-
- void updateAccessTime();
-
- boolean hasPreviousFailure();
-
- void markAsFailed();
- }
-
- private final class LockManager {
- public FileBasedLock createLock(Task task, File lockFile) {
- return new IOControlledFileBasedLock(task, lockFile);
- }
- }
-
- private static abstract class HealthFileUsingFileBasedLock implements FileBasedLock {
-
- private final File healthyFile;
-
- private boolean hasFailed = false;
-
- private HealthFileUsingFileBasedLock(File healthyFile) {
- this.healthyFile = healthyFile;
- }
-
- @Override
- public boolean hasPreviousFailure() {
- return hasFailed || !healthyFile.exists();
- }
-
- @Override
- public void markAsFailed() {
- this.hasFailed = true;
- }
-
- @Override
- public void close() throws Exception {
- //Creation of the healthy file is the last thing we do, so if it exists, we know the lock was successful
- if (!hasFailed) {
- this.healthyFile.createNewFile();
- } else if (this.healthyFile.exists() && !this.healthyFile.delete()) {
- //We grabbed a lock together with another process, the other process succeeded, we failed, but also failed to delete our healthy marker.
- //Something is wrong, we should not continue.
- throw new IllegalStateException("Failed to delete healthy marker file: " + this.healthyFile.getAbsolutePath());
- }
- }
- }
-
- private final class IOControlledFileBasedLock extends HealthFileUsingFileBasedLock {
-
- private final Task task;
- private final File lockFile;
- private final PIDBasedFileLock pidBasedFileLock;
-
- private IOControlledFileBasedLock(Task task, File lockFile) {
- super(new File(lockFile.getParentFile(), "healthy"));
- this.task = task;
- this.lockFile = lockFile;
- this.pidBasedFileLock = new PIDBasedFileLock(task, lockFile);
- }
-
- @Override
- public void updateAccessTime() {
- if (!lockFile.setLastModified(System.currentTimeMillis())) {
- throw new RuntimeException("Failed to update access time for lock file: " + lockFile.getAbsolutePath());
- }
-
- debugLog(task, "Updated access time for lock file: " + lockFile.getAbsolutePath());
- }
-
- @Override
- public void close() throws Exception {
- //Close the super first, this ensures that the healthy file is created only if the lock was successful
- super.close();
- this.pidBasedFileLock.close();
- debugLog(task, "Lock file closed: " + lockFile.getAbsolutePath());
- }
- }
-
- private final class PIDBasedFileLock implements AutoCloseable {
-
- private static final Map FILE_LOCKS = new ConcurrentHashMap<>();
- private final Task task;
- private final File lockFile;
-
- private PIDBasedFileLock(Task task, File lockFile) {
- this.task = task;
- this.lockFile = lockFile;
- this.lockFile();
- }
-
- private void lockFile() {
- debugLog(task, "Attempting to acquire lock on file: " + lockFile.getAbsolutePath());
- while (!attemptFileLock()) {
- //We attempt a lock every 500ms
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to acquire lock on file: " + lockFile.getAbsolutePath(), e);
- }
- }
- debugLog(task, "Lock acquired on file: " + lockFile.getAbsolutePath());
- }
-
- private synchronized boolean attemptFileLock() {
- try {
- if (!lockFile.exists()) {
- //No lock file exists, create one
- Files.write(lockFile.toPath(), String.valueOf(ProcessHandle.current().pid()).getBytes(), StandardOpenOption.CREATE_NEW);
- return true;
- }
-
- //Lock file exists, check if we are the owner
- for (String s : Files.readAllLines(lockFile.toPath())) {
- //Get the written pid.
- int pid = Integer.parseInt(s);
- if (ProcessHandle.current().pid() == pid) {
- debugLog(task, "Lock file is owned by current process: " + lockFile.getAbsolutePath() + " pid: " + pid);
- final OwnerAwareReentrantLock lock = FILE_LOCKS.computeIfAbsent(lockFile.getAbsolutePath(), s1 -> new OwnerAwareReentrantLock());
- debugLog(task, "Lock file is held by thread: " + lock.getOwner().getId() + " - " + lock.getOwner().getName() + " current thread: " + Thread.currentThread().getId() + " - " + Thread.currentThread().getName());
- lock.lock();
- return true;
- }
-
- //Check if the process is still running
- if (ProcessHandle.of(pid).isEmpty()) {
- //Process is not running, we can take over the lock
- debugLog(task, "Lock file is owned by a killed process: " + lockFile.getAbsolutePath() + " taking over. Old pid: " + pid);
- Files.write(lockFile.toPath(), String.valueOf(ProcessHandle.current().pid()).getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
- return true;
- }
-
-
- debugLog(task, "Lock file is owned by another process: " + lockFile.getAbsolutePath() + " pid: " + pid);
- //Process is still running, we can't take over the lock
- return false;
- }
-
- //No pid found in lock file, we can take over the lock
- debugLog(task, "Lock file is empty: " + lockFile.getAbsolutePath());
- Files.write(lockFile.toPath(), String.valueOf(ProcessHandle.current().pid()).getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
- final OwnerAwareReentrantLock lock = FILE_LOCKS.computeIfAbsent(lockFile.getAbsolutePath(), s1 -> new OwnerAwareReentrantLock());
- debugLog(task, "Created local thread lock for thread: " + Thread.currentThread().getId() + " - " + Thread.currentThread().getName());
- lock.lock();
- return true;
- } catch (Exception e) {
- debugLog(task, "Failed to acquire lock on file: " + lockFile.getAbsolutePath() + " - Failure message: " + e.getLocalizedMessage(), e);
- return false;
- }
- }
-
- @Override
- public void close() throws Exception {
- debugLog(task, "Releasing lock on file: " + lockFile.getAbsolutePath());
- Files.write(lockFile.toPath(), Lists.newArrayList(), StandardOpenOption.TRUNCATE_EXISTING);
- if (FILE_LOCKS.containsKey(lockFile.getAbsolutePath())) {
- FILE_LOCKS.get(lockFile.getAbsolutePath()).unlock();
- }
- }
- }
-
- private static final class OwnerAwareReentrantLock extends ReentrantLock {
- @Override
- public Thread getOwner() {
- return super.getOwner();
- }
- }
-}
diff --git a/common/src/main/java/net/neoforged/gradle/common/conventions/ConventionConfigurator.java b/common/src/main/java/net/neoforged/gradle/common/conventions/ConventionConfigurator.java
new file mode 100644
index 000000000..1e8c6e922
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/conventions/ConventionConfigurator.java
@@ -0,0 +1,134 @@
+package net.neoforged.gradle.common.conventions;
+
+import net.neoforged.gradle.common.extensions.IdeManagementExtension;
+import net.neoforged.gradle.common.runs.ide.IdeRunIntegrationManager;
+import net.neoforged.gradle.common.util.ConfigurationUtils;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.Conventions;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.Configurations;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.IDE;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.Runs;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.SourceSets;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.ide.IDEA;
+import net.neoforged.gradle.dsl.common.runs.run.RunManager;
+import org.gradle.StartParameter;
+import org.gradle.TaskExecutionRequest;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.tasks.SourceSetContainer;
+import org.gradle.api.tasks.TaskProvider;
+import org.gradle.internal.DefaultTaskExecutionRequest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConventionConfigurator {
+
+ public static void configureConventions(Project project) {
+ final Conventions conventions = project.getExtensions().getByType(Subsystems.class).getConventions();
+ if (!conventions.getIsEnabled().get())
+ return;
+
+ configureRunConventions(project, conventions);
+ configureSourceSetConventions(project, conventions);
+ configureIDEConventions(project, conventions);
+ }
+
+ private static void configureSourceSetConventions(Project project, Conventions conventions) {
+ final SourceSets sourceSets = conventions.getSourceSets();
+ final Configurations configurations = conventions.getConfigurations();
+
+ if (!sourceSets.getIsEnabled().get())
+ return;
+
+ if (configurations.getIsEnabled().get()) {
+ project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> {
+ final Configuration sourceSetLocalRuntimeConfiguration = project.getConfigurations().maybeCreate(ConfigurationUtils.getSourceSetName(sourceSet, configurations.getLocalRuntimeConfigurationPostFix().get()));
+ project.getConfigurations().maybeCreate(ConfigurationUtils.getSourceSetName(sourceSet, configurations.getRunRuntimeConfigurationPostFix().get()));
+
+ final Configuration sourceSetRuntimeClasspath = project.getConfigurations().maybeCreate(sourceSet.getRuntimeClasspathConfigurationName());
+ sourceSetRuntimeClasspath.extendsFrom(sourceSetLocalRuntimeConfiguration);
+ });
+ }
+ }
+
+ private static void configureRunConventions(Project project, Conventions conventions) {
+ final Configurations configurations = conventions.getConfigurations();
+ final Runs runs = conventions.getRuns();
+
+ if (!runs.getIsEnabled().get())
+ return;
+
+ if (!configurations.getIsEnabled().get())
+ return;
+
+ final Configuration runRuntimeConfiguration = project.getConfigurations().maybeCreate(configurations.getRunRuntimeConfigurationName().get());
+
+ project.getExtensions().configure(RunManager.class, runContainer -> runContainer.configureAll(run -> {
+ final Configuration runSpecificRuntimeConfiguration = project.getConfigurations().maybeCreate(ConfigurationUtils.getRunName(run, configurations.getPerRunRuntimeConfigurationPostFix().get()));
+
+ run.getDependencies().getRuntime().add(runRuntimeConfiguration);
+ run.getDependencies().getRuntime().add(runSpecificRuntimeConfiguration);
+ }));
+ }
+
+ private static void configureIDEConventions(Project project, Conventions conventions) {
+ final IDE ideConventions = conventions.getIde();
+ if (!ideConventions.getIsEnabled().get())
+ return;
+
+ configureIDEAIDEConventions(project, ideConventions);
+ }
+
+ private static void configureIDEAIDEConventions(Project project, IDE ideConventions) {
+ final IDEA ideaConventions = ideConventions.getIdea();
+ if (!ideaConventions.getIsEnabled().get())
+ return;
+
+ //We need to configure the tasks to run during sync.
+ final IdeManagementExtension ideManagementExtension = project.getExtensions().getByType(IdeManagementExtension.class);
+ ideManagementExtension
+ .onIdea((innerProject, rootProject, idea, ideaExtension) -> {
+ if (!ideaConventions.getIsEnabled().get())
+ return;
+
+ if (ideaConventions.getShouldUsePostSyncTask().get())
+ return;
+
+ if (!ideManagementExtension.isIdeaSyncing())
+ return;
+
+ final StartParameter startParameter = innerProject.getGradle().getStartParameter();
+ final List taskRequests = new ArrayList<>(startParameter.getTaskRequests());
+
+ final TaskProvider> ideImportTask = ideManagementExtension.getOrCreateIdeImportTask();
+ final List taskPaths = new ArrayList<>();
+
+ final String ideImportTaskName = ideImportTask.getName();
+ final String projectPath = innerProject.getPath();
+
+ String taskPath;
+ if (ideImportTaskName.startsWith(":")) {
+ if (projectPath.equals(":")) {
+ taskPath = ideImportTaskName;
+ } else {
+ taskPath = String.format("%s%s", projectPath, ideImportTaskName);
+ }
+ } else {
+ if (projectPath.equals(":")) {
+ taskPath = String.format(":%s", ideImportTaskName);
+ } else {
+ taskPath = String.format("%s:%s", projectPath, ideImportTaskName);
+ }
+ }
+
+ taskPaths.add(taskPath);
+
+ taskRequests.add(new DefaultTaskExecutionRequest(taskPaths));
+ startParameter.setTaskRequests(taskRequests);
+ });
+
+ IdeRunIntegrationManager.getInstance().configureIdeaConventions(project, ideaConventions);
+ }
+
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/dependency/ExtraJarDependencyManager.java b/common/src/main/java/net/neoforged/gradle/common/dependency/ExtraJarDependencyManager.java
index d6bd37664..784383e8d 100644
--- a/common/src/main/java/net/neoforged/gradle/common/dependency/ExtraJarDependencyManager.java
+++ b/common/src/main/java/net/neoforged/gradle/common/dependency/ExtraJarDependencyManager.java
@@ -1,13 +1,15 @@
package net.neoforged.gradle.common.dependency;
import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
+import net.neoforged.gradle.common.extensions.NeoGradleProblemReporter;
import net.neoforged.gradle.common.runtime.tasks.GenerateExtraJar;
import net.neoforged.gradle.dsl.common.extensions.MinecraftArtifactCache;
import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.DependencyReplacement;
import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.ReplacementResult;
-import net.neoforged.gradle.dsl.common.util.ConfigurationUtils;
+import net.neoforged.gradle.common.util.ConfigurationUtils;
+import net.neoforged.gradle.dsl.common.tasks.WithOutput;
import net.neoforged.gradle.dsl.common.util.DistributionType;
+import net.neoforged.gradle.dsl.common.util.GameArtifact;
import org.apache.commons.lang3.StringUtils;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
@@ -74,20 +76,33 @@ private boolean hasMatchingArtifact(ExternalModuleDependency externalModuleDepen
private ReplacementResult generateReplacement(final Project project, final Dependency dependency) {
final String minecraftVersion = dependency.getVersion();
- if (minecraftVersion == null)
- throw new IllegalArgumentException("Dependency version is null");
+ if (minecraftVersion == null) {
+ final NeoGradleProblemReporter problemReporter = project.getExtensions().getByType(NeoGradleProblemReporter.class);
+ throw problemReporter.throwing(spec -> {
+ spec.id("dependencies.extra-jar", "missingVersion")
+ .contextualLabel("Client-Extra Jar: Missing Version")
+ .details("The dependency %s does not have a version specified".formatted(dependency))
+ .solution("Specify a version for the dependency")
+ .section("common-dep-management-extra-jar");
+ });
+ }
return replacements.computeIfAbsent(minecraftVersion, (v) -> {
final MinecraftArtifactCache minecraftArtifactCacheExtension = project.getExtensions().getByType(MinecraftArtifactCache.class);
+ Map> tasks = minecraftArtifactCacheExtension.cacheGameVersionTasks(project, minecraftVersion, DistributionType.CLIENT);
+
final TaskProvider extraJarTaskProvider = project.getTasks().register("create" + minecraftVersion + StringUtils.capitalize(dependency.getName()) + "ExtraJar", GenerateExtraJar.class, task -> {
- task.getOriginalJar().set(minecraftArtifactCacheExtension.cacheVersionArtifact(minecraftVersion, DistributionType.CLIENT));
+ task.getOriginalJar().set(tasks.get(GameArtifact.CLIENT_JAR).flatMap(WithOutput::getOutput));
task.getOutput().set(project.getLayout().getBuildDirectory().dir("jars/extra/" + dependency.getName()).map(cacheDir -> cacheDir.dir(Objects.requireNonNull(minecraftVersion)).file( dependency.getName() + "-extra.jar")));
+
+ task.dependsOn(tasks.get(GameArtifact.CLIENT_JAR));
});
return new ReplacementResult(
project,
extraJarTaskProvider,
+ project.getConfigurations().detachedConfiguration(),
ConfigurationUtils.temporaryUnhandledConfiguration(
project.getConfigurations(),
"EmptyExtraJarConfigurationFor" + minecraftVersion.replace(".", "_")
diff --git a/common/src/main/java/net/neoforged/gradle/common/dependency/JarJarArtifacts.java b/common/src/main/java/net/neoforged/gradle/common/dependency/JarJarArtifacts.java
index 68d2d396c..e1ef593a1 100644
--- a/common/src/main/java/net/neoforged/gradle/common/dependency/JarJarArtifacts.java
+++ b/common/src/main/java/net/neoforged/gradle/common/dependency/JarJarArtifacts.java
@@ -1,12 +1,14 @@
package net.neoforged.gradle.common.dependency;
import net.neoforged.gradle.common.extensions.JarJarExtension;
+import net.neoforged.gradle.common.extensions.NeoGradleProblemReporter;
import net.neoforged.gradle.dsl.common.dependency.DependencyFilter;
import net.neoforged.gradle.dsl.common.dependency.DependencyManagementObject;
import net.neoforged.gradle.dsl.common.dependency.DependencyVersionInformationHandler;
import net.neoforged.jarjar.metadata.ContainedJarIdentifier;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
+import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.component.ComponentSelector;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
@@ -40,6 +42,7 @@ public abstract class JarJarArtifacts {
private transient final SetProperty includedRootComponents;
private transient final SetProperty includedArtifacts;
+ private final NeoGradleProblemReporter reporter;
private final DependencyFilter dependencyFilter;
private final DependencyVersionInformationHandler dependencyVersionInformationHandler;
@@ -70,7 +73,9 @@ public DependencyVersionInformationHandler getDependencyVersionInformationHandle
return dependencyVersionInformationHandler;
}
- public JarJarArtifacts() {
+ @Inject
+ public JarJarArtifacts(NeoGradleProblemReporter reporter) {
+ this.reporter = reporter;
dependencyFilter = getObjectFactory().newInstance(DefaultDependencyFilter.class);
dependencyVersionInformationHandler = getObjectFactory().newInstance(DefaultDependencyVersionInformationHandler.class);
includedRootComponents = getObjectFactory().setProperty(ResolvedComponentResult.class);
@@ -102,7 +107,7 @@ public void setConfigurations(Collection extends Configuration> configurations
}
}
- private static List getIncludedJars(DependencyFilter filter, DependencyVersionInformationHandler versionHandler, Set rootComponents, Set artifacts) {
+ private List getIncludedJars(DependencyFilter filter, DependencyVersionInformationHandler versionHandler, Set rootComponents, Set artifacts) {
Map versions = new HashMap<>();
Map versionRanges = new HashMap<>();
Set knownIdentifiers = new HashSet<>();
@@ -143,7 +148,13 @@ private static List getIncludedJars(DependencyFilter fil
if (version != null && versionRange != null) {
data.add(new ResolvedJarJarArtifact(result.getFile(), version, versionRange, jarIdentifier.group(), jarIdentifier.artifact()));
} else {
- throw new IllegalStateException("Could not determine version or version range for " + jarIdentifier.group()+":"+jarIdentifier.artifact());
+ throw reporter.throwing(spec ->
+ spec.id("jarjar", "no-version-range")
+ .contextualLabel("Missing version range for " + jarIdentifier.group() + ":" + jarIdentifier.artifact())
+ .solution("Ensure that the version is defined in the dependency management block or that the dependency is included in the JarJar configuration")
+ .details("The version for " + jarIdentifier.group() + ":" + jarIdentifier.artifact() + " could not be determined")
+ .section("common-jar-in-jar-publishing")
+ );
}
}
return data.stream()
@@ -151,7 +162,7 @@ private static List getIncludedJars(DependencyFilter fil
.collect(Collectors.toList());
}
- private static void collectFromComponent(ResolvedComponentResult rootComponent, Set knownIdentifiers, Map versions, Map versionRanges) {
+ private void collectFromComponent(ResolvedComponentResult rootComponent, Set knownIdentifiers, Map versions, Map versionRanges) {
for (DependencyResult result : rootComponent.getDependencies()) {
if (!(result instanceof ResolvedDependencyResult resolvedResult)) {
continue;
@@ -181,7 +192,8 @@ private static void collectFromComponent(ResolvedComponentResult rootComponent,
versionRange = requestedModule.getVersionConstraint().getRequiredVersion();
} else if (isValidVersionRange(requestedModule.getVersionConstraint().getPreferredVersion())) {
versionRange = requestedModule.getVersionConstraint().getPreferredVersion();
- } if (isValidVersionRange(requestedModule.getVersion())) {
+ }
+ if (isValidVersionRange(requestedModule.getVersion())) {
versionRange = requestedModule.getVersion();
}
}
@@ -193,8 +205,13 @@ private static void collectFromComponent(ResolvedComponentResult rootComponent,
String originalVersion = getVersionFrom(originalVariant);
if (!Objects.equals(version, originalVersion)) {
- throw new IllegalStateException("Version mismatch for " + originalVariant.getOwner() + ": available-at directs to " +
- version + " but original is " + originalVersion + " which jarJar cannot handle well; consider depending on the available-at target directly"
+ throw reporter.throwing(spec ->
+ spec.id("jarjar", "version-mismatch")
+ .contextualLabel("Version mismatch for " + originalVariant.getOwner())
+ .solution("Consider depending on the available-at target directly")
+ .details("Version mismatch for " + originalVariant.getOwner() + ": available-at directs to " +
+ version + " but original is " + originalVersion + " which jarJar cannot handle well")
+ .section("common-jar-in-jar-publishing-moves-and-collisions")
);
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/dependency/ResolvedJarJarArtifact.java b/common/src/main/java/net/neoforged/gradle/common/dependency/ResolvedJarJarArtifact.java
index adcb5e784..437408d1f 100644
--- a/common/src/main/java/net/neoforged/gradle/common/dependency/ResolvedJarJarArtifact.java
+++ b/common/src/main/java/net/neoforged/gradle/common/dependency/ResolvedJarJarArtifact.java
@@ -48,7 +48,7 @@ public ContainedVersion createContainedVersion() {
}
public ContainedJarMetadata createContainerMetadata() {
- return new ContainedJarMetadata(createContainedJarIdentifier(), createContainedVersion(), "META-INF/jarjar/"+file.getName(), isObfuscated(file));
+ return new ContainedJarMetadata(createContainedJarIdentifier(), createContainedVersion(), "META-INF/jarjar/"+file.getName(), false);
}
@InputFile
@@ -76,13 +76,4 @@ public String getGroup() {
public String getArtifact() {
return artifact;
}
-
- private static boolean isObfuscated(final File dependency) {
- try(final JarFile jarFile = new JarFile(dependency)) {
- final Manifest manifest = jarFile.getManifest();
- return manifest.getMainAttributes().containsKey("Obfuscated-By");
- } catch (IOException e) {
- throw new RuntimeException("Could not read jar file for dependency", e);
- }
- }
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/AccessTransformersExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/AccessTransformersExtension.java
index d7198e10d..61dcf4637 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/AccessTransformersExtension.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/AccessTransformersExtension.java
@@ -1,6 +1,7 @@
package net.neoforged.gradle.common.extensions;
import net.neoforged.gradle.common.CommonProjectPlugin;
+import net.neoforged.gradle.common.accesstransformers.AccessTransformerPublishing;
import net.neoforged.gradle.common.extensions.base.BaseFilesWithEntriesExtension;
import net.neoforged.gradle.dsl.common.extensions.AccessTransformers;
import org.gradle.api.Action;
@@ -26,15 +27,15 @@ public AccessTransformersExtension(Project project) {
// We have to add these after project evaluation because of dependency replacement making configurations non-lazy; adding them earlier would prevent further addition of dependencies
project.afterEvaluate(p -> {
- p.getConfigurations().maybeCreate(CommonProjectPlugin.ACCESS_TRANSFORMER_CONFIGURATION).fromDependencyCollector(getConsume());
- p.getConfigurations().maybeCreate(CommonProjectPlugin.ACCESS_TRANSFORMER_API_CONFIGURATION).fromDependencyCollector(getConsumeApi());
+ p.getConfigurations().maybeCreate(AccessTransformerPublishing.ACCESS_TRANSFORMER_CONFIGURATION).fromDependencyCollector(getConsume());
+ p.getConfigurations().maybeCreate(AccessTransformerPublishing.ACCESS_TRANSFORMER_API_CONFIGURATION).fromDependencyCollector(getConsumeApi());
});
}
@Override
public void expose(Object path, Action action) {
file(path);
- projectArtifacts.add(CommonProjectPlugin.ACCESS_TRANSFORMER_ELEMENTS_CONFIGURATION, path, action);
+ projectArtifacts.add(AccessTransformerPublishing.ACCESS_TRANSFORMER_ELEMENTS_CONFIGURATION, path, action);
}
@Override
@@ -44,6 +45,6 @@ public void expose(Object path) {
@Override
public void expose(Dependency dependency) {
- projectDependencies.add(CommonProjectPlugin.ACCESS_TRANSFORMER_API_CONFIGURATION, dependency);
+ projectDependencies.add(AccessTransformerPublishing.ACCESS_TRANSFORMER_API_CONFIGURATION, dependency);
}
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/ConfigurationDataExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/ConfigurationDataExtension.java
index f28294c45..d2621ba9a 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/ConfigurationDataExtension.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/ConfigurationDataExtension.java
@@ -3,7 +3,6 @@
import net.minecraftforge.gdi.ConfigurableDSLElement;
import net.neoforged.gradle.dsl.common.extensions.ConfigurationData;
import org.gradle.api.Project;
-import org.jetbrains.annotations.NotNull;
public abstract class ConfigurationDataExtension implements ConfigurableDSLElement, ConfigurationData {
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/EclipseIdeImportAction.java b/common/src/main/java/net/neoforged/gradle/common/extensions/EclipseIdeImportAction.java
deleted file mode 100644
index 16e0a24fc..000000000
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/EclipseIdeImportAction.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.neoforged.gradle.common.extensions;
-
-import org.gradle.api.Project;
-import org.gradle.plugins.ide.eclipse.model.EclipseModel;
-
-
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/ExtensionManager.java b/common/src/main/java/net/neoforged/gradle/common/extensions/ExtensionManager.java
deleted file mode 100644
index 4779bbbe4..000000000
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/ExtensionManager.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package net.neoforged.gradle.common.extensions;
-
-import groovy.lang.MissingPropertyException;
-import org.gradle.api.Project;
-
-import javax.inject.Inject;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-
-public abstract class ExtensionManager {
-
- public static final String EXTENSION_CHECK_PROPERTY_NAME = "neogradle.extensions.%s.type";
-
- private final Project project;
-
- @Inject
- public ExtensionManager(Project project) {
- this.project = project;
- }
-
- @SuppressWarnings("unchecked")
- public void registerExtension(String name, Class publicFacingType, IExtensionCreator defaultCreator) {
- final Object projectExtensionTypeProperty;
- try {
- projectExtensionTypeProperty = project.findProperty(String.format(EXTENSION_CHECK_PROPERTY_NAME, name));
- } catch (MissingPropertyException missingPropertyException) {
- project.getExtensions().add(publicFacingType, name, defaultCreator.apply(project));
- return;
- }
-
- if (projectExtensionTypeProperty == null) {
- project.getExtensions().add(publicFacingType, name, defaultCreator.apply(project));
- return;
- }
-
- if (projectExtensionTypeProperty instanceof IExtensionCreator) {
- try {
- final IExtensionCreator overrideCreator = (IExtensionCreator) projectExtensionTypeProperty;
- project.getExtensions().add(publicFacingType, name, overrideCreator.apply(project));
- return;
- } catch (ClassCastException classCastException) {
- throw new IllegalArgumentException(String.format("Property '%s' is not a valid extension creator for type: %s", String.format(EXTENSION_CHECK_PROPERTY_NAME, name), publicFacingType.getName()), classCastException);
- }
- }
-
- if (projectExtensionTypeProperty instanceof String) {
- final String overrideCreatorName = (String) projectExtensionTypeProperty;
- try {
- final Class> overrideCreatorClass = Class.forName(overrideCreatorName);
- final Constructor> overrideCreatorConstructor = overrideCreatorClass.getConstructor();
- overrideCreatorConstructor.setAccessible(true);
- final Object overrideCreatorCandidate = overrideCreatorConstructor.newInstance();
-
- if (!(overrideCreatorCandidate instanceof IExtensionCreator))
- throw new IllegalArgumentException(String.format("Object of type '%s', returned by property '%S' is not a extension creator.", overrideCreatorClass.getName(), String.format(EXTENSION_CHECK_PROPERTY_NAME, name)));
-
- final IExtensionCreator overrideCreator = (IExtensionCreator) overrideCreatorCandidate;
- project.getLogger().warn("Using Extension Creator Candidate: " + overrideCreatorName + " for extension: " + name + " of type: " + publicFacingType.getName() + ".");
- project.getExtensions().add(publicFacingType, name, overrideCreator.apply(project));
- return;
- } catch (ClassCastException classCastException) {
- throw new IllegalArgumentException(String.format("Property '%s' is not a valid extension creator for type: %s", String.format(EXTENSION_CHECK_PROPERTY_NAME, name), publicFacingType.getName()), classCastException);
- } catch (ClassNotFoundException e) {
- throw new IllegalArgumentException(String.format("Property '%s' targets an unknown class: '%s', so it can not be used as a extension creator", String.format(EXTENSION_CHECK_PROPERTY_NAME, name), overrideCreatorName), e);
- } catch (NoSuchMethodException e) {
- throw new IllegalArgumentException(String.format("Extension Creator Candidate: '%s' has no public no-args constructor. As such it can not be used as a Extension Creator.", overrideCreatorName), e);
- } catch (InvocationTargetException e) {
- throw new IllegalArgumentException("Failed to invoke Extension Creator Candidate: " + overrideCreatorName, e);
- } catch (InstantiationException e) {
- throw new IllegalArgumentException("Failed to instantiate Extension Creator Candidate: " + overrideCreatorName, e);
- } catch (IllegalAccessException e) {
- throw new IllegalArgumentException("Failed to access Extension Creator Candidate: " + overrideCreatorName, e);
- }
- }
-
- throw new IllegalArgumentException("Property '" + String.format(EXTENSION_CHECK_PROPERTY_NAME, name) + "' is not a valid extension creator. It must be either a string of a class name implementing IExtensionCreator, or an instance of IExtensionCreator.");
- }
-
- public static void registerOverride(final Project project, final String name, final IExtensionCreator> creator) {
- project.getExtensions().add(String.format(EXTENSION_CHECK_PROPERTY_NAME, name), creator);
- }
-}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/IdeManagementExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/IdeManagementExtension.java
index 77066793f..8abfbe097 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/IdeManagementExtension.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/IdeManagementExtension.java
@@ -129,10 +129,22 @@ public void registerTaskToRun(TaskProvider> taskToRun) {
//Configure the idePostSync task to depend on the task to run, causing the past in task to become part of the task-tree that is ran after import.
idePostSyncTask.configure(task -> task.dependsOn(taskToRun));
}
+
+
+ /**
+ * Configures the current project to run a task after the IDE import is complete.
+ *
+ * @param taskToRun The task to run
+ */
+ public void registerTaskToRun(Task taskToRun) {
+ final TaskProvider extends Task> idePostSyncTask = getOrCreateIdeImportTask();
+ //Configure the idePostSync task to depend on the task to run, causing the past in task to become part of the task-tree that is ran after import.
+ idePostSyncTask.configure(task -> task.dependsOn(taskToRun));
+ }
@NotNull
- public TaskProvider extends Task> getOrCreateIdeImportTask() {
- final TaskProvider extends Task> idePostSyncTask;
+ public TaskProvider extends IdePostSyncExecutionTask> getOrCreateIdeImportTask() {
+ final TaskProvider extends IdePostSyncExecutionTask> idePostSyncTask;
//Check for the existence of the idePostSync task, which is created by us as a central entry point for all IDE post-sync tasks
if (!project.getTasks().getNames().contains(IDE_POST_SYNC_TASK_NAME)) {
@@ -142,7 +154,7 @@ public TaskProvider extends Task> getOrCreateIdeImportTask() {
//Register the task to run after the IDE import is complete
apply(new IdeImportAction() {
@Override
- public void idea(Project project, IdeaModel idea, ProjectSettings ideaExtension) {
+ public void idea(Project project, Project rootProject, IdeaModel idea, ProjectSettings ideaExtension) {
final Conventions conventions = project.getExtensions().getByType(Subsystems.class).getConventions();
final IDE ideConventions = conventions.getIde();
final IDEA ideaConventions = ideConventions.getIdea();
@@ -170,7 +182,7 @@ public void vscode(Project project, EclipseModel eclipse) {
}
else {
//Found -> Use it.
- idePostSyncTask = project.getTasks().named(IDE_POST_SYNC_TASK_NAME);
+ idePostSyncTask = project.getTasks().named(IDE_POST_SYNC_TASK_NAME, IdePostSyncExecutionTask.class);
}
return idePostSyncTask;
}
@@ -223,7 +235,7 @@ public void onIdea(final IdeaIdeImportAction toPerform) {
final ProjectSettings ideaExt = ((ExtensionAware) model.getProject()).getExtensions().getByType(ProjectSettings.class);
//Configure the project, passing the model, extension, and the relevant project. Which does not need to be the root, but can be.
- toPerform.idea(project, model, ideaExt);
+ toPerform.idea(project, rootProject, model, ideaExt);
});
}
@@ -296,10 +308,11 @@ public interface IdeaIdeImportAction {
* Configure an IntelliJ project.
*
* @param project the project to configure on import
+ * @param rootProject the root project to configure
* @param idea the basic idea gradle extension
* @param ideaExtension JetBrain's extensions to the base idea model
*/
- void idea(Project project, IdeaModel idea, ProjectSettings ideaExtension);
+ void idea(Project project, Project rootProject, IdeaModel idea, ProjectSettings ideaExtension);
}
/**
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/MappingsExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/MappingsExtension.java
index fa675a7a7..b510dd1bd 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/MappingsExtension.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/MappingsExtension.java
@@ -2,7 +2,6 @@
import groovy.lang.MissingMethodException;
import net.minecraftforge.gdi.ConfigurableDSLElement;
-import net.neoforged.gradle.common.util.MappingUtils;
import net.neoforged.gradle.dsl.common.extensions.Mappings;
import net.neoforged.gradle.dsl.common.extensions.Minecraft;
import net.neoforged.gradle.dsl.common.util.NamingConstants;
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/MinecraftArtifactCacheExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/MinecraftArtifactCacheExtension.java
index f2f0b5e35..653a78ea0 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/MinecraftArtifactCacheExtension.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/MinecraftArtifactCacheExtension.java
@@ -4,7 +4,6 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraftforge.gdi.ConfigurableDSLElement;
-import net.neoforged.gradle.common.tasks.MinecraftLauncherFileCacheProvider;
import net.neoforged.gradle.common.tasks.MinecraftVersionManifestFileCacheProvider;
import net.neoforged.gradle.common.util.FileCacheUtils;
import net.neoforged.gradle.common.util.FileDownloadingUtils;
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/NeoGradleProblemReporter.java b/common/src/main/java/net/neoforged/gradle/common/extensions/NeoGradleProblemReporter.java
new file mode 100644
index 000000000..2879fe5c4
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/NeoGradleProblemReporter.java
@@ -0,0 +1,114 @@
+package net.neoforged.gradle.common.extensions;
+
+
+import net.neoforged.gradle.common.util.NeoGradleUtils;
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.problems.ProblemReporter;
+import org.gradle.api.problems.ProblemSpec;
+import org.gradle.api.problems.Severity;
+
+public class NeoGradleProblemReporter {
+
+ private final ProblemReporter delegate;
+
+ public NeoGradleProblemReporter(ProblemReporter delegate) {
+ this.delegate = delegate;
+ }
+
+ public void reporting(Action spec, Logger logger) {
+ delegate.reporting(problemSpec -> {
+ final NeoGradleProblemSpec neoGradleProblemSpec = new NeoGradleProblemSpec();
+ spec.execute(neoGradleProblemSpec);
+
+ final String url = readMeUrl(neoGradleProblemSpec.section);
+
+ problemSpec.id(neoGradleProblemSpec.category, neoGradleProblemSpec.id)
+ .contextualLabel(neoGradleProblemSpec.contextualLabel)
+ .solution(neoGradleProblemSpec.solution)
+ .details(neoGradleProblemSpec.details)
+ .severity(Severity.WARNING)
+ .documentedAt(url);
+
+ neoGradleProblemSpec.log(logger);
+ });
+
+
+ }
+
+ public RuntimeException throwing(Action spec) {
+ return delegate.throwing(problemSpec -> {
+ final NeoGradleProblemSpec neoGradleProblemSpec = new NeoGradleProblemSpec();
+ spec.execute(neoGradleProblemSpec);
+
+ final String url = readMeUrl(neoGradleProblemSpec.section);
+
+ problemSpec.id(neoGradleProblemSpec.category, neoGradleProblemSpec.id)
+ .contextualLabel(neoGradleProblemSpec.contextualLabel)
+ .solution(neoGradleProblemSpec.solution)
+ .details(neoGradleProblemSpec.details)
+ .severity(Severity.ERROR)
+ .withException(new InvalidUserDataException( "(%s) %s.\nPotential Solution: %s.\nMore information: %s".formatted(
+ neoGradleProblemSpec.contextualLabel,
+ neoGradleProblemSpec.details,
+ neoGradleProblemSpec.solution,
+ url
+ )))
+ .documentedAt(url);
+ });
+ }
+
+ private static String readMeUrl(String section) {
+ final String neogradleVersion = NeoGradleUtils.getNeogradleVersion();
+ final String branchMajorVersion = neogradleVersion.split("\\.")[0];
+ final String branchMinorVersion = neogradleVersion.split("\\.")[1];
+ final String branch = "NG_%s.%s".formatted(branchMajorVersion, branchMinorVersion);
+
+ return "https://github.com/neoforged/NeoGradle/blob/%s/README.md#%s".formatted(branch, section);
+ }
+
+ public static final class NeoGradleProblemSpec {
+ private String category;
+ private String id;
+ private String contextualLabel;
+ private String solution;
+ private String details;
+ private String section;
+
+ public NeoGradleProblemSpec id(String category, String id) {
+ this.category = category;
+ this.id = id;
+ return this;
+ }
+
+ public NeoGradleProblemSpec contextualLabel(String contextualLabel) {
+ this.contextualLabel = contextualLabel;
+ return this;
+ }
+
+ public NeoGradleProblemSpec solution(String solution) {
+ this.solution = solution;
+ return this;
+ }
+
+ public NeoGradleProblemSpec details(String details) {
+ this.details = details;
+ return this;
+ }
+
+ public NeoGradleProblemSpec section(String section) {
+ this.section = section;
+ return this;
+ }
+
+ private void log(Logger logger) {
+ logger.warn("-------------------------------------");
+ logger.warn("NeoGradle detected a problem with your project: %s".formatted(contextualLabel));
+ logger.warn("Details: %s".formatted(details));
+ logger.warn("Potential Solution: %s".formatted(solution));
+ logger.warn("More information: %s".formatted(readMeUrl(section)));
+ logger.warn("-------------------------------------");
+ }
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/base/WithEnabledProperty.java b/common/src/main/java/net/neoforged/gradle/common/extensions/base/WithEnabledProperty.java
index 8b770cf54..aba9e720d 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/base/WithEnabledProperty.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/base/WithEnabledProperty.java
@@ -1,6 +1,7 @@
package net.neoforged.gradle.common.extensions.base;
import org.gradle.api.Project;
+import org.gradle.api.file.Directory;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
@@ -14,8 +15,8 @@ public WithEnabledProperty(Project project, String prefix) {
super(project);
this.prefix = prefix;
- getIsEnabled().convention(
- getBooleanProperty("enabled").orElse(true)
+ getIsEnabled().set(
+ getBooleanLocalProperty("enabled", true)
);
};
@@ -23,25 +24,58 @@ public WithEnabledProperty(WithEnabledProperty parent, String prefix) {
super(parent.project);
this.prefix = String.format("%s.%s", parent.prefix, prefix);
- getIsEnabled().convention(
- parent.getIsEnabled().zip(getBooleanProperty("enabled"), (parentEnabled, enabled) -> parentEnabled && enabled).orElse(true)
+ getIsEnabled().set(
+ parent.getIsEnabled().zip(getBooleanLocalProperty("enabled", true), (parentEnabled, enabled) -> parentEnabled && enabled)
);
}
public abstract Property getIsEnabled();
+ protected Provider getStringLocalProperty(String propertyName, String defaultValue) {
+ return super.getStringProperty(String.format("%s.%s", prefix, propertyName), defaultValue);
+ }
+
+ protected Provider getDirectoryLocalProperty(String propertyName, Provider defaultValue) {
+ return super.getDirectoryProperty(String.format("%s.%s", prefix, propertyName), defaultValue);
+ }
+
+ protected Provider getBooleanLocalProperty(String propertyName, boolean defaultValue) {
+ return super.getBooleanProperty(String.format("%s.%s", prefix, propertyName), defaultValue, false);
+ }
+
+ protected Provider> getSpaceSeparatedListLocalProperty(String propertyName, List defaultValue) {
+ return super.getSpaceSeparatedListProperty(String.format("%s.%s", prefix, propertyName), defaultValue);
+ }
+
@Override
- protected Provider getStringProperty(String propertyName) {
- return super.getStringProperty(String.format("%s.%s", prefix, propertyName));
+ protected Provider getStringProperty(String propertyName, String defaultValue) {
+ return getIsEnabled().zip(
+ getStringLocalProperty(propertyName, defaultValue),
+ (enabled, value) -> enabled ? value : ""
+ );
}
@Override
- protected Provider getBooleanProperty(String propertyName) {
- return super.getBooleanProperty(String.format("%s.%s", prefix, propertyName));
+ protected Provider getDirectoryProperty(String propertyName, Provider defaultValue) {
+ return getIsEnabled().zip(
+ getDirectoryLocalProperty(propertyName, defaultValue),
+ (enabled, value) -> enabled ? value : null
+ );
}
@Override
- protected Provider> getSpaceSeparatedListProperty(String propertyName) {
- return super.getSpaceSeparatedListProperty(String.format("%s.%s", prefix, propertyName));
+ protected Provider getBooleanProperty(String propertyName, boolean defaultValue, boolean disabledValue) {
+ return getIsEnabled().zip(
+ getBooleanLocalProperty(propertyName, defaultValue),
+ (enabled, value) -> enabled ? value : disabledValue
+ );
+ }
+
+ @Override
+ protected Provider> getSpaceSeparatedListProperty(String propertyName, List defaultValue) {
+ return getIsEnabled().zip(
+ getSpaceSeparatedListLocalProperty(propertyName, defaultValue),
+ (enabled, value) -> enabled ? value : List.of()
+ );
}
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/base/WithPropertyLookup.java b/common/src/main/java/net/neoforged/gradle/common/extensions/base/WithPropertyLookup.java
index 2fa5b43a1..c91811ea9 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/base/WithPropertyLookup.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/base/WithPropertyLookup.java
@@ -2,8 +2,10 @@
import org.gradle.api.GradleException;
import org.gradle.api.Project;
+import org.gradle.api.file.Directory;
import org.gradle.api.provider.Provider;
+import java.io.File;
import java.util.Arrays;
import java.util.List;
@@ -16,11 +18,18 @@ public WithPropertyLookup(Project project) {
this.project = project;
}
- protected Provider getStringProperty(String propertyName) {
- return this.project.getProviders().gradleProperty(SUBSYSTEM_PROPERTY_PREFIX + propertyName);
+ protected Provider getStringProperty(String propertyName, String defaultValue) {
+ return this.project.getProviders().gradleProperty(SUBSYSTEM_PROPERTY_PREFIX + propertyName)
+ .orElse(defaultValue);
+ }
+
+ protected Provider getDirectoryProperty(String propertyName, Provider defaultValue) {
+ return this.project.getProviders().gradleProperty(SUBSYSTEM_PROPERTY_PREFIX + propertyName)
+ .flatMap(path -> project.getLayout().dir(project.provider(() -> new File(path))))
+ .orElse(defaultValue);
}
- protected Provider getBooleanProperty(String propertyName) {
+ protected Provider getBooleanProperty(String propertyName, boolean defaultValue, boolean disabledValue) {
String fullPropertyName = SUBSYSTEM_PROPERTY_PREFIX + propertyName;
return this.project.getProviders().gradleProperty(fullPropertyName)
.map(value -> {
@@ -29,12 +38,14 @@ protected Provider getBooleanProperty(String propertyName) {
} catch (Exception e) {
throw new GradleException("Gradle Property " + fullPropertyName + " is not set to a boolean value: '" + value + "'");
}
- });
+ })
+ .orElse(defaultValue);
}
- protected Provider> getSpaceSeparatedListProperty(String propertyName) {
+ protected Provider> getSpaceSeparatedListProperty(String propertyName, List defaultValue) {
return this.project.getProviders().gradleProperty(SUBSYSTEM_PROPERTY_PREFIX + propertyName)
- .map(s -> Arrays.asList(s.split("\\s+")));
+ .map(s -> Arrays.asList(s.split("\\s+")))
+ .orElse(defaultValue);
}
public Project getProject() {
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java b/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java
index 7f699e528..2d14ce9de 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java
@@ -13,8 +13,7 @@
import net.neoforged.gradle.dsl.common.extensions.repository.Repository;
import net.neoforged.gradle.dsl.common.tasks.WithOutput;
import net.neoforged.gradle.dsl.common.util.CommonRuntimeUtils;
-import net.neoforged.gradle.dsl.common.util.ConfigurationUtils;
-import org.gradle.api.Action;
+import net.neoforged.gradle.common.util.ConfigurationUtils;
import org.gradle.api.GradleException;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
@@ -49,10 +48,11 @@ public abstract class ReplacementLogic implements ConfigurableDSLElement this.project.getObjects().newInstance(Handler.class, this.project, name));
+
+ //Wire up a replacement handler to each configuration for when a dependency is added.
+ this.project.getConfigurations().configureEach(this::handleConfiguration);
}
@Override
@@ -201,7 +201,7 @@ private void onDependencyAdded(final Configuration configuration, final ModuleDe
//If so handle the prospective replacement data.
if (candidate.isPresent()) {
final ReplacementResult result = candidate.get();
- handleProspectiveDependencyReplacement(dependency, result);
+ handleProspectiveDependencyReplacement(dependency, result, configuration);
}
}
@@ -229,13 +229,23 @@ private void handleDependency(final Configuration configuration, final Dependenc
*
* @param dependency The dependency that is being replaced.
* @param result The replacement result from one of the handlers.
+ * @param target The target configuration in which the replacement will happen.
*/
- private void handleProspectiveDependencyReplacement(final ModuleDependency dependency, final ReplacementResult result) {
+ private void handleProspectiveDependencyReplacement(final ModuleDependency dependency, final ReplacementResult result, Configuration target) {
//Create a new dependency in our dummy repo
final Entry newRepoEntry = createDummyDependency(dependency, result);
+ //We need all tasks tot exist before we can register them.
registerTasks(dependency, result, newRepoEntry);
+ //We create all tasks before we register the sdks, so that the sdks can depend on the tasks.
+ registerSdks(result, target);
+
+ //Additionally we also ensure that we have discovered, and created all configurations that we need as targets
+ //Else we can get into concurrent modification exceptions, when dependencies are replaced and as such replacement
+ //configurations are created during general configuration handling.
+ ConfigurationUtils.findReplacementConfigurations(project, target);
+
if (result instanceof ReplacementAware replacementAware) {
replacementAware.onTargetDependencyAdded();
}
@@ -295,6 +305,21 @@ private void handleDependencyReplacement(Configuration configuration, Dependency
}
}
+ /**
+ * Registers the sdks from the replacement result to the sdk configuration neighbors of the target configuration.
+ *
+ * @param result The replacement result.
+ * @param target The target configuration.
+ */
+ private void registerSdks(ReplacementResult result, Configuration target) {
+ final List sdkConfigurations = ConfigurationUtils.findSdkConfigurationForSourceSetReplacement(project, target);
+
+ //Register the SDK to all relevant sourcesets.
+ for (Configuration sdkConfiguration : sdkConfigurations) {
+ sdkConfiguration.getDependencies().addAll(result.getSdk().getAllDependencies());
+ }
+ }
+
/**
* Method invoked to register the tasks needed to replace a given dependency using the given replacement result.
*
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/RepoEntryDefinition.java b/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/RepoEntryDefinition.java
index 8551e68c3..c3f90d897 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/RepoEntryDefinition.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/RepoEntryDefinition.java
@@ -5,7 +5,6 @@
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.plugins.ExtensionContainer;
import javax.inject.Inject;
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/repository/IvyEntry.java b/common/src/main/java/net/neoforged/gradle/common/extensions/repository/IvyEntry.java
index f4b2ecd64..c85429ab7 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/repository/IvyEntry.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/repository/IvyEntry.java
@@ -2,7 +2,7 @@
import net.minecraftforge.gdi.BaseDSLElement;
import net.neoforged.gradle.dsl.common.extensions.repository.Entry;
-import net.neoforged.gradle.dsl.common.util.ConfigurationUtils;
+import net.neoforged.gradle.common.util.ConfigurationUtils;
import net.neoforged.gradle.util.ModuleDependencyUtils;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/sourcesets/SourceSetDependencyExtensionImpl.java b/common/src/main/java/net/neoforged/gradle/common/extensions/sourcesets/SourceSetDependencyExtensionImpl.java
new file mode 100644
index 000000000..10156e696
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/sourcesets/SourceSetDependencyExtensionImpl.java
@@ -0,0 +1,57 @@
+package net.neoforged.gradle.common.extensions.sourcesets;
+
+import net.neoforged.gradle.common.extensions.NeoGradleProblemReporter;
+import net.neoforged.gradle.common.util.SourceSetUtils;
+import net.neoforged.gradle.dsl.common.extensions.sourceset.SourceSetDependencyExtension;
+import net.neoforged.gradle.dsl.common.extensions.sourceset.SourceSetInheritanceExtension;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.SourceSet;
+
+import javax.inject.Inject;
+
+public abstract class SourceSetDependencyExtensionImpl implements SourceSetDependencyExtension {
+
+ private final Project project;
+ private final SourceSet target;
+
+ @Inject
+ public SourceSetDependencyExtensionImpl(SourceSet target) {
+ this.project = SourceSetUtils.getProject(target);
+ this.target = target;
+ }
+
+ @Override
+ public Project getProject() {
+ return project;
+ }
+
+ @Override
+ public void on(SourceSet sourceSet) {
+ final Project sourceSetProject = SourceSetUtils.getProject(sourceSet);
+
+ if (sourceSetProject != project) {
+ final NeoGradleProblemReporter reporter = project.getExtensions().getByType(NeoGradleProblemReporter.class);
+ throw reporter.throwing(spec -> spec
+ .id("source-set-dependencies", "wrong-project")
+ .contextualLabel("on(SourceSet)")
+ .details("SourceSet '%s' is not from the same project as the current SourceSet '%s', as such it can not depend on it".formatted(sourceSet.getName(), project.getName()))
+ .section("common-dep-sourceset-management-depend")
+ .solution("Ensure that the SourceSet is from the same project as the current SourceSet")
+ );
+ }
+
+ final SourceSetInheritanceExtension sourceSetInheritanceExtension = target.getExtensions().getByType(SourceSetInheritanceExtension.class);
+ sourceSetInheritanceExtension.from(sourceSet);
+
+ target.setCompileClasspath(
+ target.getCompileClasspath().plus(
+ sourceSet.getOutput()
+ )
+ );
+ target.setRuntimeClasspath(
+ target.getRuntimeClasspath().plus(
+ sourceSet.getOutput()
+ )
+ );
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/sourcesets/SourceSetInheritanceExtensionImpl.java b/common/src/main/java/net/neoforged/gradle/common/extensions/sourcesets/SourceSetInheritanceExtensionImpl.java
new file mode 100644
index 000000000..b4ce6bcd5
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/sourcesets/SourceSetInheritanceExtensionImpl.java
@@ -0,0 +1,53 @@
+package net.neoforged.gradle.common.extensions.sourcesets;
+
+import net.neoforged.gradle.common.extensions.NeoGradleProblemReporter;
+import net.neoforged.gradle.common.util.SourceSetUtils;
+import net.neoforged.gradle.dsl.common.extensions.sourceset.SourceSetInheritanceExtension;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.SourceSet;
+
+import javax.inject.Inject;
+
+public abstract class SourceSetInheritanceExtensionImpl implements SourceSetInheritanceExtension {
+
+ private final Project project;
+ private final SourceSet target;
+
+ @Inject
+ public SourceSetInheritanceExtensionImpl(SourceSet target) {
+ this.project = SourceSetUtils.getProject(target);
+ this.target = target;
+ }
+
+ @Override
+ public Project getProject() {
+ return project;
+ }
+
+ @Override
+ public void from(SourceSet sourceSet) {
+ final Project sourceSetProject = SourceSetUtils.getProject(sourceSet);
+
+ if (sourceSetProject != project) {
+ final NeoGradleProblemReporter reporter = project.getExtensions().getByType(NeoGradleProblemReporter.class);
+ throw reporter.throwing(spec -> spec
+ .id("source-set-inheritance", "wrong-project")
+ .contextualLabel("from(SourceSet)")
+ .details("SourceSet '%s' is not from the same project as the current SourceSet '%s', as such it can not inherit from it".formatted(sourceSet.getName(), project.getName()))
+ .section("common-dep-sourceset-management-inherit")
+ .solution("Ensure that the SourceSet is from the same project as the current SourceSet")
+ );
+ }
+
+ target.setCompileClasspath(
+ target.getCompileClasspath().plus(
+ sourceSet.getCompileClasspath()
+ )
+ );
+ target.setRuntimeClasspath(
+ target.getRuntimeClasspath().plus(
+ sourceSet.getRuntimeClasspath()
+ )
+ );
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/ConventionsExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/ConventionsExtension.java
index e7e0d6f18..9c4991f60 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/ConventionsExtension.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/ConventionsExtension.java
@@ -8,8 +8,9 @@
import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.Runs;
import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.SourceSets;
import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.ide.IDEA;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.runs.DevLogin;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.runs.RenderDoc;
import org.gradle.api.Project;
-import org.gradle.api.provider.Property;
import javax.inject.Inject;
@@ -56,10 +57,10 @@ public static abstract class ConfigurationsExtension extends WithEnabledProperty
public ConfigurationsExtension(WithEnabledProperty parent) {
super(parent, "configurations");
- getLocalRuntimeConfigurationPostFix().convention(getStringProperty("localRuntimeConfigurationPostFix").orElse("LocalRuntime"));
- getRunRuntimeConfigurationPostFix().convention(getStringProperty("perSourceSetRunRuntimeConfigurationPostFix").orElse("LocalRunRuntime"));
- getPerRunRuntimeConfigurationPostFix().convention(getStringProperty("perRunRuntimeConfigurationPostFix").orElse("Run"));
- getRunRuntimeConfigurationName().convention(getStringProperty("runRuntimeConfigurationName").orElse("runs"));
+ getLocalRuntimeConfigurationPostFix().convention(getStringProperty("localRuntimeConfigurationPostFix", "LocalRuntime"));
+ getRunRuntimeConfigurationPostFix().convention(getStringProperty("perSourceSetRunRuntimeConfigurationPostFix", "LocalRunRuntime"));
+ getPerRunRuntimeConfigurationPostFix().convention(getStringProperty("perRunRuntimeConfigurationPostFix", "Run"));
+ getRunRuntimeConfigurationName().convention(getStringProperty("runRuntimeConfigurationName", "runs"));
}
}
@@ -69,8 +70,9 @@ public static abstract class SourceSetsExtension extends WithEnabledProperty imp
public SourceSetsExtension(WithEnabledProperty parent) {
super(parent, "sourcesets");
- getShouldMainSourceSetBeAutomaticallyAddedToRuns().convention(getBooleanProperty("automatic-inclusion").orElse(true));
- getShouldSourceSetsLocalRunRuntimesBeAutomaticallyAddedToRuns().convention(getBooleanProperty("automatic-inclusion-local-run-runtime").orElse(true));
+ getShouldMainSourceSetBeAutomaticallyAddedToRuns().convention(getBooleanProperty("automatic-inclusion", true, false));
+ getShouldTestSourceSetBeAutomaticallyAddedToRuns().convention(getBooleanProperty("automatic-inclusion-test", false, false));
+ getShouldSourceSetsLocalRunRuntimesBeAutomaticallyAddedToRuns().convention(getBooleanProperty("automatic-inclusion-local-run-runtime", true, false));
}
}
@@ -93,12 +95,48 @@ public IDEA getIdea() {
public static abstract class RunsExtension extends WithEnabledProperty implements BaseDSLElement, Runs {
+ private final DevLogin devLogin;
+ private final RenderDoc renderDoc;
+
@Inject
public RunsExtension(WithEnabledProperty parent) {
super(parent, "runs");
- getShouldDefaultRunsBeCreated().convention(getBooleanProperty("create-default-run-per-type").orElse(true));
- getShouldDefaultTestTaskBeReused().convention(getBooleanProperty("reuse-default-test-task").orElse(true));
+ this.devLogin = getProject().getObjects().newInstance(DevLoginExtension.class, this);
+ this.renderDoc = getProject().getObjects().newInstance(RenderDocExtension.class, this);
+
+ getShouldDefaultRunsBeCreated().convention(getBooleanProperty("create-default-run-per-type", true, false));
+ getShouldDefaultTestTaskBeReused().convention(getBooleanProperty("reuse-default-test-task", false, false));
+ }
+
+ @Override
+ public DevLogin getDevLogin() {
+ return devLogin;
+ }
+
+ @Override
+ public RenderDoc getRenderDoc() {
+ return renderDoc;
+ }
+ }
+
+ public static abstract class DevLoginExtension extends WithEnabledProperty implements BaseDSLElement, DevLogin {
+
+ @Inject
+ public DevLoginExtension(WithEnabledProperty parent) {
+ super(parent, "devlogin");
+
+ getConventionForRun().convention(getBooleanProperty("conventionForRun", false, false));
+ }
+ }
+
+ public static abstract class RenderDocExtension extends WithEnabledProperty implements BaseDSLElement, RenderDoc {
+
+ @Inject
+ public RenderDocExtension(WithEnabledProperty parent) {
+ super(parent, "renderdoc");
+
+ getConventionForRun().convention(getBooleanProperty("conventionForRun", false, false));
}
}
@@ -108,9 +146,9 @@ public static abstract class IDEAExtension extends WithEnabledProperty implement
public IDEAExtension(WithEnabledProperty parent) {
super(parent, "idea");
- getShouldUseCompilerDetection().convention(getBooleanProperty("compiler-detection").orElse(true));
- getCompilerOutputDir().convention(getProject().getLayout().getProjectDirectory().dir(getStringProperty("compiler-output-dir").orElse("out")));
- getShouldUsePostSyncTask().convention(getBooleanProperty("use-post-sync-task").orElse(false));
+ getShouldUseCompilerDetection().convention(getBooleanProperty("compiler-detection", true, false));
+ getShouldUsePostSyncTask().convention(getBooleanProperty("use-post-sync-task", false, false));
+ getShouldReconfigureTemplatesForTests().convention(getBooleanProperty("reconfigure-unit-test-templates", false, false));
}
}
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/SubsystemsExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/SubsystemsExtension.java
index 28f8745dd..d9df0ff0e 100644
--- a/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/SubsystemsExtension.java
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/SubsystemsExtension.java
@@ -1,8 +1,10 @@
package net.neoforged.gradle.common.extensions.subsystems;
import net.minecraftforge.gdi.ConfigurableDSLElement;
+import net.neoforged.gradle.common.extensions.base.WithEnabledProperty;
import net.neoforged.gradle.common.extensions.base.WithPropertyLookup;
import net.neoforged.gradle.dsl.common.extensions.subsystems.*;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.tools.RenderDocTools;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
@@ -18,97 +20,91 @@
public abstract class SubsystemsExtension extends WithPropertyLookup implements ConfigurableDSLElement, Subsystems {
private final Conventions conventions;
+ private final Parchment parchment;
+ private final Tools tools;
+
@Inject
public SubsystemsExtension(Project project) {
super(project);
this.conventions = project.getObjects().newInstance(ConventionsExtension.class, project);
+ this.parchment = project.getObjects().newInstance(ParchmentExtensions.class, project);
+ this.tools = project.getObjects().newInstance(ToolsExtension.class, project);
configureDecompilerDefaults();
configureRecompilerDefaults();
configureParchmentDefaults();
configureToolsDefaults();
configureDevLoginDefaults();
+ configureRenderDocDefaults();
+ }
+
+ private void configureRenderDocDefaults() {
+ RenderDoc devLogin = getRenderDoc();
+ devLogin.getConfigurationSuffix().convention(
+ getStringProperty("renderDoc.configurationSuffix", "RenderDocLocalOnly")
+ );
}
private void configureDevLoginDefaults() {
DevLogin devLogin = getDevLogin();
- devLogin.getEnabled().convention(
- getBooleanProperty("devLogin.enabled").orElse(true)
- );
devLogin.getMainClass().convention(
- getStringProperty("devLogin.mainClass").orElse(DEVLOGIN_MAIN_CLASS)
+ getStringProperty("devLogin.mainClass", DEVLOGIN_MAIN_CLASS)
);
devLogin.getConfigurationSuffix().convention(
- getStringProperty("devLogin.configurationSuffix").orElse("DevLoginLocalOnly")
- );
- devLogin.getConventionForRun().convention(
- getBooleanProperty("devLogin.conventionForRun").orElse(false)
+ getStringProperty("devLogin.configurationSuffix", "DevLoginLocalOnly")
);
}
private void configureToolsDefaults() {
Tools tools = getTools();
tools.getJST().convention(
- getStringProperty("tools.jst").orElse(JST_TOOL_ARTIFACT)
+ getStringProperty("tools.jst", JST_TOOL_ARTIFACT)
);
tools.getDevLogin().convention(
- getStringProperty("tools.devLogin").orElse(DEVLOGIN_TOOL_ARTIFACT)
+ getStringProperty("tools.devLogin", DEVLOGIN_TOOL_ARTIFACT)
+ );
+
+ RenderDocTools renderDocTools = tools.getRenderDoc();
+ renderDocTools.getRenderDocPath().convention(
+ getDirectoryProperty("tools.renderDoc.path", getProject().getLayout().getBuildDirectory().dir("renderdoc"))
+ );
+ renderDocTools.getRenderDocVersion().convention(
+ getStringProperty("tools.renderDoc.version", "1.33")
+ );
+ renderDocTools.getRenderNurse().convention(
+ getStringProperty("tools.renderDoc.renderNurse", RENDERNURSE_TOOL_ARTIFACT)
);
}
private void configureDecompilerDefaults() {
Decompiler decompiler = getDecompiler();
- decompiler.getMaxMemory().convention(getStringProperty("decompiler.maxMemory"));
- decompiler.getMaxThreads().convention(getStringProperty("decompiler.maxThreads").map(Integer::parseUnsignedInt));
- decompiler.getLogLevel().convention(getStringProperty("decompiler.logLevel").map(s -> {
+ decompiler.getMaxMemory().convention(getStringProperty("decompiler.maxMemory", "4g"));
+ decompiler.getMaxThreads().convention(getStringProperty("decompiler.maxThreads", "0").map(Integer::parseUnsignedInt));
+ decompiler.getLogLevel().convention(getStringProperty("decompiler.logLevel", "ERROR").map(s -> {
try {
return DecompilerLogLevel.valueOf(s.toUpperCase(Locale.ROOT));
} catch (Exception e) {
throw new GradleException("Unknown DecompilerLogLevel: " + s + ". Available options: " + Arrays.toString(DecompilerLogLevel.values()));
}
}));
- decompiler.getJvmArgs().convention(getSpaceSeparatedListProperty("decompiler.jvmArgs").orElse(Collections.emptyList()));
+ decompiler.getJvmArgs().convention(getSpaceSeparatedListProperty("decompiler.jvmArgs", Collections.emptyList()));
}
private void configureRecompilerDefaults() {
Recompiler recompiler = getRecompiler();
- recompiler.getArgs().convention(getSpaceSeparatedListProperty("recompiler.args").orElse(Collections.emptyList()));
- recompiler.getJvmArgs().convention(getSpaceSeparatedListProperty("recompiler.jvmArgs").orElse(Collections.emptyList()));
- recompiler.getMaxMemory().convention(getStringProperty("recompiler.maxMemory").orElse(DEFAULT_RECOMPILER_MAX_MEMORY));
- recompiler.getShouldFork().convention(getBooleanProperty("recompiler.shouldFork").orElse(true));
+ recompiler.getArgs().convention(getSpaceSeparatedListProperty("recompiler.args", Collections.emptyList()));
+ recompiler.getJvmArgs().convention(getSpaceSeparatedListProperty("recompiler.jvmArgs", Collections.emptyList()));
+ recompiler.getMaxMemory().convention(getStringProperty("recompiler.maxMemory", DEFAULT_RECOMPILER_MAX_MEMORY));
+ recompiler.getShouldFork().convention(getBooleanProperty("recompiler.shouldFork", true, false));
}
private void configureParchmentDefaults() {
Parchment parchment = getParchment();
- parchment.getParchmentArtifact().convention(
- getStringProperty("parchment.parchmentArtifact").orElse(
- parchment.getMinecraftVersion()
- .zip(parchment.getMappingsVersion(), (minecraftVersion, mappingVersion) -> {
- return DEFAULT_PARCHMENT_GROUP
- + ":" + DEFAULT_PARCHMENT_ARTIFACT_PREFIX + minecraftVersion
- + ":" + mappingVersion
- + "@zip";
- })
- )
- );
- parchment.getConflictPrefix().convention("p_");
- parchment.getMinecraftVersion().convention(
- getStringProperty("parchment.minecraftVersion")
- );
- parchment.getMappingsVersion().convention(
- getStringProperty("parchment.mappingsVersion")
- );
- parchment.getAddRepository().convention(
- getBooleanProperty("parchment.addRepository").orElse(true)
- );
- parchment.getEnabled().convention(parchment.getParchmentArtifact()
- .map(s -> !s.isEmpty()).orElse(getBooleanProperty("parchment.enabled").orElse(false)));
-
// Add a filtered parchment repository automatically if enabled
project.afterEvaluate(p -> {
- if (!parchment.getEnabled().get() || !parchment.getAddRepository().get()) {
+ if (!parchment.getIsEnabled().get() || !parchment.getAddRepository().get()) {
return;
}
MavenArtifactRepository repo = p.getRepositories().maven(m -> {
@@ -126,4 +122,47 @@ private void configureParchmentDefaults() {
public Conventions getConventions() {
return conventions;
}
+
+ @Override
+ public Parchment getParchment() {
+ return parchment;
+ }
+
+ @Override
+ public Tools getTools() {
+ return tools;
+ }
+
+ public static abstract class ParchmentExtensions extends WithEnabledProperty implements Parchment {
+
+ @Inject
+ public ParchmentExtensions(Project project) {
+ super(project, "parchment");
+
+ getParchmentArtifact().convention(
+ getStringLocalProperty("parchmentArtifact", "").orElse(
+ getMinecraftVersion()
+ .zip(getMappingsVersion(), (minecraftVersion, mappingVersion) -> {
+ return DEFAULT_PARCHMENT_GROUP
+ + ":" + DEFAULT_PARCHMENT_ARTIFACT_PREFIX + minecraftVersion
+ + ":" + mappingVersion
+ + "@zip";
+ })
+ )
+ );
+ getConflictPrefix().convention("p_");
+ getMinecraftVersion().convention(
+ getStringProperty("minecraftVersion", null)
+ );
+ getMappingsVersion().convention(
+ getStringProperty("mappingsVersion", null)
+ );
+ getAddRepository().convention(
+ getBooleanProperty("addRepository", true, false)
+ );
+ getIsEnabled().set(getParchmentArtifact()
+ .map(s -> !s.isEmpty()).orElse(getBooleanLocalProperty("enabled", true))
+ );
+ }
+ }
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/ToolsExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/ToolsExtension.java
new file mode 100644
index 000000000..fca14ab96
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/ToolsExtension.java
@@ -0,0 +1,23 @@
+package net.neoforged.gradle.common.extensions.subsystems;
+
+import net.neoforged.gradle.common.extensions.subsystems.tools.RenderDocToolsImpl;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.Tools;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.tools.RenderDocTools;
+import org.gradle.api.Project;
+
+import javax.inject.Inject;
+
+public abstract class ToolsExtension implements Tools {
+
+ private final RenderDocTools renderDocTools;
+
+ @Inject
+ public ToolsExtension(Project project) {
+ renderDocTools = project.getObjects().newInstance(RenderDocToolsImpl.class, project);
+ }
+
+ @Override
+ public RenderDocTools getRenderDoc() {
+ return renderDocTools;
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/tools/RenderDocToolsImpl.java b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/tools/RenderDocToolsImpl.java
new file mode 100644
index 000000000..627d44e40
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/tools/RenderDocToolsImpl.java
@@ -0,0 +1,25 @@
+package net.neoforged.gradle.common.extensions.subsystems.tools;
+
+import net.neoforged.gradle.dsl.common.extensions.subsystems.tools.RenderDocTools;
+import org.gradle.api.Project;
+
+import javax.inject.Inject;
+
+public abstract class RenderDocToolsImpl implements RenderDocTools {
+
+ private final Project project;
+
+ @Inject
+ public RenderDocToolsImpl(Project project) {
+ this.project = project;
+
+ getRenderDocPath().convention(getProject().getLayout().getBuildDirectory().dir("renderdoc"));
+ getRenderDocVersion().convention("1.33");
+ getRenderNurse().convention("net.neoforged:render-nurse:0.0.12");
+ }
+
+ @Override
+ public Project getProject() {
+ return project;
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/rules/LaterAddedReplacedDependencyRule.java b/common/src/main/java/net/neoforged/gradle/common/rules/LaterAddedReplacedDependencyRule.java
index 554159879..2ed9fa1b6 100644
--- a/common/src/main/java/net/neoforged/gradle/common/rules/LaterAddedReplacedDependencyRule.java
+++ b/common/src/main/java/net/neoforged/gradle/common/rules/LaterAddedReplacedDependencyRule.java
@@ -1,29 +1,19 @@
package net.neoforged.gradle.common.rules;
-import net.neoforged.gradle.common.util.constants.RunsConstants;
-import net.neoforged.gradle.common.util.run.RunsUtil;
import net.neoforged.gradle.dsl.common.extensions.subsystems.Conventions;
import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems;
import net.neoforged.gradle.dsl.common.runs.run.Run;
-import org.apache.tools.ant.TaskContainer;
-import org.gradle.api.NamedDomainObjectCollection;
-import org.gradle.api.NamedDomainObjectContainer;
+import net.neoforged.gradle.dsl.common.runs.run.RunManager;
import org.gradle.api.Project;
import org.gradle.api.Rule;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ConfigurationContainer;
-
-import java.util.HashSet;
-import java.util.Set;
public class LaterAddedReplacedDependencyRule implements Rule {
private final Project project;
- private final NamedDomainObjectContainer runs;
+ private final RunManager runs;
- @SuppressWarnings("unchecked")
public LaterAddedReplacedDependencyRule(Project project) {
- this.runs = (NamedDomainObjectContainer) project.getExtensions().getByName(RunsConstants.Extensions.RUNS);
+ this.runs = project.getExtensions().getByType(RunManager.class);
this.project = project;
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/ide/IdeRunIntegrationManager.java b/common/src/main/java/net/neoforged/gradle/common/runs/ide/IdeRunIntegrationManager.java
index c9d850608..a973d8129 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runs/ide/IdeRunIntegrationManager.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/ide/IdeRunIntegrationManager.java
@@ -7,15 +7,17 @@
import net.neoforged.elc.configs.LaunchGroup;
import net.neoforged.gradle.common.extensions.IdeManagementExtension;
import net.neoforged.gradle.common.runs.ide.extensions.IdeaRunExtensionImpl;
+import net.neoforged.gradle.common.runs.ide.idea.JUnitWithBeforeRun;
import net.neoforged.gradle.common.runs.run.RunImpl;
import net.neoforged.gradle.common.util.ProjectUtils;
import net.neoforged.gradle.common.util.SourceSetUtils;
-import net.neoforged.gradle.common.util.constants.RunsConstants;
import net.neoforged.gradle.common.util.run.RunsUtil;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems;
import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.ide.IDEA;
import net.neoforged.gradle.dsl.common.runs.ide.extensions.IdeaRunExtension;
import net.neoforged.gradle.dsl.common.runs.idea.extensions.IdeaRunsExtension;
import net.neoforged.gradle.dsl.common.runs.run.Run;
+import net.neoforged.gradle.dsl.common.runs.run.RunManager;
import net.neoforged.gradle.dsl.common.util.CommonRuntimeUtils;
import net.neoforged.gradle.util.FileUtils;
import net.neoforged.vsclc.BatchedLaunchWriter;
@@ -24,19 +26,20 @@
import net.neoforged.vsclc.attribute.ShortCmdBehaviour;
import net.neoforged.vsclc.writer.WritingMode;
import org.apache.commons.lang3.StringUtils;
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.GradleException;
import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
import org.gradle.api.plugins.ExtensionAware;
-import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Copy;
import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.jvm.tasks.ProcessResources;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
import org.gradle.plugins.ide.idea.model.IdeaModel;
import org.gradle.plugins.ide.idea.model.IdeaProject;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.gradle.ext.*;
import javax.xml.stream.XMLStreamException;
@@ -50,8 +53,6 @@
import java.util.List;
import java.util.Map;
import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* A simple manager which configures runs based on the IDE it is attached to.
@@ -66,16 +67,15 @@ public static IdeRunIntegrationManager getInstance() {
private IdeRunIntegrationManager() {
}
-
-
+
/**
* Configures the IDE integration DSLs.
*
* @param project The project to configure.
*/
public void setup(final Project project) {
- project.getExtensions().configure(RunsConstants.Extensions.RUNS, (Action>) runs -> runs.configureEach(run -> {
- run.getExtensions().create(IdeaRunExtension.class, "idea", IdeaRunExtensionImpl.class, project, run);
+ project.getExtensions().configure(RunManager.class, runs -> runs.configureAll(run -> {
+ setupRun(project, run);
}));
final Project rootProject = project.getRootProject();
@@ -86,7 +86,11 @@ public void setup(final Project project) {
extensionAware.getExtensions().create("runs", IdeaRunsExtension.class, project);
}
}
-
+
+ public void setupRun(Project project, Run run) {
+ run.getExtensions().create(IdeaRunExtension.class, "idea", IdeaRunExtensionImpl.class, project, run);
+ }
+
/**
* Configures the IDE integration to run runs as tasks from the IDE.
*
@@ -118,59 +122,142 @@ public void configureIdeaConventions(Project project, IDEA ideaConventions) {
return FileUtils.contains(GradleXml, "");
})
);
-
- runsExtension.getOutDirectory().convention(
- ideaConventions.getCompilerOutputDir()
- );
}
private static final class RunsImportAction implements IdeManagementExtension.IdeImportAction {
-
+
+ private void common(Project project) {
+ final SourceSetContainer container = project.getExtensions().getByType(SourceSetContainer.class);
+ container.forEach(sourceSet -> {
+ final Configuration compileClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName());
+ final Configuration runtimeClasspath = project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName());
+
+ compileClasspath.resolve();
+ runtimeClasspath.resolve();
+ });
+ }
+
@Override
- public void idea(Project project, IdeaModel idea, ProjectSettings ideaExtension) {
- final RunConfigurationContainer ideaRuns = ((ExtensionAware) ideaExtension).getExtensions().getByType(RunConfigurationContainer.class);
-
- project.getExtensions().configure(RunsConstants.Extensions.RUNS, (Action>) runs -> runs.getAsMap().forEach((name, run) -> {
- final String nameWithoutSpaces = name.replace(" ", "-");
- final String runName = StringUtils.capitalize(project.getName() + ": " + StringUtils.capitalize(nameWithoutSpaces));
-
- final RunImpl runImpl = (RunImpl) run;
-
- //Do not generate a run configuration for unit tests
- if (runImpl.getIsJUnit().get())
- return;
-
- final IdeaRunExtension runIdeaConfig = run.getExtensions().getByType(IdeaRunExtension.class);
- final TaskProvider> ideBeforeRunTask = createIdeBeforeRunTask(project, nameWithoutSpaces, run, runImpl);
-
+ public void idea(Project project, Project rootProject, IdeaModel idea, ProjectSettings ideaExtension) {
+ project.afterEvaluate(evaluatedProject -> {
+ common(project);
+
+ final RunConfigurationContainer ideaRuns = ((ExtensionAware) ideaExtension).getExtensions().getByType(RunConfigurationContainer.class);
+
+ attemptJUnitWithBeforeRunRegistration(project, ideaRuns);
+
+ project.getExtensions().configure(RunManager.class, runs -> runs.all(run -> createIdeaRun(project, run, ideaRuns, false)));
+
+ if (project.getExtensions().getByType(Subsystems.class).getConventions().getIde().getIdea().getShouldReconfigureTemplatesForTests().get()) {
+ final ExtensionAware ideaModelExtensions = (ExtensionAware) idea;
+ final Run ideaDefaultUnitTestRun = ideaModelExtensions.getExtensions().getByType(Run.class);
+
+ //We finally know that the user wants this to be registered, additionally the IDE integration resolved the lazy dependencies
+ //here, so we can now with a gentle heart register the run type, and the run configuration conversion.
+ RunManager runManager = project.getExtensions().getByType(RunManager.class);
+ runManager.addInternal(ideaDefaultUnitTestRun);
+
+ createIdeaRun(project, ideaDefaultUnitTestRun, ideaRuns, true);
+ }
+ });
+ }
+
+ private static void attemptJUnitWithBeforeRunRegistration(Project project, RunConfigurationContainer ideaRuns) {
+ try {
+ ideaRuns.registerFactory(JUnitWithBeforeRun.class, ideaTaskName -> project.getObjects().newInstance(JUnitWithBeforeRun.class, project, ideaTaskName));
+ } catch (GradleException ignored) {
+ //Gets thrown when the factory is already registered
+ }
+ }
+
+ private void createIdeaRun(Project project, Run run, RunConfigurationContainer ideaRuns, boolean defaultRun) {
+ if (!defaultRun && !run.getShouldExportToIDE().get())
+ return;
+
+ final String nameWithoutSpaces = run.getName().replace(" ", "-");
+ final String runName = StringUtils.capitalize(project.getName() + ": " + StringUtils.capitalize(nameWithoutSpaces));
+
+ final RunImpl runImpl = (RunImpl) run;
+
+ final TaskProvider> ideBeforeRunTask = getOrCreateIdeBeforeRunTask(project, runImpl);
+
+ if (RunsUtil.isRunWithIdea(project)) {
+ final List> copyProcessResourcesTasks = createIntelliJCopyResourcesTasks(run);
+
+ ideBeforeRunTask.configure(task -> copyProcessResourcesTasks.forEach(task::dependsOn));
+ }
+
+ //Do not generate a run configuration for unit tests
+ if (!runImpl.getIsJUnit().get()) {
ideaRuns.register(runName, Application.class, ideaRun -> {
runImpl.getWorkingDirectory().get().getAsFile().mkdirs();
-
+
ideaRun.setMainClass(runImpl.getMainClass().get());
ideaRun.setWorkingDirectory(runImpl.getWorkingDirectory().get().getAsFile().getAbsolutePath());
- ideaRun.setJvmArgs(quoteAndJoin(runImpl.realiseJvmArguments()));
- ideaRun.moduleRef(project, runIdeaConfig.getPrimarySourceSet().get());
- ideaRun.setProgramParameters(quoteAndJoin(runImpl.getProgramArguments().get()));
- ideaRun.setEnvs(adaptEnvironment(runImpl, RunsUtil::buildRunWithIdeaModClasses));
+ ideaRun.setJvmArgs(RunsUtil.escapeAndJoin(RunsUtil.deduplicateElementsFollowingEachOther(runImpl.realiseJvmArguments().stream()).toList()));
+ ideaRun.setModuleName(RunsUtil.getIntellijModuleName(run.getExtensions().getByType(IdeaRunExtension.class).getPrimarySourceSet().get()));
+ ideaRun.setProgramParameters(RunsUtil.escapeAndJoin(RunsUtil.deduplicateElementsFollowingEachOther(runImpl.getArguments().get().stream()).toList()));
+ ideaRun.setEnvs(adaptEnvironment(runImpl, multimapProvider -> RunsUtil.buildRunWithIdeaModClasses(multimapProvider, RunsUtil.IdeaCompileType.Production)));
ideaRun.setShortenCommandLine(ShortenCommandLine.ARGS_FILE);
-
+
ideaRun.beforeRun(beforeRuns -> {
beforeRuns.create("Build", Make.class);
-
+
beforeRuns.create("Prepare Run", GradleTask.class, gradleTask -> {
gradleTask.setTask(ideBeforeRunTask.get());
});
});
+
+ ideaRun.setDefaults(defaultRun);
});
- }));
-
-
+ } else {
+ ideaRuns.register(runName, JUnitWithBeforeRun.class, ideaRun -> {
+ final RunsUtil.PreparedUnitTestEnvironment preparedUnitTestEnvironment = RunsUtil.prepareUnitTestEnvironment(run);
+
+ ideaRun.setWorkingDirectory(runImpl.getWorkingDirectory().get().getAsFile().getAbsolutePath());
+ ideaRun.setModuleName(RunsUtil.getIntellijModuleName(run.getExtensions().getByType(IdeaRunExtension.class).getPrimarySourceSet().get()));
+
+ ideaRun.setPackageName(runImpl.getTestScope().getPackageName().getOrNull());
+ ideaRun.setDirectory(runImpl.getTestScope().getDirectory().map(dir -> dir.getAsFile().getAbsolutePath()).getOrNull());
+ ideaRun.setPattern(runImpl.getTestScope().getPattern().getOrNull());
+ ideaRun.setClassName(runImpl.getTestScope().getClassName().getOrNull());
+ ideaRun.setMethod(runImpl.getTestScope().getMethod().getOrNull());
+ ideaRun.setCategory(runImpl.getTestScope().getCategory().getOrNull());
+
+ ideaRun.setWorkingDirectory(runImpl.getWorkingDirectory().get().getAsFile().getAbsolutePath());
+ ideaRun.setVmParameters(RunsUtil.escapeAndJoin(runImpl.realiseJvmArguments(),
+ "-Dfml.junit.argsfile=%s".formatted(preparedUnitTestEnvironment.programArgumentsFile().getAbsolutePath()),
+ "@%s".formatted(preparedUnitTestEnvironment.jvmArgumentsFile().getAbsolutePath())
+ ));
+ ideaRun.setEnvs(adaptEnvironment(runImpl,
+ multimapProvider -> RunsUtil.buildRunWithIdeaModClasses(multimapProvider, RunsUtil.IdeaCompileType.Production),
+ multimapProvider -> RunsUtil.buildRunWithIdeaModClasses(multimapProvider, RunsUtil.IdeaCompileType.Test))
+ );
+ ideaRun.setShortenCommandLine(ShortenCommandLine.ARGS_FILE);
+
+ ideaRun.beforeRun(beforeRuns -> {
+ beforeRuns.create("Build", Make.class);
+
+ beforeRuns.create("Prepare JUnit Test", GradleTask.class, gradleTask -> {
+ gradleTask.setTask(ideBeforeRunTask.get());
+ });
+ });
+
+ ideaRun.setDefaults(defaultRun);
+ });
+ }
}
@Override
public void eclipse(Project project, EclipseModel eclipse) {
ProjectUtils.afterEvaluate(project, () -> {
- project.getExtensions().configure(RunsConstants.Extensions.RUNS, (Action>) runs -> runs.getAsMap().forEach((name, run) -> {
+ common(project);
+
+ project.getExtensions().configure(RunManager.class, runs -> runs.all(run -> {
+ if (!run.getShouldExportToIDE().get())
+ return;
+
+ final String name = run.getName();
final String runName = StringUtils.capitalize(project.getName() + " - " + StringUtils.capitalize(name.replace(" ", "-")));
final RunImpl runImpl = (RunImpl) run;
@@ -179,9 +266,9 @@ public void eclipse(Project project, EclipseModel eclipse) {
if (runImpl.getIsJUnit().get())
return;
- final TaskProvider> ideBeforeRunTask = createIdeBeforeRunTask(project, name, run, runImpl);
+ final TaskProvider> ideBeforeRunTask = getOrCreateIdeBeforeRunTask(project, runImpl);
final List> copyProcessResourcesTasks = createEclipseCopyResourcesTasks(eclipse, run);
- ideBeforeRunTask.configure(task -> copyProcessResourcesTasks.forEach(t -> task.dependsOn(t)));
+ ideBeforeRunTask.configure(task -> copyProcessResourcesTasks.forEach(task::dependsOn));
try {
final GradleLaunchConfig idePreRunTask = GradleLaunchConfig.builder(eclipse.getProject().getName())
@@ -194,8 +281,8 @@ public void eclipse(Project project, EclipseModel eclipse) {
final JavaApplicationLaunchConfig debugRun =
JavaApplicationLaunchConfig.builder(eclipse.getProject().getName())
.workingDirectory(runImpl.getWorkingDirectory().get().getAsFile().getAbsolutePath())
- .vmArgs(quoteStream(runImpl.realiseJvmArguments()).toArray(String[]::new))
- .args(quoteStream(runImpl.getProgramArguments().get()).toArray(String[]::new))
+ .vmArgs(RunsUtil.deduplicateElementsFollowingEachOther(RunsUtil.escapeStream(runImpl.realiseJvmArguments())).toArray(String[]::new))
+ .args(RunsUtil.deduplicateElementsFollowingEachOther(RunsUtil.escapeStream(runImpl.getArguments().get())).toArray(String[]::new))
.envVar(adaptEnvironment(runImpl, RunsUtil::buildRunWithEclipseModClasses))
.useArgumentsFile()
.build(runImpl.getMainClass().get());
@@ -227,19 +314,26 @@ public void eclipse(Project project, EclipseModel eclipse) {
public void vscode(Project project, EclipseModel eclipse)
{
ProjectUtils.afterEvaluate(project, () -> {
+ common(project);
+
final BatchedLaunchWriter launchWriter = new BatchedLaunchWriter(WritingMode.MODIFY_CURRENT);
- project.getExtensions().configure(RunsConstants.Extensions.RUNS, (Action>) runs -> runs.getAsMap().forEach((name, run) -> {
+ project.getExtensions().configure(RunManager.class, runs -> runs.all(run -> {
+ if (!run.getShouldExportToIDE().get())
+ return;
+
+ final String name = run.getName();
final String runName = StringUtils.capitalize(project.getName() + " - " + StringUtils.capitalize(name.replace(" ", "-")));
+
final RunImpl runImpl = (RunImpl) run;
- final TaskProvider> ideBeforeRunTask = createIdeBeforeRunTask(project, name, run, runImpl);
+ final TaskProvider> ideBeforeRunTask = getOrCreateIdeBeforeRunTask(project, runImpl);
final List> copyProcessResourcesTasks = createEclipseCopyResourcesTasks(eclipse, run);
- ideBeforeRunTask.configure(task -> copyProcessResourcesTasks.forEach(t -> task.dependsOn(t)));
+ ideBeforeRunTask.configure(task -> copyProcessResourcesTasks.forEach(task::dependsOn));
final LaunchConfiguration cfg = launchWriter.createGroup("NG - " + project.getName(), WritingMode.REMOVE_EXISTING)
.createLaunchConfiguration()
- .withAdditionalJvmArgs(runImpl.realiseJvmArguments())
- .withArguments(runImpl.getProgramArguments().get())
+ .withAdditionalJvmArgs(RunsUtil.deduplicateElementsFollowingEachOther(runImpl.realiseJvmArguments().stream()).toList())
+ .withArguments(RunsUtil.deduplicateElementsFollowingEachOther(runImpl.getArguments().get().stream()).toList())
.withCurrentWorkingDirectory(PathLike.ofNio(runImpl.getWorkingDirectory().get().getAsFile().toPath()))
.withEnvironmentVariables(adaptEnvironment(runImpl, RunsUtil::buildRunWithEclipseModClasses))
.withShortenCommandLine(ShortCmdBehaviour.ARGUMENT_FILE)
@@ -264,35 +358,56 @@ public void vscode(Project project, EclipseModel eclipse)
});
}
- private static String quoteAndJoin(List args) {
- return quoteStream(args).collect(Collectors.joining(" "));
- }
+ private TaskProvider> getOrCreateIdeBeforeRunTask(Project project, RunImpl run) {
+ if (project.getTasks().getNames().contains("ideBeforeRun")) {
+ final TaskProvider> provider = project.getTasks().named("ideBeforeRun");
+ provider.configure(task -> {
+ RunsUtil.addRunSourcesDependenciesToTask(task, run, false);
+ task.getDependsOn().add(run.getDependsOn());
+ });
+ return provider;
+ }
- private static Stream quoteStream(List args) {
- return args.stream().map(RunsImportAction::quote);
+ return project.getTasks().register("ideBeforeRun", task -> {
+ RunsUtil.addRunSourcesDependenciesToTask(task, run, false);
+ task.getDependsOn().add(run.getDependsOn());
+ });
}
- /**
- * This expects users to escape quotes in their system arguments on their own, which matches
- * Gradles own behavior when used in JavaExec.
- */
- private static String quote(String arg) {
- if (!arg.contains(" ")) {
- return arg;
+ private List> createIntelliJCopyResourcesTasks(Run run) {
+ final List> copyProcessResources = new ArrayList<>();
+ for (SourceSet sourceSet : run.getModSources().all().get().values()) {
+ copyProcessResources.add(setupCopyResourcesForIdea(sourceSet));
+ }
+
+ if (run.getIsJUnit().get()) {
+ for (SourceSet sourceSet : run.getUnitTestSources().all().get().values()) {
+ copyProcessResources.add(setupCopyResourcesForIdea(sourceSet));
+ }
}
- return "\"" + arg + "\"";
+
+ return copyProcessResources;
}
- private TaskProvider> createIdeBeforeRunTask(Project project, String name, Run run, RunImpl runImpl) {
- final TaskProvider> ideBeforeRunTask = project.getTasks().register(CommonRuntimeUtils.buildTaskName("ideBeforeRun", name), task -> {
- RunsUtil.addRunSourcesDependenciesToTask(task, run);
- });
+ private static @NotNull TaskProvider> setupCopyResourcesForIdea(SourceSet sourceSet) {
+ final Project sourceSetProject = SourceSetUtils.getProject(sourceSet);
- ideBeforeRunTask.configure(task -> {
- task.getDependsOn().add(runImpl.getDependsOn());
- });
+ final String taskName = CommonRuntimeUtils.buildTaskName("intelliJCopy", sourceSet.getProcessResourcesTaskName());
+ final TaskProvider> intelliJResourcesTask;
+
+ if (sourceSetProject.getTasks().findByName(taskName) != null) {
+ intelliJResourcesTask = sourceSetProject.getTasks().named(taskName);
+ }
+ else {
+ intelliJResourcesTask = sourceSetProject.getTasks().register(taskName, Copy.class, task -> {
+ final TaskProvider defaultProcessResources = sourceSetProject.getTasks().named(sourceSet.getProcessResourcesTaskName(), ProcessResources.class);
+ task.from(defaultProcessResources.map(ProcessResources::getDestinationDir));
+ task.into(RunsUtil.getRunWithIdeaResourcesDirectory(sourceSet));
- return ideBeforeRunTask;
+ task.dependsOn(defaultProcessResources);
+ });
+ }
+ return intelliJResourcesTask;
}
private List> createEclipseCopyResourcesTasks(EclipseModel eclipse, Run run) {
@@ -309,7 +424,7 @@ private List> createEclipseCopyResourcesTasks(EclipseModel eclip
else {
eclipseResourcesTask = sourceSetProject.getTasks().register(taskName, Copy.class, task -> {
final TaskProvider defaultProcessResources = sourceSetProject.getTasks().named(sourceSet.getProcessResourcesTaskName(), ProcessResources.class);
- task.from(defaultProcessResources.get().getDestinationDir());
+ task.from(defaultProcessResources.map(ProcessResources::getDestinationDir));
Path outputDir = eclipse.getClasspath().getDefaultOutputDir().toPath();
if (outputDir.endsWith("default")) {
// sometimes it has default value from org.gradle.plugins.ide.eclipse.internal.EclipsePluginConstants#DEFAULT_PROJECT_OUTPUT_PATH
@@ -350,5 +465,19 @@ private static Map adaptEnvironment(
environment.put("MOD_CLASSES", modClassesProvider.apply(run.getModSources().all()).get());
return environment;
}
+
+ private static Map adaptEnvironment(
+ final RunImpl run,
+ final Function>, Provider> modClassesProvider,
+ final Function>, Provider> modTestClassesProvider
+ ) {
+ final Map environment = new HashMap<>(run.getEnvironmentVariables().get());
+
+ final Provider joinedModClasses = modClassesProvider.apply(run.getModSources().all())
+ .zip(modTestClassesProvider.apply(run.getUnitTestSources().all()), (modClasses, testClasses) -> modClasses + File.pathSeparator + testClasses);
+
+ environment.put("MOD_CLASSES", joinedModClasses.get());
+ return environment;
+ }
}
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/ide/extensions/IdeaRunExtensionImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/ide/extensions/IdeaRunExtensionImpl.java
index d90781664..e33611ee8 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runs/ide/extensions/IdeaRunExtensionImpl.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/ide/extensions/IdeaRunExtensionImpl.java
@@ -19,7 +19,9 @@ public IdeaRunExtensionImpl(Project project, Run run) {
this.run = run;
final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
- this.getPrimarySourceSet().convention(javaPluginExtension.getSourceSets().getAt("main"));
+ this.getPrimarySourceSet().convention(
+ run.getIsJUnit().map(isJUnit -> isJUnit ? javaPluginExtension.getSourceSets().getByName("test") : javaPluginExtension.getSourceSets().getByName("main"))
+ );
}
@ProjectGetter
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/ide/idea/JUnitWithBeforeRun.java b/common/src/main/java/net/neoforged/gradle/common/runs/ide/idea/JUnitWithBeforeRun.java
new file mode 100644
index 000000000..49152e1fb
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/ide/idea/JUnitWithBeforeRun.java
@@ -0,0 +1,46 @@
+package net.neoforged.gradle.common.runs.ide.idea;
+
+import org.gradle.api.Action;
+import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
+import org.gradle.api.PolymorphicDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.tooling.internal.adapter.ObjectGraphAdapter;
+import org.jetbrains.gradle.ext.*;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+public class JUnitWithBeforeRun extends JUnit {
+
+ private final ExtensiblePolymorphicDomainObjectContainer beforeRun;
+
+ @Inject
+ public JUnitWithBeforeRun(final Project project, final String nameParam) {
+ super(nameParam);
+ beforeRun = createBeforeRun(project);
+ }
+
+ private static ExtensiblePolymorphicDomainObjectContainer createBeforeRun(Project project) {
+ ExtensiblePolymorphicDomainObjectContainer beforeRun = project.getObjects().polymorphicDomainObjectContainer(BeforeRunTask.class);
+
+ beforeRun.registerFactory(Make.class, makeName -> project.getObjects().newInstance(Make.class, makeName));
+ beforeRun.registerFactory(GradleTask.class, gradleTaskName -> project.getObjects().newInstance(GradleTask.class, gradleTaskName));
+ beforeRun.registerFactory(BuildArtifact.class, buildArtifactName -> project.getObjects().newInstance(BuildArtifact.class, buildArtifactName));
+
+ return beforeRun;
+ }
+
+ public void beforeRun(Action> action) {
+ action.execute(beforeRun);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ @Override
+ public Map toMap() {
+ final Map map = super.toMap();
+
+ map.put("beforeRun", beforeRun.stream().map(BeforeRunTask::toMap).toList());
+
+ return map;
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/DependencyHandlerImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/DependencyHandlerImpl.java
index 686bd11ae..72d5b7ae9 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runs/run/DependencyHandlerImpl.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/DependencyHandlerImpl.java
@@ -1,12 +1,11 @@
package net.neoforged.gradle.common.runs.run;
import net.neoforged.gradle.dsl.common.runs.run.DependencyHandler;
-import net.neoforged.gradle.dsl.common.util.ConfigurationUtils;
+import net.neoforged.gradle.common.util.ConfigurationUtils;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import javax.inject.Inject;
-import java.util.concurrent.atomic.AtomicInteger;
public abstract class DependencyHandlerImpl implements DependencyHandler {
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunDevLoginOptionsImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunDevLoginOptionsImpl.java
new file mode 100644
index 000000000..aa650910e
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunDevLoginOptionsImpl.java
@@ -0,0 +1,27 @@
+package net.neoforged.gradle.common.runs.run;
+
+import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.runs.DevLogin;
+import net.neoforged.gradle.dsl.common.runs.run.Run;
+import net.neoforged.gradle.dsl.common.runs.run.RunDevLoginOptions;
+import org.gradle.api.Project;
+
+import javax.inject.Inject;
+
+public abstract class RunDevLoginOptionsImpl implements RunDevLoginOptions {
+
+ private final Project project;
+
+ @Inject
+ public RunDevLoginOptionsImpl(Project project, Run run) {
+ this.project = project;
+
+ final DevLogin devLogin = project.getExtensions().getByType(Subsystems.class).getConventions().getRuns().getDevLogin();
+ getIsEnabled().convention(devLogin.getConventionForRun().zip(run.getIsClient(), (devLoginRun, runIsClient) -> devLoginRun && runIsClient));
+ }
+
+ @Override
+ public Project getProject() {
+ return project;
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunImpl.java
index 1f0ad88dc..ca91afff5 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunImpl.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunImpl.java
@@ -2,44 +2,50 @@
import com.google.common.collect.Multimap;
import net.minecraftforge.gdi.ConfigurableDSLElement;
+import net.neoforged.gradle.common.extensions.NeoGradleProblemReporter;
import net.neoforged.gradle.common.runtime.definition.CommonRuntimeDefinition;
+import net.neoforged.gradle.common.util.ConfigurationUtils;
import net.neoforged.gradle.common.util.SourceSetUtils;
import net.neoforged.gradle.common.util.TaskDependencyUtils;
-import net.neoforged.gradle.common.util.constants.RunsConstants;
import net.neoforged.gradle.common.util.exceptions.MultipleDefinitionsFoundException;
import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.DependencyReplacement;
-import net.neoforged.gradle.dsl.common.runs.run.Run;
-import net.neoforged.gradle.dsl.common.runs.run.RunSourceSets;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.runs.DevLogin;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.runs.RenderDoc;
+import net.neoforged.gradle.dsl.common.runs.RunSpecification;
+import net.neoforged.gradle.dsl.common.runs.run.*;
import net.neoforged.gradle.dsl.common.runs.type.RunType;
import net.neoforged.gradle.dsl.common.runs.type.RunTypeManager;
import net.neoforged.gradle.util.StringCapitalizationUtils;
import net.neoforged.gradle.util.TransformerUtils;
-import org.gradle.api.GradleException;
-import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.Task;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.Directory;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
-import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
+import java.io.File;
import java.util.*;
import java.util.function.Function;
-import java.util.stream.Stream;
public abstract class RunImpl implements ConfigurableDSLElement, Run {
private final Project project;
private final String name;
- private final ListProperty runTypes;
+ private final ListProperty specifications;
private final RunSourceSets modSources;
private final RunSourceSets unitTestSources;
+ private final RunTestScope testScope;
+ private final RunRenderDocOptions renderDocOptions;
+ private final RunDevLoginOptions devLoginOptions;
+ private final DependencyHandler dependencies;
private ListProperty jvmArguments;
private MapProperty environmentVariables;
@@ -53,12 +59,16 @@ public RunImpl(final Project project, final String name) {
this.name = name;
this.modSources = project.getObjects().newInstance(RunSourceSetsImpl.class, project);
this.unitTestSources = project.getObjects().newInstance(RunSourceSetsImpl.class, project);
+ this.testScope = project.getObjects().newInstance(RunTestScopeImpl.class, project);
+ this.renderDocOptions = project.getObjects().newInstance(RunRenderDocOptionsImpl.class, project, this);
+ this.devLoginOptions = project.getObjects().newInstance(RunDevLoginOptionsImpl.class, project, this);
+ this.dependencies = project.getObjects().newInstance(DependencyHandlerImpl.class, project, String.format("RunDependencies%s", StringCapitalizationUtils.capitalize(name)));
this.jvmArguments = this.project.getObjects().listProperty(String.class);
this.environmentVariables = this.project.getObjects().mapProperty(String.class, String.class);
this.programArguments = this.project.getObjects().listProperty(String.class);
this.systemProperties = this.project.getObjects().mapProperty(String.class, String.class);
- this.runTypes = this.project.getObjects().listProperty(RunType.class);
+ this.specifications = this.project.getObjects().listProperty(RunSpecification.class);
getIsSingleInstance().convention(true);
getIsClient().convention(false);
@@ -66,8 +76,6 @@ public RunImpl(final Project project, final String name) {
getIsDataGenerator().convention(false);
getIsGameTest().convention(false);
getIsJUnit().convention(false);
- getShouldBuildAllProjects().convention(false);
- getDependencies().convention(project.getObjects().newInstance(DependencyHandlerImpl.class, project, String.format("RunRuntimeDependencies%s", StringCapitalizationUtils.capitalize(name))));
getConfigureAutomatically().convention(true);
getConfigureFromTypeWithName().convention(getConfigureAutomatically());
@@ -93,6 +101,16 @@ public RunImpl(final Project project, final String name) {
getUnitTestSources().all().map(Multimap::values)
.map(sourcesSets -> sourcesSets.stream().map(SourceSet::getCompileClasspath).toList())
);
+ getSdkClasspath().from(
+ getModSources().all().map(Multimap::values)
+ .map(sourcesSets -> sourcesSets.stream().map(ConfigurationUtils::getSdkConfiguration).toList())
+ );
+ getSdkClasspath().from(
+ getUnitTestSources().all().map(Multimap::values)
+ .map(sourcesSets -> sourcesSets.stream().map(ConfigurationUtils::getSdkConfiguration).toList())
+ );
+
+ getShouldExportToIDE().convention(true);
}
@Override
@@ -115,10 +133,19 @@ public void overrideEnvironmentVariables(MapProperty environment
}
@Override
- public abstract Property getMainClass();
+ public RunRenderDocOptions getRenderDoc() {
+ return renderDocOptions;
+ }
+
+ @Override
+ public RunDevLoginOptions getDevLogin() {
+ return devLoginOptions;
+ }
@Override
- public abstract Property getShouldBuildAllProjects();
+ public DependencyHandler getDependencies() {
+ return dependencies;
+ }
@Override
public RunSourceSets getUnitTestSources() {
@@ -161,12 +188,27 @@ public void modSources(@NotNull final Iterable extends SourceSet> sourceSets)
}
@Override
- public ListProperty getProgramArguments() {
+ public ListProperty getArguments() {
return programArguments;
}
- public void overrideProgramArguments(ListProperty programArguments) {
- this.programArguments = programArguments;
+ public void overrideArguments(ListProperty arguments) {
+ this.programArguments = arguments;
+ }
+
+ @Deprecated
+ public ListProperty getProgramArguments() {
+ getProject().getExtensions().getByType(NeoGradleProblemReporter.class)
+ .reporting(problem -> problem
+ .id("deprecated-method", "Deprecated method")
+ .contextualLabel("Run.getProgramArguments()")
+ .details("The method getProgramArguments() is deprecated and will be removed in the future")
+ .solution("Use getArguments() instead of getProgramArguments()")
+ .section("common-runs-configuration-types-configure-by-type"),
+ getProject().getLogger()
+ );
+
+ return programArguments;
}
@Override
@@ -216,50 +258,189 @@ public Provider> getTestCompileClasspathElements() {
}
@Override
- public void runType(@NotNull String string) {
- configure(string);
+ public Provider> getSdkClasspathElements() {
+ return getLooselyCoupledConfigurableFileCollectionElements(getSdkClasspath());
+ }
+
+ @Override
+ public void runType(@NotNull String name) {
+ getConfigureFromTypeWithName().set(false); // Don't re-configure
+ specifications.addAll(getRunTypesByName(name));
+ }
+
+ @Override
+ public void run(@NotNull String name) {
+ getConfigureFromTypeWithName().set(false); // Don't re-configure
+ specifications.addAll(getRunByName(name));
+ }
+
+ @Override
+ public RunTestScope getTestScope() {
+ return testScope;
}
@Override
public final void configure() {
- if (getConfigureFromTypeWithName().get()) {
- runTypes.addAll(getRunTypesByName(name));
- }
+ potentiallyAddRunTypeByName();
+ configureRunSpecification();
+ configureFromSDKs();
+ configureFromRuns();
+ }
- getEnvironmentVariables().putAll(runTypes.flatMap(TransformerUtils.combineAllMaps(
- getProject(),
- String.class,
- String.class,
- RunType::getEnvironmentVariables
- )));
- getMainClass().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getMainClass)));
- getProgramArguments().addAll(runTypes.flatMap(TransformerUtils.combineAllLists(
- getProject(),
- String.class,
- RunType::getArguments
- )));
- getJvmArguments().addAll(runTypes.flatMap(TransformerUtils.combineAllLists(
- getProject(),
- String.class,
- RunType::getJvmArguments
- )));
- getIsSingleInstance().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsSingleInstance)));
- getSystemProperties().putAll(runTypes.flatMap(TransformerUtils.combineAllMaps(
- getProject(),
- String.class,
- String.class,
- RunType::getSystemProperties
- )));
- getIsClient().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsClient)));
- getIsServer().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsServer)));
- getIsDataGenerator().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsDataGenerator)));
- getIsGameTest().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsGameTest)));
- getIsJUnit().convention(runTypes.flatMap(TransformerUtils.takeLast(getProject(), RunType::getIsJUnit)));
- getRuntimeClasspath().from(runTypes.map(TransformerUtils.combineFileCollections(
- getProject(),
- RunType::getClasspath
- )));
+ private void configureFromRuns() {
+ Provider> runSpecifications = specifications.map(l -> l.stream().filter(Run.class::isInstance).map(Run.class::cast).toList());
+
+ //Properties of the run
+ getWorkingDirectory().convention(
+ TransformerUtils.defaulted(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, Run::getWorkingDirectory)
+ ),
+ project.getLayout().getProjectDirectory().dir("runs").dir(getName())
+ )
+ );
+
+ final RenderDoc renderDoc = project.getExtensions().getByType(Subsystems.class).getConventions().getRuns().getRenderDoc();
+ //Properties of the renderdoc integration
+ getRenderDoc().getEnabled().convention(
+ TransformerUtils.lazyDefaulted(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, run -> run.getRenderDoc().getEnabled())
+ ),
+ renderDoc.getConventionForRun().zip(getIsClient(), (conventionForRun, isClient) -> conventionForRun && isClient)
+ )
+ );
+
+ //Properties of the dev login integration
+ final DevLogin devLogin = project.getExtensions().getByType(Subsystems.class).getConventions().getRuns().getDevLogin();
+ getDevLogin().getIsEnabled().convention(
+ TransformerUtils.lazyDefaulted(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, run -> run.getDevLogin().getIsEnabled())
+ ),
+ devLogin.getConventionForRun().zip(getIsClient(), (conventionForRun, isClient) -> conventionForRun && isClient)
+ )
+ );
+ getDevLogin().getProfile().convention(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, run -> run.getDevLogin().getProfile())
+ )
+ );
+
+ //ModSources
+ getModSources().addAllLater(
+ runSpecifications.flatMap(
+ TransformerUtils.combineAllMultiMaps(
+ project,
+ String.class,
+ SourceSet.class,
+ run -> run.getModSources().all()
+ )
+ )
+ );
+
+ //UnitTestSources
+ getUnitTestSources().addAllLater(
+ runSpecifications.flatMap(
+ TransformerUtils.combineAllMultiMaps(
+ project,
+ String.class,
+ SourceSet.class,
+ run -> run.getUnitTestSources().all()
+ )
+ )
+ );
+ //Properties of the test scope
+ getTestScope().getPackageName().convention(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, run -> run.getTestScope().getPackageName())
+ )
+ );
+ getTestScope().getDirectory().convention(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, run -> run.getTestScope().getDirectory())
+ )
+ );
+ getTestScope().getPattern().convention(
+ TransformerUtils.lazyDefaulted(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, run -> run.getTestScope().getPattern())
+ ),
+ project.provider(() -> {
+ if (getTestScope().getPackageName().orElse(getTestScope().getDirectory().map(Directory::getAsFile).map(File::getName))
+ .orElse(getTestScope().getClassName())
+ .orElse(getTestScope().getMethod())
+ .orElse(getTestScope().getCategory())
+ .getOrNull() == null) {
+ return RunTestScopeImpl.DEFAULT_PATTERN;
+ }
+
+ return null;
+ })
+ )
+ );
+ getTestScope().getClassName().convention(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, run -> run.getTestScope().getClassName())
+ )
+ );
+ getTestScope().getMethod().convention(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, run -> run.getTestScope().getMethod())
+ )
+ );
+ getTestScope().getCategory().convention(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, run -> run.getTestScope().getCategory())
+ )
+ );
+
+ //Dependencies
+ getDependencies().getRuntime().bundle(
+ runSpecifications.flatMap(
+ TransformerUtils.combineAllSets(
+ project,
+ Dependency.class,
+ run -> run.getDependencies().getRuntime().getDependencies()
+ )
+ )
+ );
+
+ //Task dependencies
+ getDependsOn().addAll(
+ runSpecifications.flatMap(
+ TransformerUtils.combineAllSets(
+ project,
+ Task.class,
+ Run::getDependsOn
+ )
+ )
+ );
+
+ //Pre-sync tasks
+ getPostSyncTasks().addAll(
+ runSpecifications.flatMap(
+ TransformerUtils.combineAllSets(
+ project,
+ Task.class,
+ Run::getPostSyncTasks
+ )
+ )
+ );
+
+ //Exporting to IDEs
+ getShouldExportToIDE().convention(
+ TransformerUtils.defaulted(
+ runSpecifications.flatMap(
+ TransformerUtils.takeLast(project, Run::getShouldExportToIDE)
+ ),
+ true
+ )
+ );
+ }
+
+ private void configureFromSDKs() {
final Set unconfiguredSourceSets = new HashSet<>();
final Set> configuredDefinitions = new HashSet<>();
@@ -276,7 +457,14 @@ public final void configure() {
}
}, () -> unconfiguredSourceSets.add(sourceSet));
} catch (MultipleDefinitionsFoundException e) {
- throw new RuntimeException("Failed to configure run: " + getName() + " there are multiple runtime definitions found for the source set: " + sourceSet.getName(), e);
+ final NeoGradleProblemReporter reporter = project.getExtensions().getByType(NeoGradleProblemReporter.class);
+ throw reporter.throwing(problem -> problem
+ .id("multiple-definitions-found", "Multiple runtime definitions found")
+ .contextualLabel("Run: " + this.getName())
+ .solution("Ensure only one SDK definition is present for the source set")
+ .details("There are multiple runtime definitions found for the source set: " + sourceSet.getName())
+ .section("common-runs-configuration-runs")
+ );
}
});
@@ -307,22 +495,105 @@ public final void configure() {
});
}
+ private void potentiallyAddRunTypeByName() {
+ if (getConfigureFromTypeWithName().get()) {
+ specifications.addAll(getRunTypesByName(name));
+ }
+ }
+
+ private void configureRunSpecification() {
+ getEnvironmentVariables().putAll(specifications.flatMap(TransformerUtils.combineAllMaps(
+ getProject(),
+ String.class,
+ String.class,
+ RunSpecification::getEnvironmentVariables
+ )));
+ getMainClass().convention(specifications.flatMap(TransformerUtils.takeLast(getProject(), RunSpecification::getMainClass)));
+ getArguments().addAll(specifications.flatMap(TransformerUtils.combineAllLists(
+ getProject(),
+ String.class,
+ RunSpecification::getArguments
+ )));
+ getJvmArguments().addAll(specifications.flatMap(TransformerUtils.combineAllLists(
+ getProject(),
+ String.class,
+ RunSpecification::getJvmArguments
+ )));
+ getIsSingleInstance().convention(
+ TransformerUtils.defaulted(
+ specifications.flatMap(TransformerUtils.takeLast(getProject(), RunSpecification::getIsSingleInstance)),
+ true
+ )
+ );
+ getSystemProperties().putAll(specifications.flatMap(TransformerUtils.combineAllMaps(
+ getProject(),
+ String.class,
+ String.class,
+ RunSpecification::getSystemProperties
+ )));
+ getIsClient().convention(
+ TransformerUtils.defaulted(
+ specifications.flatMap(TransformerUtils.takeLast(getProject(), RunSpecification::getIsClient)),
+ false
+ )
+ );
+ getIsServer().convention(
+ TransformerUtils.defaulted(
+ specifications.flatMap(TransformerUtils.takeLast(getProject(), RunSpecification::getIsServer)),
+ false
+ )
+ );
+ getIsDataGenerator().convention(
+ TransformerUtils.defaulted(
+ specifications.flatMap(TransformerUtils.takeLast(getProject(), RunSpecification::getIsDataGenerator)),
+ false
+ )
+ );
+ getIsGameTest().convention(
+ TransformerUtils.defaulted(
+ specifications.flatMap(TransformerUtils.takeLast(getProject(), RunSpecification::getIsGameTest)),
+ false
+ )
+ );
+ getIsJUnit().convention(
+ TransformerUtils.defaulted(
+ specifications.flatMap(TransformerUtils.takeLast(getProject(), RunSpecification::getIsJUnit)),
+ false
+ )
+ );
+ getRuntimeClasspath().from(specifications.map(TransformerUtils.combineFileCollections(
+ getProject(),
+ RunSpecification::getClasspath
+ )));
+ }
+
+ @Deprecated
@Override
public final void configure(final @NotNull String name) {
+ getProject().getExtensions().getByType(NeoGradleProblemReporter.class)
+ .reporting(problem -> problem
+ .id("deprecated-method", "Deprecated method")
+ .contextualLabel("Run.configure(String)")
+ .details("The method configure(String) is deprecated and will be removed in the future")
+ .solution("Use Run.runType(String) or Run.run(String) instead of Run.configure(String) to indicate from what the run should be configured")
+ .section("common-runs-configuration-types-configure-by-type"),
+ getProject().getLogger()
+ );
+
getConfigureFromTypeWithName().set(false); // Don't re-configure
- runTypes.addAll(getRunTypesByName(name));
+ specifications.addAll(getRunTypesByName(name));
}
@Override
- public final void configure(final @NotNull RunType runType) {
+ public final void configure(final @NotNull RunSpecification runType) {
getConfigureFromTypeWithName().set(false); // Don't re-configure
- this.runTypes.add(project.provider(() -> runType));
+ this.specifications.add(project.provider(() -> runType));
}
@Override
- public void configure(@NotNull Provider typeProvider) {
+ public void configure(@NotNull Provider extends RunSpecification> typeProvider) {
getConfigureFromTypeWithName().set(false); // Don't re-configure
- this.runTypes.add(typeProvider);
+ this.specifications.add(typeProvider);
}
@NotNull
@@ -345,25 +616,45 @@ private Provider> getRunTypesByName(String name) {
RunTypeManager runTypes = project.getExtensions().getByType(RunTypeManager.class);
return project.provider(() -> {
- if (runTypes.getNames().contains(name)) {
- return List.of(runTypes.getByName(name));
- } else {
- return null;
- }
- }).orElse(
- TransformerUtils.ifTrue(getConfigureFromDependencies(),
- getCompileClasspathElements()
- .map(files -> files.stream()
- .map(FileSystemLocation::getAsFile)
- .map(runTypes::parse)
- .flatMap(Collection::stream)
- .filter(runType -> runType.getName().equals(name))
- .toList()
- ))).map(types -> {
- if (types.isEmpty()) {
- throw new GradleException("No run type found with name: " + name);
- }
- return types;
- });
+ if (runTypes.getNames().contains(name)) {
+ return List.of(runTypes.getByName(name));
+ } else {
+ return null;
+ }
+ })
+ .orElse(
+ TransformerUtils.ifTrue(getConfigureFromDependencies(),
+ getSdkClasspathElements()
+ .map(files -> files.stream()
+ .map(FileSystemLocation::getAsFile)
+ .map(runTypes::parse)
+ .flatMap(Collection::stream)
+ .filter(runType -> runType.getName().equals(name))
+ .toList()
+ ))).map(types -> {
+ if (types.isEmpty()) {
+ final NeoGradleProblemReporter reporter = project.getExtensions().getByType(NeoGradleProblemReporter.class);
+ throw reporter.throwing(problem -> problem
+ .id("run-type-not-found", "Run type not found")
+ .contextualLabel("The run type '%s' was not found".formatted(name))
+ .solution("Ensure the run type is defined in the run or a dependency")
+ .section("common-runs-configuration-types-configure-by-type")
+ );
+ }
+ return types;
+ });
+ }
+
+ private Provider> getRunByName(String name) {
+ RunManager runTypes = project.getExtensions().getByType(RunManager.class);
+
+ return project.provider(() -> {
+ if (runTypes.getNames().contains(name)) {
+ return List.of(runTypes.getByName(name));
+ } else {
+ return null;
+ }
+ })
+ .orElse(List.of());
}
}
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunManagerImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunManagerImpl.java
new file mode 100644
index 000000000..5e1d49964
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunManagerImpl.java
@@ -0,0 +1,60 @@
+package net.neoforged.gradle.common.runs.run;
+
+import net.neoforged.gradle.common.util.DelegatingDomainObjectContainer;
+import net.neoforged.gradle.dsl.common.runs.run.Run;
+import net.neoforged.gradle.dsl.common.runs.run.RunManager;
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RunManagerImpl extends DelegatingDomainObjectContainer implements RunManager {
+
+ private final List> actions = new ArrayList<>();
+ private final List internalRuns = new ArrayList<>();
+
+ private static NamedDomainObjectContainer createAndRegisterContainer(Project project) {
+ final NamedDomainObjectContainer container = project.container(Run.class, name -> project.getObjects().newInstance(RunImpl.class, project, name));
+ project.getExtensions().add("runs", container);
+ return container;
+ }
+
+ @Inject
+ public RunManagerImpl(Project project) {
+ super(createAndRegisterContainer(project));
+ }
+
+ @Override
+ public void addInternal(Run run) {
+ internalRuns.add(run);
+
+ for (Action action : actions) {
+ action.execute(run);
+ }
+ }
+
+ @Override
+ public void realizeAll(Action forAll) {
+ super.all(forAll);
+
+ this.actions.add(forAll);
+
+ for (Run run : internalRuns) {
+ forAll.execute(run);
+ }
+ }
+
+ @Override
+ public void configureAll(Action configure) {
+ super.configureEach(configure);
+
+ this.actions.add(configure);
+
+ for (Run run : internalRuns) {
+ configure.execute(run);
+ }
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunRenderDocOptionsImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunRenderDocOptionsImpl.java
new file mode 100644
index 000000000..ccf6b3707
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunRenderDocOptionsImpl.java
@@ -0,0 +1,27 @@
+package net.neoforged.gradle.common.runs.run;
+
+import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems;
+import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.runs.RenderDoc;
+import net.neoforged.gradle.dsl.common.runs.run.Run;
+import net.neoforged.gradle.dsl.common.runs.run.RunRenderDocOptions;
+import org.gradle.api.Project;
+
+import javax.inject.Inject;
+
+public abstract class RunRenderDocOptionsImpl implements RunRenderDocOptions {
+
+ private final Project project;
+
+ @Inject
+ public RunRenderDocOptionsImpl(Project project, Run run) {
+ this.project = project;
+
+ final RenderDoc renderDoc = project.getExtensions().getByType(Subsystems.class).getConventions().getRuns().getRenderDoc();
+ getEnabled().convention(renderDoc.getConventionForRun().zip(run.getIsClient(), (renderDocRun, runIsClient) -> renderDocRun && runIsClient));
+ }
+
+ @Override
+ public Project getProject() {
+ return project;
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java
index 4f31fdc04..cb27f986d 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java
@@ -20,15 +20,14 @@
public abstract class RunSourceSetsImpl implements RunSourceSets {
private final Project project;
- private final Provider> provider;
private final Multimap sourceSets;
private final List> callbacks = new ArrayList<>();
+ private final List>> sourceSetProviders = new ArrayList<>();
@Inject
public RunSourceSetsImpl(Project project) {
this.project = project;
this.sourceSets = HashMultimap.create();
- this.provider = project.provider(() -> sourceSets);
}
@@ -105,6 +104,11 @@ public void add(String groupId, SourceSet... sourceSets) {
}
}
+ @Override
+ public void addAllLater(Provider> sourceSets) {
+ this.sourceSetProviders.add(sourceSets);
+ }
+
@DSLProperty
@Input
@Optional
@@ -113,7 +117,16 @@ public void add(String groupId, SourceSet... sourceSets) {
@Override
public Provider> all() {
- return this.provider;
+ //Realize all lazy source sets
+ if (!this.sourceSetProviders.isEmpty()) {
+ for (Provider> sourceSetProvider : this.sourceSetProviders) {
+ final Multimap sourceSets = sourceSetProvider.get();
+ sourceSets.forEach(this::add);
+ }
+ this.sourceSetProviders.clear();
+ }
+
+ return this.project.provider(() -> this.sourceSets);
}
@Override
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTestScopeImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTestScopeImpl.java
new file mode 100644
index 000000000..b9929b5dd
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTestScopeImpl.java
@@ -0,0 +1,40 @@
+package net.neoforged.gradle.common.runs.run;
+
+import net.neoforged.gradle.dsl.common.runs.run.RunTestScope;
+import org.gradle.api.Project;
+import org.gradle.api.file.Directory;
+
+import javax.inject.Inject;
+import java.io.File;
+
+
+public abstract class RunTestScopeImpl implements RunTestScope {
+
+ static final String DEFAULT_PATTERN = ".*";
+
+ private final Project project;
+
+ @Inject
+ public RunTestScopeImpl(Project project) {
+ this.project = project;
+
+ getPattern().convention(
+ project.provider(() -> {
+ if (getPackageName().orElse(getDirectory().map(Directory::getAsFile).map(File::getName))
+ .orElse(getClassName())
+ .orElse(getMethod())
+ .orElse(getCategory())
+ .getOrNull() == null) {
+ return DEFAULT_PATTERN;
+ }
+
+ return null;
+ })
+ );
+ }
+
+ @Override
+ public Project getProject() {
+ return project;
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTypeManagerImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTypeManagerImpl.java
index 0ea1140ae..1760fa32a 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTypeManagerImpl.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunTypeManagerImpl.java
@@ -1,8 +1,10 @@
package net.neoforged.gradle.common.runs.run;
import net.neoforged.gradle.common.util.DelegatingDomainObjectContainer;
+import net.neoforged.gradle.dsl.common.runs.run.Run;
import net.neoforged.gradle.dsl.common.runs.type.RunType;
import net.neoforged.gradle.dsl.common.runs.type.RunTypeManager;
+import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
import javax.inject.Inject;
@@ -18,9 +20,15 @@ public class RunTypeManagerImpl extends DelegatingDomainObjectContainer
private final List parsers = new ArrayList<>();
+ private static NamedDomainObjectContainer createAndRegisterContainer(Project project) {
+ final NamedDomainObjectContainer container = project.container(RunType.class, name -> project.getObjects().newInstance(RunType.class, name));
+ project.getExtensions().add("runTypes", container);
+ return container;
+ }
+
@Inject
public RunTypeManagerImpl(Project project) {
- super(project.getObjects().domainObjectContainer(RunType.class));
+ super(createAndRegisterContainer(project));
}
@Override
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunsReport.java b/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunsReport.java
index 91d31965c..618d7d3ec 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunsReport.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunsReport.java
@@ -1,9 +1,9 @@
package net.neoforged.gradle.common.runs.tasks;
import com.google.common.collect.Multimap;
-import net.neoforged.gradle.common.util.constants.RunsConstants;
+import net.neoforged.gradle.common.util.run.RunsUtil;
import net.neoforged.gradle.dsl.common.runs.run.Run;
-import org.gradle.api.NamedDomainObjectContainer;
+import net.neoforged.gradle.dsl.common.runs.run.RunManager;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.diagnostics.AbstractProjectBasedReportTask;
@@ -36,14 +36,14 @@ protected void generateReportFor(@NotNull ProjectDetails project, @NotNull RunsR
renderer.completeProject(project);
}
- @SuppressWarnings("unchecked")
@Override
protected @NotNull RunsReport.RunsProjectReport calculateReportModelFor(@NotNull Project project) {
- final NamedDomainObjectContainer runs = (NamedDomainObjectContainer) project.getExtensions().getByName(RunsConstants.Extensions.RUNS);
+ final RunManager runs = project.getExtensions().getByType(RunManager.class);
final RunsProjectReport report = new RunsProjectReport();
- runs.forEach(run -> {
- report.addRun(new RenderableRun(run));
- });
+
+ runs.stream().toList().forEach(run -> report.addRun(new RenderableRun(run)));
+ //runs.realizeAll(run -> getLogger().debug("Realized run: " + run.getName()));
+
return report;
}
@@ -68,9 +68,8 @@ public static class RenderableRun {
private final String name;
private final Map environment;
private final String mainClass;
- private final boolean shouldBuildAllProjects;
private final Map properties;
- private final List programArguments;
+ private final List arguments;
private final List jvmArguments;
private final boolean isSingleInstance;
private final String workingDirectory;
@@ -88,10 +87,9 @@ public RenderableRun(Run run) {
this.name = run.getName();
this.environment = run.getEnvironmentVariables().get();
this.mainClass = run.getMainClass().get();
- this.shouldBuildAllProjects = run.getShouldBuildAllProjects().get();
this.properties = run.getSystemProperties().get();
- this.programArguments = run.getProgramArguments().get();
- this.jvmArguments = run.getJvmArguments().get();
+ this.arguments = RunsUtil.deduplicateElementsFollowingEachOther(run.getArguments().get().stream()).toList();
+ this.jvmArguments = RunsUtil.deduplicateElementsFollowingEachOther(run.getJvmArguments().get().stream()).toList();
this.isSingleInstance = run.getIsSingleInstance().get();
this.workingDirectory = run.getWorkingDirectory().get().getAsFile().getAbsolutePath();
this.isClient = run.getIsClient().get();
@@ -102,7 +100,7 @@ public RenderableRun(Run run) {
this.modSources = run.getModSources().all().get();
this.unitTestSources = run.getUnitTestSources().all().get();
this.classpath = run.getRuntimeClasspath().getFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
- this.dependencies = run.getDependencies().get().getRuntimeConfiguration().getFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
+ this.dependencies = run.getDependencies().getRuntimeConfiguration().getFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
}
public String getName() {
@@ -117,16 +115,12 @@ public String getMainClass() {
return mainClass;
}
- public boolean isShouldBuildAllProjects() {
- return shouldBuildAllProjects;
- }
-
public Map getProperties() {
return properties;
}
- public List getProgramArguments() {
- return programArguments;
+ public List getArguments() {
+ return arguments;
}
public List getJvmArguments() {
@@ -223,7 +217,6 @@ private void renderRun(RenderableRun run) {
outputNormal(run.getMainClass());
outputNewLine();
outputIdentifier("Should Build All Projects:");
- outputNormal(String.valueOf(run.isShouldBuildAllProjects()));
outputNewLine();
outputIdentifier("Working Directory:");
outputNormal(run.getWorkingDirectory());
@@ -249,7 +242,7 @@ private void renderRun(RenderableRun run) {
renderEnvironment(run.getEnvironment());
renderProperties(run.getProperties());
- renderProgramArguments(run.getProgramArguments());
+ renderProgramArguments(run.getArguments());
renderJvmArguments(run.getJvmArguments());
renderModSources(run.getModSources());
renderUnitTestSources(run.getUnitTestSources());
diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/unittest/UnitTestConfigurator.java b/common/src/main/java/net/neoforged/gradle/common/runs/unittest/UnitTestConfigurator.java
new file mode 100644
index 000000000..401359e23
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/runs/unittest/UnitTestConfigurator.java
@@ -0,0 +1,31 @@
+package net.neoforged.gradle.common.runs.unittest;
+
+import net.neoforged.gradle.common.extensions.IdeManagementExtension;
+import net.neoforged.gradle.common.util.run.RunsUtil;
+import net.neoforged.gradle.dsl.common.runs.run.Run;
+import net.neoforged.gradle.dsl.common.runs.run.RunManager;
+import org.gradle.api.Project;
+import org.gradle.api.plugins.ExtensionAware;
+
+public class UnitTestConfigurator {
+
+ public static void configureIdeUnitTests(final Project project) {
+ IdeManagementExtension ide = project.getExtensions().getByType(IdeManagementExtension.class);
+
+ ide.onIdea((ideaProject, rootProject, idea, ideaExtension) -> {
+ final ExtensionAware extensionAware = (ExtensionAware) idea;
+ if (extensionAware.getExtensions().findByType(Run.class) != null) {
+ return;
+ }
+
+ //The actual project that this run is associated with does not matter,
+ //So we can just use the idea project, we can use the root project,
+ //because it is not guaranteed to have all the needed extensions.
+ final Run ideaDefaultTestRun = RunsUtil.create(ideaProject, "idea");
+ extensionAware.getExtensions().add(Run.class, "unitTests", ideaDefaultTestRun);
+
+ ideaDefaultTestRun.getIsJUnit().set(true);
+ ideaDefaultTestRun.runType("junit");
+ });
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java b/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java
index b3433f14c..485419444 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java
@@ -24,13 +24,10 @@
import org.gradle.api.tasks.TaskProvider;
import org.jetbrains.annotations.NotNull;
-import javax.sql.rowset.spi.TransactionalWriter;
import java.io.File;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
-import java.util.stream.Collectors;
public abstract class CommonRuntimeDefinition implements Definition {
@@ -180,7 +177,7 @@ public void configureRun(RunImpl run) {
);
run.overrideJvmArguments(interpolate(run.getJvmArguments(), runtimeInterpolationData));
- run.overrideProgramArguments(interpolate(run.getProgramArguments(), runtimeInterpolationData));
+ run.overrideArguments(interpolate(run.getArguments(), runtimeInterpolationData));
run.overrideEnvironmentVariables(interpolate(run.getEnvironmentVariables(), runtimeInterpolationData));
run.overrideSystemProperties(interpolate(run.getSystemProperties(), runtimeInterpolationData));
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/NamingChannelProvider.java b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/NamingChannelProvider.java
index 0b854efd4..e6ea0b533 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/NamingChannelProvider.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/NamingChannelProvider.java
@@ -21,7 +21,6 @@
package net.neoforged.gradle.common.runtime.naming;
import net.minecraftforge.gdi.ConfigurableDSLElement;
-import net.neoforged.gradle.dsl.common.util.NamingConstants;
import net.neoforged.gradle.dsl.common.runtime.naming.NamingChannel;
import org.gradle.api.Project;
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/OfficialNamingChannelConfigurator.java b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/OfficialNamingChannelConfigurator.java
index e7be6874f..aa3711df0 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/OfficialNamingChannelConfigurator.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/OfficialNamingChannelConfigurator.java
@@ -1,10 +1,5 @@
package net.neoforged.gradle.common.runtime.naming;
-import net.neoforged.gradle.common.runtime.extensions.CommonRuntimeExtension;
-import net.neoforged.gradle.common.runtime.naming.tasks.ApplyOfficialMappingsToCompiledJar;
-import net.neoforged.gradle.common.runtime.naming.tasks.ApplyOfficialMappingsToSourceJar;
-import net.neoforged.gradle.common.util.MappingUtils;
-import net.minecraftforge.srgutils.IMappingFile;
import net.neoforged.gradle.common.runtime.extensions.RuntimesExtension;
import net.neoforged.gradle.common.runtime.naming.tasks.*;
import net.neoforged.gradle.common.util.StreamUtils;
@@ -19,10 +14,8 @@
import net.neoforged.gradle.dsl.common.runtime.tasks.Runtime;
import net.neoforged.gradle.dsl.common.tasks.WithOutput;
import net.neoforged.gradle.dsl.common.util.*;
-import net.neoforged.gradle.util.TransformerUtils;
import org.gradle.api.Project;
import org.gradle.api.Transformer;
-import org.gradle.api.file.RegularFile;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.reflect.TypeOf;
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/tasks/ApplyMappingsToSourceJar.java b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/tasks/ApplyMappingsToSourceJar.java
index 7c5fb85ec..593eba8e0 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/tasks/ApplyMappingsToSourceJar.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/tasks/ApplyMappingsToSourceJar.java
@@ -20,13 +20,15 @@
package net.neoforged.gradle.common.runtime.naming.tasks;
+import net.neoforged.gradle.common.services.caching.CachedExecutionService;
+import net.neoforged.gradle.common.services.caching.jobs.ICacheableJob;
import net.neoforged.gradle.util.FileUtils;
import net.neoforged.gradle.common.runtime.naming.renamer.ISourceRenamer;
import net.neoforged.gradle.common.runtime.tasks.DefaultRuntime;
-import net.neoforged.gradle.dsl.common.runtime.tasks.Runtime;
import org.apache.commons.io.IOUtils;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
+import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.*;
import java.io.FileOutputStream;
@@ -44,8 +46,19 @@ public ApplyMappingsToSourceJar() {
getRemapJavadocs().convention(false);
}
+ @ServiceReference(CachedExecutionService.NAME)
+ public abstract Property getCacheService();
+
@TaskAction
- public void apply() throws Exception {
+ public final void execute() throws Throwable {
+ getCacheService().get()
+ .cached(
+ this,
+ ICacheableJob.Default.file(getOutput(), this::apply)
+ ).execute();
+ }
+
+ protected final void apply() throws Exception {
final ISourceRenamer renamer = getSourceRenamer().get();
try (ZipFile zin = new ZipFile(getInput().get().getAsFile())) {
try (FileOutputStream fos = new FileOutputStream(getOutput().get().getAsFile());
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/tasks/ApplyOfficialMappingsToSourceJar.java b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/tasks/ApplyOfficialMappingsToSourceJar.java
index cec626790..d5aff0f37 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/tasks/ApplyOfficialMappingsToSourceJar.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/tasks/ApplyOfficialMappingsToSourceJar.java
@@ -2,7 +2,6 @@
import net.neoforged.gradle.util.TransformerUtils;
import net.neoforged.gradle.common.runtime.naming.renamer.IMappingFileSourceRenamer;
-import net.neoforged.gradle.dsl.common.runtime.tasks.Runtime;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/AccessTransformerFileGenerator.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/AccessTransformerFileGenerator.java
index 9af928137..1a5a8f30a 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/AccessTransformerFileGenerator.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/AccessTransformerFileGenerator.java
@@ -1,13 +1,10 @@
package net.neoforged.gradle.common.runtime.tasks;
import net.neoforged.gradle.dsl.common.extensions.AccessTransformers;
-import net.neoforged.gradle.dsl.common.runtime.tasks.Runtime;
-import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import java.io.File;
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultExecute.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultExecute.java
index 3d3e64c47..63d1ca1a7 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultExecute.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultExecute.java
@@ -1,7 +1,7 @@
package net.neoforged.gradle.common.runtime.tasks;
-import net.neoforged.gradle.common.CommonProjectPlugin;
-import net.neoforged.gradle.common.caching.CentralCacheService;
+import net.neoforged.gradle.common.services.caching.CachedExecutionService;
+import net.neoforged.gradle.common.services.caching.jobs.ICacheableJob;
import net.neoforged.gradle.dsl.common.tasks.Execute;
import net.neoforged.gradle.util.TransformerUtils;
import org.gradle.api.provider.ListProperty;
@@ -39,12 +39,16 @@ public DefaultExecute() {
getLogLevel().convention(LogLevel.ERROR);
}
- @ServiceReference(CommonProjectPlugin.EXECUTE_SERVICE)
- public abstract Property getCacheService();
+ @ServiceReference(CachedExecutionService.NAME)
+ public abstract Property getCacheService();
@TaskAction
public void execute() throws Throwable {
- getCacheService().get().doCached(this, this::doExecute, getOutput());
+ getCacheService().get()
+ .cached(
+ this,
+ ICacheableJob.Default.file(getOutput(), this::doExecute)
+ ).execute();
}
@Input
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultRuntime.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultRuntime.java
index f4f4d8755..69e0e2621 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultRuntime.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultRuntime.java
@@ -6,7 +6,6 @@
import net.neoforged.gradle.dsl.common.runtime.tasks.RuntimeMultiArguments;
import net.neoforged.gradle.dsl.common.util.DistributionType;
import org.gradle.api.file.FileTree;
-import org.gradle.api.file.RegularFile;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.*;
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java
index ecb565ec8..4b3994a5b 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java
@@ -1,8 +1,8 @@
package net.neoforged.gradle.common.runtime.tasks;
import com.google.common.collect.Maps;
-import net.neoforged.gradle.common.CommonProjectPlugin;
-import net.neoforged.gradle.common.caching.CentralCacheService;
+import net.neoforged.gradle.common.services.caching.CachedExecutionService;
+import net.neoforged.gradle.common.services.caching.jobs.ICacheableJob;
import net.neoforged.gradle.common.util.FileCacheUtils;
import net.neoforged.gradle.dsl.common.tasks.WithWorkspace;
import net.neoforged.gradle.util.TransformerUtils;
@@ -12,7 +12,6 @@
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.file.Directory;
-import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
@@ -25,6 +24,7 @@
import javax.inject.Inject;
import java.io.File;
+import java.io.IOException;
import java.util.Map;
@SuppressWarnings({"UnstableApiUsage"})
@@ -32,9 +32,12 @@
public abstract class DownloadAssets extends DefaultTask implements WithWorkspace {
private final Provider assetsCache;
+ private final Provider assetsObjects;
public DownloadAssets() {
this.assetsCache = getAssetsDirectory(getProject(), getVersionJson());
+ this.assetsObjects = assetsCache.map(directory -> directory.dir("objects"));
+
getAssetIndex().convention("asset-index");
getAssetIndexFileName().convention(getAssetIndex().map(index -> index + ".json"));
getAssetIndexTargetFile().convention(getRegularFileInAssetsDirectory(getAssetIndexFileName().map(name -> "indexes/" + name)));
@@ -49,23 +52,30 @@ public DownloadAssets() {
}
protected Provider getFileInAssetsDirectory(final String fileName) {
- return assetsCache.map(directory -> directory.file(fileName).getAsFile());
+ return assetsObjects.map(directory -> directory.file(fileName).getAsFile());
}
protected Provider getRegularFileInAssetsDirectory(final Provider fileName) {
return assetsCache.flatMap(directory -> fileName.map(directory::file));
}
- @ServiceReference(CommonProjectPlugin.ASSETS_SERVICE)
- public abstract Property getAssetsCache();
+ @ServiceReference(CachedExecutionService.NAME)
+ public abstract Property getCache();
@TaskAction
- public void run() {
- downloadAssetIndex();
- downloadAssets();
+ public void run() throws IOException {
+ getCache().get()
+ .cached(
+ this,
+ ICacheableJob.Initial.file("assetIndex", getAssetIndexFile(), this::downloadAssetIndex)
+ )
+ .withStage(
+ ICacheableJob.Initial.directory("assets", assetsObjects, this::downloadAssets)
+ )
+ .execute();
}
- private void downloadAssetIndex() {
+ private Void downloadAssetIndex() {
final VersionJson json = getVersionJson().get();
final VersionJson.AssetIndex assetIndexData = json.getAssetIndex();
@@ -79,15 +89,17 @@ private void downloadAssetIndex() {
});
executor.await();
+
+ return null;
}
- private void downloadAssets() {
+ private Void downloadAssets() {
final AssetIndex assetIndex = SerializationUtils.fromJson(getAssetIndexFile().getAsFile().get(), AssetIndex.class);
final WorkQueue executor = getWorkerExecutor().noIsolation();
assetIndex.getObjects().values().stream().distinct().forEach((asset) -> {
- final Provider assetFile = getFileInAssetsDirectory(String.format("objects%s%s", File.separator, asset.getPath()));
+ final Provider assetFile = getFileInAssetsDirectory(asset.getPath());
final Provider assetUrl = getAssetRepository()
.map(repo -> repo.endsWith("/") ? repo : repo + "/")
.map(TransformerUtils.guard(repository -> repository + asset.getPath()));
@@ -102,6 +114,8 @@ private void downloadAssets() {
});
executor.await();
+
+ return null;
}
@Inject
@@ -117,7 +131,7 @@ private void downloadAssets() {
@Input
public abstract Property getAssetIndex();
-
+
@Input
public abstract Property getAssetIndexFileName();
@@ -132,7 +146,7 @@ private void downloadAssets() {
@Input
public abstract Property getIsOffline();
-
+
private static class AssetIndex {
private Map objects = Maps.newHashMap();
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/ExtractNatives.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/ExtractNatives.java
index 8d4e36393..bfbe4ccf0 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/ExtractNatives.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/ExtractNatives.java
@@ -8,7 +8,6 @@
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.*;
-import org.gradle.work.DisableCachingByDefault;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/GenerateExtraJar.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/GenerateExtraJar.java
index 14daa0223..d64b73620 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/GenerateExtraJar.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/GenerateExtraJar.java
@@ -1,7 +1,7 @@
package net.neoforged.gradle.common.runtime.tasks;
-import net.neoforged.gradle.common.CommonProjectPlugin;
-import net.neoforged.gradle.common.caching.CentralCacheService;
+import net.neoforged.gradle.common.services.caching.CachedExecutionService;
+import net.neoforged.gradle.common.services.caching.jobs.ICacheableJob;
import net.neoforged.gradle.dsl.common.tasks.NeoGradleBase;
import net.neoforged.gradle.dsl.common.tasks.WithOutput;
import net.neoforged.gradle.dsl.common.tasks.WithWorkspace;
@@ -28,15 +28,19 @@ public GenerateExtraJar() {
getOutputFileName().set("client-extra.jar");
}
- @ServiceReference(CommonProjectPlugin.EXECUTE_SERVICE)
- public abstract Property getCacheService();
+ @ServiceReference(CachedExecutionService.NAME)
+ public abstract Property getCacheService();
@TaskAction
public void run() throws Throwable {
- getCacheService().get().doCached(this, this::doRun, getOutput());
+ getCacheService().get()
+ .cached(
+ this,
+ ICacheableJob.Default.file(getOutput(), this::doRun)
+ ).execute();
}
- public File doRun() throws Exception {
+ private void doRun() throws Exception {
final File originalJar = getOriginalJar().get().getAsFile();
final File outputJar = ensureFileWorkspaceReady(getOutput());
@@ -49,8 +53,6 @@ public File doRun() throws Exception {
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputJar))) {
filteredInput.visit(new ZipBuildingFileTreeVisitor(zos));
}
-
- return outputJar;
}
@InputFile
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/ListLibraries.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/ListLibraries.java
index ac455994b..e2a62fd10 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/ListLibraries.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/ListLibraries.java
@@ -2,8 +2,8 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import net.neoforged.gradle.common.CommonProjectPlugin;
-import net.neoforged.gradle.common.caching.CentralCacheService;
+import net.neoforged.gradle.common.services.caching.CachedExecutionService;
+import net.neoforged.gradle.common.services.caching.jobs.ICacheableJob;
import net.neoforged.gradle.common.runtime.tasks.action.DownloadFileAction;
import net.neoforged.gradle.common.util.FileCacheUtils;
import net.neoforged.gradle.common.util.SerializationUtils;
@@ -52,13 +52,25 @@ public ListLibraries() {
@Input
public abstract Property getIsOffline();
-
- @ServiceReference(CommonProjectPlugin.LIBRARIES_SERVICE)
- public abstract Property getLibrariesCache();
+
+ @ServiceReference(CachedExecutionService.NAME)
+ public abstract Property getCacheService();
+
@TaskAction
public void run() throws IOException {
- final File output = ensureFileWorkspaceReady(getOutput());
+ getCacheService().get()
+ .cached(
+ this,
+ ICacheableJob.Initial.directory("collect", getLibrariesDirectory(), this::extractAndCollect)
+ )
+ .withStage(
+ ICacheableJob.Staged.file("list", getOutput(), this::createList)
+ )
+ .execute();
+ }
+
+ private Set extractAndCollect() throws IOException {
try (FileSystem bundleFs = !getServerBundleFile().isPresent() ? null : FileSystems.newFileSystem(getServerBundleFile().get().getAsFile().toPath(), this.getClass().getClassLoader())) {
final Set libraries;
if (bundleFs == null) {
@@ -66,17 +78,23 @@ public void run() throws IOException {
} else {
libraries = unpackAndListBundleLibraries(bundleFs);
}
-
- // Write the list
- PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(output), StandardCharsets.UTF_8));
- Iterator itr = libraries.stream().sorted(Comparator.naturalOrder()).iterator();
- while (itr.hasNext()) {
- writer.println("-e=" + itr.next().getAbsolutePath());
- }
- writer.flush();
- writer.close();
+ return libraries;
}
}
+
+ private Void createList(final Set libraries) throws FileNotFoundException {
+ // Write the list
+ final File output = ensureFileWorkspaceReady(getOutput());
+ PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(output), StandardCharsets.UTF_8));
+ Iterator itr = libraries.stream().sorted(Comparator.comparing(File::getAbsolutePath)).iterator();
+ while (itr.hasNext()) {
+ writer.println("-e=" + itr.next().getAbsolutePath());
+ }
+ writer.flush();
+ writer.close();
+
+ return null;
+ }
private List listBundleLibraries(FileSystem bundleFs) throws IOException {
Path mfp = bundleFs.getPath("META-INF", "MANIFEST.MF");
@@ -181,8 +199,7 @@ private Set downloadAndListJsonLibraries() throws IOException {
@PathSensitive(PathSensitivity.NONE)
public abstract RegularFileProperty getDownloadedVersionJsonFile();
- @InputDirectory
- @PathSensitive(PathSensitivity.NONE)
+ @OutputDirectory
public abstract DirectoryProperty getLibrariesDirectory();
private static class FileList {
diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/SourceAccessTransformer.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/SourceAccessTransformer.java
index 166a7a741..0b2545fd4 100644
--- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/SourceAccessTransformer.java
+++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/SourceAccessTransformer.java
@@ -57,15 +57,14 @@ public SourceAccessTransformer() {
}
@Override
- public File doExecute() throws Throwable {
+ public void doExecute() throws Exception {
//We need a separate check here that skips the execute call if there are no transformers.
if (getTransformers().isEmpty()) {
final File output = ensureFileWorkspaceReady(getOutput());
FileUtils.copyFile(getInputFile().get().getAsFile(), output);
- return output;
}
- return super.doExecute();
+ super.doExecute();
}
@InputFile
diff --git a/common/src/main/java/net/neoforged/gradle/common/services/caching/CachedExecutionBuilder.java b/common/src/main/java/net/neoforged/gradle/common/services/caching/CachedExecutionBuilder.java
new file mode 100644
index 000000000..2a3cf40ad
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/services/caching/CachedExecutionBuilder.java
@@ -0,0 +1,280 @@
+package net.neoforged.gradle.common.services.caching;
+
+import net.neoforged.gradle.common.services.caching.cache.DirectoryCache;
+import net.neoforged.gradle.common.services.caching.cache.FileCache;
+import net.neoforged.gradle.common.services.caching.cache.ICache;
+import net.neoforged.gradle.common.services.caching.hasher.TaskHasher;
+import net.neoforged.gradle.common.services.caching.jobs.ICacheableJob;
+import net.neoforged.gradle.common.services.caching.locking.FileBasedLock;
+import net.neoforged.gradle.common.services.caching.logging.CacheLogger;
+import net.neoforged.gradle.common.util.hash.HashCode;
+import net.neoforged.gradle.common.util.hash.Hasher;
+import net.neoforged.gradle.common.util.hash.Hashing;
+import net.neoforged.gradle.util.GradleInternalUtils;
+import org.apache.commons.io.FileUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+public class CachedExecutionBuilder {
+
+ public record LoggingOptions(boolean cacheHits, boolean debug) {}
+
+ public record Options(boolean enabled, File cache, LoggingOptions logging) {}
+
+ private record JobHasher(HashCode taskHash, ICacheableJob,?> job, Hasher hasher) {
+
+ public JobHasher(HashCode taskHash, ICacheableJob, ?> job) {
+ this(taskHash, job, Hashing.sha256().newHasher());
+ }
+
+ public HashCode hash() {
+ hasher.putHash(taskHash);
+ hasher.putString(job.name());
+ return hasher.hash();
+ }
+ }
+
+ private record CacheStatus(@Nullable FileBasedLock lock, boolean shouldExecute, @Nullable ICache cache) implements AutoCloseable {
+
+ private static CacheStatus alwaysUnlocked() {
+ return new CacheStatus(null, true, null);
+ }
+
+ public static CacheStatus runWithLock(FileBasedLock lock, ICache cache) {
+ return new CacheStatus(lock, true, cache);
+ }
+
+ public static CacheStatus cachedWithLock(FileBasedLock lock) {
+ return new CacheStatus(lock, false, null);
+ }
+
+ @NotNull
+ public ICache cache() {
+ if (cache == null) {
+ throw new IllegalStateException("No cache is available.");
+ }
+
+ return cache;
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (lock != null) {
+ lock.close();
+ }
+ }
+
+ public void onSuccess() {
+ if (lock != null) {
+ lock.updateAccessTime();
+ lock.markAsSuccess();
+ }
+ }
+ }
+
+ private final Options options;
+ private final Task targetTask;
+ private final List> stages;
+
+ private final CacheLogger logger;
+
+ public CachedExecutionBuilder(Options options, Task targetTask, ICacheableJob initialJob) {
+ this(options, targetTask, List.of(initialJob));
+ }
+
+ private CachedExecutionBuilder(Options options, Task targetTask, List> stages) {
+ this.options = options;
+ this.targetTask = targetTask;
+ this.stages = stages;
+ this.logger = new CacheLogger(targetTask, options.logging().debug(), options.logging().cacheHits());
+ }
+
+ public CachedExecutionBuilder withStage(ICacheableJob job) {
+ List> newStages = new ArrayList<>(stages);
+ newStages.add(job);
+ return new CachedExecutionBuilder<>(options, targetTask, newStages);
+ }
+
+ /**
+ * Executes the cached execution.
+ *
+ * @throws IOException If an error occurs while executing the cached execution.
+ */
+ public void execute() throws IOException {
+ //When caching is disabled, we do not need to do anything.
+ if (!options.enabled()) {
+ logger.debug("Caching is disabled, executing all stages.");
+ executeAll(
+ (stage) -> CacheStatus.alwaysUnlocked(),
+ (stage, status) -> logger.onCacheMiss(stage)
+ );
+ return;
+ }
+
+ //Create the hash of the task
+ final TaskHasher hasher = new TaskHasher(targetTask, logger);
+ final HashCode taskHash = hasher.create();
+
+ logger.debug("Task hash: %s".formatted(taskHash));
+ executeAll(
+ shouldExecuteCachedFor(targetTask, taskHash),
+ afterExecution()
+ );
+ }
+
+ /**
+ * Creates a function that determines if a stage should be executed.
+ *
+ * @param targetTask The target task.
+ * @param taskHash The hash of the task.
+ * @return The function that determines if a stage should be executed.
+ */
+ private Function, CacheStatus> shouldExecuteCachedFor(Task targetTask, HashCode taskHash) {
+ return (stage) -> {
+ //Create the cache
+ final ICache cache = createCache(taskHash, stage);
+
+ //Create and acquire the lock on the cache
+ final FileBasedLock lock = cache.createLock(logger);
+
+ try {
+ //A cached execution is only healthy if the healthy file exists
+ if (lock.hasPreviousFailure()) {
+ logger.debug("Previous failure detected for stage: %s".formatted(stage));
+ return CacheStatus.runWithLock(lock, cache);
+ }
+
+ //We have a healthy lock, and the previous execution was successful
+ //We can now attempt to restore the cache
+ if (!cache.restoreTo(stage.output())) {
+ //No cache restore was needed, we can skip the stage
+ logger.onCacheEquals(stage);
+ }
+
+ targetTask.setDidWork(false);
+
+ //The cache was restored successfully, we do not need to execute the stage
+ return CacheStatus.cachedWithLock(lock);
+ } catch (Exception e) {
+ throw new GradleException("Failed to restore cache for stage: %s".formatted(stage), e);
+ }
+ };
+ }
+
+ private AfterExecute afterExecution() {
+ //Return a consumer that logs the cache hit or miss
+ return (stage, status) -> {
+ if (status.shouldExecute()) {
+ logger.onCacheMiss(stage);
+ status.cache().loadFrom(stage.output());
+ } else {
+ logger.onCacheHit(stage);
+ }
+ };
+ }
+
+ /**
+ * Creates a cache for the given task hash and job.
+ *
+ * @param taskHash The hash of the task.
+ * @param job The job to create the cache for.
+ * @return The cache for the given task hash and job.
+ */
+ private ICache createCache(final HashCode taskHash, final ICacheableJob,?> job) {
+ final JobHasher jobHasher = new JobHasher(taskHash, job);
+ final File cacheDir = new File(options.cache(), jobHasher.hash().toString());
+
+ return job.createsDirectory() ? new DirectoryCache(cacheDir) : new FileCache(cacheDir);
+ }
+
+ /**
+ * Executes the given job with the given input.
+ *
+ * @param job The job to execute.
+ * @param input The input for the job.
+ * @return The output of the job.
+ * @throws IOException If an error occurs while executing the job.
+ */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private Object executeStage(ICacheableJob job, Object input) throws Throwable {
+ final File intendedOutput = job.output();
+
+ prepareWorkspace(intendedOutput, job.createsDirectory());
+
+ return job.execute(input);
+ }
+
+ /**
+ * Prepares the workspace for the given output.
+ *
+ * @param output The output to prepare the workspace for.
+ * @param isDirectory Whether the output is a directory.
+ * @throws IOException If an error occurs while preparing the workspace.
+ */
+ private void prepareWorkspace(final File output, final boolean isDirectory) throws IOException {
+ if (isDirectory) {
+ if (!output.exists() && !output.mkdirs()) {
+ throw new RuntimeException("Failed to create directory: %s".formatted(output.getAbsolutePath()));
+ }
+
+ if (output.exists()) {
+ FileUtils.cleanDirectory(output);
+ }
+ } else {
+ if (output.exists() && !output.delete()) {
+ throw new RuntimeException("Failed to delete file: %s".formatted(output.getAbsolutePath()));
+ }
+ }
+ }
+
+ /**
+ * Executes all stages.
+ *
+ * @param beforeExecute The function to execute before executing a stage.
+ * @param afterExecute The consumer to execute after executing a stage.
+ */
+ private void executeAll(
+ final Function, CacheStatus> beforeExecute,
+ final AfterExecute afterExecute
+ ) {
+ //Holds the current state.
+ Object state = null;
+
+ //Loop over all stages and execute them if needed.
+ for (ICacheableJob, ?> stage : stages) {
+
+ //Grab a cache status for the stage
+ try(CacheStatus status = beforeExecute.apply(stage)) {
+
+ //If we should execute the stage, execute it.
+ if (status.shouldExecute()) {
+ state = executeStage(stage, state);
+ }
+
+ //Run the after execute consumer
+ afterExecute.accept(stage, status);
+
+ //Mark the status as successful
+ status.onSuccess();
+ } catch (Throwable e) {
+ //If an exception occurs, throw a Gradle exception.
+ //We do not need to notify the status of the failure, as it will be closed and assumes failure
+ //if it is not marked as successful.
+ throw new GradleException("Failed to execute stage: %s".formatted(stage), e);
+ }
+ }
+ }
+
+ private interface AfterExecute {
+ void accept(ICacheableJob,?> stage, CacheStatus status) throws Exception;
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/services/caching/CachedExecutionService.java b/common/src/main/java/net/neoforged/gradle/common/services/caching/CachedExecutionService.java
new file mode 100644
index 000000000..ba7bcb259
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/services/caching/CachedExecutionService.java
@@ -0,0 +1,82 @@
+package net.neoforged.gradle.common.services.caching;
+
+
+import net.neoforged.gradle.common.services.caching.jobs.ICacheableJob;
+import org.apache.commons.io.FileUtils;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.services.BuildService;
+import org.gradle.api.services.BuildServiceParameters;
+
+import java.io.File;
+import java.io.IOException;
+
+public abstract class CachedExecutionService implements BuildService {
+
+ public static final String NAME = "CachedExecutionService";
+
+ public static final String DIRECTORY_NAME = "ng_execute";
+
+ public static final String CACHING_PROPERTY_PREFIX = "net.neoforged.gradle.caching.";
+ public static final String CACHE_DIRECTORY_PROPERTY = CACHING_PROPERTY_PREFIX + "cacheDirectory";
+ public static final String LOG_CACHE_HITS_PROPERTY = CACHING_PROPERTY_PREFIX + "logCacheHits";
+ public static final String MAX_CACHE_SIZE_PROPERTY = CACHING_PROPERTY_PREFIX + "maxCacheSize";
+ public static final String DEBUG_CACHE_PROPERTY = CACHING_PROPERTY_PREFIX + "debug";
+ public static final String IS_ENABLED_PROPERTY = CACHING_PROPERTY_PREFIX + "enabled";
+
+
+ public interface Parameters extends BuildServiceParameters {
+
+ DirectoryProperty getCacheDirectory();
+
+ Property getLogCacheHits();
+
+ Property getMaxCacheSize();
+
+ Property getDebugCache();
+
+ Property getIsEnabled();
+ }
+
+ public static void register(Project project) {
+ project.getGradle().getSharedServices().registerIfAbsent(
+ NAME,
+ CachedExecutionService.class,
+ spec -> {
+ spec.getParameters().getCacheDirectory()
+ .fileProvider(project.getProviders().gradleProperty(CACHE_DIRECTORY_PROPERTY)
+ .map(File::new)
+ .orElse(new File(new File(project.getGradle().getGradleUserHomeDir(), "caches"), DIRECTORY_NAME)));
+ spec.getParameters().getLogCacheHits().set(project.getProviders().gradleProperty(LOG_CACHE_HITS_PROPERTY).map(Boolean::parseBoolean).orElse(false));
+ spec.getParameters().getMaxCacheSize().set(project.getProviders().gradleProperty(MAX_CACHE_SIZE_PROPERTY).map(Integer::parseInt).orElse(100));
+ spec.getParameters().getDebugCache().set(project.getProviders().gradleProperty(DEBUG_CACHE_PROPERTY).map(Boolean::parseBoolean).orElse(false));
+ spec.getParameters().getIsEnabled().set(project.getProviders().gradleProperty(IS_ENABLED_PROPERTY).map(Boolean::parseBoolean).orElse(true));
+ }
+ );
+ }
+
+ public void clean() throws IOException {
+ FileUtils.cleanDirectory(getParameters().getCacheDirectory().get().getAsFile());
+ }
+
+ public CachedExecutionBuilder cached(
+ Task task,
+ ICacheableJob initial
+ ) {
+ return new CachedExecutionBuilder<>(
+ new CachedExecutionBuilder.Options(
+ getParameters().getIsEnabled().get(),
+ getParameters().getCacheDirectory().get().getAsFile(),
+ new CachedExecutionBuilder.LoggingOptions(
+ getParameters().getLogCacheHits().get(),
+ getParameters().getDebugCache().get()
+ )
+ ),
+ task,
+ initial
+ );
+ }
+
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/services/caching/cache/DirectoryCache.java b/common/src/main/java/net/neoforged/gradle/common/services/caching/cache/DirectoryCache.java
new file mode 100644
index 000000000..8015eaa8e
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/services/caching/cache/DirectoryCache.java
@@ -0,0 +1,68 @@
+package net.neoforged.gradle.common.services.caching.cache;
+
+import net.neoforged.gradle.common.services.caching.locking.FileBasedLock;
+import net.neoforged.gradle.common.services.caching.locking.LockManager;
+import net.neoforged.gradle.common.services.caching.logging.CacheLogger;
+import net.neoforged.gradle.common.util.hash.Hashing;
+import org.apache.commons.io.FileUtils;
+import org.gradle.api.GradleException;
+
+import java.io.File;
+import java.io.IOException;
+
+public class DirectoryCache implements ICache {
+
+ private final File cacheDir;
+
+ public DirectoryCache(File cacheDir) {
+ this.cacheDir = cacheDir;
+ }
+
+ @Override
+ public void loadFrom(File file) throws IOException {
+ if (file.exists()) {
+ final File output = new File(cacheDir, "output");
+ if (!output.exists()) {
+ output.mkdirs();
+ }
+
+ FileUtils.cleanDirectory(output);
+ FileUtils.copyDirectory(file, output);
+ }
+ }
+
+ @Override
+ public boolean restoreTo(File file) throws IOException {
+ final File output = new File(cacheDir, "output");
+
+ if (file.exists()) {
+ if (file.isDirectory() && output.exists()) {
+ if (Hashing.hashDirectory(file).equals(Hashing.hashDirectory(output))) {
+ return false;
+ }
+ }
+
+ if (file.isDirectory()) {
+ FileUtils.cleanDirectory(file);
+ }
+ file.delete();
+ }
+
+ file.mkdirs();
+
+ if (output.exists()) {
+ try {
+ FileUtils.copyDirectory(output, file);
+ } catch (IOException e) {
+ throw new GradleException("Failed to restore cache.", e);
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public FileBasedLock createLock(CacheLogger logger) {
+ return LockManager.createLock(cacheDir, logger);
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/services/caching/cache/FileCache.java b/common/src/main/java/net/neoforged/gradle/common/services/caching/cache/FileCache.java
new file mode 100644
index 000000000..d9980d841
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/services/caching/cache/FileCache.java
@@ -0,0 +1,71 @@
+package net.neoforged.gradle.common.services.caching.cache;
+
+import net.neoforged.gradle.common.services.caching.locking.FileBasedLock;
+import net.neoforged.gradle.common.services.caching.locking.LockManager;
+import net.neoforged.gradle.common.services.caching.logging.CacheLogger;
+import net.neoforged.gradle.common.util.hash.Hashing;
+import org.apache.commons.io.FileUtils;
+import org.gradle.api.GradleException;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FileCache implements ICache {
+
+ private final File cacheDir;
+
+ public FileCache(File cacheDir) {
+ this.cacheDir = cacheDir;
+ }
+
+ @Override
+ public void loadFrom(File file) throws IOException {
+ final File cacheFile = new File(cacheDir, "output");
+ if (cacheFile.exists()) {
+ cacheFile.delete();
+ }
+
+ // If the file does not exist, there is nothing to load
+ if (!file.exists()) {
+ return;
+ }
+
+ FileUtils.copyFile(file, cacheFile);
+ }
+
+ @Override
+ public boolean restoreTo(File file) throws IOException {
+ final File cacheFile = new File(cacheDir, "output");
+
+ if (file.exists()) {
+ if (file.isFile() && cacheFile.exists()) {
+ if (Hashing.hashFile(file).equals(Hashing.hashFile(cacheFile))) {
+ return false;
+ }
+ }
+
+ if (file.isDirectory()) {
+ FileUtils.cleanDirectory(file);
+ }
+
+ file.delete();
+ }
+
+ //If the file exists we can restore it, that means if previous executions did not create an output
+ //Then we should not restore it as our cache file would not exist.
+ if (cacheFile.exists()) {
+ try {
+ FileUtils.copyFile(cacheFile, file);
+ } catch (IOException e) {
+ throw new GradleException("Failed to restore cache. Copying of the cache file failed.", e);
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public FileBasedLock createLock(CacheLogger logger) {
+ return LockManager.createLock(cacheDir, logger);
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/services/caching/cache/ICache.java b/common/src/main/java/net/neoforged/gradle/common/services/caching/cache/ICache.java
new file mode 100644
index 000000000..6654f13b5
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/services/caching/cache/ICache.java
@@ -0,0 +1,35 @@
+package net.neoforged.gradle.common.services.caching.cache;
+
+import net.neoforged.gradle.common.services.caching.locking.FileBasedLock;
+import net.neoforged.gradle.common.services.caching.logging.CacheLogger;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Represents a cache that can be loaded from and restored to a file or directory.
+ */
+public interface ICache {
+
+ /**
+ * Clears the current cache and loads the cache from the given file or directory.
+ *
+ * @param file The file to load the cache from.
+ */
+ void loadFrom(File file) throws IOException;
+
+ /**
+ * Restores the cache to the given file or directory.
+ *
+ * @param file The file to restore the cache to.
+ * @return True if the cache was restored, false if the cache was not restored and considered equal.
+ */
+ boolean restoreTo(File file) throws IOException;
+
+ /**
+ * Creates a lock for the cache.
+ *
+ * @return The lock for the cache.
+ */
+ FileBasedLock createLock(CacheLogger logger);
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/services/caching/hasher/TaskHasher.java b/common/src/main/java/net/neoforged/gradle/common/services/caching/hasher/TaskHasher.java
new file mode 100644
index 000000000..f0f15eb2e
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/services/caching/hasher/TaskHasher.java
@@ -0,0 +1,73 @@
+package net.neoforged.gradle.common.services.caching.hasher;
+
+import net.neoforged.gradle.common.services.caching.logging.CacheLogger;
+import net.neoforged.gradle.common.util.hash.HashCode;
+import net.neoforged.gradle.common.util.hash.HashFunction;
+import net.neoforged.gradle.common.util.hash.Hasher;
+import net.neoforged.gradle.common.util.hash.Hashing;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.TaskInputs;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Stream;
+
+public final class TaskHasher {
+ private final HashFunction hashFunction = Hashing.sha256();
+ private final Hasher hasher = hashFunction.newHasher();
+
+ private final Task task;
+ private final CacheLogger logger;
+
+ public TaskHasher(Task task, CacheLogger logger) {
+ this.task = task;
+ this.logger = logger;
+ }
+
+ public void hash() throws IOException {
+ logger.debug("Hashing task: " + task.getPath());
+ hasher.putString(task.getClass().getName());
+
+ final TaskInputs taskInputs = task.getInputs();
+ hash(taskInputs);
+ }
+
+ private void hash(TaskInputs inputs) throws IOException {
+ logger.debug("Hashing task inputs: " + task.getPath());
+ inputs.getProperties().forEach((key, value) -> {
+ logger.debug("Hashing task input property: " + key);
+ hasher.putString(key);
+ logger.debug("Hashing task input property value: " + value);
+ hasher.put(value, false); //We skin unknown types (mostly file collections)
+ });
+
+ final Set inputFiles = new HashSet<>();
+
+ for (File file : inputs.getFiles()) {
+ try (Stream pathStream = Files.walk(file.toPath())) {
+ for (Path path : pathStream.filter(Files::isRegularFile).toList()) {
+ inputFiles.add(path.toFile());
+ }
+ }
+ }
+
+ final List files = new ArrayList<>(inputFiles);
+ files.sort(Comparator.comparing(File::getAbsolutePath));
+
+ for (File file : files) {
+ logger.debug("Hashing task input file: " + file.getAbsolutePath());
+ hasher.putString(file.getName());
+ final HashCode code = hashFunction.hashFile(file);
+ logger.debug("Hashing task input file hash: " + code);
+ hasher.putHash(code);
+ }
+ }
+
+ public HashCode create() throws IOException {
+ hash();
+ return hasher.hash();
+ }
+}
diff --git a/common/src/main/java/net/neoforged/gradle/common/services/caching/jobs/ICacheableJob.java b/common/src/main/java/net/neoforged/gradle/common/services/caching/jobs/ICacheableJob.java
new file mode 100644
index 000000000..621f54aa9
--- /dev/null
+++ b/common/src/main/java/net/neoforged/gradle/common/services/caching/jobs/ICacheableJob.java
@@ -0,0 +1,169 @@
+package net.neoforged.gradle.common.services.caching.jobs;
+
+import com.machinezoo.noexception.throwing.ThrowingFunction;
+import com.machinezoo.noexception.throwing.ThrowingSupplier;
+import org.gradle.api.file.Directory;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.Provider;
+
+import java.io.File;
+
+/**
+ * Defines a job that can be cached.
+ *
+ * @param The input type of the job.
+ * @param The type of the output of the job.
+ */
+public interface ICacheableJob {
+
+ /**
+ * @return The name of the job.
+ * @implSpec The default name of a job is 'default'
+ */
+ default String name() {
+ return "default";
+ }
+
+ /**
+ * @return The output of the job.
+ */
+ File output();
+
+ /**
+ * Executes the job.
+ *
+ * @param input The input of the job.
+ * @return The output of the job.
+ */
+ O execute(I input) throws Throwable;
+
+ /**
+ * @return True if the output is a directory.
+ */
+ boolean createsDirectory();
+
+ /**
+ * The functional interface for a runnable that throws an exception.
+ */
+ @FunctionalInterface
+ interface ThrowingRunnable {
+ void run() throws Exception;
+ }
+
+ /**
+ * Creates a new cacheable job that executes the given code.
+ *
+ * @param output The output of the job.
+ * @param createsDirectory True if the output is a directory.
+ * @param execute The code to execute.
+ */
+ record Default(File output, boolean createsDirectory, ThrowingRunnable execute) implements ICacheableJob {
+
+ /**
+ * Creates a new cacheable job that executes the given code for the file provided by the property.
+ * Realising the property when this method is called.
+ *
+ * @param output The output of the job.
+ * @param execute The code to execute.
+ * @return The created job.
+ */
+ public static Default file(RegularFileProperty output, ThrowingRunnable execute) {
+ return new Default(output.get().getAsFile(), false, execute);
+ }
+
+ /**
+ * Creates a new cacheable job that executes the given code for the directory provided by the property.
+ * Realising the property when this method is called.
+ *
+ * @param output The output of the job.
+ * @param execute The code to execute.
+ * @return The created job.
+ */
+ public static Default directory(DirectoryProperty output, ThrowingRunnable execute) {
+ return new Default(output.get().getAsFile(), true, execute);
+ }
+
+ @Override
+ public Void execute(Void input) throws Exception {
+ execute().run();
+ return null;
+ }
+ }
+
+ /**
+ * Creates a new cacheable job that executes the given code.
+ *
+ * @param name The name of the job.
+ * @param output The output of the job.
+ * @param createsDirectory True if the output is a directory.
+ * @param execute The code to execute.
+ * @param The type of the output of the job.
+ */
+ record Initial(String name, File output, boolean createsDirectory, ThrowingSupplier execute) implements ICacheableJob {
+
+ /**
+ * Creates a new cacheable job that executes the given code for the file provided by the property.
+ * Realising the property when this method is called.
+ *
+ * @param name The name of the job.
+ * @param output The output of the job.
+ * @param execute The code to execute.
+ * @return The created job.
+ */
+ public static Initial file(String name, RegularFileProperty output, ThrowingSupplier execute) {
+ return new Initial<>(name, output.get().getAsFile(), false, execute);
+ }
+
+ /**
+ * Creates a new cacheable job that executes the given code for the directory provided by the property.
+ * Realising the property when this method is called.
+ *
+ * @param name The name of the job.
+ * @param output The output of the job.
+ * @param execute The code to execute.
+ * @return The created job.
+ */
+ public static Initial directory(String name, Provider output, ThrowingSupplier execute) {
+ return new Initial<>(name, output.get().getAsFile(), true, execute);
+ }
+
+ @Override
+ public V execute(Void input) throws Throwable {
+ return execute().get();
+ }
+ }
+
+ /**
+ * Creates a new cacheable job that stages the given job.
+ *
+ * @param name The name of the job.
+ * @param output The output of the job.
+ * @param createsDirectory True if the output is a directory.
+ * @param job The job to stage.
+ * @param The input type of the job.
+ * @param The output type of the job.
+ */
+ record Staged(String name, File output, boolean createsDirectory, ThrowingFunction