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

[WIP] - Build packagemanagement based on Steffen's Package manager design. #4042

Closed
wants to merge 8 commits into from

Conversation

KevinRansom
Copy link
Member

This is @forki's original PR. With the paket specific stuff removed.

Discussion: #2483

So there will be a few minor tweaks to implement a package based extensibility mechanism, that will be aligned with Don's proposal for TypeProvider design time components. Other than that the basic idea remains unchanged. The package manger receives the text for a #r" it ensures that the packages are deployed, provides FSI a script fragment that references the correct dlls to be executed.

Packages can be specified and loaded during an interactive session.
new PackageManagers can be loaded whilst a sript is executing and from that point on in the session.

Todo:

  1. Implement compilerTools extensibility mechanism in compiler.
  2. Rationalize extensibility probing to match Type Provider design time mechanism from: Type provider design time component loading suitable for .NET Core tooling #3864
  3. Unit tests for compilerTools extensibility
  4. Unit tests for packagemanager extension point
  5. A default package manager for windows desktop / Cross Platform coreclr

@@ -1422,3 +1422,6 @@ notAFunctionButMaybeIndexer,"This expression is not a function and cannot be app
notAFunctionButMaybeDeclaration,"This value is not a function and cannot be applied. Did you forget to terminate a declaration?"
3218,ArgumentsInSigAndImplMismatch,"The argument names in the signature '%s' and implementation '%s' do not match. The argument name from the signature file will be used. This may cause problems when debugging or profiling."
3219,pickleUnexpectedNonZero,"An error occurred while reading the F# metadata of assembly '%s'. A reserved construct was utilized. You may need to upgrade your F# compiler or use an earlier version of the assembly that doesn't make use of a specific construct."
3220,couldNotLoadDependencyManagerExtenstion,"The dependency manager extension %s could not be loaded. Message: %s"
Copy link
Contributor

Choose a reason for hiding this comment

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

%s should be in single quotes here

try
CustomAttributeExtensions.GetCustomAttributes(theAssembly)
|> Seq.tryFind (fun a -> a.GetType().Name = attributeName)
|> function | Some _ -> true | _ -> false
Copy link
Contributor

@saul saul Dec 1, 2017

Choose a reason for hiding this comment

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

This can be replaced with: |> Seq.exists (fun a -> ...

// * to minimize footprint of integration in fsi/CompileOps

/// hardcoded to net461 as we don't have fsi on netcore
let targetFramework = "net461"
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this actually used for? Can we not determine our current .Net Framework version instead of hardcoding?

Copy link
Contributor

Choose a reason for hiding this comment

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

eventually yes. but not needed now

Copy link
Contributor

Choose a reason for hiding this comment

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

Well what if I’m on .Net 4.5?

Copy link
Contributor

Choose a reason for hiding this comment

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

then fsi is still...

Copy link
Contributor

Choose a reason for hiding this comment

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

Right but this is going to be passed to nuget/paket/whatever to determine which assembly to reference... the precise .Net Framework version is important.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hard coding net461 is wrong for FSharp.Compiler.Service.dll (which can be used to build tools that run on other frameworks) and for editing environments. I'd rather see this passed down from the host tool, or otherwise configurable somehow.

Copy link
Contributor

Choose a reason for hiding this comment

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

Of course hard coding is wrong, but it's a bit tricky since it needs to be the executing environment. Not necessarily the fcs environment. Consider we host the feature in fake

Copy link
Contributor

Choose a reason for hiding this comment

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

Then ionide might run in net461 but fake process in netcore. Of course fake would restore a second time, but that would be a waste

Copy link
Contributor

Choose a reason for hiding this comment

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

Then ionide might run in net461 but fake process in netcore. Of course fake would restore a second time, but that would be a waste

I see.

It is true that the "default assumed target framework" for the F# scripting model is net461. That is, an editing tool, or execution tool should process a script on the assumption it is targeting net461.

For FSI on .NET Core this will be netcoreapp20 (we should at least define this conditionally here?), but editing tools will still have to assume net461 by default.

Because of this, we will likely need to add a directive #framework "net451" or #framework "netcoreapp20" I suppose, to allow editing tools to know that the script is really designed for use on .NET Core.

Copy link
Member Author

Choose a reason for hiding this comment

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

Specification and switching of an alternate target framework is TBD. The default target framework will either depend on the host or be netstandard2.0.

tcConfigB.AddIncludePath(m,folder,"")

match loadScript with
| Some loadScript -> istate := fsiDynamicCompiler.EvalSourceFiles (ctok, !istate, m, [loadScript], lexResourceManager, errorLogger)
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of using a ref variable here, this can be restructured as a Seq.fold over tcConfigB.packageManagerLines

@realvictorprm
Copy link
Contributor

Thank you @KevinRansom! :)

| _ ->
match DependencyManagerIntegration.tryFindDependencyManagerByKey m packageManagerKey with
| None ->
errorR(DependencyManagerIntegration.createPackageManagerUnknownError packageManagerKey m)
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps this should just be a warning. There will be a later error almost for sure - and if there isn't for some reason then it seems reasonable to proceed with compilation/execution

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes additionally, I think this would be a breaking change

|> Seq.tryFind (fun a -> a.GetType().Name = attributeName)
with | _ -> None

let getInstanceProperty<'treturn> (theType: Type) indexParameterTypes propertyName =
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe indexParameterTypes is always empty in this PR, I think it's best to remove it.

let executingAssembly =
typeof<IDependencyManagerProvider>.GetTypeInfo().Assembly
yield Path.GetDirectoryName(executingAssembly.Location)
#if FX_NO_APP_DOMAINS
Copy link
Contributor

Choose a reason for hiding this comment

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

