Skip to content

Commit 78d0aa4

Browse files
Add support to generate SBRP usage report (#42200)
1 parent 2a70175 commit 78d0aa4

File tree

5 files changed

+199
-6
lines changed

5 files changed

+199
-6
lines changed

eng/pipelines/templates/jobs/vmr-build.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ jobs:
314314
315315
if [[ '${{ parameters.buildSourceOnly }}' == 'True' ]]; then
316316
customBuildArgs="$customBuildArgs --source-only"
317+
extraBuildProperties="$extraBuildProperties /p:ReportSbrpUsage=true"
317318
fi
318319
319320
if [[ '${{ parameters.useMonoRuntime }}' == 'True' ]]; then
@@ -433,8 +434,7 @@ jobs:
433434
434435
cd "$(sourcesPath)"
435436
436-
CopyWithRelativeFolders "artifacts/log/" $targetFolder "*.binlog"
437-
CopyWithRelativeFolders "artifacts/log/" $targetFolder "*.log"
437+
CopyWithRelativeFolders "artifacts/log/" $targetFolder "*"
438438
CopyWithRelativeFolders "src/" $targetFolder "*.binlog"
439439
CopyWithRelativeFolders "src/" $targetFolder "*.log"
440440
@@ -468,8 +468,7 @@ jobs:
468468
mkdir -p ${targetFolder}
469469
470470
cd "$(sourcesPath)"
471-
find artifacts/log/ -type f -name "*.binlog" -exec rsync -R {} -t ${targetFolder} \;
472-
find artifacts/log/ -type f -name "*.log" -exec rsync -R {} -t ${targetFolder} \;
471+
find artifacts/log/ -exec rsync -R {} -t ${targetFolder} \;
473472
if [ -d "artifacts/scenario-tests/" ]; then
474473
find artifacts/scenario-tests/ -type f -name "*.binlog" -exec rsync -R {} -t ${targetFolder} \;
475474
echo "##vso[task.setvariable variable=hasScenarioTestResults]true"

src/SourceBuild/content/Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
<PackageReportDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'prebuilt-report'))</PackageReportDir>
173173
<ResultingPrebuiltPackagesDir>$([MSBuild]::NormalizeDirectory('$(PackageReportDir)', 'prebuilt-packages'))</ResultingPrebuiltPackagesDir>
174174

175+
<SbrpRepoSrcDir>$([MSBuild]::NormalizeDirectory('$(SrcDir)', 'source-build-reference-packages', 'src'))</SbrpRepoSrcDir>
175176
<ReferencePackagesDir>$([MSBuild]::NormalizeDirectory('$(PrereqsPackagesDir)', 'reference'))</ReferencePackagesDir>
176177
<SourceBuiltArtifactsTarballName>Private.SourceBuilt.Artifacts</SourceBuiltArtifactsTarballName>
177178
<SourceBuiltPrebuiltsTarballName>Private.SourceBuilt.Prebuilts</SourceBuiltPrebuiltsTarballName>

src/SourceBuild/content/eng/finish-source-only.proj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@
2121
<MSBuild Projects="$(RepoProjectsDir)$(RootRepo).proj" Targets="WritePrebuiltUsageData;ReportPrebuiltUsage" />
2222
</Target>
2323

