Skip to content

Commit 5db7250

Browse files
Enable pipeline artifact usage for V4 publishing and rework tests (#15727)
Co-authored-by: Jeremy Koritzinsky <jkoritzinsky@gmail.com> Co-authored-by: Jeremy Koritzinsky <jekoritz@microsoft.com>
1 parent 6b63184 commit 5db7250

File tree

18 files changed

+1404
-560
lines changed

18 files changed

+1404
-560
lines changed

eng/publishing/v3/publish.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ stages:
3232
-DarcVersion $(DarcVersion)
3333
3434
- task: DownloadPipelineArtifact@2
35-
displayName: Download Merged Manifest
35+
displayName: Download V3 Merged Manifest
3636
continueOnError: true
3737
enabled: true
3838
inputs:
@@ -44,11 +44,11 @@ stages:
4444
downloadType: 'specific'
4545
artifact: BlobArtifacts
4646
itemPattern: |
47-
BlobArtifacts/MergedManifest.xml
47+
**/MergedManifest.xml
4848
downloadPath: '$(Build.ArtifactStagingDirectory)/BlobArtifacts'
4949

5050
- task: DownloadPipelineArtifact@2
51-
displayName: Download PDB Artifacts
51+
displayName: Download V4 Merged Manifest
5252
continueOnError: true
5353
enabled: true
5454
inputs:
@@ -58,8 +58,10 @@ stages:
5858
pipeline: $(AzDOPipelineId)
5959
buildId: $(AzDOBuildId)
6060
downloadType: 'specific'
61-
artifact: PdbArtifacts
62-
downloadPath: '$(Build.ArtifactStagingDirectory)/PdbArtifacts'
61+
artifact: AssetManifests
62+
itemPattern: |
63+
**/MergedManifest.xml
64+
downloadPath: '$(Build.ArtifactStagingDirectory)/BlobArtifacts'
6365

6466
- task: DownloadPipelineArtifact@2
6567
displayName: Download Release Configs

src/Common/Microsoft.Arcade.Common.Tests/Microsoft.Arcade.Common.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
<ItemGroup>
1313
<ProjectReference Include="..\Microsoft.Arcade.Common\Microsoft.Arcade.Common.csproj" />
14+
<ProjectReference Include="..\..\Microsoft.DotNet.XUnitExtensions\src\Microsoft.DotNet.XUnitExtensions.csproj" />
1415
</ItemGroup>
1516

1617
</Project>

src/Common/Microsoft.Arcade.Common/FileSystem.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.IO;
56

67
#nullable enable
@@ -25,19 +26,32 @@ public class FileSystem : IFileSystem
2526

2627
public string? GetExtension(string? path) => Path.GetExtension(path);
2728

29+
public string GetFullPath(string path) => Path.GetFullPath(path);
30+
2831
public string PathCombine(string path1, string path2) => Path.Combine(path1, path2);
2932

33+
public string PathCombine(string path1, string path2, string path3) => Path.Combine(path1, path2, path3);
34+
3035
public void WriteToFile(string path, string content)
3136
{
3237
string? dirPath = Path.GetDirectoryName(path);
3338
Directory.CreateDirectory(dirPath!);
3439
File.WriteAllText(path, content);
3540
}
3641

37-
public void CopyFile(string sourceFileName, string destFileName, bool overwrite = false) => File.Copy(sourceFileName, destFileName, overwrite);
42+
public virtual void CopyFile(string sourceFileName, string destFileName, bool overwrite = false) => File.Copy(sourceFileName, destFileName, overwrite);
3843

3944
public Stream GetFileStream(string path, FileMode mode, FileAccess access) => new FileStream(path, mode, access);
4045

4146
public FileAttributes GetAttributes(string path) => File.GetAttributes(path);
47+
48+
/// <summary>
49+
/// Intentionally NYI since the backing API is not supported on framework and core.
50+
/// We use this in the PushToBuildStorage task under a limited set of circumstances.
51+
/// </summary>
52+
/// <param name="basePath">Base path</param>
53+
/// <param name="targetPath">Target path that is relative to base path.</param>
54+
/// <exception cref="NotImplementedException"></exception>
55+
public virtual string GetRelativePath(string basePath, string targetPath) => throw new NotImplementedException("Not supported in default FileSystem implementation");
4256
}
4357
}

