launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/version/version.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // The version package implements version parsing.
     5  // It also acts as guardian of the current client Juju version number.
     6  package version
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"regexp"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"labix.org/v2/mgo/bson"
    20  )
    21  
    22  // The presence and format of this constant is very important.
    23  // The debian/rules build recipe uses this value for the version
    24  // number of the release package.
    25  const version = "1.17.3"
    26  
    27  // Current gives the current version of the system.  If the file
    28  // "FORCE-VERSION" is present in the same directory as the running
    29  // binary, it will override this.
    30  var Current = Binary{
    31  	Number: MustParse(version),
    32  	Series: readSeries("/etc/lsb-release"),
    33  	Arch:   ubuntuArch(runtime.GOARCH),
    34  }
    35  
    36  func init() {
    37  	toolsDir := filepath.Dir(os.Args[0])
    38  	v, err := ioutil.ReadFile(filepath.Join(toolsDir, "FORCE-VERSION"))
    39  	if err != nil {
    40  		if !os.IsNotExist(err) {
    41  			fmt.Fprintf(os.Stderr, "WARNING: cannot read forced version: %v\n", err)
    42  		}
    43  		return
    44  	}
    45  	Current.Number = MustParse(strings.TrimSpace(string(v)))
    46  }
    47  
    48  // Number represents a juju version.  When bugs are fixed the patch
    49  // number is incremented; when new features are added the minor number
    50  // is incremented and patch is reset; and when compatibility is broken
    51  // the major version is incremented and minor and patch are reset.  The
    52  // build number is automatically assigned and has no well defined
    53  // sequence.  If the build number is greater than zero or the minor
    54  // version is odd, it indicates that the release is still in
    55  // development.
    56  type Number struct {
    57  	Major int
    58  	Minor int
    59  	Patch int
    60  	Build int
    61  }
    62  
    63  // Zero is occasionally convenient and readable.
    64  // Please don't change its value.
    65  var Zero = Number{}
    66  
    67  // Binary specifies a binary version of juju.
    68  type Binary struct {
    69  	Number
    70  	Series string
    71  	Arch   string
    72  }
    73  
    74  func (v Binary) String() string {
    75  	return fmt.Sprintf("%v-%s-%s", v.Number, v.Series, v.Arch)
    76  }
    77  
    78  // GetBSON turns v into a bson.Getter so it can be saved directly
    79  // on a MongoDB database with mgo.
    80  func (v Binary) GetBSON() (interface{}, error) {
    81  	return v.String(), nil
    82  }
    83  
    84  // SetBSON turns v into a bson.Setter so it can be loaded directly
    85  // from a MongoDB database with mgo.
    86  func (vp *Binary) SetBSON(raw bson.Raw) error {
    87  	var s string
    88  	err := raw.Unmarshal(&s)
    89  	if err != nil {
    90  		return err
    91  	}
    92  	v, err := ParseBinary(s)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	*vp = v
    97  	return nil
    98  }
    99  
   100  func (v Binary) MarshalJSON() ([]byte, error) {
   101  	return json.Marshal(v.String())
   102  }
   103  
   104  func (vp *Binary) UnmarshalJSON(data []byte) error {
   105  	var s string
   106  	if err := json.Unmarshal(data, &s); err != nil {
   107  		return err
   108  	}
   109  	v, err := ParseBinary(s)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	*vp = v
   114  	return nil
   115  }
   116  
   117  var (
   118  	binaryPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})\.(\d{1,9})(\.\d{1,9})?-([^-]+)-([^-]+)$`)
   119  	numberPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})\.(\d{1,9})(\.\d{1,9})?$`)
   120  )
   121  
   122  // MustParse parses a version and panics if it does
   123  // not parse correctly.
   124  func MustParse(s string) Number {
   125  	v, err := Parse(s)
   126  	if err != nil {
   127  		panic(err)
   128  	}
   129  	return v
   130  }
   131  
   132  // MustParseBinary parses a binary version and panics if it does
   133  // not parse correctly.
   134  func MustParseBinary(s string) Binary {
   135  	v, err := ParseBinary(s)
   136  	if err != nil {
   137  		panic(err)
   138  	}
   139  	return v
   140  }
   141  
   142  // ParseBinary parses a binary version of the form "1.2.3-series-arch".
   143  func ParseBinary(s string) (Binary, error) {
   144  	m := binaryPat.FindStringSubmatch(s)
   145  	if m == nil {
   146  		return Binary{}, fmt.Errorf("invalid binary version %q", s)
   147  	}
   148  	var v Binary
   149  	v.Major = atoi(m[1])
   150  	v.Minor = atoi(m[2])
   151  	v.Patch = atoi(m[3])
   152  	if m[4] != "" {
   153  		v.Build = atoi(m[4][1:])
   154  	}
   155  	v.Series = m[5]
   156  	v.Arch = m[6]
   157  	return v, nil
   158  }
   159  
   160  // Parse parses the version, which is of the form 1.2.3
   161  // giving the major, minor and release versions
   162  // respectively.
   163  func Parse(s string) (Number, error) {
   164  	m := numberPat.FindStringSubmatch(s)
   165  	if m == nil {
   166  		return Number{}, fmt.Errorf("invalid version %q", s)
   167  	}
   168  	var v Number
   169  	v.Major = atoi(m[1])
   170  	v.Minor = atoi(m[2])
   171  	v.Patch = atoi(m[3])
   172  	if m[4] != "" {
   173  		v.Build = atoi(m[4][1:])
   174  	}
   175  	return v, nil
   176  }
   177  
   178  // atoi is the same as strconv.Atoi but assumes that
   179  // the string has been verified to be a valid integer.
   180  func atoi(s string) int {
   181  	n, err := strconv.Atoi(s)
   182  	if err != nil {
   183  		panic(err)
   184  	}
   185  	return n
   186  }
   187  
   188  func (v Number) String() string {
   189  	s := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
   190  	if v.Build > 0 {
   191  		s += fmt.Sprintf(".%d", v.Build)
   192  	}
   193  	return s
   194  }
   195  
   196  // Less returns whether v is semantically earlier in the
   197  // version sequence than w.
   198  func (v Number) Less(w Number) bool {
   199  	return v.lessMaybeEqual(w, false)
   200  }
   201  
   202  // LessEqual returns whether v is either the same number or
   203  // semantically earlier in the version sequence than w.
   204  func (v Number) LessEqual(w Number) bool {
   205  	return v.lessMaybeEqual(w, true)
   206  }
   207  
   208  func (v Number) lessMaybeEqual(w Number, wantEqual bool) bool {
   209  	switch {
   210  	case v.Major != w.Major:
   211  		return v.Major < w.Major
   212  	case v.Minor != w.Minor:
   213  		return v.Minor < w.Minor
   214  	case v.Patch != w.Patch:
   215  		return v.Patch < w.Patch
   216  	case v.Build != w.Build:
   217  		return v.Build < w.Build
   218  	}
   219  	return wantEqual
   220  }
   221  
   222  // GetBSON turns v into a bson.Getter so it can be saved directly
   223  // on a MongoDB database with mgo.
   224  func (v Number) GetBSON() (interface{}, error) {
   225  	return v.String(), nil
   226  }
   227  
   228  // SetBSON turns v into a bson.Setter so it can be loaded directly
   229  // from a MongoDB database with mgo.
   230  func (vp *Number) SetBSON(raw bson.Raw) error {
   231  	var s string
   232  	err := raw.Unmarshal(&s)
   233  	if err != nil {
   234  		return err
   235  	}
   236  	v, err := Parse(s)
   237  	if err != nil {
   238  		return err
   239  	}
   240  	*vp = v
   241  	return nil
   242  }
   243  
   244  func (v Number) MarshalJSON() ([]byte, error) {
   245  	return json.Marshal(v.String())
   246  }
   247  
   248  func (vp *Number) UnmarshalJSON(data []byte) error {
   249  	var s string
   250  	if err := json.Unmarshal(data, &s); err != nil {
   251  		return err
   252  	}
   253  	v, err := Parse(s)
   254  	if err != nil {
   255  		return err
   256  	}
   257  	*vp = v
   258  	return nil
   259  }
   260  
   261  func isOdd(x int) bool {
   262  	return x%2 != 0
   263  }
   264  
   265  // IsDev returns whether the version represents a development
   266  // version. A version with an odd-numbered minor component or
   267  // a nonzero build component is considered to be a development
   268  // version.
   269  func (v Number) IsDev() bool {
   270  	return isOdd(v.Minor) || v.Build > 0
   271  }
   272  
   273  func readSeries(releaseFile string) string {
   274  	data, err := ioutil.ReadFile(releaseFile)
   275  	if err != nil {
   276  		return "unknown"
   277  	}
   278  	for _, line := range strings.Split(string(data), "\n") {
   279  		const p = "DISTRIB_CODENAME="
   280  		if strings.HasPrefix(line, p) {
   281  			return strings.Trim(line[len(p):], "\t '\"")
   282  		}
   283  	}
   284  	return "unknown"
   285  }
   286  
   287  func ubuntuArch(arch string) string {
   288  	if arch == "386" {
   289  		arch = "i386"
   290  	}
   291  	return arch
   292  }
   293  
   294  // ParseMajorMinor takes an argument of the form "major.minor" and returns ints major and minor.
   295  func ParseMajorMinor(vers string) (int, int, error) {
   296  	parts := strings.Split(vers, ".")
   297  	major, err := strconv.Atoi(parts[0])
   298  	minor := -1
   299  	if err != nil {
   300  		return -1, -1, fmt.Errorf("invalid major version number %s: %v", parts[0], err)
   301  	}
   302  	if len(parts) == 2 {
   303  		minor, err = strconv.Atoi(parts[1])
   304  		if err != nil {
   305  			return -1, -1, fmt.Errorf("invalid minor version number %s: %v", parts[1], err)
   306  		}
   307  	} else if len(parts) > 2 {
   308  		return -1, -1, fmt.Errorf("invalid major.minor version number %s", vers)
   309  	}
   310  	return major, minor, nil
   311  }