aboutsummaryrefslogtreecommitdiffstats
path: root/common/glob.go
blob: 152be94eaa0fbae314eeac1265e87e8d7e29341d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// 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),
			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))
	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},
		Implicits: []string{globCmd},
		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
}