src/Common/Microsoft.Arcade.Common/IFileSystem.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,18 @@ public interface IFileSystem
2626

2727
string PathCombine(string path1, string path2);
2828

29+
string PathCombine(string path1, string path2, string path3);
30+
31+
string GetFullPath(string path);
32+
2933
void DeleteFile(string path);
3034

3135
void CopyFile(string sourceFileName, string destFileName, bool overwrite = false);
3236

3337
Stream GetFileStream(string path, FileMode mode, FileAccess access);
3438

3539
FileAttributes GetAttributes(string path);
40+
41+
string GetRelativePath(string basePath, string targetPath);
3642
}
3743
}

src/Common/Microsoft.Arcade.Test.Common/MockFileSystem.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.IO;
67
using Microsoft.Arcade.Common;
@@ -20,12 +21,16 @@ public class MockFileSystem : IFileSystem
2021

2122
#endregion
2223

24+
public string DirectorySeparator { get; }
25+
2326
public MockFileSystem(
2427
Dictionary<string, string>? files = null,
25-
IEnumerable<string>? directories = null)
28+
IEnumerable<string>? directories = null,
29+
string directorySeparator = "/")
2630
{
2731
Directories = new(directories ?? new string[0]);
2832
Files = files ?? new();
33+
DirectorySeparator = directorySeparator;
2934
}
3035

3136
#region IFileSystem implementation
@@ -50,7 +55,9 @@ public void DeleteFile(string path)
5055

5156
public string? GetExtension(string? path) => Path.GetExtension(path);
5257

53-
public string PathCombine(string path1, string path2) => path1 + "/" + path2;
58+
public string PathCombine(string path1, string path2) => path1 + DirectorySeparator + path2;
59+
60+
public string PathCombine(string path1, string path2, string path3) => path1 + DirectorySeparator + path2 + DirectorySeparator + path3;
5461

5562
public void WriteToFile(string path, string content) => Files[path] = content;
5663

@@ -71,6 +78,18 @@ public FileAttributes GetAttributes(string path)
7178
return attributes;
7279
}
7380

81+
public string GetFullPath(string path) => path;
82+
83+
public string GetRelativePath(string basePath, string targetPath)
84+
{
85+
if (targetPath.IndexOf(basePath) != 0)
86+
{
87+
throw new ArgumentException("targetPath is not relative to basePath");
88+
}
89+
90+
return targetPath.Replace(basePath, "").TrimStart('/', '\\');
91+
}
92+
7493
#endregion
7594

7695
/// <summary>

src/Microsoft.DotNet.Arcade.Sdk/tools/Publish.proj

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -261,18 +261,64 @@
261261

