Add support for arbitrary globs in subdirs

Change-Id: I874535483266622213dbcfe3c84875f41a136b9f
diff --git a/Blueprints b/Blueprints
index 7b0d22a..c254136 100644
--- a/Blueprints
+++ b/Blueprints
@@ -2,6 +2,7 @@
     name = "blueprint",
     deps = [
         "blueprint-parser",
+        "blueprint-pathtools",
         "blueprint-proptools",
     ],
     pkgPath = "github.com/google/blueprint",
diff --git a/build.ninja.in b/build.ninja.in
index 073ed7f..ab32834 100644
--- a/build.ninja.in
+++ b/build.ninja.in
@@ -65,8 +65,9 @@
         ${g.bootstrap.srcDir}/singleton_ctx.go ${g.bootstrap.srcDir}/unpack.go $
         | ${g.bootstrap.gcCmd} $
         .bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+        .bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
         .bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a
-    incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-proptools/pkg
+    incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-proptools/pkg
     pkgPath = github.com/google/blueprint
 default .bootstrap/blueprint/pkg/github.com/google/blueprint.a
 
@@ -75,7 +76,7 @@
 # Variant:
 # Type:    bootstrap_go_package
 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:55:1
+# Defined: Blueprints:56:1
 
 build $
         .bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
@@ -85,11 +86,11 @@
         ${g.bootstrap.srcDir}/bootstrap/config.go $
         ${g.bootstrap.srcDir}/bootstrap/doc.go | ${g.bootstrap.gcCmd} $
         .bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+        .bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
         .bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
         .bootstrap/blueprint/pkg/github.com/google/blueprint.a $
-        .bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
-        .bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a
-    incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-pathtools/pkg
+        .bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a
+    incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg
     pkgPath = github.com/google/blueprint/bootstrap
 default $
         .bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a
@@ -99,7 +100,7 @@
 # Variant:
 # Type:    bootstrap_go_package
 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:34:1
+# Defined: Blueprints:35:1
 
 build .bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
         : g.bootstrap.gc ${g.bootstrap.srcDir}/deptools/depfile.go | $
@@ -113,7 +114,7 @@
 # Variant:
 # Type:    bootstrap_go_package
 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:23:1
+# Defined: Blueprints:24:1
 
 build .bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a: $
         g.bootstrap.gc ${g.bootstrap.srcDir}/parser/modify.go $
@@ -128,7 +129,7 @@
 # Variant:
 # Type:    bootstrap_go_package
 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:40:1
+# Defined: Blueprints:41:1
 
 build $
         .bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
@@ -143,7 +144,7 @@
 # Variant:
 # Type:    bootstrap_go_package
 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModule
-# Defined: Blueprints:49:1
+# Defined: Blueprints:50:1
 
 build $
         .bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
@@ -158,7 +159,7 @@
 # Variant:
 # Type:    bootstrap_go_binary
 # Factory: github.com/google/blueprint/bootstrap.newGoBinaryModule
-# Defined: Blueprints:81:1
+# Defined: Blueprints:82:1
 
 build .bootstrap/bpfmt/obj/bpfmt.a: g.bootstrap.gc $
         ${g.bootstrap.srcDir}/bpfmt/bpfmt.go | ${g.bootstrap.gcCmd} $
@@ -180,7 +181,7 @@
 # Variant:
 # Type:    bootstrap_go_binary
 # Factory: github.com/google/blueprint/bootstrap.newGoBinaryModule
-# Defined: Blueprints:87:1
+# Defined: Blueprints:88:1
 
 build .bootstrap/bpmodify/obj/bpmodify.a: g.bootstrap.gc $
         ${g.bootstrap.srcDir}/bpmodify/bpmodify.go | ${g.bootstrap.gcCmd} $
@@ -202,23 +203,23 @@
 # Variant:
 # Type:    bootstrap_go_binary
 # Factory: github.com/google/blueprint/bootstrap.newGoBinaryModule
-# Defined: Blueprints:72:1
+# Defined: Blueprints:73:1
 
 build .bootstrap/minibp/obj/minibp.a: g.bootstrap.gc $
         ${g.bootstrap.srcDir}/bootstrap/minibp/main.go | ${g.bootstrap.gcCmd} $
         .bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+        .bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
         .bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
         .bootstrap/blueprint/pkg/github.com/google/blueprint.a $
         .bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
-        .bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
         .bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a
-    incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-bootstrap/pkg
+    incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-bootstrap/pkg
     pkgPath = minibp
 default .bootstrap/minibp/obj/minibp.a
 
 build .bootstrap/minibp/obj/a.out: g.bootstrap.link $
         .bootstrap/minibp/obj/minibp.a | ${g.bootstrap.linkCmd}
