MinecraftForge / ForgeGradle

Minecraft mod development framework used by Forge and FML for the gradle build system
GNU Lesser General Public License v2.1
509 stars 437 forks source link

Run prepare tasks are not available if Gradle is set to configure on demand #838

Open Hubry opened 2 years ago

Hubry commented 2 years ago

If org.gradle.configureondemand=true is present in gradle.properties, or if running the task with --configure-on-demand, running the prepareRunClient or any similar task in a multiproject setup will fail, saying that the task was not found in the project. This blocks running the game through Gradle, as the run tasks require these tasks.

Tested with FG 5.1.+ and Gradle 7.3.3.

Reporting at Sci's request on discord.

sciwhiz12 commented 2 years ago

This seems to be caused by our registration of the run configuration-related tasks[^runtasks] during a Gradle#projectsEvaluated listener, which causes Gradle to not find the task when trying to execute.

Example

Take the following example:

tasks.create("task1") { group 'boop' }
afterEvaluate {
    tasks.create("task2") { group 'boop' }
    gradle.projectsEvaluated {
        tasks.create("task3") { group 'boop' }
    }
}

When running tasks to list out all tasks, all three tasks appear as expected. Yet running the last task, task3, while configuration on demand is enabled causes a build failure as Gradle cannot find the task, even though the first two tasks run successfully.

According to @OrionDevelopment[^orion], this is because the "task tree evaluation happens in practice immediately after project evaluation, but before the afterEvaluate blocks run."

ForgeGradle

In ForgeGradle, we register our run config-related tasks in both userdev and patcherdev plugins by calling the Utils#createRunConfigTasks method from within a Project#afterEvaluate block (the area for task2's registration above), which itself calls RunConfigGenerator#createIDEGenRunsTasks from withn a Gradle#projectsEvaluated block (the area for task3's registration above) as well as registering the tasks for each RunConfig through successive calls to RunConfigGenerator#createRunTask.

This means that when configuration on demand is enabled, as seen in the example above, the run config-related tasks will fail to run due to them seemingly being nonexistent to Gradle.

Conclusions

Fixing this seems to be non-trivial -- the 'ideal' solution would be to modify RunConfig to be lazy and use Propertys, so they can be plugged into tasks lazily, but that involves understanding and replicating in some fashion the confusing mess that is RunConfig's merging system (which I have yet to figure out).

@OrionDevelopment's suggestion to fixing it would be to register the tasks as early as possible -- when the run configs are registered to their containers -- then using 'configuration tasks' which run before each run config task and configure them according to the RunConfig's information. That seems more plausible[^plausible], though someone will need to try implement that suggestion to see any possibly shortcomings or limitations.

This issue seems related to https://github.com/gradle/gradle/issues/9489, which also relates to task execution troubles with configuration on demand and Gradle#projectsEvaluated.

[^runtasks]: The prepare* and run* tasks for each RunConfig, as well as the IDE run generation tasks -- genEclipseRuns, genIntellijRuns, and genVSCodeRuns. [^orion]: Taken from a discussion in Forgecord's #gradle channel. [^plausible]: Insofar that it requires less refactoring and breaking changes than refactoring the whole RunConfig system.