24+
<UsingTask TaskName="Microsoft.DotNet.UnifiedBuild.Tasks.WriteSBRPUsageReport" AssemblyFile="$(MicrosoftDotNetUnifiedBuildTasksAssembly)" TaskFactory="TaskHostFactory" />
25+
<Target Name="ReportSbrpUsage"
26+
AfterTargets="Build"
27+
Condition="'$(ReportSbrpUsage)' == 'true'">
28+
<WriteSbrpUsageReport SbrpRepoSrcPath="$(SbrpRepoSrcDir)"
29+
SrcPath="$(SrcDir)"
30+
OutputPath="$(ArtifactsLogDir)" />
31+
</Target>
32+
2433
<!--
2534
Determine symbols tarball names and discover all intermediate symbols,
2635
to be used as inputs and outputs of symbols repackaging targets.
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System.Collections.Generic;
8+
using System.IO;
9+
using System.Linq;
10+
using System.Text.Json;
11+
using System.Xml.Linq;
12+
using Microsoft.Build.Framework;
13+
using Microsoft.Build.Utilities;
14+
using NuGet.ProjectModel;
15+
16+
namespace Microsoft.DotNet.UnifiedBuild.Tasks;
17+
18+
/// <summary>
19+
/// Reports the usage of the source-build-reference-packages:
20+
/// 1. SBRP references
21+
/// 2. Unreferenced packages
22+
/// </summary>
23+
public class WriteSbrpUsageReport : Task
24+
{
25+
private const string SbrpRepoName = "source-build-reference-packages";
26+
27+
private readonly Dictionary<string, PackageInfo> _sbrpPackages = [];
28+
29+
/// <summary>
30+
/// Path to the SBRP src directory.
31+
/// </summary>
32+
[Required]
33+
public required string SbrpRepoSrcPath { get; set; }
34+
35+
/// <summary>
36+
/// Path to the VMR src directory.
37+
/// </summary>
38+
[Required]
39+
public required string SrcPath { get; set; }
40+
41+
/// <summary>
42+
/// Path to the usage report to.
43+
/// </summary>
44+
[Required]
45+
public required string OutputPath { get; set; }
46+
47+
public override bool Execute()
48+
{
49+
Log.LogMessage($"Scanning for SBRP Package Usage...");
50+
51+
ReadSbrpPackages("referencePackages", trackTfms: true);
52+
ReadSbrpPackages("textOnlyPackages", trackTfms: false);
53+
54+
ScanProjectReferences();
55+
56+
GenerateUsageReport();
57+
58+
return !Log.HasLoggedErrors;
59+
}
60+
61+
private void GenerateUsageReport()
62+
{
63+
PackageInfo[] existingSbrps = [.. _sbrpPackages.Values.OrderBy(pkg => pkg.Id)];
64+
PurgeNonReferencedReferences();
65+
IEnumerable<string> unreferencedSbrps = GetUnreferencedSbrps().Select(pkg => pkg.Id).OrderBy(id => id);
66+
Report report = new(existingSbrps, unreferencedSbrps);
67+
68+
string reportFilePath = Path.Combine(OutputPath, "sbrpPackageUsage.json");
69+
#pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances
70+
string jsonContent = JsonSerializer.Serialize(report, new JsonSerializerOptions { WriteIndented = true });
71+
#pragma warning restore CA1869 // Cache and reuse 'JsonSerializerOptions' instances
72+
File.WriteAllText(reportFilePath, jsonContent);
73+
}
74+
75+
/// <summary>
76+
/// Removes all references from unreferenced SBRP packages. This is necessary to determine the
77+
/// complete set of unreferenced SBRP packages.
78+
/// </summary>
79+
private void PurgeNonReferencedReferences()
80+
{
81+
bool hasPurged;
82+
do
83+
{
84+
hasPurged = false;
85+
PackageInfo[] unrefPkgs = GetUnreferencedSbrps().ToArray();
86+
87+
foreach (PackageInfo sbrpPkg in _sbrpPackages.Values)
88+
{
89+
foreach (PackageInfo unrefPkg in unrefPkgs)
90+
{
91+
var unref = sbrpPkg.References.Keys
92+
.SingleOrDefault(path => path.Contains(SbrpRepoName) && path.Contains($"{unrefPkg.Name}.{unrefPkg.Version}"));
93+
if (unref != null)
94+
{
95+
Log.LogMessage($"Removing {unrefPkg.Id} from {sbrpPkg.Id}'s references.");
96+
sbrpPkg.References.Remove(unref);
97+
hasPurged = true;
98+
}
99+
}
100+
}
101+
} while (hasPurged);
102+
}
103+
104+
private IEnumerable<PackageInfo> GetUnreferencedSbrps() =>
105+
_sbrpPackages.Values.Where(pkg => pkg.References.Count == 0);
106+
107+
private string GetSbrpPackagesPath(string packageType) => Path.Combine(SbrpRepoSrcPath, packageType, "src");
108+
109+
private void ReadSbrpPackages(string packageType, bool trackTfms)
110+
{
111+
foreach (string projectPath in Directory.GetFiles(GetSbrpPackagesPath(packageType), "*.csproj", SearchOption.AllDirectories))
112+
{
113+
DirectoryInfo? directory = Directory.GetParent(projectPath);
114+
string version = directory!.Name;
115+
string projectName = Path.GetFileNameWithoutExtension(projectPath);
116+
HashSet<string>? tfms = null;
117+
118+
if (trackTfms)
119+
{
120+
XDocument xmlDoc = XDocument.Load(projectPath);
121+
// Reference packages are generated using the TargetFrameworks property
122+
// so there is no need to handle the TargetFramework property.
123+
tfms = xmlDoc.Element("Project")?
124+
.Elements("PropertyGroup")
125+
.Elements("TargetFrameworks")
126+
.FirstOrDefault()?.Value?
127+
.Split(';')
128+
.ToHashSet();
129+
130+
if (tfms == null || tfms.Count == 0)
131+
{
132+
Log.LogError($"No TargetFrameworks were detected in {projectPath}.");
133+
continue;
134+
}
135+
}
136+
137+
PackageInfo info = new(projectName[..(projectName.Length - 1 - version.Length)],
138+
version,
139+
directory.FullName,
140+
tfms);
141+
142+
_sbrpPackages.Add(info.Id, info);
143+
Log.LogMessage($"Detected package: {info.Id}");
144+
}
145+
}
146+
147+
private void ScanProjectReferences()
148+
{
149+
foreach (string projectJsonFile in Directory.GetFiles(SrcPath, "project.assets.json", SearchOption.AllDirectories))
150+
{
151+
LockFile lockFile = new LockFileFormat().Read(projectJsonFile);
152+
foreach (LockFileTargetLibrary lib in lockFile.Targets.SelectMany(t => t.Libraries))
153+
{
154+
if (!_sbrpPackages.TryGetValue($"{lib.Name}/{lib.Version}", out PackageInfo? info))
155+
{
156+
continue;
157+
}
158+
159+
if (!info.References.TryGetValue(lockFile.Path, out HashSet<string>? referencedTfms))
160+
{
161+
referencedTfms = [];
162+
info.References.Add(lockFile.Path, referencedTfms);
163+
}
164+
165+
IEnumerable<string> tfms = lib.CompileTimeAssemblies
166+
.Where(asm => asm.Path.StartsWith("lib") || asm.Path.StartsWith("ref"))
167+
.Select(asm => asm.Path.Split('/')[1]);
168+
foreach (string tfm in tfms)
169+
{
170+
referencedTfms.Add(tfm);
171+
}
172+
}
173+
}
174+
}
175+
176+
private record PackageInfo(string Name, string Version, string Path, HashSet<string>? Tfms = default)
177+
{
178+
public string Id => $"{Name}/{Version}";
179+
180+
// Dictionary of projects referencing the SBRP and the TFMs referenced by each project
181+
public Dictionary<string, HashSet<string>> References { get; } = [];
182+
}
183+
184+
private record Report(IEnumerable<PackageInfo> Sbrps, IEnumerable<string> UnreferencedSbrps);
185+
}