-    libDirFlags = -L .bootstrap/blueprint-parser/pkg -L .bootstrap/blueprint-proptools/pkg -L .bootstrap/blueprint/pkg -L .bootstrap/blueprint-deptools/pkg -L .bootstrap/blueprint-pathtools/pkg -L .bootstrap/blueprint-bootstrap/pkg
+    libDirFlags = -L .bootstrap/blueprint-parser/pkg -L .bootstrap/blueprint-pathtools/pkg -L .bootstrap/blueprint-proptools/pkg -L .bootstrap/blueprint/pkg -L .bootstrap/blueprint-deptools/pkg -L .bootstrap/blueprint-bootstrap/pkg
 default .bootstrap/minibp/obj/a.out
 
 build .bootstrap/bin/minibp: g.bootstrap.cp .bootstrap/minibp/obj/a.out
diff --git a/context.go b/context.go
index 73b5e35..b98ffbe 100644
--- a/context.go
+++ b/context.go
@@ -19,6 +19,7 @@
 	"errors"
 	"fmt"
 	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
 	"io"
 	"os"
@@ -471,6 +472,7 @@
 	}
 
 	subdirs, newErrs := c.processSubdirs(scope)
+
 	if len(newErrs) > 0 {
 		errs = append(errs, newErrs...)
 	}
@@ -590,77 +592,50 @@
 	modulesCh <- modules
 
 	for _, subdir := range subdirs {
-		subdir = filepath.Join(dir, subdir)
+		globPattern := filepath.Join(dir, subdir)
+		matches, matchedDirs, err := pathtools.Glob(globPattern)
+		if err != nil {
+			errsCh <- []error{err}
+			return
+		}
 
-		dirPart, filePart := filepath.Split(subdir)
-		dirPart = filepath.Clean(dirPart)
+		// Depend on all searched directories so we pick up future changes.
+		for _, matchedDir := range matchedDirs {
+			depsCh <- matchedDir
+		}
 
-		if filePart == "*" {
-			foundSubdirs, err := listSubdirs(dirPart)
-			if err != nil {
-				errsCh <- []error{err}
-				return
+		for _, foundSubdir := range matches {
+			fileInfo, subdirStatErr := os.Stat(foundSubdir)
+			if subdirStatErr != nil {
+				errsCh <- []error{subdirStatErr}
+				continue
 			}
 
-			for _, foundSubdir := range foundSubdirs {
-				subBlueprints := filepath.Join(dirPart, foundSubdir,
-					"Blueprints")
+			// Skip files
+			if !fileInfo.IsDir() {
+				continue
+			}
 
-				_, err := os.Stat(subBlueprints)
-				if os.IsNotExist(err) {
-					// There is no Blueprints file in this subdirectory.  We
-					// need to add the directory to the list of dependencies
-					// so that if someone adds a Blueprints file in the
-					// future we'll pick it up.
-					depsCh <- filepath.Dir(subBlueprints)
-				} else {
-					depsCh <- subBlueprints
-					blueprintsCh <- stringAndScope{
-						subBlueprints,
-						subScope,
-					}
+			subBlueprints := filepath.Join(foundSubdir, "Blueprints")
+
+			_, err := os.Stat(subBlueprints)
+			if os.IsNotExist(err) {
+				// There is no Blueprints file in this subdirectory.  We
+				// need to add the directory to the list of dependencies
+				// so that if someone adds a Blueprints file in the
+				// future we'll pick it up.
+				depsCh <- filepath.Dir(subBlueprints)
+			} else {
+				depsCh <- subBlueprints
+				blueprintsCh <- stringAndScope{
+					subBlueprints,
+					subScope,
 				}
 			}
-
-			// We now depend on the directory itself because if any new
-			// subdirectories get added or removed we need to rebuild the
-			// Ninja manifest.
-			depsCh <- dirPart
-		} else {
-			subBlueprints := filepath.Join(subdir, "Blueprints")
-			depsCh <- subBlueprints
-			blueprintsCh <- stringAndScope{
-				subBlueprints,
-				subScope,
-			}
-
 		}
 	}
 }
 
-func listSubdirs(dir string) ([]string, error) {
-	d, err := os.Open(dir)
-	if err != nil {
-		return nil, err
-	}
-	defer d.Close()
-
-	infos, err := d.Readdir(-1)
-	if err != nil {
-		return nil, err
-	}
-
-	var subdirs []string
-	for _, info := range infos {
-		isDotFile := strings.HasPrefix(info.Name(), ".")
-		if info.IsDir() && !isDotFile {
-			subdirs = append(subdirs, info.Name())
-		}
-	}
-
-	return subdirs, nil
-}
-
 func (c *Context) processSubdirs(
 	scope *parser.Scope) (subdirs []string, errs []error) {
 
@@ -675,19 +650,6 @@
 					panic("non-string value found in list")
 				}
 
-				dirPart, filePart := filepath.Split(value.StringValue)
-				if (filePart != "*" && strings.ContainsRune(filePart, '*')) ||
-					strings.ContainsRune(dirPart, '*') {
-
-					errs = append(errs, &Error{
-						Err: fmt.Errorf("subdirs may only wildcard whole " +
-							"directories"),
-						Pos: value.Pos,
-					})
-
-					continue
-				}
-
 				subdirs = append(subdirs, value.StringValue)
 			}