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_ci/* 7cb93a386Sopenharmony_ci This file contains logic related to task/job name schemas. 8cb93a386Sopenharmony_ci*/ 9cb93a386Sopenharmony_ci 10cb93a386Sopenharmony_ciimport ( 11cb93a386Sopenharmony_ci "encoding/json" 12cb93a386Sopenharmony_ci "fmt" 13cb93a386Sopenharmony_ci "log" 14cb93a386Sopenharmony_ci "os" 15cb93a386Sopenharmony_ci "regexp" 16cb93a386Sopenharmony_ci "strings" 17cb93a386Sopenharmony_ci) 18cb93a386Sopenharmony_ci 19cb93a386Sopenharmony_ci// parts represents the key/value pairs which make up task and job names. 20cb93a386Sopenharmony_citype parts map[string]string 21cb93a386Sopenharmony_ci 22cb93a386Sopenharmony_ci// equal returns true if the given part of this job's name equals any of the 23cb93a386Sopenharmony_ci// given values. Panics if no values are provided. 24cb93a386Sopenharmony_cifunc (p parts) equal(part string, eq ...string) bool { 25cb93a386Sopenharmony_ci if len(eq) == 0 { 26cb93a386Sopenharmony_ci log.Fatal("No values provided for equal!") 27cb93a386Sopenharmony_ci } 28cb93a386Sopenharmony_ci v := p[part] 29cb93a386Sopenharmony_ci for _, e := range eq { 30cb93a386Sopenharmony_ci if v == e { 31cb93a386Sopenharmony_ci return true 32cb93a386Sopenharmony_ci } 33cb93a386Sopenharmony_ci } 34cb93a386Sopenharmony_ci return false 35cb93a386Sopenharmony_ci} 36cb93a386Sopenharmony_ci 37cb93a386Sopenharmony_ci// role returns true if the role for this job equals any of the given values. 38cb93a386Sopenharmony_cifunc (p parts) role(eq ...string) bool { 39cb93a386Sopenharmony_ci return p.equal("role", eq...) 40cb93a386Sopenharmony_ci} 41cb93a386Sopenharmony_ci 42cb93a386Sopenharmony_ci// os returns true if the OS for this job equals any of the given values. 43cb93a386Sopenharmony_cifunc (p parts) os(eq ...string) bool { 44cb93a386Sopenharmony_ci return p.equal("os", eq...) 45cb93a386Sopenharmony_ci} 46cb93a386Sopenharmony_ci 47cb93a386Sopenharmony_ci// compiler returns true if the compiler for this job equals any of the given 48cb93a386Sopenharmony_ci// values. 49cb93a386Sopenharmony_cifunc (p parts) compiler(eq ...string) bool { 50cb93a386Sopenharmony_ci return p.equal("compiler", eq...) 51cb93a386Sopenharmony_ci} 52cb93a386Sopenharmony_ci 53cb93a386Sopenharmony_ci// model returns true if the model for this job equals any of the given values. 54cb93a386Sopenharmony_cifunc (p parts) model(eq ...string) bool { 55cb93a386Sopenharmony_ci return p.equal("model", eq...) 56cb93a386Sopenharmony_ci} 57cb93a386Sopenharmony_ci 58cb93a386Sopenharmony_ci// frequency returns true if the frequency for this job equals any of the given 59cb93a386Sopenharmony_ci// values. 60cb93a386Sopenharmony_cifunc (p parts) frequency(eq ...string) bool { 61cb93a386Sopenharmony_ci return p.equal("frequency", eq...) 62cb93a386Sopenharmony_ci} 63cb93a386Sopenharmony_ci 64cb93a386Sopenharmony_ci// cpu returns true if the task's cpu_or_gpu is "CPU" and the CPU for this 65cb93a386Sopenharmony_ci// task equals any of the given values. If no values are provided, cpu returns 66cb93a386Sopenharmony_ci// true if this task runs on CPU. 67cb93a386Sopenharmony_cifunc (p parts) cpu(eq ...string) bool { 68cb93a386Sopenharmony_ci if p["cpu_or_gpu"] == "CPU" { 69cb93a386Sopenharmony_ci if len(eq) == 0 { 70cb93a386Sopenharmony_ci return true 71cb93a386Sopenharmony_ci } 72cb93a386Sopenharmony_ci return p.equal("cpu_or_gpu_value", eq...) 73cb93a386Sopenharmony_ci } 74cb93a386Sopenharmony_ci return false 75cb93a386Sopenharmony_ci} 76cb93a386Sopenharmony_ci 77cb93a386Sopenharmony_ci// gpu returns true if the task's cpu_or_gpu is "GPU" and the GPU for this task 78cb93a386Sopenharmony_ci// equals any of the given values. If no values are provided, gpu returns true 79cb93a386Sopenharmony_ci// if this task runs on GPU. 80cb93a386Sopenharmony_cifunc (p parts) gpu(eq ...string) bool { 81cb93a386Sopenharmony_ci if p["cpu_or_gpu"] == "GPU" { 82cb93a386Sopenharmony_ci if len(eq) == 0 { 83cb93a386Sopenharmony_ci return true 84cb93a386Sopenharmony_ci } 85cb93a386Sopenharmony_ci return p.equal("cpu_or_gpu_value", eq...) 86cb93a386Sopenharmony_ci } 87cb93a386Sopenharmony_ci return false 88cb93a386Sopenharmony_ci} 89cb93a386Sopenharmony_ci 90cb93a386Sopenharmony_ci// arch returns true if the architecture for this job equals any of the 91cb93a386Sopenharmony_ci// given values. 92cb93a386Sopenharmony_cifunc (p parts) arch(eq ...string) bool { 93cb93a386Sopenharmony_ci return p.equal("arch", eq...) || p.equal("target_arch", eq...) 94cb93a386Sopenharmony_ci} 95cb93a386Sopenharmony_ci 96cb93a386Sopenharmony_ci// extraConfig returns true if any of the extra_configs for this job equals 97cb93a386Sopenharmony_ci// any of the given values. If the extra_config starts with "SK_", 98cb93a386Sopenharmony_ci// it is considered to be a single config. 99cb93a386Sopenharmony_cifunc (p parts) extraConfig(eq ...string) bool { 100cb93a386Sopenharmony_ci if len(eq) == 0 { 101cb93a386Sopenharmony_ci log.Fatal("No values provided for extraConfig()!") 102cb93a386Sopenharmony_ci } 103cb93a386Sopenharmony_ci ec := p["extra_config"] 104cb93a386Sopenharmony_ci if ec == "" { 105cb93a386Sopenharmony_ci return false 106cb93a386Sopenharmony_ci } 107cb93a386Sopenharmony_ci var cfgs []string 108cb93a386Sopenharmony_ci if strings.HasPrefix(ec, "SK_") { 109cb93a386Sopenharmony_ci cfgs = []string{ec} 110cb93a386Sopenharmony_ci } else { 111cb93a386Sopenharmony_ci cfgs = strings.Split(ec, "_") 112cb93a386Sopenharmony_ci } 113cb93a386Sopenharmony_ci for _, c := range cfgs { 114cb93a386Sopenharmony_ci for _, e := range eq { 115cb93a386Sopenharmony_ci if c == e { 116cb93a386Sopenharmony_ci return true 117cb93a386Sopenharmony_ci } 118cb93a386Sopenharmony_ci } 119cb93a386Sopenharmony_ci } 120cb93a386Sopenharmony_ci return false 121cb93a386Sopenharmony_ci} 122cb93a386Sopenharmony_ci 123cb93a386Sopenharmony_ci// noExtraConfig returns true if there are no extra_configs for this job. 124cb93a386Sopenharmony_cifunc (p parts) noExtraConfig(eq ...string) bool { 125cb93a386Sopenharmony_ci ec := p["extra_config"] 126cb93a386Sopenharmony_ci return ec == "" 127cb93a386Sopenharmony_ci} 128cb93a386Sopenharmony_ci 129cb93a386Sopenharmony_ci// matchPart returns true if the given part of this job's name matches any of 130cb93a386Sopenharmony_ci// the given regular expressions. Note that a regular expression might match any 131cb93a386Sopenharmony_ci// substring, so if you need an exact match on the entire string you'll need to 132cb93a386Sopenharmony_ci// use `^` and `$`. Panics if no regular expressions are provided. 133cb93a386Sopenharmony_cifunc (p parts) matchPart(part string, re ...string) bool { 134cb93a386Sopenharmony_ci if len(re) == 0 { 135cb93a386Sopenharmony_ci log.Fatal("No regular expressions provided for matchPart()!") 136cb93a386Sopenharmony_ci } 137cb93a386Sopenharmony_ci v := p[part] 138cb93a386Sopenharmony_ci for _, r := range re { 139cb93a386Sopenharmony_ci if regexp.MustCompile(r).MatchString(v) { 140cb93a386Sopenharmony_ci return true 141cb93a386Sopenharmony_ci } 142cb93a386Sopenharmony_ci } 143cb93a386Sopenharmony_ci return false 144cb93a386Sopenharmony_ci} 145cb93a386Sopenharmony_ci 146cb93a386Sopenharmony_ci// matchRole returns true if the role for this job matches any of the given 147cb93a386Sopenharmony_ci// regular expressions. 148cb93a386Sopenharmony_cifunc (p parts) matchRole(re ...string) bool { 149cb93a386Sopenharmony_ci return p.matchPart("role", re...) 150cb93a386Sopenharmony_ci} 151cb93a386Sopenharmony_ci 152cb93a386Sopenharmony_cifunc (p parts) project(re ...string) bool { 153cb93a386Sopenharmony_ci return p.matchPart("project", re...) 154cb93a386Sopenharmony_ci} 155cb93a386Sopenharmony_ci 156cb93a386Sopenharmony_ci// matchOs returns true if the OS for this job matches any of the given regular 157cb93a386Sopenharmony_ci// expressions. 158cb93a386Sopenharmony_cifunc (p parts) matchOs(re ...string) bool { 159cb93a386Sopenharmony_ci return p.matchPart("os", re...) 160cb93a386Sopenharmony_ci} 161cb93a386Sopenharmony_ci 162cb93a386Sopenharmony_ci// matchCompiler returns true if the compiler for this job matches any of the 163cb93a386Sopenharmony_ci// given regular expressions. 164cb93a386Sopenharmony_cifunc (p parts) matchCompiler(re ...string) bool { 165cb93a386Sopenharmony_ci return p.matchPart("compiler", re...) 166cb93a386Sopenharmony_ci} 167cb93a386Sopenharmony_ci 168cb93a386Sopenharmony_ci// matchModel returns true if the model for this job matches any of the given 169cb93a386Sopenharmony_ci// regular expressions. 170cb93a386Sopenharmony_cifunc (p parts) matchModel(re ...string) bool { 171cb93a386Sopenharmony_ci return p.matchPart("model", re...) 172cb93a386Sopenharmony_ci} 173cb93a386Sopenharmony_ci 174cb93a386Sopenharmony_ci// matchCpu returns true if the task's cpu_or_gpu is "CPU" and the CPU for this 175cb93a386Sopenharmony_ci// task matches any of the given regular expressions. If no regular expressions 176cb93a386Sopenharmony_ci// are provided, cpu returns true if this task runs on CPU. 177cb93a386Sopenharmony_cifunc (p parts) matchCpu(re ...string) bool { 178cb93a386Sopenharmony_ci if p["cpu_or_gpu"] == "CPU" { 179cb93a386Sopenharmony_ci if len(re) == 0 { 180cb93a386Sopenharmony_ci return true 181cb93a386Sopenharmony_ci } 182cb93a386Sopenharmony_ci return p.matchPart("cpu_or_gpu_value", re...) 183cb93a386Sopenharmony_ci } 184cb93a386Sopenharmony_ci return false 185cb93a386Sopenharmony_ci} 186cb93a386Sopenharmony_ci 187cb93a386Sopenharmony_ci// matchGpu returns true if the task's cpu_or_gpu is "GPU" and the GPU for this task 188cb93a386Sopenharmony_ci// matches any of the given regular expressions. If no regular expressions are 189cb93a386Sopenharmony_ci// provided, gpu returns true if this task runs on GPU. 190cb93a386Sopenharmony_cifunc (p parts) matchGpu(re ...string) bool { 191cb93a386Sopenharmony_ci if p["cpu_or_gpu"] == "GPU" { 192cb93a386Sopenharmony_ci if len(re) == 0 { 193cb93a386Sopenharmony_ci return true 194cb93a386Sopenharmony_ci } 195cb93a386Sopenharmony_ci return p.matchPart("cpu_or_gpu_value", re...) 196cb93a386Sopenharmony_ci } 197cb93a386Sopenharmony_ci return false 198cb93a386Sopenharmony_ci} 199cb93a386Sopenharmony_ci 200cb93a386Sopenharmony_ci// matchArch returns true if the architecture for this job matches any of the 201cb93a386Sopenharmony_ci// given regular expressions. 202cb93a386Sopenharmony_cifunc (p parts) matchArch(re ...string) bool { 203cb93a386Sopenharmony_ci return p.matchPart("arch", re...) || p.matchPart("target_arch", re...) 204cb93a386Sopenharmony_ci} 205cb93a386Sopenharmony_ci 206cb93a386Sopenharmony_ci// matchExtraConfig returns true if any of the extra_configs for this job matches 207cb93a386Sopenharmony_ci// any of the given regular expressions. If the extra_config starts with "SK_", 208cb93a386Sopenharmony_ci// it is considered to be a single config. 209cb93a386Sopenharmony_cifunc (p parts) matchExtraConfig(re ...string) bool { 210cb93a386Sopenharmony_ci if len(re) == 0 { 211cb93a386Sopenharmony_ci log.Fatal("No regular expressions provided for matchExtraConfig()!") 212cb93a386Sopenharmony_ci } 213cb93a386Sopenharmony_ci ec := p["extra_config"] 214cb93a386Sopenharmony_ci if ec == "" { 215cb93a386Sopenharmony_ci return false 216cb93a386Sopenharmony_ci } 217cb93a386Sopenharmony_ci var cfgs []string 218cb93a386Sopenharmony_ci if strings.HasPrefix(ec, "SK_") { 219cb93a386Sopenharmony_ci cfgs = []string{ec} 220cb93a386Sopenharmony_ci } else { 221cb93a386Sopenharmony_ci cfgs = strings.Split(ec, "_") 222cb93a386Sopenharmony_ci } 223cb93a386Sopenharmony_ci compiled := make([]*regexp.Regexp, 0, len(re)) 224cb93a386Sopenharmony_ci for _, r := range re { 225cb93a386Sopenharmony_ci compiled = append(compiled, regexp.MustCompile(r)) 226cb93a386Sopenharmony_ci } 227cb93a386Sopenharmony_ci for _, c := range cfgs { 228cb93a386Sopenharmony_ci for _, r := range compiled { 229cb93a386Sopenharmony_ci if r.MatchString(c) { 230cb93a386Sopenharmony_ci return true 231cb93a386Sopenharmony_ci } 232cb93a386Sopenharmony_ci } 233cb93a386Sopenharmony_ci } 234cb93a386Sopenharmony_ci return false 235cb93a386Sopenharmony_ci} 236cb93a386Sopenharmony_ci 237cb93a386Sopenharmony_ci// debug returns true if this task runs in debug mode. 238cb93a386Sopenharmony_cifunc (p parts) debug() bool { 239cb93a386Sopenharmony_ci return p["configuration"] == "Debug" 240cb93a386Sopenharmony_ci} 241cb93a386Sopenharmony_ci 242cb93a386Sopenharmony_ci// release returns true if this task runs in release mode. 243cb93a386Sopenharmony_cifunc (p parts) release() bool { 244cb93a386Sopenharmony_ci return p["configuration"] == "Release" 245cb93a386Sopenharmony_ci} 246cb93a386Sopenharmony_ci 247cb93a386Sopenharmony_ci// isLinux returns true if the task runs on Linux. 248cb93a386Sopenharmony_cifunc (p parts) isLinux() bool { 249cb93a386Sopenharmony_ci return p.matchOs("Debian", "Ubuntu") 250cb93a386Sopenharmony_ci} 251cb93a386Sopenharmony_ci 252cb93a386Sopenharmony_ci// TODO(borenet): The below really belongs in its own file, probably next to the 253cb93a386Sopenharmony_ci// builder_name_schema.json file. 254cb93a386Sopenharmony_ci 255cb93a386Sopenharmony_ci// schema is a sub-struct of JobNameSchema. 256cb93a386Sopenharmony_citype schema struct { 257cb93a386Sopenharmony_ci Keys []string `json:"keys"` 258cb93a386Sopenharmony_ci OptionalKeys []string `json:"optional_keys"` 259cb93a386Sopenharmony_ci RecurseRoles []string `json:"recurse_roles"` 260cb93a386Sopenharmony_ci} 261cb93a386Sopenharmony_ci 262cb93a386Sopenharmony_ci// JobNameSchema is a struct used for (de)constructing Job names in a 263cb93a386Sopenharmony_ci// predictable format. 264cb93a386Sopenharmony_citype JobNameSchema struct { 265cb93a386Sopenharmony_ci Schema map[string]*schema `json:"builder_name_schema"` 266cb93a386Sopenharmony_ci Sep string `json:"builder_name_sep"` 267cb93a386Sopenharmony_ci} 268cb93a386Sopenharmony_ci 269cb93a386Sopenharmony_ci// NewJobNameSchema returns a JobNameSchema instance based on the given JSON 270cb93a386Sopenharmony_ci// file. 271cb93a386Sopenharmony_cifunc NewJobNameSchema(jsonFile string) (*JobNameSchema, error) { 272cb93a386Sopenharmony_ci var rv JobNameSchema 273cb93a386Sopenharmony_ci f, err := os.Open(jsonFile) 274cb93a386Sopenharmony_ci if err != nil { 275cb93a386Sopenharmony_ci return nil, err 276cb93a386Sopenharmony_ci } 277cb93a386Sopenharmony_ci defer func() { 278cb93a386Sopenharmony_ci if err := f.Close(); err != nil { 279cb93a386Sopenharmony_ci log.Println(fmt.Sprintf("Failed to close %s: %s", jsonFile, err)) 280cb93a386Sopenharmony_ci } 281cb93a386Sopenharmony_ci }() 282cb93a386Sopenharmony_ci if err := json.NewDecoder(f).Decode(&rv); err != nil { 283cb93a386Sopenharmony_ci return nil, err 284cb93a386Sopenharmony_ci } 285cb93a386Sopenharmony_ci return &rv, nil 286cb93a386Sopenharmony_ci} 287cb93a386Sopenharmony_ci 288cb93a386Sopenharmony_ci// ParseJobName splits the given Job name into its component parts, according 289cb93a386Sopenharmony_ci// to the schema. 290cb93a386Sopenharmony_cifunc (s *JobNameSchema) ParseJobName(n string) (map[string]string, error) { 291cb93a386Sopenharmony_ci popFront := func(items []string) (string, []string, error) { 292cb93a386Sopenharmony_ci if len(items) == 0 { 293cb93a386Sopenharmony_ci return "", nil, fmt.Errorf("Invalid job name: %s (not enough parts)", n) 294cb93a386Sopenharmony_ci } 295cb93a386Sopenharmony_ci return items[0], items[1:], nil 296cb93a386Sopenharmony_ci } 297cb93a386Sopenharmony_ci 298cb93a386Sopenharmony_ci result := map[string]string{} 299cb93a386Sopenharmony_ci 300cb93a386Sopenharmony_ci var parse func(int, string, []string) ([]string, error) 301cb93a386Sopenharmony_ci parse = func(depth int, role string, parts []string) ([]string, error) { 302cb93a386Sopenharmony_ci s, ok := s.Schema[role] 303cb93a386Sopenharmony_ci if !ok { 304cb93a386Sopenharmony_ci return nil, fmt.Errorf("Invalid job name; %q is not a valid role.", role) 305cb93a386Sopenharmony_ci } 306cb93a386Sopenharmony_ci if depth == 0 { 307cb93a386Sopenharmony_ci result["role"] = role 308cb93a386Sopenharmony_ci } else { 309cb93a386Sopenharmony_ci result[fmt.Sprintf("sub-role-%d", depth)] = role 310cb93a386Sopenharmony_ci } 311cb93a386Sopenharmony_ci var err error 312cb93a386Sopenharmony_ci for _, key := range s.Keys { 313cb93a386Sopenharmony_ci var value string 314cb93a386Sopenharmony_ci value, parts, err = popFront(parts) 315cb93a386Sopenharmony_ci if err != nil { 316cb93a386Sopenharmony_ci return nil, err 317cb93a386Sopenharmony_ci } 318cb93a386Sopenharmony_ci result[key] = value 319cb93a386Sopenharmony_ci } 320cb93a386Sopenharmony_ci for _, subRole := range s.RecurseRoles { 321cb93a386Sopenharmony_ci if len(parts) > 0 && parts[0] == subRole { 322cb93a386Sopenharmony_ci parts, err = parse(depth+1, parts[0], parts[1:]) 323cb93a386Sopenharmony_ci if err != nil { 324cb93a386Sopenharmony_ci return nil, err 325cb93a386Sopenharmony_ci } 326cb93a386Sopenharmony_ci } 327cb93a386Sopenharmony_ci } 328cb93a386Sopenharmony_ci for _, key := range s.OptionalKeys { 329cb93a386Sopenharmony_ci if len(parts) > 0 { 330cb93a386Sopenharmony_ci var value string 331cb93a386Sopenharmony_ci value, parts, err = popFront(parts) 332cb93a386Sopenharmony_ci if err != nil { 333cb93a386Sopenharmony_ci return nil, err 334cb93a386Sopenharmony_ci } 335cb93a386Sopenharmony_ci result[key] = value 336cb93a386Sopenharmony_ci } 337cb93a386Sopenharmony_ci } 338cb93a386Sopenharmony_ci if len(parts) > 0 { 339cb93a386Sopenharmony_ci return nil, fmt.Errorf("Invalid job name: %s (too many parts)", n) 340cb93a386Sopenharmony_ci } 341cb93a386Sopenharmony_ci return parts, nil 342cb93a386Sopenharmony_ci } 343cb93a386Sopenharmony_ci 344cb93a386Sopenharmony_ci split := strings.Split(n, s.Sep) 345cb93a386Sopenharmony_ci if len(split) < 2 { 346cb93a386Sopenharmony_ci return nil, fmt.Errorf("Invalid job name: %s (not enough parts)", n) 347cb93a386Sopenharmony_ci } 348cb93a386Sopenharmony_ci role := split[0] 349cb93a386Sopenharmony_ci split = split[1:] 350cb93a386Sopenharmony_ci _, err := parse(0, role, split) 351cb93a386Sopenharmony_ci return result, err 352cb93a386Sopenharmony_ci} 353cb93a386Sopenharmony_ci 354cb93a386Sopenharmony_ci// MakeJobName assembles the given parts of a Job name, according to the schema. 355cb93a386Sopenharmony_cifunc (s *JobNameSchema) MakeJobName(parts map[string]string) (string, error) { 356cb93a386Sopenharmony_ci rvParts := make([]string, 0, len(parts)) 357cb93a386Sopenharmony_ci 358cb93a386Sopenharmony_ci var process func(int, map[string]string) (map[string]string, error) 359cb93a386Sopenharmony_ci process = func(depth int, parts map[string]string) (map[string]string, error) { 360cb93a386Sopenharmony_ci roleKey := "role" 361cb93a386Sopenharmony_ci if depth != 0 { 362cb93a386Sopenharmony_ci roleKey = fmt.Sprintf("sub-role-%d", depth) 363cb93a386Sopenharmony_ci } 364cb93a386Sopenharmony_ci role, ok := parts[roleKey] 365cb93a386Sopenharmony_ci if !ok { 366cb93a386Sopenharmony_ci return nil, fmt.Errorf("Invalid job parts; missing key %q", roleKey) 367cb93a386Sopenharmony_ci } 368cb93a386Sopenharmony_ci 369cb93a386Sopenharmony_ci s, ok := s.Schema[role] 370cb93a386Sopenharmony_ci if !ok { 371cb93a386Sopenharmony_ci return nil, fmt.Errorf("Invalid job parts; unknown role %q", role) 372cb93a386Sopenharmony_ci } 373cb93a386Sopenharmony_ci rvParts = append(rvParts, role) 374cb93a386Sopenharmony_ci delete(parts, roleKey) 375cb93a386Sopenharmony_ci 376cb93a386Sopenharmony_ci for _, key := range s.Keys { 377cb93a386Sopenharmony_ci value, ok := parts[key] 378cb93a386Sopenharmony_ci if !ok { 379cb93a386Sopenharmony_ci return nil, fmt.Errorf("Invalid job parts; missing %q", key) 380cb93a386Sopenharmony_ci } 381cb93a386Sopenharmony_ci rvParts = append(rvParts, value) 382cb93a386Sopenharmony_ci delete(parts, key) 383cb93a386Sopenharmony_ci } 384cb93a386Sopenharmony_ci 385cb93a386Sopenharmony_ci if len(s.RecurseRoles) > 0 { 386cb93a386Sopenharmony_ci subRoleKey := fmt.Sprintf("sub-role-%d", depth+1) 387cb93a386Sopenharmony_ci subRole, ok := parts[subRoleKey] 388cb93a386Sopenharmony_ci if !ok { 389cb93a386Sopenharmony_ci return nil, fmt.Errorf("Invalid job parts; missing %q", subRoleKey) 390cb93a386Sopenharmony_ci } 391cb93a386Sopenharmony_ci rvParts = append(rvParts, subRole) 392cb93a386Sopenharmony_ci delete(parts, subRoleKey) 393cb93a386Sopenharmony_ci found := false 394cb93a386Sopenharmony_ci for _, recurseRole := range s.RecurseRoles { 395cb93a386Sopenharmony_ci if recurseRole == subRole { 396cb93a386Sopenharmony_ci found = true 397cb93a386Sopenharmony_ci var err error 398cb93a386Sopenharmony_ci parts, err = process(depth+1, parts) 399cb93a386Sopenharmony_ci if err != nil { 400cb93a386Sopenharmony_ci return nil, err 401cb93a386Sopenharmony_ci } 402cb93a386Sopenharmony_ci break 403cb93a386Sopenharmony_ci } 404cb93a386Sopenharmony_ci } 405cb93a386Sopenharmony_ci if !found { 406cb93a386Sopenharmony_ci return nil, fmt.Errorf("Invalid job parts; unknown sub-role %q", subRole) 407cb93a386Sopenharmony_ci } 408cb93a386Sopenharmony_ci } 409cb93a386Sopenharmony_ci for _, key := range s.OptionalKeys { 410cb93a386Sopenharmony_ci if value, ok := parts[key]; ok { 411cb93a386Sopenharmony_ci rvParts = append(rvParts, value) 412cb93a386Sopenharmony_ci delete(parts, key) 413cb93a386Sopenharmony_ci } 414cb93a386Sopenharmony_ci } 415cb93a386Sopenharmony_ci if len(parts) > 0 { 416cb93a386Sopenharmony_ci return nil, fmt.Errorf("Invalid job parts: too many parts: %v", parts) 417cb93a386Sopenharmony_ci } 418cb93a386Sopenharmony_ci return parts, nil 419cb93a386Sopenharmony_ci } 420cb93a386Sopenharmony_ci 421cb93a386Sopenharmony_ci // Copy the parts map, so that we can modify at will. 422cb93a386Sopenharmony_ci partsCpy := make(map[string]string, len(parts)) 423cb93a386Sopenharmony_ci for k, v := range parts { 424cb93a386Sopenharmony_ci partsCpy[k] = v 425cb93a386Sopenharmony_ci } 426cb93a386Sopenharmony_ci if _, err := process(0, partsCpy); err != nil { 427cb93a386Sopenharmony_ci return "", err 428cb93a386Sopenharmony_ci } 429cb93a386Sopenharmony_ci return strings.Join(rvParts, s.Sep), nil 430cb93a386Sopenharmony_ci} 431