github.com/vanadium-archive/go.jiri@v0.0.0-20160715023856-abfb8b131290/profiles/profilesreader/reader.go (about)

     1  // Copyright 2015 The Vanadium Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package profilesreader provides support for reading and processing jiri profiles.
     6  package profilesreader
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"os"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"v.io/jiri"
    17  	"v.io/jiri/profiles"
    18  	"v.io/x/lib/envvar"
    19  )
    20  
    21  // GoFlags lists all of the Go environment variables and will be sorted in the
    22  // init function for this package.
    23  var GoFlags = []string{
    24  	"CC",
    25  	"CC_FOR_TARGET",
    26  	"CGO_ENABLED",
    27  	"CXX_FOR_TARGET",
    28  	"GO15VENDOREXPERIMENT",
    29  	"GOARCH",
    30  	"GOBIN",
    31  	"GOEXE",
    32  	"GOGCCFLAGS",
    33  	"GOHOSTARCH",
    34  	"GOHOSTOS",
    35  	"GOOS",
    36  	"GOPATH",
    37  	"GORACE",
    38  	"GOROOT",
    39  	"GOTOOLDIR",
    40  }
    41  
    42  type ProfilesMode bool
    43  
    44  func (pm *ProfilesMode) Set(s string) error {
    45  	v, err := strconv.ParseBool(s)
    46  	*pm = ProfilesMode(v)
    47  	return err
    48  }
    49  
    50  func (pm *ProfilesMode) Get() interface{} { return bool(*pm) }
    51  
    52  func (pm *ProfilesMode) String() string { return fmt.Sprintf("%v", *pm) }
    53  
    54  func (pm *ProfilesMode) IsBoolFlag() bool { return true }
    55  
    56  const (
    57  	UseProfiles  ProfilesMode = false
    58  	SkipProfiles ProfilesMode = true
    59  )
    60  
    61  func init() {
    62  	sort.Strings(GoFlags)
    63  }
    64  
    65  // UnsetGoEnvVars unsets Go environment variables in the given environment.
    66  func UnsetGoEnvVars(env *envvar.Vars) {
    67  	for _, k := range GoFlags {
    68  		env.Delete(k)
    69  	}
    70  }
    71  
    72  // UnsetGoEnvMap unsets Go environment variables in the given environment.
    73  func UnsetGoEnvMap(env map[string]string) {
    74  	for _, k := range GoFlags {
    75  		delete(env, k)
    76  	}
    77  }
    78  
    79  // GoEnvironmentFromOS() returns the values of all Go environment variables
    80  // as set via the OS; unset variables are omitted.
    81  func GoEnvironmentFromOS() []string {
    82  	os := envvar.SliceToMap(os.Environ())
    83  	vars := make([]string, 0, len(GoFlags))
    84  	for _, k := range GoFlags {
    85  		v, present := os[k]
    86  		if !present {
    87  			continue
    88  		}
    89  		vars = append(vars, envvar.JoinKeyValue(k, v))
    90  	}
    91  	return vars
    92  }
    93  
    94  // Reader wraps the various sources of configuration and profile
    95  // information to provide convenient methods for determing the environment
    96  // variables to use for a given situation. It creates an initial copy of the OS
    97  // environment that is mutated by its various methods.
    98  type Reader struct {
    99  	*envvar.Vars
   100  	profilesMode bool
   101  	path         string
   102  	jirix        *jiri.X
   103  	pdb          *profiles.DB
   104  }
   105  
   106  // NewReader creates a new profiles reader. If path is of non-zero
   107  // length then that path will be read as a profiles database, if not, the
   108  // existing, if any, in-memory profiles information will be used. If SkipProfiles
   109  // is specified for profilesMode, then no profiles are used.
   110  func NewReader(jirix *jiri.X, profilesMode ProfilesMode, path string) (*Reader, error) {
   111  	pdb := profiles.NewDB()
   112  	if profilesMode == UseProfiles && len(path) > 0 {
   113  		if err := pdb.Read(jirix, path); err != nil {
   114  			return nil, err
   115  		}
   116  	}
   117  	rd := &Reader{
   118  		jirix:        jirix,
   119  		path:         path,
   120  		profilesMode: bool(profilesMode),
   121  		pdb:          pdb,
   122  	}
   123  	rd.Vars = envvar.VarsFromOS()
   124  	if profilesMode == SkipProfiles {
   125  		return rd, nil
   126  	}
   127  	if len(os.Getenv("JIRI_PROFILE")) > 0 {
   128  		return nil, fmt.Errorf(`old style profiles are no longer supported. Please
   129  do not set JIRI_PROFILE.`)
   130  	}
   131  	return rd, nil
   132  }
   133  
   134  func (rd *Reader) SchemaVersion() profiles.Version {
   135  	return rd.pdb.SchemaVersion()
   136  }
   137  
   138  func (rd *Reader) Path() string {
   139  	return rd.path
   140  }
   141  
   142  func (rd *Reader) ProfileNames() []string {
   143  	return rd.pdb.Names()
   144  }
   145  
   146  func (rd *Reader) Profiles() []*profiles.Profile {
   147  	return rd.pdb.Profiles()
   148  }
   149  
   150  func (rd *Reader) DebugString() string {
   151  	var buf bytes.Buffer
   152  	buf.WriteString("Root: " + rd.jirix.Root + "\n")
   153  	buf.WriteString("Path: " + rd.path + "\n")
   154  	for _, p := range rd.pdb.Profiles() {
   155  		for _, t := range p.Targets() {
   156  			buf.WriteString(p.Name() + ": " + t.DebugString() + "\n")
   157  		}
   158  	}
   159  	return buf.String()
   160  }
   161  
   162  func (rd *Reader) LookupProfile(name string) *profiles.Profile {
   163  	installer, profile := profiles.SplitProfileName(name)
   164  	return rd.pdb.LookupProfile(installer, profile)
   165  }
   166  
   167  func (rd *Reader) LookupProfileTarget(name string, target profiles.Target) *profiles.Target {
   168  	installer, profile := profiles.SplitProfileName(name)
   169  	return rd.pdb.LookupProfileTarget(installer, profile, target)
   170  }
   171  
   172  // MergeEnv merges the embedded environment with the environment
   173  // variables provided by the vars parameter according to the policies parameter.
   174  func (rd *Reader) MergeEnv(policies map[string]MergePolicy, vars ...[]string) {
   175  	MergeEnv(policies, rd.Vars, vars...)
   176  }
   177  
   178  // EnvFromProfile obtains the environment variable settings from the specified
   179  // profile and target. It returns nil if the target and/or profile could not
   180  // be found.
   181  func (rd *Reader) EnvFromProfile(name string, target profiles.Target) []string {
   182  	installer, profile := profiles.SplitProfileName(name)
   183  	return rd.pdb.EnvFromProfile(installer, profile, target)
   184  }
   185  
   186  // MergeEnvFromProfiles merges the embedded environment with the environment
   187  // variables stored in the requested profiles. The profiles are those read from
   188  // the manifest. It will also expand all instances of ${JIRI_ROOT} in the
   189  // returned environment.
   190  func (rd *Reader) MergeEnvFromProfiles(policies map[string]MergePolicy, target profiles.Target, profileNames ...string) {
   191  	envs := [][]string{}
   192  	for _, name := range profileNames {
   193  		installer, profile := profiles.SplitProfileName(name)
   194  		e := rd.pdb.EnvFromProfile(installer, profile, target)
   195  		if e == nil {
   196  			continue
   197  		}
   198  		envs = append(envs, e)
   199  	}
   200  	MergeEnv(policies, rd.Vars, envs...)
   201  	jiri.ExpandEnv(rd.jirix, rd.Vars)
   202  }
   203  
   204  // SkippingProfiles returns true if no profiles are being used.
   205  func (rd *Reader) SkippingProfiles() bool {
   206  	return rd.profilesMode == bool(SkipProfiles)
   207  }
   208  
   209  // ValidateRequestProfilesAndTarget checks that the supplied slice of profiles
   210  // names is supported (including the 'jiri' profile) and that each has
   211  // the specified target installed taking account if running using profiles
   212  // at all or if using old-style profiles.
   213  func (rd *Reader) ValidateRequestedProfilesAndTarget(profileNames []string, target profiles.Target) error {
   214  	if rd.SkippingProfiles() {
   215  		return nil
   216  	}
   217  	for _, name := range profileNames {
   218  		installer, profile := profiles.SplitProfileName(name)
   219  		if rd.pdb.LookupProfileTarget(installer, profile, target) == nil {
   220  			return fmt.Errorf("%q for %q is not available or not installed, use the \"list\" command to see the installed/available profiles.", target, name)
   221  		}
   222  	}
   223  	return nil
   224  }
   225  
   226  // PrependToPath prepends its argument to the PATH environment variable.
   227  func (rd *Reader) PrependToPATH(path string) {
   228  	existing := rd.GetTokens("PATH", ":")
   229  	rd.SetTokens("PATH", append([]string{path}, existing...), ":")
   230  }
   231  
   232  // The environment variables passed to a subprocess are the result
   233  // of merging those in the processes environment and those from
   234  // one or more profiles according to the policies defined below.
   235  // There is a starting environment, nominally called 'base', and one
   236  // or profile environments. The base environment will typically be that
   237  // inherited by the running process from its invoking shell. A policy
   238  // consists of an 'action' and an optional separator to use when concatenating
   239  // variables.
   240  type MergePolicy struct {
   241  	Action    MergeAction
   242  	Separator string
   243  }
   244  
   245  type MergeAction int
   246  
   247  const (
   248  	// Use the first value encountered
   249  	First MergeAction = iota
   250  	// Use the last value encountered.
   251  	Last
   252  	// Ignore the variable regardless of where it occurs.
   253  	Ignore
   254  	// Append the current value to the values already accumulated.
   255  	Append
   256  	// Prepend the current value to the values already accumulated.
   257  	Prepend
   258  	// Ignore the value in the base environment, but append in the profiles.
   259  	IgnoreBaseAndAppend
   260  	// Ignore the value in the base environment, but prepend in the profiles.
   261  	IgnoreBaseAndPrepend
   262  	// Ignore the value in the base environment, but use the first value from profiles.
   263  	IgnoreBaseAndUseFirst
   264  	// Ignore the value in the base environment, but use the last value from profiles.
   265  	IgnoreBaseAndUseLast
   266  	// Ignore the values in the profiles.
   267  	IgnoreProfiles
   268  )
   269  
   270  var (
   271  	// A MergePolicy with a Last action.
   272  	UseLast = MergePolicy{Action: Last}
   273  	// A MergePolicy with a First action.
   274  	UseFirst = MergePolicy{Action: First}
   275  	// A MergePolicy that ignores the variable, regardless of where it occurs.
   276  	IgnoreVariable = MergePolicy{Action: Ignore}
   277  	// A MergePolicy that appends using : as a separator.
   278  	AppendPath = MergePolicy{Action: Append, Separator: ":"}
   279  	// A MergePolicy that appends using " " as a separator.
   280  	AppendFlag = MergePolicy{Action: Append, Separator: " "}
   281  	// A MergePolicy that prepends using : as a separator.
   282  	PrependPath = MergePolicy{Action: Prepend, Separator: ":"}
   283  	// A MergePolicy that prepends using " " as a separator.
   284  	PrependFlag = MergePolicy{Action: Prepend, Separator: " "}
   285  	// A MergePolicy that will ignore base, but append across profiles using ':'
   286  	IgnoreBaseAppendPath = MergePolicy{Action: IgnoreBaseAndAppend, Separator: ":"}
   287  	// A MergePolicy that will ignore base, but append across profiles using ' '
   288  	IgnoreBaseAppendFlag = MergePolicy{Action: IgnoreBaseAndAppend, Separator: " "}
   289  	// A MergePolicy that will ignore base, but prepend across profiles using ':'
   290  	IgnoreBasePrependPath = MergePolicy{Action: IgnoreBaseAndPrepend, Separator: ":"}
   291  	// A MergePolicy that will ignore base, but prepend across profiles using ' '
   292  	IgnoreBasePrependFlag = MergePolicy{Action: IgnoreBaseAndPrepend, Separator: " "}
   293  	// A MergePolicy that will ignore base, but use the last value from profiles.
   294  	IgnoreBaseUseFirst = MergePolicy{Action: IgnoreBaseAndUseFirst}
   295  	// A MergePolicy that will ignore base, but use the last value from profiles.
   296  	IgnoreBaseUseLast = MergePolicy{Action: IgnoreBaseAndUseLast}
   297  	// A MergePolicy that will always use the value from base and ignore profiles.
   298  	UseBaseIgnoreProfiles = MergePolicy{Action: IgnoreProfiles}
   299  )
   300  
   301  // ProfileMergePolicies returns an instance of MergePolicies that containts
   302  // appropriate default policies for use with MergeEnv from within
   303  // profile implementations.
   304  func ProfileMergePolicies() MergePolicies {
   305  	values := MergePolicies{
   306  		"PATH":         AppendPath,
   307  		"CCFLAGS":      AppendFlag,
   308  		"CXXFLAGS":     AppendFlag,
   309  		"LDFLAGS":      AppendFlag,
   310  		"CGO_CFLAGS":   AppendFlag,
   311  		"CGO_CXXFLAGS": AppendFlag,
   312  		"CGO_LDFLAGS":  AppendFlag,
   313  		"GOPATH":       IgnoreBaseAppendPath,
   314  		"GOARCH":       UseBaseIgnoreProfiles,
   315  		"GOOS":         UseBaseIgnoreProfiles,
   316  	}
   317  	mp := MergePolicies{}
   318  	for k, v := range values {
   319  		mp[k] = v
   320  	}
   321  	return mp
   322  }
   323  
   324  // JiriMergePolicies returns an instance of MergePolicies that contains
   325  // appropriate default policies for use with MergeEnv from jiri packages
   326  // and subcommands such as those used to build go, java etc.
   327  func JiriMergePolicies() MergePolicies {
   328  	mp := ProfileMergePolicies()
   329  	mp["GOPATH"] = PrependPath
   330  	mp["VDLPATH"] = PrependPath
   331  	mp["GOARCH"] = UseFirst
   332  	mp["GOOS"] = UseFirst
   333  	mp["GOROOT"] = IgnoreBaseUseLast
   334  	return mp
   335  }
   336  
   337  // MergeEnv merges environment variables in base with those
   338  // in vars according to the suppled policies.
   339  func MergeEnv(policies map[string]MergePolicy, base *envvar.Vars, vars ...[]string) {
   340  	// Remove any variables that have the IgnoreBase policy.
   341  	for k, _ := range base.ToMap() {
   342  		switch policies[k].Action {
   343  		case Ignore, IgnoreBaseAndAppend, IgnoreBaseAndPrepend, IgnoreBaseAndUseFirst, IgnoreBaseAndUseLast:
   344  			base.Delete(k)
   345  		}
   346  	}
   347  	for _, ev := range vars {
   348  		for _, tmp := range ev {
   349  			k, v := envvar.SplitKeyValue(tmp)
   350  			policy := policies[k]
   351  			action := policy.Action
   352  			switch policy.Action {
   353  			case IgnoreBaseAndAppend:
   354  				action = Append
   355  			case IgnoreBaseAndPrepend:
   356  				action = Prepend
   357  			case IgnoreBaseAndUseLast:
   358  				action = Last
   359  			case IgnoreBaseAndUseFirst:
   360  				action = First
   361  			}
   362  			switch action {
   363  			case Ignore, IgnoreProfiles:
   364  				continue
   365  			case Append, Prepend:
   366  				sep := policy.Separator
   367  				ov := base.GetTokens(k, sep)
   368  				nv := envvar.SplitTokens(v, sep)
   369  				if action == Append {
   370  					base.SetTokens(k, append(ov, nv...), sep)
   371  				} else {
   372  					base.SetTokens(k, append(nv, ov...), sep)
   373  				}
   374  			case First:
   375  				if !base.Contains(k) {
   376  					base.Set(k, v)
   377  				}
   378  			case Last:
   379  				base.Set(k, v)
   380  			}
   381  		}
   382  	}
   383  }
   384  
   385  // WithDefaultVersion returns a copy of the supplied target with its
   386  // version set to the default (i.e. emtpy string).
   387  func WithDefaultVersion(target profiles.Target) profiles.Target {
   388  	t := &target
   389  	t.SetVersion("")
   390  	return target
   391  }
   392  
   393  type MergePolicies map[string]MergePolicy
   394  
   395  func (mp *MergePolicy) String() string {
   396  	switch mp.Action {
   397  	case First:
   398  		return "use first"
   399  	case Last:
   400  		return "use last"
   401  	case Append:
   402  		return "append using '" + mp.Separator + "'"
   403  	case Prepend:
   404  		return "prepend using '" + mp.Separator + "'"
   405  	case IgnoreBaseAndAppend:
   406  		return "ignore in environment/base, append using '" + mp.Separator + "'"
   407  	case IgnoreBaseAndPrepend:
   408  		return "ignore in environment/base, prepend using '" + mp.Separator + "'"
   409  	case IgnoreBaseAndUseLast:
   410  		return "ignore in environment/base, use last value from profiles"
   411  	case IgnoreBaseAndUseFirst:
   412  		return "ignore in environment/base, use first value from profiles"
   413  	case IgnoreProfiles:
   414  		return "ignore in profiles"
   415  	}
   416  	return "unrecognised action"
   417  }
   418  
   419  func (mp MergePolicies) Usage() string {
   420  	return `<var>:<var>|<var>:|+<var>|<var>+|=<var>|<var>=
   421  <var> - use the first value of <var> encountered, this is the default action.
   422  <var>* - use the last value of <var> encountered.
   423  -<var> - ignore the variable, regardless of where it occurs.
   424  :<var> - append instances of <var> using : as a separator.
   425  <var>: - prepend instances of <var> using : as a separator.
   426  +<var> - append instances of <var> using space as a separator.
   427  <var>+ - prepend instances of <var> using space as a separator.
   428  ^:<var> - ignore <var> from the base/inherited environment but append in profiles as per :<var>.
   429  ^<var>: - ignore <var> from the base/inherited environment but prepend in profiles as per <var>:.
   430  ^+<var> - ignore <var> from the base/inherited environment but append in profiles as per +<var>.
   431  ^<var>+ - ignore <var> from the base/inherited environment but prepend in profiles as per <var>+.
   432  ^<var> - ignore <var> from the base/inherited environment but use the first value encountered in profiles.
   433  ^<var>* - ignore <var> from the base/inherited environment but use the last value encountered in profiles.
   434  <var>^ - ignore <var> from profiles.`
   435  }
   436  
   437  func separator(s string) string {
   438  	switch s {
   439  	case ":":
   440  		return ":"
   441  	default:
   442  		return "+"
   443  	}
   444  }
   445  
   446  // String implements flag.Value. It generates a string that can be used
   447  // to recreate the MergePolicies value and that can be passed as a parameter
   448  // to another process.
   449  func (mp MergePolicies) String() string {
   450  	buf := bytes.Buffer{}
   451  	// Ensure a stable order.
   452  	keys := make([]string, 0, len(mp))
   453  	for k, _ := range mp {
   454  		keys = append(keys, k)
   455  	}
   456  	sort.Strings(keys)
   457  	for _, k := range keys {
   458  		v := mp[k]
   459  		var s string
   460  		switch v.Action {
   461  		case First:
   462  			s = k
   463  		case Last:
   464  			s = k + "*"
   465  		case Append:
   466  			s = separator(v.Separator) + k
   467  		case Prepend:
   468  			s = k + separator(v.Separator)
   469  		case IgnoreBaseAndAppend:
   470  			s = "^" + separator(v.Separator) + k
   471  		case IgnoreBaseAndPrepend:
   472  			s = "^" + k + separator(v.Separator)
   473  		case IgnoreBaseAndUseLast:
   474  			s = "^" + k + "*"
   475  		case IgnoreBaseAndUseFirst:
   476  			s = "^" + k
   477  		case IgnoreProfiles:
   478  			s = k + "^"
   479  		}
   480  		buf.WriteString(s)
   481  		buf.WriteString(",")
   482  	}
   483  	return strings.TrimSuffix(buf.String(), ",")
   484  }
   485  
   486  func (mp MergePolicies) DebugString() string {
   487  	buf := bytes.Buffer{}
   488  	for k, v := range mp {
   489  		buf.WriteString(k + ": " + v.String() + ", ")
   490  	}
   491  	return strings.TrimSuffix(buf.String(), ", ")
   492  }
   493  
   494  // Get implements flag.Getter
   495  func (mp MergePolicies) Get() interface{} {
   496  	r := make(MergePolicies, len(mp))
   497  	for k, v := range mp {
   498  		r[k] = v
   499  	}
   500  	return r
   501  }
   502  
   503  func parseIgnoreBase(val string) (MergePolicy, string) {
   504  	if len(val) == 0 {
   505  		return IgnoreBaseUseLast, val
   506  	}
   507  	// [:+]<var>
   508  	switch val[0] {
   509  	case ':':
   510  		return IgnoreBaseAppendPath, val[1:]
   511  	case '+':
   512  		return IgnoreBaseAppendFlag, val[1:]
   513  	}
   514  	// <var>[:+]
   515  	last := len(val) - 1
   516  	switch val[last] {
   517  	case ':':
   518  		return IgnoreBasePrependPath, val[:last]
   519  	case '+':
   520  		return IgnoreBasePrependFlag, val[:last]
   521  	case '*':
   522  		return IgnoreBaseUseLast, val[:last]
   523  	}
   524  	return IgnoreBaseUseFirst, val
   525  }
   526  
   527  // Set implements flag.Value
   528  func (mp MergePolicies) Set(values string) error {
   529  	if len(values) == 0 {
   530  		return fmt.Errorf("no value!")
   531  	}
   532  	for _, val := range strings.Split(values, ",") {
   533  		// [:+^-]<var>
   534  		switch val[0] {
   535  		case '^':
   536  			a, s := parseIgnoreBase(val[1:])
   537  			mp[s] = a
   538  			continue
   539  		case '-':
   540  			mp[val[1:]] = IgnoreVariable
   541  			continue
   542  		case ':':
   543  			mp[val[1:]] = AppendPath
   544  			continue
   545  		case '+':
   546  			mp[val[1:]] = AppendFlag
   547  			continue
   548  		}
   549  		// <var>[:+^]
   550  		last := len(val) - 1
   551  		switch val[last] {
   552  		case ':':
   553  			mp[val[:last]] = PrependPath
   554  		case '+':
   555  			mp[val[:last]] = PrependFlag
   556  		case '*':
   557  			mp[val[:last]] = UseLast
   558  		case '^':
   559  			mp[val[:last]] = UseBaseIgnoreProfiles
   560  		default:
   561  			mp[val] = UseFirst
   562  		}
   563  	}
   564  	return nil
   565  }