Skip to content

Commit b4e8bb2

Browse files
Dependencies: add consumer-side Alias for multi-version references
Adds a per-use-site `Alias` and `ArtifactPickup` (Snapshot/LastSuccessful) on `ParametrizedDependency` so a referencing product can list the same logical dependency under two different `ProductFamily` versions without MSBuild property-name collisions. The alias drives the `dependencies/{Key}/` directory layout, the generated MSBuild property prefix, and the TeamCity artifact-rule destination. A fetch-time XML transform projects the producer's `<{Name}.version.props>` into a `<{Alias}.version.props>` whose producer-prefixed property/item names are renamed (using a curated suffix list to avoid renaming transitive Feed dep properties). Transitive dependency resolution now starts from the direct dep's family, so an aliased Metalama 2026.0 routes its transitive `Metalama.Compiler` to the V2026_0 definition rather than the consumer's V2026_1 family. `Metalama.Vsx` 2026.1 now references Metalama 2026.0 alongside 2026.1, mapped to BuildConfiguration.Public with `LastSuccessful` artifact pickup (no snapshot dependency). Includes a new `PostSharp.Engineering.BuildTools.Tests` project with 13 tests covering the alias semantics and the version.props transform. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 16b16e1 commit b4e8bb2

21 files changed

Lines changed: 1263 additions & 450 deletions

Directory.Packages.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,9 @@
5050
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.2" />
5151
<PackageVersion Include="AvalonEdit" Version="6.3.0.90" />
5252
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
53+
<!-- Test packages -->
54+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
55+
<PackageVersion Include="xunit" Version="2.9.2" />
56+
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
5357
</ItemGroup>
5458
</Project>

