In FAKE you typically have a buildscript like:
// "foo.fsx"
#r @"./packages/tools/FAKE/tools/FakeLib.dll"
open Fake
Target "Foo" (fun _ -> trace "Here I am, foo the size of a bar" )
Run "Foo"
Which works fine. Now suppose you want another buildscript, called "bar.fsx",
// "bar.fsx"
#r @"./packages/tools/FAKE/tools/FakeLib.dll"
#load "foo.fsx"
open Fake
Target "Bar" (fun _ -> trace "And you have me building software ..." )
Run "Bar"
This is now wrong. If you try and run "bar.fsx" it will first execute the task "Foo" even though I didn't explicitly ask for that as a dependency. It's because the load
directive runs the foo.fsx
script.
So, my question is, how can I import the foo.fsx
file, obtain all the Targets, but not execute say the Run
command. Or perhaps more interestingly, and what I really want to do; how can I share Target
s between scripts in FAKE?
It doesn't help if I put the Target
definitions into a module, like so:
module FooTargets =
Target "Foo" (fun _ -> trace "Here I am, foo the size of a bar" )
which is perhaps mildly surprising to me ...
Appreciate any advice on how to accomplish this. My "best" idea so far has been to define some constant in say 'foo.fsx' and then conditionally load
it in all other scripts. This seems pretty offensive, though (edit: and happily, not even possible.)
-- Update: Below is a more complete (i.e. runnable) example. It fails with FAKE 2.2.12.0, with a Duplicate Key error, as the module Common has been loaded twice, and FAKE has populated the target dictionary with the same "Env" target.
// "common.fsx"
#r @"../../tools/FAKE/FakeLib.dll"
open Fake
module Common =
Target "Env" (fun _ -> trace "Env")
,
// "foo.fsx"
#r @"../../tools/FAKE/FakeLib.dll"
#load @"common.fsx"
open Fake
module Foo =
Target "Foo" (fun _ -> trace "Here I am, foo the size of a bar" )
"Env" ==> "Foo"
,
// "bar.fsx"
#r @"../../tools/FAKE/FakeLib.dll"
#load @"common.fsx"
open Fake
module Bar =
Target "Bar" (fun _ -> trace "And you have me building software ..." )
"Env" ==> "Bar"
,
// "marvin.fsx"
#r @"../../tools/FAKE/FakeLib.dll"
#load @"common.fsx"
#load @"foo.fsx"
#load @"bar.fsx"
open Fake
module Marvin =
Target "Marvin" (fun _ -> ())
"Bar" ==> "Marvin"
"Foo" ==> "Marvin"
Run "Marvin"
Build with: fake.exe Marvin.fsx
.
Error:
System.TypeInitializationException: The type initializer for '<StartupCode$FSI_0002>.$FSI_0002_Common$fsx' threw an exception. ---> System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at Fake.TargetHelper.targetFromTemplate[a](TargetTemplate`1 template, String name, a parameters)
at [email protected](String name, a parameters)
at <StartupCode$FSI_0002>.$FSI_0002_Common$fsx..cctor() in c:\Users\Noon\dev\beep\common.fsx:line 7
--- End of inner exception stack trace ---
at <StartupCode$FSI_0002>.$FSI_0002_Foo$fsx.main@() in c:\Users\Noon\dev\beep\foo.fsx:line 8
Stopped due to error
Searching for process with name = fsi.exe
Searching for process with name = msbuild
In Build.Tools we ended up putting the code for the various targets in different script files as normal F# functions, and then composing the targets together in a Core.fsx that sets up the targets and their dependencies.
One of the things on my low priority todo list is actually to separate Core into two files - one which builds up the configuration and target definitions, and one which sets up the dependencies and calls Run
. In that way you could re-use all underlying targets while defining different runners that didn't have to include the full default dependency tree.
The current Core.fsx
looks like this:
#r "./fake/fakelib.dll"
#load "./Utils.fsx"
#load "./Packaging.fsx"
#load "./Versioning.fsx"
#load "./Solution.fsx"
#load "./Test.fsx"
#load "./Specflow.fsx"
open System.IO
open Fake
let config =
Map.ofList [
"build:configuration", environVarOrDefault "configuration" "Release"
"build:solution", environVar "solution"
"core:tools", environVar "tools"
"packaging:output", environVarOrDefault "output" (sprintf "%s\output" (Path.GetFullPath(".")))
"packaging:updateid", environVarOrDefault "updateid" ""
"packaging:pushurl", environVarOrDefault "pushurl" ""
"packaging:apikey", environVarOrDefault "apikey" ""
"packaging:packages", environVarOrDefault "packages" ""
"versioning:build", environVarOrDefault "build_number" "0"
"versioning:branch", match environVar "teamcity_build_branch" with
| "<default>" -> environVar "vcsroot_branch"
| _ -> environVar "teamcity_build_branch"
]
Target "Default" <| DoNothing
Target "Packaging:Package" <| Packaging.package config
Target "Packaging:Restore" <| Packaging.restore config
Target "Packaging:Update" <| Packaging.update config
Target "Packaging:Push" <| Packaging.push config
Target "Solution:Build" <| Solution.build config
Target "Solution:Clean" <| Solution.clean config
Target "Versioning:Update" <| Versioning.update config
Target "Test:Run" <| Test.run config
Target "SpecFlow:Run" <| Specflow.run config
"Solution:Clean"
==> "Packaging:Restore"
==> "Versioning:Update"
==> "Solution:Build"
==> "Packaging:Package"
==> "SpecFlow:Run"
==> "Test:Run"
=?> ("Packaging:Push", not isLocalBuild)
==> "Default"
RunParameterTargetOrDefault "target" "Default"
Here's a solution I came up with. I had to separate out the targets from the build process, due to the realities of #load
and the FAKE process for running targets. But it at least accomplishes my goal. I'm not entirely sure how I feel about it, because of the "loose" connection between the inter-file dependencies; but perhaps one could argue that that is a good thing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With