Watching for file changes with "FAKE - F# Make"

INFO

This documentation is for FAKE.exe before version 5 (or the non-netcore version). The documentation for FAKE 5 can be found here

FAKE makes it easy to setup monitoring for filesystem changes. Using the standard glob patterns you can watch for changes, and automatically run a function or another target.

Using WatchChanges

Add a new target named "Watch" to your build:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let generateDocs() = 
    tracefn "Generating docs."
    
Target "GenerateDocs" (fun _ ->
    generateDocs()
)

Target "Watch" (fun _ ->
    use watcher = !! "docs/**/*.*" |> WatchChanges (fun changes -> 
        tracefn "%A" changes
        generateDocs()
    )

    System.Console.ReadLine() |> ignore //Needed to keep FAKE from exiting

    watcher.Dispose() // Use to stop the watch from elsewhere, ie another task.
)

Now run build.fsx and make some changes to the docs directory. They should be printed out to the console as they happen, and the GenerateDocs target should be rerun.

If you need to watch only a subset of the files, say you want to rerun tests as soon as the compiled DLLs change:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let runTests() =
    tracefn "Running tests." 
    
Target "RunTests" (fun _ ->
    runTests()
)

Target "Watch" (fun _ ->
    use watcher = !! "tests/**/bin/debug/*.dll" |> WatchChanges (fun changes -> 
        tracefn "%A" changes
        runTests()
    )

    System.Console.ReadLine() |> ignore //Needed to keep FAKE from exiting

    watcher.Dispose() // Use to stop the watch from elsewhere, ie another task.
)

Do note that FAKE will only ever run a target once within a session, so Run "RunTests" inside of WatchChanges would only run the RunTests target once.

Running on Linux or Mac OSX

WatchChanges requires additional care when running on Linux or Mac OSX. The following sections describe potential issues you may encounter.

Maximum Number of Files to Watch Exception

When running on Linux or Mac OSX, you should add the following export to your .bashrc or .bash_profile:

1: 
export MONO_MANAGED_WATCHER=false

If you don't add this, you may see the following exception when attempting to run the WatchChanges task:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
Running build failed.
Error:
System.IO.IOException: kqueue() FileSystemWatcher has reached the maximum nunmber of files to watch.
  at System.IO.KqueueMonitor.Add (System.String path, Boolean postEvents, System.Collections.Generic.List`1& fds) [0x00000] in <filename unknown>0
  at System.IO.KqueueMonitor.Scan (System.String path, Boolean postEvents, System.Collections.Generic.List`1& fds) [0x00000] in <filename unknown>0
  at System.IO.KqueueMonitor.Setup () [0x00000] in <filename unknown>0
  at System.IO.KqueueMonitor.DoMonitor () [0x00000] in <filename unknown>0

Watching Changes from Windows over Parallels

The Windows file watcher does not appear to be able to correctly identify changes that occur within a folder shared by Parallels between Mac OSX and Windows. If you want to run WatchChanges, you will need to run your FAKE script from Mac OSX.

At this time, only Parallels is known to have this problem, but you should assume that any other virtualization solutions will have the same problem. If you confirm a similar problem with other Linux distros or VM platforms, please update this document accordingly.

val generateDocs : unit -> 'a
namespace System
type Console =
  static member BackgroundColor : ConsoleColor with get, set
  static member Beep : unit -> unit + 1 overload
  static member BufferHeight : int with get, set
  static member BufferWidth : int with get, set
  static member CapsLock : bool
  static member Clear : unit -> unit
  static member CursorLeft : int with get, set
  static member CursorSize : int with get, set
  static member CursorTop : int with get, set
  static member CursorVisible : bool with get, set
  ...
System.Console.ReadLine() : string
val ignore : value:'T -> unit
val runTests : unit -> 'a
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
namespace System.IO
Multiple items
type IOException =
  inherit SystemException
  new : unit -> IOException + 3 overloads

--------------------
System.IO.IOException() : System.IO.IOException
System.IO.IOException(message: string) : System.IO.IOException
System.IO.IOException(message: string, hresult: int) : System.IO.IOException
System.IO.IOException(message: string, innerException: exn) : System.IO.IOException
Multiple items
type String =
  new : value:char[] -> string + 8 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool + 3 overloads
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 3 overloads
  member EnumerateRunes : unit -> StringRuneEnumerator
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  ...

--------------------
System.String(value: char []) : System.String
System.String(value: nativeptr<char>) : System.String
System.String(value: nativeptr<sbyte>) : System.String
System.String(value: System.ReadOnlySpan<char>) : System.String
System.String(c: char, count: int) : System.String
System.String(value: char [], startIndex: int, length: int) : System.String
System.String(value: nativeptr<char>, startIndex: int, length: int) : System.String
System.String(value: nativeptr<sbyte>, startIndex: int, length: int) : System.String
System.String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: System.Text.Encoding) : System.String
namespace System.Collections
namespace System.Collections.Generic
Multiple items
type List<'T> =
  new : unit -> List<'T> + 2 overloads
  member Add : item:'T -> unit
  member AddRange : collection:IEnumerable<'T> -> unit
  member AsReadOnly : unit -> ReadOnlyCollection<'T>
  member BinarySearch : item:'T -> int + 2 overloads
  member Capacity : int with get, set
  member Clear : unit -> unit
  member Contains : item:'T -> bool
  member ConvertAll<'TOutput> : converter:Converter<'T, 'TOutput> -> List<'TOutput>
  member CopyTo : array:'T[] -> unit + 2 overloads
  ...
  nested type Enumerator

--------------------
System.Collections.Generic.List() : System.Collections.Generic.List<'T>
System.Collections.Generic.List(capacity: int) : System.Collections.Generic.List<'T>
System.Collections.Generic.List(collection: System.Collections.Generic.IEnumerable<'T>) : System.Collections.Generic.List<'T>