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  }