Append / to directories in Glob results

This makes it easy for users of Glob to detect whether the match is a
file or directory. Doing the check at this level means that the filelist
file used as a dependency will be updated if a directory is replaced
with a file of the same name, or vice versa.

Change-Id: I79ebba39327218bcdcf50b393498306119de9d6c
diff --git a/context.go b/context.go
index 2051365..ebe4e12 100644
--- a/context.go
+++ b/context.go
@@ -1017,6 +1017,12 @@
 		}
 
 		for _, foundBlueprints := range matches {
+			if strings.HasSuffix(foundBlueprints, "/") {
+				errs = append(errs, &BlueprintError{
+					Err: fmt.Errorf("%q: is a directory", foundBlueprints),
+					Pos: buildPos,
+				})
+			}
 			blueprints = append(blueprints, foundBlueprints)
 		}
 	}
@@ -1053,6 +1059,12 @@
 		}
 
 		for _, subBlueprints := range matches {
+			if strings.HasSuffix(subBlueprints, "/") {
+				errs = append(errs, &BlueprintError{
+					Err: fmt.Errorf("%q: is a directory", subBlueprints),
+					Pos: subdirsPos,
+				})
+			}
 			blueprints = append(blueprints, subBlueprints)
 		}
 	}
diff --git a/module_ctx.go b/module_ctx.go
index 5e0faa9..fe6c12b 100644
--- a/module_ctx.go
+++ b/module_ctx.go
@@ -131,10 +131,12 @@
 	PropertyErrorf(property, fmt string, args ...interface{})
 	Failed() bool
 
-	// GlobWithDeps returns a list of files that match the specified pattern but do not match any
-	// of the patterns in excludes.  It also adds efficient dependencies to rerun the primary
-	// builder whenever a file matching the pattern as added or removed, without rerunning if a
-	// file that does not match the pattern is added to a searched directory.
+	// GlobWithDeps returns a list of files and directories that match the
+	// specified pattern but do not match any of the patterns in excludes.
+	// Any directories will have a '/' suffix.  It also adds efficient
+	// dependencies to rerun the primary builder whenever a file matching
+	// the pattern as added or removed, without rerunning if a file that
+	// does not match the pattern is added to a searched directory.
 	GlobWithDeps(pattern string, excludes []string) ([]string, error)
 
 	Fs() pathtools.FileSystem
diff --git a/pathtools/glob.go b/pathtools/glob.go
index 367d219..f7b32d7 100644
--- a/pathtools/glob.go
+++ b/pathtools/glob.go
@@ -28,12 +28,13 @@
 var GlobMultipleRecursiveErr = errors.New("pattern contains multiple **")
 var GlobLastRecursiveErr = errors.New("pattern ** as last path element")
 
-// Glob returns the list of files that match the given pattern but do not match
-// the given exclude patterns, along with the list of directories and other
-// dependencies that were searched to construct the file list.  The supported
-// glob and exclude patterns are equivalent to filepath.Glob, with an extension
-// that recursive glob (** matching zero or more complete path entries) is
-// supported.  Glob also returns a list of directories that were searched.
+// Glob returns the list of files and directories that match the given pattern
+// but do not match the given exclude patterns, along with the list of
+// directories and other dependencies that were searched to construct the file
+// list.  The supported glob and exclude patterns are equivalent to
+// filepath.Glob, with an extension that recursive glob (** matching zero or
+// more complete path entries) is supported. Any directories in the matches
+// list will have a '/' suffix.
 //
 // In general ModuleContext.GlobWithDeps or SingletonContext.GlobWithDeps
 // should be used instead, as they will automatically set up dependencies
@@ -71,6 +72,14 @@
 		deps = append(deps, matches...)
 	}
 
+	for i, match := range matches {
+		if isDir, err := fs.IsDir(match); err != nil {
+			return nil, nil, fmt.Errorf("IsDir(%s): %s", match, err.Error())
+		} else if isDir {
+			matches[i] = match + "/"
+		}
+	}
+
 	return matches, deps, nil
 }
 
@@ -325,12 +334,14 @@
 	return false
 }
 
-// GlobWithDepFile finds all files that match glob.  It compares the list of files
-// against the contents of fileListFile, and rewrites fileListFile if it has changed.  It also
-// writes all of the the directories it traversed as a depenencies on fileListFile to depFile.
+// GlobWithDepFile finds all files and directories that match glob.  Directories
+// will have a trailing '/'.  It compares the list of matches against the
+// contents of fileListFile, and rewrites fileListFile if it has changed.  It
+// also writes all of the the directories it traversed as dependencies on
+// fileListFile to depFile.
 //
-// The format of glob is either path/*.ext for a single directory glob, or path/**/*.ext
-// for a recursive glob.
+// The format of glob is either path/*.ext for a single directory glob, or
+// path/**/*.ext for a recursive glob.
 //
 // Returns a list of file paths, and an error.
 //
