v.io/jiri@v0.0.0-20160715023856-abfb8b131290/profiles/target.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 profiles 6 7 import ( 8 "fmt" 9 "os" 10 "runtime" 11 "sort" 12 "strconv" 13 "strings" 14 "time" 15 16 "v.io/x/lib/envvar" 17 ) 18 19 // Target represents specification for the environment that the profile is 20 // to be built for. Targets include a version string to allow for upgrades and 21 // for the simultaneous existence of incompatible versions. 22 // 23 // Target and Environment implement flag.Getter so that they may be used 24 // with the flag package. Two flags are required, one to specify the target 25 // in <arch>-<os>@<version> format and a second to specify environment 26 // variables either as comma separated values or as repeated arguments. 27 type Target struct { 28 arch, opsys, version string 29 // The environment as specified on the command line 30 commandLineEnv Environment 31 // The environment as modified by a profile implementation 32 Env Environment 33 InstallationDir string // where this target is installed. 34 UpdateTime time.Time 35 isSet bool 36 } 37 38 // Arch returns the archiecture of this target. 39 func (pt *Target) Arch() string { 40 return pt.arch 41 } 42 43 // OS returns the operating system of this target. 44 func (pt *Target) OS() string { 45 return pt.opsys 46 } 47 48 // Version returns the version of this target. 49 func (pt *Target) Version() string { 50 return pt.version 51 } 52 53 // SetVersion sets the version for the target. 54 func (pt *Target) SetVersion(v string) { 55 pt.version = v 56 } 57 58 // CommandLineEnv returns the environment variables set on the 59 // command line for this target. 60 func (pt Target) CommandLineEnv() Environment { 61 r := Environment{Vars: make([]string, len(pt.commandLineEnv.Vars))} 62 copy(r.Vars, pt.commandLineEnv.Vars) 63 return r 64 } 65 66 // UseCommandLineEnv copies the command line supplied environment variables 67 // into the mutable environment of the Target. It should be called as soon 68 // as all command line parsing has been completed and before the target is 69 // otherwise used. 70 func (pt *Target) UseCommandLineEnv() { 71 pt.Env = pt.CommandLineEnv() 72 } 73 74 // TargetSpecificDirname returns a directory name that is specific 75 // to that target taking account the architecture, operating system and 76 // command line environment variables, if relevant, into account (e.g 77 // GOARM={5,6,7}). 78 func (pt *Target) TargetSpecificDirname() string { 79 env := envvar.SliceToMap(pt.commandLineEnv.Vars) 80 dir := pt.arch + "_" + pt.opsys 81 if pt.arch == "arm" { 82 if armv, present := env["GOARM"]; present { 83 dir += "_armv" + armv 84 } 85 } 86 return dir 87 } 88 89 type Environment struct { 90 Vars []string `xml:"var"` 91 } 92 93 // NewTarget creates a new target using the supplied target and environment 94 // parameters specified in command line format. 95 func NewTarget(target string, env ...string) (Target, error) { 96 t := &Target{} 97 err := t.Set(target) 98 for _, e := range env { 99 t.commandLineEnv.Set(e) 100 } 101 return *t, err 102 } 103 104 // Match returns true if pt and pt2 meet the following criteria in the 105 // order they are listed: 106 // - if the Arch and OS fields are exactly the same 107 // - if pt has a non-zero length Version field, then it must be 108 // the same as that in pt2 109 // Match is used by the various methods and functions in this package 110 // when looking up Targets unless otherwise specified. 111 func (pt Target) Match(pt2 *Target) bool { 112 if pt.arch != pt2.arch || pt.opsys != pt2.opsys { 113 return false 114 } 115 if (len(pt.version) > 0) && (pt.version != pt2.version) { 116 return false 117 } 118 return true 119 } 120 121 // Less returns true if pt2 is considered less than pt. The ordering 122 // takes into account only the architecture, operating system and version of 123 // the target. The architecture and operating system are ordered 124 // lexicographically in ascending order, then the version is ordered but in 125 // descending lexicographic order except that the empty string is considered 126 // the 'highest' value. 127 // Thus, (targets in <arch>-<os>[@<version>] format), are all true: 128 // b-c < c-c 129 // b-c@3 < b-c@2 130 func (pt *Target) Less(pt2 *Target) bool { 131 switch { 132 case pt.arch != pt2.arch: 133 return pt.arch < pt2.arch 134 case pt.opsys != pt2.opsys: 135 return pt.opsys < pt2.opsys 136 case len(pt.version) == 0 && len(pt2.version) > 0: 137 return true 138 case len(pt.version) > 0 && len(pt2.version) == 0: 139 return false 140 case pt.version != pt2.version: 141 return compareVersions(pt.version, pt2.version) > 0 142 default: 143 return false 144 } 145 } 146 147 // CrossCompiling returns true if the target differs from that of the runtime. 148 func (pt Target) CrossCompiling() bool { 149 arch, _ := goarch() 150 return (pt.arch != arch) || (pt.opsys != runtime.GOOS) 151 } 152 153 // Usage returns the usage string for Target. 154 func (pt *Target) Usage() string { 155 return "specifies a profile target in the following form: <arch>-<os>[@<version>]" 156 } 157 158 // Set implements flag.Value. 159 func (t *Target) Set(val string) error { 160 index := strings.IndexByte(val, '@') 161 if index > -1 { 162 t.version = val[index+1:] 163 val = val[:index] 164 } 165 parts := strings.Split(val, "-") 166 if len(parts) != 2 || (len(parts[0]) == 0 || len(parts[1]) == 0) { 167 return fmt.Errorf("%q doesn't look like <arch>-<os>[@<version>]", val) 168 } 169 t.arch = parts[0] 170 t.opsys = parts[1] 171 t.isSet = true 172 return nil 173 } 174 175 // Get implements flag.Getter. 176 func (t Target) Get() interface{} { 177 if !t.isSet { 178 // Default value. 179 arch, isSet := goarch() 180 return Target{ 181 isSet: isSet, 182 arch: arch, 183 opsys: runtime.GOOS, 184 version: t.version, 185 Env: t.Env, 186 } 187 } 188 return t 189 } 190 191 func goarch() (string, bool) { 192 // GOARCH may be set to 386 for binaries compiled for amd64 - i.e. 193 // the same binary can be run in these two modes, but the compiled 194 // in value of runtime.GOARCH will only ever be the value that it 195 // was compiled with. 196 if a := os.Getenv("GOARCH"); len(a) > 0 { 197 return a, true 198 } 199 return runtime.GOARCH, false 200 } 201 202 // DefaultTarget returns a default value for a Target. Use this function to 203 // initialize Targets that are expected to set from the command line via 204 // the flags package. 205 func DefaultTarget() Target { 206 arch, isSet := goarch() 207 return Target{ 208 isSet: isSet, 209 arch: arch, 210 opsys: runtime.GOOS, 211 } 212 } 213 214 // NativeTarget returns a value for Target for the host on which it is running. 215 // Use this function for Target values that are passed into other functions 216 // and libraries where a native target is specifically required. 217 func NativeTarget() Target { 218 arch, _ := goarch() 219 return Target{ 220 isSet: true, 221 arch: arch, 222 opsys: runtime.GOOS, 223 } 224 } 225 226 // IsSet returns true if this target has had its value set. 227 func (pt Target) IsSet() bool { 228 return pt.isSet 229 } 230 231 // String implements flag.Getter. 232 func (pt Target) String() string { 233 v := pt.Get().(Target) 234 return fmt.Sprintf("%v-%v@%s", v.arch, v.opsys, v.version) 235 } 236 237 // Targets is a list of *Target's ordered by architecture, 238 // operating system and descending versions. 239 type Targets []*Target 240 241 // Implements sort.Len 242 func (tl Targets) Len() int { 243 return len(tl) 244 } 245 246 // Implements sort.Less 247 func (tl Targets) Less(i, j int) bool { 248 return tl[i].Less(tl[j]) 249 } 250 251 // Implements sort.Swap 252 func (tl Targets) Swap(i, j int) { 253 tl[i], tl[j] = tl[i], tl[j] 254 } 255 256 func (tl Targets) Sort() { 257 sort.Sort(tl) 258 } 259 260 // DebugString returns a pretty-printed representation of pt. 261 func (pt Target) DebugString() string { 262 v := pt.Get().(Target) 263 return fmt.Sprintf("%v-%v@%s dir:%s --env=%s envvars:%v", v.arch, v.opsys, v.version, pt.InstallationDir, strings.Join(pt.commandLineEnv.Vars, ","), pt.Env.Vars) 264 } 265 266 // Set implements flag.Getter. 267 func (e *Environment) Get() interface{} { 268 return *e 269 } 270 271 // Set implements flag.Value. 272 func (e *Environment) Set(val string) error { 273 for _, v := range strings.Split(val, ",") { 274 parts := strings.SplitN(v, "=", 2) 275 if len(parts) != 2 || (len(parts[0]) == 0) { 276 return fmt.Errorf("%q doesn't look like var=[val]", v) 277 } 278 e.Vars = append(e.Vars, v) 279 } 280 return nil 281 } 282 283 // String implements flag.Getter. 284 func (e Environment) String() string { 285 return strings.Join(e.Vars, ",") 286 } 287 288 // Usage returns the usage string for Environment. 289 func (e Environment) Usage() string { 290 return "specify an environment variable in the form: <var>=[<val>],..." 291 } 292 293 // InsertTarget inserts the given target into Targets if it's not 294 // already there and returns a new slice. 295 func InsertTarget(targets Targets, target *Target) Targets { 296 for i, t := range targets { 297 if !t.Less(target) { 298 targets = append(targets, nil) 299 copy(targets[i+1:], targets[i:]) 300 targets[i] = target 301 return targets 302 } 303 } 304 return append(targets, target) 305 } 306 307 // RemoveTarget removes the given target from a slice of Target and returns 308 // a slice. 309 func RemoveTarget(targets Targets, target *Target) Targets { 310 for i, t := range targets { 311 if target.Match(t) { 312 targets, targets[len(targets)-1] = append(targets[:i], targets[i+1:]...), nil 313 return targets 314 } 315 } 316 return targets 317 } 318 319 // FindTarget returns the first target that matches the requested target from 320 // the slice of Targets. If target has not been explicitly set and there is 321 // only a single target available in targets then that one target is considered 322 // as matching. 323 func FindTarget(targets Targets, target *Target) *Target { 324 for _, t := range targets { 325 if target.Match(t) { 326 tmp := *t 327 return &tmp 328 } 329 } 330 return nil 331 } 332 333 // FindTargetWithDefault is like FindTarget except that if there is only one 334 // target in the slice and the requested target has not been explicitly set 335 // (IsSet is false) then that one target is returned by default. 336 func FindTargetWithDefault(targets Targets, target *Target) *Target { 337 if len(targets) == 1 && !target.IsSet() { 338 tmp := *targets[0] 339 return &tmp 340 } 341 return FindTarget(targets, target) 342 } 343 344 // compareVersions compares version numbers. It handles cases like: 345 // compareVersions("2", "11") => 1 346 // compareVersions("1.1", "1.2") => 1 347 // compareVersions("1.2", "1.2.1") => 1 348 // compareVersions("1.2.1.b", "1.2.1.c") => 1 349 func compareVersions(v1, v2 string) int { 350 v1parts := strings.Split(v1, ".") 351 v2parts := strings.Split(v2, ".") 352 353 maxLen := len(v1parts) 354 if len(v2parts) > maxLen { 355 maxLen = len(v2parts) 356 } 357 358 for i := 0; i < maxLen; i++ { 359 if i == len(v1parts) { 360 // v2 has more parts than v1, so v2 > v1. 361 return -1 362 } 363 if i == len(v2parts) { 364 // v1 has more parts than v2, so v1 > v2. 365 return 1 366 } 367 368 mustCompareStrings := false 369 v1part, err := strconv.Atoi(v1parts[i]) 370 if err != nil { 371 mustCompareStrings = true 372 } 373 v2part, err := strconv.Atoi(v2parts[i]) 374 if err != nil { 375 mustCompareStrings = true 376 } 377 if mustCompareStrings { 378 return strings.Compare(v1parts[i], v2parts[i]) 379 } 380 381 if v1part > v2part { 382 return 1 383 } 384 if v2part > v1part { 385 return -1 386 } 387 } 388 return 0 389 }