// 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" "strings" "github.com/google/blueprint" "github.com/google/blueprint/bootstrap" "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), Description: "glob $glob", Restat: true, Generator: 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 } func ExpandGlobs(ctx AndroidModuleContext, in []string) []string { if !hasGlob(in) { return in } out := make([]string, 0, len(in)) for _, s := range in { if glob.IsGlob(s) { out = append(out, Glob(ctx, s)...) } else { out = append(out, s) } } return out } func Glob(ctx AndroidModuleContext, globPattern string) []string { fileListFile := filepath.Join(ModuleOutDir(ctx), "glob", globToString(globPattern)) depFile := fileListFile + ".d" var excludes []string // Get a globbed file list, and write out fileListFile and depFile files, err := glob.GlobWithDepFile(globPattern, fileListFile, depFile, excludes) if err != nil { ctx.ModuleErrorf("glob: %s", err.Error()) return []string{globPattern} } GlobRule(ctx, globPattern, excludes, fileListFile, depFile) // Make build.ninja depend on the fileListFile ctx.AddNinjaFileDeps(fileListFile) return files } func GlobRule(ctx AndroidModuleContext, globPattern string, excludes []string, fileListFile, depFile string) { var excludeArgs []string for _, e := range excludes { excludeArgs = append(excludeArgs, "-e "+e) } // 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}, Implicits: []string{globCmd}, Args: map[string]string{ "glob": globPattern, "excludes": strings.Join(excludeArgs, " "), }, }) // Phony rule so the cleanup phase doesn't delete the depFile ctx.Build(pctx, blueprint.BuildParams{ Rule: blueprint.Phony, Outputs: []string{depFile}, }) } 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 }