Skip to content
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

support .NET Standard 2.0 in F# Interactive and type providers #3307

Closed
wants to merge 11 commits into from

Conversation

dsyme
Copy link
Contributor

@dsyme dsyme commented Jul 5, 2017

Our plan in the long run is to have type provider design time components (the ones hosted in the F# compiler and IDEs and FsAutoComplete.exe and FSI.exe) be implemented as .NET Standard 2.0 components. These components can then be used and hosted whether the F# compiler is running on .NET Core, .NET Framework or Mono.

As a first step in this direction, I've done an experiment to see what is required for the .NET Framework/Mono version of the compiler to host a type provider implemented as a .NET Standard 2.0 component.

I have tested this with a private version of FSharp.Data where both FSharp.Data.dll and FSharp.Data.DesignTime.dll are .NET Standard 2.0 components. It works, which is good.

The good news is that it isn't too bad to implement. We basically have to

  1. use .NET Framework 4.6.1 instead of 4.6/4.5
  2. add references to NETStandard.Library.NETFramework. from FSharp.Compiler.Private.dll. This adds references to .NET 4.6.1 versions of type-forwarding facade DLLs such as netstandard.dll and also copies them to release\net40\bin\netstandard.dll etc.

The bad news is

  1. as far as I can see these facade assemblies are not shipped on .NET 4.6.1 or .NET 4.7 and must be part of the F# compiler when shipped as a .NET 4.6.1 application. This is a consequence of how the .NET team have tricked .NET 4.6.1 into supporting .NET Standard 2.0: i.e. by requiring .NET 4.6.1 applications to ship versions of netstandard.dll and friends that simply redirect to mscorlib.dll.

  2. The list of added facade assemblies is quite long - there are 96 of them. We already ship a handful of these. But because type providers are loaded by reflection into the compiler, then in principle we need to ship all of these facade DLLs as part of our .NET Framework compiler, because any .NET Standard 2.0 type provider could end up using any of these (the DLLs can't be included with the type provider because they are .NET 4.6.1. forwarding DLLs). I presume any .NET Framework 4.6.1 application which is going to reflection-load arbitrary NET Standard 2.0 components will need all of these facade assemblies. Without release\net40\bin\netstandard.dll in place we get an error that netstandard.dll 2.0.0.0 is missing whenever we try to reflection-load a .NET Standard 2.0 component into the F# compiler running on 4.6.1

To see that these components are not included in the .NET Framework itself:

C:\GitHub\dsyme\FSharp.Data> dir /s c:\Windows\Microsoft.NET\Framework\netstandard.dll
...
File Not Found

I very much hope that there is a plan to eventually ship these "in-application forwarding DLLs" as part of the .NET Framework itself, because it really doesn't feel that .NET 4.6.1 properly supports reflection-loading of arbitrary .NET Standard 2.0 components if that means adding ~96 small forwarding DLLs to your application. It's not the end of the world, it just feels odd that one call to Reflection.Load should necessitate adding so many DLLs. Note that this will also affect all applications that host FSharp.Compiler.Service as well.

@cartermp
Copy link
Contributor

cartermp commented Jul 5, 2017

cc @terrajobst - perhaps you could chime in about this?

I very much hope that there is a plan to eventually ship these "in-application forwarding DLLs" as part of the .NET Framework itself, because it really doesn't feel that .NET 4.6.1 properly supports reflection-loading of arbitrary .NET Standard 2.0 components if that means adding ~96 small forwarding DLLs to your application. It's not the end of the world, it just feels odd that one call to Reflection.Load should necessitate adding so many DLLs. Note that this will also affect all applications that host FSharp.Compiler.Service as well.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 5, 2017

@cartermp @terrajobst It turns out that #3309 is basically the same problem - support loading .NET Standard 2.0 components into F# Interactive on .NET Framework

Some of these facade DLLs (e.g. System.IO.dll) are included with .NET Framework 4.6.1 (our minimal assumption with this PR), so it's a bit confusing if we need to include them or not. Do we only need to include netstandard.dll in our application, or do we need all 96 facade DLLs?

@dsyme
Copy link
Contributor Author

dsyme commented Jul 5, 2017

I augmented the PR with enough to include netstandard.dll in the installers. This also fixes the related bug #3309

@@ -246,6 +247,19 @@
<PackageTags Condition="'$(PackageTags)' == ''" >Visual F# Compiler FSharp coreclr functional programming</PackageTags>
</PropertyGroup>

<Import Project="$(NugetLocalPackagesDir)\Microsoft.Packaging.Tools.1.0.0-preview2-25401-01\build\Microsoft.Packaging.Tools.targets" Condition="Exists('$(NugetLocalPackagesDir)\Microsoft.Packaging.Tools.1.0.0-preview2-25401-01\build\Microsoft.Packaging.Tools.targets')" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be we don't need all this and would be better off with a simple direct reference to the appropriate netstandard.dll

@dsyme
Copy link
Contributor Author

dsyme commented Jul 5, 2017

Investigating CI failure: Ubuntu build (still currently using "xbuild") doesn't like those new Import declarations

	/mnt/j/workspace/Microsoft_visualfsharp/master/release_ubuntu14.04_prtest/src/fsharp/FSharp.Core/FSharp.Core.fsproj: error : /mnt/j/workspace/Microsoft_visualfsharp/master/release_ubuntu14.04_prtest/src/fsharp/FSharp.Core/FSharp.Core.fsproj: Object reference not set to an instance of an object
	/mnt/j/workspace/Microsoft_visualfsharp/master/release_ubuntu14.04_prtest/src/fsharp/FSharp.Build-proto/FSharp.Build-proto.fsproj: error : /mnt/j/workspace/Microsoft_visualfsharp/master/release_ubuntu14.04_prtest/src/fsharp/FSharp.Build-proto/FSharp.Build-proto.fsproj: Object reference not set to an instance of an object
	/mnt/j/workspace/Microsoft_visualfsharp/master/release_ubuntu14.04_prtest/src/fsharp/Fsc-proto/Fsc-proto.fsproj: error : /mnt/j/workspace/Microsoft_visualfsharp/master/release_ubuntu14.04_prtest/src/fsharp/Fsc-proto/Fsc-proto.fsproj: Object reference not set to an instance of an object

We should move to "msbuild" and/or simplify what we're adding here

@dsyme
Copy link
Contributor Author

dsyme commented Jul 6, 2017

Additional changes were needed to

  1. for scripts to include .NET Standard 2.0 within the F# scripting programming model.
  2. for Visual Studio IDE to behave properly when it sees netstandard.dll

@dsyme dsyme changed the title [WIP] support .NET Standard 2.0 type providers loading into .NET Framework compiler support .NET Standard 2.0 in F# Interactive and type providers Jul 6, 2017
@terrajobst
Copy link
Member

@cartermp

perhaps you could chime in about this?

Thanks for looping me in.

@dsyme

I very much hope that there is a plan to eventually ship these "in-application forwarding DLLs" as part of the .NET Framework itself

That is the plan: .NET Framework 4.7.1 will ship the type forwarding facades for .NET Standard 1.x and 2.0 in-box. The reason we couldn't do this .NET Framework 4.6.1 is because it shipped way before .NET Standard 2.0 was even conceived. However, since it basically supports the API surface we thought it's best to enable the .NET Standard 2.0 consumption by requiring the application to ship the necessary facades.

because it really doesn't feel that .NET 4.6.1 properly supports reflection-loading of arbitrary .NET Standard 2.0 components if that means adding ~96 small forwarding DLLs to your application. It's not the end of the world, it just feels odd that one call to Reflection.Load should necessitate adding so many DLLs. Note that this will also affect all applications that host FSharp.Compiler.Service as well.

Strictly speaking loading .NET Standard 2.0 components only requires loading one extra forwarding DLL, namely netstandard.dll (and of course whatever implementation assemblies are necessary). The other 95 facades are only required for .NET Standard 1.x components.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 6, 2017

@dotnet-bot Test Windows_NT Release_ci_part3 Build please

@dsyme
Copy link
Contributor Author

dsyme commented Jul 6, 2017

However, since it basically supports the API surface we thought it's best to enable the .NET Standard 2.0 consumption by requiring the application to ship the necessary facades.

OK, thanks. That makes sense

The other 95 facades are only required for .NET Standard 1.x components.

Thanks, that makes sense too. I think there are scenarios today where F# Interactive (Fsi.exe) will not correctly load all .NET Standard 1.x components. I'll try to repro this.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 6, 2017

A snippet of mine from a conversation with @KevinRansom about whether we need this, i.e. whether F# Interactive should support loading .NET Standard 1.x and 2.0 components in Visual Studio 2017 (i.e. on .NET 4.6.1)

@dsyme says:

Let's focus on whether desktop F# Interactive supports loading .NET Standard 2.0 DLLs

The thing is, technically speaking supporting this (i.e. shipping the .NET Standard facade DLLs) is exactly the same as System.ValueTuple.

These facade DLLs will be built into .NET 4.7.1, just like System.ValueTuple.dll will be built into 4.7.1.

But we support System.ValueTuple on .NET 4.6.1 by shipping the DLL

Likewise, we can support .NET Standard 2.0 components by shipping the facade DLLs.

That's pretty much the whole story. It's quite simple really. It's just that there are quite a lot of the facade DLLs if you want proper .NET Standard 1.x coverage

The other alternative being discussed was basically whether we should punt on support for loading .NET Standard 2.0 into F# Interactive and for type providers until .NET 4.7.1 can be assumed, i.e. VS vNext. That feels wrong to me

  • the approach in the PR works technically and is the recommended approach described by @terrajobst

  • We can get rid of all the facade DLLs once .NET 4.7.1 can be assumed. They will be needed for FSharp.Compiler.Tools and FsAutoComplete where people will be running tools on .NET 4.6.1 for a long time ahead

  • My belief is that both C# people and F# people will start producing a lot of .NET Standard 2.0 libraries. It is much, much easier to move a .NET library to .NET Standard 2.0 because you basically don't have to think about anything: almost all of the .NET Framework is supported. Libraries such as FSharp.Data compiled immediately for .NET Standard 2.0, but were very painful to compile for .NET Standard 1.x of portable profiles. This tells me that .NET Standard 2.0 will be very popular once the tooling actually lands.

  • In this thread the F# community leads (myself, @enricosada and others) were basically discussing delaying moving any significant libraries to .NET Standard until .NET Standard 2.0 support is fully ready.

Copy link
Member

@KevinRansom KevinRansom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well ... it does what it says on the tin.

After this F# will need DotNet 4.6.1, but I suppose it required 4.6 before so that is probably not hideous.

Some asm.Location
else None
with _ -> None
let path = Path.GetDirectoryName(GetDefaultFSharpCoreReference())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really right and the existing code is wrong too ...

This forces us to co-locate System.ValueTuple.dll with FSharp.Core.dll which is not right. Especially since dotnet cli hosting can locate assemblies in a central shared cache rather than app local.

The existing code assumes that ValueTuple was loaded from an assembly named System.ValueTuple.dll which is also not right. Since from dotnet 4.7 it exists in mscorlib, probably System.Private.Corlib.dll on coreclr.

I guess I will have to revisit this code at some point, but we need to do better long term.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, yes, this is wrong when running the compiler or F# Interactive in .NET Core without --noframework etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it won't work on the dotnet cli. Since we don't co-locate system.valuetuple.dll

Copy link
Member

@KevinRansom KevinRansom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we don't co-locate system.valuetuple on dotnet/cli that change needs to be different.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 6, 2017

However, since it basically supports the API surface we thought it's best to enable the .NET Standard 2.0 consumption by requiring the application to ship the necessary facades.

@terrajobst You might want to add some documentation explaining that applications running on .NET 4.6.1/4.7 which do Assembly.Load/LoadFrom on .NET Standard 1.x DLLs or .NET Standard 2.0 DLLs should ship the entire set of facades. It's not at all obvious :)

@dsyme
Copy link
Contributor Author

dsyme commented Jul 6, 2017

@KevinRansom I checked the PR comment

(1) that code you mentioned is protected by if assumeDotNetFramework then. This is only set if the primary assembly is mscorlib So when the compiler runs on .NET Core this would only kick in when full mscorlib arguments have been given.

(2) The old code (before the last FCS integration) just used yield "System.ValueTuple" and relied on MSBuild resolution to the compiler implementation assemblies.

So I think the code here is no worse. However the code we really need to adjust in order to get sane default references for dotnet.exe .../fsi.exe is this code that was added in the FCS integration: https://github.com/Microsoft/visualfsharp/blob/20e93a4bfeec8e0075960c9cfd0ca51fe92b473f/src/fsharp/CompileOps.fs#L1691

That code that was added came from @ncave who wanted a functioning default set of references for the F# scripting model implemented by FCS. We have that under test in the FCS repository but not yet here. Adjust it as needed

This also opens the question of whether the .NET Core version of the compiler (and the compiler service DLL) is fully behaviour-compatible with the .NET Framework version, especially w.r.t. what assumptions are made about the compilation if incomplete arguments (without --noframework) are given. Right now it is impossible to get the .NET Core version of the compiler (or FCS) to do the equivalent of fsc.exe foo.fs - with all the default references to the facade DLLs - indeed it doesn't even have access to the facade DLLs.

I'll open an issue to track known subtle behaviour differences between the .NET Core and NET Framework versions of the compiler.

@terrajobst
Copy link
Member

terrajobst commented Jul 6, 2017

@dsyme

You might want to add some documentation explaining that applications running on .NET 4.6.1/4.7 which do Assembly.Load/LoadFrom on .NET Standard 1.x DLLs or .NET Standard 2.0 DLLs should ship the entire set of facades. It's not at all obvious :)

"This theorem can easily be proven and is thus left as an exercise for the reader" 😄

Agreed. We've recently changed how the applications acquire the necessary facades. We've switched from a NuGet package to global machine state. I'll ensure that reflection scenarios will work. Let me come back to you on this.

@ncave
Copy link
Contributor

ncave commented Jul 6, 2017

@dsyme

Without release\net40\bin\netstandard.dll in place we get an error that netstandard.dll 2.0.0.0 is missing whenever we try to reflection-load a .NET Standard 2.0 component into the F# compiler running on 4.6.1

For some other project, I'm getting around that by compiling against netcoreapp2.0 (even for libraries). It works, but it may (or may not) be a good solution for distributed libraries, so YMMV.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 6, 2017

@ncave

I'm getting around that by compiling against netcoreapp2.0 (even for libraries), it works, but it may or may not be a good solution for distributed libraries.

Is the final consumption of your library only .NET Core App 2.0 right? In that case yes, that's a workaround.
netcoreapp2.0 libraries won't be usable in .NET Framework, which is the scenario we're really looking at here

I'm not entirely sure how this issue plays out for your .NET Core F# scripting model using the .NET Core version of FSharp.Compiler.Service.dll. It could be that having the compiler service add a default reference to the the right netstandard.dll to match all the rest of the default references when doing .NET Core compilation will work.

We should probably add an issue in the FCS repository about this ("make sure .NET Standard 2.0 libraries can be referenced in the F# scripting model implemented by the .NET Core version of FSharp.Compiler.Service.dll"). Could you do that? And maybe add a failing test case?

@ncave
Copy link
Contributor

ncave commented Jul 6, 2017

@dsyme I just tried it, you can still consume such libraries from other targets (netcoreapp1.0 etc.) if they are multi-target built for them:

  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFrameworks>netcoreapp2.0;netstandard1.6</TargetFrameworks>
  </PropertyGroup>

And the reference trimming seems pretty good, only what is actually used. But yes, it's a work-around.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 6, 2017

@dsyme I just tried it, you can still consume such libraries from other targets (netcoreapp1.0 etc.) if they are multi-target built for them:

Yes, that's correct. But why not build for netstandard1.6 alone?

@dsyme
Copy link
Contributor Author

dsyme commented Jul 6, 2017

I have added a basic smoke test for .NET Framework fsi.exe referencing a .NET Standard 2.0 type provider

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

@KevinRansom Just to mention that I removed the change to System.ValueTuple resolution, it was not relevant to the PR

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

@forki

props and targets file are ignored in scripting context. [ by paket #r "paket : Foo" prototype ]

I assume a nuget/MSBuild15 #r "nuget: Foo" version of this would keep a running MSBuild context and incrementally add <PackageReference> entries to it, re-resolve and scrape the extra assembly references. This MSBuild context could also add additional <Import> to the .props/.targets - indeed it could just use nuget API to adjust an invisible running project file.

As I understand it from this doc, MSBuild15/.NET SDK support for Paket is actually mediated via msbuild, so perhaps Paket should actually use a similar technique?

This could be a very satisfying direction as it would get F# Interactive out of the business of resolving anything.

@forki
Copy link
Contributor

forki commented Jul 8, 2017

The problem is: there is no MSBuild context. ;-)
And especially in nuget there is none. It's just a tool to download and extract zip files. All the logic around that in dotnet cli is other MSBuild tasks.

I mean we could probably run MSBuild in a temp folder and give it all the files it needs and reference whatever it came up with after restore. But is that really needed?

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

Side note: It feels like part of the long-term solution here would be to have a #r-typeprovider "..." that specifies the design-time component of the type provider reference independently of any runtime assembly reference (indeed there may be no corresponding runtime assembly - some type providers are like that). This would be emitted by the package resolution mechanism and the set of type provider references determined by targets/props files attached to the nuget package.

#r-typeprovider "packages/FSharp.Data/build/netcore/FSharp.Data.DesignTime.dll``

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

@forki

The problem is: there is no MSBuild context. ;-)

There must be some kind of technique for doing this - it's much the same as what the devenv.exe project system does when you add a package reference using the GUI, isn't it? At some level #r "nuget: Foo" in fsi.exe isn't too different to right-clicking on Foo in devenv.exe, adding it to your project and checking what the computed MSBuild FullPath reference is for the DLL. The old F# project system keeps an incremental MSBuild resolution context for doing this.

At the high level I'm just wondering if we can use .targets/.props in the nuget package to compute the relevant type provider references, and incorporate this into the #r "nuget: Foo" and/or #r "paket: Foo" mechanism for incrementally added references during scripting.

@forki
Copy link
Contributor

forki commented Jul 8, 2017

Vs has it's own implementation of MSBuild. It's not the MSBuild.exe we know and love. Their background build is not 100% compatible with MSBuild. It even uses its own targets. Just saying...

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

Vs has it's own implementation of MSBuild. It's not 5he MSBuild.exe we know and love.

OK, then Xamarin Studio. In both cases my understanding is that they use the MSBuild API to maintain a resolved state through incremental changes to a project specification. That's basically what the MSBild API is for (I've only experience of the MSBuild 14 API for doing this , not MSBuild 15 - though they may be basically the same).

