-
Notifications
You must be signed in to change notification settings - Fork 830
Support .NET Core F# Interactive scripting in VS #10163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@dsyme -- please ... don't. netfx is going to be deprecated eventually and only legacy tooling will target it. |
Well, we're not exactly going to wait until then to support script editing in Visual Studio, are we? The current status quo is just weird. The only other possibility is a global setting, which we can have but is broken as the only mechanism. |
|
@dsyme - a switch is fine and manageable. A directive creates a difficult comparability story. |
Nah, it's not fine. I edit both .NET Core and .NET Framework scripts, and we do that even in-repo. It's not a solution.
Why? To be honest it's the opposite - it's being explicit about something scripts should always have been explicit about since .NET Core scripting came into existence. If the problem is the netcore --> net5 naming then that's just naming and/or compat can be thrashed out. But fundamentally we need this metadata until there is no more default assumption that scripts are .NET Framework. Anyway this is just a spike, we can discuss this in a RFC. Your concerns are legitimate but we need a solution. |
|
Here is a shot of .NET Core scripting editing working in Visual Studio:
|
|
We had to do similar in FSAC, where I implemented probing logic to enumerate the versions of the sdk and runtime installed, using the pack references whenever possible. I also implemented a varying dotnet root via environment variables because of course the sdks can be located anywhere. It makes me feel good that you're hitting some of these same issues, validates that I wasn't going crazy. |
4b0ea9e to
e8533e1
Compare
|
Based on a discussion with @KevinRansom Given the code in this PR there are a number of simple variations in overall design we can now implement for the F# scripting model and its defaults in Visual Studio Design 1 (@KevinRansom ):
Design 2 (current spike):
I think design 1 is better.
Anyway option 1 is a relatively small tweak from the PR now the mechanisms are in place |
KevinRansom
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good so far. I especially like the FxResolver for DotnetFrameworkReferences refactor.
| // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. | ||
|
|
||
| // Functions to retrieve framework dependencies | ||
| module internal FSharp.Compiler.DotNetFrameworkDependencies |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably we can lose the DotNetFrameworkDependencies
| None, None | ||
| else | ||
| // Running on .NET Framework 32 bit windows (e.g. devenv.exe), need to find a .NET SDK | ||
| let sdks = @"C:\Program Files\dotnet\sdk" // TODO - correct this technique assuming this is devenv.exe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kind of tough, the ide may run on the desktop ... for sure it is at the moment. I will try and find an API, I think there may be one somewhere ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kind of tough, the ide may run on the desktop ... for sure it is at the moment. I will try and find an API, I think there may be one somewhere ...
All the searches say "run dotnet --info or dotnet--list-sdks". But how to find dotnet.exe? There must be a way for VS to probe it's corresponding dotnet install, and we can pass that through
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is dotnet.exe always on PATH? This is how the official MSBuildLocator does it: https://github.com/microsoft/MSBuildLocator/blob/master/src/MSBuildLocator/DotNetSdkLocationHelper.cs
|
@dsyme can this be handled in tooling first (with an option to pick FSI version) without making the breaking change if there are no directives, in which case it shouldn't assume either runtime but match with the tooling selected FSI? Adding the directive is fine, but forcing a new default runtime breaks editing majority of existing scripts in VS tooling. Other editors seems to enable picking which version of FSI it uses, VS is the exception there. |
As long as we cannot run Dotnet FSI from VS, I think the default should be NetFx as default also matches the current default. If we suddenly change the default in absence of a Regardless the choice of default, there should always be the possibility to explicitly state such default. That's a self-documenting feature. If we do choose
As far as I know it is non-trivial to change the FSI inside VS to run as .NET Core. I've come to understand that it requires out-of-process execution because VS itself is .NET Framework (though I hope I'm wrong here ;) ). |
We should simply next extend this feature work to make it possible (and the default) to run .NET Core FSI from Visual Studio. First thing however is to get the build green, there are some issues with the default settings |
|
@KevinRansom and I talked this through and come to the following spec
|
|
@KevinRansom I've updated this PR
I'll test the editing and do the rest tomorrow/Friday |
|
Okay, sounds good.
|
KevinRansom
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, there is a lot here.
There are some queries, perhaps a rename my preference is that the spawning of a process to get the tfm was elsewhere but I can see why you put it here.
Take care of the feedback you think impactful, otherwise I am good with this.
src/fsharp/CompilerImports.fs
Outdated
| let primaryReference = tcConfig.PrimaryAssemblyDllReference() | ||
|
|
||
| let assumeDotNetFramework = primaryReference.SimpleAssemblyNameIs("mscorlib") | ||
| let useDotNetFramework = primaryReference.SimpleAssemblyNameIs("mscorlib") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are renaming, might as well go with DesktopFramework, DotnetFramework is a bit ambiguous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll just undo this renaming to minmise the diff, we can rename later
| pdbDirPath = None | ||
| tryGetMetadataSnapshot = (fun _ -> None) (* tryGetMetadataSnapshot *) } | ||
|
|
||
| let reader = OpenILModuleReader path opts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice.
| static member ClearStaticCaches() = | ||
| desiredDotNetSdkVersionForDirectoryCache.Clear() | ||
|
|
||
| member _.GetFrameworkRefsPackDirectory() = tryGetSdkRefsPackDirectory() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this to be used? I would rather not spread this throughout the compiler ... it's bad enough that this file has to know about it.
|
|
||
| member _.GetSystemAssemblies() = systemAssemblies | ||
|
|
||
| member _.IsInReferenceAssemblyPackDirectory filename = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used elsewhere in the compiler, if so, lets not. We already have SystemTypes that covers this.
| member _.TryGetSdkDir() = tryGetSdkDir() | ||
|
|
||
| /// Gets the selected target framework moniker, e.g netcore3.0, net472, and the running rid of the current machine | ||
| member _.GetTfmAndRid() = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, how is this intended to be used?
src/fsharp/fsi/fsi.fs
Outdated
|
|
||
| try | ||
| let result = fsiOptions.DependencyProvider.Resolve(dependencyManager, ".fsx", packageManagerTextLines, reportError m, executionTfm, executionRid, tcConfigB.implicitIncludeDir, "stdin.fsx", "stdin.fsx") | ||
| let tfm, rid = tcConfigB.fxResolver.GetTfmAndRid() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not have fxResolver on fsiOptions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done thanks
src/fsharp/service/service.fs
Outdated
| let DefaultReferencesForOrphanSources useDotNetFramework = | ||
| let currentDirectory = Directory.GetCurrentDirectory() | ||
| let fxResolver = FxResolver(Some useDotNetFramework, currentDirectory, range0) | ||
| let references, _ = fxResolver.GetDefaultReferences (useFsiAuxLib=false, useDotNetFramework=useDotNetFramework, useSdkRefs=false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe useSdkRefs is default true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup I'll change this. Though this entry point DefaultReferencesForOrphanSources isn't actually used anymore at least by VS, and it's not under test. We should probably mark it as obsolete. It seems that FSharp.Editor relies on using the "empty" command-line arguments without --noframework etc. for out of project non-script sources, which is I believe equivalent (i.e. equivalent to this entry with useDotNetFramework=true)
| // Use the location of this dll | ||
| location | ||
|
|
||
| let getDefaultFSharpCoreLocation() = Path.Combine(getFSharpCompilerLocation(), getFSharpCoreLibraryName + ".dll") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noice
|
|
||
| #blaaaaaa // blaaaaaa is not a known command;; | ||
| ^ | ||
| ^^^^^^^^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this change, not that it's not a good change it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes bug fix in pars.fsy I noticed while doing the #netfx thing
| // Otherwise give up | ||
| raise (SessionError (VFSIstrings.SR.couldNotFindFsiExe fsiRegistryPath)) | ||
| if SessionsProperties.fsiUseNetCore then | ||
| let exe = @"c:\Program Files\dotnet\dotnet.exe" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Combine the ProgramFiles Environment variable with dotnet\dotnet.exe
Set ProgramFiles
ProgramFiles=C:\Program Files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Need to use this https://weblog.west-wind.com/posts/2019/Feb/05/Finding-the-ProgramFiles64-Folder-in-a-32-Bit-App as we're in a 32-bit app)
| [<InlineData("""#i " """, // Single quote | ||
| "input.fsx (1,4)-(1,5) parse error End of file in string begun at or before here", | ||
| "input.fsx (1,1)-(1,6) interactive warning Invalid directive '#i '")>] | ||
| "input.fsx (1,1)-(1,3) interactive warning Invalid directive '#i '")>] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bug fix (see pars.fsy) for the range reported for bad hash errors. Could be separated
Could we have an option to set the directory |
|
I'd second @auduchinok's remark. Having a way to set this sort of internally-probed state is a great escape hatch. Right now in FSAC we completely overwrite the refs derived from FCS for scripts based on custom logic due to some assumptions that didn't hold in our specific scenario, and one of those assumptions used to be that the runtime of the process hosting FCS (.netcoreapp2.0 at the time) was the same as the TFM that scripts would run as (which was often 3.0/3.1). This mismatch led us to have to replace the ref list entirely. Having a way to provide that as a more structured input would mean we could delete lots of probing code and get back on a supported/shared mechanism. |
|
@auduchinok @baronfel Yes in principle, we should discuss in a separate PR I think.
I think the appropriate thing is to pass an optional SDK directory to GetProjectOptionsFromScript So I have added an Note that the Note also global.json next to a script are only respected if you start
Yes I was just sorting through a similar problem. My understanding is that @baronfel I think you're also saying you want to specify a SDK directory in some situations, e.g. where there is no global.json for the script and you know your execution tooling (eg F# Interactive) is designed to execute scripts for one particular SDK version. |
…ws SDK resolution to succeed
|
Random unrelated test failure, could indicate a race condition |
Support .NET Core F# Interactive scripting in VS, also explicit framework specifications in scripts