262262
<!-- When we have <account>.visualstudio.com -->
263263
<AzureDevOpsAccount Condition="$(CollectionUri.IndexOf('visualstudio.com')) >= 0">$(CollectionUri.Split('.')[0].Split('/')[2])</AzureDevOpsAccount>
264-
265-
<!-- Directory where pdbs pointed in `FilesToPublishToSymbolServer` are copied before publishing to AzDO artifacts. -->
266-
<PDBsToPublishTempLocation>$(ArtifactsTmpDir)PDBsToPublish/</PDBsToPublishTempLocation>
267-
<PdbArtifactsSourceBuiltOrTempDir Condition="'$(PushToLocalStorage)' == 'true'">$(SourceBuiltPdbArtifactsDir)</PdbArtifactsSourceBuiltOrTempDir>
268-
<PdbArtifactsSourceBuiltOrTempDir Condition="'$(PushToLocalStorage)' != 'true'">$(PDBsToPublishTempLocation)</PdbArtifactsSourceBuiltOrTempDir>
264+
</PropertyGroup>
265+
266+
<!-- Asset output locations
267+
When V4 publishing is enabled, there are two primary scenarios:
268+
1. The build is running as part of an inner build in the VMR, and the future artifacts location is NOT yet know.
269+
Publishing is primarily used to propagate assets between repos in a vertical.
270+
2. Publishing at the end of a vertical, or the end of a normal build, where the future artifacts needs to be known.
271+
-->
272+
<PropertyGroup>
273+
<LocallyStageArtifacts>false</LocallyStageArtifacts>
274+
<LocallyStageArtifacts Condition="'$(PublishingVersion)' == '4' or '$(PushToLocalStorage)' == 'true'">true</LocallyStageArtifacts>
269275

270276
<!-- Use the SYSTEM_PHASENAME variable when available as that is guaranteed to be a unique identifier.
271277
For local scenarios and when the variable isn't available, use "<os>-<arch>-<buildpass>"" as the manifest name. -->
272278
<AssetManifestFileName Condition="'$(AssetManifestFileName)' == '' and '$(SYSTEM_PHASENAME)' != ''">$(SYSTEM_PHASENAME).xml</AssetManifestFileName>
273279
<AssetManifestFileName Condition="'$(AssetManifestFileName)' == ''">$(AssetManifestOS)-$(AssetManifestArch)$(AssetManifestPass).xml</AssetManifestFileName>
274280
<AssetManifestFilePath Condition="'$(AssetManifestFilePath)' == ''">$(ArtifactsLogDir)AssetManifest\$(AssetManifestFileName)</AssetManifestFilePath>
275281