Their background build is not 100% compatible with MSBuild. It even uses its own targets. Just saying...

Yeah, I know. I get the impression that much of this is due to incrementality, e.g. you can get subtle differences where

F(F(InitialState,A1),A2) <> F(InitialState,A1+A2)

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

@forki

At the high level I'm just wondering if we can use .targets/.props in the nuget package to compute the relevant type provider references, and incorporate this into the #r "nuget: Foo" and/or #r "paket: Foo"mechanism for incrementally added references during scripting.

Just to step back: do you see way to get #r "paket: Foo" to respect the props/targets coming from nuget pacakges, if those props/targets are involved in computing assembly references?

BTW we should probably move this to the discussion thread of https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1027-fsi-references.md#unresolved-questions, or at least summarize there after we've done his thread to it's death :)

@forki
Copy link
Contributor

forki commented Jul 8, 2017 via email

@matthid
Copy link
Contributor

matthid commented Jul 8, 2017

To be honest this all reads like running into the wrong direction (or you could say against a wall).

  • props and targets are nice to integrate into the msbuild process and not designed for the problem we are trying to solve here
  • If we solve everything with props/targets why do we even have well defined folders in the nuget spec with references and other stuff. Basically we could get rid of all of that and only use props/targets (not what I recommend)
  • I don't think we want to take the msbuild dependency for the scripting story

