Add support for build= variable

build is a magic variable that acts similarly to subdirs, but specifies
files to build instead of subdirs.  Using files provides a few advantages:
files can be named something more appropriate for the project than
"Blueprints", whatever you want instead of Blueprints, and multiple
Blueprints files can exist in the same directory.

A new variable is used instead of putting filenames into subdirs to avoid
unexpected behavior when a glob matches both files and directories.

subdirs= and build= entries that don't match any directories or files
are now reported as errors.

Change-Id: Id329504ace251eab4ccea1081a3c8665a4c52f5a
diff --git a/context.go b/context.go
index ff3a6ce..788e055 100644
--- a/context.go
+++ b/context.go
@@ -18,9 +18,6 @@
 	"bytes"
 	"errors"
 	"fmt"
-	"github.com/google/blueprint/parser"
-	"github.com/google/blueprint/pathtools"
-	"github.com/google/blueprint/proptools"
 	"io"
 	"os"
 	"path/filepath"
@@ -31,6 +28,10 @@
 	"strings"
 	"text/scanner"
 	"text/template"
+
+	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/pathtools"
+	"github.com/google/blueprint/proptools"
 )
 
 var ErrBuildActionsNotReady = errors.New("build actions are not ready")
@@ -415,22 +416,25 @@
 // Parse parses a single Blueprints file from r, creating Module objects for
 // each of the module definitions encountered.  If the Blueprints file contains
 // an assignment to the "subdirs" variable, then the subdirectories listed are
-// returned in the subdirs first return value.
+// searched for Blueprints files returned in the subBlueprints return value.
+// If the Blueprints file contains an assignment to the "build" variable, then
+// the file listed are returned in the subBlueprints return value.
 //
 // rootDir specifies the path to the root directory of the source tree, while
 // filename specifies the path to the Blueprints file.  These paths are used for
 // error reporting and for determining the module's directory.
 func (c *Context) parse(rootDir, filename string, r io.Reader,
-	scope *parser.Scope) (subdirs []string, modules []*moduleInfo, errs []error,
-	outScope *parser.Scope) {
+	scope *parser.Scope) (modules []*moduleInfo, subBlueprints []stringAndScope, deps []string,
+	errs []error) {
 
 	relBlueprintsFile, err := filepath.Rel(rootDir, filename)
 	if err != nil {
-		return nil, nil, []error{err}, nil
+		return nil, nil, nil, []error{err}
 	}
 
 	scope = parser.NewScope(scope)
 	scope.Remove("subdirs")
+	scope.Remove("build")
 	file, errs := parser.ParseAndEval(filename, r, scope)
 	if len(errs) > 0 {
 		for i, err := range errs {
@@ -445,7 +449,7 @@
 
 		// If there were any parse errors don't bother trying to interpret the
 		// result.
-		return nil, nil, errs, nil
+		return nil, nil, nil, errs
 	}
 
 	for _, def := range file.Defs {
@@ -471,13 +475,28 @@
 		}
 	}
 
-	subdirs, newErrs := c.processSubdirs(scope)
+	subdirs, subdirsPos, err := getStringListFromScope(scope, "subdirs")
+	if err != nil {
+		errs = append(errs, err)
+	}
 
+	build, buildPos, err := getStringListFromScope(scope, "build")
+	if err != nil {
+		errs = append(errs, err)
+	}
+
+	blueprints, deps, newErrs := c.findSubdirBlueprints(filepath.Dir(filename), subdirs, build,
+		subdirsPos, buildPos)
 	if len(newErrs) > 0 {
 		errs = append(errs, newErrs...)
 	}
 
-	return subdirs, modules, errs, scope
+	subBlueprintsAndScope := make([]stringAndScope, len(blueprints))
+	for i, b := range blueprints {
+		subBlueprintsAndScope[i] = stringAndScope{b, scope}
+	}
+
+	return modules, subBlueprintsAndScope, deps, errs
 }
 
 type stringAndScope struct {
@@ -571,43 +590,61 @@
 	errsCh chan<- []error, modulesCh chan<- []*moduleInfo, blueprintsCh chan<- stringAndScope,
 	depsCh chan<- string) {
 
-	dir := filepath.Dir(filename)
-
 	file, err := os.Open(filename)
 	if err != nil {
 		errsCh <- []error{err}
 		return
 	}
 
-	subdirs, modules, errs, subScope := c.parse(rootDir, filename, file, scope)
+	modules, subBlueprints, deps, errs := c.parse(rootDir, filename, file, scope)
 	if len(errs) > 0 {
 		errsCh <- errs
 	}
 
+	for _, b := range subBlueprints {
+		blueprintsCh <- b
+	}
+
+	for _, d := range deps {
+		depsCh <- d
+	}
+
 	err = file.Close()
 	if err != nil {
 		errsCh <- []error{err}
 	}
 
 	modulesCh <- modules
+}
+
+func (c *Context) findSubdirBlueprints(dir string, subdirs, build []string,
+	subdirsPos, buildPos scanner.Position) (blueprints, deps []string, errs []error) {
 
 	for _, subdir := range subdirs {
 		globPattern := filepath.Join(dir, subdir)
 		matches, matchedDirs, err := pathtools.Glob(globPattern)
 		if err != nil {
-			errsCh <- []error{err}
-			return
+			errs = append(errs, &Error{
+				Err: fmt.Errorf("%q: %s", globPattern, err.Error()),
+				Pos: subdirsPos,
+			})
+			continue
+		}
+
+		if len(matches) == 0 {
+			errs = append(errs, &Error{
+				Err: fmt.Errorf("%q: not found", globPattern),
+				Pos: subdirsPos,
+			})
 		}
 
 		// Depend on all searched directories so we pick up future changes.
-		for _, matchedDir := range matchedDirs {
-			depsCh <- matchedDir
-		}
+		deps = append(deps, matchedDirs...)
 
 		for _, foundSubdir := range matches {
 			fileInfo, subdirStatErr := os.Stat(foundSubdir)
 			if subdirStatErr != nil {
-				errsCh <- []error{subdirStatErr}
+				errs = append(errs, subdirStatErr)
 				continue
 			}
 
@@ -624,25 +661,63 @@
 				// 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)
+				deps = append(deps, filepath.Dir(subBlueprints))
 			} else {
-				depsCh <- subBlueprints
-				blueprintsCh <- stringAndScope{
-					subBlueprints,
-					subScope,
-				}
+				deps = append(deps, subBlueprints)
+				blueprints = append(blueprints, subBlueprints)
 			}
 		}
 	}
