From 4d0c2c0d6fc6eb4d301454ec2e158b63b090f098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Mon, 13 Jul 2020 10:35:14 -0700 Subject: [PATCH] InteractiveHost for .NET Core (#45046) * InteractiveHost for .NET Core * Merge fix * Fixes * Typo * Workaround for https://github.com/dotnet/interactive-window/issues/156 * Fix * Update OptProf config * Fix deployment of rsp files * Fix signing. * Clean up unit tests dependencies. * Remove unnecessary references. * Fix reset interactive command * Fix OptProf config * Add missing project refs * Fix InteractiveHost deployment * Process rsp file fully in InteractiveHost process and send back results. * Fix build from VS. * Retrieve metadata resolver parameters from InteractiveHost process. Simplify keeping search paths in-sync between InteractiveHost and InteractiveEvaluator. * Fixes * Fixes * Check for failures to create process in unit tests * Test * Pending buffers * Fix submission project creation * Logging * Serialize exceution and process initialization * Fix reference completion, resolution. * MetadataReferenceResolver fixes * Fix failing .NET Core tests * Target netcoreapp. * Set DOTNET_ROOT * Fix NGEN/OptProf * Avoid loading Microsoft.CodeAnalysis.Scripting * Fix * Feedback * Feedback * Feedback * Comment on dependent target * Disable interpreting of strings as DateTime during deserialization: * Merge fix --- Roslyn.sln | 11 +- eng/Signing.props | 4 + eng/config/OptProf.json | 124 ---- ...CompilerExecutableBindingRedirects.targets | 2 +- .../GacFileResolver.cs | 9 +- src/Deployment/RoslynDeployment.csproj | 6 - src/Deployment/source.extension.vsixmanifest | 9 - .../Interactive/CSharpInteractiveEvaluator.cs | 3 + .../AbstractCSharpCompletionProviderTests.cs | 19 +- ...nteractiveCSharpCompletionProviderTests.cs | 19 + ...ferenceDirectiveCompletionProviderTests.cs | 7 +- .../Interactive/CSharpVBResetCommand.cs | 68 +- .../InteractiveEditorFeaturesResources.resx | 3 + .../Interactive/InteractiveEvaluator.cs | 614 ++++++++---------- .../InteractiveEvaluatorResetOptions.cs | 12 +- .../Core.Wpf/Interactive/ResetInteractive.cs | 13 +- .../InteractiveEditorFeaturesResources.cs.xlf | 5 + .../InteractiveEditorFeaturesResources.de.xlf | 5 + .../InteractiveEditorFeaturesResources.es.xlf | 5 + .../InteractiveEditorFeaturesResources.fr.xlf | 5 + .../InteractiveEditorFeaturesResources.it.xlf | 5 + .../InteractiveEditorFeaturesResources.ja.xlf | 5 + .../InteractiveEditorFeaturesResources.ko.xlf | 5 + .../InteractiveEditorFeaturesResources.pl.xlf | 5 + ...teractiveEditorFeaturesResources.pt-BR.xlf | 5 + .../InteractiveEditorFeaturesResources.ru.xlf | 5 + .../InteractiveEditorFeaturesResources.tr.xlf | 5 + ...ractiveEditorFeaturesResources.zh-Hans.xlf | 5 + ...ractiveEditorFeaturesResources.zh-Hant.xlf | 5 + ...actReferenceDirectiveCompletionProvider.cs | 19 +- .../InteractiveCSharpTestWorkspaceFixture.cs | 27 + .../TestWorkspace_XmlConsumption.cs | 8 +- .../DesktopHost/InteractiveHost32.csproj | 22 - .../DesktopHost/InteractiveHost64.csproj | 22 - .../DesktopHost/InteractiveHostEntryPoint.cs | 31 - ...nteractiveHost.InitializedRemoteService.cs | 2 +- .../Core/InteractiveHost.LazyRemoteService.cs | 86 ++- .../Core/InteractiveHost.RemoteService.cs | 6 +- .../Core/InteractiveHost.Service.cs | 345 ++++------ .../Host/Interactive/Core/InteractiveHost.cs | 65 +- .../Core/InteractiveHostOptions.cs | 53 +- .../Core/InteractiveHostPlatform.cs | 15 + .../Core/InteractiveHostPlatformInfo.cs | 59 ++ .../Interactive/Core/RemoteExecutionResult.cs | 58 +- .../Core/RemoteInitializationResult.cs | 52 ++ .../Host/InteractiveHostResources.Designer.cs | 198 ------ .../Host/InteractiveHostResources.resx | 2 +- ...rosoft.CodeAnalysis.InteractiveHost.csproj | 26 +- .../Host/xlf/InteractiveHostResources.cs.xlf | 4 +- .../Host/xlf/InteractiveHostResources.de.xlf | 4 +- .../Host/xlf/InteractiveHostResources.es.xlf | 4 +- .../Host/xlf/InteractiveHostResources.fr.xlf | 4 +- .../Host/xlf/InteractiveHostResources.it.xlf | 4 +- .../Host/xlf/InteractiveHostResources.ja.xlf | 4 +- .../Host/xlf/InteractiveHostResources.ko.xlf | 4 +- .../Host/xlf/InteractiveHostResources.pl.xlf | 4 +- .../xlf/InteractiveHostResources.pt-BR.xlf | 4 +- .../Host/xlf/InteractiveHostResources.ru.xlf | 4 +- .../Host/xlf/InteractiveHostResources.tr.xlf | 4 +- .../xlf/InteractiveHostResources.zh-Hans.xlf | 4 +- .../xlf/InteractiveHostResources.zh-Hant.xlf | 4 +- .../{DesktopHost => HostProcess}/App.config | 0 .../HostProcess/Core/CSharpInteractive.rsp | 41 ++ .../Desktop}/CSharpInteractive.rsp | 0 .../HostProcess/InteractiveHost32.csproj | 39 ++ .../HostProcess/InteractiveHost64.csproj | 48 ++ .../HostProcess/InteractiveHostEntryPoint.cs | 91 +++ .../HostTest/AbstractInteractiveHostTests.cs | 213 +++++- .../HostTest/InteractiveHost.UnitTests.csproj | 78 ++- .../HostTest/InteractiveHostCoreInitTests.cs | 97 +++ .../InteractiveHostDesktopInitTests.cs | 90 +++ ...ests.cs => InteractiveHostDesktopTests.cs} | 400 ++++-------- src/Interactive/HostTest/StressTests.cs | 10 +- .../HostTest/SynchronizedTextWriter.cs | 2 +- src/Interactive/HostTest/TestUtils.cs | 15 + .../VS.ExternalAPIs.Roslyn.Package.csproj | 2 +- .../Hosting/CommandLine/CommandLineRunner.cs | 8 +- .../RuntimeMetadataReferenceResolver.cs | 146 +++-- .../Microsoft.CodeAnalysis.Scripting.csproj | 2 + src/Scripting/Core/ScriptMetadataResolver.cs | 26 +- src/Scripting/Core/ScriptOptions.cs | 2 +- .../RuntimeMetadataReferenceResolverTests.cs | 12 +- .../CSharpVsInteractiveWindowProvider.cs | 5 + .../Commands/ResetInteractiveTests.cs | 6 +- .../Commands/TestResetInteractive.cs | 8 +- ...o.LanguageServices.CSharp.UnitTests.csproj | 6 +- .../MiscellaneousFilesWorkspace.cs | 68 +- .../VisualStudioProjectOptionsProcessor.cs | 13 +- .../VsInteractiveWindowProvider.cs | 11 +- .../Def/Interactive/VsResetInteractive.cs | 25 +- .../Core/Def/ServicesVSResources.resx | 6 + .../Core/Def/xlf/ServicesVSResources.cs.xlf | 10 + .../Core/Def/xlf/ServicesVSResources.de.xlf | 10 + .../Core/Def/xlf/ServicesVSResources.es.xlf | 10 + .../Core/Def/xlf/ServicesVSResources.fr.xlf | 10 + .../Core/Def/xlf/ServicesVSResources.it.xlf | 10 + .../Core/Def/xlf/ServicesVSResources.ja.xlf | 10 + .../Core/Def/xlf/ServicesVSResources.ko.xlf | 10 + .../Core/Def/xlf/ServicesVSResources.pl.xlf | 10 + .../Def/xlf/ServicesVSResources.pt-BR.xlf | 10 + .../Core/Def/xlf/ServicesVSResources.ru.xlf | 10 + .../Core/Def/xlf/ServicesVSResources.tr.xlf | 10 + .../Def/xlf/ServicesVSResources.zh-Hans.xlf | 10 + .../Def/xlf/ServicesVSResources.zh-Hant.xlf | 10 + ...alStudio.LanguageServices.UnitTests.vbproj | 2 +- .../InProcess/InteractiveWindow_InProc.cs | 3 + .../Setup/Roslyn.VisualStudio.Setup.csproj | 41 +- ...io.LanguageServices.Test.Utilities2.vbproj | 2 +- ....VisualStudio.InteractiveComponents.csproj | 110 ---- .../TestHooks/FeatureAttribute_Names.cs | 1 + 110 files changed, 2157 insertions(+), 1738 deletions(-) create mode 100644 src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractInteractiveCSharpCompletionProviderTests.cs create mode 100644 src/EditorFeatures/TestUtilities/Workspaces/InteractiveCSharpTestWorkspaceFixture.cs delete mode 100644 src/Interactive/DesktopHost/InteractiveHost32.csproj delete mode 100644 src/Interactive/DesktopHost/InteractiveHost64.csproj delete mode 100644 src/Interactive/DesktopHost/InteractiveHostEntryPoint.cs create mode 100644 src/Interactive/Host/Interactive/Core/InteractiveHostPlatform.cs create mode 100644 src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs create mode 100644 src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs delete mode 100644 src/Interactive/Host/InteractiveHostResources.Designer.cs rename src/Interactive/{DesktopHost => HostProcess}/App.config (100%) create mode 100644 src/Interactive/HostProcess/Core/CSharpInteractive.rsp rename src/Interactive/{DesktopHost => HostProcess/Desktop}/CSharpInteractive.rsp (100%) create mode 100644 src/Interactive/HostProcess/InteractiveHost32.csproj create mode 100644 src/Interactive/HostProcess/InteractiveHost64.csproj create mode 100644 src/Interactive/HostProcess/InteractiveHostEntryPoint.cs create mode 100644 src/Interactive/HostTest/InteractiveHostCoreInitTests.cs create mode 100644 src/Interactive/HostTest/InteractiveHostDesktopInitTests.cs rename src/Interactive/HostTest/{InteractiveHostTests.cs => InteractiveHostDesktopTests.cs} (73%) create mode 100644 src/Interactive/HostTest/TestUtils.cs delete mode 100644 src/VisualStudio/VisualStudioInteractiveComponents/Roslyn.VisualStudio.InteractiveComponents.csproj diff --git a/Roslyn.sln b/Roslyn.sln index 72fb85be190..b5c0c107bba 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -169,7 +169,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.Services.Test.Utilit EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf", "src\EditorFeatures\CSharp.Wpf\Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj", "{FE2CBEA6-D121-4FAA-AA8B-FC9900BF8C83}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteractiveHost32", "src\Interactive\DesktopHost\InteractiveHost32.csproj", "{EBA4DFA1-6DED-418F-A485-A3B608978906}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteractiveHost32", "src\Interactive\HostProcess\InteractiveHost32.csproj", "{EBA4DFA1-6DED-418F-A485-A3B608978906}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteractiveHost.UnitTests", "src\Interactive\HostTest\InteractiveHost.UnitTests.csproj", "{8CEE3609-A5A9-4A9B-86D7-33118F5D6B34}" EndProject @@ -193,8 +193,6 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.VisualStudio.Lang EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.VisualStudio.Setup", "src\VisualStudio\Setup\Roslyn.VisualStudio.Setup.csproj", "{201EC5B7-F91E-45E5-B9F2-67A266CCE6FC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.VisualStudio.InteractiveComponents", "src\VisualStudio\VisualStudioInteractiveComponents\Roslyn.VisualStudio.InteractiveComponents.csproj", "{2169F526-8A88-435D-8732-486ACA095A6A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.VisualStudio.DiagnosticsWindow", "src\VisualStudio\VisualStudioDiagnosticsToolWindow\Roslyn.VisualStudio.DiagnosticsWindow.csproj", "{A486D7DE-F614-409D-BB41-0FFDF582E35C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressionEvaluatorPackage", "src\ExpressionEvaluator\Package\ExpressionEvaluatorPackage.csproj", "{B617717C-7881-4F01-AB6D-B1B6CC0483A0}" @@ -344,7 +342,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Pool EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests", "src\Workspaces\MSBuildTest\Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj", "{037F06F0-3BE8-42D0-801E-2F74FC380AB8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteractiveHost64", "src\Interactive\DesktopHost\InteractiveHost64.csproj", "{2F11618A-9251-4609-B3D5-CE4D2B3D3E49}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteractiveHost64", "src\Interactive\HostProcess\InteractiveHost64.csproj", "{2F11618A-9251-4609-B3D5-CE4D2B3D3E49}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.IntegrationTest.IntegrationService", "src\VisualStudio\IntegrationTest\IntegrationService\Microsoft.VisualStudio.IntegrationTest.IntegrationService.csproj", "{764D2C19-0187-4837-A2A3-96DDC6EF4CE2}" EndProject @@ -825,10 +823,6 @@ Global {201EC5B7-F91E-45E5-B9F2-67A266CCE6FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {201EC5B7-F91E-45E5-B9F2-67A266CCE6FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {201EC5B7-F91E-45E5-B9F2-67A266CCE6FC}.Release|Any CPU.Build.0 = Release|Any CPU - {2169F526-8A88-435D-8732-486ACA095A6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2169F526-8A88-435D-8732-486ACA095A6A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2169F526-8A88-435D-8732-486ACA095A6A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2169F526-8A88-435D-8732-486ACA095A6A}.Release|Any CPU.Build.0 = Release|Any CPU {A486D7DE-F614-409D-BB41-0FFDF582E35C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A486D7DE-F614-409D-BB41-0FFDF582E35C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A486D7DE-F614-409D-BB41-0FFDF582E35C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1300,7 +1294,6 @@ Global {91C574AD-0352-47E9-A019-EE02CC32A396} = {8DBA5174-B0AA-4561-82B1-A46607697753} {A1455D30-55FC-45EF-8759-3AEBDB13D940} = {8DBA5174-B0AA-4561-82B1-A46607697753} {201EC5B7-F91E-45E5-B9F2-67A266CCE6FC} = {8DBA5174-B0AA-4561-82B1-A46607697753} - {2169F526-8A88-435D-8732-486ACA095A6A} = {19148439-436F-4CDA-B493-70AF4FFC13E9} {A486D7DE-F614-409D-BB41-0FFDF582E35C} = {8DBA5174-B0AA-4561-82B1-A46607697753} {B617717C-7881-4F01-AB6D-B1B6CC0483A0} = {4C81EBB2-82E1-4C81-80C4-84CC40FA281B} {FD6BA96C-7905-4876-8BCC-E38E2CA64F31} = {913A4C08-898E-49C7-9692-0EF9DC56CF6E} diff --git a/eng/Signing.props b/eng/Signing.props index 316721684f9..2b645a258ff 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -31,7 +31,11 @@ + + + + diff --git a/eng/config/OptProf.json b/eng/config/OptProf.json index 33aa919c4b7..be2d5d99a69 100644 --- a/eng/config/OptProf.json +++ b/eng/config/OptProf.json @@ -18,22 +18,10 @@ "filename": "/Microsoft.CodeAnalysis.EditorFeatures.Text.dll", "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.Features.dll", - "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.dll", "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] }, - { - "filename": "/DesktopHost/System.Reflection.Metadata.dll", - "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] - }, - { - "filename": "/DesktopHost/System.Numerics.Vectors.dll", - "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.Xaml.dll", "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] @@ -58,14 +46,6 @@ "filename": "/Microsoft.CodeAnalysis.EditorFeatures.dll", "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.CSharp.dll", - "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] - }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.dll", - "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] - }, { "filename": "/Microsoft.CodeAnalysis.CSharp.Features.dll", "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] @@ -86,18 +66,10 @@ "filename": "/Microsoft.VisualStudio.LanguageServices.Implementation.dll", "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] }, - { - "filename": "/DesktopHost/System.Collections.Immutable.dll", - "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.SolutionExplorer.dll", "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] }, - { - "filename": "/DesktopHost/System.Runtime.CompilerServices.Unsafe.dll", - "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] - }, { "filename": "/Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll", "testCases":[ "Microsoft.Test.Performance.XamlOptProfCreateTests.WpfCreateProject_DesignerIsolated" ] @@ -123,22 +95,10 @@ "filename": "/Microsoft.CodeAnalysis.EditorFeatures.Text.dll", "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.Features.dll", - "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.dll", "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] }, - { - "filename": "/DesktopHost/System.Reflection.Metadata.dll", - "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] - }, - { - "filename": "/DesktopHost/System.Numerics.Vectors.dll", - "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] - }, { "filename": "/Microsoft.CodeAnalysis.VisualBasic.Features.dll", "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] @@ -159,14 +119,6 @@ "filename": "/Microsoft.CodeAnalysis.EditorFeatures.dll", "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.CSharp.dll", - "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] - }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.dll", - "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] - }, { "filename": "/Microsoft.CodeAnalysis.CSharp.Features.dll", "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] @@ -195,18 +147,10 @@ "filename": "/Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] }, - { - "filename": "/DesktopHost/System.Collections.Immutable.dll", - "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.SolutionExplorer.dll", "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] }, - { - "filename": "/DesktopHost/System.Runtime.CompilerServices.Unsafe.dll", - "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] - }, { "filename": "/Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll", "testCases":[ "WinForms.OptProfTests.winforms_largeform_vb" ] @@ -228,22 +172,10 @@ "filename": "/Microsoft.CodeAnalysis.EditorFeatures.Text.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.Features.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] }, - { - "filename": "/DesktopHost/System.Reflection.Metadata.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, - { - "filename": "/DesktopHost/System.Numerics.Vectors.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, { "filename": "/Microsoft.CodeAnalysis.VisualBasic.Features.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] @@ -264,14 +196,6 @@ "filename": "/Microsoft.CodeAnalysis.EditorFeatures.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.CSharp.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, { "filename": "/Microsoft.CodeAnalysis.CSharp.Features.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] @@ -292,22 +216,10 @@ "filename": "/Microsoft.VisualStudio.LanguageServices.Implementation.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] }, - { - "filename": "/DesktopHost/System.Collections.Immutable.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, - { - "filename": "/DesktopHost/System.Memory.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.SolutionExplorer.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] }, - { - "filename": "/DesktopHost/System.Runtime.CompilerServices.Unsafe.dll", - "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] - }, { "filename": "/Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll", "testCases":[ "TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble" ] @@ -333,22 +245,10 @@ "filename": "/Microsoft.CodeAnalysis.EditorFeatures.Text.dll", "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.Features.dll", - "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.dll", "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] }, - { - "filename": "/DesktopHost/System.Reflection.Metadata.dll", - "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] - }, - { - "filename": "/DesktopHost/System.Numerics.Vectors.dll", - "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] - }, { "filename": "/Microsoft.CodeAnalysis.VisualBasic.Features.dll", "testCases":[ "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment" ] @@ -365,14 +265,6 @@ "filename": "/Microsoft.CodeAnalysis.EditorFeatures.dll", "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.CSharp.dll", - "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment" ] - }, - { - "filename": "/DesktopHost/Microsoft.CodeAnalysis.dll", - "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] - }, { "filename": "/Microsoft.CodeAnalysis.CSharp.Features.dll", "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug" ] @@ -401,30 +293,14 @@ "filename": "/Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.dll", "testCases":[ "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment" ] }, - { - "filename": "/DesktopHost/System.Text.Encoding.CodePages.dll", - "testCases":[ "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "testCases":[ "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] }, - { - "filename": "/DesktopHost/System.Collections.Immutable.dll", - "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] - }, - { - "filename": "/DesktopHost/System.Memory.dll", - "testCases":[ "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment" ] - }, { "filename": "/Microsoft.VisualStudio.LanguageServices.SolutionExplorer.dll", "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] }, - { - "filename": "/DesktopHost/System.Runtime.CompilerServices.Unsafe.dll", - "testCases":[ "VSPE.OptProfTests.vs_asl_cs_scenario", "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_DesignTime_solution_loadclose_cs_picasso", "VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs", "VSPE.OptProfTests.vs_perf_designtime_ide_searchtest", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Typing", "VSPE.OptProfTests.DDRIT_RPS_ManagedLangs_Debug", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] - }, { "filename": "/Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll", "testCases":[ "VSPE.OptProfTests.vs_asl_vb_scenario", "VSPE.OptProfTests.vs_perf_designtime_solution_build_vb_australiangovernment", "VSPE.OptProfTests.vs_perf_designtime_solution_loadclose_vb_australiangovernment" ] diff --git a/eng/targets/GenerateCompilerExecutableBindingRedirects.targets b/eng/targets/GenerateCompilerExecutableBindingRedirects.targets index 6f7f6c21aee..9045000b207 100644 --- a/eng/targets/GenerateCompilerExecutableBindingRedirects.targets +++ b/eng/targets/GenerateCompilerExecutableBindingRedirects.targets @@ -5,7 +5,7 @@ The inclusion of this file will cause the resulting .exe.config to contain redirects for all non-framework dependencies, which is needed for plugins loaded into the compiler (e.g. analyzers) that might target lower versions of these dependencies. --> - + true diff --git a/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GacFileResolver.cs b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GacFileResolver.cs index d40d2e49deb..55412dbe779 100644 --- a/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GacFileResolver.cs +++ b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GacFileResolver.cs @@ -17,13 +17,10 @@ namespace Microsoft.CodeAnalysis.Scripting.Hosting /// internal sealed class GacFileResolver : IEquatable { - // Consider a better availability check (perhaps the presence of Assembly.GlobalAssemblyCache once CoreCLR mscorlib is cleaned up). - // https://github.com/dotnet/roslyn/issues/5538 - /// - /// Returns true if GAC is available on the platform. + /// Returns true if GAC is available on the current platform. /// - public static bool IsAvailable => CoreClrShim.AssemblyLoadContext.Type == null; + public static bool IsAvailable => typeof(object).Assembly.GlobalAssemblyCache; /// /// Architecture filter used when resolving assembly references. @@ -43,7 +40,7 @@ internal sealed class GacFileResolver : IEquatable /// among the set filtered by /// The platform doesn't support GAC. public GacFileResolver( - ImmutableArray architectures = default(ImmutableArray), + ImmutableArray architectures = default, CultureInfo preferredCulture = null) { if (!IsAvailable) diff --git a/src/Deployment/RoslynDeployment.csproj b/src/Deployment/RoslynDeployment.csproj index 558b0236d2f..f173c1fd74b 100644 --- a/src/Deployment/RoslynDeployment.csproj +++ b/src/Deployment/RoslynDeployment.csproj @@ -38,12 +38,6 @@ false VSIXContainerProjectOutputGroup%3b - - VisualStudioInteractiveComponents - Vsixes - false - VSIXContainerProjectOutputGroup%3b - diff --git a/src/Deployment/source.extension.vsixmanifest b/src/Deployment/source.extension.vsixmanifest index 47eb1f09c5b..44745d0ece0 100644 --- a/src/Deployment/source.extension.vsixmanifest +++ b/src/Deployment/source.extension.vsixmanifest @@ -29,15 +29,6 @@ Location="|VisualStudioSetup;VSIXContainerProjectOutputGroup|" Id="0b5e8ddb-f12d-4131-a71d-77acc26a798f" /> - - + public abstract class AbstractCSharpCompletionProviderTests : AbstractCSharpCompletionProviderTests { - protected AbstractCSharpCompletionProviderTests(CSharpTestWorkspaceFixture workspaceFixture) : base(workspaceFixture) + protected AbstractCSharpCompletionProviderTests(CSharpTestWorkspaceFixture workspaceFixture) + : base(workspaceFixture) + { + } + } + + public abstract class AbstractCSharpCompletionProviderTests : AbstractCompletionProviderTests + where TWorkspaceFixture : TestWorkspaceFixture, new() + { + protected AbstractCSharpCompletionProviderTests(TWorkspaceFixture workspaceFixture) + : base(workspaceFixture) { } @@ -135,11 +145,10 @@ protected static string AddUsingDirectives(string usingDirectives, string text) text; } - protected async Task VerifySendEnterThroughToEnterAsync(string initialMarkup, string textTypedSoFar, EnterKeyRule sendThroughEnterOption, bool expected, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) + protected async Task VerifySendEnterThroughToEnterAsync(string initialMarkup, string textTypedSoFar, EnterKeyRule sendThroughEnterOption, bool expected) { - using var workspace = TestWorkspace.CreateCSharp(initialMarkup, exportProvider: ExportProvider); + using var workspace = CreateWorkspace(initialMarkup); var hostDocument = workspace.DocumentWithCursor; - workspace.OnDocumentSourceCodeKindChanged(hostDocument.Id, sourceCodeKind); var documentId = workspace.GetDocumentId(hostDocument); var document = workspace.CurrentSolution.GetDocument(documentId); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractInteractiveCSharpCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractInteractiveCSharpCompletionProviderTests.cs new file mode 100644 index 00000000000..81f8a239f32 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractInteractiveCSharpCompletionProviderTests.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders +{ + public abstract class AbstractInteractiveCSharpCompletionProviderTests : AbstractCSharpCompletionProviderTests + { + protected AbstractInteractiveCSharpCompletionProviderTests(InteractiveCSharpTestWorkspaceFixture workspaceFixture) + : base(workspaceFixture) + { + } + + protected override TestWorkspace CreateWorkspace(string fileContents) + => InteractiveCSharpTestWorkspaceFixture.CreateInteractiveWorkspace(fileContents, exportProvider: ExportProvider); + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs index c5dc672ac52..428d304110f 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs @@ -19,9 +19,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders { [Trait(Traits.Feature, Traits.Features.Completion)] - public class ReferenceDirectiveCompletionProviderTests : AbstractCSharpCompletionProviderTests + public class ReferenceDirectiveCompletionProviderTests : AbstractInteractiveCSharpCompletionProviderTests { - public ReferenceDirectiveCompletionProviderTests(CSharpTestWorkspaceFixture workspaceFixture) : base(workspaceFixture) + public ReferenceDirectiveCompletionProviderTests(InteractiveCSharpTestWorkspaceFixture workspaceFixture) + : base(workspaceFixture) { } @@ -66,7 +67,7 @@ public void IsTextualTriggerCharacterTest(string markup) [InlineData(EnterKeyRule.AfterFullyTypedWord)] [InlineData(EnterKeyRule.Always)] // note: GAC completion helper uses its own EnterKeyRule public async Task SendEnterThroughToEditorTest(EnterKeyRule enterKeyRule) - => await VerifySendEnterThroughToEnterAsync("#r \"System$$", "System", enterKeyRule, expected: false, SourceCodeKind.Script); + => await VerifySendEnterThroughToEnterAsync("#r \"System$$", "System", enterKeyRule, expected: false); [ConditionalFact(typeof(WindowsOnly))] public async Task GacReference() diff --git a/src/EditorFeatures/Core.Wpf/Interactive/CSharpVBResetCommand.cs b/src/EditorFeatures/Core.Wpf/Interactive/CSharpVBResetCommand.cs index bb47dc9a442..34939ceb38b 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/CSharpVBResetCommand.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/CSharpVBResetCommand.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +extern alias InteractiveHost; using System; using System.Collections.Generic; @@ -15,6 +16,8 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Utilities; +using InteractiveHost::Microsoft.CodeAnalysis.Interactive; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Interactive { @@ -27,6 +30,11 @@ internal sealed class ResetCommand : IInteractiveWindowCommand { private const string CommandName = "reset"; private const string NoConfigParameterName = "noconfig"; + private const string PlatformCore = "core"; + private const string PlatformDesktop32 = "32"; + private const string PlatformDesktop64 = "64"; + private const string PlatformNames = PlatformCore + "|" + PlatformDesktop32 + "|" + PlatformDesktop64; + private static readonly int s_noConfigParameterNameLength = NoConfigParameterName.Length; private readonly IStandardClassificationService _registry; @@ -36,44 +44,36 @@ public ResetCommand(IStandardClassificationService registry) => _registry = registry; public string Description - { - get { return InteractiveEditorFeaturesResources.Reset_the_execution_environment_to_the_initial_state_keep_history; } - } + => InteractiveEditorFeaturesResources.Reset_the_execution_environment_to_the_initial_state_keep_history; public IEnumerable DetailedDescription - { - get { return null; } - } + => null; public IEnumerable Names - { - get { yield return CommandName; } - } + => SpecializedCollections.SingletonEnumerable(CommandName); public string CommandLine - { - get { return "[" + NoConfigParameterName + "] [32|64]"; } - } + => "[" + NoConfigParameterName + "] [" + PlatformNames + "]"; public IEnumerable> ParametersDescription { get { yield return new KeyValuePair(NoConfigParameterName, InteractiveEditorFeaturesResources.Reset_to_a_clean_environment_only_mscorlib_referenced_do_not_run_initialization_script); - yield return new KeyValuePair("32|64", $"Interactive host process bitness."); + yield return new KeyValuePair(PlatformNames, InteractiveEditorFeaturesResources.Interactive_host_process_platform); } } public Task Execute(IInteractiveWindow window, string arguments) { - if (!TryParseArguments(arguments, out var initialize, out var is64bit)) + if (!TryParseArguments(arguments, out var initialize, out var platform)) { ReportInvalidArguments(window); return ExecutionResult.Failed; } var evaluator = (InteractiveEvaluator)window.Evaluator; - evaluator.ResetOptions = new InteractiveEvaluatorResetOptions(is64bit); + evaluator.ResetOptions = new InteractiveEvaluatorResetOptions(platform); return window.Operations.ResetAsync(initialize); } @@ -112,33 +112,42 @@ internal static IEnumerable GetNoConfigPositions(string arguments) /// /// Accessibility is internal for testing. /// - internal static bool TryParseArguments(string arguments, out bool initialize, out bool? is64bit) + internal static bool TryParseArguments(string arguments, out bool initialize, out InteractiveHostPlatform? platform) { - is64bit = null; + platform = null; initialize = true; var noConfigSpecified = false; foreach (var argument in arguments.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) { - switch (argument) + switch (argument.ToLowerInvariant()) { - case "32": - if (is64bit != null) + case PlatformDesktop32: + if (platform != null) + { + return false; + } + + platform = InteractiveHostPlatform.Desktop32; + break; + + case PlatformDesktop64: + if (platform != null) { return false; } - is64bit = false; + platform = InteractiveHostPlatform.Desktop64; break; - case "64": - if (is64bit != null) + case PlatformCore: + if (platform != null) { return false; } - is64bit = true; + platform = InteractiveHostPlatform.Core; break; case NoConfigParameterName: @@ -159,8 +168,15 @@ internal static bool TryParseArguments(string arguments, out bool initialize, ou return true; } - internal static string GetCommandLine(bool initialize, bool? is64bit) - => CommandName + (initialize ? "" : " " + NoConfigParameterName) + (is64bit == null ? "" : is64bit.Value ? " 64" : " 32"); + internal static string GetCommandLine(bool initialize, InteractiveHostPlatform? platform) + => CommandName + (initialize ? "" : " " + NoConfigParameterName) + platform switch + { + null => "", + InteractiveHostPlatform.Core => " " + PlatformCore, + InteractiveHostPlatform.Desktop64 => " " + PlatformDesktop64, + InteractiveHostPlatform.Desktop32 => " " + PlatformDesktop32, + _ => throw ExceptionUtilities.Unreachable + }; private void ReportInvalidArguments(IInteractiveWindow window) { diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEditorFeaturesResources.resx b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEditorFeaturesResources.resx index 53349e71cf8..63a5ff35531 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEditorFeaturesResources.resx +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEditorFeaturesResources.resx @@ -147,4 +147,7 @@ The CurrentWindow property may only be assigned once. + + Interactive host process platform + \ No newline at end of file diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluator.cs b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluator.cs index 7188d344bc7..b377fd73520 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluator.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluator.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -extern alias Scripting; + +#nullable enable + extern alias InteractiveHost; +extern alias Scripting; using System; using System.Collections.Generic; @@ -11,15 +14,17 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Implementation.Interactive; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Interactive; -using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.InteractiveWindow.Commands; @@ -30,58 +35,76 @@ namespace Microsoft.CodeAnalysis.Editor.Interactive { - using RelativePathResolver = Scripting::Microsoft.CodeAnalysis.RelativePathResolver; using InteractiveHost::Microsoft.CodeAnalysis.Interactive; + using RelativePathResolver = Scripting::Microsoft.CodeAnalysis.RelativePathResolver; internal abstract class InteractiveEvaluator : IResettableInteractiveEvaluator { private const string CommandPrefix = "#"; - // full path or null - private readonly string _responseFilePath; + private readonly string _responseFileName; private readonly InteractiveHost _interactiveHost; - private readonly string _initialWorkingDirectory; - private string _initialScriptFileOpt; + private readonly string _hostDirectory; private readonly IThreadingContext _threadingContext; private readonly IContentType _contentType; - private readonly InteractiveWorkspace _workspace; - private IInteractiveWindow _currentWindow; - private ImmutableArray _responseFileReferences; - private ImmutableArray _responseFileImports; - private MetadataReferenceResolver _metadataReferenceResolver; - private SourceReferenceResolver _sourceReferenceResolver; - - private ProjectId _previousSubmissionProjectId; - private ProjectId _currentSubmissionProjectId; - private readonly IViewClassifierAggregatorService _classifierAggregator; private readonly IInteractiveWindowCommandsFactory _commandsFactory; private readonly ImmutableArray _commands; - private IInteractiveWindowCommands _interactiveCommands; - private ITextBuffer _currentSubmissionBuffer; + private readonly CancellationTokenSource _shutdownCancellationSource; + + private IInteractiveWindow? _lazyInteractiveWindow; + private IInteractiveWindowCommands? _lazyInteractiveCommands; + + #region UI Thread only /// - /// This is a set because the same buffer might be re-added when the content type is changed. + /// Submission buffers in the order they were submitted. + /// Includes both command buffers as well as language buffers. + /// Does not include the current buffer unless it has been submitted. /// - private readonly HashSet _submissionBuffers = new HashSet(); + private readonly List _submittedBuffers = new List(); - private int _submissionCount = 0; - private readonly EventHandler _contentTypeChangedHandler; + #endregion - internal InteractiveEvaluatorResetOptions ResetOptions { get; set; } - = new InteractiveEvaluatorResetOptions(is64Bit: true); + #region State only accessible by queued tasks + + // Use to serialize InteractiveHost process initialization and code execution. + // The process may restart any time and we need to react to that by clearing + // the current solution and setting up the first submission project. + // At the same time a code submission might be in progress. + // If we left these operations run in parallel we might get into a state + // inconsistent with the state of the host. + private readonly TaskQueue _taskQueue; + private readonly InteractiveWorkspace _workspace; + private ProjectId? _lastSuccessfulSubmissionProjectId; + private ProjectId? _currentSubmissionProjectId; + public int SubmissionCount { get; private set; } + + private RemoteInitializationResult? _initializationResult; + private InteractiveHostPlatformInfo _platformInfo; public ImmutableArray ReferenceSearchPaths { get; private set; } public ImmutableArray SourceSearchPaths { get; private set; } public string WorkingDirectory { get; private set; } + /// + /// Buffers that need to be associated with a submission project once the process initialization completes. + /// + private readonly List<(ITextBuffer buffer, string name)> _pendingBuffers = new List<(ITextBuffer, string)>(); + + #endregion + + internal InteractiveEvaluatorResetOptions ResetOptions { get; set; } + = new InteractiveEvaluatorResetOptions(InteractiveHostPlatform.Desktop64); + InteractiveEvaluatorResetOptions IResettableInteractiveEvaluator.ResetOptions { get => ResetOptions; set => ResetOptions = value; } internal InteractiveEvaluator( IThreadingContext threadingContext, + IAsynchronousOperationListener listener, IContentType contentType, HostServices hostServices, IViewClassifierAggregatorService classifierAggregator, @@ -91,62 +114,51 @@ internal abstract class InteractiveEvaluator : IResettableInteractiveEvaluator string initialWorkingDirectory, Type replType) { - Debug.Assert(responseFileName == null || responseFileName.IndexOfAny(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }) == -1); + Debug.Assert(responseFileName.IndexOfAny(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }) == -1); _threadingContext = threadingContext; + _taskQueue = new TaskQueue(listener, TaskScheduler.Default); + _shutdownCancellationSource = new CancellationTokenSource(); _contentType = contentType; - _responseFilePath = Path.Combine(GetDesktopHostDirectory(), responseFileName); + _responseFileName = responseFileName; _workspace = new InteractiveWorkspace(hostServices, this); - _contentTypeChangedHandler = new EventHandler(LanguageBufferContentTypeChanged); _classifierAggregator = classifierAggregator; - _initialWorkingDirectory = initialWorkingDirectory; _commandsFactory = commandsFactory; _commands = commands; + _hostDirectory = Path.Combine(Path.GetDirectoryName(typeof(InteractiveEvaluator).Assembly.Location), "InteractiveHost"); // The following settings will apply when the REPL starts without .rsp file. // They are discarded once the REPL is reset. ReferenceSearchPaths = ImmutableArray.Empty; SourceSearchPaths = ImmutableArray.Empty; WorkingDirectory = initialWorkingDirectory; - var metadataService = _workspace.CurrentSolution.Services.MetadataService; - _metadataReferenceResolver = CreateMetadataReferenceResolver(metadataService, ReferenceSearchPaths, _initialWorkingDirectory); - _sourceReferenceResolver = CreateSourceReferenceResolver(SourceSearchPaths, _initialWorkingDirectory); _interactiveHost = new InteractiveHost(replType, initialWorkingDirectory); - _interactiveHost.ProcessStarting += ProcessStarting; + _interactiveHost.ProcessInitialized += ProcessInitialized; } - public int SubmissionCount => _submissionCount; + public IContentType ContentType => _contentType; - public IContentType ContentType + public IInteractiveWindow? CurrentWindow { - get - { - return _contentType; - } - } - - public IInteractiveWindow CurrentWindow - { - get - { - return _currentWindow; - } + get => _lazyInteractiveWindow; set { - if (_currentWindow != null) + _threadingContext.ThrowIfNotOnUIThread(); + + if (_lazyInteractiveWindow != null) { throw new NotSupportedException(InteractiveEditorFeaturesResources.The_CurrentWindow_property_may_only_be_assigned_once); } - _currentWindow = value ?? throw new ArgumentNullException(); + _lazyInteractiveWindow = value ?? throw new ArgumentNullException(nameof(value)); _workspace.Window = value; - Task.Run(() => _interactiveHost.SetOutputs(_currentWindow.OutputWriter, _currentWindow.ErrorOutputWriter)); + Task.Run(() => _interactiveHost.SetOutputs(value.OutputWriter, value.ErrorOutputWriter)); - _currentWindow.SubmissionBufferAdded += SubmissionBufferAdded; - _interactiveCommands = _commandsFactory.CreateInteractiveCommands(_currentWindow, CommandPrefix, _commands); + value.SubmissionBufferAdded += SubmissionBufferAdded; + _lazyInteractiveCommands = _commandsFactory.CreateInteractiveCommands(value, CommandPrefix, _commands); } } @@ -156,122 +168,91 @@ public IInteractiveWindow CurrentWindow protected abstract CommandLineParser CommandLineParser { get; } /// - /// Invoked before the process is reset. The argument is the value of . + /// Invoked before the process is reset. The argument is the value of . /// - public event Action OnBeforeReset; + public event Action? OnBeforeReset; #region Initialization - public static string GetConfiguration() - => null; + private IInteractiveWindow GetInteractiveWindow() + => _lazyInteractiveWindow ?? throw new InvalidOperationException(EditorFeaturesResources.Engine_must_be_attached_to_an_Interactive_Window); - private IInteractiveWindow GetCurrentWindowOrThrow() - { - var window = _currentWindow; - if (window == null) - { - throw new InvalidOperationException(EditorFeaturesResources.Engine_must_be_attached_to_an_Interactive_Window); - } - - return window; - } + private IInteractiveWindowCommands GetInteractiveCommands() + => _lazyInteractiveCommands ?? throw new InvalidOperationException(EditorFeaturesResources.Engine_must_be_attached_to_an_Interactive_Window); public void Dispose() { + _shutdownCancellationSource.Cancel(); + _shutdownCancellationSource.Dispose(); + _workspace.Dispose(); _interactiveHost.Dispose(); - if (_currentWindow != null) + var interactiveWindow = _lazyInteractiveWindow; + if (interactiveWindow != null) { - _currentWindow.SubmissionBufferAdded -= SubmissionBufferAdded; + interactiveWindow.SubmissionBufferAdded -= SubmissionBufferAdded; } } - /// - /// Invoked by when a new process is being started. - /// - private void ProcessStarting(bool initialize) + private void CaptureClassificationSpans() { - var textView = GetCurrentWindowOrThrow().TextView; + _threadingContext.ThrowIfNotOnUIThread(); - if (!_threadingContext.JoinableTaskContext.IsOnMainThread) - { - _threadingContext.JoinableTaskFactory.RunAsync(async () => - { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - ProcessStarting(initialize); - }); - - return; - } + var textView = GetInteractiveWindow().TextView; // Freeze all existing classifications and then clear the list of submission buffers we have. - _submissionBuffers.Remove(_currentSubmissionBuffer); // if present - foreach (var textBuffer in _submissionBuffers) + foreach (var textBuffer in _submittedBuffers) { InertClassifierProvider.CaptureExistingClassificationSpans(_classifierAggregator, textView, textBuffer); } - _submissionBuffers.Clear(); - // We always start out empty - _workspace.ClearSolution(); - _currentSubmissionProjectId = null; - _previousSubmissionProjectId = null; - - var metadataService = _workspace.CurrentSolution.Services.MetadataService; - var mscorlibRef = metadataService.GetReference(typeof(object).Assembly.Location, MetadataReferenceProperties.Assembly); - var interactiveHostObjectRef = metadataService.GetReference(typeof(InteractiveScriptGlobals).Assembly.Location, Script.HostAssemblyReferenceProperties); + _submittedBuffers.Clear(); + } - _responseFileReferences = ImmutableArray.Create(mscorlibRef, interactiveHostObjectRef); - _responseFileImports = ImmutableArray.Empty; - _initialScriptFileOpt = null; - ReferenceSearchPaths = ImmutableArray.Empty; - SourceSearchPaths = ImmutableArray.Empty; + /// + /// Invoked by when a new process initialization completes. + /// + private void ProcessInitialized(InteractiveHostPlatformInfo platformInfo, InteractiveHostOptions options, RemoteExecutionResult result) + { + Contract.ThrowIfFalse(result.InitializationResult != null); - if (initialize && File.Exists(_responseFilePath)) + _ = _threadingContext.JoinableTaskFactory.RunAsync(async () => { - // The base directory for relative paths is the directory that contains the .rsp file. - // Note that .rsp files included by this .rsp file will share the base directory (Dev10 behavior of csc/vbc). - var responseFileDirectory = Path.GetDirectoryName(_responseFilePath); - var args = this.CommandLineParser.Parse(new[] { "@" + _responseFilePath }, responseFileDirectory, RuntimeEnvironment.GetRuntimeDirectory(), null); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + CaptureClassificationSpans(); + }); - if (args.Errors.Length == 0) + _ = _taskQueue.ScheduleTask(nameof(ProcessInitialized), () => + { + // clear workspace state: + _workspace.ClearSolution(); + _currentSubmissionProjectId = null; + _lastSuccessfulSubmissionProjectId = null; + + // update host state: + _platformInfo = platformInfo; + _initializationResult = result.InitializationResult; + UpdatePathsNoLock(result); + + // Create submission projects for buffers that were added by the Interactive Window + // before the process initialization completed. + foreach (var (buffer, languageName) in _pendingBuffers) { - var metadataResolver = CreateMetadataReferenceResolver(metadataService, args.ReferencePaths, responseFileDirectory); - var sourceResolver = CreateSourceReferenceResolver(args.SourcePaths, responseFileDirectory); - - // ignore unresolved references, they will be reported in the interactive window: - var responseFileReferences = args.ResolveMetadataReferences(metadataResolver).Where(r => !(r is UnresolvedMetadataReference)); - - _initialScriptFileOpt = args.SourceFiles.IsEmpty ? null : args.SourceFiles[0].Path; - - ReferenceSearchPaths = args.ReferencePaths; - SourceSearchPaths = args.SourcePaths; - - _responseFileReferences = _responseFileReferences.AddRange(responseFileReferences); - _responseFileImports = CommandLineHelpers.GetImports(args); + AddSubmissionProjectNoLock(buffer, languageName); } - } - - _metadataReferenceResolver = CreateMetadataReferenceResolver(metadataService, ReferenceSearchPaths, _initialWorkingDirectory); - _sourceReferenceResolver = CreateSourceReferenceResolver(SourceSearchPaths, _initialWorkingDirectory); - // create the first submission project in the workspace after reset: - if (_currentSubmissionBuffer != null) - { - AddSubmission(_currentSubmissionBuffer, this.LanguageName); - } + _pendingBuffers.Clear(); + }, _shutdownCancellationSource.Token); } - private static MetadataReferenceResolver CreateMetadataReferenceResolver(IMetadataService metadataService, ImmutableArray searchPaths, string baseDirectory) + private static RuntimeMetadataReferenceResolver CreateMetadataReferenceResolver(IMetadataService metadataService, InteractiveHostPlatformInfo platformInfo, ImmutableArray searchPaths, string baseDirectory) { - // TODO: To support CoreCLR we need to query the remote process for TPA list and pass it to the resolver. - // https://github.com/dotnet/roslyn/issues/4788 return new RuntimeMetadataReferenceResolver( - new RelativePathResolver(searchPaths, baseDirectory), - packageResolver: null, - gacFileResolver: GacFileResolver.IsAvailable ? new GacFileResolver(preferredCulture: CultureInfo.CurrentCulture) : null, - useCoreResolver: false, + searchPaths, + baseDirectory, + gacFileResolver: platformInfo.HasGlobalAssemblyCache ? new GacFileResolver(preferredCulture: CultureInfo.CurrentCulture) : null, + platformAssemblyPaths: platformInfo.PlatformAssemblyPaths, fileReferenceProvider: (path, properties) => metadataService.GetReference(path, properties)); } @@ -282,121 +263,108 @@ private static SourceReferenceResolver CreateSourceReferenceResolver(ImmutableAr #region Workspace + /// + /// Invoked on UI thread when a new language buffer is created and before it is added to the projection. + /// private void SubmissionBufferAdded(object sender, SubmissionBufferAddedEventArgs args) - => AddSubmission(args.NewBuffer, this.LanguageName); + { + _threadingContext.ThrowIfNotOnUIThread(); + + _taskQueue.ScheduleTask(nameof(SubmissionBufferAdded), () => AddSubmissionProjectNoLock(args.NewBuffer, LanguageName), _shutdownCancellationSource.Token); + } - // The REPL window might change content type to host command content type (when a host command is typed at the beginning of the buffer). - private void LanguageBufferContentTypeChanged(object sender, ContentTypeChangedEventArgs e) + private void AddSubmissionProjectNoLock(ITextBuffer submissionBuffer, string languageName) { - // It's not clear whether this situation will ever happen, but just in case. - if (e.BeforeContentType == e.AfterContentType) + var solution = _workspace.CurrentSolution; + Project project; + var imports = ImmutableArray.Empty; + var references = ImmutableArray.Empty; + + if (_currentSubmissionProjectId == null) { - return; - } + Debug.Assert(_lastSuccessfulSubmissionProjectId == null); - var buffer = e.Before.TextBuffer; - var contentTypeName = this.ContentType.TypeName; + // The Interactive Window may have added the first language buffer before + // the host initialization has completed. Do not create a submission project + // for the buffer in such case. It will be created when the initialization completes. + if (_initializationResult == null) + { + _pendingBuffers.Add((submissionBuffer, languageName)); + return; + } - var afterIsLanguage = e.AfterContentType.IsOfType(contentTypeName); - var afterIsInteractiveCommand = e.AfterContentType.IsOfType(PredefinedInteractiveCommandsContentTypes.InteractiveCommandContentTypeName); - var beforeIsLanguage = e.BeforeContentType.IsOfType(contentTypeName); - var beforeIsInteractiveCommand = e.BeforeContentType.IsOfType(PredefinedInteractiveCommandsContentTypes.InteractiveCommandContentTypeName); + var initResult = _initializationResult; - Debug.Assert((afterIsLanguage && beforeIsInteractiveCommand) - || (beforeIsLanguage && afterIsInteractiveCommand)); + imports = initResult.Imports.ToImmutableArrayOrEmpty(); - // We're switching between the target language and the Interactive Command "language". - // First, remove the current submission from the solution. + var metadataService = _workspace.Services.GetRequiredService(); + references = initResult.MetadataReferencePaths.ToImmutableArrayOrEmpty().SelectAsArray( + (path, metadataService) => (MetadataReference)metadataService.GetReference(path, MetadataReferenceProperties.Assembly), + metadataService); - var documentId = _workspace.GetDocumentIdInCurrentContext(buffer.AsTextContainer()); - var oldSolution = _workspace.CurrentSolution; - var relatedDocumentIds = oldSolution.GetRelatedDocumentIds(documentId); + // if a script was specified in .rps file insert a project with a document that represents it: + var scriptPath = initResult.ScriptPath; + if (scriptPath != null) + { + project = CreateSubmissionProjectNoLock(solution, previousSubmissionProjectId: null, languageName, imports, references); - var newSolution = oldSolution; + var initDocumentId = DocumentId.CreateNewId(project.Id, debugName: scriptPath); + solution = project.Solution.AddDocument(initDocumentId, Path.GetFileName(scriptPath), new FileTextLoader(scriptPath, defaultEncoding: null)); + _lastSuccessfulSubmissionProjectId = project.Id; - foreach (var relatedDocumentId in relatedDocumentIds) - { - Debug.Assert(relatedDocumentId != null); + // imports and references will be inherited: + imports = ImmutableArray.Empty; + references = ImmutableArray.Empty; + } + } - newSolution = newSolution.RemoveDocument(relatedDocumentId); + // Project for the new submission - chain to the last submission that successfully executed. + project = CreateSubmissionProjectNoLock(solution, _lastSuccessfulSubmissionProjectId, languageName, imports, references); - // TODO (tomat): Is there a better way to remove mapping between buffer and document in REPL? - // Perhaps TrackingWorkspace should implement RemoveDocumentAsync? - _workspace.ClearOpenDocument(relatedDocumentId); - } + var documentId = DocumentId.CreateNewId(project.Id, debugName: project.Name); + solution = project.Solution.AddDocument(documentId, project.Name, submissionBuffer.CurrentSnapshot.AsText()); - // Next, remove the previous submission project and update the workspace. - newSolution = newSolution.RemoveProject(_currentSubmissionProjectId); - _workspace.SetCurrentSolution(newSolution); + _workspace.SetCurrentSolution(solution); - // Add a new submission with the correct language for the current buffer. - var languageName = afterIsLanguage - ? this.LanguageName - : InteractiveLanguageNames.InteractiveCommand; + // opening document will start workspace listening to changes in this text container + _workspace.OpenDocument(documentId, submissionBuffer.AsTextContainer()); - AddSubmission(buffer, languageName); + _currentSubmissionProjectId = project.Id; } - private void AddSubmission(ITextBuffer subjectBuffer, string languageName) + private Project CreateSubmissionProjectNoLock(Solution solution, ProjectId? previousSubmissionProjectId, string languageName, ImmutableArray imports, ImmutableArray references) { - var solution = _workspace.CurrentSolution; - Project project; - ImmutableArray imports; - ImmutableArray references; + var name = "Submission#" + SubmissionCount++; - if (_previousSubmissionProjectId != null) - { - // only the first project needs imports and references - imports = ImmutableArray.Empty; - references = ImmutableArray.Empty; - } - else if (_initialScriptFileOpt != null) + CompilationOptions compilationOptions; + if (previousSubmissionProjectId != null) { - // insert a project for initialization script listed in .rsp: - project = CreateSubmissionProject(solution, languageName, _responseFileImports, _responseFileReferences); - var documentId = DocumentId.CreateNewId(project.Id, debugName: _initialScriptFileOpt); - solution = project.Solution.AddDocument(documentId, Path.GetFileName(_initialScriptFileOpt), new FileTextLoader(_initialScriptFileOpt, defaultEncoding: null)); - _previousSubmissionProjectId = project.Id; - - imports = ImmutableArray.Empty; - references = ImmutableArray.Empty; - } - else - { - imports = _responseFileImports; - references = _responseFileReferences; - } - - // project for the new submission: - project = CreateSubmissionProject(solution, languageName, imports, references); + compilationOptions = solution.GetRequiredProject(previousSubmissionProjectId).CompilationOptions!; - // Keep track of this buffer so we can freeze the classifications for it in the future. - _submissionBuffers.Add(subjectBuffer); - - SetSubmissionDocument(subjectBuffer, project); - - _currentSubmissionProjectId = project.Id; + var metadataResolver = (RuntimeMetadataReferenceResolver)compilationOptions.MetadataReferenceResolver!; + if (metadataResolver.PathResolver.BaseDirectory != WorkingDirectory || + !metadataResolver.PathResolver.SearchPaths.SequenceEqual(ReferenceSearchPaths)) + { + compilationOptions = compilationOptions.WithMetadataReferenceResolver(metadataResolver.WithRelativePathResolver(new RelativePathResolver(ReferenceSearchPaths, WorkingDirectory))); + } - if (_currentSubmissionBuffer != null) + var sourceResolver = (SourceFileResolver)compilationOptions.SourceReferenceResolver!; + if (sourceResolver.BaseDirectory != WorkingDirectory || + !sourceResolver.SearchPaths.SequenceEqual(SourceSearchPaths)) + { + compilationOptions = compilationOptions.WithSourceReferenceResolver(CreateSourceReferenceResolver(sourceResolver.SearchPaths, WorkingDirectory)); + } + } + else { - _currentSubmissionBuffer.ContentTypeChanged -= _contentTypeChangedHandler; + var metadataService = _workspace.Services.GetRequiredService(); + compilationOptions = GetSubmissionCompilationOptions( + name, + CreateMetadataReferenceResolver(metadataService, _platformInfo, ReferenceSearchPaths, WorkingDirectory), + CreateSourceReferenceResolver(SourceSearchPaths, WorkingDirectory), + imports); } - subjectBuffer.ContentTypeChanged += _contentTypeChangedHandler; - - _currentSubmissionBuffer = subjectBuffer; - } - - private Project CreateSubmissionProject(Solution solution, string languageName, ImmutableArray imports, ImmutableArray references) - { - var name = "Submission#" + _submissionCount++; - - // Grab a local copy so we aren't closing over the field that might change. The - // collection itself is an immutable collection. - var localCompilationOptions = GetSubmissionCompilationOptions(name, _metadataReferenceResolver, _sourceReferenceResolver, imports); - - var localParseOptions = ParseOptions; - var projectId = ProjectId.CreateNewId(debugName: name); solution = solution.AddProject( @@ -406,32 +374,20 @@ private Project CreateSubmissionProject(Solution solution, string languageName, name: name, assemblyName: name, language: languageName, - compilationOptions: localCompilationOptions, - parseOptions: localParseOptions, + compilationOptions: compilationOptions, + parseOptions: ParseOptions, documents: null, projectReferences: null, metadataReferences: references, hostObjectType: typeof(InteractiveScriptGlobals), isSubmission: true)); - if (_previousSubmissionProjectId != null) + if (previousSubmissionProjectId != null) { - solution = solution.AddProjectReference(projectId, new ProjectReference(_previousSubmissionProjectId)); + solution = solution.AddProjectReference(projectId, new ProjectReference(previousSubmissionProjectId)); } - return solution.GetProject(projectId); - } - - private void SetSubmissionDocument(ITextBuffer buffer, Project project) - { - var documentId = DocumentId.CreateNewId(project.Id, debugName: project.Name); - var solution = project.Solution - .AddDocument(documentId, project.Name, buffer.CurrentSnapshot.AsText()); - - _workspace.SetCurrentSolution(solution); - - // opening document will start workspace listening to changes in this text container - _workspace.OpenDocument(documentId, buffer.AsTextContainer()); + return solution.GetProject(projectId)!; } #endregion @@ -440,58 +396,67 @@ private void SetSubmissionDocument(ITextBuffer buffer, Project project) public virtual bool CanExecuteCode(string text) { - if (_interactiveCommands != null && _interactiveCommands.InCommand) - { - return true; - } - return false; + _threadingContext.ThrowIfNotOnUIThread(); + + return _lazyInteractiveCommands?.InCommand == true; } + /// + /// Invoked when the Interactive Window is created. + /// Task IInteractiveEvaluator.InitializeAsync() { - var window = GetCurrentWindowOrThrow(); + _threadingContext.ThrowIfNotOnUIThread(); + + var window = GetInteractiveWindow(); var resetOptions = ResetOptions; _interactiveHost.SetOutputs(window.OutputWriter, window.ErrorOutputWriter); - return ResetCoreAsync(GetHostOptions(initialize: true, resetOptions.Is64Bit)); + return ResetCoreAsync(GetHostOptions(initialize: true, resetOptions.Platform)); } + /// + /// Invoked by the reset toolbar button. + /// Task IInteractiveEvaluator.ResetAsync(bool initialize) { - var window = GetCurrentWindowOrThrow(); + _threadingContext.ThrowIfNotOnUIThread(); + + var window = GetInteractiveWindow(); var resetOptions = ResetOptions; - Debug.Assert(_interactiveCommands.CommandPrefix == CommandPrefix); - window.AddInput(CommandPrefix + ResetCommand.GetCommandLine(initialize, resetOptions.Is64Bit)); + Debug.Assert(GetInteractiveCommands().CommandPrefix == CommandPrefix); + window.AddInput(CommandPrefix + ResetCommand.GetCommandLine(initialize, resetOptions.Platform)); window.WriteLine(InteractiveEditorFeaturesResources.Resetting_execution_engine); window.FlushOutput(); - return ResetCoreAsync(GetHostOptions(initialize, resetOptions.Is64Bit)); + return ResetCoreAsync(GetHostOptions(initialize, resetOptions.Platform)); } - private static string GetDesktopHostDirectory() - => Path.Combine(Path.GetDirectoryName(typeof(InteractiveEvaluator).Assembly.Location), "DesktopHost"); - - public InteractiveHostOptions GetHostOptions(bool initialize, bool? is64bit) - => new InteractiveHostOptions( - hostDirectory: _interactiveHost.OptionsOpt?.HostDirectory ?? GetDesktopHostDirectory(), - initializationFile: initialize ? _responseFilePath : null, - culture: CultureInfo.CurrentUICulture, - is64Bit: is64bit ?? _interactiveHost.OptionsOpt?.Is64Bit ?? InteractiveHost.DefaultIs64Bit); + public InteractiveHostOptions GetHostOptions(bool initialize, InteractiveHostPlatform? platform) + => InteractiveHostOptions.CreateFromDirectory( + _hostDirectory, + initialize ? _responseFileName : null, + CultureInfo.CurrentUICulture, + platform ?? _interactiveHost.OptionsOpt?.Platform ?? InteractiveHost.DefaultPlatform); private async Task ResetCoreAsync(InteractiveHostOptions options) { try { - OnBeforeReset(options.Is64Bit); + _threadingContext.ThrowIfNotOnUIThread(); + + OnBeforeReset?.Invoke(options.Platform); + + // Do not queue reset operation - invoke it directly. + // Code execution might be in progress when the user requests reset (via a reset button, or process terminating on its own). + // We need the execution to be interrupted and the process restarted, not wait for it to complete. var result = await _interactiveHost.ResetAsync(options).ConfigureAwait(false); - if (result.Success) - { - UpdateResolvers(result); - } + // Note: Not calling UpdatePathsNoLock here. The paths will be updated by ProcessInitialized + // which is executed once the new host process finishes its initialization. return new ExecutionResult(result.Success); } @@ -501,36 +466,50 @@ private async Task ResetCoreAsync(InteractiveHostOptions option } } + /// + /// Called on UI thread by the Interactive Window once a code snippet is submitted. + /// Followed on UI thread by creation of a new language buffer and call to . + /// public async Task ExecuteCodeAsync(string text) { try { - if (_interactiveCommands.InCommand) + _threadingContext.ThrowIfNotOnUIThread(); + + var window = GetInteractiveWindow(); + var commands = GetInteractiveCommands(); + + var currentSubmissionBuffer = window.CurrentLanguageBuffer; + Contract.ThrowIfNull(currentSubmissionBuffer); + _submittedBuffers.Add(currentSubmissionBuffer); + + if (commands.InCommand) { - var cmdResult = _interactiveCommands.TryExecuteCommand(); - if (cmdResult != null) + // Takes the content of the current language buffer, parses it as a command + // and returns a task that execute the command, or null if the text doesn't parse. + var commandTask = commands.TryExecuteCommand(); + if (commandTask != null) { - return await cmdResult.ConfigureAwait(false); + return await commandTask.ConfigureAwait(false); } } - var result = await _interactiveHost.ExecuteAsync(text).ConfigureAwait(false); + // If process initialization is in progress we will wait with code + // execution after the initialization is completed. - if (result.Success) + return await _taskQueue.ScheduleTask(nameof(ExecuteCodeAsync), async () => { - // We are not executing a command (the current content type is not "Interactive Command"), - // so the source document should not have been removed. - Debug.Assert(_workspace.CurrentSolution.GetProject(_currentSubmissionProjectId).HasDocuments); - - // only remember the submission if we compiled successfully, otherwise we - // ignore it's id so we don't reference it in the next submission. - _previousSubmissionProjectId = _currentSubmissionProjectId; + var result = await _interactiveHost.ExecuteAsync(text).ConfigureAwait(false); + if (result.Success) + { + _lastSuccessfulSubmissionProjectId = _currentSubmissionProjectId; - // update local search paths - remote paths has already been updated - UpdateResolvers(result); - } + // update local search paths - remote paths has already been updated + UpdatePathsNoLock(result); + } - return new ExecutionResult(result.Success); + return new ExecutionResult(result.Success); + }, _shutdownCancellationSource.Token).ConfigureAwait(false); } catch (Exception e) when (FatalError.Report(e)) { @@ -543,7 +522,7 @@ public void AbortExecution() // TODO (https://github.com/dotnet/roslyn/issues/4725) } - public string FormatClipboard() + public string? FormatClipboard() { // keep the clipboard content as is return null; @@ -551,62 +530,11 @@ public string FormatClipboard() #endregion - #region Paths, Resolvers - - private void UpdateResolvers(RemoteExecutionResult result) - => UpdateResolvers(result.ChangedReferencePaths.AsImmutableOrNull(), result.ChangedSourcePaths.AsImmutableOrNull(), result.ChangedWorkingDirectory); - - private void UpdateResolvers(ImmutableArray changedReferenceSearchPaths, ImmutableArray changedSourceSearchPaths, string changedWorkingDirectory) + private void UpdatePathsNoLock(RemoteExecutionResult result) { - if (changedReferenceSearchPaths.IsDefault && changedSourceSearchPaths.IsDefault && changedWorkingDirectory == null) - { - return; - } - - var solution = _workspace.CurrentSolution; - - // Maybe called after reset, when no submissions are available. - var optionsOpt = (_currentSubmissionProjectId != null) ? solution.GetProjectState(_currentSubmissionProjectId).CompilationOptions : null; - - if (changedWorkingDirectory != null) - { - WorkingDirectory = changedWorkingDirectory; - } - - if (!changedReferenceSearchPaths.IsDefault) - { - ReferenceSearchPaths = changedReferenceSearchPaths; - } - - if (!changedSourceSearchPaths.IsDefault) - { - SourceSearchPaths = changedSourceSearchPaths; - } - - if (!changedReferenceSearchPaths.IsDefault || changedWorkingDirectory != null) - { - _metadataReferenceResolver = CreateMetadataReferenceResolver(_workspace.CurrentSolution.Services.MetadataService, ReferenceSearchPaths, WorkingDirectory); - - if (optionsOpt != null) - { - optionsOpt = optionsOpt.WithMetadataReferenceResolver(_metadataReferenceResolver); - } - } - - if (!changedSourceSearchPaths.IsDefault || changedWorkingDirectory != null) - { - _sourceReferenceResolver = CreateSourceReferenceResolver(SourceSearchPaths, WorkingDirectory); - - if (optionsOpt != null) - { - optionsOpt = optionsOpt.WithSourceReferenceResolver(_sourceReferenceResolver); - } - } - - if (optionsOpt != null) - { - _workspace.SetCurrentSolution(solution.WithProjectCompilationOptions(_currentSubmissionProjectId, optionsOpt)); - } + WorkingDirectory = result.WorkingDirectory; + ReferenceSearchPaths = result.ReferencePaths; + SourceSearchPaths = result.SourcePaths; } public async Task SetPathsAsync(ImmutableArray referenceSearchPaths, ImmutableArray sourceSearchPaths, string workingDirectory) @@ -614,7 +542,7 @@ public async Task SetPathsAsync(ImmutableArray referenceSearchPaths, Imm try { var result = await _interactiveHost.SetPathsAsync(referenceSearchPaths.ToArray(), sourceSearchPaths.ToArray(), workingDirectory).ConfigureAwait(false); - UpdateResolvers(result); + UpdatePathsNoLock(result); } catch (Exception e) when (FatalError.Report(e)) { @@ -624,12 +552,10 @@ public async Task SetPathsAsync(ImmutableArray referenceSearchPaths, Imm public string GetPrompt() { - var buffer = GetCurrentWindowOrThrow().CurrentLanguageBuffer; + var buffer = GetInteractiveWindow().CurrentLanguageBuffer; return buffer != null && buffer.CurrentSnapshot.LineCount > 1 ? ". " : "> "; } - - #endregion } } diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluatorResetOptions.cs b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluatorResetOptions.cs index 19e77d60667..e7c534574b4 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluatorResetOptions.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveEvaluatorResetOptions.cs @@ -1,13 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +#nullable enable + +extern alias InteractiveHost; +using InteractiveHost::Microsoft.CodeAnalysis.Interactive; + namespace Microsoft.CodeAnalysis.Editor.Interactive { internal sealed class InteractiveEvaluatorResetOptions { - public bool? Is64Bit; + public readonly InteractiveHostPlatform? Platform; - public InteractiveEvaluatorResetOptions(bool? is64Bit) - => Is64Bit = is64Bit; + public InteractiveEvaluatorResetOptions(InteractiveHostPlatform? platform) + => Platform = platform; } } diff --git a/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs b/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs index 8ce1fb6a517..eaad6179621 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/ResetInteractive.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +extern alias InteractiveHost; using System; using System.Linq; @@ -14,7 +15,7 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; using System.Collections.Generic; -using Microsoft.CodeAnalysis; +using InteractiveHost::Microsoft.CodeAnalysis.Interactive; namespace Microsoft.VisualStudio.LanguageServices.Interactive { @@ -41,7 +42,7 @@ internal ResetInteractive(IEditorOptionsFactoryService editorOptionsFactoryServi internal Task ExecuteAsync(IInteractiveWindow interactiveWindow, string title) { - if (GetProjectProperties(out var references, out var referenceSearchPaths, out var sourceSearchPaths, out var projectNamespaces, out var projectDirectory, out var is64Bit)) + if (GetProjectProperties(out var references, out var referenceSearchPaths, out var sourceSearchPaths, out var projectNamespaces, out var projectDirectory, out var platform)) { // Now, we're going to do a bunch of async operations. So create a wait // indicator so the user knows something is happening, and also so they cancel. @@ -55,7 +56,7 @@ internal Task ExecuteAsync(IInteractiveWindow interactiveWindow, string title) sourceSearchPaths, projectNamespaces, projectDirectory, - is64Bit, + platform, waitContext); // Once we're done resetting, dismiss the wait indicator and focus the REPL window. @@ -78,7 +79,7 @@ internal Task ExecuteAsync(IInteractiveWindow interactiveWindow, string title) ImmutableArray sourceSearchPaths, ImmutableArray projectNamespaces, string projectDirectory, - bool? is64Bit, + InteractiveHostPlatform? platform, IWaitContext waitContext) { // First, open the repl window. @@ -100,7 +101,7 @@ internal Task ExecuteAsync(IInteractiveWindow interactiveWindow, string title) // Then reset the REPL waitContext.Message = InteractiveEditorFeaturesResources.Resetting_Interactive; - evaluator.ResetOptions = new InteractiveEvaluatorResetOptions(is64Bit); + evaluator.ResetOptions = new InteractiveEvaluatorResetOptions(platform); await interactiveWindow.Operations.ResetAsync(initialize: true).ConfigureAwait(true); // TODO: load context from an rsp file. @@ -136,7 +137,7 @@ internal Task ExecuteAsync(IInteractiveWindow interactiveWindow, string title) out ImmutableArray sourceSearchPaths, out ImmutableArray projectNamespaces, out string projectDirectory, - out bool? is64bit); + out InteractiveHostPlatform? platform); /// /// A method that should trigger an async project build. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.cs.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.cs.xlf index a6df628d2d8..422ad8daadb 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.cs.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.cs.xlf @@ -17,6 +17,11 @@ Výběr se spouští v interaktivním okně. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. Vytiskne seznam odkazovaných sestavení. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.de.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.de.xlf index fdba8764e43..4ecf141117a 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.de.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.de.xlf @@ -17,6 +17,11 @@ Die Auswahl wird in Interactive Window ausgeführt. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. Gibt eine Liste der Assemblys aus, auf die verwiesen wird. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.es.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.es.xlf index a6314a154f5..1adad191003 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.es.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.es.xlf @@ -17,6 +17,11 @@ Ejecutando selección en ventana interactiva. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. Imprima una lista de ensamblados de referencia. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.fr.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.fr.xlf index 80e9c9ba6f3..447a98857bd 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.fr.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.fr.xlf @@ -17,6 +17,11 @@ Exécution de la sélection dans la fenêtre interactive. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. Affichez la liste des assemblys référencés. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.it.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.it.xlf index 2fd684d7aeb..b0a6159f5b3 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.it.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.it.xlf @@ -17,6 +17,11 @@ Esecuzione della selezione nella finestra interattiva. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. Stampa un elenco di tutti gli assembly di riferimento. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ja.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ja.xlf index 4e95a67b723..d8ad83908ca 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ja.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ja.xlf @@ -17,6 +17,11 @@ 対話型ウィンドウで選択を実行します。 + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. 参照アセンブリの一覧を印刷します。 diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ko.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ko.xlf index 04706b03a51..7f8e47459d5 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ko.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ko.xlf @@ -17,6 +17,11 @@ 선택 영역을 대화형 창에서 실행하는 중입니다. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. 참조된 어셈블리의 목록을 인쇄합니다. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.pl.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.pl.xlf index de72b4e516a..553b7c74a0d 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.pl.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.pl.xlf @@ -17,6 +17,11 @@ Wykonywanie zaznaczenia w oknie interaktywnym. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. Wydrukuj listę przywoływanych zestawów. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.pt-BR.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.pt-BR.xlf index 203e3f42c5e..b5b674169be 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.pt-BR.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.pt-BR.xlf @@ -17,6 +17,11 @@ Executando seleção na Janela Interativa. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. Imprima uma lista de assemblies referenciados. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ru.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ru.xlf index fc241a807f9..c3d951dedef 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ru.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.ru.xlf @@ -17,6 +17,11 @@ Выполнение выделенного фрагмента в интерактивном окне. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. Печать списка сборок, на которые указывают ссылки. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.tr.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.tr.xlf index 37a168919db..4d7f5a631eb 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.tr.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.tr.xlf @@ -17,6 +17,11 @@ Seçim, Etkileşimli Pencere'de yürütülüyor. + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. Başvurulan bütünleştirilmiş kodların listesini yazdırın. diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.zh-Hans.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.zh-Hans.xlf index c43d56232a1..5f136ff5316 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.zh-Hans.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.zh-Hans.xlf @@ -17,6 +17,11 @@ 在交互式窗口中执行所选内容。 + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. 打印引用程序集的列表。 diff --git a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.zh-Hant.xlf b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.zh-Hant.xlf index 5d05d4bfd96..553697aed5c 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.zh-Hant.xlf +++ b/src/EditorFeatures/Core.Wpf/Interactive/xlf/InteractiveEditorFeaturesResources.zh-Hant.xlf @@ -17,6 +17,11 @@ 正在於互動式視窗中執行選取範圍。 + + Interactive host process platform + Interactive host process platform + + Print a list of referenced assemblies. 列印參考組件的清單。 diff --git a/src/EditorFeatures/Core/Implementation/Interactive/Completion/AbstractReferenceDirectiveCompletionProvider.cs b/src/EditorFeatures/Core/Implementation/Interactive/Completion/AbstractReferenceDirectiveCompletionProvider.cs index 1d0b2aabca1..ca4cf66d7e1 100644 --- a/src/EditorFeatures/Core/Implementation/Interactive/Completion/AbstractReferenceDirectiveCompletionProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Interactive/Completion/AbstractReferenceDirectiveCompletionProvider.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; using Roslyn.Utilities; @@ -47,15 +48,25 @@ private static ImmutableArray GetCommitCharacters() protected override async Task ProvideCompletionsAsync(CompletionContext context, string pathThroughLastSlash) { - if (GacFileResolver.IsAvailable && pathThroughLastSlash.IndexOfAny(s_pathIndicators) < 0) + var resolver = context.Document.Project.CompilationOptions.MetadataReferenceResolver as RuntimeMetadataReferenceResolver; + if (resolver != null && pathThroughLastSlash.IndexOfAny(s_pathIndicators) < 0) { - var gacHelper = new GlobalAssemblyCacheCompletionHelper(s_rules); - context.AddItems(await gacHelper.GetItemsAsync(pathThroughLastSlash, context.CancellationToken).ConfigureAwait(false)); + foreach (var (name, path) in resolver.TrustedPlatformAssemblies) + { + context.AddItem(CommonCompletionItem.Create(name, displayTextSuffix: "", glyph: Glyph.Assembly, rules: s_rules)); + context.AddItem(CommonCompletionItem.Create(PathUtilities.GetFileName(path, includeExtension: true), displayTextSuffix: "", glyph: Glyph.Assembly, rules: s_rules)); + } + + if (resolver.GacFileResolver is object) + { + var gacHelper = new GlobalAssemblyCacheCompletionHelper(s_rules); + context.AddItems(await gacHelper.GetItemsAsync(pathThroughLastSlash, context.CancellationToken).ConfigureAwait(false)); + } } if (pathThroughLastSlash.IndexOf(',') < 0) { - var helper = GetFileSystemCompletionHelper(context.Document, Glyph.Assembly, ImmutableArray.Create(".dll", ".exe"), s_rules); + var helper = GetFileSystemCompletionHelper(context.Document, Glyph.Assembly, RuntimeMetadataReferenceResolver.AssemblyExtensions, s_rules); context.AddItems(await helper.GetItemsAsync(pathThroughLastSlash, context.CancellationToken).ConfigureAwait(false)); } } diff --git a/src/EditorFeatures/TestUtilities/Workspaces/InteractiveCSharpTestWorkspaceFixture.cs b/src/EditorFeatures/TestUtilities/Workspaces/InteractiveCSharpTestWorkspaceFixture.cs new file mode 100644 index 00000000000..1ce56be57f0 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/Workspaces/InteractiveCSharpTestWorkspaceFixture.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Xml.Linq; +using Microsoft.VisualStudio.Composition; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +{ + public class InteractiveCSharpTestWorkspaceFixture : CSharpTestWorkspaceFixture + { + internal static TestWorkspace CreateInteractiveWorkspace(string fileContent, ExportProvider exportProvider) + { + var workspaceDefinition = $@" + + + + + +"; + return TestWorkspace.Create(XElement.Parse(workspaceDefinition), exportProvider: exportProvider, workspaceKind: WorkspaceKind.Interactive); + } + + protected override TestWorkspace CreateWorkspace(ExportProvider exportProvider = null) + => CreateInteractiveWorkspace(fileContent: "", exportProvider); + } +} diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs index 1cbd9f948ba..216e0df2ef7 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs @@ -12,12 +12,14 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.ServiceModel.Description; using System.Threading; using System.Xml.Linq; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Scripting.Hosting; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; @@ -215,9 +217,13 @@ public static TestWorkspace Create(string xmlDefinition, bool openDocuments = fa continue; } + var metadataService = workspace.Services.GetService(); + var metadataResolver = RuntimeMetadataReferenceResolver.CreateCurrentPlatformResolver(fileReferenceProvider: metadataService.GetReference); var syntaxFactory = languageServices.GetService(); var compilationFactory = languageServices.GetService(); - var compilationOptions = compilationFactory.GetDefaultCompilationOptions().WithOutputKind(OutputKind.DynamicallyLinkedLibrary); + var compilationOptions = compilationFactory.GetDefaultCompilationOptions() + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary) + .WithMetadataReferenceResolver(metadataResolver); var parseOptions = syntaxFactory.GetDefaultParseOptions().WithKind(SourceCodeKind.Script); diff --git a/src/Interactive/DesktopHost/InteractiveHost32.csproj b/src/Interactive/DesktopHost/InteractiveHost32.csproj deleted file mode 100644 index 3552157cf0a..00000000000 --- a/src/Interactive/DesktopHost/InteractiveHost32.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - true - Exe - net472 - - - - PreserveNewest - - - - - - - - - - \ No newline at end of file diff --git a/src/Interactive/DesktopHost/InteractiveHost64.csproj b/src/Interactive/DesktopHost/InteractiveHost64.csproj deleted file mode 100644 index 5b5fbdf4219..00000000000 --- a/src/Interactive/DesktopHost/InteractiveHost64.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - false - Exe - net472 - - - - PreserveNewest - - - - - - - - - - \ No newline at end of file diff --git a/src/Interactive/DesktopHost/InteractiveHostEntryPoint.cs b/src/Interactive/DesktopHost/InteractiveHostEntryPoint.cs deleted file mode 100644 index b6814336d89..00000000000 --- a/src/Interactive/DesktopHost/InteractiveHostEntryPoint.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ErrorReporting; - -namespace Microsoft.CodeAnalysis.Interactive -{ - internal static class InteractiveHostEntryPoint - { - private static async Task Main(string[] args) - { - FatalError.Handler = FailFast.OnFatalException; - - try - { - await InteractiveHost.Service.RunServerAsync(args).ConfigureAwait(false); - return 0; - } - catch (Exception e) - { - Console.Error.WriteLine(e); - return 1; - } - } - } -} diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.InitializedRemoteService.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.InitializedRemoteService.cs index 15338273d3b..7957f3f7179 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.InitializedRemoteService.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.InitializedRemoteService.cs @@ -10,7 +10,7 @@ internal partial class InteractiveHost { private readonly struct InitializedRemoteService { - public readonly RemoteService Service; + public readonly RemoteService? Service; public readonly RemoteExecutionResult InitializationResult; public InitializedRemoteService(RemoteService service, RemoteExecutionResult initializationResult) diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.LazyRemoteService.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.LazyRemoteService.cs index 2a9417c2330..96250ec7fbf 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.LazyRemoteService.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.LazyRemoteService.cs @@ -4,9 +4,13 @@ #nullable enable +extern alias Scripting; + using System; +using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; +using System.IO; using System.IO.Pipes; using System.Text; using System.Threading; @@ -14,6 +18,7 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Roslyn.Utilities; using StreamJsonRpc; +using Scripting::Microsoft.CodeAnalysis.Scripting.Hosting; namespace Microsoft.CodeAnalysis.Interactive { @@ -62,17 +67,28 @@ private async Task TryStartAndInitializeProcessAsync(C { try { - Host.ProcessStarting?.Invoke(Options.InitializationFile != null); - - var remoteService = await TryStartProcessAsync(Options.GetHostPath(), Options.Culture, cancellationToken).ConfigureAwait(false); + var remoteService = await TryStartProcessAsync(Options.HostPath, Options.Culture, cancellationToken).ConfigureAwait(false); if (remoteService == null) { return default; } + RemoteExecutionResult result; + if (SkipInitialization) { - return new InitializedRemoteService(remoteService, new RemoteExecutionResult(success: true)); + result = new RemoteExecutionResult( + success: true, + sourcePaths: ImmutableArray.Empty, + referencePaths: ImmutableArray.Empty, + workingDirectory: Host._initialWorkingDirectory, + initializationResult: new RemoteInitializationResult( + initializationScript: null, + metadataReferencePaths: ImmutableArray.Create(typeof(object).Assembly.Location, typeof(InteractiveScriptGlobals).Assembly.Location), + imports: ImmutableArray.Empty)); + + Host.ProcessInitialized?.Invoke(remoteService.PlatformInfo, Options, result); + return new InitializedRemoteService(remoteService, result); } bool initializing = true; @@ -87,9 +103,10 @@ private async Task TryStartAndInitializeProcessAsync(C // try to execute initialization script: var isRestarting = InstanceId > 1; - var initializationResult = await InvokeRemoteAsync(remoteService, nameof(Service.InitializeContextAsync), Options.InitializationFile, isRestarting).ConfigureAwait(false); + result = await ExecuteRemoteAsync(remoteService, nameof(Service.InitializeContextAsync), Options.InitializationFilePath, isRestarting).ConfigureAwait(false); + initializing = false; - if (!initializationResult.Success) + if (!result.Success) { Host.ReportProcessExited(remoteService.Process); remoteService.Dispose(); @@ -97,12 +114,16 @@ private async Task TryStartAndInitializeProcessAsync(C return default; } + Contract.ThrowIfNull(result.InitializationResult); + // Hook up a handler that initiates restart when the process exits. // Note that this is just so that we restart the process as soon as we see it dying and it doesn't need to be 100% bullet-proof. // If we don't receive the "process exited" event we will restart the process upon the next remote operation. remoteService.HookAutoRestartEvent(); - return new InitializedRemoteService(remoteService, initializationResult); + Host.ProcessInitialized?.Invoke(remoteService.PlatformInfo, Options, result); + + return new InitializedRemoteService(remoteService, result); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { @@ -133,7 +154,20 @@ private async Task TryStartAndInitializeProcessAsync(C EnableRaisingEvents = true }; - newProcess.Start(); + try + { + newProcess.Start(); + } + catch (Exception e) + { + Host.WriteOutputInBackground( + isError: true, + string.Format(InteractiveHostResources.Failed_to_create_a_remote_process_for_interactive_code_execution, hostPath), + e.Message); + + Host.InteractiveHostProcessCreationFailed?.Invoke(e, TryGetExitCode(newProcess)); + return null; + } Host.InteractiveHostProcessCreated?.Invoke(newProcess); @@ -148,38 +182,45 @@ private async Task TryStartAndInitializeProcessAsync(C } var clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); - JsonRpc jsonRpc; + JsonRpc? jsonRpc = null; void ProcessExitedBeforeEstablishingConnection(object sender, EventArgs e) - => _cancellationSource.Cancel(); + { + Host.InteractiveHostProcessCreationFailed?.Invoke(null, TryGetExitCode(newProcess)); + _cancellationSource.Cancel(); + } // Connecting the named pipe client would hang if the process exits before the connection is established, // as the client waits for the server to become available. We signal the cancellation token to abort. newProcess.Exited += ProcessExitedBeforeEstablishingConnection; + InteractiveHostPlatformInfo platformInfo; try { if (!CheckAlive(newProcess, hostPath)) { + Host.InteractiveHostProcessCreationFailed?.Invoke(null, TryGetExitCode(newProcess)); return null; } await clientStream.ConnectAsync(cancellationToken).ConfigureAwait(false); + jsonRpc = CreateRpc(clientStream, incomingCallTarget: null); - jsonRpc = JsonRpc.Attach(clientStream); - - await jsonRpc.InvokeWithCancellationAsync( + platformInfo = (await jsonRpc.InvokeWithCancellationAsync( nameof(Service.InitializeAsync), new object[] { Host._replServiceProviderType.AssemblyQualifiedName, culture.Name }, - cancellationToken).ConfigureAwait(false); + cancellationToken).ConfigureAwait(false)).Deserialize(); } - catch + catch (Exception e) { if (CheckAlive(newProcess, hostPath)) { RemoteService.InitiateTermination(newProcess, newProcessId); } + jsonRpc?.Dispose(); + + Host.InteractiveHostProcessCreationFailed?.Invoke(e, TryGetExitCode(newProcess)); return null; } finally @@ -187,8 +228,9 @@ void ProcessExitedBeforeEstablishingConnection(object sender, EventArgs e) newProcess.Exited -= ProcessExitedBeforeEstablishingConnection; } - return new RemoteService(Host, newProcess, newProcessId, jsonRpc); + return new RemoteService(Host, newProcess, newProcessId, jsonRpc, platformInfo); } + private bool CheckAlive(Process process, string hostPath) { bool alive = process.IsAlive(); @@ -204,6 +246,18 @@ private bool CheckAlive(Process process, string hostPath) return alive; } + + private static int? TryGetExitCode(Process process) + { + try + { + return process.ExitCode; + } + catch + { + return null; + } + } } } } diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.RemoteService.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.RemoteService.cs index eb41833ee64..673302d761e 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.RemoteService.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.RemoteService.cs @@ -7,7 +7,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Security.RightsManagement; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; @@ -22,6 +21,8 @@ internal sealed class RemoteService { public readonly Process Process; public readonly JsonRpc JsonRpc; + public readonly InteractiveHostPlatformInfo PlatformInfo; + private readonly int _processId; private readonly SemaphoreSlim _disposeSemaphore = new SemaphoreSlim(initialCount: 1); private readonly bool _joinOutputWritingThreadsOnDisposal; @@ -32,10 +33,11 @@ internal sealed class RemoteService private Thread? _readErrorOutputThread; // nulled on dispose private volatile ProcessExitHandlerStatus _processExitHandlerStatus; // set to Handled on dispose - internal RemoteService(InteractiveHost host, Process process, int processId, JsonRpc jsonRpc) + internal RemoteService(InteractiveHost host, Process process, int processId, JsonRpc jsonRpc, InteractiveHostPlatformInfo platformInfo) { Process = process; JsonRpc = jsonRpc; + PlatformInfo = platformInfo; _host = host; _joinOutputWritingThreadsOnDisposal = _host._joinOutputWritingThreadsOnDisposal; diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.Service.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.Service.cs index d55a1d38bf8..249c781d33d 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.Service.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.Service.cs @@ -18,11 +18,9 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; using Roslyn.Utilities; -using StreamJsonRpc; namespace Microsoft.CodeAnalysis.Interactive { @@ -35,7 +33,7 @@ internal sealed class Service : IDisposable { private static readonly ManualResetEventSlim s_clientExited = new ManualResetEventSlim(false); - private static Control? s_control; + private readonly Func, object> _invokeOnMainThread; private ServiceState? _serviceState; @@ -46,6 +44,8 @@ internal sealed class Service : IDisposable private readonly object _lastTaskGuard = new object(); private Task _lastTask; + private static InteractiveHostPlatformInfo s_currentPlatformInfo = InteractiveHostPlatformInfo.GetCurrentPlatformInfo(); + private sealed class ServiceState : IDisposable { public readonly InteractiveAssemblyLoader AssemblyLoader; @@ -68,7 +68,6 @@ public void Dispose() private readonly struct EvaluationState { internal readonly ImmutableArray SourceSearchPaths; - internal readonly ImmutableArray ReferenceSearchPaths; internal readonly string WorkingDirectory; internal readonly ScriptState? ScriptState; internal readonly ScriptOptions ScriptOptions; @@ -77,26 +76,26 @@ public void Dispose() ScriptState? scriptState, ScriptOptions scriptOptions, ImmutableArray sourceSearchPaths, - ImmutableArray referenceSearchPaths, string workingDirectory) { Debug.Assert(!sourceSearchPaths.IsDefault); - Debug.Assert(!referenceSearchPaths.IsDefault); + Debug.Assert(scriptOptions.MetadataResolver is ScriptMetadataResolver); ScriptState = scriptState; ScriptOptions = scriptOptions; SourceSearchPaths = sourceSearchPaths; - ReferenceSearchPaths = referenceSearchPaths; WorkingDirectory = workingDirectory; } + public ScriptMetadataResolver MetadataReferenceResolver + => (ScriptMetadataResolver)ScriptOptions.MetadataResolver; + internal EvaluationState WithScriptState(ScriptState state) { return new EvaluationState( state, ScriptOptions, SourceSearchPaths, - ReferenceSearchPaths, WorkingDirectory); } @@ -106,7 +105,6 @@ internal EvaluationState WithOptions(ScriptOptions options) ScriptState, options, SourceSearchPaths, - ReferenceSearchPaths, WorkingDirectory); } } @@ -119,14 +117,26 @@ internal EvaluationState WithOptions(ScriptOptions options) #region Setup - public Service() + private Service(Func, object> invokeOnMainThread) { + _invokeOnMainThread = invokeOnMainThread; + + // The initial working directory is set when the process is created. + var workingDirectory = Directory.GetCurrentDirectory(); + + var referenceResolver = new RuntimeMetadataReferenceResolver( + searchPaths: ImmutableArray.Empty, + baseDirectory: workingDirectory, + packageResolver: null, + gacFileResolver: s_currentPlatformInfo.HasGlobalAssemblyCache ? new GacFileResolver(preferredCulture: CultureInfo.CurrentCulture) : null, + s_currentPlatformInfo.PlatformAssemblyPaths, + (path, properties) => new ShadowCopyReference(GetServiceState().MetadataFileProvider, path, properties)); + var initialState = new EvaluationState( scriptState: null, - scriptOptions: ScriptOptions.Default, - sourceSearchPaths: ImmutableArray.Empty, - referenceSearchPaths: ImmutableArray.Empty, - workingDirectory: Directory.GetCurrentDirectory()); + scriptOptions: ScriptOptions.Default.WithMetadataResolver(new ScriptMetadataResolver(referenceResolver)), + ImmutableArray.Empty, + workingDirectory); _lastTask = Task.FromResult(initialState); @@ -151,28 +161,8 @@ public void Dispose() _serviceState = null; } - /*public override object? InitializeLifetimeService() - { - return null; - }*/ - - public Task InitializeAsync(string replServiceProviderTypeName, string cultureName) + public Task InitializeAsync(string replServiceProviderTypeName, string cultureName) { - Debug.Assert(cultureName != null); - using (var resetEvent = new ManualResetEventSlim(false)) - { - var uiThread = new Thread(() => - { - s_control = new Control(); - s_control.CreateControl(); - resetEvent.Set(); - Application.Run(); - }); - uiThread.SetApartmentState(ApartmentState.STA); - uiThread.IsBackground = true; - uiThread.Start(); - resetEvent.Wait(); - } // TODO (tomat): we should share the copied files with the host var metadataFileProvider = new MetadataShadowCopyProvider( Path.Combine(Path.GetTempPath(), "InteractiveHostShadow"), @@ -186,24 +176,15 @@ public Task InitializeAsync(string replServiceProviderTypeName, string cultureNa _serviceState = new ServiceState(assemblyLoader, metadataFileProvider, replServiceProvider, globals); - return Task.CompletedTask; + return Task.FromResult(s_currentPlatformInfo.Serialize()); } + private ServiceState GetServiceState() { Contract.ThrowIfNull(_serviceState, "Service not initialized"); return _serviceState; } - private MetadataReferenceResolver CreateMetadataReferenceResolver(ImmutableArray searchPaths, string baseDirectory) - { - return new RuntimeMetadataReferenceResolver( - new RelativePathResolver(searchPaths, baseDirectory), - packageResolver: null, - gacFileResolver: GacFileResolver.IsAvailable ? new GacFileResolver(preferredCulture: CultureInfo.CurrentCulture) : null, - useCoreResolver: !GacFileResolver.IsAvailable, - fileReferenceProvider: (path, properties) => new ShadowCopyReference(GetServiceState().MetadataFileProvider, path, properties)); - } - private SourceReferenceResolver CreateSourceReferenceResolver(ImmutableArray searchPaths, string baseDirectory) { return new SourceFileResolver(searchPaths, baseDirectory); @@ -233,68 +214,31 @@ public void EmulateClientExit() s_clientExited.Set(); } - internal static async Task RunServerAsync(string[] args) + internal static Task RunServerAsync(string[] args, Func, object> invokeOnMainThread) { - if (args.Length != 2) - { - throw new ArgumentException("Expecting arguments: "); - } - - await RunServerAsync(args[0], int.Parse(args[1], CultureInfo.InvariantCulture)).ConfigureAwait(false); + Contract.ThrowIfFalse(args.Length == 2, "Expecting arguments: "); + return RunServerAsync(args[0], int.Parse(args[1], CultureInfo.InvariantCulture), invokeOnMainThread); } /// /// Implements remote server. /// - private static async Task RunServerAsync(string pipeName, int clientProcessId) + private static async Task RunServerAsync(string pipeName, int clientProcessId, Func, object> invokeOnMainThread) { if (!AttachToClientProcess(clientProcessId)) { return; } - // Disables Windows Error Reporting for the process, so that the process fails fast. - // Unfortunately, this doesn't work on Windows Server 2008 (OS v6.0), Vista (OS v6.0) and XP (OS v5.1) - // Note that GetErrorMode is not available on XP at all. - if (Environment.OSVersion.Version >= new Version(6, 1, 0, 0)) - { - SetErrorMode(GetErrorMode() | ErrorMode.SEM_FAILCRITICALERRORS | ErrorMode.SEM_NOOPENFILEERRORBOX | ErrorMode.SEM_NOGPFAULTERRORBOX); - } + var serverStream = new NamedPipeServerStream(pipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + await serverStream.WaitForConnectionAsync().ConfigureAwait(false); - try - { - using (var resetEvent = new ManualResetEventSlim(false)) - { - var uiThread = new Thread(() => - { - s_control = new Control(); - s_control.CreateControl(); - resetEvent.Set(); - Application.Run(); - }); - uiThread.SetApartmentState(ApartmentState.STA); - uiThread.IsBackground = true; - uiThread.Start(); - resetEvent.Wait(); - } + var jsonRpc = CreateRpc(serverStream, incomingCallTarget: new Service(invokeOnMainThread)); - var serverStream = new NamedPipeServerStream(pipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - await serverStream.WaitForConnectionAsync().ConfigureAwait(false); - var jsonRPC = JsonRpc.Attach(serverStream, new Service()); - await jsonRPC.Completion.ConfigureAwait(false); - // the client can instantiate interactive host now: - s_clientExited.Wait(); - } - finally - { - // TODO:(miziga): delete and make a finally or catch statement for the try - } // force exit even if there are foreground threads running: - Environment.Exit(0); - } + await jsonRpc.Completion.ConfigureAwait(false); - internal static string ServiceName - { - get { return typeof(Service).Name; } + // the client can instantiate interactive host now: + s_clientExited.Wait(); } #endregion @@ -303,21 +247,18 @@ internal static string ServiceName // Used by ResetInteractive - consider improving (we should remember the parameters for auto-reset, e.g.) - public async Task SetPathsAsync( + public async Task SetPathsAsync( string[] referenceSearchPaths, string[] sourceSearchPaths, string? baseDirectory) { - Debug.Assert(referenceSearchPaths != null); - Debug.Assert(sourceSearchPaths != null); - Debug.Assert(baseDirectory != null); var completionSource = new TaskCompletionSource(); lock (_lastTaskGuard) { _lastTask = SetPathsAsync(_lastTask, completionSource, referenceSearchPaths, sourceSearchPaths, baseDirectory); } - return await completionSource.Task.ConfigureAwait(false); + return (await completionSource.Task.ConfigureAwait(false)).Serialize(); } private async Task SetPathsAsync( @@ -354,14 +295,15 @@ internal static string ServiceName /// Reads given initialization file (.rsp) and loads and executes all assembly references and files, respectively specified in it. /// Execution is performed on the UI thread. /// - public async Task InitializeContextAsync(string? initializationFile, bool isRestarting) + public async Task InitializeContextAsync(string? initializationFilePath, bool isRestarting) { var completionSource = new TaskCompletionSource(); lock (_lastTaskGuard) { - _lastTask = InitializeContextAsync(_lastTask, completionSource, initializationFile, isRestarting); + _lastTask = InitializeContextAsync(_lastTask, completionSource, initializationFilePath, isRestarting); } - return await completionSource.Task.ConfigureAwait(false); + + return (await completionSource.Task.ConfigureAwait(false)).Serialize(); } /// @@ -374,6 +316,7 @@ public async Task AddReferenceAsync(string reference) { _lastTask = AddReferenceAsync(_lastTask, completionSource, reference); } + return await completionSource.Task.ConfigureAwait(false); } @@ -402,20 +345,22 @@ private async Task AddReferenceAsync(Task last { completionSource.SetResult(success); } + return state; } /// /// Executes given script snippet on the UI thread in the context of the current session. /// - public async Task ExecuteAsync(string text) + public async Task ExecuteAsync(string text) { var completionSource = new TaskCompletionSource(); lock (_lastTaskGuard) { _lastTask = ExecuteAsync(completionSource, _lastTask, text); } - return await completionSource.Task.ConfigureAwait(false); + + return (await completionSource.Task.ConfigureAwait(false)).Serialize(); } private async Task ExecuteAsync(TaskCompletionSource completionSource, Task lastTask, string text) @@ -465,7 +410,7 @@ private void DisplayException(Exception e) /// /// Remote API. Executes given script file on the UI thread in the context of the current session. /// - public async Task ExecuteFileAsync(string path) + public async Task ExecuteFileAsync(string path) { var completionSource = new TaskCompletionSource(); @@ -473,40 +418,48 @@ public async Task ExecuteFileAsync(string path) { _lastTask = ExecuteFileAsync(completionSource, _lastTask, path); } - return await completionSource.Task.ConfigureAwait(false); + + return (await completionSource.Task.ConfigureAwait(false)).Serialize(); } - private EvaluationState CompleteExecution(EvaluationState state, TaskCompletionSource completionSource, bool success) + private EvaluationState CompleteExecution(EvaluationState state, TaskCompletionSource completionSource, bool success, RemoteInitializationResult? initResult = null) { // send any updates to the host object and current directory back to the client: var globals = GetServiceState().Globals; - var currentSourcePaths = globals.SourcePaths.ToArray(); - var currentReferencePaths = globals.ReferencePaths.ToArray(); - var currentWorkingDirectory = Directory.GetCurrentDirectory(); + var newSourcePaths = globals.SourcePaths.ToImmutableArray(); + var newReferencePaths = globals.ReferencePaths.ToImmutableArray(); + var newWorkingDirectory = Directory.GetCurrentDirectory(); - var changedSourcePaths = currentSourcePaths.SequenceEqual(state.SourceSearchPaths) ? null : currentSourcePaths; - var changedReferencePaths = currentReferencePaths.SequenceEqual(state.ReferenceSearchPaths) ? null : currentReferencePaths; - var changedWorkingDirectory = currentWorkingDirectory == state.WorkingDirectory ? null : currentWorkingDirectory; + completionSource.TrySetResult(new RemoteExecutionResult(success, newSourcePaths, newReferencePaths, newWorkingDirectory, initResult)); - completionSource.TrySetResult(new RemoteExecutionResult(success, changedSourcePaths, changedReferencePaths, changedWorkingDirectory)); + var metadataResolver = state.MetadataReferenceResolver; + var sourcePathsChanged = !newSourcePaths.SequenceEqual(state.SourceSearchPaths); + var referencePathsChanged = !newReferencePaths.SequenceEqual(metadataResolver.SearchPaths); + var workingDirectoryChanged = newWorkingDirectory != state.WorkingDirectory; // no changes in resolvers: - if (changedReferencePaths == null && changedSourcePaths == null && changedWorkingDirectory == null) + if (!sourcePathsChanged && !referencePathsChanged && !workingDirectoryChanged) { return state; } - var newSourcePaths = ImmutableArray.CreateRange(currentSourcePaths); - var newReferencePaths = ImmutableArray.CreateRange(currentReferencePaths); - var newWorkingDirectory = currentWorkingDirectory; - - ScriptOptions newOptions = state.ScriptOptions; - if (changedReferencePaths != null || changedWorkingDirectory != null) + var newOptions = state.ScriptOptions; + if (referencePathsChanged || workingDirectoryChanged) { - newOptions = newOptions.WithMetadataResolver(CreateMetadataReferenceResolver(newReferencePaths, newWorkingDirectory)); + if (referencePathsChanged) + { + metadataResolver = metadataResolver.WithSearchPaths(newReferencePaths); + } + + if (workingDirectoryChanged) + { + metadataResolver = metadataResolver.WithBaseDirectory(newWorkingDirectory); + } + + newOptions = newOptions.WithMetadataResolver(metadataResolver); } - if (changedSourcePaths != null || changedWorkingDirectory != null) + if (sourcePathsChanged || workingDirectoryChanged) { newOptions = newOptions.WithSourceResolver(CreateSourceReferenceResolver(newSourcePaths, newWorkingDirectory)); } @@ -515,8 +468,7 @@ private EvaluationState CompleteExecution(EvaluationState state, TaskCompletionS state.ScriptState, newOptions, newSourcePaths, - newReferencePaths, - workingDirectory: newWorkingDirectory); + newWorkingDirectory); } private static async Task ReportUnhandledExceptionIfAnyAsync(Task lastTask) @@ -551,31 +503,38 @@ private static void ReportUnhandledException(Exception e) private async Task InitializeContextAsync( Task lastTask, TaskCompletionSource completionSource, - string? initializationFile, + string? initializationFilePath, bool isRestarting) { - Contract.ThrowIfFalse(initializationFile == null || PathUtilities.IsAbsolute(initializationFile)); + Contract.ThrowIfFalse(initializationFilePath == null || PathUtilities.IsAbsolute(initializationFilePath)); var serviceState = GetServiceState(); var state = await ReportUnhandledExceptionIfAnyAsync(lastTask).ConfigureAwait(false); + string? initializationScriptPath = null; + var initialImports = ImmutableArray.Empty; + var metadataReferencePaths = ImmutableArray.CreateBuilder(); + try { - // TODO (tomat): this is also done in CommonInteractiveEngine, perhaps we can pass the parsed command lines to here? + metadataReferencePaths.Add(typeof(object).Assembly.Location); + metadataReferencePaths.Add(typeof(InteractiveScriptGlobals).Assembly.Location); if (!isRestarting) { Console.Out.WriteLine(serviceState.ReplServiceProvider.Logo); } - if (File.Exists(initializationFile)) + if (File.Exists(initializationFilePath)) { - Console.Out.WriteLine(string.Format(InteractiveHostResources.Loading_context_from_0, Path.GetFileName(initializationFile))); + Console.Out.WriteLine(string.Format(InteractiveHostResources.Loading_context_from_0, Path.GetFileName(initializationFilePath))); var parser = serviceState.ReplServiceProvider.CommandLineParser; - // The base directory for relative paths is the directory that contains the .rsp file. - // Note that .rsp files included by this .rsp file will share the base directory (Dev10 behavior of csc/vbc). - var rspDirectory = Path.GetDirectoryName(initializationFile); - var args = parser.Parse(new[] { "@" + initializationFile }, rspDirectory, RuntimeEnvironment.GetRuntimeDirectory(), null); + // Add the Framework runtime directory to reference search paths when running on .NET Framework (PlatformAssemblyPaths list is empty). + // Otherwise, platform assemblies are looked up in PlatformAssemblyPaths directly. + var sdkDirectory = s_currentPlatformInfo.PlatformAssemblyPaths.IsEmpty ? RuntimeEnvironment.GetRuntimeDirectory() : null; + + var rspDirectory = Path.GetDirectoryName(initializationFilePath); + var args = parser.Parse(new[] { "@" + initializationFilePath }, baseDirectory: rspDirectory, sdkDirectory, additionalReferenceDirectories: null); foreach (var error in args.Errors) { @@ -585,48 +544,56 @@ private static void ReportUnhandledException(Exception e) if (args.Errors.Length == 0) { - var metadataResolver = CreateMetadataReferenceResolver(args.ReferencePaths, rspDirectory); - var sourceResolver = CreateSourceReferenceResolver(args.SourcePaths, rspDirectory); + var referencePaths = args.ReferencePaths; + var sourcePaths = args.SourcePaths; + + // TODO: Workaround for https://github.com/dotnet/roslyn/issues/45346 + var referencePathsWithoutRspDir = referencePaths.Remove(rspDirectory); + var metadataResolver = state.MetadataReferenceResolver.WithSearchPaths(referencePathsWithoutRspDir); + var rspMetadataResolver = state.MetadataReferenceResolver.WithSearchPaths(referencePaths).WithBaseDirectory(rspDirectory); + + var sourceResolver = CreateSourceReferenceResolver(sourcePaths, rspDirectory); var metadataReferences = new List(); - foreach (CommandLineReference cmdLineReference in args.MetadataReferences) + foreach (var cmdLineReference in args.MetadataReferences) { // interactive command line parser doesn't accept modules or linked assemblies Debug.Assert(cmdLineReference.Properties.Kind == MetadataImageKind.Assembly && !cmdLineReference.Properties.EmbedInteropTypes); - var resolvedReferences = metadataResolver.ResolveReference(cmdLineReference.Reference, baseFilePath: null, properties: MetadataReferenceProperties.Assembly); + var resolvedReferences = rspMetadataResolver.ResolveReference(cmdLineReference.Reference, baseFilePath: null, properties: MetadataReferenceProperties.Assembly); if (!resolvedReferences.IsDefaultOrEmpty) { metadataReferences.AddRange(resolvedReferences); + metadataReferencePaths.AddRange(resolvedReferences.Where(r => r.FilePath != null).Select(r => r.FilePath!)); } } - var scriptPathOpt = args.SourceFiles.IsEmpty ? null : args.SourceFiles[0].Path; + initializationScriptPath = args.SourceFiles.IsEmpty ? null : args.SourceFiles[0].Path; + initialImports = CommandLineHelpers.GetImports(args); var rspState = new EvaluationState( state.ScriptState, state.ScriptOptions. - WithFilePath(scriptPathOpt). + WithFilePath(initializationScriptPath). WithReferences(metadataReferences). - WithImports(CommandLineHelpers.GetImports(args)). + WithImports(initialImports). WithMetadataResolver(metadataResolver). WithSourceResolver(sourceResolver), args.SourcePaths, - args.ReferencePaths, rspDirectory); var globals = serviceState.Globals; globals.ReferencePaths.Clear(); - globals.ReferencePaths.AddRange(args.ReferencePaths); + globals.ReferencePaths.AddRange(referencePathsWithoutRspDir); globals.SourcePaths.Clear(); - globals.SourcePaths.AddRange(args.SourcePaths); + globals.SourcePaths.AddRange(sourcePaths); globals.Args.AddRange(args.ScriptArguments); - if (scriptPathOpt != null) + if (initializationScriptPath != null) { - var newScriptState = await TryExecuteFileAsync(rspState, scriptPathOpt).ConfigureAwait(false); + var newScriptState = await TryExecuteFileAsync(rspState, initializationScriptPath).ConfigureAwait(false); if (newScriptState != null) { // remove references and imports from the options, they have been applied and will be inherited from now on: @@ -651,7 +618,8 @@ private static void ReportUnhandledException(Exception e) } finally { - state = CompleteExecution(state, completionSource, success: true); + var initResult = new RemoteInitializationResult(initializationScriptPath, metadataReferencePaths.ToImmutableArray(), initialImports); + state = CompleteExecution(state, completionSource, success: true, initResult); } return state; @@ -786,33 +754,29 @@ private static void DisplaySearchPaths(TextWriter writer, List attempted } } - private async Task> ExecuteOnUIThreadAsync(Script script, ScriptState? state, bool displayResult) + private Task> ExecuteOnUIThreadAsync(Script script, ScriptState? state, bool displayResult) { - Contract.ThrowIfNull(s_control, "UI thread not initialized"); - - return await ((Task>)s_control.Invoke( - (Func>>)(async () => - { - var serviceState = GetServiceState(); - - var task = (state == null) ? - script.RunAsync(serviceState.Globals, catchException: e => true, cancellationToken: CancellationToken.None) : - script.RunFromAsync(state, catchException: e => true, cancellationToken: CancellationToken.None); + return (Task>)_invokeOnMainThread((Func>>)(async () => + { + var serviceState = GetServiceState(); - var newState = await task.ConfigureAwait(false); + var task = (state == null) ? + script.RunAsync(serviceState.Globals, catchException: e => true, cancellationToken: CancellationToken.None) : + script.RunFromAsync(state, catchException: e => true, cancellationToken: CancellationToken.None); - if (newState.Exception != null) - { - DisplayException(newState.Exception); - } - else if (displayResult && newState.Script.HasReturnValue()) - { - serviceState.Globals.Print(newState.ReturnValue); - } + var newState = await task.ConfigureAwait(false); - return newState; + if (newState.Exception != null) + { + DisplayException(newState.Exception); + } + else if (displayResult && newState.Script.HasReturnValue()) + { + serviceState.Globals.Print(newState.ReturnValue); + } - }))).ConfigureAwait(false); + return newState; + })); } private void DisplayInteractiveErrors(ImmutableArray diagnostics, TextWriter output) @@ -842,45 +806,6 @@ private void DisplayInteractiveErrors(ImmutableArray diagnostics, Te #endregion - #region Win32 API - - [DllImport("kernel32", PreserveSig = true)] - internal static extern ErrorMode SetErrorMode(ErrorMode mode); - - [DllImport("kernel32", PreserveSig = true)] - internal static extern ErrorMode GetErrorMode(); - - [Flags] - internal enum ErrorMode : int - { - /// - /// Use the system default, which is to display all error dialog boxes. - /// - SEM_FAILCRITICALERRORS = 0x0001, - - /// - /// The system does not display the critical-error-handler message box. Instead, the system sends the error to the calling process. - /// Best practice is that all applications call the process-wide SetErrorMode function with a parameter of SEM_FAILCRITICALERRORS at startup. - /// This is to prevent error mode dialogs from hanging the application. - /// - SEM_NOGPFAULTERRORBOX = 0x0002, - - /// - /// The system automatically fixes memory alignment faults and makes them invisible to the application. - /// It does this for the calling process and any descendant processes. This feature is only supported by - /// certain processor architectures. For more information, see the Remarks section. - /// After this value is set for a process, subsequent attempts to clear the value are ignored. - /// - SEM_NOALIGNMENTFAULTEXCEPT = 0x0004, - - /// - /// The system does not display a message box when it fails to find a file. Instead, the error is returned to the calling process. - /// - SEM_NOOPENFILEERRORBOX = 0x8000, - } - - #endregion - #region Testing // TODO(tomat): remove when the compiler supports events @@ -919,6 +844,12 @@ public Task RemoteConsoleWriteAsync(byte[] data, bool isError) return Task.CompletedTask; } + /// + /// Remote API for testing purposes. + /// + public Task GetRuntimeDirectoryAsync() + => Task.FromResult(RuntimeEnvironment.GetRuntimeDirectory()); + #endregion } } diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.cs index b3e2f268bc8..281664954d9 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.cs @@ -7,14 +7,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; -using System.IO.Pipes; -using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; using Microsoft.CodeAnalysis.ErrorReporting; +using Newtonsoft.Json; using Roslyn.Utilities; using StreamJsonRpc; @@ -28,7 +25,16 @@ namespace Microsoft.CodeAnalysis.Interactive /// internal sealed partial class InteractiveHost : IDisposable { - internal const bool DefaultIs64Bit = true; + internal const InteractiveHostPlatform DefaultPlatform = InteractiveHostPlatform.Desktop32; + + private static readonly JsonRpcTargetOptions s_jsonRpcTargetOptions = new JsonRpcTargetOptions() + { + // Do not allow JSON-RPC to automatically subscribe to events and remote their calls. + NotifyClientOfEvents = false, + + // Only allow public methods (may be on internal types) to be invoked remotely. + AllowNonPublicInvocation = false + }; private readonly Type _replServiceProviderType; private readonly string _initialWorkingDirectory; @@ -53,7 +59,7 @@ internal sealed partial class InteractiveHost : IDisposable /// private readonly bool _joinOutputWritingThreadsOnDisposal; - internal event Action? ProcessStarting; + internal event Action? ProcessInitialized; public InteractiveHost( Type replServiceProviderType, @@ -77,15 +83,18 @@ internal sealed partial class InteractiveHost : IDisposable internal event Action? ErrorOutputReceived; internal Process? TryGetProcess() - => _lazyRemoteService?.TryGetInitializedService()?.Service.Process; + => _lazyRemoteService?.TryGetInitializedService()?.Service?.Process; - internal async Task TryGetServiceAsync() + internal async Task TryGetServiceAsync() => (await TryGetOrCreateRemoteServiceAsync().ConfigureAwait(false)).Service; // Triggered whenever we create a fresh process. // The ProcessExited event is not hooked yet. internal event Action? InteractiveHostProcessCreated; + // Triggered whenever InteractiveHost process creation fails. + internal event Action? InteractiveHostProcessCreationFailed; + #endregion ~InteractiveHost() @@ -250,6 +259,9 @@ private async Task TryGetOrCreateRemoteServiceAsync() return default; } + private async Task ExecuteRemoteAsync(string targetName, params object?[] arguments) + => (await InvokeRemoteAsync(targetName, arguments).ConfigureAwait(false))?.Deserialize() ?? default; + private async Task InvokeRemoteAsync(string targetName, params object?[] arguments) { var initializedRemoteService = await TryGetOrCreateRemoteServiceAsync().ConfigureAwait(false); @@ -257,8 +269,13 @@ private async Task InvokeRemoteAsync(string targetName, params { return default!; } + return await InvokeRemoteAsync(initializedRemoteService.Service, targetName, arguments).ConfigureAwait(false); } + + private static async Task ExecuteRemoteAsync(RemoteService remoteService, string targetName, params object?[] arguments) + => (await InvokeRemoteAsync(remoteService, targetName, arguments).ConfigureAwait(false))?.Deserialize() ?? default; + private static async Task InvokeRemoteAsync(RemoteService remoteService, string targetName, params object?[] arguments) { try @@ -271,6 +288,28 @@ private static async Task InvokeRemoteAsync(RemoteService remo } } + private static JsonRpc CreateRpc(Stream stream, object? incomingCallTarget) + { + var jsonFormatter = new JsonMessageFormatter(); + + // disable interpreting of strings as DateTime during deserialization: + jsonFormatter.JsonSerializer.DateParseHandling = DateParseHandling.None; + + var rpc = new JsonRpc(new HeaderDelimitedMessageHandler(stream, jsonFormatter)) + { + CancelLocallyInvokedMethodsWhenConnectionIsClosed = true, + }; + + if (incomingCallTarget != null) + { + rpc.AddLocalRpcTarget(incomingCallTarget, s_jsonRpcTargetOptions); + } + + rpc.StartListening(); + + return rpc; + } + #region Operations public InteractiveHostOptions? OptionsOpt @@ -318,7 +357,7 @@ public async Task ResetAsync(InteractiveHostOptions optio public Task ExecuteAsync(string code) { Contract.ThrowIfNull(code); - return InvokeRemoteAsync(nameof(Service.ExecuteAsync), code); + return ExecuteRemoteAsync(nameof(Service.ExecuteAsync), code); } /// @@ -333,9 +372,11 @@ public Task ExecuteAsync(string code) public Task ExecuteFileAsync(string path) { Contract.ThrowIfNull(path); - return InvokeRemoteAsync(nameof(Service.ExecuteFileAsync), path); + return ExecuteRemoteAsync(nameof(Service.ExecuteFileAsync), path); } - /// /// Asynchronously adds a reference to the set of available references for next submission. + + /// + /// Asynchronously adds a reference to the set of available references for next submission. /// /// The reference to add. /// @@ -357,7 +398,7 @@ public Task SetPathsAsync(string[] referenceSearchPaths, Contract.ThrowIfNull(sourceSearchPaths); Contract.ThrowIfNull(baseDirectory); - return InvokeRemoteAsync(nameof(Service.SetPathsAsync), referenceSearchPaths, sourceSearchPaths, baseDirectory); + return ExecuteRemoteAsync(nameof(Service.SetPathsAsync), referenceSearchPaths, sourceSearchPaths, baseDirectory); } #endregion diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHostOptions.cs b/src/Interactive/Host/Interactive/Core/InteractiveHostOptions.cs index 90423247573..fbc2211115b 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHostOptions.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHostOptions.cs @@ -4,8 +4,6 @@ #nullable enable -using System; -using System.Diagnostics; using System.Globalization; using System.IO; using Roslyn.Utilities; @@ -18,39 +16,52 @@ namespace Microsoft.CodeAnalysis.Interactive internal sealed class InteractiveHostOptions { /// - /// Optional path to the .rsp file to process when initializing context of the process. + /// Path to interactive host executable. /// - public string? InitializationFile { get; } + public string HostPath { get; } /// - /// Host culture used for localization of doc comments, errors. + /// Optional file name of the .rsp file to use to initialize the REPL. /// - public CultureInfo Culture { get; } + public string? InitializationFilePath { get; } /// - /// Path to interactive host directory. + /// Host culture used for localization of doc comments, errors. /// - public string HostDirectory { get; } + public CultureInfo Culture { get; } /// - /// Host process bitness. + /// Host process platform. /// - public bool Is64Bit { get; } + public InteractiveHostPlatform Platform { get; } public InteractiveHostOptions( - string hostDirectory, - string? initializationFile = null, - CultureInfo? culture = null, - bool is64Bit = false) + string hostPath, + string? initializationFilePath, + CultureInfo culture, + InteractiveHostPlatform platform) { - Contract.ThrowIfNull(hostDirectory); - HostDirectory = hostDirectory; - InitializationFile = initializationFile; - Culture = culture ?? CultureInfo.CurrentUICulture; - Is64Bit = is64Bit; + Contract.ThrowIfNull(hostPath); + + HostPath = hostPath; + InitializationFilePath = initializationFilePath; + Culture = culture; + Platform = platform; } - public string GetHostPath() - => Path.Combine(HostDirectory, "InteractiveHost" + (Is64Bit ? "64" : "32") + ".exe"); + public static InteractiveHostOptions CreateFromDirectory( + string hostDirectory, + string? initializationFileName, + CultureInfo culture, + InteractiveHostPlatform platform) + { + var hostSubdirectory = (platform == InteractiveHostPlatform.Core) ? "Core" : "Desktop"; + var hostExecutableFileName = "InteractiveHost" + (platform == InteractiveHostPlatform.Desktop32 ? "32" : "64") + ".exe"; + + var hostPath = Path.Combine(hostDirectory, hostSubdirectory, hostExecutableFileName); + var initializationFilePath = (initializationFileName != null) ? Path.Combine(hostDirectory, hostSubdirectory, initializationFileName) : null; + + return new InteractiveHostOptions(hostPath, initializationFilePath, culture, platform); + } } } diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatform.cs b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatform.cs new file mode 100644 index 00000000000..8506b6af843 --- /dev/null +++ b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatform.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +namespace Microsoft.CodeAnalysis.Interactive +{ + internal enum InteractiveHostPlatform + { + Core = 1, + Desktop32 = 2, + Desktop64 = 3 + } +} diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs new file mode 100644 index 00000000000..a3ff8a46e8f --- /dev/null +++ b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +extern alias Scripting; + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using Roslyn.Utilities; +using Scripting::Microsoft.CodeAnalysis.Scripting.Hosting; + +namespace Microsoft.CodeAnalysis.Interactive +{ + internal readonly struct InteractiveHostPlatformInfo + { + internal sealed class Data + { + public string[] PlatformAssemblyPaths = null!; + public bool HasGlobalAssemblyCache; + + public InteractiveHostPlatformInfo Deserialize() + => new InteractiveHostPlatformInfo( + PlatformAssemblyPaths.ToImmutableArray(), + HasGlobalAssemblyCache); + } + + private static readonly string s_hostDirectory = PathUtilities.GetDirectoryName(typeof(InteractiveHostPlatformInfo).Assembly.Location)!; + + public readonly ImmutableArray PlatformAssemblyPaths; + public readonly bool HasGlobalAssemblyCache; + + public InteractiveHostPlatformInfo(ImmutableArray platformAssemblyPaths, bool hasGlobalAssemblyCache) + { + Debug.Assert(!platformAssemblyPaths.IsDefault); + + HasGlobalAssemblyCache = hasGlobalAssemblyCache; + PlatformAssemblyPaths = platformAssemblyPaths; + } + + public Data Serialize() + => new Data() + { + HasGlobalAssemblyCache = HasGlobalAssemblyCache, + PlatformAssemblyPaths = PlatformAssemblyPaths.ToArray(), + }; + + public static InteractiveHostPlatformInfo GetCurrentPlatformInfo() + => new InteractiveHostPlatformInfo( + RuntimeMetadataReferenceResolver.GetTrustedPlatformAssemblyPaths().Where(IsNotHostAssembly).ToImmutableArray(), + GacFileResolver.IsAvailable); + + private static bool IsNotHostAssembly(string path) + => !StringComparer.OrdinalIgnoreCase.Equals(PathUtilities.GetDirectoryName(path), s_hostDirectory); + } +} diff --git a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs index 809e5c132d3..497c6e8ce19 100644 --- a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs +++ b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs @@ -5,39 +5,71 @@ #nullable enable using System; +using System.Collections.Immutable; +using System.Linq; namespace Microsoft.CodeAnalysis.Interactive { - [Serializable] internal readonly struct RemoteExecutionResult { + internal sealed class Data + { + public bool Success; + public string[] SourcePaths = null!; + public string[] ReferencePaths = null!; + public string WorkingDirectory = null!; + public RemoteInitializationResult.Data? InitializationResult; + + public RemoteExecutionResult Deserialize() + => new RemoteExecutionResult( + Success, + SourcePaths.ToImmutableArray(), + ReferencePaths.ToImmutableArray(), + WorkingDirectory, + InitializationResult?.Deserialize()); + } + public readonly bool Success; /// - /// New value of source search paths after execution, or null if not changed since the last execution. + /// New value of source search paths after execution. /// - public readonly string[]? ChangedSourcePaths; + public readonly ImmutableArray SourcePaths; /// - /// New value of reference search paths after execution, or null if not changed since the last execution. + /// New value of reference search paths after execution. /// - public readonly string[]? ChangedReferencePaths; + public readonly ImmutableArray ReferencePaths; /// - /// New value of working directory in the remote process after execution, or null if not changed since the last execution. + /// New value of working directory in the remote process after execution. /// - public readonly string? ChangedWorkingDirectory; + public readonly string WorkingDirectory; + + public readonly RemoteInitializationResult? InitializationResult; public RemoteExecutionResult( bool success, - string[]? changedSourcePaths = null, - string[]? changedReferencePaths = null, - string? changedWorkingDirectory = null) + ImmutableArray sourcePaths, + ImmutableArray referencePaths, + string workingDirectory, + RemoteInitializationResult? initializationResult) { Success = success; - ChangedSourcePaths = changedSourcePaths; - ChangedReferencePaths = changedReferencePaths; - ChangedWorkingDirectory = changedWorkingDirectory; + SourcePaths = sourcePaths; + ReferencePaths = referencePaths; + WorkingDirectory = workingDirectory; + InitializationResult = initializationResult; } + + public Data Serialize() + => new Data() + { + Success = Success, + SourcePaths = SourcePaths.ToArray(), + ReferencePaths = ReferencePaths.ToArray(), + WorkingDirectory = WorkingDirectory, + InitializationResult = InitializationResult?.Serialize(), + }; } } diff --git a/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs new file mode 100644 index 00000000000..1b4bd99d9f2 --- /dev/null +++ b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Immutable; +using System.Linq; + +namespace Microsoft.CodeAnalysis.Interactive +{ + internal sealed class RemoteInitializationResult + { + internal sealed class Data + { + public string? ScriptPath; + public string[] MetadataReferencePaths = null!; + public string[] Imports = null!; + + public RemoteInitializationResult Deserialize() + => new RemoteInitializationResult( + ScriptPath, + ImmutableArray.Create(MetadataReferencePaths), + ImmutableArray.Create(Imports)); + } + + /// + /// Full path to the initialization script that has been executed as part of initialization process. + /// + public readonly string? ScriptPath; + + public readonly ImmutableArray MetadataReferencePaths; + + public readonly ImmutableArray Imports; + + public RemoteInitializationResult(string? initializationScript, ImmutableArray metadataReferencePaths, ImmutableArray imports) + { + ScriptPath = initializationScript; + MetadataReferencePaths = metadataReferencePaths; + Imports = imports; + } + + public Data Serialize() + => new Data() + { + ScriptPath = ScriptPath, + MetadataReferencePaths = MetadataReferencePaths.ToArray(), + Imports = Imports.ToArray(), + }; + } +} diff --git a/src/Interactive/Host/InteractiveHostResources.Designer.cs b/src/Interactive/Host/InteractiveHostResources.Designer.cs deleted file mode 100644 index 1e8d1c57fda..00000000000 --- a/src/Interactive/Host/InteractiveHostResources.Designer.cs +++ /dev/null @@ -1,198 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.CodeAnalysis.Interactive { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class InteractiveHostResources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal InteractiveHostResources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CodeAnalysis.Interactive.InteractiveHostResources", typeof(InteractiveHostResources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Attempt to connect to process #{0} failed, retrying .... - /// - internal static string Attempt_to_connect_to_process_Sharp_0_failed_retrying { - get { - return ResourceManager.GetString("Attempt_to_connect_to_process_Sharp_0_failed_retrying", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot resolve reference '{0}'.. - /// - internal static string Cannot_resolve_reference_0 { - get { - return ResourceManager.GetString("Cannot_resolve_reference_0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to create a remote process for interactive code execution.. - /// - internal static string Failed_to_create_a_remote_process_for_interactive_code_execution { - get { - return ResourceManager.GetString("Failed_to_create_a_remote_process_for_interactive_code_execution", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to initialize remote interactive process.. - /// - internal static string Failed_to_initialize_remote_interactive_process { - get { - return ResourceManager.GetString("Failed_to_initialize_remote_interactive_process", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to launch '{0}' process (exit code: {1}) with output: . - /// - internal static string Failed_to_launch_0_process_exit_code_colon_1_with_output_colon { - get { - return ResourceManager.GetString("Failed_to_launch_0_process_exit_code_colon_1_with_output_colon", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Hosting process exited with exit code {0}.. - /// - internal static string Hosting_process_exited_with_exit_code_0 { - get { - return ResourceManager.GetString("Hosting_process_exited_with_exit_code_0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Interactive Host not initialized.. - /// - internal static string Interactive_Host_not_initialized { - get { - return ResourceManager.GetString("Interactive_Host_not_initialized", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Loading context from '{0}'.. - /// - internal static string Loading_context_from_0 { - get { - return ResourceManager.GetString("Loading_context_from_0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to + additional {0} {1}. - /// - internal static string plus_additional_0_1 { - get { - return ResourceManager.GetString("plus_additional_0_1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Searched in directories:. - /// - internal static string Searched_in_directories_colon { - get { - return ResourceManager.GetString("Searched_in_directories_colon", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Searched in directory:. - /// - internal static string Searched_in_directory_colon { - get { - return ResourceManager.GetString("Searched_in_directory_colon", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specified file not found.. - /// - internal static string Specified_file_not_found { - get { - return ResourceManager.GetString("Specified_file_not_found", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specified file not found: {0}. - /// - internal static string Specified_file_not_found_colon_0 { - get { - return ResourceManager.GetString("Specified_file_not_found_colon_0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type "#help" for more information.. - /// - internal static string Type_Sharphelp_for_more_information { - get { - return ResourceManager.GetString("Type_Sharphelp_for_more_information", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to create hosting process.. - /// - internal static string Unable_to_create_hosting_process { - get { - return ResourceManager.GetString("Unable_to_create_hosting_process", resourceCulture); - } - } - } -} diff --git a/src/Interactive/Host/InteractiveHostResources.resx b/src/Interactive/Host/InteractiveHostResources.resx index ae295c078b2..3b41a16366d 100644 --- a/src/Interactive/Host/InteractiveHostResources.resx +++ b/src/Interactive/Host/InteractiveHostResources.resx @@ -154,7 +154,7 @@ Failed to launch '{0}' process (exit code: {1}) with output: - Failed to create a remote process for interactive code execution. + Failed to create a remote process for interactive code execution: '{0}' Failed to initialize remote interactive process. diff --git a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj index d9452d758c2..fb350a27fe7 100644 --- a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj +++ b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj @@ -1,10 +1,11 @@  - + Library Microsoft.CodeAnalysis.Interactive - net472 + netstandard2.0 + true true true @@ -14,12 +15,11 @@ .NET Compiler Platform ("Roslyn") interactive host implementation. - + global,Scripting - @@ -27,16 +27,12 @@ + - - - - - @@ -63,17 +59,7 @@ - - InteractiveHostResources.resx - True - True - - - - Designer - InteractiveHostResources.Designer.cs - ResXFileCodeGenerator - + \ No newline at end of file diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.cs.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.cs.xlf index 062281446a9..1e3fc11cb0e 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.cs.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.cs.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - Nepodařilo se vytvořit vzdálený proces pro provádění interaktivního kódu. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.de.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.de.xlf index edb3b234ef8..f74d32033b8 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.de.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.de.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - Ein Remoteprozess für die interaktive Codeausführung konnte nicht erstellt werden. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.es.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.es.xlf index 373b9331a60..d29a09a5e62 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.es.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.es.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - Error al crear un proceso remoto para la ejecución de código interactivo. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.fr.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.fr.xlf index 111d9d99253..922f6fcc26d 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.fr.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.fr.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - Échec de la création d'un processus à distance pour l'exécution de code interactif. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.it.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.it.xlf index f95eff2aa77..979a38bd598 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.it.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.it.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - Non è stato possibile creare un processo remoto per l'esecuzione di codice interattiva. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.ja.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.ja.xlf index 59a68eb9e30..f5dd1ec2e2a 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.ja.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.ja.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - 対話型コードの実行のリモート プロセスを作成できませんでした。 + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.ko.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.ko.xlf index 473c457ca63..296338f8269 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.ko.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.ko.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - 대화형 코드 실행에 대한 원격 프로세스를 만들지 못했습니다. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.pl.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.pl.xlf index 08a1380212f..ba8214a6161 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.pl.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.pl.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - Utworzenie procesu zdalnego dla wykonania kodu interaktywnego nie powiodło się. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.pt-BR.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.pt-BR.xlf index 77f6e3a99c3..8e5318d6006 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.pt-BR.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.pt-BR.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - Falha ao criar um processo remoto para a execução de código interativo. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.ru.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.ru.xlf index 38a0141d7e9..7c2ef1fffb5 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.ru.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.ru.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - Не удалось создать удаленный процесс для выполнения интерактивного кода. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.tr.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.tr.xlf index d5da6bbd66e..e7e29767f57 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.tr.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.tr.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - Etkileşimli kod yürütme için uzak işlem oluşturulamadı. + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.zh-Hans.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.zh-Hans.xlf index 02c6458c6c4..465bd30f4a6 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.zh-Hans.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.zh-Hans.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - 为交互代码的执行创建远程进程失败。 + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/Host/xlf/InteractiveHostResources.zh-Hant.xlf b/src/Interactive/Host/xlf/InteractiveHostResources.zh-Hant.xlf index 0c4c91cc791..9123f23c0f8 100644 --- a/src/Interactive/Host/xlf/InteractiveHostResources.zh-Hant.xlf +++ b/src/Interactive/Host/xlf/InteractiveHostResources.zh-Hant.xlf @@ -13,8 +13,8 @@ - Failed to create a remote process for interactive code execution. - 無法為互動式程式碼執行,建立遠端處理序。 + Failed to create a remote process for interactive code execution: '{0}' + Failed to create a remote process for interactive code execution: '{0}' diff --git a/src/Interactive/DesktopHost/App.config b/src/Interactive/HostProcess/App.config similarity index 100% rename from src/Interactive/DesktopHost/App.config rename to src/Interactive/HostProcess/App.config diff --git a/src/Interactive/HostProcess/Core/CSharpInteractive.rsp b/src/Interactive/HostProcess/Core/CSharpInteractive.rsp new file mode 100644 index 00000000000..7b7f0089f2b --- /dev/null +++ b/src/Interactive/HostProcess/Core/CSharpInteractive.rsp @@ -0,0 +1,41 @@ +/r:Microsoft.CSharp +/r:System.Collections +/r:System.Collections.Concurrent +/r:System.Console +/r:System.Diagnostics.Debug +/r:System.Diagnostics.Process +/r:System.Diagnostics.StackTrace +/r:System.Dynamic.Runtime +/r:System.Globalization +/r:System.IO +/r:System.IO.FileSystem +/r:System.IO.FileSystem.Primitives +/r:System.Linq +/r:System.Linq.Expressions +/r:System.Reflection +/r:System.Reflection.Extensions +/r:System.Reflection.Primitives +/r:System.Runtime +/r:System.Runtime.Extensions +/r:System.Runtime.Numerics +/r:System.Runtime.InteropServices +/r:System.Text.Encoding +/r:System.Text.Encoding.CodePages +/r:System.Text.Encoding.Extensions +/r:System.Text.RegularExpressions +/r:System.Threading +/r:System.Threading.Tasks +/r:System.Threading.Tasks.Parallel +/r:System.Threading.Thread +/r:System.ValueTuple + +/u:System +/u:System.IO +/u:System.Collections.Generic +/u:System.Console +/u:System.Diagnostics +/u:System.Dynamic +/u:System.Linq +/u:System.Linq.Expressions +/u:System.Text +/u:System.Threading.Tasks diff --git a/src/Interactive/DesktopHost/CSharpInteractive.rsp b/src/Interactive/HostProcess/Desktop/CSharpInteractive.rsp similarity index 100% rename from src/Interactive/DesktopHost/CSharpInteractive.rsp rename to src/Interactive/HostProcess/Desktop/CSharpInteractive.rsp diff --git a/src/Interactive/HostProcess/InteractiveHost32.csproj b/src/Interactive/HostProcess/InteractiveHost32.csproj new file mode 100644 index 00000000000..7c17a830d38 --- /dev/null +++ b/src/Interactive/HostProcess/InteractiveHost32.csproj @@ -0,0 +1,39 @@ + + + + + + true + Exe + net472 + true + + + + + + + + + + + + + <_PublishProjectOutputGroup Include="$(TargetPath)"> + $(TargetFileName) + true + 3 + X86 + /InteractiveHost/Desktop/InteractiveHost32.exe + + <_PublishProjectOutputGroup Include="$(TargetPath).config" TargetPath="$(TargetFileName).config"/> + + + \ No newline at end of file diff --git a/src/Interactive/HostProcess/InteractiveHost64.csproj b/src/Interactive/HostProcess/InteractiveHost64.csproj new file mode 100644 index 00000000000..d0db53fc5e6 --- /dev/null +++ b/src/Interactive/HostProcess/InteractiveHost64.csproj @@ -0,0 +1,48 @@ + + + + + + false + Exe + net472;netcoreapp3.1 + win10-x64 + true + + + true + false + false + + + + + + + + + + + + + <_PublishedFiles Include="$(PublishDir)**\*.*" /> + <_PublishedFiles Remove="@(_PublishedFiles)" Condition="'%(Extension)' == '.pdb'" /> + + + <_PublishedFiles Include="$(MSBuildProjectDirectory)\Desktop\CSharpInteractive.rsp" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" /> + <_PublishedFiles Include="$(MSBuildProjectDirectory)\Core\CSharpInteractive.rsp" Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'" /> + + + <_PublishedFiles Update="@(_PublishedFiles)" TargetPath="%(RecursiveDir)%(Filename)%(Extension)" /> + + + <_PublishedFiles Update="@(_PublishedFiles)" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework' and ('%(Extension)' == '.dll' or '%(Extension)' == '.exe')"> + true + 3 + All + X64 + /InteractiveHost/Desktop/InteractiveHost64.exe + + + + \ No newline at end of file diff --git a/src/Interactive/HostProcess/InteractiveHostEntryPoint.cs b/src/Interactive/HostProcess/InteractiveHostEntryPoint.cs new file mode 100644 index 00000000000..dd9f40b89a3 --- /dev/null +++ b/src/Interactive/HostProcess/InteractiveHostEntryPoint.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.CodeAnalysis.ErrorReporting; + +namespace Microsoft.CodeAnalysis.Interactive +{ + internal static class InteractiveHostEntryPoint + { + private static async Task Main(string[] args) + { + FatalError.Handler = FailFast.OnFatalException; + + // Disables Windows Error Reporting for the process, so that the process fails fast. + SetErrorMode(GetErrorMode() | ErrorMode.SEM_FAILCRITICALERRORS | ErrorMode.SEM_NOOPENFILEERRORBOX | ErrorMode.SEM_NOGPFAULTERRORBOX); + + Control? control = null; + using (var resetEvent = new ManualResetEventSlim(false)) + { + var uiThread = new Thread(() => + { + control = new Control(); + control.CreateControl(); + resetEvent.Set(); + Application.Run(); + }); + + uiThread.SetApartmentState(ApartmentState.STA); + uiThread.IsBackground = true; + uiThread.Start(); + resetEvent.Wait(); + } + + var invokeOnMainThread = new Func, object>(operation => control!.Invoke(operation)); + + try + { + await InteractiveHost.Service.RunServerAsync(args, invokeOnMainThread).ConfigureAwait(false); + return 0; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return 1; + } + } + + [DllImport("kernel32", PreserveSig = true)] + internal static extern ErrorMode SetErrorMode(ErrorMode mode); + + [DllImport("kernel32", PreserveSig = true)] + internal static extern ErrorMode GetErrorMode(); + + [Flags] + internal enum ErrorMode : int + { + /// + /// Use the system default, which is to display all error dialog boxes. + /// + SEM_FAILCRITICALERRORS = 0x0001, + + /// + /// The system does not display the critical-error-handler message box. Instead, the system sends the error to the calling process. + /// Best practice is that all applications call the process-wide SetErrorMode function with a parameter of SEM_FAILCRITICALERRORS at startup. + /// This is to prevent error mode dialogs from hanging the application. + /// + SEM_NOGPFAULTERRORBOX = 0x0002, + + /// + /// The system automatically fixes memory alignment faults and makes them invisible to the application. + /// It does this for the calling process and any descendant processes. This feature is only supported by + /// certain processor architectures. For more information, see the Remarks section. + /// After this value is set for a process, subsequent attempts to clear the value are ignored. + /// + SEM_NOALIGNMENTFAULTEXCEPT = 0x0004, + + /// + /// The system does not display a message box when it fails to find a file. Instead, the error is returned to the calling process. + /// + SEM_NOOPENFILEERRORBOX = 0x8000, + } + } +} diff --git a/src/Interactive/HostTest/AbstractInteractiveHostTests.cs b/src/Interactive/HostTest/AbstractInteractiveHostTests.cs index a09f49378c0..b1956bdf462 100644 --- a/src/Interactive/HostTest/AbstractInteractiveHostTests.cs +++ b/src/Interactive/HostTest/AbstractInteractiveHostTests.cs @@ -5,26 +5,217 @@ #nullable enable extern alias InteractiveHost; - using System; -using Microsoft.CodeAnalysis.CSharp; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; +using Xunit; namespace Microsoft.CodeAnalysis.UnitTests.Interactive { - using System.IO; using InteractiveHost::Microsoft.CodeAnalysis.Interactive; - public abstract class AbstractInteractiveHostTests : CSharpTestBase + public abstract class AbstractInteractiveHostTests : CSharpTestBase, IAsyncLifetime { - internal static string GetInteractiveHostDirectory() - => Path.GetDirectoryName(typeof(StressTests).Assembly.Location); + private SynchronizedStringWriter _synchronizedOutput = null!; + private SynchronizedStringWriter _synchronizedErrorOutput = null!; + private int[] _outputReadPosition = new int[] { 0, 0 }; + + internal readonly InteractiveHost Host; + + // DOTNET_ROOT must be set in order to run host process on .NET Core on machines (like CI) + // that do not have the required version of the runtime installed globally. + // + // If it was not set the process would fail with exit code -2147450749: + // "A fatal error occurred. The required library hostfxr.dll could not be found." + // + // See https://github.com/dotnet/runtime/issues/38462. + static AbstractInteractiveHostTests() + { + if (Environment.GetEnvironmentVariable("DOTNET_ROOT") == null) + { + var dir = RuntimeEnvironment.GetRuntimeDirectory(); + + // find directory above runtime dir that contains dotnet.exe + while (dir != null && !File.Exists(Path.Combine(dir, "dotnet.exe"))) + { + dir = Path.GetDirectoryName(dir); + } + + // dotnet.exe not found + Assert.NotNull(dir); + + Environment.SetEnvironmentVariable("DOTNET_ROOT", dir); + } + } + + protected AbstractInteractiveHostTests() + { + Host = new InteractiveHost(typeof(CSharpReplServiceProvider), ".", millisecondsTimeout: -1, joinOutputWritingThreadsOnDisposal: true); + + Host.InteractiveHostProcessCreationFailed += (exception, exitCode) => + Assert.False(true, (exception?.Message ?? "Host process terminated unexpectedly.") + $" Exit code: {exitCode?.ToString() ?? ""}"); + + RedirectOutput(); + } + + internal abstract InteractiveHostPlatform DefaultPlatform { get; } + internal abstract bool UseDefaultInitializationFile { get; } + + public async Task InitializeAsync() + { + var initializationFileName = UseDefaultInitializationFile ? "CSharpInteractive.rsp" : null; + + await Host.ResetAsync(InteractiveHostOptions.CreateFromDirectory(TestUtils.HostRootPath, initializationFileName, CultureInfo.InvariantCulture, DefaultPlatform)); + + // assert and remove logo: + var output = SplitLines(await ReadOutputToEnd()); + var errorOutput = await ReadErrorOutputToEnd(); + + AssertEx.AssertEqualToleratingWhitespaceDifferences("", errorOutput); + + var expectedOutput = new List(); + expectedOutput.Add(string.Format(CSharpScriptingResources.LogoLine1, CommonCompiler.GetProductVersion(typeof(CSharpReplServiceProvider)))); + + if (UseDefaultInitializationFile) + { + expectedOutput.Add(string.Format(InteractiveHostResources.Loading_context_from_0, initializationFileName)); + } + + expectedOutput.Add(InteractiveHostResources.Type_Sharphelp_for_more_information); + + AssertEx.Equal(expectedOutput, output); + + // remove logo: + ClearOutput(); + } + + public async Task DisposeAsync() + { + var service = await Host.TryGetServiceAsync(); + Assert.NotNull(service); + + var process = service!.Process; + + Host.Dispose(); + + // the process should be terminated + if (process != null && !process.HasExited) + { + process.WaitForExit(); + } + } + + public void RedirectOutput() + { + _synchronizedOutput = new SynchronizedStringWriter(); + _synchronizedErrorOutput = new SynchronizedStringWriter(); + ClearOutput(); + Host.SetOutputs(_synchronizedOutput, _synchronizedErrorOutput); + } + + public static ImmutableArray SplitLines(string text) + { + return ImmutableArray.Create(text.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)); + } + + public async Task LoadReference(string reference) + { + return await Execute($"#r \"{reference}\""); + } + + public async Task Execute(string code) + { + var task = await Host.ExecuteAsync(code); + return task.Success; + } + + public Task ReadErrorOutputToEnd() + { + return ReadOutputToEnd(isError: true); + } + + public void ClearOutput() + { + _outputReadPosition = new int[] { 0, 0 }; + _synchronizedOutput.Clear(); + _synchronizedErrorOutput.Clear(); + } + + public async Task RestartHost() + { + ClearOutput(); + + await Host.ResetAsync(InteractiveHostOptions.CreateFromDirectory(TestUtils.HostRootPath, initializationFileName: null, CultureInfo.InvariantCulture, InteractiveHostPlatform.Desktop64)); + } + + public async Task ReadOutputToEnd(bool isError = false) + { + // writes mark to the STDOUT/STDERR pipe in the remote process: + var remoteService = await Host.TryGetServiceAsync().ConfigureAwait(false); + + if (remoteService == null) + { + Assert.True(false, @$" +Remote service unavailable +STDERR: {_synchronizedErrorOutput} +STDOUT: {_synchronizedOutput} +"); + } + + var writer = isError ? _synchronizedErrorOutput : _synchronizedOutput; + var markPrefix = '\uFFFF'; + var mark = markPrefix + Guid.NewGuid().ToString(); + + await remoteService!.JsonRpc.InvokeAsync(nameof(InteractiveHost.Service.RemoteConsoleWriteAsync), Encoding.UTF8.GetBytes(mark), isError).ConfigureAwait(false); + while (true) + { + var data = writer.Prefix(mark, ref _outputReadPosition[isError ? 0 : 1]); + if (data != null) + { + return data; + } + + await Task.Delay(10); + } + } + + public static (string Path, ImmutableArray Image) CompileLibrary( + TempDirectory dir, string fileName, string assemblyName, string source, params MetadataReference[] references) + { + var file = dir.CreateFile(fileName); + var compilation = CreateEmptyCompilation( + new[] { source }, + assemblyName: assemblyName, + references: TargetFrameworkUtil.GetReferences(TargetFramework.NetStandard20, references), + options: fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ? TestOptions.ReleaseExe : TestOptions.ReleaseDll); + + var image = compilation.EmitToArray(); + file.WriteAllBytes(image); + + return (file.Path, image); + } + + public static string PrintSearchPaths(params string[] paths) + => paths.Length == 0 ? "SearchPaths { }" : $"SearchPaths {{ {string.Join(", ", paths.Select(p => "\"" + p.Replace("\\", "\\\\") + "\""))} }}"; - // Forces xUnit to load dependent assemblies before we launch InteractiveHost.exe process. - private static readonly Type[] s_testDependencies = new[] + public async Task GetHostRuntimeDirectoryAsync() { - typeof(InteractiveHost), - typeof(CSharpCompilation) - }; + var remoteService = await Host.TryGetServiceAsync().ConfigureAwait(false); + Assert.NotNull(remoteService); + return await remoteService!.JsonRpc.InvokeAsync(nameof(InteractiveHost.Service.GetRuntimeDirectoryAsync)).ConfigureAwait(false); + } } } diff --git a/src/Interactive/HostTest/InteractiveHost.UnitTests.csproj b/src/Interactive/HostTest/InteractiveHost.UnitTests.csproj index a9f8e6b4d69..16f16cdeda5 100644 --- a/src/Interactive/HostTest/InteractiveHost.UnitTests.csproj +++ b/src/Interactive/HostTest/InteractiveHost.UnitTests.csproj @@ -4,55 +4,63 @@ Library Roslyn.InteractiveHost.UnitTests - net472 + netcoreapp3.1 + - - - - - InteractiveHost - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + TargetFramework=net472 + InteractiveHostFiles_Desktop32 + + + + TargetFramework=net472 + InteractiveHostFiles_Desktop64 + + + + TargetFramework=netcoreapp3.1 + InteractiveHostFiles_Core + + + + + + + + + + + + <_SourceFiles Include="@(InteractiveHostFiles_Desktop64)" TargetDirectory="Host\Desktop\" /> + <_SourceFiles Include="@(InteractiveHostFiles_Desktop32)" TargetDirectory="Host\Desktop\" /> + <_SourceFiles Include="@(InteractiveHostFiles_Core)" TargetDirectory="Host\Core\" /> + + + \ No newline at end of file diff --git a/src/Interactive/HostTest/InteractiveHostCoreInitTests.cs b/src/Interactive/HostTest/InteractiveHostCoreInitTests.cs new file mode 100644 index 00000000000..a671cf9fc4e --- /dev/null +++ b/src/Interactive/HostTest/InteractiveHostCoreInitTests.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +extern alias InteractiveHost; +using System.IO; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests.Interactive +{ + using InteractiveHost::Microsoft.CodeAnalysis.Interactive; + + [Trait(Traits.Feature, Traits.Features.InteractiveHost)] + public sealed class InteractiveHostCoreInitTests : AbstractInteractiveHostTests + { + internal override InteractiveHostPlatform DefaultPlatform => InteractiveHostPlatform.Core; + internal override bool UseDefaultInitializationFile => true; + + [Fact] + public async Task DefaultReferencesAndImports() + { + await Execute(@" +dynamic d = (""home"", Directory.GetCurrentDirectory(), await Task.FromResult(1)); +WriteLine(d.ToString()); +"); + + var dir = Path.GetDirectoryName(typeof(InteractiveHostCoreInitTests).Assembly.Location); + + var output = await ReadOutputToEnd(); + var error = await ReadErrorOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", error); + AssertEx.AssertEqualToleratingWhitespaceDifferences($"(home, {dir}, 1)", output); + } + + [Fact] + public async Task InteractiveHostImplAssemblies() + { + var scriptingAssemblyName = typeof(CSharpScript).Assembly.GetName().Name; + + await Execute($@"#r ""{scriptingAssemblyName}"""); + + var error = await ReadErrorOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences(@$" +(1,1): error CS0006: {string.Format(CSharpResources.ERR_NoMetadataFile, scriptingAssemblyName)}", error); + } + + [Fact] + public async Task SearchPaths1() + { + var dll = Temp.CreateFile(extension: ".dll").WriteAllBytes(TestResources.MetadataTests.InterfaceAndClass.CSInterfaces01); + var srcDir = Temp.CreateDirectory(); + var dllDir = Path.GetDirectoryName(dll.Path)!; + srcDir.CreateFile("goo.csx").WriteAllText("ReferencePaths.Add(@\"" + dllDir + "\");"); + + // print default: + await Host.ExecuteAsync(@"ReferencePaths"); + var output = await ReadOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences(PrintSearchPaths(), output); + + await Host.ExecuteAsync(@"SourcePaths"); + output = await ReadOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences(PrintSearchPaths(), output); + + // add and test if added: + await Host.ExecuteAsync("SourcePaths.Add(@\"" + srcDir + "\");"); + + await Host.ExecuteAsync(@"SourcePaths"); + + output = await ReadOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences(PrintSearchPaths(srcDir.Path), output); + + // execute file (uses modified search paths), the file adds a reference path + await Host.ExecuteFileAsync("goo.csx"); + + await Host.ExecuteAsync(@"ReferencePaths"); + + output = await ReadOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences(PrintSearchPaths(dllDir), output); + + await Host.AddReferenceAsync(Path.GetFileName(dll.Path)); + + await Host.ExecuteAsync(@"typeof(Metadata.ICSProp)"); + + var error = await ReadErrorOutputToEnd(); + output = await ReadOutputToEnd(); + Assert.Equal("", error); + Assert.Equal("[Metadata.ICSProp]\r\n", output); + } + } +} diff --git a/src/Interactive/HostTest/InteractiveHostDesktopInitTests.cs b/src/Interactive/HostTest/InteractiveHostDesktopInitTests.cs new file mode 100644 index 00000000000..ab987686d0c --- /dev/null +++ b/src/Interactive/HostTest/InteractiveHostDesktopInitTests.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +extern alias InteractiveHost; + +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests.Interactive +{ + using InteractiveHost::Microsoft.CodeAnalysis.Interactive; + + [Trait(Traits.Feature, Traits.Features.InteractiveHost)] + public sealed class InteractiveHostDesktopInitTests : AbstractInteractiveHostTests + { + internal override InteractiveHostPlatform DefaultPlatform => InteractiveHostPlatform.Desktop32; + internal override bool UseDefaultInitializationFile => true; + + [Fact] + public async Task SearchPaths1() + { + var fxDir = await GetHostRuntimeDirectoryAsync(); + + var dll = Temp.CreateFile(extension: ".dll").WriteAllBytes(TestResources.MetadataTests.InterfaceAndClass.CSInterfaces01); + var srcDir = Temp.CreateDirectory(); + var dllDir = Path.GetDirectoryName(dll.Path)!; + srcDir.CreateFile("goo.csx").WriteAllText("ReferencePaths.Add(@\"" + dllDir + "\");"); + + // print default: + await Host.ExecuteAsync(@"ReferencePaths"); + var output = await ReadOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences(PrintSearchPaths(fxDir), output); + + await Host.ExecuteAsync(@"SourcePaths"); + output = await ReadOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences(PrintSearchPaths(), output); + + // add and test if added: + await Host.ExecuteAsync("SourcePaths.Add(@\"" + srcDir + "\");"); + + await Host.ExecuteAsync(@"SourcePaths"); + + output = await ReadOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences(PrintSearchPaths(srcDir.Path), output); + + // execute file (uses modified search paths), the file adds a reference path + await Host.ExecuteFileAsync("goo.csx"); + + await Host.ExecuteAsync(@"ReferencePaths"); + + output = await ReadOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences(PrintSearchPaths(fxDir, dllDir), output); + + await Host.AddReferenceAsync(Path.GetFileName(dll.Path)); + + await Host.ExecuteAsync(@"typeof(Metadata.ICSProp)"); + + var error = await ReadErrorOutputToEnd(); + output = await ReadOutputToEnd(); + Assert.Equal("", error); + Assert.Equal("[Metadata.ICSProp]\r\n", output); + } + + [Fact] + public async Task AddReference_AssemblyAlreadyLoaded() + { + var result = await LoadReference("System.Core"); + var output = await ReadOutputToEnd(); + var error = await ReadErrorOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", error); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", output); + Assert.True(result); + + result = await LoadReference("System.Core.dll"); + output = await ReadOutputToEnd(); + error = await ReadErrorOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", error); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", output); + Assert.True(result); + } + } +} diff --git a/src/Interactive/HostTest/InteractiveHostTests.cs b/src/Interactive/HostTest/InteractiveHostDesktopTests.cs similarity index 73% rename from src/Interactive/HostTest/InteractiveHostTests.cs rename to src/Interactive/HostTest/InteractiveHostDesktopTests.cs index 2f319d1271f..c1eb4bcc71b 100644 --- a/src/Interactive/HostTest/InteractiveHostTests.cs +++ b/src/Interactive/HostTest/InteractiveHostDesktopTests.cs @@ -7,21 +7,14 @@ extern alias InteractiveHost; using System; -using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; using System.IO; -using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Scripting; -using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting; -using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -33,159 +26,10 @@ namespace Microsoft.CodeAnalysis.UnitTests.Interactive using InteractiveHost::Microsoft.CodeAnalysis.Interactive; [Trait(Traits.Feature, Traits.Features.InteractiveHost)] - public sealed class InteractiveHostTests : AbstractInteractiveHostTests, IAsyncLifetime + public sealed class InteractiveHostDesktopTests : AbstractInteractiveHostTests { - #region Utils - - private SynchronizedStringWriter _synchronizedOutput = null!; - private SynchronizedStringWriter _synchronizedErrorOutput = null!; - private int[] _outputReadPosition = new int[] { 0, 0 }; - - private readonly InteractiveHost _host; - - private static readonly string s_fxDir = FileUtilities.NormalizeDirectoryPath(RuntimeEnvironment.GetRuntimeDirectory()); - private static readonly string s_homeDir = FileUtilities.NormalizeDirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); - - public InteractiveHostTests() - { - _host = new InteractiveHost(typeof(CSharpReplServiceProvider), ".", millisecondsTimeout: -1, joinOutputWritingThreadsOnDisposal: true); - - RedirectOutput(); - } - - public async Task InitializeAsync() - { - await _host.ResetAsync(new InteractiveHostOptions(GetInteractiveHostDirectory(), initializationFile: null, culture: CultureInfo.InvariantCulture)); - - await _host.SetPathsAsync(new[] { s_fxDir }, new[] { s_homeDir }, s_homeDir); - - // assert and remove logo: - var output = SplitLines(await ReadOutputToEnd()); - var errorOutput = await ReadErrorOutputToEnd(); - - Assert.Equal("", errorOutput); - Assert.Equal(2, output.Length); - var version = CommonCompiler.GetProductVersion(typeof(CSharpReplServiceProvider)); - Assert.Equal(string.Format(CSharpScriptingResources.LogoLine1, version), output[0]); - // "Type "#help" for more information." - Assert.Equal(InteractiveHostResources.Type_Sharphelp_for_more_information, output[1]); - - // remove logo: - ClearOutput(); - } - - public Task DisposeAsync() - { - return Task.CompletedTask; - } - - public override void Dispose() - { - try - { - var process = _host.TryGetProcess(); - - _host.Dispose(); - - // the process should be terminated - if (process != null && !process.HasExited) - { - process.WaitForExit(); - } - } - finally - { - // Dispose temp files only after the InteractiveHost exits, - // so that assemblies are unloaded. - base.Dispose(); - } - } - - private void RedirectOutput() - { - _synchronizedOutput = new SynchronizedStringWriter(); - _synchronizedErrorOutput = new SynchronizedStringWriter(); - ClearOutput(); - _host.SetOutputs(_synchronizedOutput, _synchronizedErrorOutput); - } - - private static ImmutableArray SplitLines(string text) - { - return ImmutableArray.Create(text.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)); - } - - private async Task LoadReference(string reference) - { - return await Execute($"#r \"{reference}\""); - } - - private async Task Execute(string code) - { - var task = await _host.ExecuteAsync(code); - return task.Success; - } - - //private bool IsShadowCopy(string path) - //{ - // return _host.TryGetService().IsShadowCopy(path); - //} - - public Task ReadErrorOutputToEnd() - { - return ReadOutputToEnd(isError: true); - } - private void ClearOutput() - { - _outputReadPosition = new int[] { 0, 0 }; - _synchronizedOutput.Clear(); - _synchronizedErrorOutput.Clear(); - } - - private async Task RestartHost(string? rspFile = null) - { - ClearOutput(); - - await _host.ResetAsync(new InteractiveHostOptions(GetInteractiveHostDirectory(), initializationFile: rspFile, culture: CultureInfo.InvariantCulture)); - } - - public async Task ReadOutputToEnd(bool isError = false) - { - var writer = isError ? _synchronizedErrorOutput : _synchronizedOutput; - var markPrefix = '\uFFFF'; - var mark = markPrefix + Guid.NewGuid().ToString(); - - // writes mark to the STDOUT/STDERR pipe in the remote process: - var remoteService = await _host.TryGetServiceAsync().ConfigureAwait(false); - await remoteService.JsonRpc.InvokeAsync("RemoteConsoleWriteAsync", Encoding.UTF8.GetBytes(mark), isError).ConfigureAwait(false); - while (true) - { - var data = writer.Prefix(mark, ref _outputReadPosition[isError ? 0 : 1]); - if (data != null) - { - return data; - } - - await Task.Delay(10); - } - } - - private static (string Path, ImmutableArray Image) CompileLibrary( - TempDirectory dir, string fileName, string assemblyName, string source, params MetadataReference[] references) - { - var file = dir.CreateFile(fileName); - var compilation = CreateEmptyCompilation( - new[] { source }, - assemblyName: assemblyName, - references: references.Concat(new[] { MetadataReference.CreateFromAssemblyInternal(typeof(object).Assembly) }), - options: fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ? TestOptions.ReleaseExe : TestOptions.ReleaseDll); - - var image = compilation.EmitToArray(); - file.WriteAllBytes(image); - - return (file.Path, image); - } - - #endregion + internal override InteractiveHostPlatform DefaultPlatform => InteractiveHostPlatform.Desktop64; + internal override bool UseDefaultInitializationFile => false; [Fact] public async Task OutputRedirection() @@ -193,8 +37,7 @@ public async Task OutputRedirection() await Execute(@" System.Console.WriteLine(""hello-\u4567!""); System.Console.Error.WriteLine(""error-\u7890!""); -1+1 - "); +1+1"); var output = await ReadOutputToEnd(); var error = await ReadErrorOutputToEnd(); @@ -233,7 +76,7 @@ public async Task StackOverflow() return; } - var process = _host.TryGetProcess(); + var process = Host.TryGetProcess(); await Execute(@" int goo(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) @@ -278,29 +121,30 @@ void goo() public async Task AsyncExecute_InfiniteLoop() { var mayTerminate = new ManualResetEvent(false); - _host.ErrorOutputReceived += (_, __) => mayTerminate.Set(); + Host.ErrorOutputReceived += (_, __) => mayTerminate.Set(); - await _host.ExecuteAsync(MethodWithInfiniteLoop + "\r\nfoo()"); + await Host.ExecuteAsync(MethodWithInfiniteLoop + "\r\nfoo()"); Assert.True(mayTerminate.WaitOne()); await RestartHost(); - await _host.ExecuteAsync(MethodWithInfiniteLoop + "\r\nfoo()"); + await Host.ExecuteAsync(MethodWithInfiniteLoop + "\r\nfoo()"); var execution = await Execute(@"1+1"); var output = await ReadOutputToEnd(); Assert.True(execution); Assert.Equal("2\r\n", output); } + [Fact(Skip = "529027")] public async Task AsyncExecute_HangingForegroundThreads() { var mayTerminate = new ManualResetEvent(false); - _host.OutputReceived += (_, __) => + Host.OutputReceived += (_, __) => { mayTerminate.Set(); }; - var executeTask = _host.ExecuteAsync(@" + var executeTask = Host.ExecuteAsync(@" using System.Threading; int i1 = 0; @@ -333,7 +177,7 @@ public async Task AsyncExecute_HangingForegroundThreads() // TODO: var service = _host.TryGetService(); // Assert.NotNull(service); - var process = _host.TryGetProcess(); + var process = Host.TryGetProcess(); Assert.NotNull(process); // service!.EmulateClientExit(); @@ -349,9 +193,9 @@ public async Task AsyncExecuteFile_InfiniteLoop() var file = Temp.CreateFile().WriteAllText(MethodWithInfiniteLoop + "\r\nfoo();").Path; var mayTerminate = new ManualResetEvent(false); - _host.ErrorOutputReceived += (_, __) => mayTerminate.Set(); + Host.ErrorOutputReceived += (_, __) => mayTerminate.Set(); - await _host.ExecuteFileAsync(file); + await Host.ExecuteFileAsync(file); mayTerminate.WaitOne(); await RestartHost(); @@ -361,11 +205,12 @@ public async Task AsyncExecuteFile_InfiniteLoop() Assert.True(execution); Assert.Equal("2\r\n", output); } + [Fact] public async Task AsyncExecuteFile_SourceKind() { var file = Temp.CreateFile().WriteAllText("1 1").Path; - var task = await _host.ExecuteFileAsync(file); + var task = await Host.ExecuteFileAsync(file); Assert.False(task.Success); var errorOut = (await ReadErrorOutputToEnd()).Trim(); @@ -376,7 +221,7 @@ public async Task AsyncExecuteFile_SourceKind() [Fact] public async Task AsyncExecuteFile_NonExistingFile() { - var result = await _host.ExecuteFileAsync("non existing file"); + var result = await Host.ExecuteFileAsync("non existing file"); Assert.False(result.Success); var errorOut = (await ReadErrorOutputToEnd()).Trim(); @@ -400,7 +245,7 @@ public class C WriteLine(5); ").Path; - var task = await _host.ExecuteFileAsync(file); + var task = await Host.ExecuteFileAsync(file); var output = await ReadOutputToEnd(); Assert.True(task.Success); @@ -418,10 +263,11 @@ public class C output = await ReadOutputToEnd(); Assert.Equal("4", output.Trim()); } + [Fact] public async Task AsyncExecuteFile_InvalidFileContent() { - await _host.ExecuteFileAsync(typeof(Process).Assembly.Location); + await Host.ExecuteFileAsync(typeof(Process).Assembly.Location); var errorOut = (await ReadErrorOutputToEnd()).Trim(); Assert.True(errorOut.StartsWith(typeof(Process).Assembly.Location + "(1,3):", StringComparison.Ordinal), "Error output should start with file name, line and column"); @@ -434,7 +280,7 @@ public async Task AsyncExecuteFile_ScriptFileWithBuildErrors() { var file = Temp.CreateFile().WriteAllText("#load blah.csx" + "\r\n" + "class C {}"); - await _host.ExecuteFileAsync(file.Path); + await Host.ExecuteFileAsync(file.Path); var errorOut = (await ReadErrorOutputToEnd()).Trim(); Assert.True(errorOut.StartsWith(file.Path + "(1,7):", StringComparison.Ordinal), "Error output should start with file name, line and column"); @@ -449,11 +295,11 @@ public async Task AsyncExecuteFile_ScriptFileWithBuildErrors() public async Task UserDefinedAssemblyResolve_InfiniteLoop() { var mayTerminate = new ManualResetEvent(false); - _host.ErrorOutputReceived += (_, __) => mayTerminate.Set(); + Host.ErrorOutputReceived += (_, __) => mayTerminate.Set(); // TODO: _host.TryGetService()!.HookMaliciousAssemblyResolve(); Assert.True(mayTerminate.WaitOne()); - await _host.AddReferenceAsync("nonexistingassembly" + Guid.NewGuid()); + await Host.AddReferenceAsync("nonexistingassembly" + Guid.NewGuid()); Assert.True(await Execute(@"1+1")); @@ -464,8 +310,9 @@ public async Task UserDefinedAssemblyResolve_InfiniteLoop() [Fact] public async Task AddReference_Path() { + var fxDir = await GetHostRuntimeDirectoryAsync(); Assert.False(await Execute("new System.Data.DataSet()")); - Assert.True(await LoadReference(Assembly.Load(new AssemblyName("System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")).Location)); + Assert.True(await LoadReference(Path.Combine(fxDir, "System.Data.dll"))); Assert.True(await Execute("new System.Data.DataSet()")); } @@ -523,24 +370,6 @@ public async Task AddReference_VersionUnification1() Assert.True(result); } - [Fact] - public async Task AddReference_AssemblyAlreadyLoaded() - { - var result = await LoadReference("System.Core"); - var output = await ReadOutputToEnd(); - var error = await ReadErrorOutputToEnd(); - Assert.Equal("", error.Trim()); - Assert.Equal("", output.Trim()); - Assert.True(result); - - result = await LoadReference("System.Core.dll"); - output = await ReadOutputToEnd(); - error = await ReadErrorOutputToEnd(); - Assert.Equal("", error.Trim()); - Assert.Equal("", output.Trim()); - Assert.True(result); - } - // Caused by submission not inheriting references. [Fact(Skip = "101161")] public async Task AddReference_ShadowCopy() @@ -628,6 +457,7 @@ public async Task AddReference_Dependencies_DllExe() Assert.Equal("", error.Trim()); Assert.Equal("1", output.Trim()); } + [Fact] public async Task AddReference_Dependencies_Versions() { @@ -655,6 +485,7 @@ public async Task AddReference_Dependencies_Versions() Assert.Equal("", error.Trim()); Assert.Equal("2", output.Trim()); } + [Fact] public async Task AddReference_AlreadyLoadedDependencies() { @@ -670,9 +501,10 @@ public async Task AddReference_AlreadyLoadedDependencies() var output = await ReadOutputToEnd(); var error = await ReadErrorOutputToEnd(); - Assert.Equal("", error.Trim()); - Assert.Equal("1", output.Trim()); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", error); + AssertEx.AssertEqualToleratingWhitespaceDifferences("1", output); } + [Fact(Skip = "101161")] public async Task AddReference_LoadUpdatedReference() { @@ -776,6 +608,7 @@ public async Task AddReference_MultipleReferencesWeakVersioning() Assert.Equal("TODO: error", error.Trim()); Assert.Equal("", output.Trim()); } + //// TODO (987032): //// [Fact] //// public void AsyncInitializeContextWithDotNETLibraries() @@ -859,28 +692,73 @@ public async Task AddReference_MultipleReferencesWeakVersioning() //// } [Fact] - public async Task ReferencePaths() + public async Task ReferencePathsRsp() { - var directory = Temp.CreateDirectory(); - var assemblyName = GetUniqueName(); - CompileLibrary(directory, assemblyName + ".dll", assemblyName, @"public class C { }"); - var rspFile = Temp.CreateFile(); - rspFile.WriteAllText("/lib:" + directory.Path); + var directory1 = Temp.CreateDirectory(); + CompileLibrary(directory1, "Assembly0.dll", "Assembly0", @"public class C0 { }"); + CompileLibrary(directory1, "Assembly1.dll", "Assembly1", @"public class C1 { }"); + + var initDirectory = Temp.CreateDirectory(); + var initFile = initDirectory.CreateFile("init.csx"); + + initFile.WriteAllText(@" +#r ""Assembly0.dll"" +System.Console.WriteLine(typeof(C0).Assembly.GetName()); +System.Console.WriteLine(typeof(C2).Assembly.GetName()); +Print(ReferencePaths); +"); + var rspDirectory = Temp.CreateDirectory(); + CompileLibrary(rspDirectory, "Assembly2.dll", "Assembly2", @"public class C2 { }"); + CompileLibrary(rspDirectory, "Assembly3.dll", "Assembly3", @"public class C3 { }"); + + var rspFile = rspDirectory.CreateFile("init.rsp"); + rspFile.WriteAllText($"/lib:{directory1.Path} /r:Assembly2.dll {initFile.Path}"); - await _host.ResetAsync(new InteractiveHostOptions(GetInteractiveHostDirectory(), initializationFile: rspFile.Path, culture: CultureInfo.InvariantCulture)); + await Host.ResetAsync(new InteractiveHostOptions(Host.OptionsOpt!.HostPath, rspFile.Path, culture: CultureInfo.InvariantCulture, Host.OptionsOpt!.Platform)); + var fxDir = await GetHostRuntimeDirectoryAsync(); - await Execute( -$@"#r ""{assemblyName}.dll"" -typeof(C).Assembly.GetName()"); + await Execute(@" +#r ""Assembly1.dll"" +System.Console.WriteLine(typeof(C1).Assembly.GetName()); +Print(ReferencePaths); +"); var error = await ReadErrorOutputToEnd(); - var output = SplitLines(await ReadOutputToEnd()); + var output = await ReadOutputToEnd(); - Assert.Equal("", error); - Assert.Equal(2, output.Length); - Assert.Equal($"{ string.Format(InteractiveHostResources.Loading_context_from_0, Path.GetFileName(rspFile.Path)) }", output[0]); - Assert.Equal($"[{assemblyName}, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]", output[1]); + var expectedSearchPaths = PrintSearchPaths(fxDir, directory1.Path); + + AssertEx.AssertEqualToleratingWhitespaceDifferences("", error); + AssertEx.AssertEqualToleratingWhitespaceDifferences($@" +{string.Format(InteractiveHostResources.Loading_context_from_0, Path.GetFileName(rspFile.Path))} +Assembly0, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null +Assembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null +{expectedSearchPaths} +Assembly1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null +{expectedSearchPaths} + ", output); + } + + [Fact] + public async Task ReferencePathsRsp_Error() + { + var initDirectory = Temp.CreateDirectory(); + var initFile = initDirectory.CreateFile("init.csx"); + initFile.WriteAllText(@"#r ""Assembly.dll"""); + + var rspDirectory = Temp.CreateDirectory(); + CompileLibrary(rspDirectory, "Assembly.dll", "Assembly", "public class C { }"); + + var rspFile = rspDirectory.CreateFile("init.rsp"); + rspFile.WriteAllText($"{initFile.Path}"); + + await Host.ResetAsync(new InteractiveHostOptions(Host.OptionsOpt!.HostPath, rspFile.Path, culture: CultureInfo.InvariantCulture, Host.OptionsOpt!.Platform)); + + var error = await ReadErrorOutputToEnd(); + AssertEx.AssertEqualToleratingWhitespaceDifferences( + @$"{initFile.Path}(1,1): error CS0006: {string.Format(CSharpResources.ERR_NoMetadataFile, "Assembly.dll")}", error); } + [Fact] public async Task DefaultUsings() { @@ -899,7 +777,7 @@ public async Task DefaultUsings() /u:System.Text /u:System.Threading.Tasks "); - await _host.ResetAsync(new InteractiveHostOptions(GetInteractiveHostDirectory(), initializationFile: rspFile.Path, culture: CultureInfo.InvariantCulture)); + await Host.ResetAsync(new InteractiveHostOptions(Host.OptionsOpt!.HostPath, rspFile.Path, CultureInfo.InvariantCulture, Host.OptionsOpt!.Platform)); await Execute(@" dynamic d = new ExpandoObject(); @@ -950,7 +828,7 @@ public async Task InitialScript_Error() {initFile.Path} "); - await _host.ResetAsync(new InteractiveHostOptions(GetInteractiveHostDirectory(), initializationFile: rspFile.Path, culture: CultureInfo.InvariantCulture)); + await Host.ResetAsync(new InteractiveHostOptions(Host.OptionsOpt!.HostPath, rspFile.Path, CultureInfo.InvariantCulture, Host.OptionsOpt!.Platform)); await Execute("new Process()"); @@ -977,7 +855,7 @@ public async Task ScriptAndArguments() b c "); - await _host.ResetAsync(new InteractiveHostOptions(GetInteractiveHostDirectory(), initializationFile: rspFile.Path, culture: CultureInfo.InvariantCulture)); + await Host.ResetAsync(new InteractiveHostOptions(Host.OptionsOpt!.HostPath, rspFile.Path, CultureInfo.InvariantCulture, Host.OptionsOpt!.Platform)); var error = await ReadErrorOutputToEnd(); Assert.Equal("", error); @@ -989,24 +867,6 @@ public async Task ScriptAndArguments() ", await ReadOutputToEnd()); } - [Fact] - public async Task ReferenceDirectives() - { - await Execute(@" -#r ""System.Numerics"" -#r """ + typeof(System.Linq.Expressions.Expression).Assembly.Location + @""" - -using static System.Console; -using System.Linq.Expressions; -using System.Numerics; -WriteLine(Expression.Constant(1)); -WriteLine(new Complex(2, 6).Real); -"); - - var output = await ReadOutputToEnd(); - Assert.Equal("1\r\n2\r\n", output); - } - [Fact] public async Task Script_NoHostNamespaces() { @@ -1074,51 +934,6 @@ public async Task MultiModuleAssembly() Assert.Equal("object[3] { Class1 { }, Class2 { }, Class3 { } }\r\n", output); } - [Fact] - public async Task SearchPaths1() - { - var dll = Temp.CreateFile(extension: ".dll").WriteAllBytes(TestResources.MetadataTests.InterfaceAndClass.CSInterfaces01); - var srcDir = Temp.CreateDirectory(); - var dllDir = Path.GetDirectoryName(dll.Path); - srcDir.CreateFile("goo.csx").WriteAllText("ReferencePaths.Add(@\"" + dllDir + "\");"); - - string normalizeSeparatorsAndFrameworkFolders(string s) => s.Replace("\\", "\\\\").Replace("Framework64", "Framework"); - - // print default: - await _host.ExecuteAsync(@"ReferencePaths"); - var output = await ReadOutputToEnd(); - Assert.Equal("SearchPaths { \"" + normalizeSeparatorsAndFrameworkFolders(string.Join("\", \"", new[] { s_fxDir })) + "\" }\r\n", output); - - await _host.ExecuteAsync(@"SourcePaths"); - output = await ReadOutputToEnd(); - Assert.Equal("SearchPaths { \"" + normalizeSeparatorsAndFrameworkFolders(string.Join("\", \"", new[] { s_homeDir })) + "\" }\r\n", output); - - // add and test if added: - await _host.ExecuteAsync("SourcePaths.Add(@\"" + srcDir + "\");"); - - await _host.ExecuteAsync(@"SourcePaths"); - - output = await ReadOutputToEnd(); - Assert.Equal("SearchPaths { \"" + normalizeSeparatorsAndFrameworkFolders(string.Join("\", \"", new[] { s_homeDir, srcDir.Path })) + "\" }\r\n", output); - - // execute file (uses modified search paths), the file adds a reference path - await _host.ExecuteFileAsync("goo.csx"); - - await _host.ExecuteAsync(@"ReferencePaths"); - - output = await ReadOutputToEnd(); - Assert.Equal("SearchPaths { \"" + normalizeSeparatorsAndFrameworkFolders(string.Join("\", \"", new[] { s_fxDir, dllDir })) + "\" }\r\n", output); - - await _host.AddReferenceAsync(Path.GetFileName(dll.Path)); - - await _host.ExecuteAsync(@"typeof(Metadata.ICSProp)"); - - var error = await ReadErrorOutputToEnd(); - output = await ReadOutputToEnd(); - Assert.Equal("", error); - Assert.Equal("[Metadata.ICSProp]\r\n", output); - } - [Fact, WorkItem(6457, "https://github.com/dotnet/roslyn/issues/6457")] public async Task MissingReferencesReuse() { @@ -1191,16 +1006,25 @@ public async Task PreservingDeclarationsOnException() [Fact] public async Task Bitness() { - await _host.ExecuteAsync(@"System.IntPtr.Size"); - await _host.ResetAsync(new InteractiveHostOptions(GetInteractiveHostDirectory(), initializationFile: null, culture: CultureInfo.InvariantCulture, is64Bit: true)); - await _host.ExecuteAsync(@"System.IntPtr.Size"); - await _host.ResetAsync(new InteractiveHostOptions(GetInteractiveHostDirectory(), initializationFile: null, culture: CultureInfo.InvariantCulture, is64Bit: false)); - await _host.ExecuteAsync(@"System.IntPtr.Size"); + await Host.ExecuteAsync(@"System.IntPtr.Size"); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", await ReadErrorOutputToEnd()); + AssertEx.AssertEqualToleratingWhitespaceDifferences("8\r\n", await ReadOutputToEnd()); - var output = await ReadOutputToEnd(); - var error = await ReadErrorOutputToEnd(); - AssertEx.AssertEqualToleratingWhitespaceDifferences("4\r\n8\r\n4\r\n", output); - AssertEx.AssertEqualToleratingWhitespaceDifferences("", error); + await Host.ResetAsync(InteractiveHostOptions.CreateFromDirectory(TestUtils.HostRootPath, initializationFileName: null, CultureInfo.InvariantCulture, InteractiveHostPlatform.Desktop32)); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", await ReadErrorOutputToEnd()); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", await ReadOutputToEnd()); + + await Host.ExecuteAsync(@"System.IntPtr.Size"); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", await ReadErrorOutputToEnd()); + AssertEx.AssertEqualToleratingWhitespaceDifferences("4\r\n", await ReadOutputToEnd()); + + var result = await Host.ResetAsync(InteractiveHostOptions.CreateFromDirectory(TestUtils.HostRootPath, initializationFileName: null, CultureInfo.InvariantCulture, InteractiveHostPlatform.Core)); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", await ReadErrorOutputToEnd()); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", await ReadOutputToEnd()); + + await Host.ExecuteAsync(@"System.IntPtr.Size"); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", await ReadErrorOutputToEnd()); + AssertEx.AssertEqualToleratingWhitespaceDifferences("8\r\n", await ReadOutputToEnd()); } #region Submission result printing - null/void/value. diff --git a/src/Interactive/HostTest/StressTests.cs b/src/Interactive/HostTest/StressTests.cs index 8b69f8c3d64..80a0f558037 100644 --- a/src/Interactive/HostTest/StressTests.cs +++ b/src/Interactive/HostTest/StressTests.cs @@ -7,18 +7,17 @@ extern alias InteractiveHost; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading; +using System.Globalization; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests.Interactive { - using System.Threading.Tasks; using InteractiveHost::Microsoft.CodeAnalysis.Interactive; - public sealed class StressTests : AbstractInteractiveHostTests + public sealed class StressTests { [Fact] public async Task TestKill() @@ -32,6 +31,7 @@ public async Task TestKill() private async Task TestKillAfterAsync(int milliseconds) { using var host = new InteractiveHost(typeof(CSharpReplServiceProvider), ".", millisecondsTimeout: 1, joinOutputWritingThreadsOnDisposal: true); + var options = InteractiveHostOptions.CreateFromDirectory(TestUtils.HostRootPath, initializationFileName: null, CultureInfo.InvariantCulture, InteractiveHostPlatform.Desktop64); host.InteractiveHostProcessCreated += new Action(proc => { @@ -49,7 +49,7 @@ private async Task TestKillAfterAsync(int milliseconds) }); }); - await host.ResetAsync(new InteractiveHostOptions(GetInteractiveHostDirectory())).ConfigureAwait(false); + await host.ResetAsync(options).ConfigureAwait(false); for (int j = 0; j < 10; j++) { diff --git a/src/Interactive/HostTest/SynchronizedTextWriter.cs b/src/Interactive/HostTest/SynchronizedTextWriter.cs index f1dd4fad2bd..7e865ff3146 100644 --- a/src/Interactive/HostTest/SynchronizedTextWriter.cs +++ b/src/Interactive/HostTest/SynchronizedTextWriter.cs @@ -21,7 +21,7 @@ public override void Write(char value) } } - public override void Write(string value) + public override void Write(string? value) { lock (SyncRoot) { diff --git a/src/Interactive/HostTest/TestUtils.cs b/src/Interactive/HostTest/TestUtils.cs new file mode 100644 index 00000000000..fcfe6849eb2 --- /dev/null +++ b/src/Interactive/HostTest/TestUtils.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.IO; + +namespace Microsoft.CodeAnalysis.UnitTests.Interactive +{ + internal static class TestUtils + { + public readonly static string HostRootPath = Path.Combine(Path.GetDirectoryName(typeof(TestUtils).Assembly.Location)!, "Host"); + } +} diff --git a/src/NuGet/VisualStudio/VS.ExternalAPIs.Roslyn.Package.csproj b/src/NuGet/VisualStudio/VS.ExternalAPIs.Roslyn.Package.csproj index a553941dc39..9aecbd89bed 100644 --- a/src/NuGet/VisualStudio/VS.ExternalAPIs.Roslyn.Package.csproj +++ b/src/NuGet/VisualStudio/VS.ExternalAPIs.Roslyn.Package.csproj @@ -85,7 +85,7 @@ <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.ExternalAccess.FSharp\$(Configuration)\net472\Microsoft.CodeAnalysis.ExternalAccess.FSharp.dll" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.ExternalAccess.Razor\$(Configuration)\netstandard2.0\Microsoft.CodeAnalysis.ExternalAccess.Razor.dll" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.Features\$(Configuration)\netstandard2.0\Microsoft.CodeAnalysis.Features.dll" TargetDir="" /> - <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.InteractiveHost\$(Configuration)\net472\Microsoft.CodeAnalysis.InteractiveHost.dll" TargetDir="" /> + <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.InteractiveHost\$(Configuration)\netstandard2.0\Microsoft.CodeAnalysis.InteractiveHost.dll" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.LanguageServer.Protocol\$(Configuration)\net472\Microsoft.CodeAnalysis.LanguageServer.Protocol.dll" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.Remote.Razor.ServiceHub\$(Configuration)\net472\Microsoft.CodeAnalysis.Remote.Razor.ServiceHub.dll" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.Remote.ServiceHub\$(Configuration)\net472\Microsoft.CodeAnalysis.Remote.ServiceHub.dll" TargetDir="" /> diff --git a/src/Scripting/Core/Hosting/CommandLine/CommandLineRunner.cs b/src/Scripting/Core/Hosting/CommandLine/CommandLineRunner.cs index 31aed321acf..ff469077527 100644 --- a/src/Scripting/Core/Hosting/CommandLine/CommandLineRunner.cs +++ b/src/Scripting/Core/Hosting/CommandLine/CommandLineRunner.cs @@ -172,11 +172,9 @@ private static ScriptOptions GetScriptOptions(CommandLineArguments arguments, st internal static MetadataReferenceResolver GetMetadataReferenceResolver(CommandLineArguments arguments, TouchedFileLogger loggerOpt) { - return new RuntimeMetadataReferenceResolver( - pathResolver: new RelativePathResolver(arguments.ReferencePaths, arguments.BaseDirectory), - packageResolver: null, - gacFileResolver: GacFileResolver.IsAvailable ? new GacFileResolver(preferredCulture: CultureInfo.CurrentCulture) : null, - useCoreResolver: !GacFileResolver.IsAvailable, + return RuntimeMetadataReferenceResolver.CreateCurrentPlatformResolver( + arguments.ReferencePaths, + arguments.BaseDirectory, fileReferenceProvider: (path, properties) => { loggerOpt?.AddRead(path); diff --git a/src/Scripting/Core/Hosting/Resolvers/RuntimeMetadataReferenceResolver.cs b/src/Scripting/Core/Hosting/Resolvers/RuntimeMetadataReferenceResolver.cs index fc6ce547448..eaae67f2133 100644 --- a/src/Scripting/Core/Hosting/Resolvers/RuntimeMetadataReferenceResolver.cs +++ b/src/Scripting/Core/Hosting/Resolvers/RuntimeMetadataReferenceResolver.cs @@ -2,16 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable #pragma warning disable 436 // The type 'RelativePathResolver' conflicts with imported type using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using System.Runtime.Loader; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Scripting.Hosting @@ -28,52 +29,75 @@ internal sealed class RuntimeMetadataReferenceResolver : MetadataReferenceResolv private static readonly MetadataReferenceProperties s_resolvedMissingAssemblyReferenceProperties = MetadataReferenceProperties.Assembly.WithAliases(ImmutableArray.Create("")); - internal static string GetDesktopFrameworkDirectory() => GacFileResolver.IsAvailable ? + internal static string? GetDesktopFrameworkDirectory() => GacFileResolver.IsAvailable ? PathUtilities.GetDirectoryName(typeof(object).GetTypeInfo().Assembly.ManifestModule.FullyQualifiedName) : null; // file name to path: - private static ImmutableDictionary _lazyTrustedPlatformAssemblies; - - public static readonly RuntimeMetadataReferenceResolver Default = - new RuntimeMetadataReferenceResolver(ImmutableArray.Empty, baseDirectory: null); + internal ImmutableDictionary TrustedPlatformAssemblies; internal readonly RelativePathResolver PathResolver; - internal readonly NuGetPackageResolver PackageResolver; - internal readonly GacFileResolver GacFileResolver; + internal readonly NuGetPackageResolver? PackageResolver; + internal readonly GacFileResolver? GacFileResolver; private readonly Func _fileReferenceProvider; - private readonly bool _useCoreResolver; // TODO: Look for .winmd, but only if the identity has content WindowsRuntime (https://github.com/dotnet/roslyn/issues/6483) // The extensions are in order in which the CLR loader looks for assembly files. internal static ImmutableArray AssemblyExtensions = ImmutableArray.Create(".dll", ".exe"); - internal RuntimeMetadataReferenceResolver(ImmutableArray searchPaths, string baseDirectory) - : this(pathResolver: new RelativePathResolver(searchPaths, baseDirectory), - packageResolver: null, - gacFileResolver: GacFileResolver.IsAvailable ? new GacFileResolver() : null, - useCoreResolver: !GacFileResolver.IsAvailable, - fileReferenceProvider: null) + private static char[] s_directorySeparators = new[] { PathUtilities.DirectorySeparatorChar, PathUtilities.AltDirectorySeparatorChar }; + + /// + /// Creates a resolver that uses the current platform settings (GAC, platform assembly list). + /// + internal static RuntimeMetadataReferenceResolver CreateCurrentPlatformResolver( + ImmutableArray searchPaths = default, + string? baseDirectory = null, + Func? fileReferenceProvider = null) + { + return new RuntimeMetadataReferenceResolver( + searchPaths, + baseDirectory, + packageResolver: null, + gacFileResolver: GacFileResolver.IsAvailable ? new GacFileResolver(preferredCulture: CultureInfo.CurrentCulture) : null, + GetTrustedPlatformAssemblyPaths(), + fileReferenceProvider); + } + + internal RuntimeMetadataReferenceResolver( + ImmutableArray searchPaths = default, + string? baseDirectory = null, + NuGetPackageResolver? packageResolver = null, + GacFileResolver? gacFileResolver = null, + ImmutableArray platformAssemblyPaths = default, + Func? fileReferenceProvider = null) + : this(new RelativePathResolver(searchPaths.NullToEmpty(), baseDirectory), + packageResolver, + gacFileResolver, + GetTrustedPlatformAssemblies(platformAssemblyPaths.NullToEmpty()), + fileReferenceProvider) { } internal RuntimeMetadataReferenceResolver( RelativePathResolver pathResolver, - NuGetPackageResolver packageResolver, - GacFileResolver gacFileResolver, - bool useCoreResolver, - Func fileReferenceProvider = null) + NuGetPackageResolver? packageResolver, + GacFileResolver? gacFileResolver, + ImmutableDictionary trustedPlatformAssemblies, + Func? fileReferenceProvider = null) { PathResolver = pathResolver; PackageResolver = packageResolver; GacFileResolver = gacFileResolver; - _useCoreResolver = useCoreResolver; + _fileReferenceProvider = fileReferenceProvider ?? new Func((path, properties) => MetadataReference.CreateFromFile(path, properties)); + + TrustedPlatformAssemblies = trustedPlatformAssemblies; } public override bool ResolveMissingAssemblies => true; - public override PortableExecutableReference ResolveMissingAssembly(MetadataReference definition, AssemblyIdentity referenceIdentity) + public override PortableExecutableReference? ResolveMissingAssembly(MetadataReference definition, AssemblyIdentity referenceIdentity) { // look in the GAC: if (GacFileResolver != null && referenceIdentity.IsStrongName) @@ -85,10 +109,10 @@ public override PortableExecutableReference ResolveMissingAssembly(MetadataRefer } } - // look into a directory containing CorLib: - if (_useCoreResolver) + // check platform assemblies: + if (!TrustedPlatformAssemblies.IsEmpty) { - var result = ResolveTrustedPlatformAssemblyCore(referenceIdentity.Name, s_resolvedMissingAssemblyReferenceProperties); + var result = ResolveTrustedPlatformAssembly(referenceIdentity.Name, s_resolvedMissingAssemblyReferenceProperties); if (result != null) { return result; @@ -96,10 +120,10 @@ public override PortableExecutableReference ResolveMissingAssembly(MetadataRefer } // look in the directory of the requesting definition: - string definitionPath = (definition as PortableExecutableReference)?.FilePath; - if (definitionPath != null) + var definitionDirectory = PathUtilities.GetDirectoryName((definition as PortableExecutableReference)?.FilePath); + if (definitionDirectory != null) { - string pathWithoutExtension = PathUtilities.CombinePathsUnchecked(PathUtilities.GetDirectoryName(definitionPath), referenceIdentity.Name); + string pathWithoutExtension = PathUtilities.CombinePathsUnchecked(definitionDirectory, referenceIdentity.Name); foreach (string extension in AssemblyExtensions) { string fullPath = pathWithoutExtension + extension; @@ -118,7 +142,7 @@ private PortableExecutableReference CreateResolvedMissingReference(string fullPa return _fileReferenceProvider(fullPath, s_resolvedMissingAssemblyReferenceProperties); } - public override ImmutableArray ResolveReference(string reference, string baseFilePath, MetadataReferenceProperties properties) + public override ImmutableArray ResolveReference(string reference, string? baseFilePath, MetadataReferenceProperties properties) { if (NuGetPackageResolver.TryParsePackageReference(reference, out string packageName, out string packageVersion)) { @@ -131,6 +155,15 @@ public override ImmutableArray ResolveReference(str } else if (PathUtilities.IsFilePath(reference)) { + if (!TrustedPlatformAssemblies.IsEmpty && reference.IndexOfAny(s_directorySeparators) < 0) + { + var result = ResolveTrustedPlatformAssembly(PathUtilities.GetFileName(reference, includeExtension: false), properties); + if (result != null) + { + return ImmutableArray.Create(result); + } + } + if (PathResolver != null) { string resolvedPath = PathResolver.ResolvePath(reference, baseFilePath); @@ -151,9 +184,9 @@ public override ImmutableArray ResolveReference(str } } - if (_useCoreResolver && AssemblyIdentity.TryParseDisplayName(reference, out var identity, out var identityParts)) + if (!TrustedPlatformAssemblies.IsEmpty && AssemblyIdentity.TryParseDisplayName(reference, out var identity, out var identityParts)) { - var result = ResolveTrustedPlatformAssemblyCore(identity.Name, properties); + var result = ResolveTrustedPlatformAssembly(identity.Name, properties); if (result != null) { return ImmutableArray.Create(result); @@ -164,40 +197,33 @@ public override ImmutableArray ResolveReference(str return ImmutableArray.Empty; } - private PortableExecutableReference ResolveTrustedPlatformAssemblyCore(string name, MetadataReferenceProperties properties) - { - if (_lazyTrustedPlatformAssemblies == null) - { - _lazyTrustedPlatformAssemblies = GetTrustedPlatformAssemblyMap(); - } + private PortableExecutableReference? ResolveTrustedPlatformAssembly(string name, MetadataReferenceProperties properties) + => TrustedPlatformAssemblies.TryGetValue(name, out var path) && File.Exists(path) ? _fileReferenceProvider(path, properties) : null; + + internal static ImmutableArray GetTrustedPlatformAssemblyPaths() + => ((AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string)?.Split(Path.PathSeparator)).ToImmutableArrayOrEmpty(); - if (_lazyTrustedPlatformAssemblies.TryGetValue(name, out string path) && File.Exists(path)) + internal static ImmutableDictionary GetTrustedPlatformAssemblies(ImmutableArray paths) + { + if (paths.IsEmpty) { - return MetadataReference.CreateFromFile(path, properties); + return ImmutableDictionary.Empty; } - return null; - } - - private static ImmutableDictionary GetTrustedPlatformAssemblyMap() - { var set = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); - if (CoreClrShim.AppContext.GetData?.Invoke("TRUSTED_PLATFORM_ASSEMBLIES") is string paths) + foreach (var path in paths) { - foreach (var path in paths.Split(Path.PathSeparator)) + if (PathUtilities.GetExtension(path) == ".dll") { - if (PathUtilities.GetExtension(path) == ".dll") + string fileName = PathUtilities.GetFileName(path, includeExtension: false); + if (fileName.EndsWith(".ni", StringComparison.OrdinalIgnoreCase)) { - string fileName = PathUtilities.GetFileName(path, includeExtension: false); - if (fileName.EndsWith(".ni", StringComparison.OrdinalIgnoreCase)) - { - fileName = fileName.Substring(0, fileName.Length - ".ni".Length); - } - - // last one wins: - set[fileName] = path; + fileName = fileName.Substring(0, fileName.Length - ".ni".Length); } + + // last one wins: + set[fileName] = path; } } @@ -209,25 +235,25 @@ public override int GetHashCode() return Hash.Combine(PathResolver, Hash.Combine(PackageResolver, Hash.Combine(GacFileResolver, - Hash.Combine(_useCoreResolver, 0)))); + RuntimeHelpers.GetHashCode(TrustedPlatformAssemblies)))); } - public bool Equals(RuntimeMetadataReferenceResolver other) + public bool Equals(RuntimeMetadataReferenceResolver? other) { return ReferenceEquals(this, other) || other != null && Equals(PathResolver, other.PathResolver) && Equals(PackageResolver, other.PackageResolver) && Equals(GacFileResolver, other.GacFileResolver) && - _useCoreResolver == other._useCoreResolver; + ReferenceEquals(TrustedPlatformAssemblies, other.TrustedPlatformAssemblies); } - public override bool Equals(object other) => Equals(other as RuntimeMetadataReferenceResolver); + public override bool Equals(object? other) => Equals(other as RuntimeMetadataReferenceResolver); internal RuntimeMetadataReferenceResolver WithRelativePathResolver(RelativePathResolver resolver) { return Equals(resolver, PathResolver) ? this : - new RuntimeMetadataReferenceResolver(resolver, PackageResolver, GacFileResolver, _useCoreResolver, _fileReferenceProvider); + new RuntimeMetadataReferenceResolver(resolver, PackageResolver, GacFileResolver, TrustedPlatformAssemblies, _fileReferenceProvider); } } } diff --git a/src/Scripting/Core/Microsoft.CodeAnalysis.Scripting.csproj b/src/Scripting/Core/Microsoft.CodeAnalysis.Scripting.csproj index 6ac1027c56b..2484760d8b2 100644 --- a/src/Scripting/Core/Microsoft.CodeAnalysis.Scripting.csproj +++ b/src/Scripting/Core/Microsoft.CodeAnalysis.Scripting.csproj @@ -53,6 +53,7 @@ + @@ -65,6 +66,7 @@ + diff --git a/src/Scripting/Core/ScriptMetadataResolver.cs b/src/Scripting/Core/ScriptMetadataResolver.cs index ea5a7923051..e44846ebff8 100644 --- a/src/Scripting/Core/ScriptMetadataResolver.cs +++ b/src/Scripting/Core/ScriptMetadataResolver.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable +#pragma warning disable 436 // The type 'RelativePathResolver' conflicts with imported type + using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -14,16 +17,17 @@ namespace Microsoft.CodeAnalysis.Scripting public sealed class ScriptMetadataResolver : MetadataReferenceResolver, IEquatable { - public static ScriptMetadataResolver Default { get; } = new ScriptMetadataResolver(ImmutableArray.Empty, null); + public static ScriptMetadataResolver Default { get; } = new ScriptMetadataResolver( + RuntimeMetadataReferenceResolver.CreateCurrentPlatformResolver(ImmutableArray.Empty, baseDirectory: null)); private readonly RuntimeMetadataReferenceResolver _resolver; public ImmutableArray SearchPaths => _resolver.PathResolver.SearchPaths; public string BaseDirectory => _resolver.PathResolver.BaseDirectory; - private ScriptMetadataResolver(ImmutableArray searchPaths, string baseDirectoryOpt) + internal ScriptMetadataResolver(RuntimeMetadataReferenceResolver resolver) { - _resolver = new RuntimeMetadataReferenceResolver(searchPaths, baseDirectoryOpt); + _resolver = resolver; } public ScriptMetadataResolver WithSearchPaths(params string[] searchPaths) @@ -39,10 +43,11 @@ public ScriptMetadataResolver WithSearchPaths(ImmutableArray searchPaths return this; } - return new ScriptMetadataResolver(ToImmutableArrayChecked(searchPaths, nameof(searchPaths)), BaseDirectory); + return new ScriptMetadataResolver(_resolver.WithRelativePathResolver( + _resolver.PathResolver.WithSearchPaths(ToImmutableArrayChecked(searchPaths, nameof(searchPaths))))); } - public ScriptMetadataResolver WithBaseDirectory(string baseDirectory) + public ScriptMetadataResolver WithBaseDirectory(string? baseDirectory) { if (BaseDirectory == baseDirectory) { @@ -54,19 +59,20 @@ public ScriptMetadataResolver WithBaseDirectory(string baseDirectory) CompilerPathUtilities.RequireAbsolutePath(baseDirectory, nameof(baseDirectory)); } - return new ScriptMetadataResolver(SearchPaths, baseDirectory); + return new ScriptMetadataResolver(_resolver.WithRelativePathResolver( + _resolver.PathResolver.WithBaseDirectory(baseDirectory))); } public override bool ResolveMissingAssemblies => _resolver.ResolveMissingAssemblies; - public override PortableExecutableReference ResolveMissingAssembly(MetadataReference definition, AssemblyIdentity referenceIdentity) + public override PortableExecutableReference? ResolveMissingAssembly(MetadataReference definition, AssemblyIdentity referenceIdentity) => _resolver.ResolveMissingAssembly(definition, referenceIdentity); - public override ImmutableArray ResolveReference(string reference, string baseFilePath, MetadataReferenceProperties properties) + public override ImmutableArray ResolveReference(string reference, string? baseFilePath, MetadataReferenceProperties properties) => _resolver.ResolveReference(reference, baseFilePath, properties); - public bool Equals(ScriptMetadataResolver other) => _resolver.Equals(other); - public override bool Equals(object other) => Equals(other as ScriptMetadataResolver); + public bool Equals(ScriptMetadataResolver? other) => _resolver.Equals(other); + public override bool Equals(object? other) => Equals(other as ScriptMetadataResolver); public override int GetHashCode() => _resolver.GetHashCode(); } } diff --git a/src/Scripting/Core/ScriptOptions.cs b/src/Scripting/Core/ScriptOptions.cs index 8d6dcc547ed..dd6b25429ec 100644 --- a/src/Scripting/Core/ScriptOptions.cs +++ b/src/Scripting/Core/ScriptOptions.cs @@ -25,7 +25,7 @@ public sealed class ScriptOptions filePath: string.Empty, references: GetDefaultMetadataReferences(), namespaces: ImmutableArray.Empty, - metadataResolver: RuntimeMetadataReferenceResolver.Default, + metadataResolver: ScriptMetadataResolver.Default, sourceResolver: SourceFileResolver.Default, emitDebugInformation: false, fileEncoding: null, diff --git a/src/Scripting/CoreTest/RuntimeMetadataReferenceResolverTests.cs b/src/Scripting/CoreTest/RuntimeMetadataReferenceResolverTests.cs index 55505f107b0..ee4c0d98e91 100644 --- a/src/Scripting/CoreTest/RuntimeMetadataReferenceResolverTests.cs +++ b/src/Scripting/CoreTest/RuntimeMetadataReferenceResolverTests.cs @@ -26,10 +26,10 @@ public void Resolve() // With NuGetPackageResolver. var resolver = new RuntimeMetadataReferenceResolver( - new RelativePathResolver(ImmutableArray.Create(directory.Path), baseDirectory: directory.Path), - new PackageResolver(ImmutableDictionary>.Empty.Add("nuget:N/1.0", ImmutableArray.Create(assembly1.Path, assembly2.Path))), + new RelativePathResolver(ImmutableArray.Create(directory.Path), directory.Path), + packageResolver: new PackageResolver(ImmutableDictionary>.Empty.Add("nuget:N/1.0", ImmutableArray.Create(assembly1.Path, assembly2.Path))), gacFileResolver: null, - useCoreResolver: false); + trustedPlatformAssemblies: ImmutableDictionary.Empty); // Recognized NuGet reference. var actualReferences = resolver.ResolveReference("nuget:N/1.0", baseFilePath: null, properties: MetadataReferenceProperties.Assembly); @@ -46,10 +46,8 @@ public void Resolve() // Without NuGetPackageResolver. resolver = new RuntimeMetadataReferenceResolver( - new RelativePathResolver(ImmutableArray.Create(directory.Path), baseDirectory: directory.Path), - packageResolver: null, - gacFileResolver: null, - useCoreResolver: false); + searchPaths: ImmutableArray.Create(directory.Path), + baseDirectory: directory.Path); // Unrecognized NuGet reference. actualReferences = resolver.ResolveReference("nuget:N/1.0", baseFilePath: null, properties: MetadataReferenceProperties.Assembly); diff --git a/src/VisualStudio/CSharp/Impl/Interactive/CSharpVsInteractiveWindowProvider.cs b/src/VisualStudio/CSharp/Impl/Interactive/CSharpVsInteractiveWindowProvider.cs index 5b46d984e24..8f79713d1fe 100644 --- a/src/VisualStudio/CSharp/Impl/Interactive/CSharpVsInteractiveWindowProvider.cs +++ b/src/VisualStudio/CSharp/Impl/Interactive/CSharpVsInteractiveWindowProvider.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.InteractiveWindow.Commands; using Microsoft.VisualStudio.InteractiveWindow.Shell; using Microsoft.VisualStudio.LanguageServices.Interactive; @@ -24,12 +25,14 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.Interactive internal sealed class CSharpVsInteractiveWindowProvider : VsInteractiveWindowProvider { private readonly IThreadingContext _threadingContext; + private readonly IAsynchronousOperationListener _listener; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CSharpVsInteractiveWindowProvider( IThreadingContext threadingContext, SVsServiceProvider serviceProvider, + IAsynchronousOperationListenerProvider listenerProvider, IVsInteractiveWindowFactory interactiveWindowFactory, IViewClassifierAggregatorService classifierAggregator, IContentTypeRegistryService contentTypeRegistry, @@ -39,6 +42,7 @@ internal sealed class CSharpVsInteractiveWindowProvider : VsInteractiveWindowPro : base(serviceProvider, interactiveWindowFactory, classifierAggregator, contentTypeRegistry, commandsFactory, commands, workspace) { _threadingContext = threadingContext; + _listener = listenerProvider.GetListener(FeatureAttribute.InteractiveEvaluator); } protected override Guid LanguageServiceGuid @@ -65,6 +69,7 @@ protected override string Title { return new CSharpInteractiveEvaluator( _threadingContext, + _listener, workspace.Services.HostServices, classifierAggregator, CommandsFactory, diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs index 8565bd6319e..38af601ea13 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/ResetInteractiveTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +extern alias InteractiveHost; using System.Collections.Generic; using System.Collections.Immutable; @@ -13,6 +14,7 @@ using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; using Roslyn.Test.Utilities; using Xunit; +using InteractiveHost::Microsoft.CodeAnalysis.Interactive; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive.Commands { @@ -91,7 +93,7 @@ public async Task TestResetREPLWithProjectContext() ProjectNamespaces = ImmutableArray.Create("System", "ResetInteractiveTestsDocument", "VisualBasicResetInteractiveTestsDocument"), NamespacesToImport = ImmutableArray.Create("System", "ResetInteractiveTestsDocument"), ProjectDirectory = "pj", - Is64Bit = true, + Platform = InteractiveHostPlatform.Desktop64, }; await resetInteractive.ExecuteAsync(testHost.Window, "Interactive C#"); @@ -102,7 +104,7 @@ public async Task TestResetREPLWithProjectContext() if (buildSucceeds) { - Assert.True(testHost.Evaluator.ResetOptions.Is64Bit); + Assert.Equal(InteractiveHostPlatform.Desktop64, testHost.Evaluator.ResetOptions.Platform); } else { diff --git a/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs b/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs index 9cd5dc8cb1c..50190636c9d 100644 --- a/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs +++ b/src/VisualStudio/CSharp/Test/Interactive/Commands/TestResetInteractive.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +extern alias InteractiveHost; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.VisualStudio.LanguageServices.Interactive; @@ -10,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.InteractiveWindow; using System.Collections.Generic; +using InteractiveHost::Microsoft.CodeAnalysis.Interactive; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive.Commands { @@ -33,7 +35,7 @@ internal class TestResetInteractive : ResetInteractive internal ImmutableArray NamespacesToImport { get; set; } - internal bool? Is64Bit { get; set; } + internal InteractiveHostPlatform? Platform { get; set; } internal string ProjectDirectory { get; set; } @@ -66,14 +68,14 @@ protected override Task BuildProjectAsync() out ImmutableArray sourceSearchPaths, out ImmutableArray projectNamespaces, out string projectDirectory, - out bool? is64Bit) + out InteractiveHostPlatform? platform) { references = References; referenceSearchPaths = ReferenceSearchPaths; sourceSearchPaths = SourceSearchPaths; projectNamespaces = ProjectNamespaces; projectDirectory = ProjectDirectory; - is64Bit = Is64Bit; + platform = Platform; return true; } diff --git a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj index b043f7ad0d6..87bbf959fd9 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -19,7 +19,9 @@ - + + InteractiveHost + @@ -46,7 +48,7 @@ - + diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs index 2f88c7aa62c..5b3667d4b8c 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs @@ -8,12 +8,14 @@ using System.ComponentModel.Composition; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.Scripting; +using Microsoft.CodeAnalysis.Scripting.Hosting; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; @@ -281,45 +283,24 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) var fileExtension = PathUtilities.GetExtension(filePath); var languageServices = Services.GetLanguageServices(languageInformation.LanguageName); - var compilationOptionsOpt = languageServices.GetService()?.GetDefaultCompilationOptions(); + var compilationOptions = languageServices.GetService()?.GetDefaultCompilationOptions(); // Use latest language version which is more permissive, as we cannot find out language version of the project which the file belongs to // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/575761 - var parseOptionsOpt = languageServices.GetService()?.GetDefaultParseOptionsWithLatestLanguageVersion(); + var parseOptions = languageServices.GetService()?.GetDefaultParseOptionsWithLatestLanguageVersion(); - if (parseOptionsOpt != null && - compilationOptionsOpt != null && + if (parseOptions != null && + compilationOptions != null && fileExtension == languageInformation.ScriptExtension) { - parseOptionsOpt = parseOptionsOpt.WithKind(SourceCodeKind.Script); - - var metadataService = Services.GetService(); - var scriptEnvironmentService = Services.GetService(); - - // Misc files workspace always provides the service: - Contract.ThrowIfNull(scriptEnvironmentService); - - var baseDirectory = PathUtilities.GetDirectoryName(filePath); - - // TODO (https://github.com/dotnet/roslyn/issues/5325, https://github.com/dotnet/roslyn/issues/13886): - // - Need to have a way to specify these somewhere in VS options. - // - Use RuntimeMetadataReferenceResolver like in InteractiveEvaluator.CreateMetadataReferenceResolver - // - Add default namespace imports, default metadata references to match csi.rsp - // - Add default script globals available in 'csi goo.csx' environment: CommandLineScriptGlobals - - var referenceResolver = new WorkspaceMetadataFileReferenceResolver( - metadataService, - new RelativePathResolver(scriptEnvironmentService.MetadataReferenceSearchPaths, baseDirectory)); - - compilationOptionsOpt = compilationOptionsOpt. - WithMetadataReferenceResolver(referenceResolver). - WithSourceReferenceResolver(new SourceFileResolver(scriptEnvironmentService.SourceReferenceSearchPaths, baseDirectory)); + parseOptions = parseOptions.WithKind(SourceCodeKind.Script); + compilationOptions = GetCompilationOptionsWithScriptReferenceResolvers(compilationOptions, filePath); } var projectId = ProjectId.CreateNewId(debugName: "Miscellaneous Files Project for " + filePath); var documentId = DocumentId.CreateNewId(projectId, debugName: filePath); - var sourceCodeKind = GetSourceCodeKind(parseOptionsOpt, fileExtension, languageInformation); + var sourceCodeKind = GetSourceCodeKind(parseOptions, fileExtension, languageInformation); var documentInfo = DocumentInfo.Create( documentId, filePath, @@ -337,8 +318,8 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) name: ServicesVSResources.Miscellaneous_Files, assemblyName, languageInformation.LanguageName, - compilationOptions: compilationOptionsOpt, - parseOptions: parseOptionsOpt, + compilationOptions: compilationOptions, + parseOptions: parseOptions, documents: SpecializedCollections.SingletonEnumerable(documentInfo), metadataReferences: _metadataReferences); @@ -347,6 +328,33 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) return projectInfo.WithHasAllInformation(hasAllInformation: sourceCodeKind == SourceCodeKind.Script); } + // Do not inline this to avoid loading Microsoft.CodeAnalysis.Scripting unless a script file is opened in the workspace. + [MethodImpl(MethodImplOptions.NoInlining)] + private CompilationOptions GetCompilationOptionsWithScriptReferenceResolvers(CompilationOptions compilationOptions, string filePath) + { + var metadataService = Services.GetService(); + var scriptEnvironmentService = Services.GetService(); + + // Misc files workspace always provides the service: + Contract.ThrowIfNull(scriptEnvironmentService); + + var baseDirectory = PathUtilities.GetDirectoryName(filePath); + + // TODO (https://github.com/dotnet/roslyn/issues/5325, https://github.com/dotnet/roslyn/issues/13886): + // - Need to have a way to specify these somewhere in VS options. + // - Add default namespace imports, default metadata references to match csi.rsp + // - Add default script globals available in 'csi goo.csx' environment: CommandLineScriptGlobals + + var referenceResolver = RuntimeMetadataReferenceResolver.CreateCurrentPlatformResolver( + searchPaths: scriptEnvironmentService.MetadataReferenceSearchPaths, + baseDirectory: baseDirectory, + fileReferenceProvider: (path, properties) => metadataService.GetReference(path, properties)); + + return compilationOptions + .WithMetadataReferenceResolver(referenceResolver) + .WithSourceReferenceResolver(new SourceFileResolver(scriptEnvironmentService.SourceReferenceSearchPaths, baseDirectory)); + } + private SourceCodeKind GetSourceCodeKind( ParseOptions parseOptionsOpt, string fileExtension, diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs index 8f6df345d0e..21663fec130 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs @@ -9,6 +9,7 @@ using System.IO; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Scripting.Hosting; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem @@ -171,21 +172,9 @@ private void UpdateProjectOptions_NoLock() } } - // TODO: #r support, should it include bin path? - var referenceSearchPaths = ImmutableArray.Empty; - - // TODO: #load support - var sourceSearchPaths = ImmutableArray.Empty; - - var referenceResolver = new WorkspaceMetadataFileReferenceResolver( - _workspaceServices.GetRequiredService(), - new RelativePathResolver(referenceSearchPaths, _commandLineArgumentsForCommandLine.BaseDirectory)); - var compilationOptions = _commandLineArgumentsForCommandLine.CompilationOptions .WithConcurrentBuild(concurrent: false) - .WithMetadataReferenceResolver(referenceResolver) .WithXmlReferenceResolver(new XmlFileResolver(_commandLineArgumentsForCommandLine.BaseDirectory)) - .WithSourceReferenceResolver(new SourceFileResolver(sourceSearchPaths, _commandLineArgumentsForCommandLine.BaseDirectory)) .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) .WithStrongNameProvider(new DesktopStrongNameProvider(_commandLineArgumentsForCommandLine.KeyFileSearchPaths.WhereNotNull().ToImmutableArray())); diff --git a/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowProvider.cs b/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowProvider.cs index a6e41cb8d2a..1c2f2f6c19c 100644 --- a/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowProvider.cs +++ b/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +extern alias InteractiveHost; using System; using System.Collections.Generic; @@ -18,6 +19,8 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.CodeAnalysis.Internal.Log; +using Roslyn.Utilities; +using InteractiveHost::Microsoft.CodeAnalysis.Interactive; namespace Microsoft.VisualStudio.LanguageServices.Interactive { @@ -93,7 +96,13 @@ public void Create(int instanceId) if (_vsInteractiveWindow is ToolWindowPane interactiveWindowPane) { - evaluator.OnBeforeReset += is64bit => interactiveWindowPane.Caption = Title + (is64bit ? " (64-bit)" : " (32-bit)"); + evaluator.OnBeforeReset += platform => interactiveWindowPane.Caption = Title + platform switch + { + InteractiveHostPlatform.Desktop64 => " (.NET Framework " + ServicesVSResources.Bitness64 + ")", + InteractiveHostPlatform.Desktop32 => " (.NET Framework " + ServicesVSResources.Bitness32 + ")", + InteractiveHostPlatform.Core => " (.NET Core)", + _ => throw ExceptionUtilities.Unreachable + }; } var window = _vsInteractiveWindow.InteractiveWindow; diff --git a/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs b/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs index 82ce374a54c..644275d74dc 100644 --- a/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs +++ b/src/VisualStudio/Core/Def/Interactive/VsResetInteractive.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +extern alias InteractiveHost; using System; using System.Collections.Generic; @@ -19,6 +20,7 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text.Editor; using Roslyn.Utilities; +using InteractiveHost::Microsoft.CodeAnalysis.Interactive; namespace Microsoft.VisualStudio.LanguageServices.Interactive { @@ -56,7 +58,7 @@ internal sealed class VsResetInteractive : ResetInteractive out ImmutableArray sourceSearchPaths, out ImmutableArray projectNamespaces, out string projectDirectory, - out bool? is64bit) + out InteractiveHostPlatform? platform) { var hierarchyPointer = default(IntPtr); var selectionContainerPointer = default(IntPtr); @@ -65,7 +67,7 @@ internal sealed class VsResetInteractive : ResetInteractive sourceSearchPaths = ImmutableArray.Empty; projectNamespaces = ImmutableArray.Empty; projectDirectory = null; - is64bit = null; + platform = null; try { @@ -74,7 +76,7 @@ internal sealed class VsResetInteractive : ResetInteractive if (hierarchyPointer != IntPtr.Zero) { - GetProjectProperties(hierarchyPointer, out references, out referenceSearchPaths, out sourceSearchPaths, out projectNamespaces, out projectDirectory, out is64bit); + GetProjectProperties(hierarchyPointer, out references, out referenceSearchPaths, out sourceSearchPaths, out projectNamespaces, out projectDirectory, out platform); return true; } } @@ -94,7 +96,7 @@ internal sealed class VsResetInteractive : ResetInteractive out ImmutableArray sourceSearchPaths, out ImmutableArray projectNamespaces, out string projectDirectory, - out bool? is64bit) + out InteractiveHostPlatform? platform) { var hierarchy = (IVsHierarchy)Marshal.GetObjectForIUnknown(hierarchyPointer); Marshal.ThrowExceptionForHR( @@ -112,6 +114,7 @@ internal sealed class VsResetInteractive : ResetInteractive var projectDir = (string)dteProject.Properties.Item("FullPath").Value; var outputFileName = (string)dteProject.Properties.Item("OutputFileName").Value; var defaultNamespace = (string)dteProject.Properties.Item("DefaultNamespace").Value; + var targetFrameworkMoniker = (string)dteProject.Properties.Item("TargetFrameworkMoniker").Value; var relativeOutputPath = (string)dteProject.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value; Debug.Assert(!string.IsNullOrEmpty(projectDir)); @@ -150,7 +153,7 @@ internal sealed class VsResetInteractive : ResetInteractive sourceSearchPaths = sourceSearchPathsBuilder.ToImmutableArray(); projectNamespaces = namespacesToImportBuilder.ToImmutableArray(); - is64bit = (projectOpt != null) ? Is64Bit(projectOpt.CompilationOptions.Platform) : null; + platform = (projectOpt != null) ? GetInteractiveHostPlatform(targetFrameworkMoniker, projectOpt.CompilationOptions.Platform) : null; } internal Project GetProjectFromHierarchy(IVsHierarchy hierarchy) @@ -185,19 +188,25 @@ private static bool ProjectIdMatchesHierarchy(VisualStudioWorkspace workspace, P return false; } - private static bool? Is64Bit(Platform platform) + private static InteractiveHostPlatform? GetInteractiveHostPlatform(string targetFrameworkMoniker, Platform platform) { + if (targetFrameworkMoniker.StartsWith(".NETCoreApp", StringComparison.OrdinalIgnoreCase) || + targetFrameworkMoniker.StartsWith(".NETStandard", StringComparison.OrdinalIgnoreCase)) + { + return InteractiveHostPlatform.Core; + } + switch (platform) { case Platform.Arm: case Platform.AnyCpu32BitPreferred: case Platform.X86: - return false; + return InteractiveHostPlatform.Desktop32; case Platform.Itanium: case Platform.X64: case Platform.Arm64: - return true; + return InteractiveHostPlatform.Desktop64; default: return null; diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index c92b007ced9..a06986186d0 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1529,4 +1529,10 @@ I agree to all of the foregoing: Warning: type does not bind + + 32-bit + + + 64-bit + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index 2c58d583e4a..e64e349c870 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -92,6 +92,16 @@ Obor analýzy na pozadí: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) Sestavení + živá analýza (balíček NuGet) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index 5b6d2f4032f..1d1b67606ef 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -92,6 +92,16 @@ Bereich für Hintergrundanalyse: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) Build + Liveanalyse (NuGet-Paket) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index b79d77dadb4..fb840e6963d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -92,6 +92,16 @@ Ámbito de análisis en segundo plano: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) Compilación y análisis en directo (paquete NuGet) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index e7b7b5d8f12..8bbac0964ea 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -92,6 +92,16 @@ Étendue de l'analyse en arrière-plan : + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) Build + analyse en temps réel (package NuGet) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index c87ba104a14..8501c764ee6 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -92,6 +92,16 @@ Ambito di analisi in background: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) Compilazione + analisi in tempo reale (pacchetto NuGet) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index 9542829c16b..5e62622705d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -92,6 +92,16 @@ バックグラウンドの分析スコープ: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) ビルド + ライブ分析 (NuGet パッケージ) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 6f3e8c5428a..b67ba90853a 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -92,6 +92,16 @@ 백그라운드 분석 범위: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) 빌드 + 실시간 분석(NuGet 패키지) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 09cc9c5799d..fdbd99915e3 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -92,6 +92,16 @@ Zakres analizy w tle: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) Kompilacja i analiza na żywo (pakiet NuGet) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index b8bf7db656a..0ff7f72cdc0 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -92,6 +92,16 @@ Escopo da análise em segundo plano: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) Build + análise em tempo real (pacote NuGet) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index 360cc7db989..d584b7b8505 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -92,6 +92,16 @@ Область фонового анализа: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) Сборка и динамический анализ (пакет NuGet) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index 0236db13195..6989276c709 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -92,6 +92,16 @@ Arka plan çözümleme kapsamı: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) Derleme ve canlı analiz (NuGet paketi) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 391f5bf8f44..1cb0c5d103c 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -92,6 +92,16 @@ 后台分析范围: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) 生成 + 实时分析(NuGet 包) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index 72e64ed86d4..d0335dfbef0 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -92,6 +92,16 @@ 背景分析範圍: + + 32-bit + 32-bit + + + + 64-bit + 64-bit + + Build + live analysis (NuGet package) 組建 + 即時分析 (NuGet 套件) diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 04a3170fafa..5bc21222875 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -43,7 +43,7 @@ - + diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InteractiveWindow_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InteractiveWindow_InProc.cs index c289d58b97f..dc75266a324 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InteractiveWindow_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/InteractiveWindow_InProc.cs @@ -10,6 +10,7 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Threading; +using Roslyn.Utilities; namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess { @@ -45,6 +46,8 @@ public void Initialize() CloseWindow(); _interactiveWindow = AcquireInteractiveWindow(); + + Contract.ThrowIfNull(_interactiveWindow); } protected abstract IInteractiveWindow AcquireInteractiveWindow(); diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index 2b17f142228..8150f3dad13 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -265,12 +265,43 @@ true BindingRedirect - - InteractiveComponents - + + InteractiveHost.Core64 + + TargetFramework=netcoreapp3.1 + true + false + + false + InteractiveHost\Core + PublishProjectOutputGroup + + + + false + + + InteractiveHost.Desktop64 + + TargetFramework=net472 + true + false + + false + InteractiveHost\Desktop + PublishProjectOutputGroup + + + + InteractiveHost.Desktop32 + + TargetFramework=net472 + true + false + false - DesktopHost - VsixSourceItemsOutputGroup + InteractiveHost\Desktop + PublishProjectOutputGroup diff --git a/src/VisualStudio/TestUtilities2/Microsoft.VisualStudio.LanguageServices.Test.Utilities2.vbproj b/src/VisualStudio/TestUtilities2/Microsoft.VisualStudio.LanguageServices.Test.Utilities2.vbproj index 7da96b36557..c0a1e7f7d58 100644 --- a/src/VisualStudio/TestUtilities2/Microsoft.VisualStudio.LanguageServices.Test.Utilities2.vbproj +++ b/src/VisualStudio/TestUtilities2/Microsoft.VisualStudio.LanguageServices.Test.Utilities2.vbproj @@ -14,7 +14,7 @@ - + diff --git a/src/VisualStudio/VisualStudioInteractiveComponents/Roslyn.VisualStudio.InteractiveComponents.csproj b/src/VisualStudio/VisualStudioInteractiveComponents/Roslyn.VisualStudio.InteractiveComponents.csproj deleted file mode 100644 index ecb469380b9..00000000000 --- a/src/VisualStudio/VisualStudioInteractiveComponents/Roslyn.VisualStudio.InteractiveComponents.csproj +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - Library - Roslyn.VisualStudio.InteractiveComponents - net472 - - - false - false - false - false - false - - false - - - - BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems - DebugSymbolsProjectOutputGroup; - - - BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems - DebugSymbolsProjectOutputGroup; - - - BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems - DebugSymbolsProjectOutputGroup; - - - BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems - DebugSymbolsProjectOutputGroup; - - - BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems - DebugSymbolsProjectOutputGroup; - - - BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems - DebugSymbolsProjectOutputGroup; - - - BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems - DebugSymbolsProjectOutputGroup; - - - BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems - DebugSymbolsProjectOutputGroup; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - false - - - - - - - - - - - - - - diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute_Names.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute_Names.cs index bec5fe781a2..25bbd70c6f3 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute_Names.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute_Names.cs @@ -23,6 +23,7 @@ internal partial class FeatureAttribute public const string GoToImplementation = nameof(GoToImplementation); public const string GraphProvider = nameof(GraphProvider); public const string InfoBar = nameof(InfoBar); + public const string InteractiveEvaluator = nameof(InteractiveEvaluator); public const string KeywordHighlighting = nameof(KeywordHighlighting); public const string LightBulb = nameof(LightBulb); public const string LineSeparators = nameof(LineSeparators); -- GitLab