• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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