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