Now that we have decent diagnostics while editing scripts, it quickly becomes apparent that VS tooling assumes that scripts are .NET Framework, e.g. see #10160
Motivated this I put together a prototype implementation of explicit target framework specifications in scripts. The mini-spec is below.
I also added an option in VS to use F# Interactive for .NET Core. .
This works and is practical for use and I'll be using it myself.
Limitations:
#targetfx "netcore"to the scriptdotnet fsiand doesn't use either the FSI installed by the VSIX (there is no .NET COre FSI installed by the VSIX)dotnetis found using a hardwired path toc:\Program Files\dotnet\dotnet.exeMini-spec: Explicit target framework specifications in scripts
Scripts may include the declarations
It is not possible to declare scripts as
#netstandard2.0even if they are neutral, though that would be relatively easy to add.If present, an explicit framework declaration must be the first non-whitespace, non
//comment token in the script.Editing
When editing (in Visual Studio):
a script with an explicit target framework declaration is analysed with respect to the latest tooling-supported version of the corresponding target framework. This will typically be the same .NET SDK used to execute scripts with
dotnet fsia script without an explicit target framework declaration is by default analysed as .NET Framework. If the option "Use .NET Core Scripting" is selected in the Visual Studio options then .NET Core is assumed.
General rules for all script analysis:
If a script being edited does
#loadon another script that has an incompatible target framework declaration then a warning is reported at the point of the#loadIf a script being edited does
#loadon another script that has no target framework declaration then no warning is reported. The overall contents are analysed with respect to the explicit or default target framework of the script being edited.Compiling
As background, F#
fsc.exesupports compiling scripts. This is the "script compilation" feature that has always been present in the F# compiler - it allows you to include scripts on the command line or in your project and during compilation you get the load closure and references from them. This applies even if the script is in a project (with 'Compile' action), for example. Any DLL references implied by package references are also retrieved from the script. When script compilation is invoked, the outputs are not necessarily a functioning application - the referenced DLLs are not copied to the output folder, for example (except perhaps FSharp.Core.dll).Wxisting compilation arguments determine the target framework (
--targetprofile:(mscorlib|netcore|netstandard)). The default forfsc.exeis always--targetprofile:mscorlib. This is unchanged.If scripts being compiled have
#netfxand#netcoredeclarations they must be consistent with the--targetprofileflag of the compilation else a warning is emitted. If the compilation uses--targetprofile:netstandard, then scripts on the command line must not have either#netfxor#netcore.Script Execution (fsi and .NET Interactive)
The FSI instance is either netfx or netcore. If a script with an incompatible target framework declaration is loaded for execution a warning is reported.
FSharp.Compiler.Service
FSharpChecker.GetProjectOptionsFromScriptaccepts an optional parameterdefaultToDotNetFrameworkthat says whether scripts should be assumed to be .NET Framework in the absence of any other declarations. In the absence of a declaration the default is assumed to be .NET Framework scripting when FCS is running in .NET Framework, and .NET Core when running in .NET Core.As today
FSharpChecker.Compiledefaults to--targetprofile:mscorlibso--targetprofile:netcoremust be given if a#netcorescript is being compiled. The presence of a#netcoredeclaration in the main script being compiled can be computed by first callingGetProjectOptionsFromScript.Script Execution in Visual Studio
By default scripts are executed using F# Interactive for .NET Framework.
If you select ".NET Core Scripting" from the Visual Studio F# Tool options then F# Interactive for .NET Core will be used on next restart of F# Interactive.
The feature is not labelled as "preview" since it requires explicit opt-in either via
#targetfxor selecting ".NET Core Scripting" form the Visual Studio F# Tool options.