| // 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 common |
| |
| 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.BinDir}", "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.StaticRule("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 |
| } |