go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/client/isolate/format.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package isolate 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "io" 22 "os" 23 "path" 24 "path/filepath" 25 "regexp" 26 "runtime" 27 "sort" 28 "strconv" 29 "strings" 30 31 "go/ast" 32 "go/parser" 33 "go/token" 34 35 "github.com/yosuke-furukawa/json5/encoding/json5" 36 "go.chromium.org/luci/common/errors" 37 ) 38 39 var osPathSeparator = string(os.PathSeparator) 40 41 // LoadIsolateAsConfig parses one .isolate file and returns a Configs instance. 42 // 43 // Arguments: 44 // isolateDir: only used to load relative includes so it doesn't depend on 45 // cwd. 46 // value: is the loaded dictionary that was defined in the gyp file. 47 // 48 // The expected format is strict, anything diverting from the format below will 49 // result in error: 50 // { 51 // 'includes': [ 52 // 'foo.isolate', 53 // ], 54 // 'conditions': [ 55 // ['OS=="vms" and foo=42', { 56 // 'variables': { 57 // 'files': [ 58 // ... 59 // ], 60 // }, 61 // }], 62 // ... 63 // ], 64 // 'variables': { 65 // ... 66 // }, 67 // } 68 func LoadIsolateAsConfig(isolateDir string, content []byte) (*Configs, error) { 69 // isolateDir must be in native style. 70 if !filepath.IsAbs(isolateDir) { 71 return nil, fmt.Errorf("%s is not an absolute path", isolateDir) 72 } 73 processedIsolate, err := processIsolate(content) 74 if err != nil { 75 return nil, errors.Annotate(err, "failed to process isolate (isolateDir: %s)", isolateDir).Err() 76 } 77 out := processedIsolate.toConfigs() 78 // Add global variables. The global variables are on the empty tuple key. 79 globalconfigName := make([]variableValue, len(out.ConfigVariables)) 80 out.setConfig(globalconfigName, newConfigSettings(processedIsolate.variables, isolateDir)) 81 82 // Add configuration-specific variables. 83 allConfigs, err := processedIsolate.getAllConfigs(out.ConfigVariables) 84 if err != nil { 85 return nil, err 86 } 87 configVariablesIndex := makeConfigVariableIndex(out.ConfigVariables) 88 for _, cond := range processedIsolate.conditions { 89 newConfigs := newConfigs(out.ConfigVariables) 90 configs := cond.matchConfigs(configVariablesIndex, allConfigs) 91 for _, config := range configs { 92 newConfigs.setConfig(configName(config), newConfigSettings(cond.variables, isolateDir)) 93 } 94 if out, err = out.union(newConfigs); err != nil { 95 return nil, err 96 } 97 } 98 // Load the includes. Process them in reverse so the last one take precedence. 99 for i := len(processedIsolate.includes) - 1; i >= 0; i-- { 100 included, err := loadIncludedIsolate(isolateDir, processedIsolate.includes[i]) 101 if err != nil { 102 return nil, err 103 } 104 if out, err = out.union(included); err != nil { 105 return nil, err 106 } 107 } 108 return out, nil 109 } 110 111 // LoadIsolateForConfig loads the .isolate file and returns 112 // the information unprocessed but filtered for the specific OS. 113 // 114 // Returns: 115 // 116 // dependencies, relDir, error. 117 // 118 // relDir and dependencies are fixed to use os.PathSeparator. 119 func LoadIsolateForConfig(isolateDir string, content []byte, configVariables map[string]string) ( 120 []string, string, error) { 121 // Load the .isolate file, process its conditions, retrieve dependencies. 122 isolate, err := LoadIsolateAsConfig(isolateDir, content) 123 if err != nil { 124 return nil, "", err 125 } 126 cn := configName{} 127 var missingVars []string 128 for _, variable := range isolate.ConfigVariables { 129 if value, ok := configVariables[variable]; ok { 130 cn = append(cn, makeVariableValue(value)) 131 } else { 132 missingVars = append(missingVars, variable) 133 } 134 } 135 if len(missingVars) > 0 { 136 sort.Strings(missingVars) 137 return nil, "", errors.Reason("these configuration variables were missing from the command line: %v", missingVars).Err() 138 } 139 // A configuration is to be created with all the combinations of free variables. 140 config, err := isolate.GetConfig(cn) 141 if err != nil { 142 return nil, "", err 143 } 144 dependencies := config.Files 145 relDir := config.IsolateDir 146 if os.PathSeparator != '/' { 147 dependencies = make([]string, len(config.Files)) 148 for i, f := range config.Files { 149 dependencies[i] = strings.Replace(f, "/", osPathSeparator, -1) 150 } 151 relDir = strings.Replace(relDir, "/", osPathSeparator, -1) 152 } 153 return dependencies, relDir, nil 154 } 155 156 func loadIncludedIsolate(isolateDir, include string) (*Configs, error) { 157 if filepath.IsAbs(include) { 158 return nil, fmt.Errorf("failed to load configuration; absolute include path %s", include) 159 } 160 includedIsolate := filepath.Clean(filepath.Join(isolateDir, include)) 161 if runtime.GOOS == "windows" && (strings.ToLower(includedIsolate)[0] != strings.ToLower(isolateDir)[0]) { 162 return nil, errors.New("can't reference a .isolate file from another drive") 163 } 164 content, err := os.ReadFile(includedIsolate) 165 if err != nil { 166 return nil, err 167 } 168 return LoadIsolateAsConfig(filepath.Dir(includedIsolate), content) 169 } 170 171 // Configs represents a processed .isolate file. 172 // 173 // Stores the file in a processed way, split by configuration. 174 // 175 // At this point, we don't know all the possibilities. So mount a partial view 176 // that we have. 177 // 178 // This class doesn't hold isolateDir, since it is dependent on the final 179 // configuration selected. 180 type Configs struct { 181 // ConfigVariables contains names only, sorted by name; the order is same as in byConfig. 182 ConfigVariables []string 183 // The config key are lists of values of vars in the same order as ConfigSettings. 184 byConfig map[string]configPair 185 } 186 187 func newConfigs(configVariables []string) *Configs { 188 c := &Configs{configVariables, map[string]configPair{}} 189 assert(sort.IsSorted(sort.StringSlice(c.ConfigVariables))) 190 return c 191 } 192 193 func (c *Configs) getSortedConfigPairs() configPairs { 194 pairs := make([]configPair, 0, len(c.byConfig)) 195 for _, pair := range c.byConfig { 196 pairs = append(pairs, pair) 197 } 198 out := configPairs(pairs) 199 sort.Sort(out) 200 return out 201 } 202 203 // GetConfig returns all configs that matches this config as a single ConfigSettings. 204 // 205 // Returns nil if none apply. 206 func (c *Configs) GetConfig(cn configName) (*ConfigSettings, error) { 207 // Order byConfig according to configNames ordering function. 208 out := &ConfigSettings{} 209 for _, pair := range c.getSortedConfigPairs() { 210 ok := true 211 for i, confKey := range cn { 212 if pair.key[i].isBound() && pair.key[i].compare(confKey) != 0 { 213 ok = false 214 break 215 } 216 } 217 if ok { 218 var err error 219 if out, err = out.union(pair.value); err != nil { 220 return nil, err 221 } 222 } 223 } 224 return out, nil 225 } 226 227 // setConfig sets the ConfigSettings for this key. 228 // 229 // The key is a tuple of bounded or unbounded variables. The global variable 230 // is the key where all values are unbounded. 231 func (c *Configs) setConfig(cn configName, value *ConfigSettings) { 232 assert(len(cn) == len(c.ConfigVariables)) 233 assert(value != nil) 234 key := cn.key() 235 pair, ok := c.byConfig[key] 236 assert(!ok, "setConfig must not override existing keys (%s => %v)", key, pair.value) 237 c.byConfig[key] = configPair{cn, value} 238 } 239 240 // union returns a new Configs instance, the union of variables from self and rhs. 241 // 242 // It keeps ConfigVariables sorted in the output. 243 func (c *Configs) union(rhs *Configs) (*Configs, error) { 244 // Merge the keys of ConfigVariables for each Configs instances. All the new 245 // variables will become unbounded. This requires realigning the keys. 246 configVariables := uniqueMergeSortedStrings( 247 c.ConfigVariables, rhs.ConfigVariables) 248 out := newConfigs(configVariables) 249 byConfig := configPairs(append( 250 c.expandConfigVariables(configVariables), 251 rhs.expandConfigVariables(configVariables)...)) 252 if len(byConfig) == 0 { 253 return out, nil 254 } 255 // Take union of ConfigSettings with the same configName (key), 256 // in order left, right. 257 // Thus, preserve the order between left, right while sorting. 258 sort.Stable(byConfig) 259 last := byConfig[0] 260 for _, curr := range byConfig[1:] { 261 if last.key.compare(curr.key) == 0 { 262 val, err := last.value.union(curr.value) 263 if err != nil { 264 return out, err 265 } 266 last.value = val 267 } else { 268 out.setConfig(last.key, last.value) 269 last = curr 270 } 271 } 272 out.setConfig(last.key, last.value) 273 return out, nil 274 } 275 276 // expandConfigVariables returns new configPair list for newConfigVars. 277 func (c *Configs) expandConfigVariables(newConfigVars []string) []configPair { 278 // Get mapping from old config vars list to new one. 279 mapping := make([]int, len(newConfigVars)) 280 i := 0 281 for n, nk := range newConfigVars { 282 if i == len(c.ConfigVariables) || c.ConfigVariables[i] > nk { 283 mapping[n] = -1 284 } else if c.ConfigVariables[i] == nk { 285 mapping[n] = i 286 i++ 287 } else { 288 // Must never happen because newConfigVars and c.configVariables are sorted ASC, 289 // and newConfigVars contain c.configVariables as a subset. 290 panic("unreachable code") 291 } 292 } 293 // Expands configName to match newConfigVars. 294 getNewconfigName := func(old configName) configName { 295 newConfig := make(configName, len(mapping)) 296 for k, v := range mapping { 297 if v != -1 { 298 newConfig[k] = old[v] 299 } 300 } 301 return newConfig 302 } 303 // Compute new byConfig. 304 out := make([]configPair, 0, len(c.byConfig)) 305 for _, pair := range c.byConfig { 306 out = append(out, configPair{getNewconfigName(pair.key), pair.value}) 307 } 308 return out 309 } 310 311 // ConfigSettings represents the dependency variables for a single build configuration. 312 // 313 // The structure is immutable. 314 type ConfigSettings struct { 315 // Files is the list of dependencies. The items use '/' as a path separator. 316 Files []string 317 // IsolateDir is the path where to start the command from. 318 // It uses the OS' native path separator and it must be an absolute path. 319 IsolateDir string 320 } 321 322 func newConfigSettings(variables variables, isolateDir string) *ConfigSettings { 323 if isolateDir == "" { 324 // It must be an empty object if isolateDir is not set. 325 assert(variables.isEmpty(), variables) 326 } else { 327 assert(filepath.IsAbs(isolateDir)) 328 } 329 c := &ConfigSettings{ 330 make([]string, len(variables.Files)), 331 isolateDir, 332 } 333 copy(c.Files, variables.Files) 334 sort.Strings(c.Files) 335 return c 336 } 337 338 // union merges two config settings together into a new instance. 339 // 340 // A new instance is not created and self or rhs is returned if the other 341 // object is the empty object. 342 // 343 // Dependencies listed in rhs are patch adjusted ONLY if they don't start with 344 // a path variable, e.g. the characters '<('. 345 func (lhs *ConfigSettings) union(rhs *ConfigSettings) (*ConfigSettings, error) { 346 // When an object has IsolateDir == "", it means it is the empty object. 347 if lhs.IsolateDir == "" { 348 return rhs, nil 349 } 350 if rhs.IsolateDir == "" { 351 return lhs, nil 352 } 353 354 if runtime.GOOS == "windows" && strings.ToLower(lhs.IsolateDir)[0] != strings.ToLower(rhs.IsolateDir)[0] { 355 return nil, errors.New("All .isolate files must be on same drive") 356 } 357 358 // Takes the difference between the two isolateDir. Note that while 359 // isolateDir is in native path case, all other references are in posix. 360 // If self doesn't define any file, use rhs. 361 useRHS := len(lhs.Files) == 0 362 363 lRelCwd, rRelCwd := lhs.IsolateDir, rhs.IsolateDir 364 lFiles, rFiles := lhs.Files, rhs.Files 365 if useRHS { 366 // Rebase files in rhs. 367 lRelCwd, rRelCwd = rhs.IsolateDir, lhs.IsolateDir 368 lFiles, rFiles = rhs.Files, lhs.Files 369 } 370 371 rebasePath, err := filepath.Rel(lRelCwd, rRelCwd) 372 if err != nil { 373 return nil, err 374 } 375 rebasePath = strings.Replace(rebasePath, osPathSeparator, "/", -1) 376 377 filesSet := map[string]bool{} 378 for _, f := range lFiles { 379 filesSet[f] = true 380 } 381 for _, f := range rFiles { 382 // Rebase item. 383 if !(strings.HasPrefix(f, "<(") || rebasePath == ".") { 384 // paths are posix here. 385 trailingSlash := strings.HasSuffix(f, "/") 386 f = path.Join(rebasePath, f) 387 if trailingSlash { 388 f += "/" 389 } 390 } 391 filesSet[f] = true 392 } 393 // Remove duplicates. 394 files := make([]string, 0, len(filesSet)) 395 for f := range filesSet { 396 files = append(files, f) 397 } 398 sort.Strings(files) 399 return &ConfigSettings{files, lRelCwd}, nil 400 } 401 402 // Private details. 403 404 // isolate represents contents of the isolate file. 405 // The main purpose is (de)serialization. 406 type isolate struct { 407 Includes []string `json:"includes,omitempty"` 408 Conditions []condition `json:"conditions"` 409 Variables variables `json:"variables,omitempty"` 410 } 411 412 // condition represents conditional part of an isolate file. 413 type condition struct { 414 Condition string 415 Variables variables 416 } 417 418 // MarshalJSON implements json.Marshaler interface. 419 func (p *condition) MarshalJSON() ([]byte, error) { 420 d := [2]json.RawMessage{} 421 var err error 422 if d[0], err = json.Marshal(&p.Condition); err != nil { 423 return nil, err 424 } 425 m := map[string]variables{"variables": p.Variables} 426 if d[1], err = json.Marshal(&m); err != nil { 427 return nil, err 428 } 429 return json.Marshal(&d) 430 } 431 432 // UnmarshalJSON implements json.Unmarshaler interface. 433 func (p *condition) UnmarshalJSON(data []byte) error { 434 var d []json.RawMessage 435 if err := json.Unmarshal(data, &d); err != nil { 436 return err 437 } 438 if len(d) != 2 { 439 return errors.New("condition must be a list with two items") 440 } 441 if err := json.Unmarshal(d[0], &p.Condition); err != nil { 442 return err 443 } 444 m := map[string]variables{} 445 if err := json.Unmarshal(d[1], &m); err != nil { 446 return err 447 } 448 var ok bool 449 if p.Variables, ok = m["variables"]; !ok { 450 return errors.New("variables item is required in condition") 451 } 452 return nil 453 } 454 455 // variables represents variable as part of condition or top level in an isolate file. 456 type variables struct { 457 Files []string `json:"files"` 458 } 459 460 // variableValue holds a single value of a string or an int, 461 // otherwise it is unbound. 462 type variableValue struct { 463 S *string 464 I *int 465 } 466 467 func makeVariableValue(s string) variableValue { 468 v := variableValue{} 469 if i, err := strconv.Atoi(s); err == nil { 470 v.I = &i 471 } else { 472 v.S = &s 473 } 474 return v 475 } 476 477 func (v variableValue) String() string { 478 if v.S != nil { 479 return *v.S 480 } else if v.I != nil { 481 return fmt.Sprintf("%d", *v.I) 482 } 483 return "" 484 } 485 486 // compare returns 0 if equal, 1 if lhs < right, else -1. 487 // Order: unbound < 1 < 2 < "abc" < "cde" . 488 func (v variableValue) compare(rhs variableValue) int { 489 if v.I != nil { 490 if rhs.I != nil { 491 // Both integers. 492 if *v.I < *rhs.I { 493 return 1 494 } else if *v.I > *rhs.I { 495 return -1 496 } 497 return 0 498 } else if rhs.S != nil { 499 // int vs string 500 return 1 501 } 502 // int vs Unbound. 503 return -1 504 } else if v.S != nil { 505 if rhs.S != nil { 506 // Both strings. 507 if *v.S < *rhs.S { 508 return 1 509 } else if *v.S > *rhs.S { 510 return -1 511 } 512 return 0 513 } 514 // string vs (int | unbound) 515 return -1 516 } else if rhs.isBound() { 517 // unbound vs (int|string) 518 return 1 519 } 520 // unbound vs unbound 521 return 0 522 } 523 524 func (v variableValue) isBound() bool { 525 return v.S != nil || v.I != nil 526 } 527 528 // variableValueKey is for indexing by variableValue in a map. 529 type variableValueKey string 530 531 func (v variableValue) key() variableValueKey { 532 if v.S != nil { 533 return variableValueKey("~" + *v.S) 534 } 535 if v.I != nil { 536 return variableValueKey(strconv.Itoa(*v.I)) 537 } 538 return variableValueKey("") 539 } 540 541 // variablesValueSet maps variable name to set of possible values 542 // found in condition strings. 543 type variablesValuesSet map[string]map[variableValueKey]variableValue 544 545 func (v variablesValuesSet) cartesianProductOfValues(orderedKeys []string) ([][]variableValue, error) { 546 if len(orderedKeys) == 0 { 547 return [][]variableValue{}, nil 548 } 549 // Prepare ordered by orderedKeys list of variableValue 550 allValues := make([][]variableValue, 0, len(orderedKeys)) 551 for _, key := range orderedKeys { 552 valuesSet := v[key] 553 values := make([]variableValue, 0, len(valuesSet)) 554 for _, value := range valuesSet { 555 values = append(values, value) 556 } 557 allValues = append(allValues, values) 558 } 559 // Precompute length of output for alloc and for assertion at the end. 560 length := 1 561 for _, values := range allValues { 562 length *= len(values) 563 } 564 if length <= 0 { 565 return nil, errors.New("some variable had empty valuesSet?") 566 } 567 out := make([][]variableValue, 0, length) 568 // indices[i] points to index in allValues[i]; stop once indices[-1] == len(allValues[-1]). 569 indices := make([]int, len(orderedKeys)) 570 for { 571 next := make([]variableValue, len(orderedKeys)) 572 for i, values := range allValues { 573 if indices[i] == len(values) { 574 if i+1 == len(orderedKeys) { 575 if length != len(out) { 576 return nil, errors.New("internal error") 577 } 578 return out, nil 579 } 580 indices[i] = 0 581 indices[i+1]++ 582 } 583 next[i] = values[indices[i]] 584 } 585 out = append(out, next) 586 indices[0]++ 587 } 588 // unreachable 589 } 590 591 // processedIsolate is verified Isolate ready for further processing. 592 // Immutable once created. 593 type processedIsolate struct { 594 includes []string 595 conditions []*processedCondition 596 variables variables 597 varsValsSet variablesValuesSet 598 } 599 600 // convertIsolateToJSON5 cleans up isolate content to be json5. 601 func convertIsolateToJSON5(content []byte) io.Reader { 602 out := &bytes.Buffer{} 603 for _, l := range strings.Split(string(content), "\n") { 604 l = strings.TrimSpace(l) 605 if len(l) == 0 || l[0] == '#' { 606 continue 607 } 608 l = strings.Replace(l, "\"", "\\\"", -1) 609 l = strings.Replace(l, "'", "\"", -1) 610 _, _ = io.WriteString(out, l+"\n") 611 } 612 return out 613 } 614 615 func parseIsolate(content []byte) (*isolate, error) { 616 isolate := &isolate{} 617 618 // Try to decode json without any modification first. 619 if err := json5.Unmarshal(content, &isolate); err == nil { 620 return isolate, nil 621 } 622 623 // TODO: remove single quotation usage from isolate user. 624 // if err := json5.NewDecoder(json5src).Decode(isolate); err != nil { 625 var data any 626 if err := json5.NewDecoder(convertIsolateToJSON5(content)).Decode(&data); err != nil { 627 return nil, errors.Annotate(err, "failed to decode json %s", string(content)).Err() 628 } 629 buf, _ := json.Marshal(&data) 630 if err := json.Unmarshal(buf, isolate); err != nil { 631 return nil, err 632 } 633 return isolate, nil 634 } 635 636 // processIsolate loads isolate, then verifies and returns it as 637 // a processedIsolate for faster further processing. 638 func processIsolate(content []byte) (*processedIsolate, error) { 639 isolate, err := parseIsolate(content) 640 if err != nil { 641 return nil, errors.Annotate(err, "failed to parse isolate").Err() 642 } 643 out := &processedIsolate{ 644 isolate.Includes, 645 make([]*processedCondition, len(isolate.Conditions)), 646 isolate.Variables, 647 variablesValuesSet{}, 648 } 649 for i, cond := range isolate.Conditions { 650 out.conditions[i], err = processCondition(cond, out.varsValsSet) 651 if err != nil { 652 return nil, err 653 } 654 } 655 return out, nil 656 } 657 658 func (p *processedIsolate) toConfigs() *Configs { 659 configVariables := make([]string, 0, len(p.varsValsSet)) 660 for varName := range p.varsValsSet { 661 configVariables = append(configVariables, varName) 662 } 663 sort.Strings(configVariables) 664 return newConfigs(configVariables) 665 } 666 667 func (p *processedIsolate) getAllConfigs(configVariables []string) ([][]variableValue, error) { 668 return p.varsValsSet.cartesianProductOfValues(configVariables) 669 } 670 671 // processedCondition is a verified Condition ready for evaluation. 672 type processedCondition struct { 673 condition string 674 variables variables 675 expr ast.Expr 676 // equalityValues are cached values of literals in "id==val" parts of 677 // Condition, uniquely indexed by their position. 678 equalityValues map[token.Pos]variableValue 679 } 680 681 // processCondition ensures condition is in correct format, and converts it 682 // to processedCondition for further evaluation. 683 func processCondition(c condition, varsAndValues variablesValuesSet) (*processedCondition, error) { 684 goCond, err := pythonToGoCondition(c.Condition) 685 if err != nil { 686 return nil, err 687 } 688 out := &processedCondition{condition: c.Condition, variables: c.Variables} 689 if out.expr, err = parser.ParseExpr(goCond); err != nil { 690 return nil, err 691 } 692 if out.equalityValues, err = processConditionAst(out.expr, varsAndValues); err != nil { 693 return nil, err 694 } 695 return out, nil 696 } 697 698 func processConditionAst(expr ast.Expr, varsAndValues variablesValuesSet) (map[token.Pos]variableValue, error) { 699 err := error(nil) 700 equalityValues := map[token.Pos]variableValue{} 701 ast.Inspect(expr, func(n ast.Node) bool { 702 if n == nil { 703 return true 704 } 705 if err != nil { 706 return false 707 } 708 switch n := n.(type) { 709 case *ast.BinaryExpr: 710 if n.Op == token.LAND || n.Op == token.LOR { 711 return true 712 } 713 if n.Op != token.EQL { 714 err = fmt.Errorf("unknown binary operator %s", n.Op) 715 return false 716 } 717 id, value, tmpErr := verifyIDEqualValue(n) 718 if tmpErr != nil { 719 err = tmpErr 720 return false 721 } 722 equalityValues[n.Pos()] = value 723 if _, exists := varsAndValues[id]; !exists { 724 varsAndValues[id] = map[variableValueKey]variableValue{} 725 } 726 varsAndValues[id][value.key()] = value 727 return false 728 case *ast.ParenExpr: 729 return true 730 default: 731 err = fmt.Errorf("unknown expression type %T", n) 732 return false 733 } 734 // unreachable 735 }) 736 if err != nil { 737 return nil, fmt.Errorf("invalid Condition: %s", err) 738 } 739 return equalityValues, nil 740 } 741 742 func (c *processedCondition) matchConfigs(configVariablesIndex map[string]int, allConfigs [][]variableValue) [][]variableValue { 743 // Brute force: try all possible subsets of boundVariables and their values 744 // (config) from allConfigs for which condition is True. 745 // Runs in O(2^len(configVariables) * len(allConfigs)), but in practice it 746 // is fast enough. 747 if len(configVariablesIndex) > 60 { 748 panic(fmt.Errorf("isolate doesn't scale to %d ConfigVariables", len(configVariablesIndex))) 749 } 750 boundSubsetsCount := int64(1) << uint(len(configVariablesIndex)) 751 okConfigs := map[string][]variableValue{} 752 // boundBits represents current subset of configVariables which are bound. 753 for boundBits := int64(0); boundBits < boundSubsetsCount; boundBits++ { 754 for _, config := range allConfigs { 755 isTrue, err := c.evaluate(func(varName string) variableValue { 756 i := configVariablesIndex[varName] 757 if (boundBits & (int64(1) << uint(i))) != 0 { 758 return config[i] 759 } 760 return variableValue{} 761 }) 762 if err == nil && isTrue { 763 okConfig := make([]variableValue, len(config)) 764 for i := 0; i < len(config); i++ { 765 if (boundBits & (int64(1) << uint(i))) != 0 { 766 okConfig[i] = config[i] 767 } 768 } 769 okConfigs[configName(okConfig).key()] = okConfig 770 } 771 } 772 } 773 out := make([][]variableValue, 0, len(okConfigs)) 774 for _, okConfig := range okConfigs { 775 out = append(out, okConfig) 776 } 777 return out 778 } 779 780 type funcGetVariableValue func(varName string) variableValue 781 782 var errUnbound = errors.New("required variable is unbound") 783 784 func (c *processedCondition) evaluate(getValue funcGetVariableValue) (bool, error) { 785 ce := conditionEvaluator{cond: c, getVarValue: getValue, stop: false} 786 isTrue := ce.eval(c.expr) 787 if ce.stop { 788 return false, errUnbound 789 } 790 return isTrue, nil 791 } 792 793 type conditionEvaluator struct { 794 cond *processedCondition 795 getVarValue funcGetVariableValue 796 stop bool 797 } 798 799 func (c *conditionEvaluator) eval(e ast.Expr) bool { 800 if c.stop { 801 return false 802 } 803 switch e := e.(type) { 804 case *ast.ParenExpr: 805 return c.eval(e.X) 806 case *ast.BinaryExpr: 807 if e.Op == token.LAND { 808 return c.eval(e.X) && c.eval(e.Y) 809 } else if e.Op == token.LOR { 810 return c.eval(e.X) || c.eval(e.Y) 811 } 812 assert(e.Op == token.EQL) 813 value := c.getVarValue(e.X.(*ast.Ident).Name) 814 if !value.isBound() { 815 c.stop = true 816 return false 817 } 818 eqValue := c.cond.equalityValues[e.Pos()] 819 assert(eqValue.isBound()) 820 return value.compare(c.cond.equalityValues[e.Pos()]) == 0 821 default: 822 panic(errors.New("processCondition must have ensured condition is evaluatable")) 823 } 824 // unreachable 825 } 826 827 func makeConfigVariableIndex(configVariables []string) map[string]int { 828 out := map[string]int{} 829 for i, name := range configVariables { 830 out[name] = i 831 } 832 return out 833 } 834 835 // verifyIDEqualValue processes identifier == (int | string) part of Condition. 836 func verifyIDEqualValue(expr *ast.BinaryExpr) (name string, value variableValue, err error) { 837 id, ok := expr.X.(*ast.Ident) 838 if !ok { 839 err = errors.New("left operand of == must be identifier") 840 return 841 } 842 name = id.Name 843 844 val, ok := expr.Y.(*ast.BasicLit) 845 if ok && val.Kind == token.INT { 846 if i, parseErr := strconv.Atoi(val.Value); parseErr != nil { 847 err = errors.New("right operand of == must be int or string value") 848 } else { 849 value.I = &i 850 } 851 } else if ok && val.Kind == token.STRING { 852 // val.Value includes quotation marks, but we need just pure string. 853 s := val.Value[1 : len(val.Value)-1] 854 value.S = &s 855 } else { 856 err = errors.New("right operand of == must be int or string value") 857 } 858 return 859 } 860 861 // pythonToGoCondition converts Python code into valid Go code. 862 func pythonToGoCondition(pyCond string) (string, error) { 863 // Isolate supported grammar is: 864 // expr ::= expr ( "or" | "and" ) expr 865 // | identifier "==" ( string | int ) 866 // and parentheses. 867 // We convert this to equivalent Go expression by: 868 // * replacing all 'string' to "string" 869 // * replacing `and` and `or` to `&&` and `||` operators, respectively. 870 // We work with runes to be safe against unicode. 871 left := []rune(pyCond) 872 var err error 873 goChunk := "" 874 var out []string 875 for len(left) > 0 { 876 // Process non-string tokens till next string token. 877 goChunk, left = pythonToGoNonString(left) 878 out = append(out, goChunk) 879 if len(left) != 0 { 880 if goChunk, left, err = pythonToGoString(left); err != nil { 881 return "", err 882 } 883 out = append(out, goChunk) 884 } 885 } 886 return strings.Join(out, ""), nil 887 } 888 889 var rePythonAnd = regexp.MustCompile(`(\band\b)`) 890 var rePythonOr = regexp.MustCompile(`(\bor\b)`) 891 892 func pythonToGoNonString(left []rune) (string, []rune) { 893 end := len(left) 894 for i, r := range left { 895 if r == '\'' || r == '"' { 896 end = i 897 break 898 } 899 } 900 out := string(left[:end]) 901 out = rePythonAnd.ReplaceAllString(out, "&&") 902 out = rePythonOr.ReplaceAllString(out, "||") 903 return out, left[end:] 904 } 905 906 var errParseCondition = errors.New("failed to parse Condition string") 907 908 func pythonToGoString(left []rune) (string, []rune, error) { 909 quoteRune := left[0] 910 if quoteRune != '"' && quoteRune != '\'' { 911 panic(fmt.Errorf("pythonToGoString must be called with ' or \" as first rune: %s", string(left))) 912 } 913 left = left[1:] 914 escaped := false 915 goRunes := []rune{'"'} 916 for i, c := range left { 917 if c == quoteRune && !escaped { 918 goRunes = append(goRunes, '"') 919 return string(goRunes), left[i+1:], nil 920 } 921 if c == '\'' && escaped { 922 // Python allows "\'a", which is the same as "'a", but Go 923 // doesn't allow this, so remove redundant escape before '. 924 goRunes = goRunes[:len(goRunes)-1] 925 } else if c == '"' && !escaped { 926 // Either " is first char, or " is unescaped inside a string. 927 goRunes = append(goRunes, '\\') 928 } 929 goRunes = append(goRunes, c) 930 if c == '\\' { 931 escaped = !escaped 932 } else { 933 escaped = false 934 } 935 } 936 return string(goRunes), left, errParseCondition 937 } 938 939 func (v *variables) isEmpty() bool { 940 return len(v.Files) == 0 941 } 942 943 // configName defines a config as an ordered set of bound and unbound variable values. 944 type configName []variableValue 945 946 func (c configName) compare(rhs configName) int { 947 // Bound value is less than unbound one. 948 assert(len(c) == len(rhs)) 949 for i, l := range c { 950 if r := l.compare(rhs[i]); r != 0 { 951 return r 952 } 953 } 954 return 0 955 } 956 957 func (c configName) Equals(o configName) bool { 958 if len(c) != len(o) { 959 return false 960 } 961 return c.compare(o) == 0 962 } 963 964 func (c configName) key() string { 965 parts := make([]string, 0, len(c)) 966 for _, v := range c { 967 if !v.isBound() { 968 parts = append(parts, "∀") 969 } else { 970 parts = append(parts, "∃", v.String()) 971 } 972 } 973 return strings.Join(parts, "\x00") 974 } 975 976 type configPair struct { 977 key configName 978 value *ConfigSettings 979 } 980 981 // configPairs implements interface for sort package sorting. 982 type configPairs []configPair 983 984 func (c configPairs) Len() int { 985 return len(c) 986 } 987 988 func (c configPairs) Less(i, j int) bool { 989 return c[i].key.compare(c[j].key) > 0 990 } 991 992 func (c configPairs) Swap(i, j int) { 993 c[i], c[j] = c[j], c[i] 994 }