blob: 9aada958483d4a85c20dd618a3ee6cab84d98b19 [file] [log] [blame]
Colin Cross3f40fa42015-01-30 17:27:36 -08001// Copyright 2015 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package common
16
17import (
18 "fmt"
19 "path/filepath"
20
21 "blueprint"
22 "blueprint/bootstrap"
23
24 "android/soong/glob"
25)
26
27// This file supports globbing source files in Blueprints files.
28//
29// The build.ninja file needs to be regenerated any time a file matching the glob is added
30// or removed. The naive solution is to have the build.ninja file depend on all the
31// traversed directories, but this will cause the regeneration step to run every time a
32// non-matching file is added to a traversed directory, including backup files created by
33// editors.
34//
35// The solution implemented here optimizes out regenerations when the directory modifications
36// don't match the glob by having the build.ninja file depend on an intermedate file that
37// is only updated when a file matching the glob is added or removed. The intermediate file
38// depends on the traversed directories via a depfile. The depfile is used to avoid build
39// errors if a directory is deleted - a direct dependency on the deleted directory would result
40// in a build failure with a "missing and no known rule to make it" error.
41
42var (
43 globCmd = filepath.Join(bootstrap.BinDir, "soong_glob")
44
45 // globRule rule traverses directories to produce a list of files that match $glob
46 // and writes it to $out if it has changed, and writes the directories to $out.d
47 globRule = pctx.StaticRule("globRule",
48 blueprint.RuleParams{
49 Command: fmt.Sprintf(`%s -o $out "$glob"`, globCmd),
50 Description: "glob $glob",
51
52 Restat: true,
53 Generator: true,
54 Deps: blueprint.DepsGCC,
55 Depfile: "$out.d",
56 },
57 "glob")
58)
59
60func hasGlob(in []string) bool {
61 for _, s := range in {
62 if glob.IsGlob(s) {
63 return true
64 }
65 }
66
67 return false
68}
69
70func ExpandGlobs(ctx AndroidModuleContext, in []string) []string {
71 if !hasGlob(in) {
72 return in
73 }
74
75 out := make([]string, 0, len(in))
76 for _, s := range in {
77 if glob.IsGlob(s) {
78 out = append(out, Glob(ctx, s)...)
79 } else {
80 out = append(out, s)
81 }
82 }
83
84 return out
85}
86
87func Glob(ctx AndroidModuleContext, globPattern string) []string {
88 fileListFile := filepath.Join(ModuleOutDir(ctx), "glob", globToString(globPattern))
89 depFile := fileListFile + ".d"
90
91 // Get a globbed file list, and write out fileListFile and depFile
92 files, err := glob.GlobWithDepFile(globPattern, fileListFile, depFile)
93 if err != nil {
94 ctx.ModuleErrorf("glob: %s", err.Error())
95 return []string{globPattern}
96 }
97
98 // Create a rule to rebuild fileListFile if a directory in depFile changes. fileListFile
99 // will only be rewritten if it has changed, preventing unnecesary build.ninja regenerations.
100 ctx.Build(pctx, blueprint.BuildParams{
101 Rule: globRule,
102 Outputs: []string{fileListFile},
103 Implicits: []string{globCmd},
104 Args: map[string]string{
105 "glob": globPattern,
106 },
107 })
108
109 // Phony rule so the cleanup phase doesn't delete the depFile
110 ctx.Build(pctx, blueprint.BuildParams{
111 Rule: blueprint.Phony,
112 Outputs: []string{depFile},
113 })
114
115 // Make build.ninja depend on the fileListFile
116 ctx.AddNinjaFileDeps(fileListFile)
117
118 return files
119}
120
121func globToString(glob string) string {
122 ret := ""
123 for _, c := range glob {
124 if c >= 'a' && c <= 'z' ||
125 c >= 'A' && c <= 'Z' ||
126 c >= '0' && c <= '9' ||
127 c == '_' || c == '-' || c == '/' {
128 ret += string(c)
129 }
130 }
131
132 return ret
133}