282+
<!-- PDBs are special in that they require a local staging location even if LocallyStageArtifacts is false.-->
283+
<PdbArtifactsLocalStorageDir Condition="'$(PdbArtifactsLocalStorageDir)' == '' and '$(LocallyStageArtifacts)' != 'true'">$(ArtifactsTmpDir)PDBsToPublish/</PdbArtifactsLocalStorageDir>
284+
</PropertyGroup>
285+
286+
<!-- Set up local staging artifact paths if we're going to locally stage artifacts.
287+
NOTE! The SourceBuilt* setups can be removed after this change has flowed to the VMR and the bootstrap.
288+
arcade is updated. -->
289+
<PropertyGroup Condition="'$(LocallyStageArtifacts)' == 'true'">
290+
<!-- Publish staging defaults.
291+
This is intended to be the location that a build will publish its pipeline artifacts. We avoid
292+
using the repo source layout for better compatibility with various scanning tools.
293+
Since not all CI builds are in AzDO, ensure that there is a reasonable default under the repo source layout. -->
294+
<ArtifactsPublishStagingDir Condition="'$(ArtifactsPublishStagingDir)' == '' and '$(Build_ArtifactStagingDirectory)' != ''">$([MSBuild]::NormalizeDirectory('$(Build_ArtifactStagingDirectory)', 'artifacts'))</ArtifactsPublishStagingDir>
295+
<ArtifactsPublishStagingDir Condition="'$(ArtifactsPublishStagingDir)' == ''">$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'staging'))</ArtifactsPublishStagingDir>
296+
297+
<!-- Directory where pdbs pointed in `FilesToPublishToSymbolServer` are copied during publishing. -->
298+
<PdbArtifactsLocalStorageDir Condition="'$(PdbArtifactsLocalStorageDir)' == ''">$(SourceBuiltPdbArtifactsDir)</PdbArtifactsLocalStorageDir>
299+
<PdbArtifactsLocalStorageDir Condition="'$(PdbArtifactsLocalStorageDir)' == ''">$(ArtifactsPublishStagingDir)pdbs</PdbArtifactsLocalStorageDir>
300+
301+
<!-- Directory where shipping packages are copied during publishing. -->
302+
<ShippingPackagesLocalStorageDir Condition="'$(ShippingPackagesLocalStorageDir)' == ''">$(SourceBuiltShippingPackagesDir)</ShippingPackagesLocalStorageDir>
303+
<ShippingPackagesLocalStorageDir Condition="'$(ShippingPackagesLocalStorageDir)' == ''">$(ArtifactsPublishStagingDir)packages/shipping</ShippingPackagesLocalStorageDir>
304+
305+
<!-- Directory where nonshipping packages are copied during publishing. -->
306+
<NonShippingPackagesLocalStorageDir Condition="'$(NonShippingPackagesLocalStorageDir)' == ''">$(SourceBuiltShippingPackagesDir)</NonShippingPackagesLocalStorageDir>
307+
<NonShippingPackagesLocalStorageDir Condition="'$(NonShippingPackagesLocalStorageDir)' == ''">$(ArtifactsPublishStagingDir)packages/nonshipping</NonShippingPackagesLocalStorageDir>
308+
309+
<!-- Directory where asset manifests are copied during publishing. -->
310+
<AssetManifestsLocalStorageDir Condition="'$(AssetManifestsLocalStorageDir)' == ''">$(SourceBuiltAssetManifestsDir)</AssetManifestsLocalStorageDir>
311+
<AssetManifestsLocalStorageDir Condition="'$(AssetManifestsLocalStorageDir)' == ''">$(ArtifactsPublishStagingDir)manifests</AssetManifestsLocalStorageDir>
312+
313+
<!-- Directory where blob assets are copied during publishing. -->
314+
<AssetsLocalStorageDir Condition="'$(AssetsLocalStorageDir)' == ''">$(SourceBuiltAssetsDir)</AssetsLocalStorageDir>
315+
<AssetsLocalStorageDir Condition="'$(AssetsLocalStorageDir)' == ''">$(ArtifactsPublishStagingDir)assets</AssetsLocalStorageDir>
316+
317+
<!-- Default values for the future artifact name and publish locations if not provided.
318+
VMR builds would typically specify these globally. Only used when publishing V4 is active. -->
319+
<FutureArtifactName Condition="'$(PublishingVersion)' == '4' and '$(FutureArtifactName)' == '' and '$(SYSTEM_PHASENAME)' != ''">$(SYSTEM_PHASENAME)_Artifacts</FutureArtifactName>
320+
<FutureArtifactName Condition="'$(PublishingVersion)' == '4' and '$(FutureArtifactName)' == ''">$(AssetManifestOS)_$(AssetManifestArch)$(AssetManifestPass)_Artifacts</FutureArtifactName>
321+
<FutureArtifactPublishBasePath Condition="'$(PublishingVersion)' == '4' and '$(FutureArtifactPublishBasePath)' == ''">$(ArtifactsPublishStagingDir)</FutureArtifactPublishBasePath>
276322
</PropertyGroup>
277323

