Skip to content

[feature/caching-rework] Centralize build planning - #2207

Open
lbussell wants to merge 9 commits into
feature/caching-reworkfrom
lbussell/centralize-build-planning
Open

[feature/caching-rework] Centralize build planning#2207
lbussell wants to merge 9 commits into
feature/caching-reworkfrom
lbussell/centralize-build-planning

Conversation

@lbussell

@lbussell lbussell commented Aug 5, 2026

Copy link
Copy Markdown
Member

This PR centralizes build planning and policies across multiple separate commands. The design is not perfect as-is but I would like to work on improving it incrementally.

Design

The design centers around three new types: BuildGraph, BuildPlanner, and IBuildPolicy.

A BuildGraph represents the relationships between the images in the manifest.
The BuildPlanner takes the BuildGraph in combination with an IBuildPolicy and uses it to determine what actions need to be taken.

classDiagram
direction LR

class BuildCommand {
}
class GenerateBuildMatrixCommand {
}
class GetStaleImagesCommand {
}
class BuildTarget {
}
class BuildGraph {
  +Targets
  +Parents
  +Children
  +SharedBuildTargets
}
class BuildPlanner {
  +CreatePlanAsync(graph, imageInfo, policy)
}
class IBuildPolicy {
}
class BuildPlanItem {
  +Target
  +Action
  +Reasons
  +PublishedImage
}
class BuildAction {
  NoAction
  UsePublishedImage
  PublishExistingImage
  BuildImage
}
class BuildReason {
  +Message
  +Cause
}
class PublishedImage {
  +Source
  +Image
  +SharedTags
}

BuildCommand --> BuildGraph : creates
GenerateBuildMatrixCommand --> BuildGraph : creates
GetStaleImagesCommand --> BuildGraph : creates

BuildCommand --> BuildPlanner : executes build plan
GenerateBuildMatrixCommand --> BuildPlanner : uses for trimming
GetStaleImagesCommand --> BuildPlanner : checks for stale images

BuildGraph *-- BuildTarget
BuildPlanner --> BuildGraph
BuildPlanner --> IBuildPolicy : evaluates
BuildPlanner --> BuildPlanItem : produces

BuildPlanItem --> BuildTarget
BuildPlanItem --> BuildAction
BuildPlanItem *-- BuildReason
BuildPlanItem --> PublishedImage
PublishedImage --> BuildTarget : metadata source
Loading

Build policies

There are multiple "policies" that we check images against to determine what actions to take. Previously, all these checks were strewn about the codebase and not managed centrally anywhere.

The CompositeBuildPolicy combines multiple different build policies into one that we can pass to the build planner.

classDiagram
direction LR

class IBuildPolicy {
  +EvaluateAsync(context) BuildPolicyResult
}
class BuildPolicyContext {
  +BuildGraph Graph
  +BuildTarget Target
  +PublishedImages
}
class BuildPolicyResult {
  +BuildAction Action
  +BuildReason[] Reasons
}
class CompositeBuildPolicy {
}
class AlwaysBuildPolicy {
}
class MissingPublishedImagePolicy {
}
class TagSetChangedPolicy {
}
class DockerfileChangedPolicy {
}
class BaseImageChangedPolicy {
}
class ImageDigestCache {
}
class IGitService {
}

IBuildPolicy <|.. CompositeBuildPolicy
IBuildPolicy <|.. AlwaysBuildPolicy
IBuildPolicy <|.. MissingPublishedImagePolicy
IBuildPolicy <|.. TagSetChangedPolicy
IBuildPolicy <|.. DockerfileChangedPolicy
IBuildPolicy <|.. BaseImageChangedPolicy

IBuildPolicy --> BuildPolicyContext : input
IBuildPolicy --> BuildPolicyResult : output
CompositeBuildPolicy o-- IBuildPolicy : combines

BuildPolicyResult --> BuildAction
BuildPolicyResult *-- BuildReason
BuildPolicyContext --> BuildGraph
BuildPolicyContext --> BuildTarget
BuildPolicyContext --> PublishedImage

BaseImageChangedPolicy --> ImageDigestCache
DockerfileChangedPolicy --> IGitService
Loading

Replace ad hoc cache checks with graph-based planning shared by matrix generation, build execution, and stale-image detection. Add composable policies, explicit actions, and causal explanations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1d9f1ac1-4713-44b4-a6ba-17cb095984f6
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1d9f1ac1-4713-44b4-a6ba-17cb095984f6
@lbussell lbussell changed the title Centralize build planning [feature/caching-rework] Centralize build planning Aug 5, 2026
@lbussell
lbussell marked this pull request as ready for review August 5, 2026 21:02
@lbussell
lbussell requested a review from a team as a code owner August 5, 2026 21:02
Comment thread src/ImageBuilder/Commands/GetStaleImagesCommand.cs
Comment thread src/ImageBuilder/Build/BuildPlan.cs
Comment thread src/ImageBuilder/Commands/GetStaleImagesCommand.cs Outdated
Comment thread src/ImageBuilder/Build/BuildPlanner.cs Outdated
@lbussell
lbussell requested a review from mthalman August 10, 2026 20:07
Comment on lines +80 to +86
.ToDictionary(
// SAFETY: PlatformInfo is checked in the Where clause above.
item => targetsByPlatform[item.Platform.PlatformInfo!],
item => new PublishedImage(
targetsByPlatform[item.Platform.PlatformInfo!],
item.Platform,
item.SharedTags)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would break if there are multiple platforms across multiple images that point to the same Dockerfile. This would only happen in a scenario like we once had when we support multi-platform manifest lists across Windows and Linux. In that scenario, we had one manifest image for that multi-platform and another for the multi-arch Debian tag, both tags pointing to the same Debian Dockerfile. That would cause a duplicate key exception to occur here. We don't currently have such a configuration but wanted to point it out.

"Build plan for {BuildTarget}: {Action}. {Reason}",
item.Target.DisplayName,
item.Decision.Action,
item.Decision.Reason);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because BuildReason is recursively nested and we're relying on the logger's default implementation of calling ToString, this results in bunch of nested output, not flattened.

Example:

Build plan for runtime (...): BuildImage. BuildReason { Message = "Dependency 'runtime-deps (...)' must build., CausedBy = BuildReason { Message = Base image 'mcr.microsoft.com/dotnet/runtime:8.0' changed from 'sha256:old' to 'sha256:new'., CausedBy = } }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants