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 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 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 getOrCreateIdeImportTask() { - final TaskProvider idePostSyncTask; + public TaskProvider getOrCreateIdeImportTask() { + final TaskProvider 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 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, "