Skip to content

Commit

Permalink
Merge pull request #455 from bentayloruk/new-cli
Browse files Browse the repository at this point in the history
New (backwards compat) CLI for FAKE that includes FSI cmd args passing
  • Loading branch information
forki committed Jun 9, 2014
2 parents d54642a + c265fcf commit dd42061
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 45 deletions.
80 changes: 80 additions & 0 deletions src/app/FAKE/Cli.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
///
/// New Command line interface for FAKE that utilises UnionArgParser.
///
[<RequireQualifiedAccessAttribute>]
module Cli

open System
open Nessos.UnionArgParser

type FakeArg =
| [<AltCommandLine("-ev")>] EnvVar of string * string
| [<AltCommandLine("-ef")>] EnvFlag of string
| [<AltCommandLine("-lf")>] LogFile of string
| [<AltCommandLine("-pd")>] PrintDetails
| [<AltCommandLine("-v")>] Version
| [<Rest>] FsiArgs of string
| [<Rest>] Boot of string
interface IArgParserTemplate with
member x.Usage =
match x with
| EnvVar _ -> "Set environment variable <name> <value>. Supports multiple."
| EnvFlag _ -> "Set environment variable flag <name> 'true'. Supports multiple."
| LogFile _ -> "Build output log file path."
| PrintDetails _ -> "Print details of FAKE's activity."
| FsiArgs _ -> "Pass args after this switch to FSI when running the build script."
| Version _ -> "Print FAKE version information."
| Boot _ -> "TBC"

/// Return the parsed FAKE args or the parse exception.
let parsedArgsOrEx args =
try
let args = args |> Seq.skip 1 |> Array.ofSeq
let parser = UnionArgParser<FakeArg>()
Choice1Of2(parser.Parse(args))
with | ex -> Choice2Of2(ex)

/// Prints the FAKE argument usage.
let printUsage () =
printfn @"
fake.exe [<scriptPath>] [<targetName>] [switches]
Switches:
%s" (UnionArgParser<FakeArg>().Usage())

type Args = { Script: string option; Target: string option; Rest: string [] }

/// Parses the positional args and provides the remaining tail args.
let parsePositionalArgs (args:string []) =

//Support this usage.
//fake.exe <script>.fsx <targetName> [switches]
//fake.exe <targetName> [switches]
let maybeScript, maybeTarget =
if args.Length > 1 then
let isScriptArg (arg:string) = arg.EndsWith(".fsx", StringComparison.InvariantCultureIgnoreCase)
let isTargetArg (arg:string) = not <| arg.StartsWith("-")//i.e. it's not a switch.
let arg1 = args.[1]
let maybeScriptOrTarget =
if isScriptArg arg1 then Some(Choice1Of2(arg1))
elif isTargetArg arg1 then Some(Choice2Of2(arg1))
else None
match maybeScriptOrTarget with
| Some(Choice1Of2(script)) when args.Length > 2 ->
let arg2 = args.[2]
if isTargetArg arg2 then Some(script), Some(arg2)
else Some(script), None
| Some(Choice1Of2(script)) -> Some(script), None
| Some(Choice2Of2(target)) -> None, Some(target)
| None -> None, None
else None, None

let restOfArgs =
let tailIndex =
match maybeScript, maybeTarget with
| Some(_), Some(_) -> 3 | Some(_), None | None, Some(_) -> 2 | None, None -> 1
if args.Length-1 >= tailIndex
then Array.concat (seq { yield [| args.[0] |]; yield args.[tailIndex..] })
else [| args.[0] |]

