Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 23, 2025

Fix csc/vbc misinterpretation of source files in root directory on Unix

Fixes dotnet/sdk#51282
Fixes #80865

Problem

On Unix systems, the C# and VB compilers misinterpret source files in the root directory as command-line switches because they start with /. For example:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="/Program.cs" />
  </ItemGroup>
</Project>

This fails with:

CSC : error CS2007: Unrecognized option: '/Program.cs'

This is particularly problematic for file-based apps where the source file lives in the root directory.

Solution

Transform Unix paths that would be misinterpreted as options to use the /./ prefix before passing them to the compiler. This uses the same heuristic as the compiler's CommandLineParser.TryParseOption: a path starting with / is treated as an option unless it contains another / after the first character.

The transformation is applied in ManagedCompiler.GetTransformedSourcesForCommandLine():

  • Only runs on Unix (detected via RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  • Transforms paths like /Program.cs/./Program.cs
  • Does NOT transform paths like /dir/File.cs (already safe due to second /)
  • Uses lazy initialization and string[] overload to minimize allocations
  • Returns null when no transformation is needed to avoid unnecessary allocations

Compatibility

The fix maintains full compatibility with:

  • CallerFilePath: The compiler normalizes /./path to /path when resolving file paths
  • SourceLink: Uses a separate JSON file for path mapping, unaffected by source file arguments
  • PathMap: The compiler resolves paths before applying path mappings
  • Debug info: File paths are normalized during PDB generation

Testing

Added tests for both C# and VB compilers covering:

  • Single source file in root directory
  • Multiple source files with mixed paths (some in root, some in subdirectories)
  • Paths with /dir/file.cs format (verified NOT transformed)
  • Windows behavior (verified no transformation, even for /test.cs)

All 377 existing MSBuild task tests continue to pass.

Comment Updates

Updated the comment in CommandLineParser.TryParseOption to reflect that this heuristic is no longer "temporary" and is now relied upon by the MSBuild tasks.

Original prompt

This section details on the original issue you should resolve

<issue_title>csc misinterprets a source file in root directory on Unix as a command-line option switch</issue_title>
<issue_description>```sh
docker run --rm -it mcr.microsoft.com/dotnet/sdk:10.0 bash

inside the docker container:

dotnet new console
sed -i 's|||' ConsoleApp1.csproj # the default globs don't work in root
dotnet build -bl


(I guess you could also create the project anywhere else, and just put the C# file to root folder and add the `<Compile Include="/Program.cs" />`.)

Observed:

CSC : error CS2007: Unrecognized option: '/Program.cs'


Motivating scenario: file-based apps: https://github.com/dotnet/sdk/issues/51282

Proposed solution: change the Csc/Vbc tasks so they pass such files as `/./Program.cs` (this workaround is mentioned in https://github.com/dotnet/roslyn/issues/401). Should also check what happens with source link, CallerMemberFilePath, etc. in that case.</issue_description>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

Fixes #80865


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Fix csc misinterpreting source file as command-line option Fix csc/vbc misinterpretation of source files in root directory on Unix Oct 23, 2025
Copilot AI requested a review from jjonescz October 23, 2025 15:46
Copilot finished work on behalf of jjonescz October 23, 2025 15:46
Copilot AI requested a review from jjonescz October 24, 2025 10:30
Copilot finished work on behalf of jjonescz October 24, 2025 10:30
@jjonescz jjonescz marked this pull request as ready for review October 24, 2025 12:48
@jjonescz jjonescz requested a review from a team as a code owner October 24, 2025 12:48
@jaredpar
Copy link
Member

The comment for this in TryParseOption is:

            // temporary heuristic to detect Unix-style rooted paths
            // pattern /goo/*  or  //* will not be treated as a compiler option
            //
            // TODO: consider introducing "/s:path" to disambiguate paths starting with /

Given this change, should we update the comment? Doesn't feel temporary anymore 😄

return null;

// On Windows, no transformation is needed
if (Path.DirectorySeparatorChar == '\\')
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (Path.DirectorySeparatorChar == '\\')
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))

Believe that works across the frameworks. If not existing code is fine.

Copy link
Member

Choose a reason for hiding this comment

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

@copilot apply this suggestion

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 7448e85. Changed to use RuntimeInformation.IsOSPlatform(OSPlatform.Windows).

Comment on lines +1134 to +1138
// Copy all items processed so far
for (int j = 0; j < i; j++)
{
transformedSources[j] = sources[j].ItemSpec;
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Copy all items processed so far
for (int j = 0; j < i; j++)
{
transformedSources[j] = sources[j].ItemSpec;
}
sources.AsSpan(0, j).CopyTo(transformedSources.AsSpan());

Copy link
Member

Choose a reason for hiding this comment

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

@copilot apply this suggestion

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The suggestion doesn't directly apply here since we're copying from ITaskItem[] to string[] (extracting ItemSpec). We need to iterate to extract the ItemSpec values, so Span.CopyTo can't be used directly.

}

[ConditionalFact(typeof(WindowsOnly)), WorkItem("https://github.com/dotnet/roslyn/issues/80865")]
public void SourceFilePathsOnWindows()
Copy link
Member

Choose a reason for hiding this comment

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

This test should be added to VbcTests as well.

Copy link
Member

Choose a reason for hiding this comment

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

@copilot add the test for VB

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 7448e85. Added SourceFilePathsOnWindows test for VB.

@RikkiGibson
Copy link
Member

The comment for this in TryParseOption is:

            // temporary heuristic to detect Unix-style rooted paths
            // pattern /goo/*  or  //* will not be treated as a compiler option
            //
            // TODO: consider introducing "/s:path" to disambiguate paths starting with /

Given this change, should we update the comment? Doesn't feel temporary anymore 😄

who wants to start a betting pool on the age of the comment? (no cheating).

Assert.Contains("/./App.vb", responseFileContents);
Assert.Contains("src/Test.vb", responseFileContents);
// /dir/File.vb should NOT be transformed (contains second '/')
Assert.Contains("/dir/File.vb", responseFileContents);
Copy link
Member

Choose a reason for hiding this comment

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

The other test had a leading space for this assertion, it seems reasonable to have it here also.

Suggested change
Assert.Contains("/dir/File.vb", responseFileContents);
Assert.Contains(" /dir/File.vb", responseFileContents);

Copy link
Member

Choose a reason for hiding this comment

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

@copilot add the space, and to other asserts where it makes sense too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 7448e85. Added leading spaces to assertions for src/Test.vb and /dir/File.vb, as well as corresponding C# test assertions.

@jaredpar
Copy link
Member

jaredpar commented Oct 24, 2025

who wants to start a betting pool on the age of the comment? (no cheating).

I'm guessing that it comes from the "Hello World" commit in Git.

Edit well at least I guessed the right person.

Assert.Contains("/./App.vb", responseFileContents);
Assert.Contains("src/Test.vb", responseFileContents);
// /dir/File.vb should NOT be transformed (contains second '/')
Assert.Contains("/dir/File.vb", responseFileContents);
Copy link
Member

Choose a reason for hiding this comment

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

@copilot add the space, and to other asserts where it makes sense too

}

[ConditionalFact(typeof(WindowsOnly)), WorkItem("https://github.com/dotnet/roslyn/issues/80865")]
public void SourceFilePathsOnWindows()
Copy link
Member

Choose a reason for hiding this comment

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

@copilot add the test for VB

Comment on lines +1134 to +1138
// Copy all items processed so far
for (int j = 0; j < i; j++)
{
transformedSources[j] = sources[j].ItemSpec;
}
Copy link
Member

Choose a reason for hiding this comment

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

@copilot apply this suggestion

return null;

// On Windows, no transformation is needed
if (Path.DirectorySeparatorChar == '\\')
Copy link
Member

Choose a reason for hiding this comment

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

@copilot apply this suggestion

for (int i = 0; i < sources.Length; i++)
{
var itemSpec = sources[i].ItemSpec;
// Check if this path needs transformation using the compiler's heuristic:
Copy link
Member

Choose a reason for hiding this comment

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

@copilot given the heuristic is not "temporary" anymore, can you update the comment in TryParseOption as jaredpar suggested?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 7448e85. Updated the comment in CommandLineParser.TryParseOption to reflect that this heuristic is no longer "temporary" and added a reference to issue #80865.

…t, improve assertions, update comment

Co-authored-by: jjonescz <[email protected]>
Copilot finished work on behalf of jjonescz October 27, 2025 11:30
Copilot AI requested a review from jjonescz October 27, 2025 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants