Using the FSharp.Compiler.Service to Compile a fsproj
I’m currently building a compiler/transcompiler for F# to WebAssembly called tinyfs
Initially TinyFS was only building individual F# files. But my goal was to support building an entire *.fsproj file.
There does exist some documentation on that, but mentions .NETFramework so not very recent.
In slack I asked a question about it and was able to get it answered. Since Slack isn’t web accessible and only shows the last 90 days worth of info, I decided to document it here for posterity sake.
Slack Conversation
My Initial Question
What’s the canonical way of having the FSharp.Compiler.Service build an fsproj and having it return the AST? I’ve been able to parse and type check individual snippets of code by writing to a script file (.fsx):
let parseAndCheckSingleFile (checker: FSharpChecker) (input: string) =
let file = Path.ChangeExtension(System.IO.Path.GetTempFileName(), ".fsx")
File.WriteAllText(file, input)
// Get context representing a stand-alone (script) file
let projOptions, _errors =
checker.GetProjectOptionsFromScript(file, SourceText.ofString input, assumeDotNetFramework = false)
|> Async.RunSynchronously
checker.ParseAndCheckProject(projOptions)
|> Async.RunSynchronously
I imagined I could pass FSharpChecker an *.fsproj file and the FSharp.Compiler.Service could determines the files to compile based on the *fs.proj file. But I’ve been having to pass the individual files for it to compile correctly:
let parseAndCheckProject (checker: FSharpChecker) (projectPath: string) (projectFilePath: string) =
let args: string array =
[|
yield $"{projectPath}/Library.fs"
|]
let projectOptions =
checker.GetProjectOptionsFromCommandLineArgs(projectFilePath, args) // projectFilePath args // GetProjectOptionsFromProjectFile(projectFilePath)
// Parse and check the entire project
let projectResults =
checker.ParseAndCheckProject(projectOptions)
|> Async.RunSynchronously
projectResults
Is this the correct way to do this? Or am I missing something?
I’ve been using this doc as a basis for the code above: https://fsharp.github.io/fsharp-compiler-docs/fcs/project.html
As my *.fsproj project grows (or I use other ones), I’d rather not have to specify every file I want it to compile. Especially since the *.fsproj already has that information.
Chet Husk’s (chethusk) Reply
You need to ‘crack’ the project file (which means doing MSBuild evaluation and pulling command line args from it) and then convert that MSBuild driven information into an FSharpProjectOptions. I maintain the ionide/proj-info library to do this in process, but I know Fable has at least one other version of this process (edited)
Me
So for a project that has 100 fs files in it, there’s 100 individual fs files passed via the command line args in order to build the project?
Chet
Yes
Same is true for C#. Also the command line contains every dll in the .net base class library. It’s a lot of args!
Me
Thank you for clarifying, learned something new today.
Conclusion
This helped point me in the right direction, I hope it can help someone else as well.