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 }