
The effort and money invested in promoting the Go language often benefits other developers as well. At the end of last year, a very good article on semantic versioning was published on the
gopheracademy website. The volume itself, which is used in npm, begins with the house
^ and breaks everything. A translation is hidden under the cut, which will help you quickly inspect the versioning rake garden and how to use it now. And a few examples on Go. Give the word to the author!
Semantic versioning (also known as SemVer) has already become a popular approach to working with versions of programs and libraries. It not only makes it easier to work with successive releases, but allows both people and automation to understand the compatibility of these releases with each other and with the rest of the world. SemVer can be used a lot where, but most of all this approach is known in dependency management systems.
Before we talk about Go-specific things, let's take a look at what semantic versioning is (translation note: for this part, the translation was started).
')
The illustration shows the semantic parts of the version string. Most often you will only see the first three digits separated by dots
"." . Fully semantic version may consist of the following parts:
- “Major” version number. Increases (and not always by 1, remember PHP 5 -> 7) when the library or application API changes back in an incompatible way.
- “Minor” version number. Increases when new functions are added to the API without violating backward compatibility (translator's note: this is the worst thing. For example, npm defaults to the version with the “^” prefix, which means “any version with the same major.” The authors of npm reasonably believe that if changing minor backwards compatibility is not lost, then we can safely update it Surprise: a lot of library developers just do not know what semantic versioning is and break backward compatibility, as it will. t, and when installing “from scratch” on another computer, we get new versions of dependencies with broken backward compatibility). When increasing the number of “major”, it is decided to reset “minor” to 0, that is, after version 4.27 ... version 5.0 will follow ... and not 5.28
- “Patch” version number. Increased when fixing bugs, refactoring and other changes that do not break anything, but do not add new features. When increasing “Major” or “Minor”, ​​it is customary to reset “Patch” to 0 (translator's note: it is often called “build number” and simply increases by one with each continuous integration build, never resetting. This makes it easy to distinguish builds from friend during testing and analysis of error messages from users. The disadvantage of this approach is that the figure will soon become five-digit, but this does not bother many).
- String “pre-release”: an optional, dotted list, separated from the three version numbers by a minus sign. For example: “1.2.3-beta.2” . Used instead of tags to “mark” certain milestones in the design. These are usually “alpha”, “beta”, “release candidate” (“rc”) and their derivatives.
- Terminates the semantic version line metadata of the build system. As well as the list of “pre-release” tags, it is separated by dots . , but separating it from numbers or tags is not a minus, but a plus. This information is usually ignored (translator's note: the number of the build should go here — but very few people do this, very long lines are obtained).
Although the specification says nothing about the
“v” prefix, it is often used before the semantic version string, for example,
“v.12.3” . As well as metadata, it is customary to ignore it.
All this and more can be found in the official specification:
http://semver.org/Thanks to a well-thought-out specification, the lines of semantic versions can be easily parsed, sorted, and, most importantly, compared with each other and with ranges of “valid” versions. What, in fact, do most dependency managers, such as npm.
Parsing semantic versions in Go
Several packages are available for Go to work with semantic versions. In this article I will review this one:
github.com/Masterminds/semver . It conforms to the specification, supports the optional
“v” prefix, sorting, working with ranges and constraints. However, with limitations, this package works the same way as most solutions for other programming languages, such as JavaScript, Rust, and others.
The example below parses the string of the semantic version and displays either the “major” version or the error message:
v, err := semver.NewVersion("1.2.3-beta.1+build345") if err != nil { fmt.Println(err) } else { fmt.Println(v.Major()) }
The return value is an instance of
semver.Version , which contains a number of useful methods. If the string passed is not a semantic version, then the error
semver.ErrInvalidSemVer is returned .
But the real benefit of this library is not the ability to parse lines, but the ability to perform complex actions on semantic actions.
Sorting semantic versions
With the semver library, you can sort semantic versions using standard library tools. For example:
raw := []string{"1.2.3", "1.0", "1.0.0-alpha.1" "1.3", "2", "0.4.2",} vs := make([]*semver.Version, len(raw)) for i, r := range raw { v, err := semver.NewVersion(r) if err != nil { t.Errorf("Error parsing version: %s", err) } vs[i] = v } sort.Sort(semver.Collection(vs))
In this example, the set of semantic versions is converted into instances of
semver.Version , which are then added up to
semver.Collection . And
semver.Collection has everything you need to use with the standard
sort library. This is very convenient for properly sorting pre-release information and ignoring meta tags.
Ranges, Restrictions and Wildcards
One of the most popular questions about versions is to check if the version is in the specified range. Or does it satisfy some other constraints. All these checks are easy to do with the library:
c, err := semver.NewConstraint(">= 1.2.3, < 2.0.0, != 1.4.5") if err != nil { fmt.Println("Error parsing constraint:", err) return } v, err := semver.NewVersion("1.3") if err != nil { fmt.Println("Error parsing version:", err) return } a := c.Check(v) fmt.Println("Version within constraint:", a)
For those familiar with the ranges of versions in other languages ​​and tools, the library offers a well-known notation:
- ^ 1.2.3 denotes compatibility at the “major” level. This is syntactic sugar for the entry “> = 1.2.3, <2.0.0” . Used to indicate the “latest version of the API, still API compliant”.
- ~ 1.2.3 indicates compatibility at the “patch” version level. This is syntactic sugar for “> = 1.2.3, <1.3.0” . Used to indicate the desired version with the latest bugfixes, but without major changes.
- “1.2.3 - 3.4.5” indicates the exact range of versions, including the initial and final. This is syntactic sugar for “> = 1.2.3, <= 3.4.5” .
- The library also supports “wildcards” using the characters “x” , ”X” or “*” . You can specify the version as “2.x” , “1.2.x” or even as “*” . And all these notations can be easily combined with each other.
Start using semantic versions right now.
And your hair will become soft and silky! If your project has work with versions, then I suggest not to waste time and start using the standard of semantic versioning. For Go there is the
github.com/Masterminds/semver library described above, most modern languages ​​and toolchains also have something to offer. Especially Node.js with npm.