Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 5 additions & 19 deletions src/Tasks/Microsoft.NET.Build.Tasks/FrameworkReferenceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,23 @@

#nullable disable

using Microsoft.Extensions.DependencyModel.Resolution;

namespace Microsoft.NET.Build.Tasks
{
internal class FrameworkReferenceResolver
{
private readonly Func<string, string> _getEnvironmentVariable;

/// <summary>
/// Creates an instance that reads environment variables from the process environment.
/// </summary>
public FrameworkReferenceResolver()
: this(Environment.GetEnvironmentVariable)
{
}

/// <summary>
/// Creates an instance that reads environment variables via the supplied delegate.
/// Use this from MSBuild tasks to route reads through TaskEnvironment.
/// </summary>
public FrameworkReferenceResolver(Func<string, string> getEnvironmentVariable)
{
_getEnvironmentVariable = getEnvironmentVariable ?? throw new ArgumentNullException(nameof(getEnvironmentVariable));
_getEnvironmentVariable = getEnvironmentVariable;
}

public string GetDefaultReferenceAssembliesPath()
{
// Allow setting the reference assemblies path via an environment variable.
// We read this directly instead of calling DotNetReferenceAssembliesPathResolver.Resolve()
// because that runtime method uses process-global Environment.GetEnvironmentVariable.
var referenceAssembliesPath = _getEnvironmentVariable(DotNetReferenceAssembliesPathResolver.DotNetReferenceAssembliesPathEnv);
// Read DOTNET_REFERENCE_ASSEMBLIES_PATH through the injected delegate
// instead of DotNetReferenceAssembliesPathResolver.Resolve(), which uses
// process-global Environment.GetEnvironmentVariable and bypasses TaskEnvironment.
var referenceAssembliesPath = _getEnvironmentVariable("DOTNET_REFERENCE_ASSEMBLIES_PATH");

if (!string.IsNullOrEmpty(referenceAssembliesPath))
{
Expand Down
21 changes: 18 additions & 3 deletions src/Tasks/Microsoft.NET.Build.Tasks/GenerateBundle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@

namespace Microsoft.NET.Build.Tasks
{
public class GenerateBundle : TaskBase, ICancelableTask
[MSBuildMultiThreadableTask]
public class GenerateBundle : TaskBase, ICancelableTask, IMultiThreadableTask
{
#if NETFRAMEWORK
private TaskEnvironment _taskEnvironment;
public TaskEnvironment TaskEnvironment
{
get => _taskEnvironment ??= TaskEnvironmentDefaults.Create();
set => _taskEnvironment = value;
}
#else
public TaskEnvironment TaskEnvironment { get; set; } = null!;
#endif

private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly Random _jitter =
#if NET
Expand Down Expand Up @@ -78,9 +90,12 @@ private async Task ExecuteWithRetry()
options |= EnableCompressionInSingleFile ? BundleOptions.EnableCompression : BundleOptions.None;

Version version = new(TargetFrameworkVersion);
string absoluteOutputDir = string.IsNullOrEmpty(OutputDir)
? TaskEnvironment.ProjectDirectory
: TaskEnvironment.GetAbsolutePath(OutputDir);
var bundler = new Bundler(
AppHostName,
OutputDir,
absoluteOutputDir,
options,
targetOS,
targetArch,
Expand All @@ -92,7 +107,7 @@ private async Task ExecuteWithRetry()

foreach (var item in FilesToBundle)
{
fileSpec.Add(new FileSpec(sourcePath: item.ItemSpec,
fileSpec.Add(new FileSpec(sourcePath: TaskEnvironment.GetAbsolutePath(item.ItemSpec),
bundleRelativePath: item.GetMetadata(MetadataKeys.RelativePath)));
}

Expand Down
38 changes: 28 additions & 10 deletions src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,20 @@ namespace Microsoft.NET.Build.Tasks
/// <summary>
/// Generates the $(project).deps.json file.
/// </summary>
public class GenerateDepsFile : TaskBase
[MSBuildMultiThreadableTask]
public class GenerateDepsFile : TaskBase, IMultiThreadableTask
{
#if NETFRAMEWORK
private TaskEnvironment _taskEnvironment;
public TaskEnvironment TaskEnvironment
{
get => _taskEnvironment ??= TaskEnvironmentDefaults.Create();
set => _taskEnvironment = value;
}
#else
public TaskEnvironment TaskEnvironment { get; set; }
#endif

[Required]
public string ProjectPath { get; set; }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If this path is relative, there is a problem. It gets transformed into the full path down the line in DependencyContextBuilder.cs like following:
string fullProjectPath = Path.GetFullPath(Path.Combine(mainProjectDirectory, projectPath));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Took me a while, but now the agents came to an agreement and claim that this was resolved and is now properly absolutized before use.
I'm mostly confident they are correct, unless I overlook something while reviewing.


Expand Down Expand Up @@ -133,13 +145,17 @@ private Dictionary<PackageIdentity, string> GetFilteredPackages()
return filteredPackages;
}

private void WriteDepsFile(string depsFilePath)
private void WriteDepsFile(string depsFilePath, string projectPath, string assetsFilePath, string runtimeGraphPath)
{
AbsolutePath absDepsFilePath = TaskEnvironment.GetAbsolutePath(depsFilePath);
AbsolutePath absProjectPath = TaskEnvironment.GetAbsolutePath(projectPath);

ProjectContext projectContext = null;
LockFileLookup lockFileLookup = null;
if (AssetsFilePath != null)
if (!string.IsNullOrEmpty(assetsFilePath))
{
LockFile lockFile = new LockFileCache(this).GetLockFile(AssetsFilePath);
AbsolutePath absAssetsFilePath = TaskEnvironment.GetAbsolutePath(assetsFilePath);
LockFile lockFile = new LockFileCache(this).GetLockFile(absAssetsFilePath.Value);
projectContext = lockFile.CreateProjectContext(
TargetFramework,
EffectiveRuntimeIdentifier,
Expand All @@ -153,7 +169,7 @@ private void WriteDepsFile(string depsFilePath)
CompilationOptions compilationOptions = CompilationOptionsConverter.ConvertFrom(CompilerOptions);

SingleProjectInfo mainProject = SingleProjectInfo.Create(
ProjectPath,
absProjectPath.Value,
AssemblyName,
AssemblyExtension,
AssemblyVersion,
Expand Down Expand Up @@ -219,7 +235,9 @@ bool ShouldIncludeRuntimeAsset(ITaskItem item)
// graph with respect to the target RuntimeIdentifier.

RuntimeGraph runtimeGraph =
IsSelfContained ? new RuntimeGraphCache(this).GetRuntimeGraph(RuntimeGraphPath) : null;
IsSelfContained && !string.IsNullOrEmpty(runtimeGraphPath)
? new RuntimeGraphCache(this).GetRuntimeGraph(TaskEnvironment.GetAbsolutePath(runtimeGraphPath).Value)
: null;

builder = new DependencyContextBuilder(mainProject, IncludeRuntimeFileVersions, runtimeGraph, projectContext, lockFileLookup);
}
Expand All @@ -244,7 +262,7 @@ bool ShouldIncludeRuntimeAsset(ITaskItem item)
.WithReferenceProjectInfos(referenceProjects)
.WithRuntimePackAssets(runtimePackAssets)
.WithCompilationOptions(compilationOptions)
.WithReferenceAssembliesPath(new FrameworkReferenceResolver().GetDefaultReferenceAssembliesPath())
.WithReferenceAssembliesPath(new FrameworkReferenceResolver(TaskEnvironment.GetEnvironmentVariable).GetDefaultReferenceAssembliesPath())
.WithPackagesThatWereFiltered(GetFilteredPackages());

if (CompileReferences.Length > 0)
Expand All @@ -265,11 +283,11 @@ bool ShouldIncludeRuntimeAsset(ITaskItem item)
DependencyContext dependencyContext = builder.Build(userRuntimeAssemblies);

var writer = new DependencyContextWriter();
using (var fileStream = File.Create(depsFilePath))
using (var fileStream = File.Create(absDepsFilePath.Value))
{
writer.Write(dependencyContext, fileStream);
}
_filesWritten.Add(new TaskItem(depsFilePath));
_filesWritten.Add(new TaskItem(DepsFilePath));

if (ValidRuntimeIdentifierPlatformsForAssets != null)
{
Expand Down Expand Up @@ -310,7 +328,7 @@ private bool ShouldWarnOnRuntimeIdentifer(string runtimeIdentifier)

protected override void ExecuteCore()
{
WriteDepsFile(DepsFilePath);
WriteDepsFile(DepsFilePath, ProjectPath, AssetsFilePath, RuntimeGraphPath);
}
}
}
35 changes: 29 additions & 6 deletions src/Tasks/Microsoft.NET.Build.Tasks/ResolveAppHosts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,20 @@

namespace Microsoft.NET.Build.Tasks
{
public class ResolveAppHosts : TaskBase
[MSBuildMultiThreadableTask]
public class ResolveAppHosts : TaskBase, IMultiThreadableTask
{
#if NETFRAMEWORK
private TaskEnvironment _taskEnvironment;
public TaskEnvironment TaskEnvironment
{
get => _taskEnvironment ??= TaskEnvironmentDefaults.Create();
set => _taskEnvironment = value;
}
#else
public TaskEnvironment TaskEnvironment { get; set; }
#endif

public string TargetFrameworkIdentifier { get; set; }

public string TargetFrameworkVersion { get; set; }
Expand Down Expand Up @@ -78,8 +90,18 @@ public class ResolveAppHosts : TaskBase
[Output]
public ITaskItem[] PackAsToolShimAppHostPacks { get; set; }

private AbsolutePath? _absoluteRuntimeGraphPath;
private AbsolutePath? _absoluteTargetingPackRoot;

protected override void ExecuteCore()
{
_absoluteRuntimeGraphPath = !string.IsNullOrEmpty(RuntimeGraphPath)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Consider backing the property with AbsolutePath objects.

? TaskEnvironment.GetAbsolutePath(RuntimeGraphPath)
: null;
_absoluteTargetingPackRoot = !string.IsNullOrEmpty(TargetingPackRoot)
? TaskEnvironment.GetAbsolutePath(TargetingPackRoot)
: null;

var normalizedTargetFrameworkVersion = ProcessFrameworkReferences.NormalizeVersion(new Version(TargetFrameworkVersion));

var knownAppHostPacksForTargetFramework = KnownAppHostPacks
Expand Down Expand Up @@ -245,7 +267,7 @@ private ITaskItem GetHostItem(string runtimeIdentifier,
}

string bestAppHostRuntimeIdentifier = NuGetUtils.GetBestMatchingRidWithExclusion(
new RuntimeGraphCache(this).GetRuntimeGraph(RuntimeGraphPath),
new RuntimeGraphCache(this).GetRuntimeGraph(_absoluteRuntimeGraphPath.Value.Value),
runtimeIdentifier,
runtimeIdentifiersToExclude.Split(';'),
appHostRuntimeIdentifiers.Split(';'),
Expand Down Expand Up @@ -288,15 +310,16 @@ private ITaskItem GetHostItem(string runtimeIdentifier,

TaskItem appHostItem = new(itemName);
string appHostPackPath = null;
if (!string.IsNullOrEmpty(TargetingPackRoot))
if (_absoluteTargetingPackRoot.HasValue)
{
appHostPackPath = Path.Combine(TargetingPackRoot, hostPackName, appHostPackVersion);
appHostPackPath = Path.Combine(_absoluteTargetingPackRoot.Value.Value, hostPackName, appHostPackVersion);
}
if (appHostPackPath != null && Directory.Exists(appHostPackPath))
{
// Use AppHost from packs folder
appHostItem.SetMetadata(MetadataKeys.PackageDirectory, appHostPackPath);
appHostItem.SetMetadata(MetadataKeys.Path, Path.Combine(appHostPackPath, hostRelativePathInPackage));
string originalAppHostPackPath = Path.Combine(TargetingPackRoot, hostPackName, appHostPackVersion);
appHostItem.SetMetadata(MetadataKeys.PackageDirectory, originalAppHostPackPath);
appHostItem.SetMetadata(MetadataKeys.Path, Path.Combine(originalAppHostPackPath, hostRelativePathInPackage));
}
else if (EnableAppHostPackDownload)
{
Expand Down
1 change: 1 addition & 0 deletions src/Tasks/Microsoft.NET.Build.Tasks/ShowPreviewMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Microsoft.NET.Build.Tasks
/// <summary>
/// Provides a localizable mechanism for logging messages with different levels of importance from the SDK targets.
/// </summary>
[MSBuildMultiThreadableTask]
Comment thread
SimaTian marked this conversation as resolved.
public class ShowPreviewMessage : TaskBase
{
protected override void ExecuteCore()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@

namespace Microsoft.NET.Build.Tasks
{
public sealed class WriteAppConfigWithSupportedRuntime : TaskBase
[MSBuildMultiThreadableTask]
Comment thread
SimaTian marked this conversation as resolved.
public sealed class WriteAppConfigWithSupportedRuntime : TaskBase, IMultiThreadableTask
{
#if NETFRAMEWORK
private TaskEnvironment _taskEnvironment;
public TaskEnvironment TaskEnvironment
{
get => _taskEnvironment ??= TaskEnvironmentDefaults.Create();
set => _taskEnvironment = value;
}
#else
public TaskEnvironment TaskEnvironment { get; set; }
#endif

/// <summary>
/// Path to the app.config source file.
/// </summary>
Expand All @@ -34,8 +46,9 @@ protected override void ExecuteCore()

AddSupportedRuntimeToAppconfig(doc, TargetFrameworkIdentifier, TargetFrameworkVersion, TargetFrameworkProfile);

string absoluteOutputPath = TaskEnvironment.GetAbsolutePath(OutputAppConfigFile.ItemSpec);
var fileStream = new FileStream(
OutputAppConfigFile.ItemSpec,
absoluteOutputPath,
FileMode.Create,
Comment thread
SimaTian marked this conversation as resolved.
FileAccess.Write,
FileShare.Read);
Expand Down Expand Up @@ -156,7 +169,7 @@ private XDocument LoadAppConfig(ITaskItem appConfigItem)
}
else
{
document = XDocument.Load(appConfigItem.ItemSpec);
document = XDocument.Load(TaskEnvironment.GetAbsolutePath(appConfigItem.ItemSpec));
if (document.Root == null || document.Root.Name != "configuration")
{
throw new BuildErrorException(Strings.AppConfigRequiresRootConfiguration);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using FluentAssertions;
using Xunit;

namespace Microsoft.NET.Build.Tasks.UnitTests
{
public class GivenAFrameworkReferenceResolver
{
[Fact]
public void ItReadsReferenceAssembliesPathThroughInjectedDelegate()
{
var envVarsRead = new List<string>();
var resolver = new FrameworkReferenceResolver(name =>
{
envVarsRead.Add(name);
return name == "DOTNET_REFERENCE_ASSEMBLIES_PATH" ? "/custom/ref/path" : null;
});

var result = resolver.GetDefaultReferenceAssembliesPath();

result.Should().Be("/custom/ref/path");
envVarsRead.Should().Contain("DOTNET_REFERENCE_ASSEMBLIES_PATH",
"env var should be read through the injected delegate, not process-global Environment");
}

[Fact]
public void ItFallsToProgramFilesWhenReferenceAssembliesPathNotSet()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// On non-Windows, it returns null when env var is not set
var resolver = new FrameworkReferenceResolver(_ => null);
resolver.GetDefaultReferenceAssembliesPath().Should().BeNull();
return;
}

var resolver2 = new FrameworkReferenceResolver(name =>
name == "ProgramFiles(x86)" ? @"C:\Program Files (x86)" : null);

var result = resolver2.GetDefaultReferenceAssembliesPath();

result.Should().Be(@"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework");
}

[Fact]
public void ItDoesNotCallProcessGlobalEnvironment()
{
// Set a process-level env var that should NOT be seen by the resolver
// if it correctly uses the injected delegate
var originalValue = Environment.GetEnvironmentVariable("DOTNET_REFERENCE_ASSEMBLIES_PATH");
try
{
Environment.SetEnvironmentVariable("DOTNET_REFERENCE_ASSEMBLIES_PATH", "/process-global-path");

// Inject a delegate that returns a DIFFERENT value
var resolver = new FrameworkReferenceResolver(name =>
name == "DOTNET_REFERENCE_ASSEMBLIES_PATH" ? "/injected-path" : null);

var result = resolver.GetDefaultReferenceAssembliesPath();

result.Should().Be("/injected-path",
"resolver should use injected delegate, not process-global Environment.GetEnvironmentVariable");
}
finally
{
Environment.SetEnvironmentVariable("DOTNET_REFERENCE_ASSEMBLIES_PATH", originalValue);
}
}
}
}
Loading
Loading