blob: 71a53a2a975e9269de2de8d0fee5a69a23803213 [file] [log] [blame]
borenetdb182c72016-09-30 12:53:12 -07001// Copyright 2016 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 main
6
7/*
8 Generate the tasks.json file.
9*/
10
11import (
borenetdb182c72016-09-30 12:53:12 -070012 "encoding/json"
Eric Boren27225492017-02-01 15:56:55 -050013 "flag"
borenetdb182c72016-09-30 12:53:12 -070014 "fmt"
Eric Boren27225492017-02-01 15:56:55 -050015 "io/ioutil"
borenetdb182c72016-09-30 12:53:12 -070016 "os"
17 "path"
Eric Boren27225492017-02-01 15:56:55 -050018 "regexp"
borenetdb182c72016-09-30 12:53:12 -070019 "sort"
20 "strings"
Eric Boren4b254b92016-11-08 12:55:32 -050021 "time"
borenetdb182c72016-09-30 12:53:12 -070022
23 "github.com/skia-dev/glog"
borenetdb182c72016-09-30 12:53:12 -070024 "go.skia.org/infra/go/util"
25 "go.skia.org/infra/task_scheduler/go/specs"
26)
27
28const (
Eric Boren8b3f9e62017-04-04 09:06:16 -040029 BUNDLE_RECIPES_NAME = "Housekeeper-PerCommit-BundleRecipes"
30
Kevin Lubick797ef162016-12-15 10:45:08 -050031 DEFAULT_OS = DEFAULT_OS_LINUX
32 DEFAULT_OS_LINUX = "Ubuntu-14.04"
borenetdb182c72016-09-30 12:53:12 -070033
borenetdb182c72016-09-30 12:53:12 -070034 // Name prefix for upload jobs.
35 PREFIX_UPLOAD = "Upload"
36)
37
38var (
39 // "Constants"
40
Eric Boren27225492017-02-01 15:56:55 -050041 // Top-level list of all jobs to run at each commit; loaded from
42 // jobs.json.
43 JOBS []string
44
45 // Mapping of human-friendly Android device names to a pair of {device_type, device_os}.
46 ANDROID_MAPPING map[string][]string
47
48 // General configuration information.
49 CONFIG struct {
Eric Boren965861b2017-02-06 15:38:41 -050050 GsBucketGm string `json:"gs_bucket_gm"`
51 GsBucketNano string `json:"gs_bucket_nano"`
52 NoUpload []string `json:"no_upload"`
53 Pool string `json:"pool"`
borenetdb182c72016-09-30 12:53:12 -070054 }
55
Eric Boren27225492017-02-01 15:56:55 -050056 // Mapping of human-friendly GPU names to PCI IDs.
57 GPU_MAPPING map[string]string
borenetdb182c72016-09-30 12:53:12 -070058
59 // Defines the structure of job names.
60 jobNameSchema *JobNameSchema
Eric Boren27225492017-02-01 15:56:55 -050061
62 // Flags.
Eric Boren1f8be682017-02-07 09:16:30 -050063 androidMapFile = flag.String("android_map", "", "JSON file containing a mapping of human-friendly Android device names to a pair of {device_type, device_os}.")
64 builderNameSchemaFile = flag.String("builder_name_schema", "", "Path to the builder_name_schema.json file. If not specified, uses infra/bots/recipe_modules/builder_name_schema/builder_name_schema.json from this repo.")
65 assetsDir = flag.String("assets_dir", "", "Directory containing assets.")
66 cfgFile = flag.String("cfg_file", "", "JSON file containing general configuration information.")
67 gpuMapFile = flag.String("gpu_map", "", "JSON file containing a mapping of human-friendly GPU names to PCI IDs.")
68 jobsFile = flag.String("jobs", "", "JSON file containing jobs to run.")
borenetdb182c72016-09-30 12:53:12 -070069)
70
Eric Boren27225492017-02-01 15:56:55 -050071// linuxGceDimensions are the Swarming dimensions for Linux GCE
72// instances.
73func linuxGceDimensions() []string {
74 return []string{
75 "cpu:x86-64-avx2",
76 "gpu:none",
77 fmt.Sprintf("os:%s", DEFAULT_OS_LINUX),
78 fmt.Sprintf("pool:%s", CONFIG.Pool),
79 }
80}
81
borenetdb182c72016-09-30 12:53:12 -070082// deriveCompileTaskName returns the name of a compile task based on the given
83// job name.
84func deriveCompileTaskName(jobName string, parts map[string]string) string {
85 if parts["role"] == "Housekeeper" {
86 return "Build-Ubuntu-GCC-x86_64-Release-Shared"
87 } else if parts["role"] == "Test" || parts["role"] == "Perf" {
88 task_os := parts["os"]
89 ec := parts["extra_config"]
Kevin Lubickb03b5ac2016-11-14 13:42:27 -050090 ec = strings.TrimSuffix(ec, "_Skpbench")
Ben Wagner46df2a12017-02-21 19:10:24 -050091 ec = strings.TrimSuffix(ec, "_AbandonGpuContext")
92 ec = strings.TrimSuffix(ec, "_PreAbandonGpuContext")
Ben Wagneradfeaf12017-02-21 19:27:07 -050093 if ec == "Valgrind" {
94 // skia:6267
95 ec = ""
96 }
borenetdb182c72016-09-30 12:53:12 -070097 if task_os == "Android" {
98 if ec == "Vulkan" {
99 ec = "Android_Vulkan"
borenetdb182c72016-09-30 12:53:12 -0700100 }
borenet52383432016-10-17 10:17:53 -0700101 task_os = "Ubuntu"
Kevin Lubickdcd2a902017-03-08 14:01:01 -0500102 } else if task_os == "Chromecast" {
103 task_os = "Ubuntu"
104 ec = "Chromecast"
Kevin Lubick261ea192017-04-05 07:32:45 -0400105 } else if strings.HasPrefix(task_os, "Chromebook") {
106 ec = task_os
107 task_os = "Ubuntu"
borenetdb182c72016-09-30 12:53:12 -0700108 } else if task_os == "iOS" {
109 ec = task_os
110 task_os = "Mac"
111 } else if strings.Contains(task_os, "Win") {
112 task_os = "Win"
Kevin Lubick893c49e2017-01-17 15:15:40 -0500113 } else if strings.Contains(task_os, "Ubuntu") {
114 task_os = "Ubuntu"
borenetdb182c72016-09-30 12:53:12 -0700115 }
Eric Boren50831302016-11-18 13:10:51 -0500116 jobNameMap := map[string]string{
borenetdb182c72016-09-30 12:53:12 -0700117 "role": "Build",
118 "os": task_os,
119 "compiler": parts["compiler"],
120 "target_arch": parts["arch"],
121 "configuration": parts["configuration"],
Eric Boren50831302016-11-18 13:10:51 -0500122 }
123 if ec != "" {
124 jobNameMap["extra_config"] = ec
125 }
126 name, err := jobNameSchema.MakeJobName(jobNameMap)
borenetdb182c72016-09-30 12:53:12 -0700127 if err != nil {
128 glog.Fatal(err)
129 }
130 return name
131 } else {
132 return jobName
133 }
134}
135
136// swarmDimensions generates swarming bot dimensions for the given task.
137func swarmDimensions(parts map[string]string) []string {
borenetdb182c72016-09-30 12:53:12 -0700138 d := map[string]string{
Eric Boren27225492017-02-01 15:56:55 -0500139 "pool": CONFIG.Pool,
borenetdb182c72016-09-30 12:53:12 -0700140 }
141 if os, ok := parts["os"]; ok {
Eric Boren54ff2fc2016-12-02 12:09:10 -0500142 d["os"] = map[string]string{
Kevin Lubick291547d2017-03-21 09:25:34 -0400143 "Android": "Android",
144 "Chromecast": "Android",
145 "Mac": "Mac-10.11",
146 "Ubuntu": DEFAULT_OS_LINUX,
147 "Ubuntu16": "Ubuntu-16.10",
148 "Win": "Windows-2008ServerR2-SP1",
149 "Win10": "Windows-10-14393",
150 "Win2k8": "Windows-2008ServerR2-SP1",
151 "Win8": "Windows-8.1-SP0",
152 "iOS": "iOS-9.3.1",
Eric Boren54ff2fc2016-12-02 12:09:10 -0500153 }[os]
Ben Wagner73557372016-12-29 16:27:03 -0500154 // Chrome Golo has a different Windows image.
155 if parts["model"] == "Golo" && os == "Win10" {
156 d["os"] = "Windows-10-10586"
Ben Wagner17f811b2016-12-22 08:40:14 -0500157 }
borenetdb182c72016-09-30 12:53:12 -0700158 } else {
159 d["os"] = DEFAULT_OS
160 }
borenetdb182c72016-09-30 12:53:12 -0700161 if parts["role"] == "Test" || parts["role"] == "Perf" {
Kevin Lubick291547d2017-03-21 09:25:34 -0400162 if strings.Contains(parts["os"], "Android") || strings.Contains(parts["os"], "Chromecast") {
borenetdb182c72016-09-30 12:53:12 -0700163 // For Android, the device type is a better dimension
164 // than CPU or GPU.
Eric Boren27225492017-02-01 15:56:55 -0500165 deviceInfo, ok := ANDROID_MAPPING[parts["model"]]
166 if !ok {
167 glog.Fatalf("Entry %q not found in Android mapping: %v", parts["model"], ANDROID_MAPPING)
168 }
Eric Boren4b254b92016-11-08 12:55:32 -0500169 d["device_type"] = deviceInfo[0]
170 d["device_os"] = deviceInfo[1]
borenetdb182c72016-09-30 12:53:12 -0700171 } else if strings.Contains(parts["os"], "iOS") {
172 d["device"] = map[string]string{
Eric Boren792079c2016-11-09 14:03:20 -0500173 "iPadMini4": "iPad5,1",
borenetdb182c72016-09-30 12:53:12 -0700174 }[parts["model"]]
borenetdb182c72016-09-30 12:53:12 -0700175 } else if parts["cpu_or_gpu"] == "CPU" {
176 d["gpu"] = "none"
177 d["cpu"] = map[string]string{
178 "AVX": "x86-64",
179 "AVX2": "x86-64-avx2",
180 "SSE4": "x86-64",
181 }[parts["cpu_or_gpu_value"]]
182 if strings.Contains(parts["os"], "Win") && parts["cpu_or_gpu_value"] == "AVX2" {
183 // AVX2 is not correctly detected on Windows. Fall back on other
184 // dimensions to ensure that we correctly target machines which we know
185 // have AVX2 support.
186 d["cpu"] = "x86-64"
187 d["os"] = "Windows-2008ServerR2-SP1"
188 }
189 } else {
Eric Boren27225492017-02-01 15:56:55 -0500190 gpu, ok := GPU_MAPPING[parts["cpu_or_gpu_value"]]
191 if !ok {
192 glog.Fatalf("Entry %q not found in GPU mapping: %v", parts["cpu_or_gpu_value"], GPU_MAPPING)
193 }
194 d["gpu"] = gpu
Ben Wagner08435892017-02-18 23:28:26 -0500195
196 // Hack: Specify machine_type dimension for NUCs and ShuttleCs. We
197 // temporarily have two types of machines with a GTX960. The only way to
198 // distinguish these bots is by machine_type.
199 machine_type, ok := map[string]string{
200 "NUC6i7KYK": "n1-highcpu-8",
201 "ShuttleC": "n1-standard-8",
202 }[parts["model"]]
203 if ok {
204 d["machine_type"] = machine_type
205 }
borenetdb182c72016-09-30 12:53:12 -0700206 }
207 } else {
208 d["gpu"] = "none"
Kevin Lubick4610a9b2017-03-22 15:54:54 -0400209 if d["os"] == DEFAULT_OS_LINUX {
210 return linuxGceDimensions()
211 }
borenetdb182c72016-09-30 12:53:12 -0700212 }
Kevin Lubick4610a9b2017-03-22 15:54:54 -0400213
borenetdb182c72016-09-30 12:53:12 -0700214 rv := make([]string, 0, len(d))
215 for k, v := range d {
216 rv = append(rv, fmt.Sprintf("%s:%s", k, v))
217 }
218 sort.Strings(rv)
219 return rv
220}
221
Eric Boren8b3f9e62017-04-04 09:06:16 -0400222// bundleRecipes generates the task to bundle and isolate the recipes.
223func bundleRecipes(b *specs.TasksCfgBuilder) string {
224 b.MustAddTask(BUNDLE_RECIPES_NAME, &specs.TaskSpec{
225 CipdPackages: []*specs.CipdPackage{},
226 Dimensions: linuxGceDimensions(),
227 ExtraArgs: []string{
228 "--workdir", "../../..", "bundle_recipes",
229 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
230 fmt.Sprintf("buildername=%s", BUNDLE_RECIPES_NAME),
231 "mastername=fake-master",
232 "buildnumber=2",
233 "slavename=fake-buildslave",
234 "nobuildbot=True",
235 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
236 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
237 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
238 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
239 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
240 },
241 Isolate: "bundle_recipes.isolate",
242 Priority: 0.95,
243 })
244 return BUNDLE_RECIPES_NAME
245}
246
Eric Boren43b9c6b2017-04-06 07:53:30 -0400247// useBundledRecipes returns true iff the given bot should use bundled recipes
248// instead of syncing recipe DEPS itself.
249func useBundledRecipes(parts map[string]string) bool {
250 // Use bundled recipes for all test/perf tasks.
251 return true
252}
253
borenetdb182c72016-09-30 12:53:12 -0700254// compile generates a compile task. Returns the name of the last task in the
255// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700256func compile(b *specs.TasksCfgBuilder, name string, parts map[string]string) string {
borenetdb182c72016-09-30 12:53:12 -0700257 // Collect the necessary CIPD packages.
258 pkgs := []*specs.CipdPackage{}
259
260 // Android bots require a toolchain.
261 if strings.Contains(name, "Android") {
borenetdb182c72016-09-30 12:53:12 -0700262 if strings.Contains(name, "Mac") {
boreneted20a702016-10-20 11:04:31 -0700263 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("android_ndk_darwin"))
Mike Klein86c2c0f2016-11-02 13:13:16 -0400264 } else if strings.Contains(name, "Win") {
Mike Kleine9215f02016-11-02 15:44:26 -0400265 pkg := b.MustGetCipdPackageFromAsset("android_ndk_windows")
266 pkg.Path = "n"
267 pkgs = append(pkgs, pkg)
borenetdb182c72016-09-30 12:53:12 -0700268 } else {
boreneted20a702016-10-20 11:04:31 -0700269 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("android_ndk_linux"))
borenetdb182c72016-09-30 12:53:12 -0700270 }
Kevin Lubickdcd2a902017-03-08 14:01:01 -0500271 } else if strings.Contains(name, "Chromecast") {
272 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("cast_toolchain"))
Kevin Lubick261ea192017-04-05 07:32:45 -0400273 } else if strings.Contains(name, "Chromebook") {
274 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("clang_linux"))
275 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("armhf_sysroot"))
276 if strings.Contains(name, "Chromebook_C100p") {
277 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("chromebook_c100p_lib"))
278 }
Kevin Lubick9c7dcac2017-01-18 09:24:56 -0500279 } else if strings.Contains(name, "Ubuntu") {
280 if strings.Contains(name, "Clang") {
281 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("clang_linux"))
282 }
283 if strings.Contains(name, "Vulkan") {
284 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("linux_vulkan_sdk"))
285 }
Mike Klein27dcee12016-11-09 16:31:42 -0500286 } else if strings.Contains(name, "Win") {
boreneted20a702016-10-20 11:04:31 -0700287 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("win_toolchain"))
borenetdb182c72016-09-30 12:53:12 -0700288 if strings.Contains(name, "Vulkan") {
boreneted20a702016-10-20 11:04:31 -0700289 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("win_vulkan_sdk"))
borenetdb182c72016-09-30 12:53:12 -0700290 }
291 }
292
Stephan Altmueller5f3b9402017-03-20 13:38:45 -0400293 // TODO(stephana): Remove this once all Mac machines are on the same
294 // OS version again. Move the call to swarmDimensions back to the
295 // creation of the TaskSpec struct below.
296 dimensions := swarmDimensions(parts)
297 if strings.Contains(name, "Mac") {
298 for idx, dim := range dimensions {
299 if strings.HasPrefix(dim, "os") {
300 dimensions[idx] = "os:Mac-10.12"
301 break
302 }
303 }
304 }
305
borenetdb182c72016-09-30 12:53:12 -0700306 // Add the task.
boreneted20a702016-10-20 11:04:31 -0700307 b.MustAddTask(name, &specs.TaskSpec{
borenetdb182c72016-09-30 12:53:12 -0700308 CipdPackages: pkgs,
Stephan Altmueller5f3b9402017-03-20 13:38:45 -0400309 Dimensions: dimensions,
borenetdb182c72016-09-30 12:53:12 -0700310 ExtraArgs: []string{
311 "--workdir", "../../..", "swarm_compile",
skia.buildbots2478c732016-11-04 14:37:26 -0400312 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700313 fmt.Sprintf("buildername=%s", name),
314 "mastername=fake-master",
315 "buildnumber=2",
316 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700317 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700318 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
319 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700320 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400321 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
322 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
borenetdb182c72016-09-30 12:53:12 -0700323 },
324 Isolate: "compile_skia.isolate",
325 Priority: 0.8,
boreneted20a702016-10-20 11:04:31 -0700326 })
Eric Boren8615fe52016-12-12 14:30:12 -0500327 // All compile tasks are runnable as their own Job. Assert that the Job
328 // is listed in JOBS.
329 if !util.In(name, JOBS) {
330 glog.Fatalf("Job %q is missing from the JOBS list!", name)
331 }
borenetdb182c72016-09-30 12:53:12 -0700332 return name
333}
334
335// recreateSKPs generates a RecreateSKPs task. Returns the name of the last
336// task in the generated chain of tasks, which the Job should add as a
337// dependency.
boreneted20a702016-10-20 11:04:31 -0700338func recreateSKPs(b *specs.TasksCfgBuilder, name string) string {
Eric Boren4b254b92016-11-08 12:55:32 -0500339 b.MustAddTask(name, &specs.TaskSpec{
Eric Borenf5a90e82016-11-15 15:18:20 -0500340 CipdPackages: []*specs.CipdPackage{},
Eric Boren27225492017-02-01 15:56:55 -0500341 Dimensions: linuxGceDimensions(),
Eric Borenf5a90e82016-11-15 15:18:20 -0500342 ExecutionTimeout: 4 * time.Hour,
Eric Boren4b254b92016-11-08 12:55:32 -0500343 ExtraArgs: []string{
344 "--workdir", "../../..", "swarm_RecreateSKPs",
345 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
346 fmt.Sprintf("buildername=%s", name),
347 "mastername=fake-master",
348 "buildnumber=2",
349 "slavename=fake-buildslave",
350 "nobuildbot=True",
351 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
352 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
353 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
354 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
355 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
356 },
Eric Borenf5a90e82016-11-15 15:18:20 -0500357 IoTimeout: 40 * time.Minute,
358 Isolate: "compile_skia.isolate",
359 Priority: 0.8,
Eric Boren4b254b92016-11-08 12:55:32 -0500360 })
borenetdb182c72016-09-30 12:53:12 -0700361 return name
362}
363
364// ctSKPs generates a CT SKPs task. Returns the name of the last task in the
365// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700366func ctSKPs(b *specs.TasksCfgBuilder, name string) string {
Eric Boren4b254b92016-11-08 12:55:32 -0500367 b.MustAddTask(name, &specs.TaskSpec{
368 CipdPackages: []*specs.CipdPackage{},
369 Dimensions: []string{"pool:SkiaCT"},
370 ExecutionTimeout: 24 * time.Hour,
371 ExtraArgs: []string{
372 "--workdir", "../../..", "swarm_ct_skps",
373 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
374 fmt.Sprintf("buildername=%s", name),
375 "mastername=fake-master",
376 "buildnumber=2",
377 "slavename=fake-buildslave",
378 "nobuildbot=True",
379 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
380 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
381 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
382 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
383 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
384 },
385 IoTimeout: time.Hour,
386 Isolate: "ct_skps_skia.isolate",
387 Priority: 0.8,
388 })
borenetdb182c72016-09-30 12:53:12 -0700389 return name
390}
391
392// housekeeper generates a Housekeeper task. Returns the name of the last task
393// in the generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700394func housekeeper(b *specs.TasksCfgBuilder, name, compileTaskName string) string {
Eric Boren4b254b92016-11-08 12:55:32 -0500395 b.MustAddTask(name, &specs.TaskSpec{
Eric Boren22f5ef72016-12-02 11:01:33 -0500396 CipdPackages: []*specs.CipdPackage{b.MustGetCipdPackageFromAsset("go")},
Eric Boren4b254b92016-11-08 12:55:32 -0500397 Dependencies: []string{compileTaskName},
Eric Boren27225492017-02-01 15:56:55 -0500398 Dimensions: linuxGceDimensions(),
Eric Boren4b254b92016-11-08 12:55:32 -0500399 ExtraArgs: []string{
400 "--workdir", "../../..", "swarm_housekeeper",
401 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
402 fmt.Sprintf("buildername=%s", name),
403 "mastername=fake-master",
404 "buildnumber=2",
405 "slavename=fake-buildslave",
406 "nobuildbot=True",
407 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
408 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
409 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
410 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
411 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
412 },
413 Isolate: "housekeeper_skia.isolate",
414 Priority: 0.8,
415 })
borenetdb182c72016-09-30 12:53:12 -0700416 return name
417}
418
borenet2dbbfa52016-10-14 06:32:09 -0700419// infra generates an infra_tests task. Returns the name of the last task in the
420// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700421func infra(b *specs.TasksCfgBuilder, name string) string {
422 b.MustAddTask(name, &specs.TaskSpec{
borenet2dbbfa52016-10-14 06:32:09 -0700423 CipdPackages: []*specs.CipdPackage{},
Eric Boren27225492017-02-01 15:56:55 -0500424 Dimensions: linuxGceDimensions(),
borenet2dbbfa52016-10-14 06:32:09 -0700425 ExtraArgs: []string{
426 "--workdir", "../../..", "swarm_infra",
skia.buildbots2478c732016-11-04 14:37:26 -0400427 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenet2dbbfa52016-10-14 06:32:09 -0700428 fmt.Sprintf("buildername=%s", name),
429 "mastername=fake-master",
430 "buildnumber=2",
431 "slavename=fake-buildslave",
432 "nobuildbot=True",
433 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
434 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
435 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400436 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
437 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
borenet2dbbfa52016-10-14 06:32:09 -0700438 },
439 Isolate: "infra_skia.isolate",
440 Priority: 0.8,
boreneted20a702016-10-20 11:04:31 -0700441 })
borenet2dbbfa52016-10-14 06:32:09 -0700442 return name
443}
444
borenetdb182c72016-09-30 12:53:12 -0700445// doUpload indicates whether the given Job should upload its results.
446func doUpload(name string) bool {
Eric Boren27225492017-02-01 15:56:55 -0500447 for _, s := range CONFIG.NoUpload {
448 m, err := regexp.MatchString(s, name)
449 if err != nil {
450 glog.Fatal(err)
451 }
452 if m {
borenetdb182c72016-09-30 12:53:12 -0700453 return false
454 }
455 }
456 return true
457}
458
459// test generates a Test task. Returns the name of the last task in the
460// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700461func test(b *specs.TasksCfgBuilder, name string, parts map[string]string, compileTaskName string, pkgs []*specs.CipdPackage) string {
Eric Boren4b254b92016-11-08 12:55:32 -0500462 s := &specs.TaskSpec{
Eric Boren1f2f64b2016-11-09 18:35:15 -0500463 CipdPackages: pkgs,
464 Dependencies: []string{compileTaskName},
465 Dimensions: swarmDimensions(parts),
466 ExecutionTimeout: 4 * time.Hour,
467 Expiration: 20 * time.Hour,
borenetdb182c72016-09-30 12:53:12 -0700468 ExtraArgs: []string{
469 "--workdir", "../../..", "swarm_test",
skia.buildbots2478c732016-11-04 14:37:26 -0400470 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700471 fmt.Sprintf("buildername=%s", name),
472 "mastername=fake-master",
473 "buildnumber=2",
474 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700475 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700476 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
477 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700478 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400479 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
480 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
borenetdb182c72016-09-30 12:53:12 -0700481 },
Eric Boren5d9f3bf2017-02-22 08:36:03 -0500482 IoTimeout: 40 * time.Minute,
483 Isolate: "test_skia.isolate",
484 MaxAttempts: 1,
485 Priority: 0.8,
Eric Boren4b254b92016-11-08 12:55:32 -0500486 }
Eric Boren43b9c6b2017-04-06 07:53:30 -0400487 if useBundledRecipes(parts) {
Eric Boren8b3f9e62017-04-04 09:06:16 -0400488 s.Dependencies = append(s.Dependencies, BUNDLE_RECIPES_NAME)
489 s.Isolate = "test_skia_bundled.isolate"
490 }
Eric Boren4b254b92016-11-08 12:55:32 -0500491 if strings.Contains(parts["extra_config"], "Valgrind") {
492 s.ExecutionTimeout = 9 * time.Hour
493 s.Expiration = 48 * time.Hour
494 s.IoTimeout = time.Hour
495 } else if strings.Contains(parts["extra_config"], "MSAN") {
496 s.ExecutionTimeout = 9 * time.Hour
497 }
498 b.MustAddTask(name, s)
499
borenetdb182c72016-09-30 12:53:12 -0700500 // Upload results if necessary.
501 if doUpload(name) {
502 uploadName := fmt.Sprintf("%s%s%s", PREFIX_UPLOAD, jobNameSchema.Sep, name)
boreneted20a702016-10-20 11:04:31 -0700503 b.MustAddTask(uploadName, &specs.TaskSpec{
borenetdb182c72016-09-30 12:53:12 -0700504 Dependencies: []string{name},
Eric Boren27225492017-02-01 15:56:55 -0500505 Dimensions: linuxGceDimensions(),
borenetdb182c72016-09-30 12:53:12 -0700506 ExtraArgs: []string{
507 "--workdir", "../../..", "upload_dm_results",
skia.buildbots2478c732016-11-04 14:37:26 -0400508 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700509 fmt.Sprintf("buildername=%s", name),
510 "mastername=fake-master",
511 "buildnumber=2",
512 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700513 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700514 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
515 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700516 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400517 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
518 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
Eric Boren965861b2017-02-06 15:38:41 -0500519 fmt.Sprintf("gs_bucket=%s", CONFIG.GsBucketGm),
borenetdb182c72016-09-30 12:53:12 -0700520 },
521 Isolate: "upload_dm_results.isolate",
522 Priority: 0.8,
boreneted20a702016-10-20 11:04:31 -0700523 })
borenetdb182c72016-09-30 12:53:12 -0700524 return uploadName
525 }
526 return name
527}
528
529// perf generates a Perf task. Returns the name of the last task in the
530// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700531func perf(b *specs.TasksCfgBuilder, name string, parts map[string]string, compileTaskName string, pkgs []*specs.CipdPackage) string {
Kevin Lubickb03b5ac2016-11-14 13:42:27 -0500532 recipe := "swarm_perf"
533 isolate := "perf_skia.isolate"
534 if strings.Contains(parts["extra_config"], "Skpbench") {
535 recipe = "swarm_skpbench"
536 isolate = "skpbench_skia.isolate"
Eric Boren43b9c6b2017-04-06 07:53:30 -0400537 if useBundledRecipes(parts) {
Eric Boren8b3f9e62017-04-04 09:06:16 -0400538 isolate = "skpbench_skia_bundled.isolate"
539 }
Eric Boren43b9c6b2017-04-06 07:53:30 -0400540 } else if useBundledRecipes(parts) {
Eric Boren8b3f9e62017-04-04 09:06:16 -0400541 isolate = "perf_skia_bundled.isolate"
Kevin Lubickb03b5ac2016-11-14 13:42:27 -0500542 }
Eric Boren4b254b92016-11-08 12:55:32 -0500543 s := &specs.TaskSpec{
Eric Boren1f2f64b2016-11-09 18:35:15 -0500544 CipdPackages: pkgs,
545 Dependencies: []string{compileTaskName},
546 Dimensions: swarmDimensions(parts),
547 ExecutionTimeout: 4 * time.Hour,
548 Expiration: 20 * time.Hour,
borenetdb182c72016-09-30 12:53:12 -0700549 ExtraArgs: []string{
Kevin Lubickb03b5ac2016-11-14 13:42:27 -0500550 "--workdir", "../../..", recipe,
skia.buildbots2478c732016-11-04 14:37:26 -0400551 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700552 fmt.Sprintf("buildername=%s", name),
553 "mastername=fake-master",
554 "buildnumber=2",
555 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700556 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700557 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
558 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700559 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400560 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
561 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
borenetdb182c72016-09-30 12:53:12 -0700562 },
Eric Boren5d9f3bf2017-02-22 08:36:03 -0500563 IoTimeout: 40 * time.Minute,
564 Isolate: isolate,
565 MaxAttempts: 1,
566 Priority: 0.8,
Eric Boren4b254b92016-11-08 12:55:32 -0500567 }
Eric Boren43b9c6b2017-04-06 07:53:30 -0400568 if useBundledRecipes(parts) {
Eric Boren8b3f9e62017-04-04 09:06:16 -0400569 s.Dependencies = append(s.Dependencies, BUNDLE_RECIPES_NAME)
570 }
Eric Boren4b254b92016-11-08 12:55:32 -0500571 if strings.Contains(parts["extra_config"], "Valgrind") {
572 s.ExecutionTimeout = 9 * time.Hour
573 s.Expiration = 48 * time.Hour
574 s.IoTimeout = time.Hour
575 } else if strings.Contains(parts["extra_config"], "MSAN") {
576 s.ExecutionTimeout = 9 * time.Hour
577 }
578 b.MustAddTask(name, s)
579
borenetdb182c72016-09-30 12:53:12 -0700580 // Upload results if necessary.
581 if strings.Contains(name, "Release") && doUpload(name) {
582 uploadName := fmt.Sprintf("%s%s%s", PREFIX_UPLOAD, jobNameSchema.Sep, name)
boreneted20a702016-10-20 11:04:31 -0700583 b.MustAddTask(uploadName, &specs.TaskSpec{
borenetdb182c72016-09-30 12:53:12 -0700584 Dependencies: []string{name},
Eric Boren27225492017-02-01 15:56:55 -0500585 Dimensions: linuxGceDimensions(),
borenetdb182c72016-09-30 12:53:12 -0700586 ExtraArgs: []string{
587 "--workdir", "../../..", "upload_nano_results",
skia.buildbots2478c732016-11-04 14:37:26 -0400588 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700589 fmt.Sprintf("buildername=%s", name),
590 "mastername=fake-master",
591 "buildnumber=2",
592 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700593 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700594 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
595 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700596 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400597 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
598 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
Eric Boren965861b2017-02-06 15:38:41 -0500599 fmt.Sprintf("gs_bucket=%s", CONFIG.GsBucketNano),
borenetdb182c72016-09-30 12:53:12 -0700600 },
601 Isolate: "upload_nano_results.isolate",
602 Priority: 0.8,
boreneted20a702016-10-20 11:04:31 -0700603 })
borenetdb182c72016-09-30 12:53:12 -0700604 return uploadName
605 }
606 return name
607}
608
609// process generates tasks and jobs for the given job name.
boreneted20a702016-10-20 11:04:31 -0700610func process(b *specs.TasksCfgBuilder, name string) {
borenetdb182c72016-09-30 12:53:12 -0700611 deps := []string{}
612
Eric Boren8b3f9e62017-04-04 09:06:16 -0400613 // Bundle Recipes.
614 if name == BUNDLE_RECIPES_NAME {
615 deps = append(deps, bundleRecipes(b))
616 }
617
borenetdb182c72016-09-30 12:53:12 -0700618 parts, err := jobNameSchema.ParseJobName(name)
619 if err != nil {
620 glog.Fatal(err)
621 }
622
623 // RecreateSKPs.
624 if strings.Contains(name, "RecreateSKPs") {
boreneted20a702016-10-20 11:04:31 -0700625 deps = append(deps, recreateSKPs(b, name))
borenetdb182c72016-09-30 12:53:12 -0700626 }
627
628 // CT bots.
629 if strings.Contains(name, "-CT_") {
boreneted20a702016-10-20 11:04:31 -0700630 deps = append(deps, ctSKPs(b, name))
borenetdb182c72016-09-30 12:53:12 -0700631 }
632
borenet2dbbfa52016-10-14 06:32:09 -0700633 // Infra tests.
634 if name == "Housekeeper-PerCommit-InfraTests" {
boreneted20a702016-10-20 11:04:31 -0700635 deps = append(deps, infra(b, name))
borenet2dbbfa52016-10-14 06:32:09 -0700636 }
637
borenetdb182c72016-09-30 12:53:12 -0700638 // Compile bots.
639 if parts["role"] == "Build" {
boreneted20a702016-10-20 11:04:31 -0700640 deps = append(deps, compile(b, name, parts))
borenetdb182c72016-09-30 12:53:12 -0700641 }
642
Eric Borenf5a90e82016-11-15 15:18:20 -0500643 // Most remaining bots need a compile task.
borenetdb182c72016-09-30 12:53:12 -0700644 compileTaskName := deriveCompileTaskName(name, parts)
borenet52383432016-10-17 10:17:53 -0700645 compileTaskParts, err := jobNameSchema.ParseJobName(compileTaskName)
646 if err != nil {
647 glog.Fatal(err)
648 }
Eric Boren628e78b2016-11-17 11:33:27 -0500649 // These bots do not need a compile task.
Eric Borenf5a90e82016-11-15 15:18:20 -0500650 if parts["role"] != "Build" &&
651 name != "Housekeeper-PerCommit-InfraTests" &&
Eric Boren71b762f2016-11-30 14:05:16 -0500652 !strings.Contains(name, "RecreateSKPs") &&
653 !strings.Contains(name, "-CT_") {
boreneted20a702016-10-20 11:04:31 -0700654 compile(b, compileTaskName, compileTaskParts)
borenet52383432016-10-17 10:17:53 -0700655 }
borenetdb182c72016-09-30 12:53:12 -0700656
657 // Housekeeper.
Eric Boren22f5ef72016-12-02 11:01:33 -0500658 if name == "Housekeeper-PerCommit" {
boreneted20a702016-10-20 11:04:31 -0700659 deps = append(deps, housekeeper(b, name, compileTaskName))
borenetdb182c72016-09-30 12:53:12 -0700660 }
661
662 // Common assets needed by the remaining bots.
663 pkgs := []*specs.CipdPackage{
boreneted20a702016-10-20 11:04:31 -0700664 b.MustGetCipdPackageFromAsset("skimage"),
665 b.MustGetCipdPackageFromAsset("skp"),
666 b.MustGetCipdPackageFromAsset("svg"),
borenetdb182c72016-09-30 12:53:12 -0700667 }
Kevin Lubick291547d2017-03-21 09:25:34 -0400668 if strings.Contains(name, "Chromecast") {
669 // Chromecasts don't have enough disk space to fit all of the content,
670 // so we do a subset of the skps.
671 pkgs = []*specs.CipdPackage{
672 b.MustGetCipdPackageFromAsset("skp"),
673 }
674 }
Eric Boren4b254b92016-11-08 12:55:32 -0500675 if strings.Contains(name, "Ubuntu") && strings.Contains(name, "SAN") {
676 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("clang_linux"))
677 }
Kevin Lubick35115eb2017-02-17 10:25:34 -0500678 if strings.Contains(name, "Ubuntu16") {
679 if strings.Contains(name, "Vulkan") {
680 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("linux_vulkan_sdk"))
681 }
Kevin Lubick0a51b482017-02-06 12:45:29 -0500682 if strings.Contains(name, "Release") {
683 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("linux_vulkan_intel_driver_release"))
684 } else {
685 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("linux_vulkan_intel_driver_debug"))
686 }
687 }
Kevin Lubickb03b5ac2016-11-14 13:42:27 -0500688 // Skpbench only needs skps
689 if strings.Contains(name, "Skpbench") {
690 pkgs = []*specs.CipdPackage{
691 b.MustGetCipdPackageFromAsset("skp"),
692 }
693 }
borenetdb182c72016-09-30 12:53:12 -0700694
695 // Test bots.
Eric Boren71b762f2016-11-30 14:05:16 -0500696 if parts["role"] == "Test" && !strings.Contains(name, "-CT_") {
boreneted20a702016-10-20 11:04:31 -0700697 deps = append(deps, test(b, name, parts, compileTaskName, pkgs))
borenetdb182c72016-09-30 12:53:12 -0700698 }
699
700 // Perf bots.
Eric Boren71b762f2016-11-30 14:05:16 -0500701 if parts["role"] == "Perf" && !strings.Contains(name, "-CT_") {
boreneted20a702016-10-20 11:04:31 -0700702 deps = append(deps, perf(b, name, parts, compileTaskName, pkgs))
borenetdb182c72016-09-30 12:53:12 -0700703 }
704
705 // Add the Job spec.
Eric Borenf5a90e82016-11-15 15:18:20 -0500706 j := &specs.JobSpec{
borenetdb182c72016-09-30 12:53:12 -0700707 Priority: 0.8,
708 TaskSpecs: deps,
Eric Borenf5a90e82016-11-15 15:18:20 -0500709 }
710 if name == "Housekeeper-Nightly-RecreateSKPs_Canary" {
711 j.Trigger = "nightly"
712 }
713 if name == "Housekeeper-Weekly-RecreateSKPs" {
714 j.Trigger = "weekly"
715 }
Eric Boren71b762f2016-11-30 14:05:16 -0500716 if name == "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-CT_DM_1m_SKPs" {
717 j.Trigger = "weekly"
718 }
Eric Boren8615fe52016-12-12 14:30:12 -0500719 b.MustAddJob(name, j)
borenetdb182c72016-09-30 12:53:12 -0700720}
721
Eric Boren27225492017-02-01 15:56:55 -0500722func loadJson(flag *string, defaultFlag string, val interface{}) {
723 if *flag == "" {
724 *flag = defaultFlag
725 }
726 b, err := ioutil.ReadFile(*flag)
727 if err != nil {
728 glog.Fatal(err)
729 }
730 if err := json.Unmarshal(b, val); err != nil {
731 glog.Fatal(err)
732 }
733}
734
borenetdb182c72016-09-30 12:53:12 -0700735// Regenerate the tasks.json file.
736func main() {
boreneted20a702016-10-20 11:04:31 -0700737 b := specs.MustNewTasksCfgBuilder()
Eric Boren27225492017-02-01 15:56:55 -0500738 b.SetAssetsDir(*assetsDir)
739 infraBots := path.Join(b.CheckoutRoot(), "infra", "bots")
740
741 // Load the jobs from a JSON file.
742 loadJson(jobsFile, path.Join(infraBots, "jobs.json"), &JOBS)
743
744 // Load the GPU mapping from a JSON file.
745 loadJson(gpuMapFile, path.Join(infraBots, "gpu_map.json"), &GPU_MAPPING)
746
747 // Load the Android device mapping from a JSON file.
748 loadJson(androidMapFile, path.Join(infraBots, "android_map.json"), &ANDROID_MAPPING)
749
750 // Load general config information from a JSON file.
751 loadJson(cfgFile, path.Join(infraBots, "cfg.json"), &CONFIG)
752
borenetdb182c72016-09-30 12:53:12 -0700753 // Create the JobNameSchema.
Eric Boren1f8be682017-02-07 09:16:30 -0500754 if *builderNameSchemaFile == "" {
755 *builderNameSchemaFile = path.Join(b.CheckoutRoot(), "infra", "bots", "recipe_modules", "builder_name_schema", "builder_name_schema.json")
756 }
757 schema, err := NewJobNameSchema(*builderNameSchemaFile)
borenetdb182c72016-09-30 12:53:12 -0700758 if err != nil {
759 glog.Fatal(err)
760 }
761 jobNameSchema = schema
762
borenetdb182c72016-09-30 12:53:12 -0700763 // Create Tasks and Jobs.
boreneted20a702016-10-20 11:04:31 -0700764 for _, name := range JOBS {
765 process(b, name)
borenetdb182c72016-09-30 12:53:12 -0700766 }
767
boreneted20a702016-10-20 11:04:31 -0700768 b.MustFinish()
borenetdb182c72016-09-30 12:53:12 -0700769}
770
771// TODO(borenet): The below really belongs in its own file, probably next to the
772// builder_name_schema.json file.
773
774// JobNameSchema is a struct used for (de)constructing Job names in a
775// predictable format.
776type JobNameSchema struct {
777 Schema map[string][]string `json:"builder_name_schema"`
778 Sep string `json:"builder_name_sep"`
779}
780
781// NewJobNameSchema returns a JobNameSchema instance based on the given JSON
782// file.
783func NewJobNameSchema(jsonFile string) (*JobNameSchema, error) {
784 var rv JobNameSchema
785 f, err := os.Open(jsonFile)
786 if err != nil {
787 return nil, err
788 }
789 defer util.Close(f)
790 if err := json.NewDecoder(f).Decode(&rv); err != nil {
791 return nil, err
792 }
793 return &rv, nil
794}
795
796// ParseJobName splits the given Job name into its component parts, according
797// to the schema.
798func (s *JobNameSchema) ParseJobName(n string) (map[string]string, error) {
799 split := strings.Split(n, s.Sep)
800 if len(split) < 2 {
801 return nil, fmt.Errorf("Invalid job name: %q", n)
802 }
803 role := split[0]
804 split = split[1:]
805 keys, ok := s.Schema[role]
806 if !ok {
807 return nil, fmt.Errorf("Invalid job name; %q is not a valid role.", role)
808 }
809 extraConfig := ""
810 if len(split) == len(keys)+1 {
811 extraConfig = split[len(split)-1]
812 split = split[:len(split)-1]
813 }
814 if len(split) != len(keys) {
815 return nil, fmt.Errorf("Invalid job name; %q has incorrect number of parts.", n)
816 }
817 rv := make(map[string]string, len(keys)+2)
818 rv["role"] = role
819 if extraConfig != "" {
820 rv["extra_config"] = extraConfig
821 }
822 for i, k := range keys {
823 rv[k] = split[i]
824 }
825 return rv, nil
826}
827
828// MakeJobName assembles the given parts of a Job name, according to the schema.
829func (s *JobNameSchema) MakeJobName(parts map[string]string) (string, error) {
830 role, ok := parts["role"]
831 if !ok {
832 return "", fmt.Errorf("Invalid job parts; jobs must have a role.")
833 }
834 keys, ok := s.Schema[role]
835 if !ok {
836 return "", fmt.Errorf("Invalid job parts; unknown role %q", role)
837 }
838 rvParts := make([]string, 0, len(parts))
839 rvParts = append(rvParts, role)
840 for _, k := range keys {
841 v, ok := parts[k]
842 if !ok {
843 return "", fmt.Errorf("Invalid job parts; missing %q", k)
844 }
845 rvParts = append(rvParts, v)
846 }
847 if _, ok := parts["extra_config"]; ok {
848 rvParts = append(rvParts, parts["extra_config"])
849 }
850 return strings.Join(rvParts, s.Sep), nil
851}