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 ".vpython", 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 "go_repositories.bzl", 62 "infra/bots/assets/android_ndk_darwin/VERSION", 63 "infra/bots/assets/android_ndk_linux/VERSION", 64 "infra/bots/assets/android_ndk_windows/VERSION", 65 "infra/bots/assets/cast_toolchain/VERSION", 66 "infra/bots/assets/clang_linux/VERSION", 67 "infra/bots/assets/clang_win/VERSION", 68 "infra/bots/run_recipe.py", 69 "infra/bots/task_drivers", 70 "infra/canvaskit", 71 "infra/pathkit", 72 "package.json", 73 "package-lock.json", 74 "requirements.txt", 75 "resources", 76 "third_party/externals", 77 "toolchain", 78 } 79) 80 81// getAllCheckedInPaths returns every path checked in to the repo. 82func getAllCheckedInPaths(cfg *Config) []string { 83 cmd := exec.Command("git", "ls-files") 84 // Use cfg.PathToSkia to get to the Skia checkout, in case this is used by 85 // another repo. 86 cmd.Dir = filepath.Join(CheckoutRoot(), cfg.PathToSkia) 87 output, err := cmd.CombinedOutput() 88 if err != nil { 89 log.Fatal(err) 90 } 91 split := strings.Split(string(output), "\n") 92 rv := make([]string, 0, len(split)) 93 for _, line := range split { 94 if line != "" { 95 rv = append(rv, line) 96 } 97 } 98 return rv 99} 100 101// getRelevantPaths returns all paths needed by compile tasks. 102func getRelevantPaths(cfg *Config) []string { 103 rv := []string{} 104 for _, path := range getAllCheckedInPaths(cfg) { 105 for _, regex := range pathRegexes { 106 if regex.MatchString(path) { 107 rv = append(rv, path) 108 break 109 } 110 } 111 } 112 return append(rv, explicitPaths...) 113} 114 115// node is a single node in a directory tree of task inputs. 116type node struct { 117 children map[string]*node 118 name string 119 isLeaf bool 120} 121 122// newNode returns a node instance. 123func newNode(name string) *node { 124 return &node{ 125 children: map[string]*node{}, 126 name: name, 127 isLeaf: false, 128 } 129} 130 131// isRoot returns true iff this is the root node. 132func (n *node) isRoot() bool { 133 return n.name == "" 134} 135 136// add the given entry (given as a slice of path components) to the node. 137func (n *node) add(entry []string) { 138 // Remove the first element if we're not the root node. 139 if !n.isRoot() { 140 if entry[0] != n.name { 141 log.Fatalf("Failed to compute compile CAS inputs; attempting to add entry %v to node %q", entry, n.name) 142 } 143 entry = entry[1:] 144 145 // If the entry is now empty, this node is a leaf. 146 if len(entry) == 0 { 147 n.isLeaf = true 148 return 149 } 150 } 151 152 // Add a child node. 153 if !n.isLeaf { 154 name := entry[0] 155 child, ok := n.children[name] 156 if !ok { 157 child = newNode(name) 158 n.children[name] = child 159 } 160 child.add(entry) 161 162 // If we have more than combinePathsThreshold immediate children, 163 // combine them into this node. 164 immediateChilden := 0 165 for _, child := range n.children { 166 if child.isLeaf { 167 immediateChilden++ 168 } 169 if !n.isRoot() && immediateChilden >= combinePathsThreshold { 170 n.isLeaf = true 171 n.children = map[string]*node{} 172 } 173 } 174 } 175} 176 177// entries returns the entries represented by this node and its children. 178// Will not return children in the following cases: 179// - This Node is a leaf, ie. it represents an entry which was explicitly 180// inserted into the Tree, as opposed to only part of a path to other 181// entries. 182// - This Node has immediate children exceeding combinePathsThreshold and 183// thus has been upgraded to a leaf node. 184func (n *node) entries() [][]string { 185 if n.isLeaf { 186 return [][]string{{n.name}} 187 } 188 rv := [][]string{} 189 for _, child := range n.children { 190 for _, entry := range child.entries() { 191 if !n.isRoot() { 192 entry = append([]string{n.name}, entry...) 193 } 194 rv = append(rv, entry) 195 } 196 } 197 return rv 198} 199 200// tree represents a directory tree of task inputs. 201type tree struct { 202 root *node 203} 204 205// newTree returns a tree instance. 206func newTree() *tree { 207 return &tree{ 208 root: newNode(""), 209 } 210} 211 212// add the given path to the tree. Entries may be combined as defined by 213// combinePathsThreshold. 214func (t *tree) add(p string) { 215 split := strings.Split(p, "/") 216 t.root.add(split) 217} 218 219// entries returns all entries in the tree. Entries may be combined as defined 220// by combinePathsThreshold. 221func (t *tree) entries() []string { 222 entries := t.root.entries() 223 rv := make([]string, 0, len(entries)) 224 for _, entry := range entries { 225 rv = append(rv, strings.Join(append([]string{"skia"}, entry...), "/")) 226 } 227 sort.Strings(rv) 228 return rv 229} 230 231// generateCompileCAS creates the CasSpec used for tasks which build Skia. 232func generateCompileCAS(b *specs.TasksCfgBuilder, cfg *Config) { 233 t := newTree() 234 for _, path := range getRelevantPaths(cfg) { 235 t.add(path) 236 } 237 spec := &specs.CasSpec{ 238 Root: "..", 239 Paths: t.entries(), 240 Excludes: []string{rbe.ExcludeGitDir}, 241 } 242 b.MustAddCasSpec(CAS_COMPILE, spec) 243} 244