dotnet/sdk

.Net7 dotnet watch regression: Failed to create MSBuildWorkspace for *.fsproj

En3Tho opened this issue ยท 17 comments

I'm running BlazorServer app which has a dependency on F# project. Previously with .Net6 there was no problem running applying HotReload and some of stuff was changing instantly (for example applying a css class to blazor markup).

With .Net 7 I'm seeing an error "Cannot_open_project_0_because_the_file_extension_1_is_not_associated_with_a_language" error because only csproj and vbproj are supported.

Now it does full rebuild even if only C# parts are changed. Before it worked fine.

To repro:
Create an empty blazor server project and reference an F# project with some data. Run dotnet watch. Change something trivial like css/markup.

Expected: hot reload works and page is updated immediately
Result: hot reload fails with this error and full rebuild is issued

While debugging dotnet watch in main I've noticed that no LanguageService was registered for F#. C#, VB and TypeScript are there, but F# is missing. I'm not sure how to register this thing.

Afaik, msbuildworkspaces never supported fsproj/shproj projects.

@baronfel @rainersigwald do you know what may have changed in watch?

We'd like to support F# in workspaces, is there anything which can be done?

No idea. @tmat is listed as a code owner for watch, any ideas?

tmat commented

I'm guessing it used to work before Hot Reload was implemented in dotnet watch. Can you try dotnet watch --no-hot-reload?

dotnet watch --no-hot-reload no longer displays that error but not suprisingly there is no hot reload and every edit results in a rebuild.

I'm guessing it used to work before Hot Reload was implemented in dotnet watch

I'm not sure I correctly understood. Do you mean in Net6 there was no hot-reload in dotnet watch? Did dotnet watch use some other mechanism to apply changes instantly?

Issue magically resolved itself after git clean -xdf . Now this error doesnt get shown and dotnet watch is working as usual. Meaning change C# files -> hot reload applies when possible, change F# file -> rebuild

Oh, no. I'm mistaken. Sorry. It applied a change only once for a static file and then failed to apply C# changes. Issue still persists

One thing I can say is that while debugging 6.0 dotnet-watch I noticed that fsproj references somehow never get added to C# projects. In "main" they do.

I don't know if it's a bug or not but it made 6.0 work

I've trried 2 things:
1: simply ignore msbuild error
2. remove unresolved references from solution object to simulate net6 behavior

image

Both cases result in:

dotnet watch โŒš Caught top-level exception from hot reload: System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at System.Collections.Immutable.ImmutableArray`1.get_Item(Int32 index)
   at Microsoft.CodeAnalysis.NodeStateTable`1.Single()
   at Microsoft.CodeAnalysis.CombineNode`2.UpdateStateTable(Builder graphState, NodeStateTable`1 previousTable, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.DriverStateTable.Builder.GetLatestStateTableForNode[T](IIncrementalGeneratorNode`1 source)
   at Microsoft.CodeAnalysis.TransformNode`2.UpdateStateTable(Builder builder, NodeStateTable`1 previousTable, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.DriverStateTable.Builder.GetLatestStateTableForNode[T](IIncrementalGeneratorNode`1 source)
   at Microsoft.CodeAnalysis.CombineNode`2.UpdateStateTable(Builder graphState, NodeStateTable`1 previousTable, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.DriverStateTable.Builder.GetLatestStateTableForNode[T](IIncrementalGeneratorNode`1 source)
   at Microsoft.CodeAnalysis.CombineNode`2.UpdateStateTable(Builder graphState, NodeStateTable`1 previousTable, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.DriverStateTable.Builder.GetLatestStateTableForNode[T](IIncrementalGeneratorNode`1 source)
   at Microsoft.CodeAnalysis.TransformNode`2.UpdateStateTable(Builder builder, NodeStateTable`1 previousTable, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.DriverStateTable.Builder.GetLatestStateTableForNode[T](IIncrementalGeneratorNode`1 source)
   at Microsoft.CodeAnalysis.SourceOutputNode`1.UpdateStateTable(Builder graphState, NodeStateTable`1 previousTable, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.DriverStateTable.Builder.GetLatestStateTableForNode[T](IIncrementalGeneratorNode`1 source)
   at Microsoft.CodeAnalysis.SourceOutputNode`1.AppendOutputs(IncrementalExecutionContext context, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.GeneratorDriver.UpdateOutputs(ImmutableArray`1 outputNodes, IncrementalGeneratorOutputKind outputKind, Builder generatorRunStateBuilder, CancellationToken cancellationToken, Bui
lder driverStateBuilder)
   at Microsoft.CodeAnalysis.GeneratorDriver.RunGeneratorsCore(Compilation compilation, DiagnosticBag diagnosticsBag, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.GeneratorDriver.RunGenerators(Compilation compilation, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.FinalizeCompilationAsync(SolutionState solution, Compilation compilationWithoutGenerators, CompilationTrackerGeneratorInfo generatorInfo, Compil
ation compilationWithStaleGeneratedTrees, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.BuildFinalStateFromInProgressStateAsync(SolutionState solution, InProgressState state, Compilation inProgressCompilation, CancellationToken canc
ellationToken)
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.BuildCompilationInfoAsync(SolutionState solution, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.GetOrBuildCompilationInfoAsync(SolutionState solution, Boolean lockGate, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.GetSourceGeneratedDocumentStatesAsync(SolutionState solution, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.EditAndContinue.EditSession.PopulateChangedAndAddedDocumentsAsync(Project oldProject, Project newProject, ArrayBuilder`1 changedOrAddedDocuments, CancellationToken cancellationT
oken)
   at Microsoft.CodeAnalysis.EditAndContinue.EditSession.EmitSolutionUpdateAsync(Solution solution, ActiveStatementSpanProvider solutionActiveStatementSpanProvider, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.EditAndContinue.DebuggingSession.EmitSolutionUpdateAsync(Solution solution, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.ExternalAccess.Watch.Api.WatchHotReloadService.EmitSolutionUpdateAsync(Solution solution, CancellationToken cancellationToken)
   at Microsoft.DotNet.Watcher.Tools.CompilationHandler.TryHandleFileChange(DotNetWatchContext context, FileItem[] files, CancellationToken cancellationToken) in G:\source\repos\dotnet\sdk\src\BuiltInTools\
dotnet-watch\HotReload\CompilationHandler.cs:line 125
   at Microsoft.DotNet.Watcher.Tools.HotReload.TryHandleFileChange(DotNetWatchContext context, FileItem[] files, CancellationToken cancellationToken) in G:\source\repos\dotnet\sdk\src\BuiltInTools\dotnet-wa
tch\HotReload\HotReload.cs:line 46
   at Microsoft.DotNet.Watcher.HotReloadDotNetWatcher.WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken) in G:\source\repos\dotnet\sdk\src\BuiltInTools\dotnet-watch\HotReloadDotNetW
atcher.cs:line 184

This is happening in
image

Only table2 here is empty.

I guess this is sorta similar to dotnet/roslyn#54470

What can I do next? Try to manually build CodeServices and tinker with it?

@tmat @chsienki Any ideas why this might be happening?

The IndexOutOfRangeException should now be fixed by dotnet/roslyn#65223

@chsienki Thanks. Lines from that PR are missing in decompiled code. I guess I will have to wait until sdk's dependencies go up a little. This gives me some hope bringing back proper behavior.

@chsienki Just in case I can confirm that after updaing sdk's dependencies to 4.5.0-1.22571.11 issue was resolved. Thanks.

@tmat I can confirm that if I simply ignore F# related errors (in this case file type not assocciated with language) watch works. In Net6 it simply didn't report this error for some reason. I don't know why.

The problem is how to separate this error and other errors that could actually fail workspace loading.
There is no real error code, just a string that is already translated at this point. Maybe there are better ways. I don't know.

As a very crude workaround I can only propose something like this:

var workspace = MSBuildWorkspace.Create();
var watchSkipTag = "DotnetWatchIgnoreError";
 // assocciate "fsproj" files with a special tag that would be checked later. 
// This tag can be added via msbuild properties or we could just bake in fsproj here.
workspace.AssociateFileExtensionWithLanguage("fsproj", watchSkipTag);

workspace.WorkspaceFailed += (_sender, diag) =>
{
    if (diag.Diagnostic.Kind == WorkspaceDiagnosticKind.Warning)
    {
        reporter.Verbose($"MSBuildWorkspace warning: {diag.Diagnostic}");
    }
    else
    {
        // simply ignore messages related to this tag as it's most probably Language is not supported error.
        if (!diag.Diagnostic.Message.Contains(watchSkipTag)) 
        {
            taskCompletionSource.TrySetException(new ApplicationException($"Failed to create MSBuildWorkspace: {diag.Diagnostic}"));
        }
    }
};

@KathleenDollard Hey ! Sorry to ping you but it's .Net 8 soon already and this thing is still broken. Maybe you can help and somehow give this a bit more priority? Simply referencing F# project breaks dotnet watch hot reload mechanism. Even if C# only stuff is edited. It started with .Net 7 and it's been almost a year aleady.

The context: I have a Blazor app that is C# but is using some internal stuff written if F#. Running dotnet watch and simply chaning markup results in a full rebuild. In .Net 6 there was no problem and changes were applied instantly (but it seems to me that it was unintentional)

I've posted a quirky workaround and yes, I can continue to use locally built version but it just seems to me that it is not really cool way for the broader audience.

@tmat can your team take a look? Is hot reload simply incompatible with cross-language references?

@baronfel I may be wrong, but It seems like there is no proper workspace implemented for F#. That's why this error is appearing. The quirky way is to simply ignore F#-related errors. But maybe some kind of workspace shim can be used instead? Something like generic workspace that is suitable for all other projects (not csproj and not vbproj)?

If I do this workaround then everything is back to normal: C# supports hot reload and F# requires restart every time. And this is okay considering there is no F# hot reload support anyway.

@En3Tho Where should this workaround be put? I also wish for this issue to be resolved, as I am using F# projects along with Blazor.

@mrakgr Inside the code of dotnet watch itself. This is the most annoying part. You have to donwload sdk repo and tinker with it manually.