I think we need to embed what we need here properly in the package metadata/contents:

  • Either ask the nuget team what they would suggest or add a new general folder for these kind of design time references
  • Hook into the analyzers concept: https://docs.microsoft.com/en-us/nuget/schema/analyzers-conventions
    I think they kind of have the same "lifetime" as what we need for type-providers. Paket could then forward the design time references to the compiler.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

The question is: what do you really need to express? Why can't be expressed in the file structure?

For most type providers we probably just need

build/netstandard2.0/MyTypeProvider.DesignTime.dll

especially once .NET 4.7.1 is widely assumed. A more complex type provider could use

build/netcoreapp2.0/x86/MyTypeProvider.DesignTime.dll
build/netstandard2.0/x64/MyTypeProvider.DesignTime.dll
build/net45/x86/MyTypeProvider.DesignTime.dll
build/net45/x64/MyTypeProvider.DesignTime.dll

etc. where these platforms refer to the compile-time context.. (not sure of the directory names - where possible, the conventions should be identical to those used by Roslyn analyzers, MSBuild tasks etc. - any other design-time tooling.)

We could indeed hack F# tooling (e.g. the F# compiler type provider loader) to probe around looking through packages for type provider DLLs to load. But this is really going against the grain: we would like the implementation of the F# scripting model (i.e. in the F# compiler) to get out of the business of assembly resolution and package interpretation altogether. As @KevinRansom has pointed out, it's a continual problem for F# engineering. It's OK to have plugins that do this, but we shouldn't have these decisions being made right inside the compiler.

