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

[workload-testing] Misc improvements #99392

Merged
merged 9 commits into from
Mar 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,34 @@ Tasks, and targets to support workload testing in `dotnet` repositories.
- `$(InstallWorkloadForTesting)` - required
- `$(BuiltNuGetsDir)` - required
- `$(DotNetInstallArgumentsForWorkloadsTesting)` - required
- `$(TemplateNuGetConfigPathForWorkloadTesting)` - required

- `$(TestUsingWorkloads)` - optional
- `$(SkipTempDirectoryCleanup)` - optional
- `$(VersionBandForManifestPackages)` - optional
- `$(ExtraWorkloadInstallCommandArguments)` - optional
- `$(WorkloadInstallCommandOutputImportance)` - optional, defaults to `Normal`

## `$(PackageSourceNameForBuiltPackages)` - optional

`<add key="<$sourceName>" value="file:///..." />`

Defaults to `nuget-local`.

## `$(NuGetConfigPackageSourceMappingsForWorkloadTesting)` - optional

For a value of `*Aspire*;Foo*`, a package source mapping will be added to the local nuget source
added for built nugets:

```xml
<packageSourceMapping>
<packageSource key="nuget-local">
<package pattern="*Aspire*" />
<package pattern="Foo*" />
</packageSource>
...
```

## items
# items

- `@(DefaultPropertiesForNuGetBuild)`
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@
TaskName="Microsoft.Workload.Build.Tasks.InstallWorkloadFromArtifacts"
AssemblyFile="$(WorkloadBuildTasksAssemblyPath)" />

<UsingTask Condition="'$(WorkloadBuildTasksAssemblyPath)' != ''"
TaskName="Microsoft.Workload.Build.Tasks.PatchNuGetConfig"
AssemblyFile="$(WorkloadBuildTasksAssemblyPath)" />

<Target Name="InstallWorkloadUsingArtifacts"
AfterTargets="$(InstallWorkloadUsingArtifactsAfterThisTarget)"
DependsOnTargets="$(InstallWorkloadUsingArtifactsDependsOn)"
Expand Down Expand Up @@ -201,8 +205,11 @@
VersionBandForManifestPackages="$(VersionBandForManifestPackages)"
LocalNuGetsPath="$(BuiltNugetsDir)"
ExtraWorkloadInstallCommandArguments="$(ExtraWorkloadInstallCommandArguments)"
PackageSourceNameForBuiltPackages="$(PackageSourceNameForBuiltPackages)"
TemplateNuGetConfigPath="$(TemplateNuGetConfigPathForWorkloadTesting)"
NuGetConfigPackageSourceMappings="$(NuGetConfigPackageSourceMappingsForWorkloadTesting)"
SdkWithNoWorkloadInstalledPath="$(_SdkWithNoWorkloadPath)"
WorkloadInstallCommandOutputImportance="$(WorkloadInstallCommandOutputImportance)"
IntermediateOutputPath="$(ArtifactsObjDir)"
SkipTempDirectoryCleanup="$(SkipTempDirectoryCleanup)"
/>
Expand Down
37 changes: 23 additions & 14 deletions src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

#nullable enable

