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 }