1cb93a386Sopenharmony_ci// Copyright 2020 The Chromium Authors. All rights reserved.
2cb93a386Sopenharmony_ci// Use of this source code is governed by a BSD-style license that can be
3cb93a386Sopenharmony_ci// found in the LICENSE file.
4cb93a386Sopenharmony_cipackage gen_tasks_logic
5cb93a386Sopenharmony_ci
6cb93a386Sopenharmony_ciimport (
7cb93a386Sopenharmony_ci	"log"
8cb93a386Sopenharmony_ci	"reflect"
9cb93a386Sopenharmony_ci	"strings"
10cb93a386Sopenharmony_ci	"time"
11cb93a386Sopenharmony_ci
12cb93a386Sopenharmony_ci	"go.skia.org/infra/go/cipd"
13cb93a386Sopenharmony_ci	"go.skia.org/infra/task_scheduler/go/specs"
14cb93a386Sopenharmony_ci)
15cb93a386Sopenharmony_ci
16cb93a386Sopenharmony_ci// taskBuilder is a helper for creating a task.
17cb93a386Sopenharmony_citype taskBuilder struct {
18cb93a386Sopenharmony_ci	*jobBuilder
19cb93a386Sopenharmony_ci	parts
20cb93a386Sopenharmony_ci	Name             string
21cb93a386Sopenharmony_ci	Spec             *specs.TaskSpec
22cb93a386Sopenharmony_ci	recipeProperties map[string]string
23cb93a386Sopenharmony_ci}
24cb93a386Sopenharmony_ci
25cb93a386Sopenharmony_ci// newTaskBuilder returns a taskBuilder instance.
26cb93a386Sopenharmony_cifunc newTaskBuilder(b *jobBuilder, name string) *taskBuilder {
27cb93a386Sopenharmony_ci	parts, err := b.jobNameSchema.ParseJobName(name)
28cb93a386Sopenharmony_ci	if err != nil {
29cb93a386Sopenharmony_ci		log.Fatal(err)
30cb93a386Sopenharmony_ci	}
31cb93a386Sopenharmony_ci	return &taskBuilder{
32cb93a386Sopenharmony_ci		jobBuilder:       b,
33cb93a386Sopenharmony_ci		parts:            parts,
34cb93a386Sopenharmony_ci		Name:             name,
35cb93a386Sopenharmony_ci		Spec:             &specs.TaskSpec{},
36cb93a386Sopenharmony_ci		recipeProperties: map[string]string{},
37cb93a386Sopenharmony_ci	}
38cb93a386Sopenharmony_ci}
39cb93a386Sopenharmony_ci
40cb93a386Sopenharmony_ci// attempts sets the desired MaxAttempts for this task.
41cb93a386Sopenharmony_cifunc (b *taskBuilder) attempts(a int) {
42cb93a386Sopenharmony_ci	b.Spec.MaxAttempts = a
43cb93a386Sopenharmony_ci}
44cb93a386Sopenharmony_ci
45cb93a386Sopenharmony_ci// cache adds the given caches to the task.
46cb93a386Sopenharmony_cifunc (b *taskBuilder) cache(caches ...*specs.Cache) {
47cb93a386Sopenharmony_ci	for _, c := range caches {
48cb93a386Sopenharmony_ci		alreadyHave := false
49cb93a386Sopenharmony_ci		for _, exist := range b.Spec.Caches {
50cb93a386Sopenharmony_ci			if c.Name == exist.Name {
51cb93a386Sopenharmony_ci				if !reflect.DeepEqual(c, exist) {
52cb93a386Sopenharmony_ci					log.Fatalf("Already have cache %s with a different definition!", c.Name)
53cb93a386Sopenharmony_ci				}
54cb93a386Sopenharmony_ci				alreadyHave = true
55cb93a386Sopenharmony_ci				break
56cb93a386Sopenharmony_ci			}
57cb93a386Sopenharmony_ci		}
58cb93a386Sopenharmony_ci		if !alreadyHave {
59cb93a386Sopenharmony_ci			b.Spec.Caches = append(b.Spec.Caches, c)
60cb93a386Sopenharmony_ci		}
61cb93a386Sopenharmony_ci	}
62cb93a386Sopenharmony_ci}
63cb93a386Sopenharmony_ci
64cb93a386Sopenharmony_ci// cmd sets the command for the task.
65cb93a386Sopenharmony_cifunc (b *taskBuilder) cmd(c ...string) {
66cb93a386Sopenharmony_ci	b.Spec.Command = c
67cb93a386Sopenharmony_ci}
68cb93a386Sopenharmony_ci
69cb93a386Sopenharmony_ci// dimension adds the given dimensions to the task.
70cb93a386Sopenharmony_cifunc (b *taskBuilder) dimension(dims ...string) {
71cb93a386Sopenharmony_ci	for _, dim := range dims {
72cb93a386Sopenharmony_ci		if !In(dim, b.Spec.Dimensions) {
73cb93a386Sopenharmony_ci			b.Spec.Dimensions = append(b.Spec.Dimensions, dim)
74cb93a386Sopenharmony_ci		}
75cb93a386Sopenharmony_ci	}
76cb93a386Sopenharmony_ci}
77cb93a386Sopenharmony_ci
78cb93a386Sopenharmony_ci// expiration sets the expiration of the task.
79cb93a386Sopenharmony_cifunc (b *taskBuilder) expiration(e time.Duration) {
80cb93a386Sopenharmony_ci	b.Spec.Expiration = e
81cb93a386Sopenharmony_ci}
82cb93a386Sopenharmony_ci
83cb93a386Sopenharmony_ci// idempotent marks the task as idempotent.
84cb93a386Sopenharmony_cifunc (b *taskBuilder) idempotent() {
85cb93a386Sopenharmony_ci	b.Spec.Idempotent = true
86cb93a386Sopenharmony_ci}
87cb93a386Sopenharmony_ci
88cb93a386Sopenharmony_ci// cas sets the CasSpec used by the task.
89cb93a386Sopenharmony_cifunc (b *taskBuilder) cas(casSpec string) {
90cb93a386Sopenharmony_ci	b.Spec.CasSpec = casSpec
91cb93a386Sopenharmony_ci}
92cb93a386Sopenharmony_ci
93cb93a386Sopenharmony_ci// env appends the given values to the given environment variable for the task.
94cb93a386Sopenharmony_cifunc (b *taskBuilder) env(key string, values ...string) {
95cb93a386Sopenharmony_ci	if b.Spec.EnvPrefixes == nil {
96cb93a386Sopenharmony_ci		b.Spec.EnvPrefixes = map[string][]string{}
97cb93a386Sopenharmony_ci	}
98cb93a386Sopenharmony_ci	for _, value := range values {
99cb93a386Sopenharmony_ci		if !In(value, b.Spec.EnvPrefixes[key]) {
100cb93a386Sopenharmony_ci			b.Spec.EnvPrefixes[key] = append(b.Spec.EnvPrefixes[key], value)
101cb93a386Sopenharmony_ci		}
102cb93a386Sopenharmony_ci	}
103cb93a386Sopenharmony_ci}
104cb93a386Sopenharmony_ci
105cb93a386Sopenharmony_ci// addToPATH adds the given locations to PATH for the task.
106cb93a386Sopenharmony_cifunc (b *taskBuilder) addToPATH(loc ...string) {
107cb93a386Sopenharmony_ci	b.env("PATH", loc...)
108cb93a386Sopenharmony_ci}
109cb93a386Sopenharmony_ci
110cb93a386Sopenharmony_ci// output adds the given paths as outputs to the task, which results in their
111cb93a386Sopenharmony_ci// contents being uploaded to the isolate server.
112cb93a386Sopenharmony_cifunc (b *taskBuilder) output(paths ...string) {
113cb93a386Sopenharmony_ci	for _, path := range paths {
114cb93a386Sopenharmony_ci		if !In(path, b.Spec.Outputs) {
115cb93a386Sopenharmony_ci			b.Spec.Outputs = append(b.Spec.Outputs, path)
116cb93a386Sopenharmony_ci		}
117cb93a386Sopenharmony_ci	}
118cb93a386Sopenharmony_ci}
119cb93a386Sopenharmony_ci
120cb93a386Sopenharmony_ci// serviceAccount sets the service account for this task.
121cb93a386Sopenharmony_cifunc (b *taskBuilder) serviceAccount(sa string) {
122cb93a386Sopenharmony_ci	b.Spec.ServiceAccount = sa
123cb93a386Sopenharmony_ci}
124cb93a386Sopenharmony_ci
125cb93a386Sopenharmony_ci// timeout sets the timeout(s) for this task.
126cb93a386Sopenharmony_cifunc (b *taskBuilder) timeout(timeout time.Duration) {
127cb93a386Sopenharmony_ci	b.Spec.ExecutionTimeout = timeout
128cb93a386Sopenharmony_ci	b.Spec.IoTimeout = timeout // With kitchen, step logs don't count toward IoTimeout.
129cb93a386Sopenharmony_ci}
130cb93a386Sopenharmony_ci
131cb93a386Sopenharmony_ci// dep adds the given tasks as dependencies of this task.
132cb93a386Sopenharmony_cifunc (b *taskBuilder) dep(tasks ...string) {
133cb93a386Sopenharmony_ci	for _, task := range tasks {
134cb93a386Sopenharmony_ci		if !In(task, b.Spec.Dependencies) {
135cb93a386Sopenharmony_ci			b.Spec.Dependencies = append(b.Spec.Dependencies, task)
136cb93a386Sopenharmony_ci		}
137cb93a386Sopenharmony_ci	}
138cb93a386Sopenharmony_ci}
139cb93a386Sopenharmony_ci
140cb93a386Sopenharmony_ci// cipd adds the given CIPD packages to the task.
141cb93a386Sopenharmony_cifunc (b *taskBuilder) cipd(pkgs ...*specs.CipdPackage) {
142cb93a386Sopenharmony_ci	for _, pkg := range pkgs {
143cb93a386Sopenharmony_ci		alreadyHave := false
144cb93a386Sopenharmony_ci		for _, exist := range b.Spec.CipdPackages {
145cb93a386Sopenharmony_ci			if pkg.Name == exist.Name {
146cb93a386Sopenharmony_ci				if !reflect.DeepEqual(pkg, exist) {
147cb93a386Sopenharmony_ci					log.Fatalf("Already have package %s with a different definition!", pkg.Name)
148cb93a386Sopenharmony_ci				}
149cb93a386Sopenharmony_ci				alreadyHave = true
150cb93a386Sopenharmony_ci				break
151cb93a386Sopenharmony_ci			}
152cb93a386Sopenharmony_ci		}
153cb93a386Sopenharmony_ci		if !alreadyHave {
154cb93a386Sopenharmony_ci			b.Spec.CipdPackages = append(b.Spec.CipdPackages, pkg)
155cb93a386Sopenharmony_ci		}
156cb93a386Sopenharmony_ci	}
157cb93a386Sopenharmony_ci}
158cb93a386Sopenharmony_ci
159cb93a386Sopenharmony_ci// useIsolatedAssets returns true if this task should use assets which are
160cb93a386Sopenharmony_ci// isolated rather than downloading directly from CIPD.
161cb93a386Sopenharmony_cifunc (b *taskBuilder) useIsolatedAssets() bool {
162cb93a386Sopenharmony_ci	// Only do this on the RPIs for now. Other, faster machines shouldn't
163cb93a386Sopenharmony_ci	// see much benefit and we don't need the extra complexity, for now.
164cb93a386Sopenharmony_ci	if b.os("Android", "ChromeOS", "iOS") {
165cb93a386Sopenharmony_ci		return true
166cb93a386Sopenharmony_ci	}
167cb93a386Sopenharmony_ci	return false
168cb93a386Sopenharmony_ci}
169cb93a386Sopenharmony_ci
170cb93a386Sopenharmony_ci// uploadAssetCASCfg represents a task which copies a CIPD package into
171cb93a386Sopenharmony_ci// isolate.
172cb93a386Sopenharmony_citype uploadAssetCASCfg struct {
173cb93a386Sopenharmony_ci	alwaysIsolate  bool
174cb93a386Sopenharmony_ci	uploadTaskName string
175cb93a386Sopenharmony_ci	path           string
176cb93a386Sopenharmony_ci}
177cb93a386Sopenharmony_ci
178cb93a386Sopenharmony_ci// asset adds the given assets to the task as CIPD packages.
179cb93a386Sopenharmony_cifunc (b *taskBuilder) asset(assets ...string) {
180cb93a386Sopenharmony_ci	shouldIsolate := b.useIsolatedAssets()
181cb93a386Sopenharmony_ci	pkgs := make([]*specs.CipdPackage, 0, len(assets))
182cb93a386Sopenharmony_ci	for _, asset := range assets {
183cb93a386Sopenharmony_ci		if cfg, ok := ISOLATE_ASSET_MAPPING[asset]; ok && (cfg.alwaysIsolate || shouldIsolate) {
184cb93a386Sopenharmony_ci			b.dep(b.uploadCIPDAssetToCAS(asset))
185cb93a386Sopenharmony_ci		} else {
186cb93a386Sopenharmony_ci			pkgs = append(pkgs, b.MustGetCipdPackageFromAsset(asset))
187cb93a386Sopenharmony_ci		}
188cb93a386Sopenharmony_ci	}
189cb93a386Sopenharmony_ci	b.cipd(pkgs...)
190cb93a386Sopenharmony_ci}
191cb93a386Sopenharmony_ci
192cb93a386Sopenharmony_ci// usesCCache adds attributes to tasks which use ccache.
193cb93a386Sopenharmony_cifunc (b *taskBuilder) usesCCache() {
194cb93a386Sopenharmony_ci	b.cache(CACHES_CCACHE...)
195cb93a386Sopenharmony_ci}
196cb93a386Sopenharmony_ci
197cb93a386Sopenharmony_ci// usesGit adds attributes to tasks which use git.
198cb93a386Sopenharmony_cifunc (b *taskBuilder) usesGit() {
199cb93a386Sopenharmony_ci	b.cache(CACHES_GIT...)
200cb93a386Sopenharmony_ci	if b.matchOs("Win") || b.matchExtraConfig("Win") {
201cb93a386Sopenharmony_ci		b.cipd(specs.CIPD_PKGS_GIT_WINDOWS_AMD64...)
202cb93a386Sopenharmony_ci	} else if b.matchOs("Mac") || b.matchExtraConfig("Mac") {
203cb93a386Sopenharmony_ci		b.cipd(specs.CIPD_PKGS_GIT_MAC_AMD64...)
204cb93a386Sopenharmony_ci	} else {
205cb93a386Sopenharmony_ci		b.cipd(specs.CIPD_PKGS_GIT_LINUX_AMD64...)
206cb93a386Sopenharmony_ci	}
207cb93a386Sopenharmony_ci}
208cb93a386Sopenharmony_ci
209cb93a386Sopenharmony_ci// usesGo adds attributes to tasks which use go. Recipes should use
210cb93a386Sopenharmony_ci// "with api.context(env=api.infra.go_env)".
211cb93a386Sopenharmony_cifunc (b *taskBuilder) usesGo() {
212cb93a386Sopenharmony_ci	b.usesGit() // Go requires Git.
213cb93a386Sopenharmony_ci	b.cache(CACHES_GO...)
214cb93a386Sopenharmony_ci	pkg := b.MustGetCipdPackageFromAsset("go")
215cb93a386Sopenharmony_ci	if b.matchOs("Win") || b.matchExtraConfig("Win") {
216cb93a386Sopenharmony_ci		pkg = b.MustGetCipdPackageFromAsset("go_win")
217cb93a386Sopenharmony_ci		pkg.Path = "go"
218cb93a386Sopenharmony_ci	}
219cb93a386Sopenharmony_ci	b.cipd(pkg)
220cb93a386Sopenharmony_ci	b.addToPATH(pkg.Path + "/go/bin")
221cb93a386Sopenharmony_ci	b.env("GOROOT", pkg.Path+"/go")
222cb93a386Sopenharmony_ci}
223cb93a386Sopenharmony_ci
224cb93a386Sopenharmony_ci// usesDocker adds attributes to tasks which use docker.
225cb93a386Sopenharmony_cifunc (b *taskBuilder) usesDocker() {
226cb93a386Sopenharmony_ci	b.dimension("docker_installed:true")
227cb93a386Sopenharmony_ci}
228cb93a386Sopenharmony_ci
229cb93a386Sopenharmony_ci// recipeProp adds the given recipe property key/value pair. Panics if
230cb93a386Sopenharmony_ci// getRecipeProps() was already called.
231cb93a386Sopenharmony_cifunc (b *taskBuilder) recipeProp(key, value string) {
232cb93a386Sopenharmony_ci	if b.recipeProperties == nil {
233cb93a386Sopenharmony_ci		log.Fatal("taskBuilder.recipeProp() cannot be called after taskBuilder.getRecipeProps()!")
234cb93a386Sopenharmony_ci	}
235cb93a386Sopenharmony_ci	b.recipeProperties[key] = value
236cb93a386Sopenharmony_ci}
237cb93a386Sopenharmony_ci
238cb93a386Sopenharmony_ci// recipeProps calls recipeProp for every key/value pair in the given map.
239cb93a386Sopenharmony_ci// Panics if getRecipeProps() was already called.
240cb93a386Sopenharmony_cifunc (b *taskBuilder) recipeProps(props map[string]string) {
241cb93a386Sopenharmony_ci	for k, v := range props {
242cb93a386Sopenharmony_ci		b.recipeProp(k, v)
243cb93a386Sopenharmony_ci	}
244cb93a386Sopenharmony_ci}
245cb93a386Sopenharmony_ci
246cb93a386Sopenharmony_ci// getRecipeProps returns JSON-encoded recipe properties. Subsequent calls to
247cb93a386Sopenharmony_ci// recipeProp[s] will panic, to prevent accidentally adding recipe properties
248cb93a386Sopenharmony_ci// after they have been added to the task.
249cb93a386Sopenharmony_cifunc (b *taskBuilder) getRecipeProps() string {
250cb93a386Sopenharmony_ci	props := make(map[string]interface{}, len(b.recipeProperties)+2)
251cb93a386Sopenharmony_ci	// TODO(borenet): I'm not sure why we supply the original task name
252cb93a386Sopenharmony_ci	// and not the upload task name.  We should investigate whether this is
253cb93a386Sopenharmony_ci	// needed.
254cb93a386Sopenharmony_ci	buildername := b.Name
255cb93a386Sopenharmony_ci	if b.role("Upload") {
256cb93a386Sopenharmony_ci		buildername = strings.TrimPrefix(buildername, "Upload-")
257cb93a386Sopenharmony_ci	}
258cb93a386Sopenharmony_ci	props["buildername"] = buildername
259cb93a386Sopenharmony_ci	props["$kitchen"] = struct {
260cb93a386Sopenharmony_ci		DevShell bool `json:"devshell"`
261cb93a386Sopenharmony_ci		GitAuth  bool `json:"git_auth"`
262cb93a386Sopenharmony_ci	}{
263cb93a386Sopenharmony_ci		DevShell: true,
264cb93a386Sopenharmony_ci		GitAuth:  true,
265cb93a386Sopenharmony_ci	}
266cb93a386Sopenharmony_ci	for k, v := range b.recipeProperties {
267cb93a386Sopenharmony_ci		props[k] = v
268cb93a386Sopenharmony_ci	}
269cb93a386Sopenharmony_ci	b.recipeProperties = nil
270cb93a386Sopenharmony_ci	return marshalJson(props)
271cb93a386Sopenharmony_ci}
272cb93a386Sopenharmony_ci
273cb93a386Sopenharmony_ci// cipdPlatform returns the CIPD platform for this task.
274cb93a386Sopenharmony_cifunc (b *taskBuilder) cipdPlatform() string {
275cb93a386Sopenharmony_ci	if b.role("Upload") {
276cb93a386Sopenharmony_ci		return cipd.PlatformLinuxAmd64
277cb93a386Sopenharmony_ci	} else if b.matchOs("Win") || b.matchExtraConfig("Win") {
278cb93a386Sopenharmony_ci		return cipd.PlatformWindowsAmd64
279cb93a386Sopenharmony_ci	} else if b.matchOs("Mac") {
280cb93a386Sopenharmony_ci		return cipd.PlatformMacAmd64
281cb93a386Sopenharmony_ci	} else if b.matchArch("Arm64") {
282cb93a386Sopenharmony_ci		return cipd.PlatformLinuxArm64
283cb93a386Sopenharmony_ci	} else if b.matchOs("Android", "ChromeOS") {
284cb93a386Sopenharmony_ci		return cipd.PlatformLinuxArm64
285cb93a386Sopenharmony_ci	} else if b.matchOs("iOS") {
286cb93a386Sopenharmony_ci		return cipd.PlatformLinuxArmv6l
287cb93a386Sopenharmony_ci	} else {
288cb93a386Sopenharmony_ci		return cipd.PlatformLinuxAmd64
289cb93a386Sopenharmony_ci	}
290cb93a386Sopenharmony_ci}
291cb93a386Sopenharmony_ci
292cb93a386Sopenharmony_ci// usesPython adds attributes to tasks which use python.
293cb93a386Sopenharmony_cifunc (b *taskBuilder) usesPython() {
294cb93a386Sopenharmony_ci	pythonPkgs := cipd.PkgsPython[b.cipdPlatform()]
295cb93a386Sopenharmony_ci	b.cipd(pythonPkgs...)
296cb93a386Sopenharmony_ci	b.addToPATH(
297cb93a386Sopenharmony_ci		"cipd_bin_packages/cpython",
298cb93a386Sopenharmony_ci		"cipd_bin_packages/cpython/bin",
299cb93a386Sopenharmony_ci		"cipd_bin_packages/cpython3",
300cb93a386Sopenharmony_ci		"cipd_bin_packages/cpython3/bin",
301cb93a386Sopenharmony_ci	)
302cb93a386Sopenharmony_ci	b.cache(&specs.Cache{
303cb93a386Sopenharmony_ci		Name: "vpython",
304cb93a386Sopenharmony_ci		Path: "cache/vpython",
305cb93a386Sopenharmony_ci	})
306cb93a386Sopenharmony_ci	b.env("VPYTHON_VIRTUALENV_ROOT", "cache/vpython")
307cb93a386Sopenharmony_ci	b.env("VPYTHON_LOG_TRACE", "1")
308cb93a386Sopenharmony_ci}
309cb93a386Sopenharmony_ci
310cb93a386Sopenharmony_cifunc (b *taskBuilder) usesNode() {
311cb93a386Sopenharmony_ci	// It is very important when including node via CIPD to also add it to the PATH of the
312cb93a386Sopenharmony_ci	// taskdriver or mysterious things can happen when subprocesses try to resolve node/npm.
313cb93a386Sopenharmony_ci	b.asset("node")
314cb93a386Sopenharmony_ci	b.addToPATH("node/node/bin")
315cb93a386Sopenharmony_ci}
316