Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
12 changes: 11 additions & 1 deletion src/Compilers/Core/MSBuildTask/Csc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.Build.Utilities;
using Microsoft.CodeAnalysis.CommandLine;
using System.Runtime.InteropServices;
using System.IO;

namespace Microsoft.CodeAnalysis.BuildTasks
{
Expand Down Expand Up @@ -200,10 +201,19 @@ protected override string ToolNameWithoutExtension
/// </summary>
protected override void AddResponseFileCommands(CommandLineBuilderExtension commandLine)
{
// Pass sdkpath if we are invoking core compiler from framework to preserve the behavior that framework compiler would have.
// Pass sdkpath and csc.rsp if we are invoking core compiler from framework to preserve the behavior that framework compiler would have.
if (IsSdkFrameworkToCoreBridgeTask)
{
commandLine.AppendSwitchIfNotNull("/sdkpath:", RuntimeEnvironment.GetRuntimeDirectory());

if (!NoConfig)
{
var rspFile = Path.Combine(Path.GetDirectoryName(typeof(ManagedCompiler).Assembly.Location)!, "csc.rsp");
if (File.Exists(rspFile))
{
commandLine.AppendSwitchIfNotNull("@", rspFile);
Copy link
Contributor

@AlekseyTs AlekseyTs Nov 6, 2025

Choose a reason for hiding this comment

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

"@"

It doesn't look like we are testing scenario when an .rsp file is already specified #Closed

}
}
}

commandLine.AppendSwitchIfNotNull("/lib:", AdditionalLibPaths, ",");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@
<InternalsVisibleTo Include="Microsoft.Build.Tasks.CodeAnalysis.Sdk.UnitTests" />
</ItemGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\..\..\CSharp\csc\csc.rsp" CopyToOutputDirectory="PreserveNewest" />
Copy link
Contributor

Choose a reason for hiding this comment

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

"PreserveNewest"

Not sure how this option works. It feels like we should always overwrite unless exact match

Copy link
Member Author

@jjonescz jjonescz Nov 6, 2025

Choose a reason for hiding this comment

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

This is inspired by other projects that include csc.rsp:

<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

It works by not copying the file if it's not modified, so the builds are more performant

Copy link
Contributor

Choose a reason for hiding this comment

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

This is inspired by other projects that include csc.rsp

That is great, but I still would like to understand how exactly this option works. Other projects could have made a mistake. Since these files are not build artifacts, it doesn't feel appropriate to me to copy files only when they are newer. BTW, what exactly does it mean for a file to be newer for the purpose of this operation?

Copy link
Member Author

Choose a reason for hiding this comment

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

The file is copied to the output directory only if the source file has newer timestamp than the target file (the one in the output directory of the project). I think this is the common default for many MSBuild operations.

It's documented here: https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-items?view=vs-2022#none

CopyToOutputDirectory	Optional string. Determines whether to copy the file to the output directory. Values are:

1. Never
2. Always
3. PreserveNewest
4. IfDifferent

Defaults to Never if DefineExplicitDefaults is set to true; otherwise, defaults to the empty string.

Looks like we could use IfDifferent (but that's a newer option: dotnet/docs#44059); or Always.

<None Include="$(MSBuildThisFileDirectory)..\..\..\VisualBasic\vbc\vbc.rsp" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<Import Project="..\..\..\..\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems" Label="Shared" />
</Project>
11 changes: 10 additions & 1 deletion src/Compilers/Core/MSBuildTask/Vbc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,10 +385,19 @@ internal void MovePdbFileIfNecessary(string? outputAssembly)
/// </summary>
protected override void AddResponseFileCommands(CommandLineBuilderExtension commandLine)
{
// Pass sdkpath if we are invoking core compiler from framework to preserve the behavior that framework compiler would have.
// Pass sdkpath and vbc.rsp if we are invoking core compiler from framework to preserve the behavior that framework compiler would have.
if (SdkPath is null && IsSdkFrameworkToCoreBridgeTask)
{
commandLine.AppendSwitchIfNotNull("/sdkpath:", RuntimeEnvironment.GetRuntimeDirectory());

if (!NoConfig)
{
var rspFile = Path.Combine(Path.GetDirectoryName(typeof(ManagedCompiler).Assembly.Location)!, "vbc.rsp");
if (File.Exists(rspFile))
{
commandLine.AppendSwitchIfNotNull("@", rspFile);
}
}
}

commandLine.AppendSwitchIfNotNull("/baseaddress:", this.GetBaseAddressInHex());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ End Module
Assert.Contains(ExecutionConditionUtil.IsWindows ? "vbc.exe" : "vbc", result.Output);
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/79907")]
public void StdLib_Csc(bool useSharedCompilation, bool disableSdkPath)
[Theory, PairwiseData, WorkItem("https://github.com/dotnet/roslyn/issues/79907")]
public void StdLib_Csc(bool useSharedCompilation, bool disableSdkPath, bool noConfig)
{
if (_msbuildExecutable == null) return;

Expand All @@ -199,20 +199,21 @@ public void StdLib_Csc(bool useSharedCompilation, bool disableSdkPath)
new Dictionary<string, string>
{
{ "File.cs", """
using System.Linq;
System.Console.WriteLine("Hello from file");
""" },
{ "Test.csproj", $"""
<Project>
<UsingTask TaskName="Microsoft.CodeAnalysis.BuildTasks.Csc" AssemblyFile="{_buildTaskDll}" />
<Target Name="CustomTarget">
<Csc Sources="File.cs" UseSharedCompilation="{useSharedCompilation}" DisableSdkPath="{disableSdkPath}" />
<Csc Sources="File.cs" UseSharedCompilation="{useSharedCompilation}" DisableSdkPath="{disableSdkPath}" NoConfig="{noConfig}" />
Copy link
Contributor

Choose a reason for hiding this comment

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

PairwiseData

What does this do and why the change is necessary?

Copy link
Member Author

Choose a reason for hiding this comment

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

It avoids exponential amount of test runs since I added new input. See https://aarnott.github.io/Xunit.Combinatorial/docs/combinatorial-vs-pairwise.html. The pairwise ones should be the interesting ones (I've verified the test still fails without the product change for example).

Copy link
Contributor

Choose a reason for hiding this comment

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

I do not find 8 cases too much and would be more comfortable with covering full matrix. I am uncomfortable with a situation when an "external" tool decides what combinations our tests cover and that combinations possibly changing between versions of that tool. My recommendation is to use full matrix or explicitly specifying combinations covered by the test.

</Target>
</Project>
""" },
});
_output.WriteLine(result.Output);

if (disableSdkPath)
if (disableSdkPath || noConfig)
{
Assert.NotEqual(0, result.ExitCode);
// Either error CS0006: Metadata file could not be found
Expand All @@ -226,8 +227,8 @@ public void StdLib_Csc(bool useSharedCompilation, bool disableSdkPath)
}
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/79907")]
public void StdLib_Vbc(bool useSharedCompilation, bool disableSdkPath)
[Theory, PairwiseData, WorkItem("https://github.com/dotnet/roslyn/issues/79907")]
public void StdLib_Vbc(bool useSharedCompilation, bool disableSdkPath, bool noConfig)
{
if (_msbuildExecutable == null) return;

Expand All @@ -240,26 +241,35 @@ public void StdLib_Vbc(bool useSharedCompilation, bool disableSdkPath)
{ "File.vb", """
Public Module Program
Public Sub Main()
System.Console.WriteLine("Hello from file")
Console.WriteLine("Hello from file")
End Sub
End Module
""" },
{ "Test.vbproj", $"""
<Project>
<UsingTask TaskName="Microsoft.CodeAnalysis.BuildTasks.Vbc" AssemblyFile="{_buildTaskDll}" />
<Target Name="CustomTarget">
<Vbc Sources="File.vb" UseSharedCompilation="{useSharedCompilation}" DisableSdkPath="{disableSdkPath}" />
<Vbc Sources="File.vb" UseSharedCompilation="{useSharedCompilation}" DisableSdkPath="{disableSdkPath}" NoConfig="{noConfig}" />
</Target>
</Project>
""" },
});
_output.WriteLine(result.Output);

if (disableSdkPath)
if (disableSdkPath || noConfig)
{
Assert.NotEqual(0, result.ExitCode);
// error BC2017: could not find library 'Microsoft.VisualBasic.dll'
Assert.Contains("error BC2017", result.Output);
if (disableSdkPath)
{
// error BC2017: could not find library 'Microsoft.VisualBasic.dll'
Assert.Contains("error BC2017", result.Output);
}
else
{
Assert.True(noConfig);
// error BC30451: 'Console' is not declared. It may be inaccessible due to its protection level.
Assert.Contains("error BC30451", result.Output);
}
}
else
{
Expand Down
7 changes: 5 additions & 2 deletions src/Compilers/Core/SdkTaskTests/CscTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.IO;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.BuildTasks.UnitTests;
using Roslyn.Test.Utilities;
Expand All @@ -11,6 +12,8 @@ namespace Microsoft.CodeAnalysis.BuildTasks.Sdk.UnitTests;

public sealed class CscTests
{
private static string RspFilePath => Path.Combine(Path.GetDirectoryName(typeof(ManagedCompiler).Assembly.Location)!, "csc.rsp");

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79907")]
public void StdLib()
{
Expand All @@ -19,7 +22,7 @@ public void StdLib()
Sources = MSBuildUtil.CreateTaskItems("test.cs"),
};

AssertEx.Equal($"/sdkpath:{RuntimeEnvironment.GetRuntimeDirectory()} /out:test.exe test.cs", csc.GenerateResponseFileContents());
AssertEx.Equal($"/sdkpath:{RuntimeEnvironment.GetRuntimeDirectory()} @{RspFilePath} /out:test.exe test.cs", csc.GenerateResponseFileContents());
Copy link
Contributor

Choose a reason for hiding this comment

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

csc.GenerateResponseFileContents()

Consider covering "noconfig" behavior as well

}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79907")]
Expand All @@ -31,6 +34,6 @@ public void StdLib_DisableSdkPath()
DisableSdkPath = true,
};

AssertEx.Equal($"/sdkpath:{RuntimeEnvironment.GetRuntimeDirectory()} /nosdkpath /out:test.exe test.cs", csc.GenerateResponseFileContents());
AssertEx.Equal($"/sdkpath:{RuntimeEnvironment.GetRuntimeDirectory()} @{RspFilePath} /nosdkpath /out:test.exe test.cs", csc.GenerateResponseFileContents());
}
}
7 changes: 5 additions & 2 deletions src/Compilers/Core/SdkTaskTests/VbcTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.IO;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.BuildTasks.UnitTests;
using Roslyn.Test.Utilities;
Expand All @@ -11,6 +12,8 @@ namespace Microsoft.CodeAnalysis.BuildTasks.Sdk.UnitTests;

public sealed class VbcTests
{
private static string RspFilePath => Path.Combine(Path.GetDirectoryName(typeof(ManagedCompiler).Assembly.Location)!, "vbc.rsp");

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79907")]
public void StdLib()
{
Expand All @@ -19,7 +22,7 @@ public void StdLib()
Sources = MSBuildUtil.CreateTaskItems("test.vb"),
};

AssertEx.Equal($"/sdkpath:{RuntimeEnvironment.GetRuntimeDirectory()} /optionstrict:custom /out:test.exe test.vb", vbc.GenerateResponseFileContents());
AssertEx.Equal($"/sdkpath:{RuntimeEnvironment.GetRuntimeDirectory()} @{RspFilePath} /optionstrict:custom /out:test.exe test.vb", vbc.GenerateResponseFileContents());
Copy link
Contributor

Choose a reason for hiding this comment

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

GenerateResponseFileContents

Consider covering "noconfig" behavior as well

}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79907")]
Expand All @@ -31,6 +34,6 @@ public void StdLib_DisableSdkPath()
DisableSdkPath = true,
};

