Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SdkArchiveDiff task to verify the sdk archive has all the expected files #18748

Merged
merged 39 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e894991
Add SdkArchiveDiff task to verify the sdk archive has all the expecte…
jtschuster Feb 22, 2024
cd1b907
fix name
jtschuster Feb 22, 2024
a4bee91
Forgot to make the project a library after testing
jtschuster Feb 22, 2024
5cc5ee0
Run the archive diff by default
jtschuster Feb 22, 2024
2862e1f
Report if there is no sdk produced in the build
jtschuster Feb 22, 2024
a336dd0
Update src/SourceBuild/content/eng/tools/tasks/Microsoft.DotNet.Sourc…
jtschuster Feb 22, 2024
a3256e2
Update src/SourceBuild/content/eng/tools/tasks/Microsoft.DotNet.Sourc…
jtschuster Feb 22, 2024
8c32f67
Don't autoredirect and check for correct aka.ms redirect code
jtschuster Feb 22, 2024
7b16584
Merge branch 'main' into TarballDiff
jtschuster Feb 26, 2024
0eea7e0
Fix stack overflow and rename method
jtschuster Feb 26, 2024
8a5244c
Merge branch 'TarballDiff' of https://github.com/jtschuster/installer…
jtschuster Feb 26, 2024
d30c740
Rename GetVersionAnonymousPath
jtschuster Feb 26, 2024
f704a53
Fix error message
jtschuster Feb 28, 2024
45728f7
Add task to find the single best tarball, and remove unused usings
jtschuster Feb 28, 2024
b6fba7e
Use property notation instead of item
jtschuster Feb 28, 2024
9ffe5c2
Make long running tasks cancellable
jtschuster Feb 28, 2024
7632ab4
PR Feedback:
jtschuster Feb 29, 2024
3cf9a38
Use cancellation token in FindArchiveDiff and move directory separato…
jtschuster Feb 29, 2024
478bb28
PR Feedback: use underscore prefix for 'local' properties
jtschuster Feb 29, 2024
f843239
PR Feedback:
jtschuster Mar 1, 2024
3ed9096
Don't validate Sdk archive diffs on shortstack
jtschuster Mar 1, 2024
9b43ca3
Rename file to match type
jtschuster Mar 1, 2024
2b3389d
Forgot to add the deletion of the old file
jtschuster Mar 1, 2024
eafa1ba
Check index before indexing array, and add common prefix to start of …
jtschuster Mar 1, 2024
5a2d878
Accidentally added test changes
jtschuster Mar 1, 2024
8eb0cd2
Update logs, add check for valid RID
jtschuster Mar 1, 2024
3adb0cc
Don't add another item in SdkTarballItem
jtschuster Mar 2, 2024
97d8e4d
Add message when there are no differences, don't validate non-portabl…
jtschuster Mar 4, 2024
d49fa67
Update link to copied source
jtschuster Mar 5, 2024
4f6bac3
Remove conditions on tasks
jtschuster Mar 5, 2024
2075e87
Use "Closest" sdk instead of "Latest"
jtschuster Mar 5, 2024
3c848d5
Update missed reference
jtschuster Mar 6, 2024
97fc3b8
PR Feedback: Formatting, move/add conditions, output diffs to csv in …
jtschuster Mar 6, 2024
36d3cfa
Update src/SourceBuild/content/eng/sdkArchiveDiffs.targets
jtschuster Mar 6, 2024
2c5bee4
Fix typo
jtschuster Mar 6, 2024
b290188
Use remote file name for downloaded sdk
jtschuster Mar 6, 2024
60c0a7e
Add args to WriteLinesToFile
jtschuster Mar 6, 2024
6ca4c6f
Use .diff instead of csv for sdk content diffs
jtschuster Mar 6, 2024
2286d77
Use Item transform instead of batching to output lines to file
jtschuster Mar 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SourceBuild/content/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
<PropertyGroup>
<XPlatSourceBuildTasksAssembly>$([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'Microsoft.DotNet.SourceBuild.Tasks.XPlat', '$(Configuration)', 'Microsoft.DotNet.SourceBuild.Tasks.XPlat.dll'))</XPlatSourceBuildTasksAssembly>
<LeakDetectionTasksAssembly>$([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'Microsoft.DotNet.SourceBuild.Tasks.LeakDetection', '$(Configuration)', 'Microsoft.DotNet.SourceBuild.Tasks.LeakDetection.dll'))</LeakDetectionTasksAssembly>
<SdkArchiveDiffTasksAssembly>$([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'Microsoft.DotNet.SourceBuild.Tasks.SdkArchiveDiff', '$(Configuration)', 'Microsoft.DotNet.SourceBuild.Tasks.SdkArchiveDiff.dll'))</SdkArchiveDiffTasksAssembly>
</PropertyGroup>