For assembly references, #2483 and its corresponding RFC goes a long way to achieving this, but leaves some things unaddressed:

  • The plugin should really resolve the default references for the scripting model

  • The plugin should really compute type provider references, rather than leaving them implicit via the "whatever is next to the runtime DLL" rule

  • The plugin should really respect the props/targets from the referenced nuget packages. For example, consider the quite complex computations in the targets for NETSTandard..Library.NETFramework under "build" (5K of targets, 4 files). These targets carefully choose the right assembly references for .NET 4.6.1, 4.7 etc. Using #r "paket: NETSTandard..Library.NETFramework" should really give exactly the right references as in a compiled setting. (I'm not sure how you deal with that in Paket today? Perhaps the interpretation of the provided declarative information is enough?)

@forki
Copy link
Contributor

forki commented Jul 8, 2017

Adding targets for resolution is really a bad idea. It's same category as the ps1 hack. This should all be done in meta data in path name and nuspec. It's sad that all teams like asp.net at first then BCL and now we are hacking around that just because nuspec is not extended fast enough. Runtime packages belong into the same category. Really bad hack around the meta data model.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

@matthid Thanks for the link to the nuget spec for analyzers - for reference the general spec is

$/analyzers/{framework_name}{version}/{supported_architecture}/{supported_language}}/{analyzer_name}.dll