{ Script = maybeScript; Target = maybeTarget; Rest = restOfArgs }
30 changes: 17 additions & 13 deletions src/app/FAKE/FAKE.fsproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
Expand Down Expand Up @@ -41,11 +41,23 @@
</OtherFlags>
<DefineConstants>TRACE;DEBUG</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.1\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.1\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
<Import Project="$(FSharpTargetsPath)" Condition="Exists('$(FSharpTargetsPath)')" />
<ItemGroup>
<Compile Include="AssemblyInfo.fs" />
<Compile Include="CommandlineParams.fs" />
<Compile Include="Cli.fs" />
<Compile Include="Program.fs" />
<Content Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Reference Include="FSharp.Core, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
Expand All @@ -57,24 +69,16 @@
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="UnionArgParser">
<HintPath>..\..\..\packages\UnionArgParser.0.6.4\lib\net40\UnionArgParser.dll</HintPath>
<Private>True</Private>
</Reference>
<ProjectReference Include="..\FakeLib\FakeLib.fsproj">
<Name>FakeLib</Name>
<Project>{13d56521-772a-41db-9772-1da1a4aa8e3a}</Project>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.1\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.1\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
<Import Project="$(FSharpTargetsPath)" Condition="Exists('$(FSharpTargetsPath)')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
Expand Down
124 changes: 105 additions & 19 deletions src/app/FAKE/Program.fs
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
open System
open Fake
open System.IO
open Nessos.UnionArgParser

let printVersion() =
traceFAKE "FakePath: %s" fakePath
traceFAKE "%s" fakeVersionStr

let printUsage () =
printfn "-------------------"
printfn " FAKE usage"
printfn "-------------------"
Cli.printUsage ()
printfn "--------------------"
printfn " Classic FAKE usage"
printfn "--------------------"
CommandlineParams.printAllParams()

let printEnvironment cmdArgs args =
printVersion()

Expand Down Expand Up @@ -33,34 +44,109 @@ let buildScripts = !! "*.fsx" |> Seq.toList
try
try
AutoCloseXmlWriter <- true

let cmdArgs = System.Environment.GetCommandLineArgs()
if containsParam "version" cmdArgs then printVersion() else
if (cmdArgs.Length = 2 && paramIsHelp cmdArgs.[1]) || (cmdArgs.Length = 1 && List.length buildScripts = 0) then CommandlineParams.printAllParams() else
match Boot.ParseCommandLine(cmdArgs) with
| None ->
let buildScriptArg = if cmdArgs.Length > 1 && cmdArgs.[1].EndsWith ".fsx" then cmdArgs.[1] else Seq.head buildScripts
let fakeArgs = cmdArgs |> Array.filter (fun x -> x.StartsWith "-d:" = false)
let fsiArgs = cmdArgs |> Array.filter (fun x -> x.StartsWith "-d:") |> Array.toList
let args = CommandlineParams.parseArgs (fakeArgs |> Seq.filter ((<>) buildScriptArg) |> Seq.filter ((<>) "details"))

traceStartBuild()
let printDetails = containsParam "details" cmdArgs
if printDetails then
printEnvironment cmdArgs args
if not (runBuildScript printDetails buildScriptArg fsiArgs args) then
Environment.ExitCode <- 1
else
if printDetails then log "Ready."
| Some handler ->
handler.Interact()

let args = Cli.parsePositionalArgs cmdArgs

match Cli.parsedArgsOrEx args.Rest with

//We have new style help args!
| Choice1Of2(fakeArgs) ->

//Boot and version force us to ignore other args, so check for them and handle.
let isBoot, bootArgs = fakeArgs.Contains <@ Cli.Boot @>, fakeArgs.GetResults <@ Cli.Boot @>
let isVersion = fakeArgs.Contains <@ Cli.Version @>
let printDetails = fakeArgs.Contains <@ Cli.PrintDetails @>

match isVersion, isBoot with

//Version.
| true, _ -> printVersion()

//Boot.
| false, true ->
let handler = Boot.HandlerForArgs bootArgs//Could be List.empty, but let Boot handle this.
handler.Interact()

//Try and run a build script!
| false, false ->

traceStartBuild()
if printDetails then printVersion()

//Maybe log.
match fakeArgs.TryGetResult <@ Cli.LogFile @> with
| Some(path) -> addXmlListener path
| None -> ()

