I’ve been doing quite some F# coding lately, which is really nice, plan to write more about F# later, here I’m gonna talk about how to build DotNet projects with Fake.
I’ve put common logic as libraries, then can share them easily across multiple projects, so I need to create NuGet packages. I’ve already created more than a dozen individual libraries, it’s clear that I need an automated process to manage them, or it’s very tedious to keep proper version of libraries in each project.
I’ve did some small work around Fake to make such process, which works quite nicely for me, I plan to write two articles on this, this one will explain the basic structure, and how I use it to manage multiple projects easily, the next one will talk about how to create NuGet package, and how to use a hacky way to do local development easily.
What’s Fake and Why Need It.
Here is the slogan form Fake’s official site: https://fake.build/
F# MAKE 5 - A DSL FOR BUILD TASKS AND MORE THE POWER OF F# - ANYWHERE - ANYTIME
It’s a make
like system, but instead of a special purpose DSL, it’s standard F#, with addition of modules and syntax to make common tasks easily. You can define dependencies of targets (a task in Fake’s terminology) very easily with it’s ==>
operator.
Since it’s standard F#, you can have type check for the make script, and quite nice IDE support in VS Code (haven’t tried other IDEs yet). Here is the code snippet from tutorial
// Targets
Target.create "Clean" (fun _ ->
Shell.cleanDir buildDir
)
Target.create "BuildApp" (fun _ ->
!! "src/app/**/*.csproj"
|> MSBuild.runRelease id buildDir "Build"
|> Trace.logItems "AppBuild-Output: "
)
Target.create "Default" (fun _ ->
Trace.trace "Hello World from FAKE"
)
open Fake.Core.TargetOperators
"Clean"
==> "BuildApp"
==> "Default"
// start build
Target.runOrDefault "Default"
Nice and clean, isn’t it?
Though in practice, this kind of simple approach not working for me.
- First of all, don’t really like the build targets as string here, which is error prune, and won’t benefit from compiler for checks, a union type will be better, though won’t fit to fake’s API easily, the best approach to me is that no need to define these standard targets manually, should be able to generate them by convention.
- Secondly, since building several projects can take time, I would like to be able to just operate on specific project if I want to, I can
cd
to certain folder and rundotnet
commands there in a shell, though it’s a bit tedious to switch current folder back and forth all the time. - Also, most projects are having similar tasks, but with the out-of-box fake, still need to write some boilerplate codes for each project, which is quite tedious.
Introducing Dap.Build
So after some time tinkering around it, I’ve create a simple package for this, which is on GitHub and NuGet:
The following example is used in a library project (not open sourced yet, pushed to private nuget ATM), which includes 13 libraries, with just 30 lines of codes, all of them have individual tasks to clean/restore/build/pack/push… and aggragated tasks to build all, restore all … and all the targets are with properly dependencies.
#r "paket: groupref Build //"
#load ".fake/build.fsx/intellisense.fsx"
open Fake.Core
open Fake.IO.Globbing.Operators
module NuGet = Dap.Build.NuGet
let feed =
NuGet.Feed.Create (
server = NuGet.ProGet "https://nuget.yjpark.org/nuget/dap",
apiKey = NuGet.Environment "API_KEY_nuget_yjpark_org"
)
let projects =
!! "lib/Dap.FlatBuffers/*.csproj"
++ "src/Fable.Dap.Prelude/*.fsproj"
++ "src/Dap.Prelude/*.fsproj"
++ "src/Fable.Dap.Context/*.fsproj"
++ "src/Dap.Context/*.fsproj"
++ "src/Fable.Dap.Platform/*.fsproj"
++ "src/Dap.Platform/*.fsproj"
++ "src/Fable.Dap.WebSocket/*.fsproj"
++ "src/Dap.WebSocket/*.fsproj"
++ "src/Fable.Dap.Remote/*.fsproj"
++ "src/Dap.Remote/*.fsproj"
++ "src/Fable.Dap.Dsl/*.fsproj"
++ "src/Dap.Archive/*.fsproj"
NuGet.createAndRun NuGet.release feed project
This example use paket
to manage packages, need the following snippet in paket.dependencies
group Build
source https://www.nuget.org/api/v2
storage: none
framework: netstandard2.0
nuget Dap.Build
Full list of targets are:
The following targets are available:
Build - Build 13 Projects
Clean - Clean 13 Projects
Dap.Archive:Build - Build Dap.Archive
Dap.Archive:Clean - Clean Dap.Archive
Dap.Archive:Fetch - Fetch Dap.Archive
Dap.Archive:Inject - Inject Dap.Archive
Dap.Archive:Pack - Pack Dap.Archive
Dap.Archive:Push - Push Dap.Archive
Dap.Archive:Recover - Recover Dap.Archive
Dap.Archive:Restore - Restore Dap.Archive
Dap.Context:Build - Build Dap.Context
Dap.Context:Clean - Clean Dap.Context
Dap.Context:Fetch - Fetch Dap.Context
Dap.Context:Inject - Inject Dap.Context
Dap.Context:Pack - Pack Dap.Context
Dap.Context:Push - Push Dap.Context
Dap.Context:Recover - Recover Dap.Context
Dap.Context:Restore - Restore Dap.Context
Dap.FlatBuffers:Build - Build Dap.FlatBuffers
Dap.FlatBuffers:Clean - Clean Dap.FlatBuffers
Dap.FlatBuffers:Fetch - Fetch Dap.FlatBuffers
Dap.FlatBuffers:Inject - Inject Dap.FlatBuffers
Dap.FlatBuffers:Pack - Pack Dap.FlatBuffers
Dap.FlatBuffers:Push - Push Dap.FlatBuffers
Dap.FlatBuffers:Recover - Recover Dap.FlatBuffers
Dap.FlatBuffers:Restore - Restore Dap.FlatBuffers
Dap.Platform:Build - Build Dap.Platform
Dap.Platform:Clean - Clean Dap.Platform
Dap.Platform:Fetch - Fetch Dap.Platform
Dap.Platform:Inject - Inject Dap.Platform
Dap.Platform:Pack - Pack Dap.Platform
Dap.Platform:Push - Push Dap.Platform
Dap.Platform:Recover - Recover Dap.Platform
Dap.Platform:Restore - Restore Dap.Platform
Dap.Prelude:Build - Build Dap.Prelude
Dap.Prelude:Clean - Clean Dap.Prelude
Dap.Prelude:Fetch - Fetch Dap.Prelude
Dap.Prelude:Inject - Inject Dap.Prelude
Dap.Prelude:Pack - Pack Dap.Prelude
Dap.Prelude:Push - Push Dap.Prelude
Dap.Prelude:Recover - Recover Dap.Prelude
Dap.Prelude:Restore - Restore Dap.Prelude
Dap.Remote:Build - Build Dap.Remote
Dap.Remote:Clean - Clean Dap.Remote
Dap.Remote:Fetch - Fetch Dap.Remote
Dap.Remote:Inject - Inject Dap.Remote
Dap.Remote:Pack - Pack Dap.Remote
Dap.Remote:Push - Push Dap.Remote
Dap.Remote:Recover - Recover Dap.Remote
Dap.Remote:Restore - Restore Dap.Remote
Dap.WebSocket:Build - Build Dap.WebSocket
Dap.WebSocket:Clean - Clean Dap.WebSocket
Dap.WebSocket:Fetch - Fetch Dap.WebSocket
Dap.WebSocket:Inject - Inject Dap.WebSocket
Dap.WebSocket:Pack - Pack Dap.WebSocket
Dap.WebSocket:Push - Push Dap.WebSocket
Dap.WebSocket:Recover - Recover Dap.WebSocket
Dap.WebSocket:Restore - Restore Dap.WebSocket
Fable.Dap.Context:Build - Build Fable.Dap.Context
Fable.Dap.Context:Clean - Clean Fable.Dap.Context
Fable.Dap.Context:Fetch - Fetch Fable.Dap.Context
Fable.Dap.Context:Inject - Inject Fable.Dap.Context
Fable.Dap.Context:Pack - Pack Fable.Dap.Context
Fable.Dap.Context:Push - Push Fable.Dap.Context
Fable.Dap.Context:Recover - Recover Fable.Dap.Context
Fable.Dap.Context:Restore - Restore Fable.Dap.Context
Fable.Dap.Dsl:Build - Build Fable.Dap.Dsl
Fable.Dap.Dsl:Clean - Clean Fable.Dap.Dsl
Fable.Dap.Dsl:Fetch - Fetch Fable.Dap.Dsl
Fable.Dap.Dsl:Inject - Inject Fable.Dap.Dsl
Fable.Dap.Dsl:Pack - Pack Fable.Dap.Dsl
Fable.Dap.Dsl:Push - Push Fable.Dap.Dsl
Fable.Dap.Dsl:Recover - Recover Fable.Dap.Dsl
Fable.Dap.Dsl:Restore - Restore Fable.Dap.Dsl
Fable.Dap.Platform:Build - Build Fable.Dap.Platform
Fable.Dap.Platform:Clean - Clean Fable.Dap.Platform
Fable.Dap.Platform:Fetch - Fetch Fable.Dap.Platform
Fable.Dap.Platform:Inject - Inject Fable.Dap.Platform
Fable.Dap.Platform:Pack - Pack Fable.Dap.Platform
Fable.Dap.Platform:Push - Push Fable.Dap.Platform
Fable.Dap.Platform:Recover - Recover Fable.Dap.Platform
Fable.Dap.Platform:Restore - Restore Fable.Dap.Platform
Fable.Dap.Prelude:Build - Build Fable.Dap.Prelude
Fable.Dap.Prelude:Clean - Clean Fable.Dap.Prelude
Fable.Dap.Prelude:Fetch - Fetch Fable.Dap.Prelude
Fable.Dap.Prelude:Inject - Inject Fable.Dap.Prelude
Fable.Dap.Prelude:Pack - Pack Fable.Dap.Prelude
Fable.Dap.Prelude:Push - Push Fable.Dap.Prelude
Fable.Dap.Prelude:Recover - Recover Fable.Dap.Prelude
Fable.Dap.Prelude:Restore - Restore Fable.Dap.Prelude
Fable.Dap.Remote:Build - Build Fable.Dap.Remote
Fable.Dap.Remote:Clean - Clean Fable.Dap.Remote
Fable.Dap.Remote:Fetch - Fetch Fable.Dap.Remote
Fable.Dap.Remote:Inject - Inject Fable.Dap.Remote
Fable.Dap.Remote:Pack - Pack Fable.Dap.Remote
Fable.Dap.Remote:Push - Push Fable.Dap.Remote
Fable.Dap.Remote:Recover - Recover Fable.Dap.Remote
Fable.Dap.Remote:Restore - Restore Fable.Dap.Remote
Fable.Dap.WebSocket:Build - Build Fable.Dap.WebSocket
Fable.Dap.WebSocket:Clean - Clean Fable.Dap.WebSocket
Fable.Dap.WebSocket:Fetch - Fetch Fable.Dap.WebSocket
Fable.Dap.WebSocket:Inject - Inject Fable.Dap.WebSocket
Fable.Dap.WebSocket:Pack - Pack Fable.Dap.WebSocket
Fable.Dap.WebSocket:Push - Push Fable.Dap.WebSocket
Fable.Dap.WebSocket:Recover - Recover Fable.Dap.WebSocket
Fable.Dap.WebSocket:Restore - Restore Fable.Dap.WebSocket
Fetch - Fetch 13 Projects
Inject - Inject 13 Projects
Pack - Pack 13 Projects
Push - Push 13 Projects
Recover - Recover 13 Projects
Restore - Restore 13 Projects
I am really happy with it, can do most operation easily. The nuget related feature is very nice, I didn’t find much information about how other developers work with nugets, what commands to pack and push them, currently Dap.Build support both nuget.org and ProGit, will have another post on this part later.
The missing part is auto complete for targets, though since I’m using fish, it’s command history can replace auto completion mostly, so this is not an urgent feature to me.