AssertEx.Equal($"/sdkpath:{RuntimeEnvironment.GetRuntimeDirectory()} /optionstrict:custom /nosdkpath /out:test.exe test.vb", vbc.GenerateResponseFileContents());
AssertEx.Equal($"/sdkpath:{RuntimeEnvironment.GetRuntimeDirectory()} @{RspFilePath} /optionstrict:custom /nosdkpath /out:test.exe test.vb", vbc.GenerateResponseFileContents());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@
<BridgeCompilerArtifact Include="$(ArtifactsBinDir)Microsoft.Build.Tasks.CodeAnalysis.Sdk\$(Configuration)\net472\System.Memory.dll" />
<BridgeCompilerArtifact Include="$(ArtifactsBinDir)Microsoft.Build.Tasks.CodeAnalysis.Sdk\$(Configuration)\net472\System.Numerics.Vectors.dll" />
<BridgeCompilerArtifact Include="$(ArtifactsBinDir)Microsoft.Build.Tasks.CodeAnalysis.Sdk\$(Configuration)\net472\System.Runtime.CompilerServices.Unsafe.dll" />
<BridgeCompilerArtifact Include="$(ArtifactsBinDir)Microsoft.Build.Tasks.CodeAnalysis.Sdk\$(Configuration)\net472\csc.rsp" />
<BridgeCompilerArtifact Include="$(ArtifactsBinDir)Microsoft.Build.Tasks.CodeAnalysis.Sdk\$(Configuration)\net472\vbc.rsp" />
<BridgeCompilerResourceArtifact Include="$(ArtifactsBinDir)Microsoft.Build.Tasks.CodeAnalysis.Sdk\$(Configuration)\net472\**\Microsoft.Build.Tasks.CodeAnalysis.Sdk.resources.dll" />
</ItemGroup>
</Target>
Expand Down
Loading