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