1// Copyright 2020 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package gen_tasks_logic 6 7import ( 8 "log" 9 "os/exec" 10 "path/filepath" 11 "regexp" 12 "sort" 13 "strings" 14 15 "go.skia.org/infra/go/cas/rbe" 16 "go.skia.org/infra/task_scheduler/go/specs" 17) 18 19const ( 20 // If a parent path contains more than this many immediate child paths (ie. 21 // files and dirs which are directly inside it as opposed to indirect 22 // descendants), we will include the parent in the isolate file instead of 23 // the children. This results in a simpler CasSpec which should need to be 24 // changed less often. 25 combinePathsThreshold = 3 26) 27 28var ( 29 // Any files in Git which match these patterns will be included, either 30 // directly or indirectly via a parent dir. 31 pathRegexes = []*regexp.Regexp{ 32 regexp.MustCompile(`.*\.c$`), 33 regexp.MustCompile(`.*\.cc$`), 34 regexp.MustCompile(`.*\.cpp$`), 35 regexp.MustCompile(`.*\.gn$`), 36 regexp.MustCompile(`.*\.gni$`), 37 regexp.MustCompile(`.*\.h$`), 38 regexp.MustCompile(`.*\.mm$`), 39 regexp.MustCompile(`.*\.storyboard$`), 40 } 41 42 // These paths are always added to the inclusion list. Note that they may 43 // not appear in the CasSpec if they are included indirectly via a parent 44 // dir. 45 explicitPaths = []string{ 46 ".bazelrc", 47 ".bazelversion", 48 ".clang-format", 49 ".clang-tidy", 50 ".vpython3", 51 "BUILD.bazel", 52 "DEPS", // Needed by bin/fetch-ninja 53 "WORKSPACE.bazel", 54 "bazel", 55 "bin/activate-emsdk", 56 "bin/fetch-clang-format", 57 "bin/fetch-gn", 58 "bin/fetch-ninja", 59 "buildtools", 60 "example", 61 "experimental/rust_png", 62 "go_repositories.bzl", 63 "infra/bots/assets/android_ndk_darwin/VERSION", 64 "infra/bots/assets/android_ndk_linux/VERSION", 65 "infra/bots/assets/android_ndk_windows/VERSION", 66 "infra/bots/assets/cast_toolchain/VERSION", 67 "infra/bots/assets/clang_linux/VERSION", 68 "infra/bots/assets/clang_win/VERSION", 69 "infra/bots/run_recipe.py", 70 "infra/bots/task_drivers", 71 "infra/canvaskit", 72 "infra/pathkit", 73 "package.json", 74 "package-lock.json", 75 "requirements.txt", 76 "resources", 77 "third_party/externals", 78 "toolchain", 79 } 80) 81 82// getAllCheckedInPaths returns every path checked in to the repo. 83func getAllCheckedInPaths(cfg *Config) []string { 84 cmd := exec.Command("git", "ls-files") 85 // Use cfg.PathToSkia to get to the Skia checkout, in case this is used by 86 // another repo. 87 cmd.Dir = filepath.Join(CheckoutRoot(), cfg.PathToSkia) 88 output, err := cmd.CombinedOutput() 89 if err != nil { 90 log.Fatal(err) 91 } 92 split := strings.Split(string(output), "\n") 93 rv := make([]string, 0, len(split)) 94 for _, line := range split { 95 if line != "" { 96 rv = append(rv, line) 97 } 98 } 99 return rv 100} 101 102// getRelevantPaths returns all paths needed by compile tasks. 103func getRelevantPaths(cfg *Config) []string { 104 rv := []string{} 105 for _, path := range getAllCheckedInPaths(cfg) { 106 for _, regex := range pathRegexes { 107 if regex.MatchString(path) { 108 rv = append(rv, path) 109 break 110 } 111 } 112 } 113 return append(rv, explicitPaths...) 114} 115 116// node is a single node in a directory tree of task inputs. 117type node struct { 118 children map[string]*node 119 name string 120 isLeaf bool 121} 122 123// newNode returns a node instance. 124func newNode(name string) *node { 125 return &node{ 126 children: map[string]*node{}, 127 name: name, 128 isLeaf: false, 129 } 130} 131 132// isRoot returns true iff this is the root node. 133func (n *node) isRoot() bool { 134 return n.name == "" 135} 136 137// add the given entry (given as a slice of path components) to the node. 138func (n *node) add(entry []string) { 139 // Remove the first element if we're not the root node. 140 if !n.isRoot() { 141 if entry[0] != n.name { 142 log.Fatalf("Failed to compute compile CAS inputs; attempting to add entry %v to node %q", entry, n.name) 143 } 144 entry = entry[1:] 145 146 // If the entry is now empty, this node is a leaf. 147 if len(entry) == 0 { 148 n.isLeaf = true 149 return 150 } 151 } 152 153 // Add a child node. 154 if !n.isLeaf { 155 name := entry[0] 156 child, ok := n.children[name] 157 if !ok { 158 child = newNode(name) 159 n.children[name] = child 160 } 161 child.add(entry) 162 163 // If we have more than combinePathsThreshold immediate children, 164 // combine them into this node. 165 immediateChilden := 0 166 for _, child := range n.children { 167 if child.isLeaf { 168 immediateChilden++ 169 } 170 if !n.isRoot() && immediateChilden >= combinePathsThreshold { 171 n.isLeaf = true 172 n.children = map[string]*node{} 173 } 174 } 175 } 176} 177 178// entries returns the entries represented by this node and its children. 179// Will not return children in the following cases: 180// - This Node is a leaf, ie. it represents an entry which was explicitly 181// inserted into the Tree, as opposed to only part of a path to other 182// entries. 183// - This Node has immediate children exceeding combinePathsThreshold and 184// thus has been upgraded to a leaf node. 185func (n *node) entries() [][]string { 186 if n.isLeaf { 187 return [][]string{{n.name}} 188 } 189 rv := [][]string{} 190 for _, child := range n.children { 191 for _, entry := range child.entries() { 192 if !n.isRoot() { 193 entry = append([]string{n.name}, entry...) 194 } 195 rv = append(rv, entry) 196 } 197 } 198 return rv 199} 200 201// tree represents a directory tree of task inputs. 202type tree struct { 203 root *node 204} 205 206// newTree returns a tree instance. 207func newTree() *tree { 208 return &tree{ 209 root: newNode(""), 210 } 211} 212 213// add the given path to the tree. Entries may be combined as defined by 214// combinePathsThreshold. 215func (t *tree) add(p string) { 216 split := strings.Split(p, "/") 217 t.root.add(split) 218} 219 220// entries returns all entries in the tree. Entries may be combined as defined 221// by combinePathsThreshold. 222func (t *tree) entries() []string { 223 entries := t.root.entries() 224 rv := make([]string, 0, len(entries)) 225 for _, entry := range entries { 226 rv = append(rv, strings.Join(append([]string{"skia"}, entry...), "/")) 227 } 228 sort.Strings(rv) 229 return rv 230} 231 232// generateCompileCAS creates the CasSpec used for tasks which build Skia. 233func generateCompileCAS(b *specs.TasksCfgBuilder, cfg *Config) { 234 t := newTree() 235 for _, path := range getRelevantPaths(cfg) { 236 t.add(path) 237 } 238 spec := &specs.CasSpec{ 239 Root: "..", 240 Paths: t.entries(), 241 Excludes: []string{rbe.ExcludeGitDir}, 242 } 243 b.MustAddCasSpec(CAS_COMPILE, spec) 244} 245