Skip to content
6 changes: 5 additions & 1 deletion src/Compilers/CSharp/csc/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ public static int Main(string[] args)

private static int MainCore(string[] args)
{
using var logger = new CompilerServerLogger($"csc {Process.GetCurrentProcess().Id}");
using var logger = new CompilerServerLogger(
$"csc {Process.GetCurrentProcess().Id}",
loggingFilePath: null,
Environment.GetEnvironmentVariable,
static path => path);

#if BOOTSTRAP
ExitingTraceListener.Install(logger);
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/Core/MSBuildTask/Csc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ namespace Microsoft.CodeAnalysis.BuildTasks
/// should be significantly faster with larger projects and have a smaller memory
/// footprint.
/// </summary>
[MSBuildMultiThreadableTask]
public class Csc : ManagedCompiler
{
#region Properties
Expand Down
33 changes: 22 additions & 11 deletions src/Compilers/Core/MSBuildTask/ManagedCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,11 @@ public string GeneratePathToTool()

protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
{
using var innerLogger = new CompilerServerLogger($"MSBuild {Process.GetCurrentProcess().Id}");
using var innerLogger = new CompilerServerLogger(
$"MSBuild {Process.GetCurrentProcess().Id}",
loggingFilePath: null,
this.TaskEnvironment.GetEnvironmentVariable,
path => this.TaskEnvironment.GetAbsolutePath(path).Value);
var logger = new TaskCompilerServerLogger(Log, innerLogger);
return ExecuteTool(pathToTool, responseFileCommands, commandLineCommands, logger);
}
Expand Down Expand Up @@ -553,7 +557,10 @@ internal int ExecuteTool(string pathToTool, string responseFileCommands, string
// commandLineCommands (the parameter) may have been mucked with
// (to support using the dotnet cli)
var buildRequestArguments = GenerateCommandLineArgsList(responseFileCommands);
CompilerOptionParseUtilities.PrependFeatureFlagFromEnvironment(buildRequestArguments, logger.Log);
CompilerOptionParseUtilities.PrependFeatureFlagFromEnvironment(
buildRequestArguments,
logger.Log,
this.TaskEnvironment.GetEnvironmentVariable);
var buildRequest = BuildServerConnection.CreateBuildRequest(
requestId,
Language,
Expand All @@ -571,8 +578,10 @@ internal int ExecuteTool(string pathToTool, string responseFileCommands, string
buildRequest,
pipeName,
clientDirectory,
logger: logger,
cancellationToken: _sharedCompileCts.Token);
this.TaskEnvironment.GetEnvironmentVariable,
BuildServerConnection.CreateEnvironmentVariableSnapshot(this.TaskEnvironment.GetEnvironmentVariables()),
logger,
_sharedCompileCts.Token);

responseTask.Wait(_sharedCompileCts.Token);

Expand Down Expand Up @@ -628,12 +637,12 @@ public override void Cancel()
/// </summary>
private string CurrentDirectoryToUse()
{
// ToolTask has a method for this. But it may return null. Use the process directory
// if ToolTask didn't override. MSBuild uses the process directory.
// ToolTask has a method for this. But it may return null. Use the task's project
// directory if ToolTask didn't override. This resolves against the task's execution environment.
string workingDirectory = GetWorkingDirectory();
if (string.IsNullOrEmpty(workingDirectory))
{
workingDirectory = Directory.GetCurrentDirectory();
workingDirectory = this.TaskEnvironment.ProjectDirectory;
}
return workingDirectory;
}
Expand All @@ -644,7 +653,7 @@ private string CurrentDirectoryToUse()
private string? LibDirectoryToUse()
{
// First check the real environment.
string? libDirectory = Environment.GetEnvironmentVariable("LIB");
string? libDirectory = this.TaskEnvironment.GetEnvironmentVariable("LIB");

Comment thread
OvesN marked this conversation as resolved.
Outdated
// Now go through additional environment variables.
string[] additionalVariables = EnvironmentVariables;
Expand Down Expand Up @@ -1098,7 +1107,7 @@ private void NormalizePaths(ITaskItem[]? taskItems)

foreach (var item in taskItems)
{
item.ItemSpec = Utilities.GetFullPathNoThrow(item.ItemSpec);
item.ItemSpec = Utilities.GetFullPathNoThrow(item.ItemSpec, TaskEnvironment);
}
}

Expand Down Expand Up @@ -1219,10 +1228,12 @@ protected bool CheckAllReferencesExistOnDisk()

foreach (ITaskItem reference in References)
{
if (!File.Exists(reference.ItemSpec))
var itemSpec = reference.ItemSpec;

if (string.IsNullOrEmpty(itemSpec) || !File.Exists(this.TaskEnvironment.GetAbsolutePath(itemSpec)))
{
Comment thread
OvesN marked this conversation as resolved.
success = false;
Log.LogErrorWithCodeFromResources("General_ReferenceDoesNotExist", reference.ItemSpec);
Log.LogErrorWithCodeFromResources("General_ReferenceDoesNotExist", itemSpec);
}
}

Expand Down
16 changes: 11 additions & 5 deletions src/Compilers/Core/MSBuildTask/ManagedToolTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ protected sealed override string GenerateFullPathToTool()
// which means `ToolExe` is not really overridden by user (yes, the user sets it but basically to its default value).
ToolExe = null;

return UseAppHost ? PathToBuiltInTool : RuntimeHostInfo.GetDotNetPathOrDefault();
return UseAppHost
? PathToBuiltInTool
: RuntimeHostInfo.GetDotNetPathOrDefault(this.TaskEnvironment.GetEnvironmentVariable);
}

return Path.Combine(ToolPath ?? "", ToolExe);
Expand Down Expand Up @@ -319,20 +321,24 @@ protected override bool ValidateParameters()
{
// Set DOTNET_ROOT so that the apphost executables launch properly.
// Unset all other DOTNET_ROOT* variables so for example DOTNET_ROOT_X64 does not override ours.
if (IsBuiltinToolRunningOnCoreClr && RuntimeHostInfo.GetToolDotNetRoot(Log.LogMessage) is { } dotNetRoot)
if (IsBuiltinToolRunningOnCoreClr &&
RuntimeHostInfo.GetToolDotNetRoot(
this.TaskEnvironment.GetEnvironmentVariable,
Log.LogMessage) is { } dotNetRoot)
{
Log.LogMessage("Setting {0} to '{1}'", RuntimeHostInfo.DotNetRootEnvironmentName, dotNetRoot);
EnvironmentVariables =
[
.. EnvironmentVariables?.Where(static e => !e.StartsWith(RuntimeHostInfo.DotNetRootEnvironmentName, StringComparison.OrdinalIgnoreCase)) ?? [],
.. Environment.GetEnvironmentVariables().Cast<System.Collections.DictionaryEntry>()
.Where(e => ((string)e.Key).StartsWith(RuntimeHostInfo.DotNetRootEnvironmentName, StringComparison.OrdinalIgnoreCase))
.. this.TaskEnvironment.GetEnvironmentVariables()
.Where(e => e.Key.StartsWith(RuntimeHostInfo.DotNetRootEnvironmentName, StringComparison.OrdinalIgnoreCase))
.Select(e => $"{e.Key}="),
$"{RuntimeHostInfo.DotNetRootEnvironmentName}={dotNetRoot}",
];
}

if (RuntimeHostInfo.ShouldDisableTieredCompilation && Environment.GetEnvironmentVariable(RuntimeHostInfo.DotNetTieredCompilationEnvironmentName) == null)
if (RuntimeHostInfo.ShouldDisableTieredCompilation &&
this.TaskEnvironment.GetEnvironmentVariable(RuntimeHostInfo.DotNetTieredCompilationEnvironmentName) == null)
{
var value = "0";
Log.LogMessage("Setting {0} to '{1}'", RuntimeHostInfo.DotNetTieredCompilationEnvironmentName, value);
Expand Down
10 changes: 10 additions & 0 deletions src/Compilers/Core/MSBuildTask/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ internal static string GetFullPathNoThrow(string path)
return path;
}

internal static string GetFullPathNoThrow(string path, TaskEnvironment taskEnvironment)
{
try
{
path = Path.GetFullPath(taskEnvironment.GetAbsolutePath(path).Value);
}
catch (Exception e) when (IsIoRelatedException(e)) { }
return path;
}

internal static bool TryCombine(string path1, string path2, [NotNullWhen(returnValue: true)] out string? combined)
{
try
Expand Down
93 changes: 92 additions & 1 deletion src/Compilers/Core/MSBuildTaskTests/CscTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.CodeAnalysis.BuildTasks.UnitTests.TestUtilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
Expand Down Expand Up @@ -513,11 +515,30 @@ public void BuiltInToolExe(bool useAppHost, bool setToolExe)
}
else
{
AssertEx.Equal(RuntimeHostInfo.GetDotNetPathOrDefault(), csc.GeneratePathToTool());
AssertEx.Equal(RuntimeHostInfo.GetDotNetPathOrDefault(Environment.GetEnvironmentVariable), csc.GeneratePathToTool());
AssertEx.Equal(RuntimeHostInfo.GetDotNetExecCommandLine(csc.PathToBuiltInTool, ""), csc.GenerateCommandLineContents());
}
}

[Fact]
public void BuiltInToolUsesTaskEnvironmentDotNetHostPath()
{
var projectDirectory = Temp.CreateDirectory();
var dotNetHost = projectDirectory.CreateFile("dotnet-host");
var csc = new Csc
{
UseAppHost_TestOnly = false,
TaskEnvironment = TaskEnvironment.CreateWithProjectDirectoryAndEnvironment(
projectDirectory.Path,
new Dictionary<string, string>
{
[RuntimeHostInfo.DotNetHostPathEnvironmentName] = Path.GetFileName(dotNetHost.Path),
}),
};

Assert.Equal(Path.GetFileName(dotNetHost.Path), csc.GeneratePathToTool());
}

[Fact]
public void EditorConfig()
{
Expand Down Expand Up @@ -673,6 +694,76 @@ void parseRef(string refText, string alias)
}
}

/// <summary>
/// The multithreaded task migration requires relative references to be resolved against the
/// task's <see cref="TaskEnvironment.ProjectDirectory"/> rather than the shared process
/// working directory. Two task instances pointed at different project directories must resolve
/// the same relative reference independently.
/// </summary>
[Fact]
public void ReferenceExistenceResolvesAgainstTaskProjectDirectory()
{
var directoryWithReference = Temp.CreateDirectory();
var directoryWithoutReference = Temp.CreateDirectory();
directoryWithReference.CreateFile("ref.dll");

var taskInDirectoryWithReference = new TestableCsc()
{
BuildEngine = new MockEngine(TestOutputHelper),
TaskEnvironment = TaskEnvironment.CreateWithProjectDirectoryAndEnvironment(directoryWithReference.Path),
References = MSBuildUtil.CreateTaskItems("ref.dll"),
};
var taskInDirectoryWithoutReference = new TestableCsc()
{
BuildEngine = new MockEngine(TestOutputHelper),
TaskEnvironment = TaskEnvironment.CreateWithProjectDirectoryAndEnvironment(directoryWithoutReference.Path),
References = MSBuildUtil.CreateTaskItems("ref.dll"),
};

Assert.True(taskInDirectoryWithReference.CheckReferences());
Assert.False(taskInDirectoryWithoutReference.CheckReferences());
}

private sealed class TestableCsc : Csc
{
public bool CheckReferences() => CheckAllReferencesExistOnDisk();
}

/// <summary>
/// An empty reference <see cref="ITaskItem.ItemSpec"/> must be reported as a missing reference
/// (matching the pre-migration <c>File.Exists</c> behavior) rather than throwing from
/// <see cref="TaskEnvironment.GetAbsolutePath"/>.
/// </summary>
[Fact]
public void EmptyReferenceItemSpecIsReportedAsMissingWithoutThrowing()
{
var engine = new MockEngine(TestOutputHelper);
var task = new TestableCsc()
{
BuildEngine = engine,
References = MSBuildUtil.CreateTaskItems(""),
};

Assert.False(task.CheckReferences());
Assert.Contains("MSB3104", engine.Log);
}

[Fact]
public void InvalidReferencePathIsReportedAsMissingWithoutThrowing()
{
var projectDirectory = Temp.CreateDirectory();
var engine = new MockEngine(TestOutputHelper);
var task = new TestableCsc()
{
BuildEngine = engine,
TaskEnvironment = TaskEnvironment.CreateWithProjectDirectoryAndEnvironment(projectDirectory.Path),
References = MSBuildUtil.CreateTaskItems("bad|ref.dll"),
};

Assert.False(task.CheckReferences());
Assert.Contains("MSB3104", engine.Log);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79907")]
public void StdLib()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

#if NET

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -190,5 +189,6 @@ environmentCachePath is null
? VisualBasicCommandLineParser.Default.Parse(arguments, Directory.GetCurrentDirectory(), sdkDirectory: null, additionalReferenceDirectories: null).ParseOptions.Features
: CSharpCommandLineParser.Default.Parse(arguments, baseDirectory: Directory.GetCurrentDirectory(), sdkDirectory: null, additionalReferenceDirectories: null).ParseOptions.Features;
}

}
#endif
45 changes: 42 additions & 3 deletions src/Compilers/Core/MSBuildTaskTests/RuntimeHostInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Build.Framework;
using Microsoft.CodeAnalysis.CommandLine;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
Expand Down Expand Up @@ -50,7 +53,7 @@ public void DotNetInPath()
new(RuntimeHostInfo.DotNetHostPathEnvironmentName, ""),
new(RuntimeHostInfo.DotNetExperimentalHostPathEnvironmentName, ""),
],
() => RuntimeHostInfo.GetToolDotNetRoot(_output.WriteLine));
() => RuntimeHostInfo.GetToolDotNetRoot(Environment.GetEnvironmentVariable, _output.WriteLine));