diff --git a/pathtools/glob_test.go b/pathtools/glob_test.go
index 85030a6..1d17e10 100644
--- a/pathtools/glob_test.go
+++ b/pathtools/glob_test.go
@@ -36,7 +36,7 @@
 	// Current directory tests
 	{
 		pattern: "*",
-		matches: []string{"a", "b", "c", "d.ext", "e.ext"},
+		matches: []string{"a/", "b/", "c/", "d.ext", "e.ext"},
 		deps:    []string{"."},
 	},
 	{
@@ -46,7 +46,7 @@
 	},
 	{
 		pattern: "*/a",
-		matches: []string{"a/a", "b/a"},
+		matches: []string{"a/a/", "b/a"},
 		deps:    []string{".", "a", "b", "c"},
 	},
 	{
@@ -63,7 +63,7 @@
 	// ./ directory tests
 	{
 		pattern: "./*",
-		matches: []string{"a", "b", "c", "d.ext", "e.ext"},
+		matches: []string{"a/", "b/", "c/", "d.ext", "e.ext"},
 		deps:    []string{"."},
 	},
 	{
@@ -73,12 +73,12 @@
 	},
 	{
 		pattern: "./*/a",
-		matches: []string{"a/a", "b/a"},
+		matches: []string{"a/a/", "b/a"},
 		deps:    []string{".", "a", "b", "c"},
 	},
 	{
 		pattern: "./[ac]/a",
-		matches: []string{"a/a"},
+		matches: []string{"a/a/"},
 		deps:    []string{".", "a", "c"},
 	},
 
@@ -112,12 +112,12 @@
 	// no-wild tests
 	{
 		pattern: "a",
-		matches: []string{"a"},
+		matches: []string{"a/"},
 		deps:    []string{"a"},
 	},
 	{
 		pattern: "a/a",
-		matches: []string{"a/a"},
+		matches: []string{"a/a/"},
 		deps:    []string{"a/a"},
 	},
 
@@ -136,17 +136,17 @@
 	// recursive tests
 	{
 		pattern: "**/a",
-		matches: []string{"a", "a/a", "a/a/a", "b/a"},
+		matches: []string{"a/", "a/a/", "a/a/a", "b/a"},
 		deps:    []string{".", "a", "a/a", "a/b", "b", "c", "c/f", "c/g", "c/h"},
 	},
 	{
 		pattern: "a/**/a",
-		matches: []string{"a/a", "a/a/a"},
+		matches: []string{"a/a/", "a/a/a"},
 		deps:    []string{"a", "a/a", "a/b"},
 	},
 	{
 		pattern: "a/**/*",
-		matches: []string{"a/a", "a/b", "a/a/a", "a/b/b"},
+		matches: []string{"a/a/", "a/b/", "a/a/a", "a/b/b"},
 		deps:    []string{"a", "a/a", "a/b"},
 	},
 
@@ -208,19 +208,19 @@
 	{
 		pattern:  "*/*",
 		excludes: []string{"a/b"},
-		matches:  []string{"a/a", "b/a", "c/c", "c/f", "c/g", "c/h"},
+		matches:  []string{"a/a/", "b/a", "c/c", "c/f/", "c/g/", "c/h/"},
 		deps:     []string{".", "a", "b", "c"},
 	},
 	{
 		pattern:  "*/*",
 		excludes: []string{"a/b", "c/c"},
-		matches:  []string{"a/a", "b/a", "c/f", "c/g", "c/h"},
+		matches:  []string{"a/a/", "b/a", "c/f/", "c/g/", "c/h/"},
 		deps:     []string{".", "a", "b", "c"},
 	},
 	{
 		pattern:  "*/*",
 		excludes: []string{"c/*", "*/a"},
-		matches:  []string{"a/b"},
+		matches:  []string{"a/b/"},
 		deps:     []string{".", "a", "b", "c"},
 	},
 	{
@@ -268,13 +268,13 @@
 	{
 		pattern:  "*/*",
 		excludes: []string{"**/b"},
-		matches:  []string{"a/a", "b/a", "c/c", "c/f", "c/g", "c/h"},
+		matches:  []string{"a/a/", "b/a", "c/c", "c/f/", "c/g/", "c/h/"},
 		deps:     []string{".", "a", "b", "c"},
 	},
 	{
 		pattern:  "*/*",
 		excludes: []string{"a/**/*"},
-		matches:  []string{"b/a", "c/c", "c/f", "c/g", "c/h"},
+		matches:  []string{"b/a", "c/c", "c/f/", "c/g/", "c/h/"},
 		deps:     []string{".", "a", "b", "c"},
 	},
 	{
@@ -439,7 +439,7 @@
 	},
 	{
 		pattern: ".t*",
-		matches: []string{".test", ".testing"},
+		matches: []string{".test/", ".testing"},
 		deps:    []string{"."},
 	},
 }
diff --git a/singleton_ctx.go b/singleton_ctx.go
index 28fecf0..bbfce00 100644
--- a/singleton_ctx.go
+++ b/singleton_ctx.go
@@ -65,10 +65,12 @@
 
 	AddNinjaFileDeps(deps ...string)
 
-	// GlobWithDeps returns a list of files that match the specified pattern but do not match any
-	// of the patterns in excludes.  It also adds efficient dependencies to rerun the primary
-	// builder whenever a file matching the pattern as added or removed, without rerunning if a
-	// file that does not match the pattern is added to a searched directory.
+	// GlobWithDeps returns a list of files and directories that match the
+	// specified pattern but do not match any of the patterns in excludes.
+	// Any directories will have a '/' suffix. It also adds efficient
+	// dependencies to rerun the primary builder whenever a file matching
+	// the pattern as added or removed, without rerunning if a file that
+	// does not match the pattern is added to a searched directory.
 	GlobWithDeps(pattern string, excludes []string) ([]string, error)
 
 	Fs() pathtools.FileSystem