+
+	for _, file := range build {
+		globPattern := filepath.Join(dir, file)
+		matches, matchedDirs, err := pathtools.Glob(globPattern)
+		if err != nil {
+			errs = append(errs, &Error{
+				Err: fmt.Errorf("%q: %s", globPattern, err.Error()),
+				Pos: buildPos,
+			})
+			continue
+		}
+
+		if len(matches) == 0 {
+			errs = append(errs, &Error{
+				Err: fmt.Errorf("%q: not found", globPattern),
+				Pos: buildPos,
+			})
+		}
+
+		// Depend on all searched directories so we pick up future changes.
+		deps = append(deps, matchedDirs...)
+
+		for _, foundBlueprints := range matches {
+			fileInfo, err := os.Stat(foundBlueprints)
+			if os.IsNotExist(err) {
+				errs = append(errs, &Error{
+					Err: fmt.Errorf("%q not found", foundBlueprints),
+				})
+				continue
+			}
+
+			if fileInfo.IsDir() {
+				errs = append(errs, &Error{
+					Err: fmt.Errorf("%q is a directory", foundBlueprints),
+				})
+				continue
+			}
+
+			blueprints = append(blueprints, foundBlueprints)
+		}
+	}
+
+	return blueprints, deps, errs
 }
 
-func (c *Context) processSubdirs(
-	scope *parser.Scope) (subdirs []string, errs []error) {
-
-	if assignment, err := scope.Get("subdirs"); err == nil {
+func getStringListFromScope(scope *parser.Scope, v string) ([]string, scanner.Position, error) {
+	if assignment, err := scope.Get(v); err == nil {
 		switch assignment.Value.Type {
 		case parser.List:
-			subdirs = make([]string, 0, len(assignment.Value.ListValue))
+			ret := make([]string, 0, len(assignment.Value.ListValue))
 
 			for _, value := range assignment.Value.ListValue {
 				if value.Type != parser.String {
@@ -650,31 +725,21 @@
 					panic("non-string value found in list")
 				}
 
-				subdirs = append(subdirs, value.StringValue)
+				ret = append(ret, value.StringValue)
 			}
 
-			if len(errs) > 0 {
-				subdirs = nil
-			}
-
-			return
-
+			return ret, assignment.Pos, nil
 		case parser.Bool, parser.String:
-			errs = []error{
-				&Error{
-					Err: fmt.Errorf("subdirs must be a list of strings"),
-					Pos: assignment.Pos,
-				},
+			return nil, scanner.Position{}, &Error{
+				Err: fmt.Errorf("%q must be a list of strings", v),
+				Pos: assignment.Pos,
 			}
-
-			return
-
 		default:
 			panic(fmt.Errorf("unknown value type: %d", assignment.Value.Type))
 		}
 	}
 
-	return nil, nil
+	return nil, scanner.Position{}, nil
 }
 
 func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
diff --git a/context_test.go b/context_test.go
index 3d8f6ee..1c4303b 100644
--- a/context_test.go
+++ b/context_test.go
@@ -71,7 +71,7 @@
 		}
 	`)
 
-	_, _, errs, _ := ctx.parse(".", "Blueprint", r, nil)
+	_, _, _, errs := ctx.parse(".", "Blueprint", r, nil)
 	if len(errs) > 0 {
 		t.Errorf("unexpected parse errors:")
 		for _, err := range errs {