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

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

Full name: todowatch.generateDocs
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
  ...

Full name: System.Console
System.Console.ReadLine() : string
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val runTests : unit -> 'a

Full name: todowatch.runTests
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
namespace System.IO
Multiple items
type IOException =
  inherit SystemException
  new : unit -> IOException + 3 overloads

Full name: System.IO.IOException

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

Full name: System.String

--------------------
System.String(value: nativeptr<char>) : unit
System.String(value: nativeptr<sbyte>) : unit
System.String(value: char []) : unit
System.String(c: char, count: int) : unit
System.String(value: nativeptr<char>, startIndex: int, length: int) : unit
System.String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
System.String(value: char [], startIndex: int, length: int) : unit
System.String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: System.Text.Encoding) : unit
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

Full name: System.Collections.Generic.List<_>

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