278324
<!--
@@ -382,15 +428,16 @@
382428
AssetManifestPath="$(AssetManifestFilePath)"
383429
IsReleaseOnlyPackageVersion="$(IsReleaseOnlyPackageVersion)"
384430
PushToLocalStorage="$(PushToLocalStorage)"
385-
AssetsLocalStorageDir="$(SourceBuiltAssetsDir)"
431+
AssetsLocalStorageDir="$(AssetsLocalStorageDir)"
386432
PreserveRepoOrigin="$(PreserveRepoOrigin)"
387-
ShippingPackagesLocalStorageDir="$(SourceBuiltShippingPackagesDir)"
388-
NonShippingPackagesLocalStorageDir="$(SourceBuiltNonShippingPackagesDir)"
389-
AssetManifestsLocalStorageDir="$(SourceBuiltAssetManifestsDir)"
390-
PdbArtifactsLocalStorageDir="$(PdbArtifactsSourceBuiltOrTempDir)"
433+
ShippingPackagesLocalStorageDir="$(ShippingPackagesLocalStorageDir)"
434+
NonShippingPackagesLocalStorageDir="$(NonShippingPackagesLocalStorageDir)"
435+
AssetManifestsLocalStorageDir="$(AssetManifestsLocalStorageDir)"
436+
PdbArtifactsLocalStorageDir="$(PdbArtifactsLocalStorageDir)"
391437
ArtifactVisibilitiesToPublish="@(ArtifactVisibilityToPublish)"
392438
UseHardlinksIfPossible="$(PublishingUseHardlinksIfPossible)"
393-
PublishManifestOnly="$(PublishManifestOnly)" />
439+
FutureArtifactName="$(FutureArtifactName)"
440+
FutureArtifactPublishBasePath="$(FutureArtifactPublishBasePath)" />
394441
</Target>
395442

396443
</Project>

src/Microsoft.DotNet.Build.Manifest/ArtifactModel.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,24 @@ public ArtifactVisibility Visibility
7272
}
7373
}
7474

75+
/// <summary>
76+
/// Path to the artifact in the pipeline artifact specified by PipelineArtifactName.
77+
/// </summary>
78+
public string PipelineArtifactPath
79+
{
80+
get => Attributes.GetOrDefault(nameof(PipelineArtifactPath));
81+
set => Attributes[nameof(PipelineArtifactPath)] = value;
82+
}
83+
84+
/// <summary>
85+
/// Name of the pipeline artifact
86+
/// </summary>
87+
public string PipelineArtifactName
88+
{
89+
get => Attributes.GetOrDefault(nameof(PipelineArtifactName));
90+
set => Attributes[nameof(PipelineArtifactName)] = value;
91+
}
92+
7593
public override bool Equals(object obj)
7694
{
7795
if (obj is ArtifactModel other)

src/Microsoft.DotNet.Build.Manifest/BuildIdentity.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public enum PublishingInfraVersion
1414
V3 = 3,
1515
V4 = 4,
1616
Latest = V3,
17-
Dev = 4
17+
Dev = V4
1818
}
1919

2020
public class BuildIdentity