though I'm not sure that includes framework restrictions.

I don't think we want to take the msbuild dependency for the scripting storyI

No one is suggesting we take an MSBuild dependency in the F# compiler.

However, for the implementation of the resolver plugin that implements #r "nuget: Foo" we have to take at least a nuget dependency to get package resolutions, and it is plausible that interpretation to final references may involve using the MSBuild API (e.g. to respect .targets in exotic packages). When we complete the resolve plugin we will take whatever dependencies are needed.

For the implementation of #r "paket: Foo" it is up to Paket, but there will be enough declarative information.

Hook into the analyzers concept: https://docs.microsoft.com/en-us/nuget/schema/analyzers-conventions .... Paket could then forward the design time references to the compiler.

Yes, we should make sure the type providers use fully declarative paths, of course, and re-use existing norms

... this all reads like running into the wrong direction ...

@matthid let's just keep iterating ok?

@forki
Copy link
Contributor

forki commented Jul 8, 2017 via email

@matthid
Copy link
Contributor

matthid commented Jul 8, 2017

I think we need to differentiate a bit:

  • What are props and targets file used in general? is that even required for scripting? Does it even matter if we don't support that? In scripting we don't really care about general build modifications, only that we can compile and run.
  • Why do type provider currently need props/targets files? Can we do better and only require nuget metadata instead of a general programming model?