Assert.NotNull(result);
AssertEx.Equal(NormalizePath(globalDotNetDir.Path), NormalizePath(result));
Expand All @@ -65,7 +68,7 @@ public void DotNetInPath_None()
new(RuntimeHostInfo.DotNetHostPathEnvironmentName, ""),
new(RuntimeHostInfo.DotNetExperimentalHostPathEnvironmentName, ""),
],
() => RuntimeHostInfo.GetToolDotNetRoot(_output.WriteLine));
() => RuntimeHostInfo.GetToolDotNetRoot(Environment.GetEnvironmentVariable, _output.WriteLine));

Assert.Null(result);
}
Expand All @@ -89,11 +92,47 @@ public void DotNetInPath_Symlinked()
new(RuntimeHostInfo.DotNetHostPathEnvironmentName, ""),
new(RuntimeHostInfo.DotNetExperimentalHostPathEnvironmentName, ""),
],
() => RuntimeHostInfo.GetToolDotNetRoot(_output.WriteLine));
() => RuntimeHostInfo.GetToolDotNetRoot(Environment.GetEnvironmentVariable, _output.WriteLine));

Assert.NotNull(result);
AssertEx.Equal(NormalizePath(globalDotNetDir.Path), NormalizePath(result));
}

[Fact]
public void DotNetInTaskEnvironmentPath()
{
var projectDirectory = Temp.CreateDirectory();
var binDirectory = projectDirectory.CreateDirectory("bin");
var dotNetPath = binDirectory.CreateFile($"dotnet{PlatformInformation.ExeExtension}").Path;
var taskEnvironment = TaskEnvironment.CreateWithProjectDirectoryAndEnvironment(
projectDirectory.Path,
new Dictionary<string, string>
{
["PATH"] = binDirectory.Path,
[RuntimeHostInfo.DotNetHostPathEnvironmentName] = "",
[RuntimeHostInfo.DotNetExperimentalHostPathEnvironmentName] = "",
});

var result = RuntimeHostInfo.GetDotNetPathOrDefault(taskEnvironment.GetEnvironmentVariable);

Assert.Equal(dotNetPath, result);
}