src/Microsoft.DotNet.Build.Tasks.Feed.Tests/ArtifactUrlHelperTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public void BuildArtifactUrlHelper_ConstructDownloadUrl_ThrowsArgumentException(
4545
[Theory]
4646
[InlineData("https://artprodcus3.artifacts.visualstudio.com/Ab55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8/7ea9116e-9fac-403d-b258-b31fcf1bb293/_apis/artifact/HASH/content?format=zip", "file.txt", "https://artprodcus3.artifacts.visualstudio.com/Ab55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8/7ea9116e-9fac-403d-b258-b31fcf1bb293/_apis/artifact/HASH/content?format=file&subPath=%2Ffile.txt")]
4747
[InlineData("https://artprodcus3.artifacts.visualstudio.com/Ab55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8/7ea9116e-9fac-403d-b258-b31fcf1bb293/_apis/artifact/HASH/content?format=zip", "subdir/file.txt", "https://artprodcus3.artifacts.visualstudio.com/Ab55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8/7ea9116e-9fac-403d-b258-b31fcf1bb293/_apis/artifact/HASH/content?format=file&subPath=%2Fsubdir%2Ffile.txt")]
48+
[InlineData("https://artprodcus3.artifacts.visualstudio.com/Ab55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8/7ea9116e-9fac-403d-b258-b31fcf1bb293/_apis/artifact/HASH/content?format=zip", "subdir///file.txt", "https://artprodcus3.artifacts.visualstudio.com/Ab55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8/7ea9116e-9fac-403d-b258-b31fcf1bb293/_apis/artifact/HASH/content?format=file&subPath=%2Fsubdir%2Ffile.txt")]
4849
public void PipelineArtifactDownloadHelper_ConstructDownloadUrl_ReturnsCorrectUrl(
4950
string baseUrl, string fileName, string expectedUrl)
5051
{

src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PublishToSymbolServerTest.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,12 @@ public async Task NoAssetsToPublishFound()
107107

108108
var path = TestInputs.GetFullPath("Symbol");
109109
var buildAsset = ReadOnlyDictionary<string, Asset>.Empty;
110+
// Clear the symbol packages added by the s
111+
task.BlobsByCategory.Clear();
110112

111113
await task.HandleSymbolPublishingAsync(
112114
buildInfo: buildInfo,
113-
buildAssets: buildAsset,
115+
assetNameToBARAssetMapping: buildAsset,
114116
pdbArtifactsBasePath: path,
115117
symbolPublishingExclusionsFile: "",
116118
publishSpecialClrFiles: false,
@@ -163,8 +165,8 @@ await task.HandleSymbolPublishingAsync(
163165
// have interesting observable state.
164166
Assert.Contains(buildEngine.BuildMessageEvents, x => x.Message.StartsWith("Publishing Symbols to Symbol server"));
165167
var message = buildEngine.BuildMessageEvents.Single(x => x.Message.StartsWith("Publishing Symbols to Symbol server"));
166-
Assert.Contains("Symbol package count: 1", message.Message);
167-
Assert.Contains("Loose symbol file count: 1", message.Message);
168+
Assert.Contains("Symbol packages (1):", message.Message);
169+
Assert.Contains("Loose symbol files (1)", message.Message);
168170

169171
Assert.Contains(buildEngine.BuildMessageEvents, x => x.Message.Contains("Creating symbol request"));
170172
Assert.Equal(2, buildEngine.BuildMessageEvents.Where(x => x.Message.Contains("Adding directory")).Count());
@@ -186,10 +188,13 @@ await task.HandleSymbolPublishingAsync(
186188

187189
private static (MockBuildEngine, PublishArtifactsInManifestV3, ReadOnlyDictionary<string, Asset>, string, string, ProductConstructionService.Client.Models.Build) GetCanonicalSymbolTestAssets(SymbolPublishVisibility targetServer = SymbolPublishVisibility.Public)
188190
{
191+
const string symbolPackageName= "test-package-a.1.0.0.symbols.nupkg";
192+
189193
var symbolPackages = new Dictionary<string, Asset>()
190194
{
191-
{ "test-package-a.1.0.0.symbols.nupkg", new(id: 0, buildId: 0, nonShipping: true, name: "test-package-a.1.0.0.symbols.nupkg", version: "1.0.0", locations: [])}
195+
{ symbolPackageName, new(id: 0, buildId: 0, nonShipping: true, name: symbolPackageName, version: "1.0.0", locations: [])}
192196
}.AsReadOnly();
197+
193198
var exclusionFile = TestInputs.GetFullPath("Symbols/SymbolPublishingExclusionsFile.txt");
194199
var symbolFilesDir = TestInputs.GetFullPath("Symbols");
195200

@@ -221,6 +226,13 @@ private static (MockBuildEngine, PublishArtifactsInManifestV3, ReadOnlyDictionar
221226
SymbolRequestProject = "dotnettest"
222227
};
223228
task.FeedConfigs.Add(TargetFeedContentType.Symbols, feedConfigsForSymbols);
229+
task.BlobsByCategory.Add(TargetFeedContentType.Symbols, new HashSet<Manifest.BlobArtifactModel>()
230+
{
231+
new Manifest.BlobArtifactModel()
232+
{
233+
Id = symbolPackageName,
234+
}
235+
});
224236

225237
ProductConstructionService.Client.Models.Build buildInfo = new(id: 4242, DateTimeOffset.Now, staleness: 0, released: false, stable: true, commit: "abcd", [], [], [], []);
226238

0 commit comments

Comments
 (0)