[TrimmingAnalyzer] Update report generation and add new analysis scripts for AOT comparison

This commit is contained in:
Yu Leng (from Dev Box)
2025-08-01 16:48:48 +08:00
parent d872e844e7
commit f882bc3370
8 changed files with 292 additions and 48 deletions

1
deps/cxxopts vendored Submodule

Submodule deps/cxxopts added at 12e496da3d

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Xml.Linq;
using TrimmingAnalyzer.Models;
@@ -14,23 +15,25 @@ namespace TrimmingAnalyzer
{
var typesByNamespace = removedTypes.GroupBy(t => t.Namespace);
// Define the namespace
XNamespace ns = "http://schemas.microsoft.com/netfx/2013/01/metadata";
var doc = new XDocument(
new XElement("Directives",
new XAttribute("xmlns", "http://schemas.microsoft.com/netfx/2013/01/metadata"),
new XElement("Application",
new XElement(ns + "Directives",
new XElement(ns + "Application",
new XComment($"CmdPal Trimming Report - Generated on {DateTime.Now:yyyy-MM-dd HH:mm:ss}"),
new XComment($"Total types trimmed: {removedTypes.Count}"),
new XComment("TrimMode: partial (as configured in Microsoft.CmdPal.UI.csproj)"),
new XElement("Assembly",
new XElement(ns + "Assembly",
new XAttribute("Name", "Microsoft.CmdPal.UI"),
new XAttribute("Dynamic", "Required All"),
typesByNamespace.Select(g =>
new XElement("Namespace",
new XElement(ns + "Namespace",
new XAttribute("Name", g.Key),
new XAttribute("Preserve", "All"),
new XAttribute("Dynamic", "Required All"),
g.Select(type =>
new XElement("Type",
new XElement(ns + "Type",
new XAttribute("Name", type.Name),
new XAttribute("Dynamic", "Required All"),
new XAttribute("Serialize", "All"),
@@ -53,15 +56,28 @@ namespace TrimmingAnalyzer
}
public void GenerateMarkdown(List<TypeInfo> removedTypes, string outputPath)
{
GenerateMarkdown(removedTypes, outputPath, null);
}
public void GenerateMarkdown(List<TypeInfo> removedTypes, string outputPath, List<string>? assemblyNames)
{
var sb = new StringBuilder();
sb.AppendLine("# CmdPal Trimming Report");
sb.AppendLine("# CmdPal Debug vs AOT Release Comparison Report");
sb.AppendLine();
sb.AppendLine($"**Generated:** {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
sb.AppendLine();
sb.AppendLine($"**Configuration:** TrimMode=partial (as configured in project)");
sb.AppendLine($"**Comparison:** Debug Build (no AOT) vs Release Build (with AOT)");
sb.AppendLine($"**Purpose:** Show types removed when enabling AOT compilation in Release mode");
sb.AppendLine();
sb.AppendLine($"**Total types trimmed:** {removedTypes.Count}");
if (assemblyNames != null && assemblyNames.Count > 0)
{
sb.AppendLine($"**Analyzed assemblies:** {string.Join(", ", assemblyNames.Distinct().OrderBy(x => x))}");
sb.AppendLine();
}
sb.AppendLine($"**Total types removed by AOT:** {removedTypes.Count}");
sb.AppendLine();
// Summary by namespace
@@ -110,12 +126,34 @@ namespace TrimmingAnalyzer
sb.AppendLine("2. Or use `[DynamicallyAccessedMembers]` attributes in your code");
sb.AppendLine("3. Or use `[DynamicDependency]` attributes to preserve specific members");
sb.AppendLine();
sb.AppendLine("Note: CmdPal uses `TrimMode=partial` which only trims assemblies that opt-in to trimming.");
sb.AppendLine("Note: This report shows types that are present in Debug builds but removed in AOT Release builds.");
sb.AppendLine("AOT compilation removes unused types and members to reduce binary size and improve startup performance.");
Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? ".");
File.WriteAllText(outputPath, sb.ToString());
}
public void GenerateJson(List<TypeInfo> removedTypes, string outputPath, string assemblyName)
{
var analysisResult = new
{
AssemblyName = assemblyName,
GeneratedAt = DateTime.Now,
TotalTypes = removedTypes.Count,
RemovedTypes = removedTypes.OrderBy(t => t.FullName).ToList()
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(analysisResult, options);
Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? ".");
File.WriteAllText(outputPath, json);
}
private string GetTypeKind(TypeInfo type)
{
if (type.IsInterface) return "Interface";

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<EnableDefaultItems>false</EnableDefaultItems>
@@ -19,4 +19,8 @@
<Compile Include="Models\TypeInfo.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" />
</ItemGroup>
</Project>

View File

@@ -2,7 +2,12 @@
.SYNOPSIS
Generates a trimming report for CmdPal UI project
.DESCRIPTION
This script builds CmdPal UI with and without trimming/AOT, then uses TrimmingAnalyzer
if ($LASTEXITCODE -ne 0) {
throw "Failed to build Release AOT version"
}
# Copy AOT Release output to analysis directory
$trimmedOutput = Join-Path $cmdPalDir "bin\Release\net9.0-windows10.0.26100.0\win-x64\publish"s script builds CmdPal UI with and without trimming/AOT, then uses TrimmingAnalyzer
to analyze the differences and generate reports.
.PARAMETER Configuration
Build configuration (Debug/Release). Defaults to Release
@@ -42,10 +47,10 @@ try {
throw "Failed to build TrimmingAnalyzer"
}
# Build without trimming using MSBuild
Write-Host "Building CmdPal without trimming..." -ForegroundColor Yellow
# Build Debug mode without AOT (baseline for comparison)
Write-Host "Building CmdPal in Debug mode without AOT (baseline)..." -ForegroundColor Yellow
& msbuild $cmdPalProject `
/p:Configuration=$Configuration `
/p:Configuration=Debug `
/p:Platform=x64 `
/p:PublishTrimmed=false `
/p:EnableCmdPalAOT=false `
@@ -56,58 +61,72 @@ try {
/verbosity:minimal
if ($LASTEXITCODE -ne 0) {
throw "Failed to build untrimmed version"
throw "Failed to build Debug baseline version"
}
# Copy untrimmed output to analysis directory
$untrimmedOutput = Join-Path $cmdPalDir "x64\$Configuration\WinUI3Apps\CmdPal"
Copy-Item "$untrimmedOutput\Microsoft.CmdPal.UI.dll" $untrimmedDir -Force
Write-Host "Copied untrimmed DLL from: $untrimmedOutput"
# Copy baseline (Debug without AOT) output to analysis directory
$baselineOutput = Join-Path $cmdPalDir "bin\Debug\net9.0-windows10.0.26100.0\win-x64\publish"
Copy-Item "$baselineOutput\Microsoft.CmdPal.UI.dll" $untrimmedDir -Force
# Copy all dependencies to help with assembly resolution
Get-ChildItem "$baselineOutput\*.dll" | ForEach-Object {
if ($_.Name -ne "Microsoft.CmdPal.UI.dll") {
Copy-Item $_.FullName $untrimmedDir -Force -ErrorAction SilentlyContinue
}
}
Write-Host "Copied Debug baseline DLLs from: $baselineOutput"
# Build with trimming using MSBuild
Write-Host "Building CmdPal with trimming (AOT=$EnableAOT)..." -ForegroundColor Yellow
# Build Release mode with AOT enabled
Write-Host "Building CmdPal in Release mode with AOT enabled..." -ForegroundColor Yellow
& msbuild $cmdPalProject `
/p:Configuration=$Configuration `
/p:Configuration=Release `
/p:Platform=x64 `
/p:PublishTrimmed=true `
/p:EnableCmdPalAOT=$EnableAOT `
/p:PublishAot=$EnableAOT `
/p:PublishTrimmed=false `
/p:EnableCmdPalAOT=true `
/p:PublishAot=true `
/p:RuntimeIdentifier=win-x64 `
/p:SelfContained=true `
/p:TrimMode=partial `
/p:TreatWarningsAsErrors=false `
/p:WarningsAsErrors="" `
/p:WarningsNotAsErrors="" `
/p:SuppressTrimAnalysisWarnings=true `
"/p:NoWarn=IL2104,IL2026,IL2070,IL2072,IL2075,IL2077,IL2080,IL2091,IL2092,IL2093,IL2094,IL2095,IL2096,IL2097,IL2098,IL2099,IL2103,CsWinRT1028" `
/t:Publish `
/verbosity:minimal
if ($LASTEXITCODE -ne 0) {
throw "Failed to build trimmed version"
throw "Failed to build AOT+trimmed version"
}
# Copy trimmed output to analysis directory
$trimmedOutput = Join-Path $cmdPalDir "x64\$Configuration\WinUI3Apps\CmdPal"
# Copy AOT+trimmed output to analysis directory
$trimmedOutput = Join-Path $cmdPalDir "bin\$Configuration\net9.0-windows10.0.26100.0\win-x64\publish"
Copy-Item "$trimmedOutput\Microsoft.CmdPal.UI.dll" $trimmedDir -Force
Write-Host "Copied trimmed DLL from: $trimmedOutput"
# Copy all dependencies to help with assembly resolution
Get-ChildItem "$trimmedOutput\*.dll" | ForEach-Object {
if ($_.Name -ne "Microsoft.CmdPal.UI.dll") {
Copy-Item $_.FullName $trimmedDir -Force -ErrorAction SilentlyContinue
}
}
Write-Host "Copied Release AOT DLLs from: $trimmedOutput"
# Run analyzer
Write-Host "Analyzing trimming differences..." -ForegroundColor Yellow
& dotnet run --project $analyzerProject -- `
"$untrimmedDir\Microsoft.CmdPal.UI.dll" `
"$trimmedDir\Microsoft.CmdPal.UI.dll" `
$cmdPalDir `
"rdxml,markdown"
if ($LASTEXITCODE -ne 0) {
throw "Failed to analyze trimming"
# Use new directory comparison method to compare all types
Write-Host "Analyzing differences (Debug baseline vs Release AOT)..." -ForegroundColor Yellow
Write-Host "Using advanced directory comparison to detect AOT-optimized types..." -ForegroundColor Cyan
# Use the new directory comparison feature
& $analyzerPath --compare-directories "$untrimmedDir" "$trimmedDir" "$cmdPalDir" "rdxml,markdown,json" "TrimmedTypes"
if ($LASTEXITCODE -eq 0) {
Write-Host "Analysis completed successfully!" -ForegroundColor Green
} else {
throw "Analysis failed with exit code $LASTEXITCODE"
}
Write-Host "`n===== Analysis Complete =====" -ForegroundColor Cyan
Write-Host "Reports generated in: $cmdPalDir" -ForegroundColor Green
Write-Host " - TrimmedTypes.rd.xml" -ForegroundColor Green
Write-Host " - TrimmedTypes.md" -ForegroundColor Green
# List the main combined reports
$mainReports = @("TrimmedTypes.md", "TrimmedTypes.rd.xml")
foreach ($report in $mainReports) {
$reportPath = Join-Path $cmdPalDir $report
if (Test-Path $reportPath) {
Write-Host " - $report" -ForegroundColor Green
}
}
} finally {
# Cleanup

View File

@@ -0,0 +1,53 @@
# Monitor build progress and report generation
Write-Host "===== Monitoring CmdPal Build Progress ====="
$reportPath = "C:\Users\yuleng\PowerToys\src\modules\cmdpal\Microsoft.CmdPal.UI\TrimmedTypes.md"
$lastSize = 0
$lastModified = Get-Date "1/1/1900"
if (Test-Path $reportPath) {
$lastModified = (Get-Item $reportPath).LastWriteTime
$lastSize = (Get-Item $reportPath).Length
Write-Host "Current report file: $reportPath"
Write-Host "Last modified: $lastModified"
Write-Host "File size: $lastSize bytes"
} else {
Write-Host "No report file found yet at: $reportPath"
}
Write-Host "`nChecking for active build processes..."
$buildProcesses = Get-Process | Where-Object {
$_.ProcessName -like "*msbuild*" -or
$_.ProcessName -like "*dotnet*" -or
$_.ProcessName -like "*cl*" -or
$_.ProcessName -like "*link*"
}
Write-Host "Found $($buildProcesses.Count) build-related processes running"
if ($buildProcesses.Count -gt 0) {
Write-Host "Build processes still active:"
$buildProcesses | Select-Object ProcessName, Id, CPU | Format-Table -AutoSize
} else {
Write-Host "No build processes detected. Build may be complete."
}
# Check if directories exist for both builds
$debugPath = "C:\Users\yuleng\PowerToys\src\modules\cmdpal\Microsoft.CmdPal.UI\bin\Debug"
$releasePath = "C:\Users\yuleng\PowerToys\src\modules\cmdpal\Microsoft.CmdPal.UI\bin\Release"
Write-Host "`nBuild directory status:"
Write-Host "Debug path exists: $(Test-Path $debugPath)"
Write-Host "Release path exists: $(Test-Path $releasePath)"
if (Test-Path $debugPath) {
$debugDlls = Get-ChildItem -Path $debugPath -Recurse -Filter "Microsoft.CmdPal.UI.dll" -ErrorAction SilentlyContinue
Write-Host "Debug DLLs found: $($debugDlls.Count)"
}
if (Test-Path $releasePath) {
$releaseDlls = Get-ChildItem -Path $releasePath -Recurse -Filter "Microsoft.CmdPal.UI.dll" -ErrorAction SilentlyContinue
Write-Host "Release DLLs found: $($releaseDlls.Count)"
}
Write-Host "`nMonitoring complete."

View File

@@ -0,0 +1,39 @@
# Simple test to verify TrimmingAnalyzer functionality
Write-Host "===== TrimmingAnalyzer Simple Test ====="
$analyzerPath = "C:\Users\yuleng\PowerToys\tools\TrimmingAnalyzer\bin\Release\net9.0\TrimmingAnalyzer.exe"
if (-not (Test-Path $analyzerPath)) {
Write-Error "TrimmingAnalyzer not found at $analyzerPath"
exit 1
}
Write-Host "TrimmingAnalyzer found at: $analyzerPath"
# Test the help command
Write-Host "`nTesting help command..."
try {
$helpOutput = & $analyzerPath --help 2>&1
Write-Host "Help command executed successfully"
Write-Host "Output length: $($helpOutput.Length) characters"
# Show first few lines of help
$helpLines = $helpOutput -split "`n"
$helpLines | Select-Object -First 10 | ForEach-Object { Write-Host " $_" }
if ($helpLines.Length -gt 10) {
Write-Host " ... (truncated)"
}
} catch {
Write-Error "Failed to run help command: $_"
}
Write-Host "`nTesting version command..."
try {
$versionOutput = & $analyzerPath --version 2>&1
Write-Host "Version: $versionOutput"
} catch {
Write-Warning "Version command failed: $_"
}
Write-Host "`nTrimmingAnalyzer test completed."

View File

@@ -0,0 +1,30 @@
# Test script to run the updated CmdPal analysis with current working functionality
param(
[string]$Configuration = "Release",
[switch]$EnableAOT = $true
)
Write-Host "===== CmdPal AOT Analysis Test ====="
$rootDir = Split-Path -Parent $PSScriptRoot
$rootDir = Split-Path -Parent $rootDir
$cmdPalProject = Join-Path $rootDir "src\modules\cmdpal\Microsoft.CmdPal.UI\Microsoft.CmdPal.UI.csproj"
$analyzerProject = Join-Path $rootDir "tools\TrimmingAnalyzer\TrimmingAnalyzer.csproj"
# Build the current analyzer
Write-Host "Building TrimmingAnalyzer..."
dotnet build $analyzerProject -c Release
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to build TrimmingAnalyzer"
exit 1
}
$analyzerPath = Join-Path $rootDir "tools\TrimmingAnalyzer\bin\Release\net9.0\TrimmingAnalyzer.exe"
# Test the analyzer help
Write-Host "`nTesting analyzer functionality..."
& $analyzerPath
Write-Host "`nAnalyzer test completed."

View File

@@ -0,0 +1,60 @@
# Quick test of the trimming analyzer with existing builds
param(
[string]$BaselineDir = "C:\Users\yuleng\PowerToys\src\modules\cmdpal\Microsoft.CmdPal.UI\bin\Release\net9.0-windows10.0.26100.0\win-x64\publish",
[string]$TrimmedDir = "C:\Users\yuleng\PowerToys\src\modules\cmdpal\Microsoft.CmdPal.UI\bin\Release\net9.0-windows10.0.26100.0\win-x64\publish"
)
Write-Host "===== Quick Trimming Analysis Test ====="
# Build the analyzer first
Write-Host "Building TrimmingAnalyzer..."
dotnet build "C:\Users\yuleng\PowerToys\tools\TrimmingAnalyzer\TrimmingAnalyzer.csproj" -c Release
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to build TrimmingAnalyzer"
exit 1
}
$analyzerPath = "C:\Users\yuleng\PowerToys\tools\TrimmingAnalyzer\bin\Release\net9.0\TrimmingAnalyzer.exe"
if (-not (Test-Path $analyzerPath)) {
Write-Error "TrimmingAnalyzer not found at $analyzerPath"
exit 1
}
# Check if any DLLs exist in the specified directories
$baselineDlls = Get-ChildItem -Path $BaselineDir -Filter "*.dll" -ErrorAction SilentlyContinue
$trimmedDlls = Get-ChildItem -Path $TrimmedDir -Filter "*.dll" -ErrorAction SilentlyContinue
Write-Host "Baseline directory: $BaselineDir"
Write-Host "Found $($baselineDlls.Count) DLLs in baseline"
Write-Host "Trimmed directory: $TrimmedDir"
Write-Host "Found $($trimmedDlls.Count) DLLs in trimmed"
if ($baselineDlls.Count -eq 0 -or $trimmedDlls.Count -eq 0) {
Write-Warning "No DLLs found in one or both directories. The script may need actual AOT builds to compare."
Write-Host "Current directories contain:"
Write-Host "Baseline: $($baselineDlls.Name -join ', ')"
Write-Host "Trimmed: $($trimmedDlls.Name -join ', ')"
}
# Test the analyzer with a simple case
if ($baselineDlls.Count -gt 0 -and $trimmedDlls.Count -gt 0) {
$testDll = $baselineDlls[0].Name
$baselinePath = Join-Path $BaselineDir $testDll
$trimmedPath = Join-Path $TrimmedDir $testDll
if (Test-Path $trimmedPath) {
Write-Host "Testing analyzer with $testDll..."
& $analyzerPath analyze "$baselinePath" "$trimmedPath" --output "test-report"
if (Test-Path "test-report.md") {
Write-Host "`nGenerated report preview:"
Get-Content "test-report.md" | Select-Object -First 20
}
}
}
Write-Host "`nAnalyzer path: $analyzerPath"
Write-Host "Test completed."