Move globbing to Blueprint

Move Soong's globbing-with-dependencies support into Blueprint so it can
be used for subdirs= lines in Android.bp files.

Blueprint has a slight change in behavior around subname= lines, it now
always uses the subname and doesn't fall back to Blueprints.  To support
the Blueprints files in build/blueprint, use them directly with build=.

Test: build, add source file that matches glob, rebuild
Change-Id: Ifd0b0d3bc061aae0a16d6c7ca9a1cd8672656b4d
This commit is contained in:
Colin Cross
2016-11-01 11:10:25 -07:00
parent 28f9094ee7
commit 7f19f37443
12 changed files with 87 additions and 405 deletions

View File

@@ -1,120 +0,0 @@
// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android
import (
"fmt"
"path/filepath"
"github.com/google/blueprint"
"android/soong/glob"
)
// This file supports globbing source files in Blueprints files.
//
// The build.ninja file needs to be regenerated any time a file matching the glob is added
// or removed. The naive solution is to have the build.ninja file depend on all the
// traversed directories, but this will cause the regeneration step to run every time a
// non-matching file is added to a traversed directory, including backup files created by
// editors.
//
// The solution implemented here optimizes out regenerations when the directory modifications
// don't match the glob by having the build.ninja file depend on an intermedate file that
// is only updated when a file matching the glob is added or removed. The intermediate file
// depends on the traversed directories via a depfile. The depfile is used to avoid build
// errors if a directory is deleted - a direct dependency on the deleted directory would result
// in a build failure with a "missing and no known rule to make it" error.
var (
globCmd = filepath.Join("${bootstrap.ToolDir}", "soong_glob")
// globRule rule traverses directories to produce a list of files that match $glob
// and writes it to $out if it has changed, and writes the directories to $out.d
globRule = pctx.AndroidStaticRule("globRule",
blueprint.RuleParams{
Command: fmt.Sprintf(`%s -o $out $excludes "$glob"`, globCmd),
CommandDeps: []string{globCmd},
Description: "glob $glob",
Restat: true,
Deps: blueprint.DepsGCC,
Depfile: "$out.d",
},
"glob", "excludes")
)
func hasGlob(in []string) bool {
for _, s := range in {
if glob.IsGlob(s) {
return true
}
}
return false
}
// The subset of ModuleContext and SingletonContext needed by Glob
type globContext interface {
Build(pctx blueprint.PackageContext, params blueprint.BuildParams)
AddNinjaFileDeps(deps ...string)
}
func Glob(ctx globContext, outDir string, globPattern string, excludes []string) ([]string, error) {
fileListFile := filepath.Join(outDir, "glob", globToString(globPattern)+".glob")
depFile := fileListFile + ".d"
// Get a globbed file list, and write out fileListFile and depFile
files, err := glob.GlobWithDepFile(globPattern, fileListFile, depFile, excludes)
if err != nil {
return nil, err
}
GlobRule(ctx, globPattern, excludes, fileListFile, depFile)
// Make build.ninja depend on the fileListFile
ctx.AddNinjaFileDeps(fileListFile)
return files, nil
}
func GlobRule(ctx globContext, globPattern string, excludes []string,
fileListFile, depFile string) {
// Create a rule to rebuild fileListFile if a directory in depFile changes. fileListFile
// will only be rewritten if it has changed, preventing unnecesary build.ninja regenerations.
ctx.Build(pctx, blueprint.BuildParams{
Rule: globRule,
Outputs: []string{fileListFile},
Args: map[string]string{
"glob": globPattern,
"excludes": JoinWithPrefixAndQuote(excludes, "-e "),
},
})
}
func globToString(glob string) string {
ret := ""
for _, c := range glob {
if c >= 'a' && c <= 'z' ||
c >= 'A' && c <= 'Z' ||
c >= '0' && c <= '9' ||
c == '_' || c == '-' || c == '/' {
ret += string(c)
}
}
return ret
}

View File

