Question

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 Targets 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 Fake.TargetHelper.TargetTemplateWithDependecies@140.Invoke(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
Was it helpful?

Solution 2

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.

OTHER TIPS

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"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top