src/SourceBuild/content/repo-projects/Directory.Build.targets

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@
103103
<PropertyGroup>
104104
<!-- Dev innerloop opt-in feed: /p:ExtraRestoreSourcePath=... -->
105105
<ExtraSourcesNuGetSourceName>ExtraSources</ExtraSourcesNuGetSourceName>
106-
<SbrpRepoSrcPath>$([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'src', 'source-build-reference-packages', 'src'))</SbrpRepoSrcPath>
107106
</PropertyGroup>
108107

109108
<PropertyGroup Condition="'$(DotNetBuildSourceOnly)' == 'true'">
@@ -187,7 +186,7 @@
187186
NuGetConfigFile="$(NuGetConfigFile)"
188187
BuildWithOnlineFeeds="$(DotNetBuildWithOnlineFeeds)"
189188
SourceBuildSources="@(_BuildSources)"
190-
SbrpRepoSrcPath="$(SbrpRepoSrcPath)"
189+
SbrpRepoSrcPath="$(SbrpRepoSrcDir)"
191190
SbrpCacheSourceName="$(SbrpCacheNuGetSourceName)"
192191
ReferencePackagesSourceName="$(ReferencePackagesNuGetSourceName)"
193192
PreviouslySourceBuiltSourceName="$(PreviouslySourceBuiltNuGetSourceName)"

0 commit comments

Comments
 (0)