github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/blueprint/bootstrap/glob.go (about)

     1  // Copyright 2016 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  
    15  package bootstrap
    16  
    17  import (
    18  	"fmt"
    19  	"path/filepath"
    20  	"strings"
    21  
    22  	"github.com/google/blueprint"
    23  	"github.com/google/blueprint/deptools"
    24  	"github.com/google/blueprint/pathtools"
    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  
    42  var (
    43  	globCmd = filepath.Join("$BinDir", "bpglob")
    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 $excludes "$glob"`, globCmd),
    50  			CommandDeps: []string{globCmd},
    51  			Description: "glob $glob",
    52  
    53  			Restat:  true,
    54  			Deps:    blueprint.DepsGCC,
    55  			Depfile: "$out.d",
    56  		},
    57  		"glob", "excludes")
    58  )
    59  
    60  // GlobFileContext is the subset of ModuleContext and SingletonContext needed by GlobFile
    61  type GlobFileContext interface {
    62  	Build(pctx blueprint.PackageContext, params blueprint.BuildParams)
    63  }
    64  
    65  // GlobFile creates a rule to write to fileListFile a list of the files that match the specified
    66  // pattern but do not match any of the patterns specified in excludes.  The file will include
    67  // appropriate dependencies written to depFile to regenerate the file if and only if the list of
    68  // matching files has changed.
    69  func GlobFile(ctx GlobFileContext, pattern string, excludes []string,
    70  	fileListFile, depFile string) {
    71  
    72  	ctx.Build(pctx, blueprint.BuildParams{
    73  		Rule:    GlobRule,
    74  		Outputs: []string{fileListFile},
    75  		Args: map[string]string{
    76  			"glob":     pattern,
    77  			"excludes": joinWithPrefixAndQuote(excludes, "-e "),
    78  		},
    79  	})
    80  }
    81  
    82  func joinWithPrefixAndQuote(strs []string, prefix string) string {
    83  	if len(strs) == 0 {
    84  		return ""
    85  	}
    86  
    87  	if len(strs) == 1 {
    88  		return prefix + `"` + strs[0] + `"`
    89  	}
    90  
    91  	n := len(" ") * (len(strs) - 1)
    92  	for _, s := range strs {
    93  		n += len(prefix) + len(s) + len(`""`)
    94  	}
    95  
    96  	ret := make([]byte, 0, n)
    97  	for i, s := range strs {
    98  		if i != 0 {
    99  			ret = append(ret, ' ')
   100  		}
   101  		ret = append(ret, prefix...)
   102  		ret = append(ret, '"')
   103  		ret = append(ret, s...)
   104  		ret = append(ret, '"')
   105  	}
   106  	return string(ret)
   107  }
   108  
   109  // globSingleton collects any glob patterns that were seen by Context and writes out rules to
   110  // re-evaluate them whenever the contents of the searched directories change, and retrigger the
   111  // primary builder if the results change.
   112  type globSingleton struct {
   113  	globLister func() []blueprint.GlobPath
   114  }
   115  
   116  func globSingletonFactory(ctx *blueprint.Context) func() blueprint.Singleton {
   117  	return func() blueprint.Singleton {
   118  		return &globSingleton{
   119  			globLister: ctx.Globs,
   120  		}
   121  	}
   122  }
   123  
   124  func (s *globSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
   125  	for _, g := range s.globLister() {
   126  		fileListFile := filepath.Join(BuildDir, ".glob", g.Name)
   127  		depFile := fileListFile + ".d"
   128  
   129  		fileList := strings.Join(g.Files, "\n") + "\n"
   130  		pathtools.WriteFileIfChanged(fileListFile, []byte(fileList), 0666)
   131  		deptools.WriteDepFile(depFile, fileListFile, g.Deps)
   132  
   133  		GlobFile(ctx, g.Pattern, g.Excludes, fileListFile, depFile)
   134  
   135  		// Make build.ninja depend on the fileListFile
   136  		ctx.AddNinjaFileDeps(fileListFile)
   137  	}
   138  }