Also we need to differentiate about the package support in F#:

  • I think we are fine if #r "nuget: Foo" hosts msbuild (if people here really think that is required to support everything)
  • On the other hand it's important that most use-cases work well with paket and without msbuild. That definitely should include type-providers. I don't think we want to add hosting msbuild for this if there is another way. Paket knows the nuget metadata the same way nuget does and can provide it. But targets/props are basically black-boxes.

@forki is correct that it's the same design decision we made for paket when supporting *.ps1 - looking at that now, I think we can agree that it was the right choice. It also saved paket users from doing stuff they shouldn't do. So if we can save type-provider authors from using targets/props and properly provide the metadata it hopefully will be good for the ecosystem in the long run.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

@forki

Adding targets for resolution is really a bad idea.

I get that. That said, I don't think that there's any need for us to make things any worse for Paket here - we can use nice declarative paths under analyzers or build or compiler-addins or whatever, that's fine.

I think @matthid and myself still don't understand why targets files would be needed at all. How would it look like? As a concrete sample for a type provider?

There are separate issues:

  1. should #r "nuget: Foo" respect targets files in the packages?

  2. should #r "paket: Foo" respect targets files in the packages?

  3. do type providers need a targets file?

My answers:

  1. should ``#r "nuget: Foo"``` respect targets files in the packages?

