Skip to content

Commit 394eed0

Browse files
Dependencies: propagate LastSuccessful through transitive deps
When a parametrized dep is LastSuccessful, the consumer doesn't trigger that dep's build — so chaining the build of its transitive deps is pointless and ends up generating snapshot dependencies that needlessly hold back the consumer's build/deploy. Example seen on MetalamaVsx 2026.1: both Metalama 2026.1 and Metalama 2026.0 are LastSuccessful, but their transitive Metalama.Compiler ref (its own ArtifactPickup defaults to Snapshot) caused a snapshot dep on Metalama.Compiler in the deploy config. Fix: GetAllDependencies now threads an `ancestorIsLastSuccessful` flag through the recursion. Any dep reached via a LastSuccessful ancestor gets EffectiveArtifactPickup = LastSuccessful in the returned DependencyConfiguration. ArtifactPickup consults the effective value first, so existing call sites (ConfigurationProperties snapshot dep generation, deploy-config filtering) automatically pick up the propagation without changes. EffectiveArtifactPickup is internal — it's an implementation detail set by GetAllDependencies, not a consumer-side override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent e847964 commit 394eed0

2 files changed

Lines changed: 28 additions & 5 deletions

File tree

src/PostSharp.Engineering.BuildTools/Dependencies/Model/DependencyConfiguration.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,21 @@ public record DependencyConfiguration( DependencyDefinition Definition, BuildCon
2929
/// </summary>
3030
public string KeyWithoutDot => this.Parametrized?.KeyWithoutDot ?? this.Definition.NameWithoutDot;
3131

32+
/// <summary>
33+
/// Gets the effective artifact pickup mode after propagation through the dep graph (set by
34+
/// <see cref="DependencyDefinition.GetAllDependencies"/>). When a transitive dep is reached only via a
35+
/// <see cref="DependencyArtifactPickup.LastSuccessful"/> ancestor, this is forced to <see cref="DependencyArtifactPickup.LastSuccessful"/>
36+
/// so the consumer doesn't generate snapshot dependencies on a subtree whose root build it doesn't trigger.
37+
/// </summary>
38+
internal DependencyArtifactPickup? EffectiveArtifactPickup { get; init; }
39+
3240
/// <summary>
3341
/// Gets the artifact pickup mode at the consumer's use site, defaulting to <see cref="DependencyArtifactPickup.Snapshot"/>.
42+
/// Returns <see cref="EffectiveArtifactPickup"/> when set (via <see cref="DependencyDefinition.GetAllDependencies"/>),
43+
/// otherwise falls back to the parametrized dep's own setting.
3444
/// </summary>
35-
public DependencyArtifactPickup ArtifactPickup => this.Parametrized?.ArtifactPickup ?? DependencyArtifactPickup.Snapshot;
45+
public DependencyArtifactPickup ArtifactPickup
46+
=> this.EffectiveArtifactPickup ?? this.Parametrized?.ArtifactPickup ?? DependencyArtifactPickup.Snapshot;
3647

3748
public virtual bool Equals( DependencyConfiguration? other )
3849
=> other is not null

src/PostSharp.Engineering.BuildTools/Dependencies/Model/DependencyDefinition.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,24 +76,36 @@ public class DependencyDefinition
7676
public IReadOnlySet<DependencyConfiguration> GetAllDependencies( BuildConfiguration buildConfiguration )
7777
{
7878
HashSet<DependencyConfiguration> dependencies = new();
79-
PopulateRecursive( this, buildConfiguration );
79+
PopulateRecursive( this, buildConfiguration, ancestorIsLastSuccessful: false );
8080

8181
return dependencies;
8282

83-
void PopulateRecursive( DependencyDefinition dependency, BuildConfiguration configuration )
83+
void PopulateRecursive( DependencyDefinition dependency, BuildConfiguration configuration, bool ancestorIsLastSuccessful )
8484
{
8585
foreach ( var child in dependency.Dependencies )
8686
{
8787
var childConfiguration = child.ConfigurationMapping[configuration];
8888

89-
var dependencyConfiguration = new DependencyConfiguration( child, childConfiguration ) { Parametrized = child };
89+
// Propagate LastSuccessful through transitive deps: if any ancestor on the path from the consumer to
90+
// this child is LastSuccessful, we don't trigger that ancestor's build, so chaining the build of its
91+
// transitive deps is pointless. Treat the whole subtree under a LastSuccessful node as LastSuccessful.
92+
var childIsLastSuccessful = ancestorIsLastSuccessful || child.ArtifactPickup == DependencyArtifactPickup.LastSuccessful;
93+
94+
var effectivePickup = childIsLastSuccessful
95+
? DependencyArtifactPickup.LastSuccessful
96+
: child.ArtifactPickup;
97+
98+
var dependencyConfiguration = new DependencyConfiguration( child, childConfiguration )
99+
{
100+
Parametrized = child, EffectiveArtifactPickup = effectivePickup
101+
};
90102

91103
if ( !dependencies.Add( dependencyConfiguration ) )
92104
{
93105
continue;
94106
}
95107

96-
PopulateRecursive( child, childConfiguration );
108+
PopulateRecursive( child, childConfiguration, childIsLastSuccessful );
97109
}
98110
}
99111
}

0 commit comments

Comments
 (0)