//Combine the key value pair vars and the flag vars.
let envVars =
seq { yield! fakeArgs.GetResults <@ Cli.EnvFlag @> |> Seq.map (fun name -> name, "true")
yield! fakeArgs.GetResults <@ Cli.EnvVar @>
if args.Target.IsSome then yield "target", args.Target.Value }

//Get our fsiargs from somewhere!
let fsiArgs =
match
fakeArgs.GetResults <@ Cli.FsiArgs @>,
args.Script,
List.isEmpty buildScripts with

//TODO check for presence of --fsiargs with no args? Make attribute for UAP?

//Use --fsiargs approach.
| x::xs, _, _ ->
match FsiArgs.parse (x::xs |> Array.ofList) with
| Choice1Of2(fsiArgs) -> fsiArgs
| Choice2Of2(msg) -> failwith (sprintf "Unable to parse --fsiargs. %s." msg)

//Script path is specified.
| [], Some(script), _ -> FsiArgs([], script, [])

//No explicit script, but have in working directory.
| [], None, false -> FsiArgs([], List.head buildScripts, [])

//Noooo script anywhere!
| [], None, true -> failwith "Build script not specified on command line, in fsi args or found in working directory."

//TODO if printDetails then printEnvironment cmdArgs args

if not (runBuildScriptWithFsiArgsAt "" printDetails fsiArgs envVars) then Environment.ExitCode <- 1
else if printDetails then log "Ready."

()

//None of the new style args parsed, so revert to the old skool.
| Choice2Of2(ex) ->
if (cmdArgs.Length = 2 && paramIsHelp cmdArgs.[1]) || (cmdArgs.Length = 1 && List.length buildScripts = 0) then printUsage () else
match Boot.ParseCommandLine(cmdArgs) with
| None ->
let buildScriptArg = if cmdArgs.Length > 1 && cmdArgs.[1].EndsWith ".fsx" then cmdArgs.[1] else Seq.head buildScripts
let fakeArgs = cmdArgs |> Array.filter (fun x -> x.StartsWith "-d:" = false)
let fsiArgs = cmdArgs |> Array.filter (fun x -> x.StartsWith "-d:") |> Array.toList
let args = CommandlineParams.parseArgs (fakeArgs |> Seq.filter ((<>) buildScriptArg) |> Seq.filter ((<>) "details"))

traceStartBuild()
let printDetails = containsParam "details" cmdArgs
if printDetails then
printEnvironment cmdArgs args
if not (runBuildScript printDetails buildScriptArg fsiArgs args) then
Environment.ExitCode <- 1
else
if printDetails then log "Ready."
| Some handler ->
handler.Interact()
with
| exn ->
if exn.InnerException <> null then
sprintf "Build failed.\nError:\n%s\nInnerException:\n%s" exn.Message exn.InnerException.Message
|> traceError
printUsage()
else
sprintf "Build failed.\nError:\n%s" exn.Message
|> traceError
printUsage()

sendTeamCityError exn.Message
Environment.ExitCode <- 1
Expand Down
4 changes: 4 additions & 0 deletions src/app/FAKE/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="UnionArgParser" version="0.6.4" targetFramework="net40" />
</packages>
6 changes: 5 additions & 1 deletion src/app/FakeLib/Boot.fs
Original file line number Diff line number Diff line change
Expand Up @@ -402,13 +402,17 @@ module private Implementation =
| Some RunOnly -> DoRunOnly env
| None -> DoHelp env

/// Creates the CommandHandler from the
let HandlerForArgs args = { Run = RunCommandLine args }

/// Detects boot-specific commands.
let ParseCommandLine (args: seq<string>) : option<CommandHandler> =
match Seq.toList args with
| "boot" :: xs
| _ :: "boot" :: xs -> Some { Run = RunCommandLine xs }
| _ :: "boot" :: xs -> Some (HandlerForArgs xs)
| _ -> None


/// The main function intended to be executed in the BOOT phase of
/// boostrapping scripts.
let Prepare (config: Config) : unit =
Expand Down
Loading

0 comments on commit dd42061

Please sign in to comment.