My answer: Yes, I think they probably should. From the F# perspective I would like ``#r "nuget: FunkyAspNetPackageThatUsesTargets" to work

  1. should ``#r "paket: Foo"``` respect targets files in the packages?

My answer:: It's up to you

  1. do type providers need a targets file?

My answer: Let's assume "no". After all, yesterday I was an advocate of type providers just being single netstandard2.0 DLLs :) If forced I could contrive something, e.g.

  • popping up a dialog to say "please install R on your machine to use the R type provider!")
  • targets that compute which Office version you have on your machine and references a different ExcelTypeProvider DLL depending on version.

But you could put that kind of logic in the implementation of the type provider itself, or just have different type provider packages.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

@forki BTW is there a definition of the exact technical difference between a "props" and "targets" file? Thanks

@matthid
Copy link
Contributor

matthid commented Jul 8, 2017

@dsyme

@forki BTW is there a definition of the exact technical difference between a "props" and "targets" file? Thanks

I think technically they are both msbuild build files. The difference is the time they are imported. props stands for properties and is imported before user project file, while targets are imported after all properties are defined

https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package#including-msbuild-props-and-targets-in-a-package

When NuGet 2.x installs a package with \build files, it will add an MSBuild elements in the project file pointing to the .targets and .props files. (.props is added at the top of the project file; .targets is added at the bottom.)

With NuGet 3.x, targets are not added to the project but are instead made available through the project.lock.json.

@dsyme
Copy link
Contributor Author

dsyme commented Jul 8, 2017

My main takeaways from all this recent discussion is

  1. we need to make sure [WIP]Adding paket package resolution to fsi and VF# editor #2483 and corresponding RFC [RFC FS-1026 Discussion] FSI Reference Model and Extending `#r` fsharp/fslang-design#167 is strong enough that the package resolution addin can compute the right type provider references suitable for the design-time context

  2. as part of this, we need to add a #r-typeprovider "..." mechanism or some equivalent to allow the package resolution addin to report the type provider references back to the compiler, independently of the runtime library references

