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