github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	"github.com/juju/juju/juju/arch"
    22  )
    23  
    24  // The presence and format of this constant is very important.
    25  // The debian/rules build recipe uses this value for the version
    26  // number of the release package.
    27  const version = "1.19.4"
    28  
    29  // The version that we switched over from old style numbering to new style.
    30  var switchOverVersion = MustParse("1.19.9")
    31  
    32  // lsbReleaseFile is the name of the file that is read in order to determine
    33  // the release version of ubuntu.
    34  var lsbReleaseFile = "/etc/lsb-release"
    35  
    36  // Current gives the current version of the system.  If the file
    37  // "FORCE-VERSION" is present in the same directory as the running
    38  // binary, it will override this.
    39  var Current = Binary{
    40  	Number: MustParse(version),
    41  	Series: osVersion(),
    42  	Arch:   arch.HostArch(),
    43  }
    44  
    45  var Compiler = runtime.Compiler
    46  
    47  func init() {
    48  	toolsDir := filepath.Dir(os.Args[0])
    49  	v, err := ioutil.ReadFile(filepath.Join(toolsDir, "FORCE-VERSION"))
    50  	if err != nil {
    51  		if !os.IsNotExist(err) {
    52  			fmt.Fprintf(os.Stderr, "WARNING: cannot read forced version: %v\n", err)
    53  		}
    54  		return
    55  	}
    56  	Current.Number = MustParse(strings.TrimSpace(string(v)))
    57  }
    58  
    59  // Number represents a juju version.  When bugs are fixed the patch number is
    60  // incremented; when new features are added the minor number is incremented
    61  // and patch is reset; and when compatibility is broken the major version is
    62  // incremented and minor and patch are reset.  The build number is
    63  // automatically assigned and has no well defined sequence.  If the build
    64  // number is greater than zero or the tag is non-empty it indicates that the
    65  // release is still in development.  For versions older than 1.19.3,
    66  // development releases were indicated by an odd Minor number of any non-zero
    67  // build number.
    68  type Number struct {
    69  	Major int
    70  	Minor int
    71  	Tag   string
    72  	Patch int
    73  	Build int
    74  }
    75  
    76  // Zero is occasionally convenient and readable.
    77  // Please don't change its value.
    78  var Zero = Number{}
    79  
    80  // Binary specifies a binary version of juju.
    81  type Binary struct {
    82  	Number
    83  	Series string
    84  	Arch   string
    85  }
    86  
    87  func (v Binary) String() string {
    88  	return fmt.Sprintf("%v-%s-%s", v.Number, v.Series, v.Arch)
    89  }
    90  
    91  // GetBSON turns v into a bson.Getter so it can be saved directly
    92  // on a MongoDB database with mgo.
    93  func (v Binary) GetBSON() (interface{}, error) {
    94  	return v.String(), nil
    95  }
    96  
    97  // SetBSON turns v into a bson.Setter so it can be loaded directly
    98  // from a MongoDB database with mgo.
    99  func (vp *Binary) SetBSON(raw bson.Raw) error {
   100  	var s string
   101  	err := raw.Unmarshal(&s)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	v, err := ParseBinary(s)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	*vp = v
   110  	return nil
   111  }
   112  
   113  func (v Binary) MarshalJSON() ([]byte, error) {
   114  	return json.Marshal(v.String())
   115  }
   116  
   117  func (vp *Binary) UnmarshalJSON(data []byte) error {
   118  	var s string
   119  	if err := json.Unmarshal(data, &s); err != nil {
   120  		return err
   121  	}
   122  	v, err := ParseBinary(s)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	*vp = v
   127  	return nil
   128  }
   129  
   130  // GetYAML implements goyaml.Getter
   131  func (v Binary) GetYAML() (tag string, value interface{}) {
   132  	return "", v.String()
   133  }
   134  
   135  // SetYAML implements goyaml.Setter
   136  func (vp *Binary) SetYAML(tag string, value interface{}) bool {
   137  	vstr := fmt.Sprintf("%v", value)
   138  	if vstr == "" {
   139  		return false
   140  	}
   141  	v, err := ParseBinary(vstr)
   142  	if err != nil {
   143  		return false
   144  	}
   145  	*vp = v
   146  	return true
   147  }
   148  
   149  var (
   150  	binaryPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})(\.|-(\w+))(\d{1,9})(\.\d{1,9})?-([^-]+)-([^-]+)$`)
   151  	numberPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})(\.|-(\w+))(\d{1,9})(\.\d{1,9})?$`)
   152  )
   153  
   154  // MustParse parses a version and panics if it does
   155  // not parse correctly.
   156  func MustParse(s string) Number {
   157  	v, err := Parse(s)
   158  	if err != nil {
   159  		panic(err)
   160  	}
   161  	return v
   162  }
   163  
   164  // MustParseBinary parses a binary version and panics if it does
   165  // not parse correctly.
   166  func MustParseBinary(s string) Binary {
   167  	v, err := ParseBinary(s)
   168  	if err != nil {
   169  		panic(err)
   170  	}
   171  	return v
   172  }
   173  
   174  // ParseBinary parses a binary version of the form "1.2.3-series-arch".
   175  func ParseBinary(s string) (Binary, error) {
   176  	m := binaryPat.FindStringSubmatch(s)
   177  	if m == nil {
   178  		return Binary{}, fmt.Errorf("invalid binary version %q", s)
   179  	}
   180  	var v Binary
   181  	v.Major = atoi(m[1])
   182  	v.Minor = atoi(m[2])
   183  	v.Tag = m[4]
   184  	v.Patch = atoi(m[5])
   185  	if m[6] != "" {
   186  		v.Build = atoi(m[6][1:])
   187  	}
   188  	v.Series = m[7]
   189  	v.Arch = m[8]
   190  	return v, nil
   191  }
   192  
   193  // Parse parses the version, which is of the form 1.2.3
   194  // giving the major, minor and release versions
   195  // respectively.
   196  func Parse(s string) (Number, error) {
   197  	m := numberPat.FindStringSubmatch(s)
   198  	if m == nil {
   199  		return Number{}, fmt.Errorf("invalid version %q", s)
   200  	}
   201  	var v Number
   202  	v.Major = atoi(m[1])
   203  	v.Minor = atoi(m[2])
   204  	v.Tag = m[4]
   205  	v.Patch = atoi(m[5])
   206  	if m[6] != "" {
   207  		v.Build = atoi(m[6][1:])
   208  	}
   209  	return v, nil
   210  }
   211  
   212  // atoi is the same as strconv.Atoi but assumes that
   213  // the string has been verified to be a valid integer.
   214  func atoi(s string) int {
   215  	n, err := strconv.Atoi(s)
   216  	if err != nil {
   217  		panic(err)
   218  	}
   219  	return n
   220  }
   221  
   222  func (v Number) String() string {
   223  	var s string
   224  	if v.Tag == "" {
   225  		s = fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
   226  	} else {
   227  		s = fmt.Sprintf("%d.%d-%s%d", v.Major, v.Minor, v.Tag, v.Patch)
   228  	}
   229  	if v.Build > 0 {
   230  		s += fmt.Sprintf(".%d", v.Build)
   231  	}
   232  	return s
   233  }
   234  
   235  // Compare returns -1, 0 or 1 depending on whether
   236  // v is less than, equal to or greater than w.
   237  func (v Number) Compare(w Number) int {
   238  	if v == w {
   239  		return 0
   240  	}
   241  	less := false
   242  	switch {
   243  	case v.Major != w.Major:
   244  		less = v.Major < w.Major
   245  	case v.Minor != w.Minor:
   246  		less = v.Minor < w.Minor
   247  	case v.Tag != w.Tag:
   248  		switch {
   249  		case v.Tag == "":
   250  			less = false
   251  		case w.Tag == "":
   252  			less = true
   253  		default:
   254  			less = v.Tag < w.Tag
   255  		}
   256  	case v.Patch != w.Patch:
   257  		less = v.Patch < w.Patch
   258  	case v.Build != w.Build:
   259  		less = v.Build < w.Build
   260  	}
   261  	if less {
   262  		return -1
   263  	}
   264  	return 1
   265  }
   266  
   267  // GetBSON turns v into a bson.Getter so it can be saved directly
   268  // on a MongoDB database with mgo.
   269  func (v Number) GetBSON() (interface{}, error) {
   270  	return v.String(), nil
   271  }
   272  
   273  // SetBSON turns v into a bson.Setter so it can be loaded directly
   274  // from a MongoDB database with mgo.
   275  func (vp *Number) SetBSON(raw bson.Raw) error {
   276  	var s string
   277  	err := raw.Unmarshal(&s)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	v, err := Parse(s)
   282  	if err != nil {
   283  		return err
   284  	}
   285  	*vp = v
   286  	return nil
   287  }
   288  
   289  func (v Number) MarshalJSON() ([]byte, error) {
   290  	return json.Marshal(v.String())
   291  }
   292  
   293  func (vp *Number) UnmarshalJSON(data []byte) error {
   294  	var s string
   295  	if err := json.Unmarshal(data, &s); err != nil {
   296  		return err
   297  	}
   298  	v, err := Parse(s)
   299  	if err != nil {
   300  		return err
   301  	}
   302  	*vp = v
   303  	return nil
   304  }
   305  
   306  // GetYAML implements goyaml.Getter
   307  func (v Number) GetYAML() (tag string, value interface{}) {
   308  	return "", v.String()
   309  }
   310  
   311  // SetYAML implements goyaml.Setter
   312  func (vp *Number) SetYAML(tag string, value interface{}) bool {
   313  	vstr := fmt.Sprintf("%v", value)
   314  	if vstr == "" {
   315  		return false
   316  	}
   317  	v, err := Parse(vstr)
   318  	if err != nil {
   319  		return false
   320  	}
   321  	*vp = v
   322  	return true
   323  }
   324  
   325  func isOdd(x int) bool {
   326  	return x%2 != 0
   327  }
   328  
   329  // IsDev returns whether the version represents a development version. A
   330  // version with a tag or a nonzero build component is considered to be a
   331  // development version.  Versions older than or equal to 1.19.3 (the switch
   332  // over time) check for odd minor versions.
   333  func (v Number) IsDev() bool {
   334  	if v.Compare(switchOverVersion) <= 0 {
   335  		return isOdd(v.Minor) || v.Build > 0
   336  	}
   337  	return v.Tag != "" || v.Build > 0
   338  }
   339  
   340  // ReleaseVersion looks for the value of DISTRIB_RELEASE in the content of
   341  // the lsbReleaseFile.  If the value is not found, the file is not found, or
   342  // an error occurs reading the file, an empty string is returned.
   343  func ReleaseVersion() string {
   344  	content, err := ioutil.ReadFile(lsbReleaseFile)
   345  	if err != nil {
   346  		return ""
   347  	}
   348  	const prefix = "DISTRIB_RELEASE="
   349  	for _, line := range strings.Split(string(content), "\n") {
   350  		if strings.HasPrefix(line, prefix) {
   351  			return strings.Trim(line[len(prefix):], "\t '\"")
   352  		}
   353  	}
   354  	return ""
   355  }
   356  
   357  // ParseMajorMinor takes an argument of the form "major.minor" and returns ints major and minor.
   358  func ParseMajorMinor(vers string) (int, int, error) {
   359  	parts := strings.Split(vers, ".")
   360  	major, err := strconv.Atoi(parts[0])
   361  	minor := -1
   362  	if err != nil {
   363  		return -1, -1, fmt.Errorf("invalid major version number %s: %v", parts[0], err)
   364  	}
   365  	if len(parts) == 2 {
   366  		minor, err = strconv.Atoi(parts[1])
   367  		if err != nil {
   368  			return -1, -1, fmt.Errorf("invalid minor version number %s: %v", parts[1], err)
   369  		}
   370  	} else if len(parts) > 2 {
   371  		return -1, -1, fmt.Errorf("invalid major.minor version number %s", vers)
   372  	}
   373  	return major, minor, nil
   374  }