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  }