Skip to content
Merged
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
29 changes: 10 additions & 19 deletions src/Workspaces/MSBuild/Core/MSBuild/MSBuildWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,27 +357,18 @@ protected override void ApplyProjectChanges(ProjectChanges projectChanges)

if (_loader.ProjectFileExtensionRegistry.TryGetLanguageNameFromProjectPath(projectPath, DiagnosticReportingMode.Log, out var languageName, out var isFileBasedApp))
{
if (isFileBasedApp)
{
Reporter.Report(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure,
string.Format(WorkspaceMSBuildResources.Applying_updates_to_file_based_apps_is_not_supported_0, projectPath),
projectChanges.ProjectId));
return;
}

try
{
var preferredBuildHostKind = isFileBasedApp
? BuildHostProcessKind.NetCore
: BuildHostProcessManager.GetKindForProject(projectPath);
var (buildHost, _) = _applyChangesBuildHostProcessManager.GetBuildHostWithFallbackAsync(preferredBuildHostKind, projectPath, CancellationToken.None).Result;

if (isFileBasedApp)
{
var fileBasedProgramService = this.Services.GetRequiredService<IFileBasedProgramService>();
_applyChangesProjectFile = FileBasedProgramsProjectLoader.LoadFileBasedAppProjectAsync(
buildHost,
fileBasedProgramService,
projectPath,
(error) => Reporter.Report(new WorkspaceDiagnostic(WorkspaceDiagnosticKind.Failure, error)),
CancellationToken.None).Result;
}
else
{
_applyChangesProjectFile = buildHost.LoadProjectFileAsync(projectPath, languageName, CancellationToken.None).Result;
}
var buildHost = _applyChangesBuildHostProcessManager.GetBuildHostWithFallbackAsync(projectPath, CancellationToken.None).Result;
_applyChangesProjectFile = buildHost.LoadProjectFileAsync(projectPath, languageName, CancellationToken.None).Result;
}
catch (IOException exception)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Workspaces/MSBuild/Core/WorkspaceMSBuildResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,8 @@
<data name="The_build_host_could_not_be_found_at_0" xml:space="preserve">
<value>The build host could not be found at '{0}'</value>
</data>
<data name="Applying_updates_to_file_based_apps_is_not_supported_0" xml:space="preserve">
<value>Applying updates to file-based apps is not supported: {0}</value>
<comment>{0} is path to the file-based app</comment>
</data>
</root>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 38 additions & 9 deletions src/Workspaces/MSBuild/Test/NetCoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -957,12 +957,15 @@ public static class Util
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
[WorkItem("https://github.com/dotnet/roslyn/issues/84721")]
public async Task TestOpenProject_FileBasedApp_AddProjectReference()
{
var programSource = """
Util.M();
""";

CreateFiles(new FileSet(
("Program.cs", """
Util.M();
"""),
("Program.cs", programSource),
("Util.cs", """
#:property OutputType=Library
public static class Util
Expand All @@ -978,8 +981,14 @@ public static class Util
Assert.Equal(["Program"], workspace.CurrentSolution.Projects.Select(p => p.Name).Order());
Assert.Empty(programProject.ProjectReferences);

var diag = Assert.Single((await programProject.GetCompilationAsync()).GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error && d.GetMessage().Contains("Util")));
Assert.Equal("CS0103", diag.Id); // The name 'Util' does not exist in the current context
var expectedDiagnostics = new[]
{
// (1,1): error CS0103: The name 'Util' does not exist in the current context
// Util.M();
Diagnostic(103, "Util").WithArguments("Util").WithLocation(1, 1),
};

(await GetDiagnosticsAsync(programProject)).Verify(expectedDiagnostics);

var utilProject = await workspace.OpenProjectAsync(GetSolutionFileName("Util.cs"));

Expand All @@ -992,14 +1001,34 @@ public static class Util
var solution = programProject.AddProjectReference(new ProjectReference(utilProject.Id)).Solution;
Assert.True(workspace.TryApplyChanges(solution));

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.

nit: feels reasonable to also assert we did not write a '.cs.csproj' file to disk.

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.

I'm asserting that below with

Assert.Collection(Directory.EnumerateFileSystemEntries(SolutionDirectory.Path).Order(),
            entry => Assert.Equal(Path.Combine(SolutionDirectory.Path, ".packages"), entry),
            entry => Assert.Equal(Path.Combine(SolutionDirectory.Path, "Program.cs"), entry),
            entry => Assert.Equal(Path.Combine(SolutionDirectory.Path, "Util.cs"), entry));

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.

It's kinda strange to me that this isn't returning false and we're not throwing an exception, but I guess that's the pattern this code uses generally....strange. If you have to touch this PR for any other reason consider adding a comment here, otherwise let it be.

@jjonescz jjonescz Aug 5, 2026

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.

I agree this is weird but also seems by design. Per its doc comment (and implementation), TryApplyChanges

  • returns false only if (newSolution.SolutionStateContentVersion != oldSolution.SolutionStateContentVersion)
  • throws only if CanApplyChange(ApplyChangesKind feature) returns false, but that only takes the kind of the change, so we can't determine whether we are adding a project reference to a file-based app yet.


Comment thread
jjonescz marked this conversation as resolved.
Assert.Empty(workspace.Diagnostics);
Assert.Collection(workspace.Diagnostics,
d =>
{
Assert.Equal(WorkspaceDiagnosticKind.Failure, d.Kind);
Assert.Contains(string.Format(WorkspaceMSBuildResources.Applying_updates_to_file_based_apps_is_not_supported_0, Path.Combine(SolutionDirectory.Path, "Program.cs")), d.Message);
});

Assert.Equal(["Program", "Util"], workspace.CurrentSolution.Projects.Select(p => p.Name).Order());

var programText = await programProject.Documents.Single(d => d.Name == "Program.cs").GetTextAsync();
AssertEx.Equal(programSource, programText.ToString());

Assert.Collection(Directory.EnumerateFileSystemEntries(SolutionDirectory.Path).Order(),
entry => Assert.Equal(Path.Combine(SolutionDirectory.Path, ".packages"), entry),
entry => Assert.Equal(Path.Combine(SolutionDirectory.Path, "Program.cs"), entry),
entry => Assert.Equal(Path.Combine(SolutionDirectory.Path, "Util.cs"), entry));

programProject = workspace.CurrentSolution.Projects.Single(p => p.Name == "Program");
var projRef = Assert.Single(programProject.ProjectReferences);
Assert.Equal(projRef.ProjectId, workspace.CurrentSolution.Projects.Single(p => p.Name == "Util").Id);
Assert.Empty(programProject.ProjectReferences);

(await GetDiagnosticsAsync(programProject)).Verify(expectedDiagnostics);

Assert.Empty((await programProject.GetCompilationAsync()).GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error && d.GetMessage().Contains("Util")));
static async Task<IEnumerable<Diagnostic>> GetDiagnosticsAsync(Project project)
{
return (await project.GetCompilationAsync())
.GetDiagnostics()
.Where(d => d.Severity == DiagnosticSeverity.Error && d.GetMessage().Contains("Util"));
}
}

[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
Expand Down
Loading