<PropertyGroup Condition="'$(EnablePoison)' == 'true'">
Expand Down
1 change: 1 addition & 0 deletions src/SourceBuild/content/build.proj
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
</Target>

<Import Project="$(RepositoryEngineeringDir)build.sourcebuild.targets" Condition="'$(DotNetBuildSourceOnly)' == 'true'" />
<Import Project="$(RepositoryEngineeringDir)build.targets" />

</Project>
29 changes: 29 additions & 0 deletions src/SourceBuild/content/eng/build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project>
<UsingTask AssemblyFile="$(SdkArchiveDiffTasksAssembly)" TaskName="FindArchiveDiffs" />
<UsingTask AssemblyFile="$(SdkArchiveDiffTasksAssembly)" TaskName="GetClosestOfficialSdk" />
<UsingTask AssemblyFile="$(SdkArchiveDiffTasksAssembly)" TaskName="GetSingleTarballItem" />

<Target Name="ReportSdkArchiveDiffs"
AfterTargets="Build"
DependsOnTargets="DetermineSourceBuiltSdkVersion" >

<GetSingleTarballItem SdkTarballItems="@(SdkTarballItem)">
<Output TaskParameter="BestSdkTarballItem" PropertyName="_TestSdkArchivePath"/>
</GetSingleTarballItem>

<GetClosestOfficialSdk BuiltSdkPath="$(_TestSdkArchivePath)" Condition="'$(_TestSdkArchivePath)' != ''">
<Output TaskParameter="ClosestOfficialSdkPath" PropertyName="ClosestOfficialSdkPath" />
</GetClosestOfficialSdk>

<Message Text="Failed to find closest official SDK archive." Importance="High" Condition="'$(_TestSdkArchivePath)' != '' AND '$(ClosestOfficialSdkPath)' == ''" />

<FindArchiveDiffs BaselineArchive="$(_TestSdkArchivePath)" TestArchive="$(ClosestOfficialSdkPath)" Condition="'$(_TestSdkArchivePath)' != '' AND '$(ClosestOfficialSdkPath)' != ''">
<Output TaskParameter="ContentDifferences" ItemName="ContentDifferences" />
</FindArchiveDiffs>

<Message Text="Difference in sdk archive: %(ContentDifferences.Kind): %(ContentDifferences.Identity)" Importance="High" Condition="'%(ContentDifferences.Identity)' != '' AND '%(ContentDifferences.Kind)' != 'Unchanged'"/>
<Delete Files="$(ClosestOfficialSdkPath)" Condition="'$(ClosestOfficialSdkPath)' == ''"/>

</Target>

</Project>
9 changes: 9 additions & 0 deletions src/SourceBuild/content/eng/tools/init-build.proj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
UnpackTarballs;
BuildXPlatTasks;
BuildMSBuildSdkResolver;
BuildSdkArchiveDiff;
BuildLeakDetection;
ExtractToolPackage;
GenerateRootFs;
Expand Down Expand Up @@ -116,6 +117,14 @@
</Touch>
</Target>

<Target Name="BuildSdkArchiveDiff" >
<MSBuild Projects="tasks\Microsoft.DotNet.SourceBuild.Tasks.SdkArchiveDiff\Microsoft.DotNet.SourceBuild.Tasks.SdkArchiveDiff.csproj"
Targets="Restore"
Properties="MSBuildRestoreSessionId=$([System.Guid]::NewGuid())" />
<MSBuild Projects="tasks\Microsoft.DotNet.SourceBuild.Tasks.SdkArchiveDiff\Microsoft.DotNet.SourceBuild.Tasks.SdkArchiveDiff.csproj"
Targets="Build" />
</Target>

