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 }