Use #if !FX_NO_APP_DOMAINS

interface System.IDisposable with
member __.Dispose() = ()

let registeredDependencyManagers = ref None
Copy link
Contributor

@dsyme dsyme Dec 1, 2017

Choose a reason for hiding this comment

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

Does this have to be a static mutable global? Why not hold it in TcConfig or the TcImports (the one for the non-framework assemblies)?

Copy link
Contributor

@dsyme dsyme left a comment

Choose a reason for hiding this comment

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

Looking good! I made a few comments above. Some more tests needed :)

@dsyme
Copy link
Contributor

dsyme commented Dec 1, 2017

@KevinRansom @cartermp We should add an RFC for this. Language/tooling suggestion is here: fsharp/fslang-suggestions#542

@cartermp
Copy link
Contributor

cartermp commented Dec 1, 2017

This is the one that will need reworking: https://github.com/fsharp/fslang-design/blob/master/tooling/FST-1027-fsi-references.md

@KevinRansom
Copy link
Member Author

Surely you had an RFC when you did this original work?

@KevinRansom KevinRansom changed the title [WIP] - Build packagemanagement based on Steffan's Package manager design. [WIP] - Build packagemanagement based on Steffen's Package manager design. Dec 2, 2017
@smoothdeveloper
Copy link
Contributor

@realvictorprm
Copy link
Contributor

Ping :-)

When do we plan to add this? End of 2018?(no trolling)

@cartermp
Copy link
Contributor

When it's ready.

@isaacabraham
Copy link
Contributor

@KevinRansom
Copy link
Member Author

@realvictorprm sorry for the lack of progress. This is on me, it's an expected deliverable, but stuff keeps coming up.
Sorry ...

Kevin

@realvictorprm
Copy link
Contributor

realvictorprm commented Jan 26, 2018 via email

@amieres
Copy link
Contributor

amieres commented Feb 20, 2018

Would this also support plain old environment variables?

#r "%pack%/WebSharper/lib/net40/WebSharper.Core.dll"

@KevinRansom
Copy link
Member Author

That would depend on the package manager.

#r "pathToDll" doesn't, and won't be amended to enable.

however:
#r "nuget:moniker"
moniker won't be a path to disk,

I'm guessing
#r "paket:moniker"

but if someone wrote a filesystem package manager:
#r "filesystem:sometoken"

would, if someone chose to make a filesystem package manager.

I hope this helps

Kevin

@amieres
Copy link
Contributor

amieres commented Feb 21, 2018

Yes, it does help.
Thank you.

Copy link
Contributor

@matthid matthid left a comment

Choose a reason for hiding this comment

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

Some questions/clarifications. Maybe some comments are required (or another signature for the interface)

| _ ->
match DependencyManagerIntegration.tryFindDependencyManagerByKey m packageManagerKey with
| None ->
errorR(DependencyManagerIntegration.createPackageManagerUnknownError packageManagerKey m)
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes additionally, I think this would be a breaking change

abstract Name : string
abstract ToolName: string
abstract Key: string
abstract ResolveDependencies : string * string * string * string * string seq -> string option * string list
Copy link
Contributor

Choose a reason for hiding this comment

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

Do I understand correctly that this returns F# code which itself is compiled and run?
Shouldn't we just have a list of implementation and reference assemblies or do we need that for fable somehow?

Copy link
Member Author

Choose a reason for hiding this comment

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

@matthid Yes, the extension manager, returns a script to FSI that is executed. The interesting thing about this approach is it's very easy to reason about and debug, what is happening. For example we know what happens when you reference an already referenced assembly.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well I have to say that there is some technical elegance in this.

But is there particular use-case where we need this? Because on the other side it slows down things and might introduce a lot more error scenarios where a user might not realize what happens behind the scenes. (You might argue that performance is probably not a huge deal considering the package-manager might go on the network and I basically agree, on the other side stuff might be cached already and the package manager might return very fast)

Copy link
Contributor

Choose a reason for hiding this comment

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

@matthid / @KevinRansom it actually returns location of a generated script, it means if any error surfaces, it will point to a temporary file which remains there for debug purpose.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok but realistically what can a user do if there is an error in that script? Especially a newcomer will not know where to report the issue. While we can probably pass the context and improve the error message I don't think it is part of this PR (or the spec for that matter).

| None -> istate // error already reported
| Some (loadScript, additionalIncludeFolders) ->
for folder in additionalIncludeFolders do
tcConfigB.AddIncludePath(m, folder, "")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we actually need include folders when we have the load-Script?

Copy link
Contributor

Choose a reason for hiding this comment

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

@matthid AFAIR they affect the resolution of subsequently loaded .fsx as well as references.

Copy link
Contributor

Choose a reason for hiding this comment

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

But what is the difference to the script containing #I?

Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect the choice to return both the script location and additional include folders was taken in context of ScriptCs integration but don't remember for sure.

I'd need to check where that second value is used in context of FSI.

Other advantages (beside troubleshooting) of having a temporary script loaded is caching (based on a hash of the package manager directive contents), and making it easier to load a graph of things using relative path from the top level temporary script.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note that I'm not against a temporary script at all. I'm just trying to follow the reasoning, because the spec doesn't really say or list pros/cons or something like that. From my perspective it just makes things more complicated. Also I don't really understand the caching argument because the package manager can still cache a list of paths and so can the compiler?

Copy link
Contributor

Choose a reason for hiding this comment

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

There is still room to adjust the PackageManager integration signatures, and actually document it better in the code (I see there are no comments about the return value).

One concern though was that the return value has to be plain .net types rather than F# specific or relying on extra assembly, to allow easier interop via reflection from reflection / tooling perspective.

Are you concerned the temporary script is going to be a concern?

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.

None yet

10 participants