From a97c4e04dac78c72a2efc3e6caac393801d3db75 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Tue, 16 Apr 2024 16:11:43 -0700 Subject: [PATCH] improve Prebuild parallelizability --- .../CSharpProjectKernelTests.cs | 1 + .../CollectionDefinitionForPrebuildFixture.cs | 14 + .../PrebuildFixture.cs | 28 ++ .../PrebuildTests.cs | 14 +- ...oslynWorkspaceServerConsoleProjectTests.cs | Bin 46522 -> 64582 bytes ...rverTestsConsoleProjectDiagnosticsTests.cs | 8 +- ...verTestsConsoleProjectIntellisenseTests.cs | 4 +- .../RoslynWorkspaceServerTestsCore.cs | 21 -- .../WorkspaceServerTests.cs | 348 ------------------ .../WorkspaceServerTestsCore.cs | 33 +- ...ynWorkspaceUtilities.cs => BuildResult.cs} | 133 +++---- .../Build/Prebuild.cs | 165 +++++---- .../Build/PrebuildExtensions.cs | 2 +- .../Build/PrebuildInitializer.cs | 1 - ...uildProjectData.cs => ProjectBuildInfo.cs} | 4 +- .../BuildDataProjectItem.cs | 19 - .../BuildDataResults.cs | 25 -- .../CodeMergeTransformer.cs | 2 +- ...up.DotNet.Interactive.CSharpProject.csproj | 71 ---- .../Servers/Roslyn/CompilationUtility.cs | 5 - .../Servers/Roslyn/RoslynWorkspaceServer.cs | 2 +- .../Utility/CommandLineInvocationException.cs | 11 +- ...dotnet-interactive.Tests.v3.ncrunchproject | 2 +- 23 files changed, 242 insertions(+), 671 deletions(-) create mode 100644 src/Microsoft.DotNet.Interactive.CSharpProject.Tests/CollectionDefinitionForPrebuildFixture.cs create mode 100644 src/Microsoft.DotNet.Interactive.CSharpProject.Tests/PrebuildFixture.cs delete mode 100644 src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsCore.cs delete mode 100644 src/Microsoft.DotNet.Interactive.CSharpProject.Tests/WorkspaceServerTests.cs rename src/Microsoft.DotNet.Interactive.CSharpProject/Build/{RoslynWorkspaceUtilities/RoslynWorkspaceUtilities.cs => BuildResult.cs} (62%) rename src/Microsoft.DotNet.Interactive.CSharpProject/Build/{RoslynWorkspaceUtilities/BuildProjectData.cs => ProjectBuildInfo.cs} (89%) delete mode 100644 src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildDataProjectItem.cs delete mode 100644 src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildDataResults.cs delete mode 100644 src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft - Backup.DotNet.Interactive.CSharpProject.csproj diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/CSharpProjectKernelTests.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/CSharpProjectKernelTests.cs index c8cc0ceb24..d5fe30402d 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/CSharpProjectKernelTests.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/CSharpProjectKernelTests.cs @@ -18,6 +18,7 @@ namespace Microsoft.DotNet.Interactive.CSharpProject.Tests; +[Collection(nameof(PrebuildFixture))] [LogToPocketLogger] public class CSharpProjectKernelTests { diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/CollectionDefinitionForPrebuildFixture.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/CollectionDefinitionForPrebuildFixture.cs new file mode 100644 index 0000000000..814a71d98d --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/CollectionDefinitionForPrebuildFixture.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; + +namespace Microsoft.DotNet.Interactive.CSharpProject.Tests; + +[CollectionDefinition(nameof(PrebuildFixture), DisableParallelization = true)] +public class CollectionDefinitionForPrebuildFixture : ICollectionFixture +{ + // This class has no code, and is never created. Its purpose is simply + // to be the place to apply [CollectionDefinition] and all the + // ICollectionFixture<> interfaces. +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/PrebuildFixture.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/PrebuildFixture.cs new file mode 100644 index 0000000000..cf6c404745 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/PrebuildFixture.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; +using Microsoft.DotNet.Interactive.CSharpProject.Build; +using Xunit; + +namespace Microsoft.DotNet.Interactive.CSharpProject.Tests; + +public class PrebuildFixture : IAsyncLifetime +{ + public async Task InitializeAsync() + { + var consolePrebuild = await Prebuild.GetOrCreateConsolePrebuildAsync(true); + await consolePrebuild.EnsureReadyAsync(); + + consolePrebuild = await Prebuild.GetOrCreateConsolePrebuildAsync(false); + await consolePrebuild.EnsureReadyAsync(); + Prebuild = consolePrebuild; + } + + public Prebuild Prebuild { get; private set; } + + public Task DisposeAsync() + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/PrebuildTests.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/PrebuildTests.cs index 52159ead5f..a1b4a2480a 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/PrebuildTests.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/PrebuildTests.cs @@ -35,8 +35,8 @@ public async Task A_prebuild_is_not_initialized_more_than_once() var prebuild = PrebuildUtilities.CreateEmptyBuildablePrebuild(initializer: initializer); - await prebuild.GetOrCreateWorkspaceAsync(); - await prebuild.GetOrCreateWorkspaceAsync(); + await prebuild.CreateWorkspaceAsync(); + await prebuild.CreateWorkspaceAsync(); initializer.InitializeCount.Should().Be(1); } @@ -57,8 +57,8 @@ public async Task Prebuild_after_create_actions_are_not_run_more_than_once() var prebuild = PrebuildUtilities.CreateEmptyBuildablePrebuild(initializer: initializer); - await prebuild.GetOrCreateWorkspaceAsync(); - await prebuild.GetOrCreateWorkspaceAsync(); + await prebuild.CreateWorkspaceAsync(); + await prebuild.CreateWorkspaceAsync(); afterCreateCallCount.Should().Be(1); } @@ -72,11 +72,11 @@ public async Task A_prebuild_copy_is_not_reinitialized_if_the_source_was_already var original = PrebuildUtilities.CreateEmptyBuildablePrebuild(initializer: initializer); - await original.GetOrCreateWorkspaceAsync(); + await original.CreateWorkspaceAsync(); var copy = await original.CreateBuildableCopy(); - await copy.GetOrCreateWorkspaceAsync(); + await copy.CreateWorkspaceAsync(); initializer.InitializeCount.Should().Be(1); } @@ -86,7 +86,7 @@ public async Task When_prebuild_contains_simple_console_app_then_entry_point_dll { var prebuild = PrebuildUtilities.CreateEmptyBuildablePrebuild(initializer: new PrebuildInitializer("console", "empty")); - await prebuild.GetOrCreateWorkspaceAsync(); + await prebuild.CreateWorkspaceAsync(); prebuild.EntryPointAssemblyPath.Exists.Should().BeTrue(); diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerConsoleProjectTests.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerConsoleProjectTests.cs index bee13d126646a3eae73f3a09ed2427822a690cbd..51bd2fafd30bd312b99b1de76e4920ebbda84fbd 100644 GIT binary patch literal 64582 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 C*OjRN delta 1107 zcmdT?O-~b16g{27q67k@EreLjvyyb$Kx1O!Li~s|0ZoibksTzCFu){arkQ?#3+x|I z61*Fin)m~n3@d+t8$W)58;!2quw==3&z5Rq0y|CBzp1l)tJ1=C@Cj0 z!?&Q2#T86(y^5^NC(bvonX>{OV}oK8))*bsC^|Iz9BcWy8n$^Gpu)etLOCqw07F9t-*?igR&7DxXs_)}Qiv94Em&-0z|2 zjMAeATIw8Q?Sw2Fd1l_gO-7&jTkulzr{lur*X8(a zPZw8gt9VGnA1HadoV?_sn|GV`^2t%T&B;5oN86njCq1tFPyVBSX*MRLwKF;Nzv>^I YPfKZBot6H}r|_SX&0_{b7{CsW03|2sJpcdz diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsConsoleProjectDiagnosticsTests.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsConsoleProjectDiagnosticsTests.cs index 53b0be44c1..706f0d5de4 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsConsoleProjectDiagnosticsTests.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsConsoleProjectDiagnosticsTests.cs @@ -8,11 +8,9 @@ namespace Microsoft.DotNet.Interactive.CSharpProject.Tests; -public class RoslynWorkspaceServerTestsConsoleProjectDiagnosticsTests : RoslynWorkspaceServerTestsCore +public class RoslynWorkspaceServerTestsConsoleProjectDiagnosticsTests : WorkspaceServerTestsCore { - - - public RoslynWorkspaceServerTestsConsoleProjectDiagnosticsTests(ITestOutputHelper output) : base(output) + public RoslynWorkspaceServerTestsConsoleProjectDiagnosticsTests(PrebuildFixture prebuildFixture, ITestOutputHelper output) : base(prebuildFixture, output) { } @@ -154,6 +152,4 @@ public static IEnumerable Fibonacci() result.Diagnostics.Should().NotBeNullOrEmpty(); result.Diagnostics.Should().Contain(diagnostics => diagnostics.Message == "generators/FibonacciGenerator.cs(14,17): error CS0246: The type or namespace name \'adddd\' could not be found (are you missing a using directive or an assembly reference?)"); } - - } \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsConsoleProjectIntellisenseTests.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsConsoleProjectIntellisenseTests.cs index beb417e1e1..46217886b6 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsConsoleProjectIntellisenseTests.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsConsoleProjectIntellisenseTests.cs @@ -10,9 +10,9 @@ namespace Microsoft.DotNet.Interactive.CSharpProject.Tests; -public class RoslynWorkspaceServerTestsConsoleProjectIntellisenseTests : RoslynWorkspaceServerTestsCore +public class RoslynWorkspaceServerTestsConsoleProjectIntellisenseTests : WorkspaceServerTestsCore { - public RoslynWorkspaceServerTestsConsoleProjectIntellisenseTests(ITestOutputHelper output) : base(output) + public RoslynWorkspaceServerTestsConsoleProjectIntellisenseTests(PrebuildFixture prebuildFixture, ITestOutputHelper output) : base(prebuildFixture, output) { } diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsCore.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsCore.cs deleted file mode 100644 index da625870cf..0000000000 --- a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/RoslynWorkspaceServerTestsCore.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.DotNet.Interactive.CSharpProject.Build; -using Microsoft.DotNet.Interactive.CSharpProject.Servers.Roslyn; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Interactive.CSharpProject.Tests; - -public abstract class RoslynWorkspaceServerTestsCore : WorkspaceServerTestsCore -{ - protected RoslynWorkspaceServerTestsCore(ITestOutputHelper output) : base(output) - { - } - - protected override ILanguageService GetLanguageService() => new RoslynWorkspaceServer(PrebuildFinder.Create(() => Prebuild.GetOrCreateConsolePrebuildAsync(false))); - - protected override ICodeCompiler GetCodeCompiler() => new RoslynWorkspaceServer(PrebuildFinder.Create(() => Prebuild.GetOrCreateConsolePrebuildAsync(false))); - - protected override ICodeRunner GetCodeRunner() => new RoslynWorkspaceServer(PrebuildFinder.Create(() => Prebuild.GetOrCreateConsolePrebuildAsync(false))); -} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/WorkspaceServerTests.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/WorkspaceServerTests.cs deleted file mode 100644 index deaeef9bd3..0000000000 --- a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/WorkspaceServerTests.cs +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.CodeAnalysis; -using Pocket; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Interactive.CSharpProject.Tests; - -public abstract class WorkspaceServerTests : WorkspaceServerTestsCore -{ - protected abstract Workspace CreateWorkspaceWithMainContaining(string text); - - protected WorkspaceServerTests(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public async Task Diagnostic_logs_do_not_show_up_in_captured_console_output() - { - using (LogEvents.Subscribe(e => Console.WriteLine(e.ToLogString()))) - { - var server = GetCodeRunner(); - - var result = await server.RunAsync( - new WorkspaceRequest( - CreateWorkspaceWithMainContaining( - "Console.WriteLine(\"hi!\");")) - ); - - result.Output - .Should() - .BeEquivalentTo( - new[] { "hi!", "" }, - options => options.WithStrictOrdering()); - } - } - - [Fact] - public async Task Response_indicates_when_compile_is_successful_and_signature_is_like_a_console_app() - { - var server = GetCodeRunner(); - - var workspace = Workspace.FromSource(@" -using System; - -public static class Hello -{ - public static void Main() - { - } -} -", workspaceType: "console"); - - var result = await server.RunAsync(new WorkspaceRequest(workspace)); - - result.ShouldSucceedWithNoOutput(); - } - - [Fact] - public async Task Response_shows_program_output_when_compile_is_successful_and_signature_is_like_a_console_app() - { - var output = nameof(Response_shows_program_output_when_compile_is_successful_and_signature_is_like_a_console_app); - - var server = GetCodeRunner(); - - var workspace = Workspace.FromSource($@" -using System; - -public static class Hello -{{ - public static void Main() - {{ - Console.WriteLine(""{output}""); - }} -}}", workspaceType: "console"); - - - var result = await server.RunAsync(new WorkspaceRequest(workspace)); - - result.ShouldSucceedWithOutput(output); - } - - [Fact] - public async Task Response_shows_program_output_when_compile_is_successful_and_signature_is_a_fragment_containing_console_output() - { - var server = GetCodeRunner(); - - var request = CreateWorkspaceWithMainContaining(@" -var person = new { Name = ""Jeff"", Age = 20 }; -var s = $""{person.Name} is {person.Age} year(s) old""; -Console.Write(s);"); - - - var result = await server.RunAsync(new WorkspaceRequest(request)); - - result.ShouldSucceedWithOutput("Jeff is 20 year(s) old"); - } - - [Fact] - public async Task When_compile_is_unsuccessful_then_no_exceptions_are_shown() - { - var server = GetCodeRunner(); - - var request = CreateWorkspaceWithMainContaining(@" -Console.WriteLine(banana);"); - - var result = await server.RunAsync(new WorkspaceRequest(request)); - result.Succeeded.Should().BeFalse(); - result.Exception.Should().BeNull(); - } - - [Fact] - public async Task When_compile_is_unsuccessful_then_diagnostics_are_displayed_in_output() - { - var server = GetCodeRunner(); - - var request = CreateWorkspaceWithMainContaining(@" -Console.WriteLine(banana);"); - - var result = await server.RunAsync(new WorkspaceRequest(request)); - result.Succeeded.Should().BeFalse(); - result.Output - .ShouldMatch( - "*(2,19): error CS0103: The name \'banana\' does not exist in the current context"); - } - - [Fact] - public async Task Multi_line_console_output_is_captured_correctly() - { - var server = GetCodeRunner(); - - var request = CreateWorkspaceWithMainContaining(@" -Console.WriteLine(1); -Console.WriteLine(2); -Console.WriteLine(3); -Console.WriteLine(4);"); - - - var result = await server.RunAsync(new WorkspaceRequest(request)); - - result.ShouldSucceedWithOutput("1", "2", "3", "4", ""); - } - - [Fact] - public async Task Whitespace_is_preserved_in_multi_line_output() - { - var server = GetCodeRunner(); - - var request = CreateWorkspaceWithMainContaining(@" -Console.WriteLine(); -Console.WriteLine(1); -Console.WriteLine(); -Console.WriteLine(); -Console.WriteLine(2);"); - - var result = await server.RunAsync(new WorkspaceRequest(request)); - - result.ShouldSucceedWithOutput("", "1", "", "", "2", ""); - } - - [Fact(Skip = "Might be causing crashes on Linux")] - public async Task Multi_line_console_output_is_captured_correctly_when_an_exception_is_thrown() - { - var server = GetCodeRunner(); - - var request = CreateWorkspaceWithMainContaining($@" -Console.WriteLine(1); -Console.WriteLine(2); -throw new Exception(""oops! from {nameof(Multi_line_console_output_is_captured_correctly_when_an_exception_is_thrown)}""); -Console.WriteLine(3); -Console.WriteLine(4);"); - - var result = await server.RunAsync(new WorkspaceRequest(request)); - - result.ShouldSucceedWithExceptionContaining( - $"System.Exception: oops! from {nameof(Multi_line_console_output_is_captured_correctly_when_an_exception_is_thrown)}", - output: new[] { "1", "2" }); - } - - [Fact(Skip = "Might be causing crashes on Linux")] - public async Task When_the_users_code_throws_on_first_line_then_it_is_returned_as_an_exception_property() - { - var server = GetCodeRunner(); - - var request = CreateWorkspaceWithMainContaining($@"throw new Exception(""oops! from {nameof(When_the_users_code_throws_on_first_line_then_it_is_returned_as_an_exception_property)}"");"); - - var result = await server.RunAsync(new WorkspaceRequest(request)); - - result.ShouldSucceedWithExceptionContaining($"System.Exception: oops! from {nameof(When_the_users_code_throws_on_first_line_then_it_is_returned_as_an_exception_property)}"); - } - - [Fact(Skip = "Might be causing crashes on Linux")] - public async Task When_the_users_code_throws_on_subsequent_line_then_it_is_returned_as_an_exception_property() - { - var server = GetCodeRunner(); - - var request = CreateWorkspaceWithMainContaining($@" -throw new Exception(""oops! from {nameof(When_the_users_code_throws_on_subsequent_line_then_it_is_returned_as_an_exception_property)}"");"); - - var result = await server.RunAsync(new WorkspaceRequest(request)); - - result.ShouldSucceedWithExceptionContaining($"System.Exception: oops! from {nameof(When_the_users_code_throws_on_subsequent_line_then_it_is_returned_as_an_exception_property)}"); - } - - [Fact] - public async Task When_a_public_void_Main_with_no_parameters_is_present_it_is_invoked() - { - var server = GetCodeRunner(); - - var workspace = Workspace.FromSource(@" -using System; - -public static class Hello -{ - public static void Main() - { - Console.WriteLine(""Hello there!""); - } -}", workspaceType: "console"); - - var result = await server.RunAsync(new WorkspaceRequest(workspace)); - - result.ShouldSucceedWithOutput("Hello there!"); - } - - [Fact] - public async Task When_a_public_void_Main_with_parameters_is_present_it_is_invoked() - { - var server = GetCodeRunner(); - - var workspace = Workspace.FromSource(@" -using System; - -public static class Hello -{ - public static void Main(params string[] args) - { - Console.WriteLine(""Hello there!""); - } -}", workspaceType: "console"); - - var result = await server.RunAsync(new WorkspaceRequest(workspace)); - - result.ShouldSucceedWithOutput("Hello there!"); - } - - [Fact] - public async Task When_an_internal_void_Main_with_no_parameters_is_present_it_is_invoked() - { - var server = GetCodeRunner(); - - var workspace = Workspace.FromSource(@" -using System; - -public static class Hello -{ - static void Main() - { - Console.WriteLine(""Hello there!""); - } -}", workspaceType:"console"); - - var result = await server.RunAsync(new WorkspaceRequest(workspace)); - - result.ShouldSucceedWithOutput("Hello there!"); - } - - [Fact] - public async Task When_an_internal_void_Main_with_parameters_is_present_it_is_invoked() - { - var server = GetCodeRunner(); - - var workspace = Workspace.FromSource(@" -using System; - -public static class Hello -{ - static void Main(string[] args) - { - Console.WriteLine(""Hello there!""); - } -}", workspaceType: "console"); - - - var result = await server.RunAsync(new WorkspaceRequest(workspace)); - - result.ShouldSucceedWithOutput("Hello there!"); - } - - - [Fact] - public async Task Response_shows_warnings_with_successful_compilation() - { - var output = nameof(Response_shows_warnings_with_successful_compilation); - - var server = GetCodeRunner(); - - var workspace = CreateWorkspaceWithMainContaining($@" -using System; -using System; - -public static class Hello -{{ - public static void Main() - {{ - var a = 0; - Console.WriteLine(""{output}""); - }} -}}"); - - var result = await server.RunAsync(new WorkspaceRequest(workspace)); - - var diagnostics = result.GetFeature(); - - diagnostics.Should().Contain(d => d.Severity == DiagnosticSeverity.Warning); - } - - [Fact] - public async Task Response_shows_warnings_when_compilation_fails() - { - var output = nameof(Response_shows_warnings_when_compilation_fails); - - var server = GetCodeRunner(); - - var workspace = CreateWorkspaceWithMainContaining($@" -using System; - -public static class Hello -{{ - public static void Main() - {{ - var a = 0; - Console.WriteLine(""{output}"") - }} -}}"); - - var result = await server.RunAsync(new WorkspaceRequest(workspace)); - - var diagnostics = result.GetFeature(); - - diagnostics.Should().Contain(d => d.Severity == DiagnosticSeverity.Warning); - } -} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/WorkspaceServerTestsCore.cs b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/WorkspaceServerTestsCore.cs index 3fb2d27ce4..71fe39a203 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/WorkspaceServerTestsCore.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/WorkspaceServerTestsCore.cs @@ -3,35 +3,40 @@ using System; using System.Threading.Tasks; +using Microsoft.DotNet.Interactive.CSharpProject.Build; +using Microsoft.DotNet.Interactive.CSharpProject.Servers.Roslyn; using Pocket; -using Serilog; +using Pocket.For.Xunit; +using Xunit; using Xunit.Abstractions; namespace Microsoft.DotNet.Interactive.CSharpProject.Tests; +[Collection(nameof(PrebuildFixture))] +[LogToPocketLogger(FileNameEnvironmentVariable = "POCKETLOGGER_LOG_PATH")] public abstract class WorkspaceServerTestsCore : IDisposable { + private readonly PrebuildFixture _prebuildFixture; private readonly CompositeDisposable _disposables = new(); - static WorkspaceServerTestsCore() - { - TaskScheduler.UnobservedTaskException += (sender, args) => - { - Log.Warning($"{nameof(TaskScheduler.UnobservedTaskException)}", args.Exception); - args.SetObserved(); - }; - } - - protected WorkspaceServerTestsCore(ITestOutputHelper output) + protected WorkspaceServerTestsCore( + PrebuildFixture prebuildFixture, + ITestOutputHelper output) { + _prebuildFixture = prebuildFixture; _disposables.Add(output.SubscribeToPocketLogger()); } public void Dispose() => _disposables.Dispose(); - protected abstract ILanguageService GetLanguageService(); + protected ILanguageService GetLanguageService() => CreateRoslynWorkspaceServer(); - protected abstract ICodeCompiler GetCodeCompiler(); + protected ICodeCompiler GetCodeCompiler() => CreateRoslynWorkspaceServer(); - protected abstract ICodeRunner GetCodeRunner(); + protected ICodeRunner GetCodeRunner() => CreateRoslynWorkspaceServer(); + + private RoslynWorkspaceServer CreateRoslynWorkspaceServer() + { + return new RoslynWorkspaceServer(PrebuildFinder.Create(() => Task.FromResult(_prebuildFixture.Prebuild))); + } } \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/RoslynWorkspaceUtilities.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/BuildResult.cs similarity index 62% rename from src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/RoslynWorkspaceUtilities.cs rename to src/Microsoft.DotNet.Interactive.CSharpProject/Build/BuildResult.cs index 45dca5b242..638f13c4d1 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/RoslynWorkspaceUtilities.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/BuildResult.cs @@ -11,41 +11,47 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; -using Microsoft.DotNet.Interactive.CSharpProject.RoslynWorkspaceUtilities; -namespace Microsoft.DotNet.Interactive.CSharpProject.Build.RoslynWorkspaceUtilities; +namespace Microsoft.DotNet.Interactive.CSharpProject.Build; -internal static class RoslynWorkspaceUtilities +internal class BuildResult { - internal static BuildDataResults GetResultsFromCacheFile(string cacheFilePath) + internal ProjectBuildInfo ProjectBuildInfo { get; init; } + + internal bool Succeeded { get; init; } + + internal string CacheFilePath { get; init; } + + internal CSharpParseOptions CSharpParseOptions { get; init; } + + internal string ProjectFilePath { get; init; } + + internal static BuildResult FromCacheFile(string cacheFilePath) { if (!File.Exists(cacheFilePath)) { return null; } - string fileContent = File.ReadAllText(cacheFilePath); + var fileContent = File.ReadAllText(cacheFilePath); PopulateBuildProjectData(fileContent, out var buildProjectData); - var workspace = GetWorkspace(buildProjectData); - var cSharpParseOptions = CreateCSharpParseOptions(buildProjectData); - return new BuildDataResults + return new BuildResult { - BuildProjectData = buildProjectData, + ProjectBuildInfo = buildProjectData, ProjectFilePath = buildProjectData.ProjectFilePath, CacheFilePath = cacheFilePath, - Workspace = workspace, Succeeded = true, CSharpParseOptions = cSharpParseOptions }; } - internal static void PopulateBuildProjectData(string fileContent, out BuildProjectData buildProjectData) + internal static void PopulateBuildProjectData(string fileContent, out ProjectBuildInfo projectBuildInfo) { - buildProjectData = new BuildProjectData(); + projectBuildInfo = new ProjectBuildInfo(); string[] lines = fileContent.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); @@ -68,26 +74,26 @@ internal static void PopulateBuildProjectData(string fileContent, out BuildProje case "ProjectGuid": if (Guid.TryParse(value, out var projectGuid)) { - buildProjectData.ProjectGuid = projectGuid; + projectBuildInfo.ProjectGuid = projectGuid; } else { // Net Core projects do not provide a project guid, so lets create one. // Project guid is not relevant for build. - buildProjectData.ProjectGuid = Guid.NewGuid(); + projectBuildInfo.ProjectGuid = Guid.NewGuid(); } break; case "ProjectReferences": projectReferencesList.Add(value); break; case "ProjectFilePath": - buildProjectData.ProjectFilePath = value; + projectBuildInfo.ProjectFilePath = value; break; case "LanguageName": - buildProjectData.LanguageName = value; + projectBuildInfo.LanguageName = value; break; case "PropertyTargetPath": - buildProjectData.TargetPath = value; + projectBuildInfo.TargetPath = value; break; case "SourceFiles": sourceFilesList.Add(value); @@ -102,10 +108,10 @@ internal static void PopulateBuildProjectData(string fileContent, out BuildProje preprocessorSymbolsList.Add(value); break; case "PropertyLangVersion": - buildProjectData.LangVersion = value; + projectBuildInfo.LangVersion = value; break; case "PropertyOutputType": - buildProjectData.OutputType = value; + projectBuildInfo.OutputType = value; break; default: break; @@ -113,31 +119,23 @@ internal static void PopulateBuildProjectData(string fileContent, out BuildProje } } - buildProjectData.References = referencesList.ToArray(); - buildProjectData.ProjectReferences = projectReferencesList.ToArray(); - buildProjectData.AnalyzerReferences = analyzerReferencesList.ToArray(); - buildProjectData.PreprocessorSymbols = preprocessorSymbolsList.ToArray(); - buildProjectData.SourceFiles = sourceFilesList.ToArray(); + projectBuildInfo.References = referencesList.ToArray(); + projectBuildInfo.ProjectReferences = projectReferencesList.ToArray(); + projectBuildInfo.AnalyzerReferences = analyzerReferencesList.ToArray(); + projectBuildInfo.PreprocessorSymbols = preprocessorSymbolsList.ToArray(); + projectBuildInfo.SourceFiles = sourceFilesList.ToArray(); } - public static AdhocWorkspace GetWorkspace(BuildProjectData buildProjectData) + public AdhocWorkspace CreateWorkspace() { - if (buildProjectData is null) - { - throw new ArgumentNullException(nameof(buildProjectData)); - } - - if (buildProjectData is null) - { - throw new ArgumentNullException(nameof(buildProjectData)); - } + var projectBuildInfo = ProjectBuildInfo; var workspace = new AdhocWorkspace(); - var projectId = ProjectId.CreateFromSerialized(buildProjectData.ProjectGuid); + var projectId = ProjectId.CreateFromSerialized(projectBuildInfo.ProjectGuid); + + var projectInfo = CreateProjectInfo(projectBuildInfo, workspace, projectId); - var projectInfo = CreateProjectInfo(buildProjectData, workspace, projectId); - var solution = workspace.CurrentSolution.AddProject(projectInfo); if (!workspace.TryApplyChanges(solution)) @@ -150,9 +148,9 @@ public static AdhocWorkspace GetWorkspace(BuildProjectData buildProjectData) return workspace; } - private static ProjectInfo CreateProjectInfo(BuildProjectData buildProjectData, CodeAnalysis.Workspace workspace, ProjectId projectId) + private static ProjectInfo CreateProjectInfo(ProjectBuildInfo projectBuildInfo, CodeAnalysis.Workspace workspace, ProjectId projectId) { - string projectName = Path.GetFileNameWithoutExtension(buildProjectData.ProjectFilePath); + string projectName = Path.GetFileNameWithoutExtension(projectBuildInfo.ProjectFilePath); return ProjectInfo.Create( projectId, @@ -160,20 +158,20 @@ private static ProjectInfo CreateProjectInfo(BuildProjectData buildProjectData, projectName, projectName, LanguageNames.CSharp, - filePath: buildProjectData.ProjectFilePath, - outputFilePath: buildProjectData.TargetPath, - documents: GetDocuments(buildProjectData, projectId), - projectReferences: GetExistingProjectReferences(buildProjectData, workspace), - metadataReferences: GetMetadataReferences(buildProjectData), - analyzerReferences: GetAnalyzerReferences(buildProjectData, workspace), - parseOptions: CreateParseOptions(buildProjectData), - compilationOptions: CreateCompilationOptions(buildProjectData)); + filePath: projectBuildInfo.ProjectFilePath, + outputFilePath: projectBuildInfo.TargetPath, + documents: GetDocuments(projectBuildInfo, projectId), + projectReferences: GetExistingProjectReferences(projectBuildInfo, workspace), + metadataReferences: GetMetadataReferences(projectBuildInfo), + analyzerReferences: GetAnalyzerReferences(projectBuildInfo, workspace), + parseOptions: CreateParseOptions(projectBuildInfo), + compilationOptions: CreateCompilationOptions(projectBuildInfo)); } - private static IReadOnlyList GetDocuments(BuildProjectData buildProjectData, ProjectId projectId) => - buildProjectData.SourceFiles is null + private static IReadOnlyList GetDocuments(ProjectBuildInfo projectBuildInfo, ProjectId projectId) => + projectBuildInfo.SourceFiles is null ? Array.Empty() - : buildProjectData.SourceFiles + : projectBuildInfo.SourceFiles .Where(File.Exists) .Select(x => DocumentInfo.Create( DocumentId.CreateNewId(projectId), @@ -183,38 +181,38 @@ buildProjectData.SourceFiles is null SourceText.From(File.ReadAllText(x), Encoding.Unicode), VersionStamp.Create())), filePath: x)).ToList(); - private static IReadOnlyList GetExistingProjectReferences(BuildProjectData buildProjectData, CodeAnalysis.Workspace workspace) => - buildProjectData.ProjectReferences + private static IReadOnlyList GetExistingProjectReferences(ProjectBuildInfo projectBuildInfo, CodeAnalysis.Workspace workspace) => + projectBuildInfo.ProjectReferences .Select(x => workspace.CurrentSolution.Projects.FirstOrDefault(y => y.FilePath.Equals(x, StringComparison.OrdinalIgnoreCase))) .Where(x => x != null) .Select(x => new ProjectReference(x.Id)).ToList(); - private static IReadOnlyList GetMetadataReferences(BuildProjectData buildProjectData) => - buildProjectData + private static IReadOnlyList GetMetadataReferences(ProjectBuildInfo projectBuildInfo) => + projectBuildInfo .References?.Where(File.Exists) .Select(x => MetadataReference.CreateFromFile(x)).ToList(); - private static IReadOnlyList GetAnalyzerReferences(BuildProjectData buildProjectData, CodeAnalysis.Workspace workspace) + private static IReadOnlyList GetAnalyzerReferences(ProjectBuildInfo projectBuildInfo, CodeAnalysis.Workspace workspace) { IAnalyzerAssemblyLoader loader = workspace.Services.GetRequiredService().GetLoader(); - return buildProjectData.AnalyzerReferences is null + return projectBuildInfo.AnalyzerReferences is null ? Array.Empty() - : buildProjectData.AnalyzerReferences? + : projectBuildInfo.AnalyzerReferences? .Where(File.Exists) .Select(x => new AnalyzerFileReference(x, loader)).ToList(); } - private static ParseOptions CreateParseOptions(BuildProjectData buildProjectData) => CreateCSharpParseOptions(buildProjectData); + private static ParseOptions CreateParseOptions(ProjectBuildInfo projectBuildInfo) => CreateCSharpParseOptions(projectBuildInfo); - private static CSharpParseOptions CreateCSharpParseOptions(BuildProjectData buildProjectData) + private static CSharpParseOptions CreateCSharpParseOptions(ProjectBuildInfo projectBuildInfo) { var parseOptions = new CSharpParseOptions(); - parseOptions = parseOptions.WithPreprocessorSymbols(buildProjectData.PreprocessorSymbols); + parseOptions = parseOptions.WithPreprocessorSymbols(projectBuildInfo.PreprocessorSymbols); - if (!string.IsNullOrWhiteSpace(buildProjectData.LangVersion) && - LanguageVersionFacts.TryParse(buildProjectData.LangVersion, out var languageVersion)) + if (!string.IsNullOrWhiteSpace(projectBuildInfo.LangVersion) && + LanguageVersionFacts.TryParse(projectBuildInfo.LangVersion, out var languageVersion)) { parseOptions = parseOptions.WithLanguageVersion(languageVersion); } @@ -222,9 +220,9 @@ private static CSharpParseOptions CreateCSharpParseOptions(BuildProjectData buil return parseOptions; } - private static CompilationOptions CreateCompilationOptions(BuildProjectData buildProjectData) + private static CompilationOptions CreateCompilationOptions(ProjectBuildInfo projectBuildInfo) { - string outputType = buildProjectData.OutputType; + string outputType = projectBuildInfo.OutputType; OutputKind? kind = null; switch (outputType) @@ -245,4 +243,9 @@ private static CompilationOptions CreateCompilationOptions(BuildProjectData buil return new CSharpCompilationOptions(kind.Value); } -} + + + + + +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/Prebuild.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/Prebuild.cs index 585444e95f..4249982422 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/Prebuild.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/Prebuild.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Microsoft.DotNet.Interactive.CSharpProject.RoslynWorkspaceUtilities; using Microsoft.DotNet.Interactive.CSharpProject.Servers.Roslyn; using Microsoft.DotNet.Interactive.Utility; using Pocket; @@ -16,7 +15,6 @@ using System.Reactive.Subjects; using System.Threading; using System.Threading.Tasks; -using static Microsoft.DotNet.Interactive.CSharpProject.Build.RoslynWorkspaceUtilities.RoslynWorkspaceUtilities; using static Pocket.Logger; using Disposable = System.Reactive.Disposables.Disposable; @@ -49,7 +47,8 @@ environmentVariable is not null Log.Info("Prebuilds path is {DefaultWorkspacesDirectory}", DefaultPrebuildsDirectory); } - private readonly AsyncLazy _lazyCreation; + private readonly AsyncLazy _lazyInitialization; + private bool? _isInitialized = null; private int buildCount = 0; @@ -58,12 +57,10 @@ environmentVariable is not null private readonly Logger _log; private readonly Subject _buildRequestChannel; - protected CodeAnalysis.Workspace _roslynWorkspace; - private readonly IScheduler _buildThrottleScheduler; private readonly SerialDisposable _buildThrottleSubscription; - private BuildDataResults _lastBuildResult; + private BuildResult _buildResult; private readonly FileInfo _lastBuildErrorLogFile; @@ -82,7 +79,7 @@ public Prebuild( Directory = directory ?? new DirectoryInfo(Path.Combine(DefaultPrebuildsDirectory.FullName, Name)); var prebuildInitializer = initializer ?? new PrebuildInitializer("console", Name); - _lazyCreation = new AsyncLazy(() => CreatePrebuildAsync(prebuildInitializer)); + _lazyInitialization = new AsyncLazy(() => InitializeAsync(prebuildInitializer)); _lastBuildErrorLogFile = new FileInfo(Path.Combine(Directory.FullName, ".net-interactive-builderror")); _log = new Logger($"{nameof(Prebuild)}:{Name}"); @@ -108,7 +105,7 @@ public Prebuild( public string Name { get; } - private Task EnsureCreatedAsync() => _lazyCreation.ValueAsync(); + private Task EnsureInitializedAsync() => _lazyInitialization.ValueAsync(); private bool TryLoadWorkspaceFromCache() { @@ -116,9 +113,9 @@ private bool TryLoadWorkspaceFromCache() { var cacheFile = FindCacheFile(Directory); - if (cacheFile is not null) + if (cacheFile is { Exists: true }) { - LoadRoslynWorkspaceFromCache(cacheFile).Wait(); + LoadRoslynWorkspaceFromCache(cacheFile); return true; } } @@ -126,31 +123,31 @@ private bool TryLoadWorkspaceFromCache() return false; } - private async Task LoadRoslynWorkspaceFromCache(FileSystemInfo cacheFile) + private CodeAnalysis.Workspace LoadRoslynWorkspaceFromCache(FileSystemInfo cacheFile) { var projectFile = GetProjectFile(); - if (projectFile is not null && - cacheFile.LastWriteTimeUtc >= projectFile.LastWriteTimeUtc) + if (projectFile is null) { - BuildDataResults result; - using (await FileLock.TryCreateAsync(Directory)) - { - result = GetResultsFromCacheFile(cacheFile.FullName); - } + throw new InvalidOperationException($"Project file not found for prebuild: {this}"); + } - if (result is null) - { - throw new InvalidOperationException("The cache file contains no solutions or projects"); - } + var result = BuildResult.FromCacheFile(cacheFile.FullName); + + if (result is null) + { + throw new InvalidOperationException("The cache file contains no solutions or projects"); + } - _roslynWorkspace = null; - _lastBuildResult = result; + _buildResult = result; - if (result.Succeeded) - { - _roslynWorkspace = CreateRoslynWorkspace(); - } + if (result.Succeeded) + { + return CreateRoslynWorkspace(_buildResult); + } + else + { + throw new InvalidOperationException("Build failed."); } } @@ -158,28 +155,51 @@ private async Task LoadRoslynWorkspaceFromCache(FileSystemInfo cacheFile) public static DirectoryInfo DefaultPrebuildsDirectory { get; } - public FileInfo EntryPointAssemblyPath => + public FileInfo EntryPointAssemblyPath => _entryPointAssemblyPath ??= this.GetEntryPointAssemblyPath(); - public string TargetFramework => + public string TargetFramework => _targetFramework ??= this.GetTargetFramework(); - public async Task GetOrCreateWorkspaceAsync() + private static CodeAnalysis.Workspace CreateRoslynWorkspace(BuildResult buildResult) { - if (_roslynWorkspace is { } ws) + if (buildResult is null) { - return ws; + throw new InvalidOperationException("No build available"); } - - CreateCompletionSourceIfNeeded(ref _buildCompletionSource, _buildCompletionSourceLock); - _buildRequestChannel.OnNext(Unit.Default); + var workspace = buildResult.CreateWorkspace(); - var newWorkspace = await _buildCompletionSource.Task; + var projectId = workspace.CurrentSolution.ProjectIds.FirstOrDefault(); + var references = buildResult.ProjectBuildInfo.References; + var metadataReferences = references.GetMetadataReferences(); + var solution = workspace.CurrentSolution; + solution = solution.WithProjectMetadataReferences(projectId, metadataReferences); + workspace.TryApplyChanges(solution); - return newWorkspace; + return workspace; } - + + public async Task CreateWorkspaceAsync() + { + if (_buildResult is { } buildResult) + { + return CreateRoslynWorkspace(buildResult); + } + + // FIX: (GetOrCreateWorkspaceAsync) mutability is a problem here + if (_buildResult is not { } ws) + { + CreateCompletionSourceIfNeeded(ref _buildCompletionSource, _buildCompletionSourceLock); + + _buildRequestChannel.OnNext(Unit.Default); + + return await _buildCompletionSource.Task; + } + + return null; + } + private void CreateCompletionSourceIfNeeded(ref TaskCompletionSource completionSource, object lockObject) { lock (lockObject) @@ -256,47 +276,19 @@ private void InitializeBuildChannel() private async Task ProcessBuildRequest() { await EnsureReadyAsync(); - var ws = CreateRoslynWorkspace(); + var ws = CreateRoslynWorkspace(_buildResult); SetCompletionSourceResult(_buildCompletionSource, ws, _buildCompletionSourceLock); } - private CodeAnalysis.Workspace CreateRoslynWorkspace() - { - var build = _lastBuildResult; - - if (build is null) - { - throw new InvalidOperationException("No design time or full build available"); - } - - var ws = build.Workspace; - - if (!ws.CanBeUsedToGenerateCompilation()) - { - _roslynWorkspace = null; - _lastBuildResult = null; - throw new InvalidOperationException("The Roslyn workspace cannot be used to generate a compilation"); - } - - var projectId = ws.CurrentSolution.ProjectIds.FirstOrDefault(); - var references = build.BuildProjectData.References; - var metadataReferences = references.GetMetadataReferences(); - var solution = ws.CurrentSolution; - solution = solution.WithProjectMetadataReferences(projectId, metadataReferences); - ws.TryApplyChanges(solution); - _roslynWorkspace = ws; - return ws; - } - public async Task EnsureReadyAsync() { - if (_roslynWorkspace is not null) + if (_buildResult is not null) { - Log.Info("Workspace already loaded for prebuild {name}.", Name); + Log.Info("Build result already loaded for prebuild {name}.", Name); return; } - await EnsureCreatedAsync(); + await EnsureInitializedAsync(); await EnsureBuiltAsync(); } @@ -305,9 +297,9 @@ protected async Task EnsureBuiltAsync() { using var operation = _log.OnEnterAndConfirmOnExit(); - await EnsureCreatedAsync(); + await EnsureInitializedAsync(); - if (IsBuildNeeded()) + if (_buildResult is null) { await BuildAsync(); } @@ -325,14 +317,14 @@ public async Task BuildAsync() if (!EnableBuild) { - throw new InvalidOperationException($"Full build is disabled for prebuild '{this}'"); + throw new InvalidOperationException($"Build is disabled for prebuild: {this}"); } var buildSemaphore = _buildSemaphores.GetOrAdd(Name, _ => new SemaphoreSlim(1, 1)); try { - await EnsureCreatedAsync(); + await EnsureInitializedAsync(); operation.Info("Building prebuild '{name}'", Name); @@ -374,7 +366,7 @@ public async Task BuildAsync() } await cacheFile.WaitForFileAvailableAsync(); - await LoadRoslynWorkspaceFromCache(cacheFile); + LoadRoslynWorkspaceFromCache(cacheFile); Interlocked.Exchange(ref buildCount, 0); } @@ -411,15 +403,20 @@ await File.WriteAllTextAsync( public override string ToString() => $"{Name} ({Directory.FullName})"; - protected virtual bool IsBuildNeeded() => _roslynWorkspace is null; - - public async Task CreatePrebuildAsync(IPrebuildInitializer initializer) + private async Task InitializeAsync(IPrebuildInitializer initializer) { using var operation = Log.OnEnterAndConfirmOnExit(); if (!EnableBuild) { - throw new InvalidOperationException($"Full build is disabled for prebuild '{this}'"); + if (IsInitialized) + { + operation.Info("Prebuild already initialized in directory {directory}.", Directory); + } + else + { + throw new InvalidOperationException($"Build disabled for prebuild: {this}"); + } } if (!Directory.Exists) @@ -435,11 +432,19 @@ public async Task CreatePrebuildAsync(IPrebuildInitializer initializer) await initializer.InitializeAsync(Directory); } + IsInitialized = true; + operation.Succeed(); return true; } + private bool IsInitialized + { + get => _isInitialized ?? Directory.Exists && FindCacheFile(Directory) is { Exists: true }; + set => _isInitialized = value; + } + public static async Task GetOrCreateConsolePrebuildAsync(bool enableBuild = false) { var builder = new PrebuildBuilder("console"); diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/PrebuildExtensions.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/PrebuildExtensions.cs index 4b00808aee..a8bdf47ff6 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/PrebuildExtensions.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/PrebuildExtensions.cs @@ -18,7 +18,7 @@ internal static class PrebuildExtensions IReadOnlyCollection sources, SourceCodeKind sourceCodeKind, IEnumerable defaultUsings) => - prebuild.GetCompilationAsync(sources, sourceCodeKind, defaultUsings, prebuild.GetOrCreateWorkspaceAsync); + prebuild.GetCompilationAsync(sources, sourceCodeKind, defaultUsings, prebuild.CreateWorkspaceAsync); public static async Task<(Compilation compilation, CodeAnalysis.Project project)> GetCompilationAsync( this Prebuild prebuild, diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/PrebuildInitializer.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/PrebuildInitializer.cs index ee67fee6ab..903f80ee15 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/PrebuildInitializer.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/PrebuildInitializer.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Threading.Tasks; -using Microsoft.DotNet.Interactive.CSharpProject.Build.RoslynWorkspaceUtilities; using Microsoft.DotNet.Interactive.Utility; namespace Microsoft.DotNet.Interactive.CSharpProject.Build; diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildProjectData.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/ProjectBuildInfo.cs similarity index 89% rename from src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildProjectData.cs rename to src/Microsoft.DotNet.Interactive.CSharpProject/Build/ProjectBuildInfo.cs index 7198995584..6b7f015de4 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildProjectData.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/ProjectBuildInfo.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -namespace Microsoft.DotNet.Interactive.CSharpProject.RoslynWorkspaceUtilities; +namespace Microsoft.DotNet.Interactive.CSharpProject.Build; -internal class BuildProjectData +internal class ProjectBuildInfo { public Guid ProjectGuid { get; set; } = Guid.Empty; public IReadOnlyList ProjectReferences { get; set; } = new List(); diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildDataProjectItem.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildDataProjectItem.cs deleted file mode 100644 index f142df5a9d..0000000000 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildDataProjectItem.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.DotNet.Interactive.CSharpProject.RoslynWorkspaceUtilities; - -internal class BuildDataProjectItem -{ - internal string ItemSpec { get; } - internal IReadOnlyDictionary Metadata { get; } - - internal BuildDataProjectItem() - { - // ItemSpec = taskItem.ItemSpec; - // Metadata = taskItem.MetadataNames.Cast().ToDictionary(x => x, x => taskItem.GetMetadata(x)); - } -} diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildDataResults.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildDataResults.cs deleted file mode 100644 index cd7ae58a33..0000000000 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Build/RoslynWorkspaceUtilities/BuildDataResults.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using Microsoft.CodeAnalysis.CSharp; - -namespace Microsoft.DotNet.Interactive.CSharpProject.RoslynWorkspaceUtilities; - -internal class BuildDataResults -{ - internal BuildProjectData BuildProjectData { get; init; } - - internal CodeAnalysis.Workspace Workspace { get; init; } - - internal bool Succeeded { get; init; } - - internal string CacheFilePath { get; init; } - - internal CSharpParseOptions CSharpParseOptions { get; init; } - - // TODO: Set this value - internal string ProjectFilePath { get; init; } - - internal Dictionary Items { get; } = new(); -} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/CodeMergeTransformer.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/CodeMergeTransformer.cs index 9086947cf1..7ae8a6b1c2 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/CodeMergeTransformer.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/CodeMergeTransformer.cs @@ -16,7 +16,7 @@ public class CodeMergeTransformer : IWorkspaceTransformer public Task TransformAsync(Workspace source) { - if (source == null) + if (source is null) { throw new ArgumentNullException(nameof(source)); } diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft - Backup.DotNet.Interactive.CSharpProject.csproj b/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft - Backup.DotNet.Interactive.CSharpProject.csproj deleted file mode 100644 index 61196dabbf..0000000000 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft - Backup.DotNet.Interactive.CSharpProject.csproj +++ /dev/null @@ -1,71 +0,0 @@ - - - - net8.0 - Latest - $(AssetTargetFallback);dotnet5.4;portable-net45+win8 - preview - $(NoWarn);1998 - $(NoWarn);8002 - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - all - - - - - diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/CompilationUtility.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/CompilationUtility.cs index 0e8f8fa648..b1a233d1a9 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/CompilationUtility.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/CompilationUtility.cs @@ -11,11 +11,6 @@ namespace Microsoft.DotNet.Interactive.CSharpProject.Servers.Roslyn; internal static class CompilationUtility { - internal static bool CanBeUsedToGenerateCompilation(this CodeAnalysis.Workspace workspace) - { - return workspace?.CurrentSolution?.Projects?.Count() > 0; - } - public static async Task WaitForFileAvailableAsync(this FileInfo file) { if (file is null) diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/RoslynWorkspaceServer.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/RoslynWorkspaceServer.cs index 111301be59..62692dabec 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/RoslynWorkspaceServer.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/RoslynWorkspaceServer.cs @@ -331,7 +331,7 @@ private async Task GetCompilationAsync( var prebuild = await _prebuildFinder.FindAsync(workspace.WorkspaceType); workspace = await workspace.InlineBuffersAsync(); var sources = workspace.GetSourceFiles(); - var (compilation, project) = await prebuild.GetCompilationAsync(sources, SourceCodeKind.Regular, workspace.Usings, () => prebuild.GetOrCreateWorkspaceAsync()); + var (compilation, project) = await prebuild.GetCompilationAsync(sources, SourceCodeKind.Regular, workspace.Usings, () => prebuild.CreateWorkspaceAsync()); var (diagnosticsInActiveBuffer, allDiagnostics) = workspace.MapDiagnostics(activeBufferId, compilation.GetDiagnostics()); return new CompilationResult( compilation, diff --git a/src/Microsoft.DotNet.Interactive/Utility/CommandLineInvocationException.cs b/src/Microsoft.DotNet.Interactive/Utility/CommandLineInvocationException.cs index 6f7b1e4506..b3bc161249 100644 --- a/src/Microsoft.DotNet.Interactive/Utility/CommandLineInvocationException.cs +++ b/src/Microsoft.DotNet.Interactive/Utility/CommandLineInvocationException.cs @@ -8,7 +8,16 @@ namespace Microsoft.DotNet.Interactive.Utility; public class CommandLineInvocationException : Exception { public CommandLineInvocationException(CommandLineResult result, string message = null) : base( - $"{message}{Environment.NewLine}Exit code {result.ExitCode}: {string.Join("\n", result.Error)}".Trim()) + $""" + {message} + Exit code {result.ExitCode} + + StdErr: + {string.Join("\n", result.Error)} + + StdOut: + {string.Join("\n", result.Output)} + """.Trim()) { } } \ No newline at end of file diff --git a/src/dotnet-interactive.Tests/dotnet-interactive.Tests.v3.ncrunchproject b/src/dotnet-interactive.Tests/dotnet-interactive.Tests.v3.ncrunchproject index d00fbe730c..1d3d685f85 100644 --- a/src/dotnet-interactive.Tests/dotnet-interactive.Tests.v3.ncrunchproject +++ b/src/dotnet-interactive.Tests/dotnet-interactive.Tests.v3.ncrunchproject @@ -3,7 +3,7 @@ ..\polyglot-notebooks\src\contracts.ts - 10000 + 20000 AspNetTestHostCompatibility