<Target Name="GenerateRootFs"
Condition="'$(BuildOS)' != 'windows' and '$(CrossBuild)' == 'true' and '$(ROOTFS_DIR)' == ''">
<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Formats.Tar;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public abstract class Archive : IDisposable
{
public static async Task<Archive> Create(string path)
{
if (path.EndsWith(".tar.gz"))
return await TarArchive.Create(path);
else if (path.EndsWith(".zip"))
return ZipFileArchive.Create(path);
else
throw new NotSupportedException("Unsupported archive type");
}

public abstract bool Contains(string relativePath);

public abstract string[] GetFileNames();

public abstract string[] GetFileLines(string relativePath);

public abstract Task<byte[]> GetFileBytesAsync(string relativePath);

public abstract void Dispose();

public class TarArchive : Archive
{
private string _extractedFolder;

private TarArchive(string extractedFolder)
{
_extractedFolder = extractedFolder;
}

public static async Task<TarArchive> Create(string path, CancellationToken cancellationToken = default)
{
var tmpFolder = Directory.CreateTempSubdirectory(nameof(FindArchiveDiffs));
using (var gzStream = File.OpenRead (path))
using (var gzipStream = new GZipStream (gzStream, CompressionMode.Decompress))
{
await TarFile.ExtractToDirectoryAsync(gzipStream, tmpFolder.FullName, true, cancellationToken);
}
return new TarArchive(tmpFolder.FullName);
}

public override bool Contains(string relativePath)
{
return File.Exists(Path.Combine(_extractedFolder, relativePath));
}

public override string[] GetFileNames()
{
return Directory.GetFiles(_extractedFolder, "*", SearchOption.AllDirectories).Select(f => f.Substring(_extractedFolder.Length + 1)).ToArray();
}

public override string[] GetFileLines(string relativePath)
{
return File.ReadAllLines(Path.Combine(_extractedFolder, relativePath));
}

public override Task<byte[]> GetFileBytesAsync(string relativePath)
{
var filePath = Path.Combine(_extractedFolder, relativePath);
if (!File.Exists(filePath))
return Task.FromResult<byte[]>([]);
return File.ReadAllBytesAsync(Path.Combine(_extractedFolder, relativePath));
}

public override void Dispose()
{
if (Directory.Exists(_extractedFolder))
Directory.Delete(_extractedFolder, true);
}
}

public class ZipFileArchive : Archive
{
private ZipArchive _archive;

private ZipFileArchive(ZipArchive archive)
{
_archive = archive;
}

public static new ZipFileArchive Create(string path)
{
return new ZipFileArchive(new ZipArchive(File.OpenRead(path)));
}

public override bool Contains(string relativePath)
{
return _archive.GetEntry(relativePath) != null;
}

public override string[] GetFileNames()
{
return _archive.Entries.Select(e => e.FullName).ToArray();
}

public override string[] GetFileLines(string relativePath)
{
var entry = _archive.GetEntry(relativePath);
if (entry == null)
throw new ArgumentException("File not found");
return entry.Lines();
}
public override Task<byte[]> GetFileBytesAsync(string relativePath)
{
using (var entry = _archive.GetEntry(relativePath)?.Open())
{
if (entry == null)
{
return Task.FromResult<byte[]>([]);
}
return entry.ReadToEndAsync();
}
}

public override void Dispose()
{
_archive.Dispose();
}
}

public static (string Version, string Rid, string extension) GetInfoFromArchivePath(string path)
{
string extension;
if (path.EndsWith(".tar.gz"))
{
extension = ".tar.gz";
}
else if (path.EndsWith(".zip"))
{
extension = ".zip";
}
else
{
throw new ArgumentException($"Invalid archive extension '{path}': must end with .tar.gz or .zip");
}

string filename = Path.GetFileName(path)[..^extension.Length];
var dashDelimitedParts = filename.Split('-');
var (rid, versionString) = dashDelimitedParts switch
{
["dotnet", "sdk", var first, var second, var third, var fourth] when PathWithVersions.IsVersionString(first) => (third + '-' + fourth, first + '-' + second),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the complexity of the version numbers that show up in the set of assets produced by .NET, we created a version identifier a long time ago. It's what powers creation of the aka.ms links: https://github.com/dotnet/arcade/blob/main/src/Microsoft.DotNet.VersionTools/lib/src/BuildManifest/VersionIdentifier.cs#L51

I'm not totally sure how well it works for paths inside an SDK, but it may be worth looking at if you start to run into corner cases with the checker.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the link! I copied that file to this project. I tried referencing that package, but it references a different version of MSBuild that Installer uses and that caused build issues.

["dotnet", "sdk", var first, var second, var third, var fourth] when PathWithVersions.IsVersionString(third) => (first + '-' + second, third + '-' + fourth),
_ => throw new ArgumentException($"Invalid archive file name '{filename}': file name should include full build version and rid in the format dotnet-sdk-<version>-<rid>{extension} or dotnet-sdk-<rid>-<version>{extension}")
};

return (versionString, rid, extension);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Task = System.Threading.Tasks.Task;

public class FindArchiveDiffs : Microsoft.Build.Utilities.Task
{
public class ArchiveItem
{
public required string Path { get; init; }
}

[Required]
public required ITaskItem BaselineArchive { get; init; }

[Required]
public required ITaskItem TestArchive { get; init; }

[Output]
public ITaskItem[] ContentDifferences { get; set; } = [];

public override bool Execute()
{
return Task.Run(ExecuteAsync).Result;
}

public async Task<bool> ExecuteAsync()
{
var baselineTask = Archive.Create(BaselineArchive.ItemSpec);
var testTask = Archive.Create(TestArchive.ItemSpec);
Task.WaitAll(baselineTask, testTask);
using var baseline = await baselineTask;
using var test = await testTask;
var baselineFiles = baseline.GetFileNames();
var testFiles = test.GetFileNames();
ContentDifferences =
GetDiffs(baselineFiles, testFiles, PathWithVersions.Equal, PathWithVersions.GetVersionlessPath)
.Select(FromDiff)
.ToArray();
return true;
}

static ITaskItem FromDiff((string, DifferenceKind) diff)
{
var item = new TaskItem(diff.Item1);
item.SetMetadata("Kind", Enum.GetName(diff.Item2));
return item;
}

public enum DifferenceKind
{
Added,
Removed,
Unchanged
}

public static List<(string, DifferenceKind DifferenceKind)> GetDiffs(
string[] originalPathsWithVersions,
string[] modifiedPathsWithVersions,
Func<string, string, bool> equalityComparer,
Func<string, string>? formatter = null)
{
formatter ??= static s => s;
// Edit distance algorithm: https://en.wikipedia.org/wiki/Longest_common_subsequence

int[,] dp = new int[originalPathsWithVersions.Length + 1, modifiedPathsWithVersions.Length + 1];

// Initialize first row and column
for (int i = 0; i <= originalPathsWithVersions.Length; i++)
{
dp[i, 0] = i;
}
for (int j = 0; j <= modifiedPathsWithVersions.Length; j++)
{
dp[0, j] = j;
}

// Compute edit distance
for (int i = 1; i <= originalPathsWithVersions.Length; i++)
{
for (int j = 1; j <= modifiedPathsWithVersions.Length; j++)
{
if (equalityComparer(originalPathsWithVersions[i - 1], modifiedPathsWithVersions[j - 1]))
{
dp[i, j] = dp[i - 1, j - 1];
}
else
{
dp[i, j] = 1 + Math.Min(dp[i - 1, j], dp[i, j - 1]);
}
}
}

// Trace back the edits
int row = originalPathsWithVersions.Length;
int col = modifiedPathsWithVersions.Length;

List<(string, DifferenceKind)> formattedDiff = [];
while (row > 0 || col > 0)
{
var baselineItem = originalPathsWithVersions[row - 1];
var testItem = modifiedPathsWithVersions[col - 1];
if (row > 0 && col > 0 && PathWithVersions.Equal(baselineItem, testItem))
{
formattedDiff.Add((formatter(originalPathsWithVersions[row - 1]), DifferenceKind.Unchanged));
row--;
col--;
}
else if (col > 0 && (row == 0 || dp[row, col - 1] <= dp[row - 1, col]))
{
formattedDiff.Add((formatter(modifiedPathsWithVersions[col - 1]), DifferenceKind.Added));
col--;
}
else if (row > 0 && (col == 0 || dp[row, col - 1] > dp[row - 1, col]))
{
formattedDiff.Add((formatter(originalPathsWithVersions[row - 1]), DifferenceKind.Removed));
row--;
}
else
{
throw new UnreachableException();
}
}
formattedDiff.Reverse();
return formattedDiff;
}
}
Loading