UPDATE: If you are looking for a ready to use solution please check out my fsc-host NuGet package. Also I wrote a blog post about it.
Intro ¶
In the previous post I showed how to extend an F# application with scripting capabilities. This time I want to extend the previous example with the #r directive support and resolving NuGet dependencies.
How to ¶
On high level we need to extract NuGet packages information from the script and pass it to the compiler as additional arguments. Before compiling we also need to dynamically load the referenced dlls to the host application. I first took the naive approach of parsing and extracting the directives from AST and feeding them into FSharpDependencyManager
. It worked to an extent. Luckily, FsChecker
comes with a built-in solution. We can use GetProjectOptionsFromScript
and subsequently ParseAndCheckProject
methods. That way we can retrieve DependencyFiles
and extract resolved dlls' paths.
let resolveNugets () =
async {
let source = File.ReadAllText script.path |> SourceText.ofString
let! projOptions, errors = checker.GetProjectOptionsFromScript(script.path, source)
match errors with
| [] ->
let! projResults = checker.ParseAndCheckProject(projOptions)
return
match projResults.HasCriticalErrors with
| false ->
projResults.DependencyFiles
|> Seq.choose(
function
| path when path.EndsWith(".dll") -> Some path
| _ -> None)
|> Seq.groupBy id
|> Seq.map (fun (path, _) -> path)
|> Ok
| _ -> Error (ScriptParseError (projResults.Errors |> Seq.map string) )
| _ -> return Error (ScriptParseError (errors |> Seq.map string) )
}
Fig. 1. Get resolved dlls' paths for all referenced NuGet packages
Now we can enrich the compiler arguments and load the assemblies.
let refs = nugetResolutions |> Seq.map(fun path -> $"-r:{path}")
nugetResolutions |> Seq.iter (fun r -> Assembly.LoadFrom r |> ignore)
let compilerArgs = [|
"-a"; script.path
"--targetprofile:netcore"
"--target:module"
yield! refs
sprintf "-r:%s" (Assembly.GetEntryAssembly().GetName().Name)
"--langversion:preview"
|]
Fig. 2. Load resolved dlls to the host application and add their paths to the compiler arguments
Wrapping up ¶
This post was supposed to be longer as I tried to go the hard way. Fortunately I found an easier and more reliable method. I hope this and the previous post will help you with hosting the F# compiler in your own apps. I’ll keep experimenting with it. The full source code for this post is available here.