namespace Microsoft.Workload.Build.Tasks
{
public partial class InstallWorkloadFromArtifacts : Task
public partial class InstallWorkloadFromArtifacts : PatchNuGetConfig
{
[Required, NotNull]
public ITaskItem[] WorkloadIds { get; set; } = Array.Empty<ITaskItem>();
Expand All @@ -33,12 +36,6 @@ public partial class InstallWorkloadFromArtifacts : Task
[Required, NotNull]
public string? VersionBandForManifestPackages { get; set; }

[Required, NotNull]
public string? LocalNuGetsPath { get; set; }

[Required, NotNull]
public string? TemplateNuGetConfigPath { get; set; }

[Required, NotNull]
public string SdkWithNoWorkloadInstalledPath { get; set; } = string.Empty;

Expand All @@ -47,7 +44,9 @@ public partial class InstallWorkloadFromArtifacts : Task
public bool OnlyUpdateManifests { get; set; }
public bool SkipTempDirectoryCleanup { get; set; }

private const string s_nugetInsertionTag = "<!-- TEST_RESTORE_SOURCES_INSERTION_LINE -->";
// Should match enum values for MessageImportance - Low, Normal (default), High
public string? WorkloadInstallCommandOutputImportance { get; set; }

private string AllManifestsStampPath => Path.Combine(SdkWithNoWorkloadInstalledPath, ".all-manifests.stamp");
private string _tempDir = string.Empty;
private string _nugetCachePath = string.Empty;
Expand All @@ -67,6 +66,10 @@ public override bool Execute()
Directory.Delete(_tempDir, recursive: true);
Directory.CreateDirectory(_tempDir);
_nugetCachePath = Path.Combine(_tempDir, "nuget-cache");
if (SkipTempDirectoryCleanup)
{
Log.LogMessage(MessageImportance.High, $"Using temporary directory {_tempDir} for installing workloads from artifacts.");
}

try
{
Expand Down Expand Up @@ -217,6 +220,12 @@ private bool InstallPacks(InstallWorkloadRequest req, string nugetConfigContents
string nugetConfigPath = Path.Combine(_tempDir, $"NuGet.{Path.GetRandomFileName()}.config");
File.WriteAllText(nugetConfigPath, nugetConfigContents);

if (string.IsNullOrEmpty(WorkloadInstallCommandOutputImportance) ||
!Enum.TryParse<MessageImportance>(WorkloadInstallCommandOutputImportance, out var outputImportance))
{
outputImportance = MessageImportance.Normal;
}

// Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** dotnet workload install {req.WorkloadId} **{Environment.NewLine}");
(int exitCode, string output) = Utils.TryRunProcess(
Log,
Expand All @@ -228,7 +237,7 @@ private bool InstallPacks(InstallWorkloadRequest req, string nugetConfigContents
},
logStdErrAsMessage: req.IgnoreErrors,
silent: false,
debugMessageImportance: MessageImportance.Normal);
debugMessageImportance: outputImportance);
if (exitCode != 0)
{
if (req.IgnoreErrors)
Expand All @@ -255,11 +264,11 @@ private bool InstallPacks(InstallWorkloadRequest req, string nugetConfigContents

private string GetNuGetConfig()
{
string contents = File.ReadAllText(TemplateNuGetConfigPath);
if (!contents.Contains(s_nugetInsertionTag, StringComparison.InvariantCultureIgnoreCase))
throw new LogAsErrorException($"Could not find {s_nugetInsertionTag} in {TemplateNuGetConfigPath}");

return contents.Replace(s_nugetInsertionTag, $@"<add key=""nuget-local"" value=""file://{LocalNuGetsPath}"" />");
var nugetConfigPath = Path.GetTempFileName();
PatchNuGetConfig.GetNuGetConfig(TemplateNuGetConfigPath, LocalNuGetsPath, PackageSourceNameForBuiltPackages, NuGetConfigPackageSourceMappings, nugetConfigPath);
string contents = File.ReadAllText(nugetConfigPath);
File.Delete(nugetConfigPath);
return contents;
}

private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string version, string sdkDir, string nugetConfigContents, bool stopOnMissing)
Expand Down
2 changes: 1 addition & 1 deletion src/tasks/WorkloadBuildTasks/PackageInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ private static string GenerateProject(IEnumerable<PackageReference> references)
<ItemGroup>");

foreach (var reference in references)
projectFileBuilder.AppendLine($"<PackageReference Include=\"{reference.Name}\" Version=\"{reference.Version}\" />");
projectFileBuilder.AppendLine($"<PackageDownload Include=\"{reference.Name}\" Version=\"[{reference.Version}]\" />");

projectFileBuilder.Append(@"
</ItemGroup>
Expand Down
140 changes: 140 additions & 0 deletions src/tasks/WorkloadBuildTasks/PatchNuGetConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// 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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

#nullable enable

namespace Microsoft.Workload.Build.Tasks;

/*
* Used for patching a nuget.config to:
*
* 1. Add a new package source to the nuget.config
* 2. Add a new package source mapping to the nuget.config
*
* This is useful specifically the case of workload testing
*/
public class PatchNuGetConfig : Task
{
[Required, NotNull]
public string? TemplateNuGetConfigPath { get; set; }

[Required, NotNull]
public string? LocalNuGetsPath { get; set; }

public string? OutputPath { get; set; }

/*
* Value: ["*Aspire*", "Foo*"]
* This will be translated to:
* <packageSourceMapping>
* <packageSource key="nuget-local">
* <package pattern="*Aspire*" />
* <package pattern="Foo*" />
* </packageSource>
*
* This is useful when using Central Package Management (https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management)
*/
public string[] NuGetConfigPackageSourceMappings { get; set; } = Array.Empty<string>();

public string PackageSourceNameForBuiltPackages { get; set; } = "nuget-local";

public override bool Execute()
{
try
{
Validate(TemplateNuGetConfigPath, PackageSourceNameForBuiltPackages, OutputPath);
GetNuGetConfig(TemplateNuGetConfigPath, LocalNuGetsPath, PackageSourceNameForBuiltPackages, NuGetConfigPackageSourceMappings, OutputPath!);
Log.LogMessage(MessageImportance.Low, $"Generated patched nuget.config at {OutputPath}");
return true;
}
catch (LogAsErrorException laee)
{
Log.LogError(laee.Message);
return false;
}
}

private static void Validate(string? templateNuGetConfigPath, string? packageSourceNameForBuiltPackages, string? outputPath)
{
if (string.IsNullOrEmpty(templateNuGetConfigPath))
throw new LogAsErrorException($"{nameof(templateNuGetConfigPath)} is required");
radical marked this conversation as resolved.
Show resolved Hide resolved

if (!File.Exists(templateNuGetConfigPath))
throw new LogAsErrorException($"Cannot find {nameof(templateNuGetConfigPath)}={templateNuGetConfigPath}");

if (string.IsNullOrEmpty(packageSourceNameForBuiltPackages))
throw new LogAsErrorException($"{nameof(packageSourceNameForBuiltPackages)} is required");

if (string.IsNullOrEmpty(outputPath))
throw new LogAsErrorException($"{nameof(outputPath)} is required");

if (Directory.Exists(outputPath))
throw new LogAsErrorException($"{nameof(outputPath)}={outputPath} is a directory, it should be a file");
}

public static void GetNuGetConfig(string templateNuGetConfigPath, string localNuGetsPath, string packageSourceNameForBuiltPackages, string[] nuGetConfigPackageSourceMappings, string outputPath)
{
Validate(templateNuGetConfigPath, packageSourceNameForBuiltPackages, outputPath);

XDocument doc = XDocument.Load(templateNuGetConfigPath);
string xpath = "/configuration/packageSources";
XElement? packageSources = doc.XPathSelectElement(xpath);
if (packageSources is null)
throw new LogAsErrorException($"Could not find {xpath} in {templateNuGetConfigPath}");

var newPackageSourceElement = new XElement("add",
new XAttribute("key", packageSourceNameForBuiltPackages),
new XAttribute("value", $"file://{localNuGetsPath}"));
if (packageSources.LastNode is not null)
{
packageSources.LastNode.AddAfterSelf(newPackageSourceElement);
}
else
{
packageSources.Add(newPackageSourceElement);
}

if (nuGetConfigPackageSourceMappings.Length > 0)
{
string mappingXpath = "/configuration/packageSourceMapping";
XElement? packageSourceMapping = doc.XPathSelectElement(mappingXpath);
if (packageSourceMapping is null)
{
if (doc.Root is null)
throw new LogAsErrorException($"Could not find root element in {templateNuGetConfigPath}");

packageSourceMapping = new XElement("packageSourceMapping");
doc.Root.Add(packageSourceMapping);
}

var newPackageSourceMappingElement = new XElement("packageSource",
new XAttribute("key", packageSourceNameForBuiltPackages),
nuGetConfigPackageSourceMappings.Select
(pattern => new XElement("package", new XAttribute("pattern", pattern))));
if (packageSourceMapping.FirstNode is not null)
{
packageSourceMapping.FirstNode?.AddBeforeSelf(newPackageSourceMappingElement);
}
else
{
packageSourceMapping.Add(newPackageSourceMappingElement);
}
}

using var xw = XmlWriter.Create(outputPath, new XmlWriterSettings { Indent = true, NewLineHandling = NewLineHandling.None, Encoding = Encoding.UTF8 });
doc.WriteTo(xw);
xw.Close();
}
}
Loading