diff --git a/src/app/FAKE/Cli.fs b/src/app/FAKE/Cli.fs index b7a0faa10f6..9f2fa1af9e3 100644 --- a/src/app/FAKE/Cli.fs +++ b/src/app/FAKE/Cli.fs @@ -30,13 +30,72 @@ type FakeArg = | Single_Target -> "Runs only the specified target and not the dependencies." | NoCache -> "Disables caching of compiled script" -/// Return the parsed FAKE args or the parse exception. -let parsedArgsOrEx args = - try - let args = args |> Seq.skip 1 |> Array.ofSeq - let parser = ArgumentParser.Create() - Choice1Of2(parser.Parse(args)) - with | ex -> Choice2Of2(ex) +open Microsoft.FSharp.Reflection + +/// Convert old style args to the Argu style. +/// NOTE: Will stop mapping if encounters any switch other than -d:. -d is mapped as was supported in the old style. +let experimentalTryMapOldArgsWip (args : string list) = + // Extract the old style fsi options first. + let isOldStyleFsiOptionArg (arg : string) = arg.StartsWith("-d:", StringComparison.InvariantCultureIgnoreCase) + let oldStyleFsiOptions = args |> List.filter isOldStyleFsiOptionArg + let nonFsiOptionArgs = args |> List.filter (fun arg -> not (isOldStyleFsiOptionArg arg)) + + // Function to map the other args, if we can. + let rec inner (args : string list) accFake = + match args with + | arg::args -> + if arg.StartsWith("-") then + // Stop processing, as we can't reliably map Argu switches without replicating the parse logic! + let unmappedArgs = arg::args + (List.rev accFake) @ unmappedArgs + elif arg.Contains("=") then + let parts = arg.Split([| '=' |]) + if parts.[0].Equals("logfile", StringComparison.InvariantCultureIgnoreCase) then + inner args (parts.[1] :: "--logfile" :: accFake) + else + inner args (parts.[1] :: "--envvar" :: accFake) + else + inner args (arg :: "--envflag" :: accFake) + | [] -> List.rev accFake + + let mappedArgs = inner nonFsiOptionArgs [] + let fsiArgs = + if not oldStyleFsiOptions.IsEmpty then + mappedArgs @ [ yield "--fsiargs"; yield! oldStyleFsiOptions] + else [] + mappedArgs @ fsiArgs + +/// List of all the argu switches and alt switches. +let arguSwitches = + lazy(let switchUnionCases = FSharpType.GetUnionCases(typeof) + let arguSwitchesFromUnionCase (unionCase : UnionCaseInfo) = + [ yield "--" + unionCase.Name.Replace("_", "-") + yield! unionCase.GetCustomAttributes(typeof) + |> Seq.cast + |> Seq.map (fun altAttr -> altAttr.Names) + |> Seq.concat + ] + switchUnionCases |> Seq.map arguSwitchesFromUnionCase |> Seq.concat) + +/// Checks to see if arg starts with any of the argu or alt argu switches. +let isArguSwitch (arg : string) = + arguSwitches.Value |> Seq.exists (fun arguSwitch -> arg.StartsWith(arguSwitch, StringComparison.InvariantCultureIgnoreCase)) + +/// Represents a parse result, with fallback context. +type ArguParseResult = + | OK of ParseResults + | FailWithArguSwitches of Exception + | FailWithoutArguSwitches of Exception + +/// Return the parsed Argu FAKE args or the parse exception with context. +let tryParseArguArgs args = + let args = args |> Seq.skip 1 |> Array.ofSeq + let parser = ArgumentParser.Create() + try OK(parser.Parse(args)) + with + | ex -> + if args |> Seq.map isArguSwitch |> Seq.exists id then FailWithArguSwitches ex + else FailWithoutArguSwitches ex /// Prints the FAKE argument usage. let printUsage () = diff --git a/src/app/FAKE/Program.fs b/src/app/FAKE/Program.fs index 760c6040641..b0eaa9b945d 100644 --- a/src/app/FAKE/Program.fs +++ b/src/app/FAKE/Program.fs @@ -45,10 +45,10 @@ try let args = Cli.parsePositionalArgs cmdArgs - match Cli.parsedArgsOrEx args.Rest with + match Cli.tryParseArguArgs args.Rest with //We have new style help args! - | Choice1Of2(fakeArgs) -> + | Cli.ArguParseResult.OK(fakeArgs) -> //Break to allow a debugger to be attached here if fakeArgs.Contains <@ Cli.Break @> then @@ -120,13 +120,13 @@ try () - //None of the new style args parsed, so revert to the old skool. - | Choice2Of2(ex) -> + // Failed to parse args AND Argu switches detected, so error. + | Cli.ArguParseResult.FailWithArguSwitches(ex) -> + traceError "Failed to parse command line args. IMPORTANT: FAKE supports an 'old' and 'new' CLI. A new style switch has been detected in your arguments, but the new style parse is failing. Please see the error and usage help below. " + traceException ex - // #1082 print a warning as we've been invoked with invalid OR old-style args. - // traceImportant "Error parsing command line arguments. You have a mistake in your args, or are using the pre-2.1.8 argument style:" - // exceptionAndInnersToString ex |> traceImportant - // trace "Attempting to run with pre-version 2.18 argument style, for backwards compat." + // Failed to parse args but NO Argu switches detected, so try old format. + | Cli.ArguParseResult.FailWithoutArguSwitches(ex) -> if (cmdArgs.Length = 2 && paramIsHelp cmdArgs.[1]) || (cmdArgs.Length = 1 && List.length buildScripts = 0) then printUsage () else match Boot.ParseCommandLine(cmdArgs) with diff --git a/src/test/Test.FAKECore/CliSpecs.cs b/src/test/Test.FAKECore/CliSpecs.cs index 7eb98a8f189..ec4b6b907fb 100644 --- a/src/test/Test.FAKECore/CliSpecs.cs +++ b/src/test/Test.FAKECore/CliSpecs.cs @@ -27,4 +27,16 @@ public class when_parsing_the_fake_cli It should_parse_rest_of_args_when_no_positional = () => Cli.parsePositionalArgs(new string[] { "fake.exe", "-a", "-b", "-c" }).Rest.Length.ShouldEqual(4); } + + public class when_parsing_the_fake_cli_with_old_and_new_arg_style + { + It should_fail_with_argu_switches_when_mixed_usage = + () => Cli.tryParseArguArgs(new [] { "fake.exe", "script.fsx", "blahFlag", "blahVar=blahdyblah", "-st" }).IsFailWithArguSwitches.ShouldBeTrue(); + + It should_fail_without_argu_switches_when_bad_switch = + () => Cli.tryParseArguArgs(new [] { "fake.exe", "script.fsx", "--madeUpSwitch", "madeUpValue" }).IsFailWithoutArguSwitches.ShouldBeTrue(); + + It should_fail_with_valid_old_style_args = + () => Cli.tryParseArguArgs(new string[] { "fake.exe", "script.fsx", "clean" }).IsFailWithoutArguSwitches.ShouldBeTrue(); + } } diff --git a/src/test/Test.FAKECore/Test.FAKECore.csproj b/src/test/Test.FAKECore/Test.FAKECore.csproj index 1e40f8c225e..583b001657f 100644 --- a/src/test/Test.FAKECore/Test.FAKECore.csproj +++ b/src/test/Test.FAKECore/Test.FAKECore.csproj @@ -407,6 +407,26 @@ --> + + + + + ..\..\..\packages\Argu\lib\net35\Argu.dll + True + True + + + + + + + ..\..\..\packages\Argu\lib\net40\Argu.dll + True + True + + + + diff --git a/src/test/Test.FAKECore/paket.references b/src/test/Test.FAKECore/paket.references index 21a8efaee07..7aa724592ab 100644 --- a/src/test/Test.FAKECore/paket.references +++ b/src/test/Test.FAKECore/paket.references @@ -2,4 +2,5 @@ FSharp.Core Machine.Specifications.Should Mono.Web.Xdt Mono.Cecil -Nuget.Core \ No newline at end of file +Nuget.Core +Argu