[Fact]
public void DotNetInPath_SkipsRelativePaths()
{
var result = RuntimeHostInfo.GetDotNetPathOrDefault(name => name switch
{
"PATH" => PlatformInformation.IsWindows
? @"relative;C:drive-relative;\root-relative"
: "relative",
RuntimeHostInfo.DotNetHostPathEnvironmentName => "",
RuntimeHostInfo.DotNetExperimentalHostPathEnvironmentName => "",
_ => null,
});

Assert.Equal($"dotnet{PlatformInformation.ExeExtension}", result);
}
}

#if !NET
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,11 @@ private static async Task ShutdownCompilerServerAsync(ProcessResult result, stri
{
var pipeName = Regex.Match(result.Output, @"Named pipe '([^']+)' connected").Groups[1].Value;
AssertEx.Equal(sharedCompilationId, pipeName);
using var logger = new CompilerServerLogger("test");
using var logger = new CompilerServerLogger(
"test",
loggingFilePath: null,
Environment.GetEnvironmentVariable,
static path => path);
await BuildServerConnection.RunServerShutdownRequestAsync(
pipeName,
timeoutOverride: null,
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/Core/MSBuildTaskTests/VbcTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ public void BuiltInToolExe(bool useAppHost, bool setToolExe)
}
else
{
AssertEx.Equal(RuntimeHostInfo.GetDotNetPathOrDefault(), vbc.GeneratePathToTool());
AssertEx.Equal(RuntimeHostInfo.GetDotNetPathOrDefault(Environment.GetEnvironmentVariable), vbc.GeneratePathToTool());
AssertEx.Equal(RuntimeHostInfo.GetDotNetExecCommandLine(vbc.PathToBuiltInTool, ""), vbc.GenerateCommandLineContents());
}
}
Expand Down
Loading
Loading