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