@@ -19,9 +19,8 @@ import (
"path/filepath"
"strings"
"android/soong/glob"
"github.com/google/blueprint"
"github.com/google/blueprint/pathtools"
)
var (
@@ -76,7 +75,7 @@ type ModuleContext interface {
ModuleBuild(pctx blueprint.PackageContext, params ModuleBuildParams)
ExpandSources(srcFiles, excludes []string) Paths
Glob(outDir, globPattern string, excludes []string) Paths
Glob(globPattern string, excludes []string) Paths
InstallFile(installPath OutputPath, srcPath Path, deps ...Path) OutputPath
InstallFileName(installPath OutputPath, name string, srcPath Path, deps ...Path) OutputPath
@@ -509,7 +508,7 @@ func (a *androidModuleContext) ninjaError(outputs []string, err error) {
}
func (a *androidModuleContext) Build(pctx blueprint.PackageContext, params blueprint.BuildParams) {
if a.missingDeps != nil && params.Rule != globRule {
if a.missingDeps != nil {
a.ninjaError(params.Outputs, fmt.Errorf("module %s missing dependencies: %s\n",
a.ModuleName(), strings.Join(a.missingDeps, ", ")))
return
@@ -718,8 +717,8 @@ func (ctx *androidModuleContext) ExpandSources(srcFiles, excludes []string) Path
globbedSrcFiles := make(Paths, 0, len(srcFiles))
for _, s := range srcFiles {
if glob.IsGlob(s) {
globbedSrcFiles = append(globbedSrcFiles, ctx.Glob("src_glob", filepath.Join(prefix, s), excludes)...)
if pathtools.IsGlob(s) {
globbedSrcFiles = append(globbedSrcFiles, ctx.Glob(filepath.Join(prefix, s), excludes)...)
} else {
globbedSrcFiles = append(globbedSrcFiles, PathForModuleSrc(ctx, s))
}
@@ -728,8 +727,8 @@ func (ctx *androidModuleContext) ExpandSources(srcFiles, excludes []string) Path
return globbedSrcFiles
}
func (ctx *androidModuleContext) Glob(outDir, globPattern string, excludes []string) Paths {
ret, err := Glob(ctx, PathForModuleOut(ctx, outDir).String(), globPattern, excludes)
func (ctx *androidModuleContext) Glob(globPattern string, excludes []string) Paths {
ret, err := ctx.GlobWithDeps(globPattern, excludes)
if err != nil {
ctx.ModuleErrorf("glob: %s", err.Error())
}

View File

@@ -21,8 +21,6 @@ import (
"reflect"
"strings"
"android/soong/glob"
"github.com/google/blueprint"
"github.com/google/blueprint/pathtools"
)
@@ -34,6 +32,10 @@ type PathContext interface {
AddNinjaFileDeps(deps ...string)
}
type PathGlobContext interface {
GlobWithDeps(globPattern string, excludes []string) ([]string, error)
}
var _ PathContext = blueprint.SingletonContext(nil)
var _ PathContext = blueprint.ModuleContext(nil)
@@ -248,7 +250,7 @@ func PathsWithOptionalDefaultForModuleSrc(ctx ModuleContext, input []string, def
// Use Glob so that if the default doesn't exist, a dependency is added so that when it
// is created, we're run again.
path := filepath.Join(ctx.AConfig().srcDir, ctx.ModuleDir(), def)
return ctx.Glob("default", path, []string{})
return ctx.Glob(path, []string{})
}
// Strings returns the Paths in string form
@@ -382,15 +384,15 @@ func OptionalPathForSource(ctx PathContext, intermediates string, paths ...strin
return OptionalPath{}
}
if glob.IsGlob(path.String()) {
if pathtools.IsGlob(path.String()) {
reportPathError(ctx, "path may not contain a glob: %s", path.String())
return OptionalPath{}
}
if gctx, ok := ctx.(globContext); ok {
if gctx, ok := ctx.(PathGlobContext); ok {
// Use glob to produce proper dependencies, even though we only want
// a single file.
files, err := Glob(gctx, PathForIntermediates(ctx, intermediates).String(), path.String(), nil)
files, err := gctx.GlobWithDeps(path.String(), nil)
if err != nil {
reportPathError(ctx, "glob: %s", err.Error())
return OptionalPath{}
@@ -444,10 +446,10 @@ func (p SourcePath) OverlayPath(ctx ModuleContext, path Path) OptionalPath {
}
dir := filepath.Join(p.config.srcDir, p.path, relDir)
// Use Glob so that we are run again if the directory is added.
if glob.IsGlob(dir) {
if pathtools.IsGlob(dir) {
reportPathError(ctx, "Path may not contain a glob: %s", dir)
}
paths, err := Glob(ctx, PathForModuleOut(ctx, "overlay").String(), dir, []string{})
paths, err := ctx.GlobWithDeps(dir, []string{})
if err != nil {
reportPathError(ctx, "glob: %s", err.Error())
return OptionalPath{}

View File

@@ -45,33 +45,6 @@ func JoinWithPrefix(strs []string, prefix string) string {
return string(ret)
}
func JoinWithPrefixAndQuote(strs []string, prefix string) string {
if len(strs) == 0 {
return ""
}
if len(strs) == 1 {
return prefix + `"` + strs[0] + `"`
}
n := len(" ") * (len(strs) - 1)
for _, s := range strs {
n += len(prefix) + len(s) + len(`""`)
}
ret := make([]byte, 0, n)
for i, s := range strs {
if i != 0 {
ret = append(ret, ' ')
}
ret = append(ret, prefix...)
ret = append(ret, '"')
ret = append(ret, s...)
ret = append(ret, '"')
}
return string(ret)
}
func sortedKeys(m map[string][]string) []string {
s := make([]string, 0, len(m))
for k := range m {