• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// 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	"github.com/google/blueprint"
22
23	"android/soong/glob"
24)
25
26// This file supports globbing source files in Blueprints files.
27//
28// The build.ninja file needs to be regenerated any time a file matching the glob is added
29// or removed.  The naive solution is to have the build.ninja file depend on all the
30// traversed directories, but this will cause the regeneration step to run every time a
31// non-matching file is added to a traversed directory, including backup files created by
32// editors.
33//
34// The solution implemented here optimizes out regenerations when the directory modifications
35// don't match the glob by having the build.ninja file depend on an intermedate file that
36// is only updated when a file matching the glob is added or removed.  The intermediate file
37// depends on the traversed directories via a depfile.  The depfile is used to avoid build
38// errors if a directory is deleted - a direct dependency on the deleted directory would result
39// in a build failure with a "missing and no known rule to make it" error.
40
41var (
42	globCmd = filepath.Join("${bootstrap.BinDir}", "soong_glob")
43
44	// globRule rule traverses directories to produce a list of files that match $glob
45	// and writes it to $out if it has changed, and writes the directories to $out.d
46	globRule = pctx.StaticRule("globRule",
47		blueprint.RuleParams{
48			Command:     fmt.Sprintf(`%s -o $out $excludes "$glob"`, globCmd),
49			CommandDeps: []string{globCmd},
50			Description: "glob $glob",
51
52			Restat:  true,
53			Deps:    blueprint.DepsGCC,
54			Depfile: "$out.d",
55		},
56		"glob", "excludes")
57)
58
59func hasGlob(in []string) bool {
60	for _, s := range in {
61		if glob.IsGlob(s) {
62			return true
63		}
64	}
65
66	return false
67}
68
69// The subset of ModuleContext and SingletonContext needed by Glob
70type globContext interface {
71	Build(pctx blueprint.PackageContext, params blueprint.BuildParams)
72	AddNinjaFileDeps(deps ...string)
73}
74
75func Glob(ctx globContext, outDir string, globPattern string, excludes []string) ([]string, error) {
76	fileListFile := filepath.Join(outDir, "glob", globToString(globPattern))
77	depFile := fileListFile + ".d"
78
79	// Get a globbed file list, and write out fileListFile and depFile
80	files, err := glob.GlobWithDepFile(globPattern, fileListFile, depFile, excludes)
81	if err != nil {
82		return nil, err
83	}
84
85	GlobRule(ctx, globPattern, excludes, fileListFile, depFile)
86
87	// Make build.ninja depend on the fileListFile
88	ctx.AddNinjaFileDeps(fileListFile)
89
90	return files, nil
91}
92
93func GlobRule(ctx globContext, globPattern string, excludes []string,
94	fileListFile, depFile string) {
95
96	// Create a rule to rebuild fileListFile if a directory in depFile changes.  fileListFile
97	// will only be rewritten if it has changed, preventing unnecesary build.ninja regenerations.
98	ctx.Build(pctx, blueprint.BuildParams{
99		Rule:    globRule,
100		Outputs: []string{fileListFile},
101		Args: map[string]string{
102			"glob":     globPattern,
103			"excludes": JoinWithPrefixAndQuote(excludes, "-e "),
104		},
105	})
106}
107
108func globToString(glob string) string {
109	ret := ""
110	for _, c := range glob {
111		if c >= 'a' && c <= 'z' ||
112			c >= 'A' && c <= 'Z' ||
113			c >= '0' && c <= '9' ||
114			c == '_' || c == '-' || c == '/' {
115			ret += string(c)
116		}
117	}
118
119	return ret
120}
121