Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for dotnet local tools #2399

Merged
merged 24 commits into from
Oct 20, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5eb6251
Provide a more general mechanism for substituting a dotnet tool for a…
SteveGilham Sep 29, 2019
7ef4c24
Sketch the "withDotNet" approach, pivoting to Fake.DotNet.Cli.
SteveGilham Oct 6, 2019
130587e
Bring the full functionality of `DotNet.exec` into play
SteveGilham Oct 15, 2019
ffe3369
Merge remote-tracking branch 'upstream/release/next' into develop/iss…
SteveGilham Oct 15, 2019
225b9b5
Use DotNet ToolType for Paket
Tarmil Oct 17, 2019
e675756
Merge remote-tracking branch 'Tarmil/FAKE/issue-2398' into develop/is…
SteveGilham Oct 18, 2019
ebac051
Fix legacy build
SteveGilham Oct 18, 2019
832bbb4
Make the new functions and types part of the Fake.DotNet namespace
SteveGilham Oct 19, 2019
36d4be4
Redesign some internals to make the Dotnet Tool stuff similar to 'wit…
matthid Oct 19, 2019
49c3804
Merge branch 'develop/issue-2398' of https://github.com/SteveGilham/F…
matthid Oct 19, 2019
9c13b4e
Merge pull request #1 from fsharp/SteveGilham-develop/issue-2398
SteveGilham Oct 19, 2019
a2df6ec
Add some docs to explain the situation
matthid Oct 19, 2019
7dc76d2
Merge branch 'release/next' of github.com:fsharp/FAKE into SteveGilha…
matthid Oct 19, 2019
05d9d6a
This should work for all tools and modes, fix tests
matthid Oct 19, 2019
b400a93
Add some docs for the new concept
matthid Oct 19, 2019
c7eb52d
add redirect if not redirected function, but add a warning
matthid Oct 19, 2019
f34a8ed
Restore original trace behaviour
SteveGilham Oct 20, 2019
d9cd497
Try to fix failing tests by waiting for output to be read completely.
matthid Oct 20, 2019
0b364a0
Merge branch 'develop/issue-2398' of https://github.com/SteveGilham/F…
matthid Oct 20, 2019
1f31618
use sh on non-windows.
matthid Oct 20, 2019
79e35e8
fix escaping
matthid Oct 20, 2019
6d69f6d
Add more options to debug #2401
matthid Oct 20, 2019
a8c20e0
add docs around local tools
matthid Oct 20, 2019
9337b69
fix build and update release notes
matthid Oct 20, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 32 additions & 26 deletions src/app/Fake.Core.Process/CreateProcess.fs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,17 @@ type ProcessResult<'a> = { Result : 'a; ExitCode : int }
/// Handle for creating a process and returning potential results.
type CreateProcess<'TRes> =
internal {
Command : Command
InternalCommand : Command
TraceCommand : bool
WorkingDirectory : string option
Environment : EnvMap option
InternalWorkingDirectory : string option
InternalEnvironment : EnvMap option
Streams : StreamSpecs
Hook : IProcessHook<'TRes>
}
member x.CommandLine = x.Command.CommandLine
member x.CommandLine = x.InternalCommand.CommandLine
member x.Command = x.InternalCommand
member x.WorkingDirectory = x.InternalWorkingDirectory
member x.Environment = x.InternalEnvironment


