mu88
mu88

Reputation: 5454

How to translate this MSBuild script to F#/FAKE?

What is the translation of the following MSBuild script into F#/FAKE?

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
    <PropertyGroup>
        <CurrentMode>None</CurrentMode>
    </PropertyGroup>

    <Target Name="Compile_A">
        <Message Text="Hello from Compile_A" />

        <CallTarget Targets="SetToA"/>
        <CallTarget Targets="IntermediateStage"/>
    </Target>

    <Target Name="Compile_B">
        <Message Text="Hello from Compile_B" />

        <CallTarget Targets="SetToB"/>
        <CallTarget Targets="IntermediateStage"/>
    </Target>

    <Target Name="IntermediateStage">
        <Message Text="Hello from IntermediateStage" />

        <CallTarget Targets="Compile"/>
    </Target>

    <Target Name="Compile">
        <Message Text="Hello from Compile. I'm using $(CurrentMode)" />
    </Target>

    <!-- The following Targets are only necessary due to a MSBuild bug (CallTarget and CreateProperty cannot be combined) -->
    <Target Name="SetToA">
        <CreateProperty Value="A">
            <Output TaskParameter="Value" PropertyName="CurrentMode" />
        </CreateProperty>
    </Target>

    <Target Name="SetToB">
        <CreateProperty Value="B">
            <Output TaskParameter="Value" PropertyName="CurrentMode" />
        </CreateProperty>
    </Target>
</Project>

My main goal is to set a property in the topmost targets with different values (CurrentMode is either A or B) and consume it in the deepest target (Compile).

Upvotes: 1

Views: 414

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243096

The best answer will probably differ based on what is your actual scenario. In particular, you might not even need separate targets for the different steps of your process if you do not ever plan to run them separately (in which case, just using a function that takes the mode as a parameter and invoking that from CompileA and CompileB would work fine).

However, if you want to keep separate targets for all the steps, you could do something like this:

#load ".fake/build.fsx/intellisense.fsx"
open Fake.Core
open Fake.Core.TargetOperators

let mutable CurrentMode = "None"

Target.create "SetA" (fun _ ->
  CurrentMode <- "A"
)

Target.create "SetB" (fun _ ->
  CurrentMode <- "B"
)

Target.create "IntermediateStage" (fun _ ->
  printfn "In the intermediate stage"
)

Target.create "Compile" (fun _ ->
  printfn "Compiling using mode %s" CurrentMode
)

Target.create "CompileA" ignore
Target.create "CompileB" ignore

"SetA" ==> "CompileA"
"SetB" ==> "CompileB"
"IntermediateStage" ==> "Compile" ==> "CompileA"
"IntermediateStage" ==> "Compile" ==> "CompileB"
"SetA" ?=> "IntermediateStage"
"SetB" ?=> "IntermediateStage"

Target.runOrDefault "CompileA"

This uses mutable variable CurrentMode that is set by SetA or SetB targets (arguably, not very functional, but it captures what you're doing).

The dependencies between targets are specified using ==>. Note that SetA has to happen before CompileA (and similar for B) and IntermediateStage needs to go before Compile which is pre-requisite for both kinds of compiles.

There is one subtle trick - you don't want to say that SetA and SetB are required for IntermediateStep, because then FAKE would run both in non-deterministic order. the ?=> opreator lets you specify soft dependencies which say that if both IntermediateStep and SetA are to be executed, then SetA has to go first - so the last two lines are not adding dependencies, but making ordering explicit.

Upvotes: 3

Related Questions