Mark build as contributing to buildscript classpath by 6hundreds · Pull Request #36872 · gradle/gradle

Fixes #36177

Current design overview

In case of included builds, we decide how to store it in CC by checking does it have a work , what is checking the contents of the build's execution plan.

When there is a work:

  • we store the build as IncludedBuild meaning storing its projects as ProjectWithWork:
    val projects = collectProjects(buildState.projects, scheduledWork.scheduledNodes, gradle.serviceOf())
    writeProjects(gradle, projects)
  • when we load, we register and create projects with work of the build:
    val projects = readProjects(gradle, build)
    build.createProjects()

When there is no work:

  • we store the build as BuildWithNoWork meaning storing its projects as ProjectWithNoWork:
    write(ProjectWithNoWork(project.projectPath, project.projectDir, project.mutableModel.buildFile))
  • when we load, we just load BuildWithNoWork without actual project registration in ProjectRegistry:
    withGradleIsolate(rootBuild.gradle, userTypesCodec) {
    val identityPath = Path.path(readString())
    val hasProjects = readBoolean()
    if (hasProjects) {
    val rootProjectName = readString()
    val projects: List<ProjectWithNoWork> = readList().uncheckedCast()
    BuildWithNoWork(identityPath, rootProjectName, projects)
    } else {
    BuildWithNoProjects(identityPath)
    }

Absence of restored project registration in ProjectRegistry is an important aspect, since if we want to have a Project as an input for the work, we have to have it restored properly after CC.

Included plugin builds

In order to provide precompiled plugins to the root build, the task graph of these builds executed early as a part of build logic building process, triggered here:

@Override
public ClassPath resolveClassPath(Configuration classpathConfiguration, ScriptClassPathResolutionContext resolutionContext) {
return buildOperationRunner.call(new CallableBuildOperation<ClassPath>() {
@Override
public ClassPath call(BuildOperationContext context) {
return buildQueue.build(
currentBuild,
taskIdentifiersForBuildDependenciesOf(classpathConfiguration),
() -> scriptClassPathResolver.resolveClassPath(classpathConfiguration, resolutionContext)
);
}

resulting setting EMPTY execution plan for the included build BEFORE CC checks the work presence:

try (ProjectExecutionServiceRegistry projectExecutionServices = new ProjectExecutionServiceRegistry(globalServices)) {
return executeWithServices(projectExecutionServices);
} finally {
executionPlan.close();
executionPlan = FinalizedExecutionPlan.EMPTY;
}

Having said that, included plugin builds always have no work at the CC store moment and being stored as BuildWithNoWork

Included library(non-plugin) builds

The goal of these type of builds is to provide dependency substitutions for the root builds, so the execution of the task graph is postponed(since it doesn't go through build logic building process). In other words, these builds always have some work at the CC store moment and being stored as IncludedBuild, meaning store/reload all its projects.

However, when a project of such a build is being used as a part of buildscript classpath(apply plugins is somewhat similar to this, as it also alters buildscript classpath), we have to go through build logic building process, where we execute task graph early, forcing build to be stored as BuildWithNoWork.

This PR

Tries to mark an included build if it contributes to buildscript classpath(either directly via buildscript{} block or providing a plugin) , to treat it similarly as build having a work.

As we now treat Included build logic build as "having a work", we emit additional Calculate work graph operation(which effectively no op as there are no any nodes for schedule) for included build also, what is making the test failing.

Reviewing cheatsheet

Before merging the PR, comments starting with

  • ❌ ❓must be fixed
  • 🤔 💅 should be fixed
  • 💭 may be fixed
  • 🎉 celebrate happy things