/// Module for creating and modifying CreateProcess<'TRes> instances.
Expand Down Expand Up @@ -102,11 +105,11 @@ module CreateProcess =
/// |> Proc.run
/// |> ignore
let fromCommand command =
{ Command = command
WorkingDirectory = None
{ InternalCommand = command
InternalWorkingDirectory = None
TraceCommand = true
// Problem: Environment not allowed when using ShellCommand
Environment = None
InternalEnvironment = None
Streams =
{ // Problem: Redirection not allowed when using ShellCommand
StandardInput = Inherit
Expand Down Expand Up @@ -161,10 +164,10 @@ module CreateProcess =

/// Create a CreateProcess from the given `ProcessStartInfo`
let ofStartInfo (p:System.Diagnostics.ProcessStartInfo) =
{ Command = if p.UseShellExecute then ShellCommand p.FileName else RawCommand(p.FileName, Arguments.OfStartInfo p.Arguments)
{ InternalCommand = if p.UseShellExecute then ShellCommand p.FileName else RawCommand(p.FileName, Arguments.OfStartInfo p.Arguments)
TraceCommand = true
WorkingDirectory = if System.String.IsNullOrWhiteSpace p.WorkingDirectory then None else Some p.WorkingDirectory
Environment =
InternalWorkingDirectory = if System.String.IsNullOrWhiteSpace p.WorkingDirectory then None else Some p.WorkingDirectory
InternalEnvironment =
p.Environment
|> Seq.map (fun kv -> kv.Key, kv.Value)
|> EnvMap.ofSeq
Expand Down Expand Up @@ -205,7 +208,7 @@ module CreateProcess =
/// Set the working directory of the new process.
let withWorkingDirectory workDir (c:CreateProcess<_>)=
{ c with
WorkingDirectory = Some workDir }
InternalWorkingDirectory = Some workDir }

/// Disable the default trace of started processes.
let disableTraceCommand (c:CreateProcess<_>)=
Expand All @@ -215,12 +218,12 @@ module CreateProcess =
/// Set the command to the given one.
let withCommand command (c:CreateProcess<_>)=
{ c with
Command = command }
InternalCommand = command }

/// Replace the file-path
let replaceFilePath newFilePath (c:CreateProcess<_>)=
{ c with
Command =
InternalCommand =
match c.Command with
| ShellCommand s -> failwith "Expected RawCommand"
| RawCommand (_, c) -> RawCommand(newFilePath, c) }
Expand All @@ -232,12 +235,13 @@ module CreateProcess =


let internal withHook h (c:CreateProcess<_>) =
{ Command = c.Command
{ InternalCommand = c.Command
TraceCommand = c.TraceCommand
WorkingDirectory = c.WorkingDirectory
Environment = c.Environment
InternalWorkingDirectory = c.InternalWorkingDirectory
InternalEnvironment = c.InternalEnvironment
Streams = c.Streams
Hook = h }

let internal withHookImpl h (c:CreateProcess<_>) =
c
|> withHook (h |> ProcessHook.toRawHook)
Expand All @@ -260,6 +264,7 @@ module CreateProcess =
if not (isNull x.State1) then
x.State1.Dispose()
x.State2.Dispose()

let internal hookAppendFuncs prepareState prepareStreams onStart onResult (c:IProcessHook<'TRes>) =
{ new IProcessHookImpl<_, _> with
member __.PrepareState () =
Expand Down Expand Up @@ -314,38 +319,39 @@ module CreateProcess =
/// Execute the given function before the process is started
let addOnSetup f (c:CreateProcess<_>) =
c
|> appendSimpleFuncs
|> appendSimpleFuncs
(fun _ -> f())
(fun state p -> ())
(fun prev state exitCode -> prev)
(fun _ -> ())
ignore

/// Execute the given function when the process is cleaned up.
let addOnFinally f (c:CreateProcess<_>) =
c
|> appendSimpleFuncs
(fun _ -> ())
ignore
(fun state p -> ())
(fun prev state exitCode -> prev)
(fun _ -> f ())
/// Execute the given function right after the process is started.
let addOnStarted f (c:CreateProcess<_>) =
c
|> appendSimpleFuncs
(fun _ -> ())
ignore
(fun state p -> f ())
(fun prev state exitCode -> prev)
(fun _ -> ())
ignore

/// Sets the given environment variables
let withEnvironment (env: (string * string) list) (c:CreateProcess<_>)=
{ c with
Environment = Some (EnvMap.ofSeq env) }
InternalEnvironment = Some (EnvMap.ofSeq env) }

/// Sets the given environment map.
let withEnvironmentMap (env: EnvMap) (c:CreateProcess<_>)=
{ c with
Environment = Some env }
InternalEnvironment = Some env }

/// Retrieve the current environment map.
let getEnvironmentMap (c:CreateProcess<_>)=
match c.Environment with
Expand All @@ -355,7 +361,7 @@ module CreateProcess =
/// Set the given environment variable.
let setEnvironmentVariable envKey (envVar:string) (c:CreateProcess<_>) =
{ c with
Environment =
InternalEnvironment =
getEnvironmentMap c
|> IMap.add envKey envVar
|> Some }
Expand Down Expand Up @@ -383,14 +389,14 @@ module CreateProcess =
let map f c =
c
|> appendSimpleFuncs
(fun _ -> ())
ignore
(fun state p -> ())
(fun prev state exitCode ->
async {
let! old = prev
return f old
})
(fun _ -> ())
ignore

/// Map only the result object and leave the exit code in the result type.
let mapResult f (c:CreateProcess<ProcessResult<_>>) =
Expand Down
2 changes: 1 addition & 1 deletion src/app/Fake.Core.Process/CreateProcessExt.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module CreateProcessExt =
match Environment.isWindows, c.Command, Process.monoPath with
| false, RawCommand(file, args), Some monoPath when file.ToLowerInvariant().EndsWith(".exe") ->
{ c with
Command = RawCommand(monoPath, Arguments.withPrefix ["--debug"; file] args) }
InternalCommand = RawCommand(monoPath, Arguments.withPrefix ["--debug"; file] args) }
| false, RawCommand(file, args), _ when file.ToLowerInvariant().EndsWith(".exe") ->
failwithf "trying to start a .NET process on a non-windows platform, but mono could not be found. Try to set the MONO environment variable or add mono to the PATH."
| _ -> c
70 changes: 40 additions & 30 deletions src/app/Fake.Core.Process/Process.fs
Original file line number Diff line number Diff line change
Expand Up @@ -429,37 +429,47 @@ module Process =
let hook = c.Hook

let state = hook.PrepareState ()
let! exitCode =
let mutable stateNeedsDispose = true
try
let! exitCode =
async {
let procRaw =
{ Command = c.InternalCommand
TraceCommand = c.TraceCommand
WorkingDirectory = c.InternalWorkingDirectory
Environment = c.InternalEnvironment
Streams = c.Streams
OutputHook =
{ new IRawProcessHook with
member x.Prepare streams = hook.PrepareStreams(state, streams)
member x.OnStart (p) = hook.ProcessStarted (state, p) } }

let! e = processStarter.Start(procRaw)
return e
}

let output =
hook.RetrieveResult (state, exitCode)
|> Async.StartImmediateAsTask
async {
let procRaw =
{ Command = c.Command
TraceCommand = c.TraceCommand
WorkingDirectory = c.WorkingDirectory
Environment = c.Environment
Streams = c.Streams
OutputHook =
{ new IRawProcessHook with
member x.Prepare streams = hook.PrepareStreams(state, streams)
member x.OnStart (p) = hook.ProcessStarted (state, p) } }

let! e = processStarter.Start(procRaw)
return e
}

let output =
hook.RetrieveResult (state, exitCode)
|> Async.StartImmediateAsTask
async {
try
let all = System.Threading.Tasks.Task.WhenAll([exitCode :> System.Threading.Tasks.Task; output:> System.Threading.Tasks.Task])
let! streams =
all.ContinueWith (new System.Func<System.Threading.Tasks.Task, unit> (fun t -> ()))
|> Async.AwaitTaskWithoutAggregate
if not (isNull state) then
state.Dispose()
with e -> Trace.traceFAKE "Error in state dispose: %O" e }
|> Async.Start
return { Result = output; Raw = exitCode }
let mutable needDispose = true
try
try
let all = System.Threading.Tasks.Task.WhenAll([exitCode :> System.Threading.Tasks.Task; output:> System.Threading.Tasks.Task])
let! streams =
all.ContinueWith (new System.Func<System.Threading.Tasks.Task, unit> (fun t -> ()))
|> Async.AwaitTaskWithoutAggregate
needDispose <- false
if not (isNull state) then
state.Dispose()
with e -> Trace.traceFAKE "Error in state dispose: %O" e
finally
if needDispose && not (isNull state) then state.Dispose() }
|> Async.Start
stateNeedsDispose <- false
return { Result = output; Raw = exitCode }
finally
if stateNeedsDispose then state.Dispose()
}
// Immediate makes sure we set the ref cell before we return the task...
|> Async.StartImmediateAsTask
Expand Down
12 changes: 6 additions & 6 deletions src/app/Fake.Core.Process/ProcessUtils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ module ProcessUtils =

/// Searches the given directories for all occurrences of the given file name
/// [omit]
let private findFilesInternal dirs file =
let files =
let private findFilesInternal dirs file =
let files =
dirs
|> Seq.choose (fun (path : string) ->
let replacedPath =
let replacedPath =
path
|> String.replace "[ProgramFiles]" Environment.ProgramFiles
|> String.replace "[ProgramFilesX86]" Environment.ProgramFilesX86
Expand Down Expand Up @@ -51,7 +51,7 @@ module ProcessUtils =
else None

/// Searches the given directories for the given file, failing if not found. Considers PATHEXT on Windows.
let findFile dirs tool =
let findFile dirs tool =
match tryFindFile dirs tool with
| Some found -> found
| None -> failwithf "%s not found in %A." tool dirs
Expand Down Expand Up @@ -88,7 +88,7 @@ module ProcessUtils =

/// Tries to find the tool via Env-Var. If no path has the right tool we are trying the PATH system variable. Considers PATHEXT on Windows.
/// [omit]
let findPath fallbackValue tool =
let findPath fallbackValue tool =
match tryFindPath fallbackValue tool with
| Some file -> file
| None -> tool
Expand Down Expand Up @@ -122,7 +122,7 @@ module ProcessUtils =
|> Seq.append envDir
findFiles dirs tool
|> Seq.tryHead

/// Like tryFindLocalTool but returns the `tool` string if nothing is found (will probably error later, but this function is OK to be used for fake default values.
let findLocalTool envVar tool recursiveDirs =
match tryFindLocalTool envVar tool recursiveDirs with
Expand Down
20 changes: 18 additions & 2 deletions src/app/Fake.Core.Process/RawProc.fs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@ module EnvMap =
then ImmutableDictionary.Empty.WithComparers(StringComparer.OrdinalIgnoreCase) :> EnvMap
else IMap.empty

let ofSeq l =
let ofSeq l : EnvMap =
empty.AddRange(l |> Seq.map (fun (k, v) -> KeyValuePair<_,_>(k, v)))

let create() =
ofSeq (Environment.environVars ())

let addRange (l) (e:EnvMap) : EnvMap=
e.AddRange(l)
//|> IMap.add defaultEnvVar defaultEnvVar

let ofMap (l) : EnvMap =
create()
|> addRange l

/// The type of command to execute
type Command =
Expand All @@ -53,6 +59,16 @@ type Command =
| ShellCommand s -> s
| RawCommand (f, arg) -> sprintf "%s %s" f arg.ToWindowsCommandLine

member x.Arguments =
match x with
| ShellCommand _ -> raise <| NotImplementedException "Cannot retrieve Arguments for ShellCommand"
| RawCommand (_, arg) -> arg

member x.Executable =
match x with
| ShellCommand _ -> raise <| NotImplementedException "Cannot retrieve Executable for ShellCommand"
| RawCommand (f, _) -> f

/// Represents basically an "out" parameter, allows to retrieve a value after a certain point in time.
/// Used to retrieve "pipes"
type DataRef<'T> =
Expand Down Expand Up @@ -112,7 +128,7 @@ type internal RawCreateProcess =
OutputHook : IRawProcessHook
}
member internal x.ToStartInfo =
let p = new System.Diagnostics.ProcessStartInfo()
let p = System.Diagnostics.ProcessStartInfo()
match x.Command with
| ShellCommand s ->
p.UseShellExecute <- true
Expand Down
6 changes: 4 additions & 2 deletions src/app/Fake.Core.Process/VisibleTo.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@

namespace System
open System.Runtime.CompilerServices

[<assembly: InternalsVisibleTo("Fake.Core.IntegrationTests")>]
[<assembly: InternalsVisibleTo("Fake.Core.UnitTests")>]
do ()

// For access to CreateProcess<'TRes>.Command
[<assembly: InternalsVisibleTo("Fake.DotNet.Cli")>]
do ()
Loading