Together with some other changes, this would basically get the compiler out of the business of doing assembly resolution and type provider resolution.

@forki
Copy link
Contributor

forki commented Jul 8, 2017 via email

@cartermp
Copy link
Contributor

cartermp commented Jul 8, 2017

Perhaps we should draft an RFC for FSI behavior in this new world of netcore and netstandard, and discuss there. It feels to me like what we ultimately want is a bit transformative for FSI. I'd personally love for us to move away from the concept of #r "dll-on-disk.dll" for FSI in general, and before/standard seems to be setting that precedent.

@forki
Copy link
Contributor

forki commented Jul 8, 2017 via email

@dotnet dotnet deleted a comment from cartermp Jul 10, 2017
@dsyme
Copy link
Contributor Author

dsyme commented Jul 10, 2017

dsyme deleted a comment from cartermp

(just edited your "before" to "netcore" to keep the conversation clean :) )

@forki
Copy link
Contributor

forki commented Jul 10, 2017

@dsyme as a note regarding MSbuild background and XS: https://bugzilla.xamarin.com/show_bug.cgi?id=58038 - this shows it's really hard and most tools are not there yet.

@KevinRansom
Copy link
Member

@dsyme :
I doubt if we need a specific type provider #reference option. F# has survived all of this time without one. And when I was convinced we needed an additional #r for reference assemblies, a minor bug avoided the need.

We need to investigate how nuget and roslyn handles analyzers for multiple platforms.

@terrajobst
Copy link
Member

@dsyme

You might want to add some documentation explaining that applications running on .NET 4.6.1/4.7 which do Assembly.Load/LoadFrom on .NET Standard 1.x DLLs or .NET Standard 2.0 DLLs should ship the entire set of facades. It's not at all obvious :)

Agreed. We've recently changed how the applications acquire the necessary facades. We've switched from a NuGet package to global machine state. I'll ensure that reflection scenarios will work. Let me come back to you on this.

Just checked. With the updated preview tooling this is how it works today:

  1. If you reference a .NET Standard 2.0 library anywhere from the application project, the output will contain the 90+ files.
  2. If you don't reference a .NET Standard 2.0 library statically, no such files will be deployed.
  3. If you wish to force the application to contain the output, you can set the MSBuild property <DependsOnNETStandard>true</DependsOnNETStandard>.

Would this design work for F#?

@dsyme
Copy link
Contributor Author

dsyme commented Jul 13, 2017

@terrajobst

If you wish to force the application to contain the output, you can set the MSBuild property DependsOnNETStandard>true</DependsOnNETStandard>

I will try adjusting the PR, thanks. It certainly makes it simpler, though perhaps the changes to setup are still required sadly

However both @KevinRansom and @cartermp are opposed to accepting this PR, primarily because

  1. there is strong opposition to auto-referencing DLLs that come as part of the implementation of the F# compiler - even for the F# scripting programming. It is seen as wrong and has been the cause of many problems in the past. It is also very awkward as a model going forward for .NET Core. (We currently auto-reference FSharp.Core and System.ValueTuple from the compiler implementation, but suddenly doing it for 96 facade DLLs that have nothing to do with F# is brutal. Today we auto-reference a random bunch of facade and implementation DLLs from the implementation assemblies of the .NET Framework we happen to be running on. This is arguably even worse, but that's a different matter).

  2. As a result it is clear that we need to add "reference a package" to F# interactive at some point and this is acting as the forcing function.

  3. it feels very wrong to add 96 facade DLLs to the F# implementation (though your advice seems to indicate it is correct)

  4. it feels as if we are storing up trouble for the future

I understand the reasoning. That said, I'm still very concerned that we have an awkward story for .NET Standard 2.0 DLLs in the interim.

@dsyme dsyme closed this Jul 25, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants