diff --git a/Gopkg.lock b/Gopkg.lock index 7c01a53c..f7da60e5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,14 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + digest = "1:b6d886569181ec96ca83d529f4d6ba0cbf92ace7bb6f633f90c5f34d9bba7aab" + name = "github.com/blang/semver" + packages = ["."] + pruneopts = "UT" + revision = "ba2c2ddd89069b46a7011d4106f6868f17ee1705" + version = "v3.6.1" + [[projects]] digest = "1:b498b36dbb2b306d1c5205ee5236c9e60352be8f9eea9bf08186723a9f75b4f3" name = "github.com/emirpasic/gods" @@ -293,6 +301,7 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/blang/semver", "github.com/golang/glog", "github.com/google/go-github/github", "github.com/renstrom/dedent", diff --git a/artifacts/scripts/construct.sh b/artifacts/scripts/construct.sh index 178460cb..a95fd805 100755 --- a/artifacts/scripts/construct.sh +++ b/artifacts/scripts/construct.sh @@ -150,6 +150,7 @@ if [ -z "${SKIP_TAGS}" ]; then --dependencies "${DEPS}" \ --mapping-output-file "../tag-${REPO}-{{.Tag}}-mapping" \ --generate-godeps=${PUBLISHER_BOT_GENERATE_GODEPS:-false} \ + --publish-v0-semver \ -alsologtostderr \ "${EXTRA_ARGS[@]-}" if [ "${LAST_HEAD}" != "$(git rev-parse ${LAST_BRANCH})" ]; then diff --git a/cmd/sync-tags/gomod.go b/cmd/sync-tags/gomod.go index 28d783f5..1124b937 100644 --- a/cmd/sync-tags/gomod.go +++ b/cmd/sync-tags/gomod.go @@ -33,7 +33,7 @@ import ( // updateGomodWithTaggedDependencies gets the dependencies at the given tag and fills go.mod and go.sum. // If anything is changed, it commits the changes. Returns true if go.mod changed. -func updateGomodWithTaggedDependencies(tag string, depsRepo []string) (bool, error) { +func updateGomodWithTaggedDependencies(tag string, depsRepo []string, semverTag bool) (bool, error) { found := map[string]bool{} changed := false @@ -59,10 +59,14 @@ func updateGomodWithTaggedDependencies(tag string, depsRepo []string) (bool, err return changed, fmt.Errorf("failed to get tag %s for %q: %v", tag, depPkg, err) } rev := commit.String() - pseudoVersion := fmt.Sprintf("v0.0.0-%s-%s", commitTime.UTC().Format("20060102150405"), rev[:12]) + pseudoVersionOrTag := fmt.Sprintf("v0.0.0-%s-%s", commitTime.UTC().Format("20060102150405"), rev[:12]) - // in case the pseudoVersion has not changed, running go mod download will help - // in avoiding packaging it up if the pseudoVersion has been published already + if semverTag { + pseudoVersionOrTag = tag + } + + // in case the pseudoVersion/tag has not changed, running go mod download will help + // in avoiding packaging it up if the pseudoVersion/tag has been published already downloadCommand := exec.Command("go", "mod", "download") downloadCommand.Env = append(os.Environ(), "GO111MODULE=on", fmt.Sprintf("GOPRIVATE=%s", depPackages), "GOPROXY=https://proxy.golang.org") downloadCommand.Stdout = os.Stdout @@ -71,26 +75,26 @@ func updateGomodWithTaggedDependencies(tag string, depsRepo []string) (bool, err return changed, fmt.Errorf("error running go mod download for %s: %v", depPkg, err) } - // check if we have the pseudoVersion published already. if we don't, package it up + // check if we have the pseudoVersion/tag published already. if we don't, package it up // and save to local mod download cache. - if err := packageDepToGoModCache(depPath, depPkg, rev, pseudoVersion, commitTime); err != nil { + if err := packageDepToGoModCache(depPath, depPkg, rev, pseudoVersionOrTag, commitTime); err != nil { return changed, fmt.Errorf("failed to package %s dependency: %v", depPkg, err) } - requireCommand := exec.Command("go", "mod", "edit", "-fmt", "-require", fmt.Sprintf("%s@%s", depPkg, pseudoVersion)) + requireCommand := exec.Command("go", "mod", "edit", "-fmt", "-require", fmt.Sprintf("%s@%s", depPkg, pseudoVersionOrTag)) requireCommand.Env = append(os.Environ(), "GO111MODULE=on") requireCommand.Stdout = os.Stdout requireCommand.Stderr = os.Stderr if err := requireCommand.Run(); err != nil { - return changed, fmt.Errorf("unable to pin %s in the require section of go.mod to %s: %v", depPkg, pseudoVersion, err) + return changed, fmt.Errorf("unable to pin %s in the require section of go.mod to %s: %v", depPkg, pseudoVersionOrTag, err) } - replaceCommand := exec.Command("go", "mod", "edit", "-fmt", "-replace", fmt.Sprintf("%s=%s@%s", depPkg, depPkg, pseudoVersion)) + replaceCommand := exec.Command("go", "mod", "edit", "-fmt", "-replace", fmt.Sprintf("%s=%s@%s", depPkg, depPkg, pseudoVersionOrTag)) replaceCommand.Env = append(os.Environ(), "GO111MODULE=on") replaceCommand.Stdout = os.Stdout replaceCommand.Stderr = os.Stderr if err := replaceCommand.Run(); err != nil { - return changed, fmt.Errorf("unable to pin %s in the replace section of go.mod to %s: %v", depPkg, pseudoVersion, err) + return changed, fmt.Errorf("unable to pin %s in the replace section of go.mod to %s: %v", depPkg, pseudoVersionOrTag, err) } downloadCommand2 := exec.Command("go", "mod", "download") @@ -98,11 +102,11 @@ func updateGomodWithTaggedDependencies(tag string, depsRepo []string) (bool, err downloadCommand2.Stdout = os.Stdout downloadCommand2.Stderr = os.Stderr if err := downloadCommand2.Run(); err != nil { - return changed, fmt.Errorf("error running go mod download for pseudo-version %s for %s: %v", pseudoVersion, depPkg, err) + return changed, fmt.Errorf("error running go mod download for pseudo-version %s for %s: %v", pseudoVersionOrTag, depPkg, err) } tidyCommand := exec.Command("go", "mod", "tidy") - tidyCommand.Env = append(os.Environ(), "GO111MODULE=on", "GOPOXY=file://${GOPATH}/pkg/mod/cache/download") + tidyCommand.Env = append(os.Environ(), "GO111MODULE=on", fmt.Sprintf("GOPROXY=file://%s/pkg/mod/cache/download", os.Getenv("GOPATH"))) tidyCommand.Stdout = os.Stdout tidyCommand.Stderr = os.Stderr if err := tidyCommand.Run(); err != nil { @@ -110,7 +114,7 @@ func updateGomodWithTaggedDependencies(tag string, depsRepo []string) (bool, err } found[dep] = true - fmt.Printf("Bumping %s in go.mod to %s\n.", depPkg, rev) + fmt.Printf("Bumping %s in go.mod to %s.\n", depPkg, rev) changed = true } @@ -146,18 +150,18 @@ type ModuleInfo struct { Time string } -func packageDepToGoModCache(depPath, depPkg, commit, pseudoVersion string, commitTime time.Time) error { +func packageDepToGoModCache(depPath, depPkg, commit, pseudoVersionOrTag string, commitTime time.Time) error { cacheDir := fmt.Sprintf("%s/pkg/mod/cache/download/%s/@v", os.Getenv("GOPATH"), depPkg) - goModFile := fmt.Sprintf("%s/%s.mod", cacheDir, pseudoVersion) + goModFile := fmt.Sprintf("%s/%s.mod", cacheDir, pseudoVersionOrTag) if _, err := os.Stat(goModFile); err == nil { - fmt.Printf("Pseudo version %s for %s is already packaged up.\n", pseudoVersion, depPkg) + fmt.Printf("%s for %s is already packaged up.\n", pseudoVersionOrTag, depPkg) return nil } else if err != nil && !os.IsNotExist(err) { return fmt.Errorf("Could not check if %s exists: %v", goModFile, err) } - fmt.Printf("Packaging up pseudo version %s for %s into go mod cache.\n", pseudoVersion, depPkg) + fmt.Printf("Packaging up %s for %s into go mod cache.\n", pseudoVersionOrTag, depPkg) // create the cache if it doesn't exist if err := os.MkdirAll(filepath.Dir(goModFile), os.FileMode(755)); err != nil { @@ -173,14 +177,14 @@ func packageDepToGoModCache(depPath, depPkg, commit, pseudoVersion string, commi return fmt.Errorf("failed to checkout %s at %s: %v", depPkg, commit, err) } - // copy go.mod to pseudoVersion.mod in the cache dir + // copy go.mod to the cache dir if err := copyFile(fmt.Sprintf("%s/go.mod", depPath), goModFile); err != nil { return fmt.Errorf("unable to copy %s file to %s to gomod cache for %s: %v", fmt.Sprintf("%s/go.mod", depPath), goModFile, depPkg, err) } - // create pseudoVersion.info file in the cache dir + // create info file in the cache dir moduleInfo := ModuleInfo{ - Version: pseudoVersion, + Version: pseudoVersionOrTag, Name: commit, Short: commit[:12], Time: commitTime.UTC().Format("2006-01-02T15:04:05Z"), @@ -190,17 +194,17 @@ func packageDepToGoModCache(depPath, depPkg, commit, pseudoVersion string, commi if err != nil { return fmt.Errorf("error marshaling .info file for %s: %v", depPkg, err) } - if err := ioutil.WriteFile(fmt.Sprintf("%s/%s.info", cacheDir, pseudoVersion), moduleFile, 0644); err != nil { - return fmt.Errorf("failed to write %s file for %s: %v", fmt.Sprintf("%s/%s.info", cacheDir, pseudoVersion), depPkg, err) + if err := ioutil.WriteFile(fmt.Sprintf("%s/%s.info", cacheDir, pseudoVersionOrTag), moduleFile, 0644); err != nil { + return fmt.Errorf("failed to write %s file for %s: %v", fmt.Sprintf("%s/%s.info", cacheDir, pseudoVersionOrTag), depPkg, err) } - // create the pseudoVersion.zip file in the cache dir. This zip file has the same hash + // create the zip file in the cache dir. This zip file has the same hash // as of the zip file that would have been created by go mod download. - zipCommand := exec.Command("/gomod-zip", "--package-name", depPkg, "--pseudo-version", pseudoVersion) + zipCommand := exec.Command("/gomod-zip", "--package-name", depPkg, "--pseudo-version", pseudoVersionOrTag) zipCommand.Stdout = os.Stdout zipCommand.Stderr = os.Stderr if err := zipCommand.Run(); err != nil { - return fmt.Errorf("failed to run gomod-zip for %s at %s: %v", depPkg, pseudoVersion, err) + return fmt.Errorf("failed to run gomod-zip for %s at %s: %v", depPkg, pseudoVersionOrTag, err) } // append the pseudoVersion to the list file in the cache dir @@ -210,7 +214,7 @@ func packageDepToGoModCache(depPath, depPkg, commit, pseudoVersion string, commi } defer listFile.Close() - if _, err := listFile.WriteString(fmt.Sprintf("%s\n", pseudoVersion)); err != nil { + if _, err := listFile.WriteString(fmt.Sprintf("%s\n", pseudoVersionOrTag)); err != nil { return fmt.Errorf("unable to write to list file in %s: %v", cacheDir, err) } diff --git a/cmd/sync-tags/main.go b/cmd/sync-tags/main.go index 293566c5..2bacc2e3 100644 --- a/cmd/sync-tags/main.go +++ b/cmd/sync-tags/main.go @@ -28,6 +28,7 @@ import ( "text/template" "time" + "github.com/blang/semver" "github.com/golang/glog" "github.com/renstrom/dedent" gogit "gopkg.in/src-d/go-git.v4" @@ -73,6 +74,7 @@ func main() { skipFetch := flag.Bool("skip-fetch", false, "skip fetching tags") generateGodeps := flag.Bool("generate-godeps", false, "regenerate Godeps.json from go.mod") mappingOutputFile := flag.String("mapping-output-file", "", "a file name to write the source->dest hash mapping to ({{.Tag}} is substituted with the tag name, {{.Branch}} with the local branch name)") + publishSemverTags := flag.Bool("publish-v0-semver", false, "publish v0.x.y tag at destination repo for v1.x.y tag at the source repo") flag.Usage = Usage flag.Parse() @@ -187,6 +189,19 @@ func main() { bName = *prefix + name[1:] // remove the v } + var ( + semverTag = "" + publishSemverTag = false + ) + // if we are publishing semver tags + if *publishSemverTags { + // and this is a valid v1... semver tag + if _, semverErr := semver.Parse(name[1:]); semverErr == nil && strings.HasPrefix(name, "v1.") { + publishSemverTag = true + semverTag = "v0." + strings.TrimPrefix(name, "v1.") // replace v1.x.y with v0.x.y + } + } + // ignore non-annotated tags tag, err := r.TagObject(kh) if err != nil { @@ -199,14 +214,41 @@ func main() { continue } - // skip if it already exists in origin - if _, found := bTagCommits[bName]; found { + // skip if either tag exists at origin + _, nonSemverTagAtOrigin := bTagCommits[bName] + _, semverTagAtOrigin := bTagCommits[semverTag] + if nonSemverTagAtOrigin || (publishSemverTag && semverTagAtOrigin) { continue } - // do not override tags (we build master first, i.e. the x.y.z-alpha.0 tag on master will not be created for feature branches) + // if any of the tag exists locally, + // delete the tags, clear the cache and recreate them if tagExists(r, bName) { - continue + commit, commitTime, err := taggedCommitHashAndTime(r, bName) + if err != nil { + glog.Fatalf("Failed to get tag %s: %v", bName, err) + } + rev := commit.String() + pseudoVersion := fmt.Sprintf("v0.0.0-%s-%s", commitTime.UTC().Format("20060102150405"), rev[:12]) + + fmt.Printf("Clearing cache for local tag %s.\n", pseudoVersion) + if err := cleanCacheForTag(pseudoVersion); err != nil { + glog.Fatalf("Failed to clean go mod cache for %s: %v", pseudoVersion, err) + } + + if err := deleteTag(bName); err != nil { + glog.Fatalf("Failed to delete tag %s: %v", bName, err) + } + } + + if publishSemverTag && tagExists(r, semverTag) { + fmt.Printf("Clearing cache for local tag %s.\n", semverTag) + if err := cleanCacheForTag(semverTag); err != nil { + glog.Fatalf("Failed to clean go mod cache for %s: %v", semverTag, err) + } + if err := deleteTag(semverTag); err != nil { + glog.Fatalf("Failed to delete tag %s: %v", semverTag, err) + } } // lazily compute kube commit map @@ -258,22 +300,18 @@ func main() { // update go.mod or Godeps.json to point to actual tagged version in the dependencies. This version might differ // from the one currently in go.mod or Godeps.json because the other repo could have gotten more commit for this // tag, but this repo didn't. Compare https://github.com/kubernetes/publishing-bot/issues/12 for details. + var changed, goModExists bool + _, err = os.Stat("go.mod") + if err == nil { + goModExists = true + } + if len(dependentRepos) > 0 { - fmt.Printf("Checking that dependencies point to the actual tags in %s.\n", strings.Join(dependentRepos, ", ")) - wt, err := r.Worktree() - if err != nil { - glog.Fatalf("Failed to get working tree: %v", err) - } - fmt.Printf("Checking out branch tag commit %s.\n", bh.String()) - if err := wt.Checkout(&gogit.CheckoutOptions{Hash: bh}); err != nil { - glog.Fatalf("Failed to checkout %v: %v", bh, err) - } + wt := checkoutBranchTagCommit(r, bh, dependentRepos) // if go.mod exists, fix only go.mod and generate Godeps.json from it later // if it doesn't exist, check if Godeps.json exists, and update it - var changed, goModChanged bool - _, err = os.Stat("go.mod") - if os.IsNotExist(err) { + if !goModExists { if _, err2 := os.Stat("Godeps/Godeps.json"); err2 == nil { fmt.Printf("Updating Godeps.json to point to %s tag.\n", bName) changed, err = updateGodepsJsonWithTaggedDependencies(bName, dependentRepos) @@ -281,44 +319,44 @@ func main() { glog.Fatalf("Failed to update Godeps.json for tag %s: %v", bName, err) } } - } else if err == nil { - fmt.Printf("Updating go.mod and go.sum to point to %s tag.\n", bName) - changed, err = updateGomodWithTaggedDependencies(bName, dependentRepos) - if err != nil { - glog.Fatalf("Failed to update go.mod and go.sum for tag %s: %v", bName, err) - } - goModChanged = true - } - - if goModChanged && *generateGodeps { - fmt.Printf("Regenerating Godeps.json from go.mod.\n") - if err := regenerateGodepsFromGoMod(); err != nil { - glog.Fatalf("Failed to regenerate Godeps.json from go.mod: %v", err) + } else { + if publishSemverTag { + changed = updateGoModAndGodeps(semverTag, dependentRepos, *generateGodeps, true) + } else { + changed = updateGoModAndGodeps(bName, dependentRepos, *generateGodeps, false) } } if changed { - fmt.Printf("Adding extra commit fixing dependencies to point to %s tags.\n", bName) - publishingBotNow := publishingBot - publishingBotNow.When = time.Now() - bh, err = wt.Commit(fmt.Sprintf("Fix dependencies to point to %s tag", bName), &gogit.CommitOptions{ - All: true, - Author: &publishingBotNow, - Committer: &publishingBotNow, - }) - if err != nil { - glog.Fatalf("Failed to commit changes to fix dependencies to point to %s tag: %v", bName, err) + if publishSemverTag { + bh = createCommitToFixDeps(wt, semverTag) + } else { + bh = createCommitToFixDeps(wt, bName) } } } - // create prefixed annotated tag - fmt.Printf("Tagging %v as %q.\n", bh, bName) - err = createAnnotatedTag(bh, bName, tag.Tagger.When, dedent.Dedent(fmt.Sprintf(` + // create semver annotated tag + if publishSemverTag { + fmt.Printf("Tagging %v as %q.\n", bh, semverTag) + err = createAnnotatedTag(bh, semverTag, tag.Tagger.When, dedent.Dedent(fmt.Sprintf(` Kubernetes release %s Based on https://github.com/kubernetes/kubernetes/releases/tag/%s `, name, name))) + if err != nil { + glog.Fatalf("Failed to create tag %q: %v", semverTag, err) + } + createdTags = append(createdTags, semverTag) + } + + // create non-semver prefixed annotated tag + fmt.Printf("Tagging %v as %q.\n", bh, bName) + err = createAnnotatedTag(bh, bName, tag.Tagger.When, dedent.Dedent(fmt.Sprintf(` + Kubernetes release %s + + Based on https://github.com/kubernetes/kubernetes/releases/tag/%s + `, name, name))) if err != nil { glog.Fatalf("Failed to create tag %q: %v", bName, err) } @@ -326,13 +364,16 @@ func main() { } // write push command for new tags + // we use git push --atomic because it treats + // any existing releases which have only non-semver tags as no-ops + // and both semver and non-semver tags are targeted in a single operation if *pushScriptPath != "" && len(createdTags) > 0 { pushScript, err := os.OpenFile(*pushScriptPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755) if err != nil { glog.Fatalf("Failed to open push-script %q for appending: %v", *pushScriptPath, err) } defer pushScript.Close() - _, err = pushScript.WriteString(fmt.Sprintf("git push origin %s\n", "refs/tags/"+strings.Join(createdTags, " refs/tags/"))) + _, err = pushScript.WriteString(fmt.Sprintf("git push --atomic origin %s\n", "refs/tags/"+strings.Join(createdTags, " refs/tags/"))) if err != nil { glog.Fatalf("Failed to write to push-script %q: %q", *pushScriptPath, err) } @@ -463,7 +504,7 @@ func mappingOutputFileName(fnameTpl string, branch, tag string) string { func regenerateGodepsFromGoMod() error { goListCommand := exec.Command("go", "list", "-m", "-json", "all") - goListCommand.Env = append(os.Environ(), "GO111MODULE=on", "GOPOXY=file://${GOPATH}/pkg/mod/cache/download") + goListCommand.Env = append(os.Environ(), "GO111MODULE=on", fmt.Sprintf("GOPROXY=file://%s/pkg/mod/cache/download", os.Getenv("GOPATH"))) goMod, err := goListCommand.Output() if err != nil { return fmt.Errorf("Failed to get output of go list -m -json all: %v", err) @@ -488,3 +529,114 @@ func regenerateGodepsFromGoMod() error { } return nil } + +func checkoutBranchTagCommit(r *gogit.Repository, bh plumbing.Hash, dependentRepos []string) *gogit.Worktree { + fmt.Printf("Checking that dependencies point to the actual tags in %s.\n", strings.Join(dependentRepos, ", ")) + wt, err := r.Worktree() + if err != nil { + glog.Fatalf("Failed to get working tree: %v", err) + } + + fmt.Printf("Checking out branch tag commit %s.\n", bh.String()) + if err := wt.Checkout(&gogit.CheckoutOptions{Hash: bh}); err != nil { + glog.Fatalf("Failed to checkout %v: %v", bh, err) + } + return wt +} + +func updateGoModAndGodeps(tag string, dependentRepos []string, generateGodeps, publishSemverTags bool) bool { + fmt.Printf("Updating go.mod and go.sum to point to %s tag.\n", tag) + changed, err := updateGomodWithTaggedDependencies(tag, dependentRepos, publishSemverTags) + if err != nil { + glog.Fatalf("Failed to update go.mod and go.sum for tag %s: %v", tag, err) + } + + if changed && generateGodeps { + fmt.Printf("Regenerating Godeps.json from go.mod.\n") + if err := regenerateGodepsFromGoMod(); err != nil { + glog.Fatalf("Failed to regenerate Godeps.json from go.mod: %v", err) + } + } + return changed +} + +func createCommitToFixDeps(wt *gogit.Worktree, tag string) plumbing.Hash { + fmt.Printf("Adding extra commit to update dependencies to %s tag.\n", tag) + publishingBotNow := publishingBot + publishingBotNow.When = time.Now() + bh, err := wt.Commit(fmt.Sprintf("Update dependencies to %s tag", tag), &gogit.CommitOptions{ + All: true, + Author: &publishingBotNow, + Committer: &publishingBotNow, + }) + if err != nil { + glog.Fatalf("Failed to commit changes to update dependencies to %s tag: %v", tag, err) + } + return bh +} + +func deleteTag(tag string) error { + cmd := exec.Command("git", "tag", "-d", tag) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// cleanCacheForTag deletes the .mod, .info, .zip for the tag +// and removes the tag from the list in the go mod cache dir. +func cleanCacheForTag(tag string) error { + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to get current working directory: %v", err) + } + pkg, err := fullPackageName(dir) + if err != nil { + return fmt.Errorf("failed to get package at %s: %v", dir, err) + } + cacheDir := fmt.Sprintf("%s/pkg/mod/cache/download/%s/@v", os.Getenv("GOPATH"), pkg) + + goModFile := fmt.Sprintf("%s/%s.mod", cacheDir, tag) + if _, err := os.Stat(goModFile); err == nil { + if err2 := os.Remove(goModFile); err2 != nil { + return fmt.Errorf("error deleting file %s: %v", goModFile, err2) + } + } + + infoFile := fmt.Sprintf("%s/%s.info", cacheDir, tag) + if _, err := os.Stat(infoFile); err == nil { + if err2 := os.Remove(infoFile); err2 != nil { + return fmt.Errorf("error deleting file %s: %v", infoFile, err2) + } + } + + zipFile := fmt.Sprintf("%s/%s.zip", cacheDir, tag) + if _, err := os.Stat(zipFile); err == nil { + if err2 := os.Remove(zipFile); err2 != nil { + return fmt.Errorf("error deleting file %s: %v", zipFile, err2) + } + } + + listFile := fmt.Sprintf("%s/list", cacheDir) + if _, err := os.Stat(listFile); err == nil { + oldContent, err2 := ioutil.ReadFile(listFile) + if err2 != nil { + return fmt.Errorf("error reading file %s: %v", listFile, err2) + } + + lines := strings.Split(string(oldContent), "\n") + newContent := []string{} + for _, line := range lines { + if line != tag { + newContent = append(newContent, line) + } + } + output := strings.Join(newContent, "\n") + + if err := ioutil.WriteFile(listFile, []byte(output), 0644); err != nil { + return fmt.Errorf("error reading file %s: %v", listFile, err) + } + } + + fmt.Printf("Cleared go mod cache files for %s tag.\n", tag) + return nil +} diff --git a/vendor/github.com/blang/semver/.travis.yml b/vendor/github.com/blang/semver/.travis.yml new file mode 100644 index 00000000..7319bdea --- /dev/null +++ b/vendor/github.com/blang/semver/.travis.yml @@ -0,0 +1,25 @@ +language: go +matrix: + include: + - go: 1.4.x + - go: 1.5.x + - go: 1.6.x + - go: 1.7.x + - go: 1.8.x + - go: 1.9.x + - go: 1.10.x + - go: 1.11.x + - go: tip + allow_failures: + - go: tip +install: +- go get golang.org/x/tools/cmd/cover +- go get github.com/mattn/goveralls +script: +- echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci + -repotoken=$COVERALLS_TOKEN +- echo "Build examples" ; cd examples && go build +- echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .) +env: + global: + secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw= diff --git a/vendor/github.com/blang/semver/LICENSE b/vendor/github.com/blang/semver/LICENSE new file mode 100644 index 00000000..5ba5c86f --- /dev/null +++ b/vendor/github.com/blang/semver/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Benedikt Lang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/github.com/blang/semver/README.md b/vendor/github.com/blang/semver/README.md new file mode 100644 index 00000000..e05f9865 --- /dev/null +++ b/vendor/github.com/blang/semver/README.md @@ -0,0 +1,194 @@ +semver for golang [![Build Status](https://travis-ci.org/blang/semver.svg?branch=master)](https://travis-ci.org/blang/semver) [![GoDoc](https://godoc.org/github.com/blang/semver?status.svg)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/blang/semver)](https://goreportcard.com/report/github.com/blang/semver) +====== + +semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`. + +Usage +----- +```bash +$ go get github.com/blang/semver +``` +Note: Always vendor your dependencies or fix on a specific version tag. + +```go +import github.com/blang/semver +v1, err := semver.Make("1.0.0-beta") +v2, err := semver.Make("2.0.0-beta") +v1.Compare(v2) +``` + +Also check the [GoDocs](http://godoc.org/github.com/blang/semver). + +Why should I use this lib? +----- + +- Fully spec compatible +- No reflection +- No regex +- Fully tested (Coverage >99%) +- Readable parsing/validation errors +- Fast (See [Benchmarks](#benchmarks)) +- Only Stdlib +- Uses values instead of pointers +- Many features, see below + + +Features +----- + +- Parsing and validation at all levels +- Comparator-like comparisons +- Compare Helper Methods +- InPlace manipulation +- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1` +- Wildcards `>=1.x`, `<=2.5.x` +- Sortable (implements sort.Interface) +- database/sql compatible (sql.Scanner/Valuer) +- encoding/json compatible (json.Marshaler/Unmarshaler) + +Ranges +------ + +A `Range` is a set of conditions which specify which versions satisfy the range. + +A condition is composed of an operator and a version. The supported operators are: + +- `<1.0.0` Less than `1.0.0` +- `<=1.0.0` Less than or equal to `1.0.0` +- `>1.0.0` Greater than `1.0.0` +- `>=1.0.0` Greater than or equal to `1.0.0` +- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0` +- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`. + +Note that spaces between the operator and the version will be gracefully tolerated. + +A `Range` can link multiple `Ranges` separated by space: + +Ranges can be linked by logical AND: + + - `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0` + - `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2` + +Ranges can also be linked by logical OR: + + - `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x` + +AND has a higher precedence than OR. It's not possible to use brackets. + +Ranges can be combined by both AND and OR + + - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` + +Range usage: + +``` +v, err := semver.Parse("1.2.3") +expectedRange, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0") +if expectedRange(v) { + //valid +} + +``` + +Example +----- + +Have a look at full examples in [examples/main.go](examples/main.go) + +```go +import github.com/blang/semver + +v, err := semver.Make("0.0.1-alpha.preview+123.github") +fmt.Printf("Major: %d\n", v.Major) +fmt.Printf("Minor: %d\n", v.Minor) +fmt.Printf("Patch: %d\n", v.Patch) +fmt.Printf("Pre: %s\n", v.Pre) +fmt.Printf("Build: %s\n", v.Build) + +// Prerelease versions array +if len(v.Pre) > 0 { + fmt.Println("Prerelease versions:") + for i, pre := range v.Pre { + fmt.Printf("%d: %q\n", i, pre) + } +} + +// Build meta data array +if len(v.Build) > 0 { + fmt.Println("Build meta data:") + for i, build := range v.Build { + fmt.Printf("%d: %q\n", i, build) + } +} + +v001, err := semver.Make("0.0.1") +// Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE +v001.GT(v) == true +v.LT(v001) == true +v.GTE(v) == true +v.LTE(v) == true + +// Or use v.Compare(v2) for comparisons (-1, 0, 1): +v001.Compare(v) == 1 +v.Compare(v001) == -1 +v.Compare(v) == 0 + +// Manipulate Version in place: +v.Pre[0], err = semver.NewPRVersion("beta") +if err != nil { + fmt.Printf("Error parsing pre release version: %q", err) +} + +fmt.Println("\nValidate versions:") +v.Build[0] = "?" + +err = v.Validate() +if err != nil { + fmt.Printf("Validation failed: %s\n", err) +} +``` + + +Benchmarks +----- + + BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op + BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op + BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op + BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op + BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op + BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op + BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op + BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op + BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op + BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op + BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op + BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op + BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op + BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op + BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op + BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op + BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op + BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op + BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op + BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op + +See benchmark cases at [semver_test.go](semver_test.go) + + +Motivation +----- + +I simply couldn't find any lib supporting the full spec. Others were just wrong or used reflection and regex which i don't like. + + +Contribution +----- + +Feel free to make a pull request. For bigger changes create a issue first to discuss about it. + + +License +----- + +See [LICENSE](LICENSE) file. diff --git a/vendor/github.com/blang/semver/go.mod b/vendor/github.com/blang/semver/go.mod new file mode 100644 index 00000000..d0083058 --- /dev/null +++ b/vendor/github.com/blang/semver/go.mod @@ -0,0 +1 @@ +module github.com/blang/semver diff --git a/vendor/github.com/blang/semver/json.go b/vendor/github.com/blang/semver/json.go new file mode 100644 index 00000000..a74bf7c4 --- /dev/null +++ b/vendor/github.com/blang/semver/json.go @@ -0,0 +1,23 @@ +package semver + +import ( + "encoding/json" +) + +// MarshalJSON implements the encoding/json.Marshaler interface. +func (v Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. +func (v *Version) UnmarshalJSON(data []byte) (err error) { + var versionString string + + if err = json.Unmarshal(data, &versionString); err != nil { + return + } + + *v, err = Parse(versionString) + + return +} diff --git a/vendor/github.com/blang/semver/package.json b/vendor/github.com/blang/semver/package.json new file mode 100644 index 00000000..1cf8ebdd --- /dev/null +++ b/vendor/github.com/blang/semver/package.json @@ -0,0 +1,17 @@ +{ + "author": "blang", + "bugs": { + "URL": "https://github.com/blang/semver/issues", + "url": "https://github.com/blang/semver/issues" + }, + "gx": { + "dvcsimport": "github.com/blang/semver" + }, + "gxVersion": "0.10.0", + "language": "go", + "license": "MIT", + "name": "semver", + "releaseCmd": "git commit -a -m \"gx publish $VERSION\"", + "version": "3.5.1" +} + diff --git a/vendor/github.com/blang/semver/range.go b/vendor/github.com/blang/semver/range.go new file mode 100644 index 00000000..95f7139b --- /dev/null +++ b/vendor/github.com/blang/semver/range.go @@ -0,0 +1,416 @@ +package semver + +import ( + "fmt" + "strconv" + "strings" + "unicode" +) + +type wildcardType int + +const ( + noneWildcard wildcardType = iota + majorWildcard wildcardType = 1 + minorWildcard wildcardType = 2 + patchWildcard wildcardType = 3 +) + +func wildcardTypefromInt(i int) wildcardType { + switch i { + case 1: + return majorWildcard + case 2: + return minorWildcard + case 3: + return patchWildcard + default: + return noneWildcard + } +} + +type comparator func(Version, Version) bool + +var ( + compEQ comparator = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 0 + } + compNE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) != 0 + } + compGT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 1 + } + compGE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) >= 0 + } + compLT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == -1 + } + compLE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) <= 0 + } +) + +type versionRange struct { + v Version + c comparator +} + +// rangeFunc creates a Range from the given versionRange. +func (vr *versionRange) rangeFunc() Range { + return Range(func(v Version) bool { + return vr.c(v, vr.v) + }) +} + +// Range represents a range of versions. +// A Range can be used to check if a Version satisfies it: +// +// range, err := semver.ParseRange(">1.0.0 <2.0.0") +// range(semver.MustParse("1.1.1") // returns true +type Range func(Version) bool + +// OR combines the existing Range with another Range using logical OR. +func (rf Range) OR(f Range) Range { + return Range(func(v Version) bool { + return rf(v) || f(v) + }) +} + +// AND combines the existing Range with another Range using logical AND. +func (rf Range) AND(f Range) Range { + return Range(func(v Version) bool { + return rf(v) && f(v) + }) +} + +// ParseRange parses a range and returns a Range. +// If the range could not be parsed an error is returned. +// +// Valid ranges are: +// - "<1.0.0" +// - "<=1.0.0" +// - ">1.0.0" +// - ">=1.0.0" +// - "1.0.0", "=1.0.0", "==1.0.0" +// - "!1.0.0", "!=1.0.0" +// +// A Range can consist of multiple ranges separated by space: +// Ranges can be linked by logical AND: +// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0" +// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2 +// +// Ranges can also be linked by logical OR: +// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" +// +// AND has a higher precedence than OR. It's not possible to use brackets. +// +// Ranges can be combined by both AND and OR +// +// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` +func ParseRange(s string) (Range, error) { + parts := splitAndTrim(s) + orParts, err := splitORParts(parts) + if err != nil { + return nil, err + } + expandedParts, err := expandWildcardVersion(orParts) + if err != nil { + return nil, err + } + var orFn Range + for _, p := range expandedParts { + var andFn Range + for _, ap := range p { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + vr, err := buildVersionRange(opStr, vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) + } + rf := vr.rangeFunc() + + // Set function + if andFn == nil { + andFn = rf + } else { // Combine with existing function + andFn = andFn.AND(rf) + } + } + if orFn == nil { + orFn = andFn + } else { + orFn = orFn.OR(andFn) + } + + } + return orFn, nil +} + +// splitORParts splits the already cleaned parts by '||'. +// Checks for invalid positions of the operator and returns an +// error if found. +func splitORParts(parts []string) ([][]string, error) { + var ORparts [][]string + last := 0 + for i, p := range parts { + if p == "||" { + if i == 0 { + return nil, fmt.Errorf("First element in range is '||'") + } + ORparts = append(ORparts, parts[last:i]) + last = i + 1 + } + } + if last == len(parts) { + return nil, fmt.Errorf("Last element in range is '||'") + } + ORparts = append(ORparts, parts[last:]) + return ORparts, nil +} + +// buildVersionRange takes a slice of 2: operator and version +// and builds a versionRange, otherwise an error. +func buildVersionRange(opStr, vStr string) (*versionRange, error) { + c := parseComparator(opStr) + if c == nil { + return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) + } + v, err := Parse(vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) + } + + return &versionRange{ + v: v, + c: c, + }, nil + +} + +// inArray checks if a byte is contained in an array of bytes +func inArray(s byte, list []byte) bool { + for _, el := range list { + if el == s { + return true + } + } + return false +} + +// splitAndTrim splits a range string by spaces and cleans whitespaces +func splitAndTrim(s string) (result []string) { + last := 0 + var lastChar byte + excludeFromSplit := []byte{'>', '<', '='} + for i := 0; i < len(s); i++ { + if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { + if last < i-1 { + result = append(result, s[last:i]) + } + last = i + 1 + } else if s[i] != ' ' { + lastChar = s[i] + } + } + if last < len(s)-1 { + result = append(result, s[last:]) + } + + for i, v := range result { + result[i] = strings.Replace(v, " ", "", -1) + } + + // parts := strings.Split(s, " ") + // for _, x := range parts { + // if s := strings.TrimSpace(x); len(s) != 0 { + // result = append(result, s) + // } + // } + return +} + +// splitComparatorVersion splits the comparator from the version. +// Input must be free of leading or trailing spaces. +func splitComparatorVersion(s string) (string, string, error) { + i := strings.IndexFunc(s, unicode.IsDigit) + if i == -1 { + return "", "", fmt.Errorf("Could not get version from string: %q", s) + } + return strings.TrimSpace(s[0:i]), s[i:], nil +} + +// getWildcardType will return the type of wildcard that the +// passed version contains +func getWildcardType(vStr string) wildcardType { + parts := strings.Split(vStr, ".") + nparts := len(parts) + wildcard := parts[nparts-1] + + possibleWildcardType := wildcardTypefromInt(nparts) + if wildcard == "x" { + return possibleWildcardType + } + + return noneWildcard +} + +// createVersionFromWildcard will convert a wildcard version +// into a regular version, replacing 'x's with '0's, handling +// special cases like '1.x.x' and '1.x' +func createVersionFromWildcard(vStr string) string { + // handle 1.x.x + vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) + vStr2 = strings.Replace(vStr2, ".x", ".0", 1) + parts := strings.Split(vStr2, ".") + + // handle 1.x + if len(parts) == 2 { + return vStr2 + ".0" + } + + return vStr2 +} + +// incrementMajorVersion will increment the major version +// of the passed version +func incrementMajorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[0]) + if err != nil { + return "", err + } + parts[0] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// incrementMajorVersion will increment the minor version +// of the passed version +func incrementMinorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[1]) + if err != nil { + return "", err + } + parts[1] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// expandWildcardVersion will expand wildcards inside versions +// following these rules: +// +// * when dealing with patch wildcards: +// >= 1.2.x will become >= 1.2.0 +// <= 1.2.x will become < 1.3.0 +// > 1.2.x will become >= 1.3.0 +// < 1.2.x will become < 1.2.0 +// != 1.2.x will become < 1.2.0 >= 1.3.0 +// +// * when dealing with minor wildcards: +// >= 1.x will become >= 1.0.0 +// <= 1.x will become < 2.0.0 +// > 1.x will become >= 2.0.0 +// < 1.0 will become < 1.0.0 +// != 1.x will become < 1.0.0 >= 2.0.0 +// +// * when dealing with wildcards without +// version operator: +// 1.2.x will become >= 1.2.0 < 1.3.0 +// 1.x will become >= 1.0.0 < 2.0.0 +func expandWildcardVersion(parts [][]string) ([][]string, error) { + var expandedParts [][]string + for _, p := range parts { + var newParts []string + for _, ap := range p { + if strings.Contains(ap, "x") { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + + versionWildcardType := getWildcardType(vStr) + flatVersion := createVersionFromWildcard(vStr) + + var resultOperator string + var shouldIncrementVersion bool + switch opStr { + case ">": + resultOperator = ">=" + shouldIncrementVersion = true + case ">=": + resultOperator = ">=" + case "<": + resultOperator = "<" + case "<=": + resultOperator = "<" + shouldIncrementVersion = true + case "", "=", "==": + newParts = append(newParts, ">="+flatVersion) + resultOperator = "<" + shouldIncrementVersion = true + case "!=", "!": + newParts = append(newParts, "<"+flatVersion) + resultOperator = ">=" + shouldIncrementVersion = true + } + + var resultVersion string + if shouldIncrementVersion { + switch versionWildcardType { + case patchWildcard: + resultVersion, _ = incrementMinorVersion(flatVersion) + case minorWildcard: + resultVersion, _ = incrementMajorVersion(flatVersion) + } + } else { + resultVersion = flatVersion + } + + ap = resultOperator + resultVersion + } + newParts = append(newParts, ap) + } + expandedParts = append(expandedParts, newParts) + } + + return expandedParts, nil +} + +func parseComparator(s string) comparator { + switch s { + case "==": + fallthrough + case "": + fallthrough + case "=": + return compEQ + case ">": + return compGT + case ">=": + return compGE + case "<": + return compLT + case "<=": + return compLE + case "!": + fallthrough + case "!=": + return compNE + } + + return nil +} + +// MustParseRange is like ParseRange but panics if the range cannot be parsed. +func MustParseRange(s string) Range { + r, err := ParseRange(s) + if err != nil { + panic(`semver: ParseRange(` + s + `): ` + err.Error()) + } + return r +} diff --git a/vendor/github.com/blang/semver/semver.go b/vendor/github.com/blang/semver/semver.go new file mode 100644 index 00000000..4165bc79 --- /dev/null +++ b/vendor/github.com/blang/semver/semver.go @@ -0,0 +1,455 @@ +package semver + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +const ( + numbers string = "0123456789" + alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + alphanum = alphas + numbers +) + +// SpecVersion is the latest fully supported spec version of semver +var SpecVersion = Version{ + Major: 2, + Minor: 0, + Patch: 0, +} + +// Version represents a semver compatible version +type Version struct { + Major uint64 + Minor uint64 + Patch uint64 + Pre []PRVersion + Build []string //No Precedence +} + +// Version to string +func (v Version) String() string { + b := make([]byte, 0, 5) + b = strconv.AppendUint(b, v.Major, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Minor, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Patch, 10) + + if len(v.Pre) > 0 { + b = append(b, '-') + b = append(b, v.Pre[0].String()...) + + for _, pre := range v.Pre[1:] { + b = append(b, '.') + b = append(b, pre.String()...) + } + } + + if len(v.Build) > 0 { + b = append(b, '+') + b = append(b, v.Build[0]...) + + for _, build := range v.Build[1:] { + b = append(b, '.') + b = append(b, build...) + } + } + + return string(b) +} + +// Equals checks if v is equal to o. +func (v Version) Equals(o Version) bool { + return (v.Compare(o) == 0) +} + +// EQ checks if v is equal to o. +func (v Version) EQ(o Version) bool { + return (v.Compare(o) == 0) +} + +// NE checks if v is not equal to o. +func (v Version) NE(o Version) bool { + return (v.Compare(o) != 0) +} + +// GT checks if v is greater than o. +func (v Version) GT(o Version) bool { + return (v.Compare(o) == 1) +} + +// GTE checks if v is greater than or equal to o. +func (v Version) GTE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// GE checks if v is greater than or equal to o. +func (v Version) GE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// LT checks if v is less than o. +func (v Version) LT(o Version) bool { + return (v.Compare(o) == -1) +} + +// LTE checks if v is less than or equal to o. +func (v Version) LTE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// LE checks if v is less than or equal to o. +func (v Version) LE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// Compare compares Versions v to o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v Version) Compare(o Version) int { + if v.Major != o.Major { + if v.Major > o.Major { + return 1 + } + return -1 + } + if v.Minor != o.Minor { + if v.Minor > o.Minor { + return 1 + } + return -1 + } + if v.Patch != o.Patch { + if v.Patch > o.Patch { + return 1 + } + return -1 + } + + // Quick comparison if a version has no prerelease versions + if len(v.Pre) == 0 && len(o.Pre) == 0 { + return 0 + } else if len(v.Pre) == 0 && len(o.Pre) > 0 { + return 1 + } else if len(v.Pre) > 0 && len(o.Pre) == 0 { + return -1 + } + + i := 0 + for ; i < len(v.Pre) && i < len(o.Pre); i++ { + if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { + continue + } else if comp == 1 { + return 1 + } else { + return -1 + } + } + + // If all pr versions are the equal but one has further prversion, this one greater + if i == len(v.Pre) && i == len(o.Pre) { + return 0 + } else if i == len(v.Pre) && i < len(o.Pre) { + return -1 + } else { + return 1 + } + +} + +// IncrementPatch increments the patch version +func (v *Version) IncrementPatch() error { + if v.Major == 0 { + return fmt.Errorf("Patch version can not be incremented for %q", v.String()) + } + v.Patch += 1 + return nil +} + +// IncrementMinor increments the minor version +func (v *Version) IncrementMinor() error { + if v.Major == 0 { + return fmt.Errorf("Minor version can not be incremented for %q", v.String()) + } + v.Minor += 1 + v.Patch = 0 + return nil +} + +// IncrementMajor increments the major version +func (v *Version) IncrementMajor() error { + if v.Major == 0 { + return fmt.Errorf("Major version can not be incremented for %q", v.String()) + } + v.Major += 1 + v.Minor = 0 + v.Patch = 0 + return nil +} + +// Validate validates v and returns error in case +func (v Version) Validate() error { + // Major, Minor, Patch already validated using uint64 + + for _, pre := range v.Pre { + if !pre.IsNum { //Numeric prerelease versions already uint64 + if len(pre.VersionStr) == 0 { + return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) + } + if !containsOnly(pre.VersionStr, alphanum) { + return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) + } + } + } + + for _, build := range v.Build { + if len(build) == 0 { + return fmt.Errorf("Build meta data can not be empty %q", build) + } + if !containsOnly(build, alphanum) { + return fmt.Errorf("Invalid character(s) found in build meta data %q", build) + } + } + + return nil +} + +// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error +func New(s string) (vp *Version, err error) { + v, err := Parse(s) + vp = &v + return +} + +// Make is an alias for Parse, parses version string and returns a validated Version or error +func Make(s string) (Version, error) { + return Parse(s) +} + +// ParseTolerant allows for certain version specifications that do not strictly adhere to semver +// specs to be parsed by this library. It does so by normalizing versions before passing them to +// Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions +// with only major and minor components specified, and removes leading 0s. +func ParseTolerant(s string) (Version, error) { + s = strings.TrimSpace(s) + s = strings.TrimPrefix(s, "v") + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + // Remove leading zeros. + for i, p := range parts { + if len(p) > 1 { + parts[i] = strings.TrimPrefix(p, "0") + } + } + // Fill up shortened versions. + if len(parts) < 3 { + if strings.ContainsAny(parts[len(parts)-1], "+-") { + return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") + } + for len(parts) < 3 { + parts = append(parts, "0") + } + } + s = strings.Join(parts, ".") + + return Parse(s) +} + +// Parse parses version string and returns a validated Version or error +func Parse(s string) (Version, error) { + if len(s) == 0 { + return Version{}, errors.New("Version string empty") + } + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + if len(parts) != 3 { + return Version{}, errors.New("No Major.Minor.Patch elements found") + } + + // Major + if !containsOnly(parts[0], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) + } + if hasLeadingZeroes(parts[0]) { + return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) + } + major, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return Version{}, err + } + + // Minor + if !containsOnly(parts[1], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) + } + if hasLeadingZeroes(parts[1]) { + return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) + } + minor, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return Version{}, err + } + + v := Version{} + v.Major = major + v.Minor = minor + + var build, prerelease []string + patchStr := parts[2] + + if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { + build = strings.Split(patchStr[buildIndex+1:], ".") + patchStr = patchStr[:buildIndex] + } + + if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { + prerelease = strings.Split(patchStr[preIndex+1:], ".") + patchStr = patchStr[:preIndex] + } + + if !containsOnly(patchStr, numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) + } + if hasLeadingZeroes(patchStr) { + return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) + } + patch, err := strconv.ParseUint(patchStr, 10, 64) + if err != nil { + return Version{}, err + } + + v.Patch = patch + + // Prerelease + for _, prstr := range prerelease { + parsedPR, err := NewPRVersion(prstr) + if err != nil { + return Version{}, err + } + v.Pre = append(v.Pre, parsedPR) + } + + // Build meta data + for _, str := range build { + if len(str) == 0 { + return Version{}, errors.New("Build meta data is empty") + } + if !containsOnly(str, alphanum) { + return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) + } + v.Build = append(v.Build, str) + } + + return v, nil +} + +// MustParse is like Parse but panics if the version cannot be parsed. +func MustParse(s string) Version { + v, err := Parse(s) + if err != nil { + panic(`semver: Parse(` + s + `): ` + err.Error()) + } + return v +} + +// PRVersion represents a PreRelease Version +type PRVersion struct { + VersionStr string + VersionNum uint64 + IsNum bool +} + +// NewPRVersion creates a new valid prerelease version +func NewPRVersion(s string) (PRVersion, error) { + if len(s) == 0 { + return PRVersion{}, errors.New("Prerelease is empty") + } + v := PRVersion{} + if containsOnly(s, numbers) { + if hasLeadingZeroes(s) { + return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) + } + num, err := strconv.ParseUint(s, 10, 64) + + // Might never be hit, but just in case + if err != nil { + return PRVersion{}, err + } + v.VersionNum = num + v.IsNum = true + } else if containsOnly(s, alphanum) { + v.VersionStr = s + v.IsNum = false + } else { + return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) + } + return v, nil +} + +// IsNumeric checks if prerelease-version is numeric +func (v PRVersion) IsNumeric() bool { + return v.IsNum +} + +// Compare compares two PreRelease Versions v and o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v PRVersion) Compare(o PRVersion) int { + if v.IsNum && !o.IsNum { + return -1 + } else if !v.IsNum && o.IsNum { + return 1 + } else if v.IsNum && o.IsNum { + if v.VersionNum == o.VersionNum { + return 0 + } else if v.VersionNum > o.VersionNum { + return 1 + } else { + return -1 + } + } else { // both are Alphas + if v.VersionStr == o.VersionStr { + return 0 + } else if v.VersionStr > o.VersionStr { + return 1 + } else { + return -1 + } + } +} + +// PreRelease version to string +func (v PRVersion) String() string { + if v.IsNum { + return strconv.FormatUint(v.VersionNum, 10) + } + return v.VersionStr +} + +func containsOnly(s string, set string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !strings.ContainsRune(set, r) + }) == -1 +} + +func hasLeadingZeroes(s string) bool { + return len(s) > 1 && s[0] == '0' +} + +// NewBuildVersion creates a new valid build version +func NewBuildVersion(s string) (string, error) { + if len(s) == 0 { + return "", errors.New("Buildversion is empty") + } + if !containsOnly(s, alphanum) { + return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) + } + return s, nil +} diff --git a/vendor/github.com/blang/semver/sort.go b/vendor/github.com/blang/semver/sort.go new file mode 100644 index 00000000..e18f8808 --- /dev/null +++ b/vendor/github.com/blang/semver/sort.go @@ -0,0 +1,28 @@ +package semver + +import ( + "sort" +) + +// Versions represents multiple versions. +type Versions []Version + +// Len returns length of version collection +func (s Versions) Len() int { + return len(s) +} + +// Swap swaps two versions inside the collection by its indices +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Less checks if version at index i is less than version at index j +func (s Versions) Less(i, j int) bool { + return s[i].LT(s[j]) +} + +// Sort sorts a slice of versions +func Sort(versions []Version) { + sort.Sort(Versions(versions)) +} diff --git a/vendor/github.com/blang/semver/sql.go b/vendor/github.com/blang/semver/sql.go new file mode 100644 index 00000000..db958134 --- /dev/null +++ b/vendor/github.com/blang/semver/sql.go @@ -0,0 +1,30 @@ +package semver + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements the database/sql.Scanner interface. +func (v *Version) Scan(src interface{}) (err error) { + var str string + switch src := src.(type) { + case string: + str = src + case []byte: + str = string(src) + default: + return fmt.Errorf("version.Scan: cannot convert %T to string", src) + } + + if t, err := Parse(str); err == nil { + *v = t + } + + return +} + +// Value implements the database/sql/driver.Valuer interface. +func (v Version) Value() (driver.Value, error) { + return v.String(), nil +}