PostSharp.Engineering.sln

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,99 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.Engineering.Syste
1313
EndProject
1414
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostSharp.Engineering.McpApprovalServer", "src\PostSharp.Engineering.McpApprovalServer\PostSharp.Engineering.McpApprovalServer.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
1515
EndProject
16+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
17+
EndProject
18+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.Engineering.BuildTools.Tests", "src\PostSharp.Engineering.BuildTools.Tests\PostSharp.Engineering.BuildTools.Tests.csproj", "{500C0F23-7CC8-4B59-9792-E0040EAEC806}"
19+
EndProject
1620
Global
1721
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1822
Debug|Any CPU = Debug|Any CPU
23+
Debug|x64 = Debug|x64
24+
Debug|x86 = Debug|x86
1925
Release|Any CPU = Release|Any CPU
26+
Release|x64 = Release|x64
27+
Release|x86 = Release|x86
2028
EndGlobalSection
2129
GlobalSection(ProjectConfigurationPlatforms) = postSolution
2230
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2331
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|x64.ActiveCfg = Debug|Any CPU
33+
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|x64.Build.0 = Debug|Any CPU
34+
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|x86.ActiveCfg = Debug|Any CPU
35+
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|x86.Build.0 = Debug|Any CPU
2436
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|Any CPU.ActiveCfg = Release|Any CPU
2537
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|x64.ActiveCfg = Release|Any CPU
39+
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|x64.Build.0 = Release|Any CPU
40+
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|x86.ActiveCfg = Release|Any CPU
41+
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|x86.Build.0 = Release|Any CPU
2642
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2743
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
44+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|x64.ActiveCfg = Debug|Any CPU
45+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|x64.Build.0 = Debug|Any CPU
46+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|x86.ActiveCfg = Debug|Any CPU
47+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|x86.Build.0 = Debug|Any CPU
2848
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
2949
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|Any CPU.Build.0 = Release|Any CPU
50+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|x64.ActiveCfg = Release|Any CPU
51+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|x64.Build.0 = Release|Any CPU
52+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|x86.ActiveCfg = Release|Any CPU
53+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|x86.Build.0 = Release|Any CPU
3054
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3155
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
56+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|x64.ActiveCfg = Debug|Any CPU
57+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|x64.Build.0 = Debug|Any CPU
58+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|x86.ActiveCfg = Debug|Any CPU
59+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|x86.Build.0 = Debug|Any CPU
3260
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
3361
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|Any CPU.Build.0 = Release|Any CPU
62+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|x64.ActiveCfg = Release|Any CPU
63+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|x64.Build.0 = Release|Any CPU
64+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|x86.ActiveCfg = Release|Any CPU
65+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|x86.Build.0 = Release|Any CPU
3466
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3567
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
68+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|x64.ActiveCfg = Debug|Any CPU
69+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|x64.Build.0 = Debug|Any CPU
70+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|x86.ActiveCfg = Debug|Any CPU
71+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|x86.Build.0 = Debug|Any CPU
3672
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
3773
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|Any CPU.Build.0 = Release|Any CPU
74+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|x64.ActiveCfg = Release|Any CPU
75+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|x64.Build.0 = Release|Any CPU
76+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|x86.ActiveCfg = Release|Any CPU
77+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|x86.Build.0 = Release|Any CPU
3878
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3979
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
80+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
81+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU
82+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU
83+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU
4084
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
4185
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
86+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU
87+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
88+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
89+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
90+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
91+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Debug|Any CPU.Build.0 = Debug|Any CPU
92+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Debug|x64.ActiveCfg = Debug|Any CPU
93+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Debug|x64.Build.0 = Debug|Any CPU
94+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Debug|x86.ActiveCfg = Debug|Any CPU
95+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Debug|x86.Build.0 = Debug|Any CPU
96+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Release|Any CPU.ActiveCfg = Release|Any CPU
97+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Release|Any CPU.Build.0 = Release|Any CPU
98+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Release|x64.ActiveCfg = Release|Any CPU
99+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Release|x64.Build.0 = Release|Any CPU
100+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Release|x86.ActiveCfg = Release|Any CPU
101+
{500C0F23-7CC8-4B59-9792-E0040EAEC806}.Release|x86.Build.0 = Release|Any CPU
42102
EndGlobalSection
43103
GlobalSection(SolutionProperties) = preSolution
44104
HideSolutionNode = FALSE
45105
EndGlobalSection
106+
GlobalSection(NestedProjects) = preSolution
107+
{500C0F23-7CC8-4B59-9792-E0040EAEC806} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
108+
EndGlobalSection
46109
GlobalSection(ExtensibilityGlobals) = postSolution
47110
SolutionGuid = {EE2C48C5-7AD7-4852-A39D-A3C5C94000A9}
48111
EndGlobalSection
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
2+
3+
using PostSharp.Engineering.BuildTools.Build;
4+
using PostSharp.Engineering.BuildTools.ContinuousIntegration;
5+
using PostSharp.Engineering.BuildTools.ContinuousIntegration.Model;
6+
using PostSharp.Engineering.BuildTools.Dependencies.Definitions;
7+
using PostSharp.Engineering.BuildTools.Dependencies.Model;
8+
using Xunit;
9+
10+
namespace PostSharp.Engineering.BuildTools.Tests;
11+
12+
public class ParametrizedDependencyAliasTests
13+
{
14+
[Fact]
15+
public void NoAlias_KeyEqualsName()
16+
{
17+
// Use an existing definition to avoid scaffolding ProductFamily registration.
18+
var definition = MetalamaDependencies.V2026_1.Metalama;
19+
var dependency = definition.ToDependency();
20+
21+
Assert.Null( dependency.Alias );
22+
Assert.Equal( definition.Name, dependency.Key );
23+
Assert.Equal( definition.NameWithoutDot, dependency.KeyWithoutDot );
24+
Assert.Equal( DependencyArtifactPickup.Snapshot, dependency.ArtifactPickup );
25+
}
26+
27+
[Fact]
28+
public void WithAlias_KeyAndKeyWithoutDotUseAlias()
29+
{
30+
var definition = MetalamaDependencies.V2026_0.Metalama;
31+
var dependency = definition.WithAlias( "Metalama20260" );
32+
33+
Assert.Equal( "Metalama20260", dependency.Alias );
34+
Assert.Equal( "Metalama20260", dependency.Key );
35+
Assert.Equal( "Metalama20260", dependency.KeyWithoutDot );
36+
Assert.Equal( definition.Name, dependency.Name ); // Name accessor still surfaces the definition's Name
37+
}
38+
39+
[Fact]
40+
public void WithAlias_StripsDots()
41+
{
42+
var definition = MetalamaDependencies.V2026_0.Metalama;
43+
var dependency = definition.WithAlias( "Foo.Bar.Baz" );
44+
45+
Assert.Equal( "Foo.Bar.Baz", dependency.Alias );
46+
Assert.Equal( "Foo.Bar.Baz", dependency.Key );
47+
Assert.Equal( "FooBarBaz", dependency.KeyWithoutDot );
48+
}
49+
50+
[Fact]
51+
public void WithLastSuccessfulOnly_SetsArtifactPickup()
52+
{
53+
var definition = MetalamaDependencies.V2026_0.Metalama;
54+
var dependency = definition.WithAlias( "Metalama20260" ).WithLastSuccessfulOnly();
55+
56+
Assert.Equal( DependencyArtifactPickup.LastSuccessful, dependency.ArtifactPickup );
57+
Assert.Equal( "Metalama20260", dependency.Alias );
58+
}
59+
60+
[Fact]
61+
public void Composes_ConfigurationMappingPlusAliasPlusLastSuccessful()
62+
{
63+
var definition = MetalamaDependencies.V2026_0.Metalama;
64+
var publicMapping = new ConfigurationSpecific<BuildConfiguration>(
65+
BuildConfiguration.Public,
66+
BuildConfiguration.Public,
67+
BuildConfiguration.Public );
68+
69+
var dependency = definition
70+
.ToDependency( publicMapping )
71+
.WithAlias( "Metalama20260" )
72+
.WithLastSuccessfulOnly();
73+
74+
Assert.Equal( BuildConfiguration.Public, dependency.ConfigurationMapping[BuildConfiguration.Debug] );
75+
Assert.Equal( BuildConfiguration.Public, dependency.ConfigurationMapping[BuildConfiguration.Release] );
76+
Assert.Equal( BuildConfiguration.Public, dependency.ConfigurationMapping[BuildConfiguration.Public] );
77+
Assert.Equal( "Metalama20260", dependency.Alias );
78+
Assert.Equal( DependencyArtifactPickup.LastSuccessful, dependency.ArtifactPickup );
79+
}
80+
81+
[Fact]
82+
public void DependencyConfiguration_KeyFallsBackToDefinitionNameWhenNoParametrized()
83+
{
84+
var definition = MetalamaDependencies.V2026_1.Metalama;
85+
var configuration = new DependencyConfiguration( definition, BuildConfiguration.Debug );
86+
87+
Assert.Null( configuration.Parametrized );
88+
Assert.Equal( definition.Name, configuration.Key );
89+
Assert.Equal( definition.NameWithoutDot, configuration.KeyWithoutDot );
90+
Assert.Equal( DependencyArtifactPickup.Snapshot, configuration.ArtifactPickup );
91+
}
92+
93+
[Fact]
94+
public void DependencyConfiguration_KeyUsesAliasFromParametrized()
95+
{
96+
var definition = MetalamaDependencies.V2026_0.Metalama;
97+
var parametrizedDependency = definition.WithAlias( "Metalama20260" ).WithLastSuccessfulOnly();
98+
var configuration = new DependencyConfiguration( definition, BuildConfiguration.Public ) { Parametrized = parametrizedDependency };
99+
100+
Assert.Equal( "Metalama20260", configuration.Key );
101+
Assert.Equal( "Metalama20260", configuration.KeyWithoutDot );
102+
Assert.Equal( DependencyArtifactPickup.LastSuccessful, configuration.ArtifactPickup );
103+
}
104+
105+
[Fact]
106+
public void DependencyConfiguration_EqualityIgnoresParametrized()
107+
{
108+
// (Definition, Configuration) tuple is the equality key. Two configurations with the same Definition+Configuration
109+
// but different Parametrized references must compare equal so HashSet deduplication in GetAllDependencies stays correct.
110+
var definition = MetalamaDependencies.V2026_1.Metalama;
111+
var first = new DependencyConfiguration( definition, BuildConfiguration.Debug );
112+
var second = new DependencyConfiguration( definition, BuildConfiguration.Debug ) { Parametrized = definition.ToDependency() };
113+
114+
Assert.Equal( first, second );
115+
Assert.Equal( first.GetHashCode(), second.GetHashCode() );
116+
}
117+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<LangVersion>preview</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
<IsTestProject>true</IsTestProject>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
13+
<PackageReference Include="xunit" />
14+
<PackageReference Include="xunit.runner.visualstudio">
15+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16+
<PrivateAssets>all</PrivateAssets>
17+
</PackageReference>
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\PostSharp.Engineering.BuildTools\PostSharp.Engineering.BuildTools.csproj" />
22+
</ItemGroup>
23+
24+
</Project>

0 commit comments

Comments
 (0)