From f991deac99657ab7e83c27c2f427f03aaa57992e Mon Sep 17 00:00:00 2001 From: Nicolas Mailhot Date: Jun 07 2020 12:19:55 +0000 Subject: add support for epochs and releases --- diff --git a/cmd/modist/main.go b/cmd/modist/main.go index bff4467..7cc432c 100644 --- a/cmd/modist/main.go +++ b/cmd/modist/main.go @@ -48,34 +48,47 @@ func main() { modVerSetByUser := false modVerProto := cli.StringOpt{ Name: "v version", - Desc: "apply the module semantic VERSION core", + Desc: "use VERSION as semantic version core string", SetByUser: &modVerSetByUser, } modPreSetByUser := false modPreProto := cli.StringOpt{ - Name: "P preversion-suffix", - Value: "", - Desc: `apply the module PREVERSION suffix - PREVERSION should not include VERSION in any form`, + Name: "P pre-release", + Desc: `use PRE as semantic version pre-release string + (PRE should not include VERSION in any form)`, SetByUser: &modPreSetByUser, } modCommitSetByUser := false modCommitProto := cli.StringOpt{ Name: "C commit", - Value: "", - Desc: "apply the module COMMIT (usually, a hash).", + Desc: "use COMMIT identifier (usually, a hash).", SetByUser: &modCommitSetByUser, } modTimeSetByUser := false modTimeProto := cli.StringOpt{ - Name: "H time", - Desc: `apply the module timestamp, in RFC 3339 format + Name: "H time", + Desc: `use source TIMESTAMP, in RFC 3339 format (as produced by “date -u --iso-8601=seconds”)`, - SetByUser: &modTimeSetByUser, - } + SetByUser: &modTimeSetByUser, + } + + modBuildSetByUser := false + modBuildProto := cli.StringOpt{ + Name: "B build", + Desc: `use BUILD as semantic version build string + (release in RPM conventions)`, + SetByUser: &modBuildSetByUser, + } + + modEpochSetByUser := false + modEpochProto := cli.IntOpt{ + Name: "Z epoch", + Desc: `use versioning EPOCH`, + SetByUser: &modEpochSetByUser, + } includeModsSetByUser := false includeModsProto := cli.StringsOpt{ @@ -230,18 +243,18 @@ Filtering options apply to relative file paths within a module tree.` ⚠ Go module versioning Go module versioning has no concept of post-version identifiers: - – PREVERSION will identify a state anterior to VERSION, + – PRE will identify a state anterior to VERSION, — COMMIT alone will identify a state anterior to any pre-version of VERSION, - — COMMIT and PREVERSION will identify a state, anterior to VERSION, preceded - by a series of pre-versions, ending in PREVERSION. + — COMMIT and PRE will identify a state, anterior to VERSION, preceded by a + series of pre-versions, ending in PRE. -When processing COMMITs and PREVERSIONs, take care to increment VERSION so it +When processing COMMITs and PREs, take care to increment VERSION so it identifies the first version that will be released after those states. ⚠ RPM versioning Since RPM versions can end up in filenames on case-insensitive filesystems or -file servers, modist will refuse to honor PREVERSION ordering that relies on +file servers, modist will refuse to honor PRE ordering that relies on casing.` app.Command("provides", "Print Go module provides", func(cmd *cli.Cmd) { @@ -262,9 +275,11 @@ casing.` SetByUser: &onlyVersionSetByUser, } - cmd.Spec = "[-v|--version=] " + - "[-P|--preversion-suffix=] " + - "[[-C|--commit=] (-H|--time=)] " + + cmd.Spec = "[-v|--version=] " + + "[-P|--pre-release=
] " +
+               "[[-C|--commit=] (-H|--time=)] " +
+                "[-B|--build=] " +
+                "[-Z|--epoch=] " +
                "[[-M|--include-module=] | " +
                 "[-m|--ignore-module=]]... " +
                 "[-n|--only-name|--only-version] " +
@@ -277,6 +292,8 @@ casing.`
     modPre       := cmd.String(modPreProto)
     modCommit    := cmd.String(modCommitProto)
     modTime      := cmd.String(modTimeProto)
+    modBuild     := cmd.String(modBuildProto)
+    modEpoch     := cmd.Int(modEpochProto)
     includeMods  := cmd.Strings(includeModsProto)
     ignoreMods   := cmd.Strings(ignoreModsProto)
     onlyName     := cmd.Bool(onlyNameProto)
@@ -290,7 +307,8 @@ casing.`
       var err error
       var ver *version.Version
       if modVerSetByUser || modPreSetByUser || modCommitSetByUser {
-        ver, err = version.New(*modVer, *modPre, *modCommit, *modTime, *quiet)
+        ver, err = version.New(*modVer, *modPre, *modCommit, *modTime,
+                               *modBuild, *modEpoch, *quiet)
         check(err)
       }
       _, _, err  = list.Provides(*sources, ver, *includeMods, *ignoreMods,
@@ -325,9 +343,11 @@ casing.`
 
   app.Command("buildrequires", "Print Go module build requires", func(cmd *cli.Cmd) {
 
-    cmd.Spec =  "[-v|--version=] " +
-                "[-P|--preversion-suffix=] " +
-               "[[-C|--commit=] (-H|--time=)] " +
+    cmd.Spec =  "[-v|--version=] " +
+                "[-P|--pre-release=
] " +
+               "[[-C|--commit=] (-H|--time=)] " +
+                "[-B|--build=] " +
+                "[-Z|--epoch=] " +
                "[[-M|--include-module=] | " +
                 "[-m|--ignore-module=]]... " +
                 "[-o|--output=] [-O|--format=] " +
@@ -355,6 +375,8 @@ modules in a single pass is not supported.`
     modPre       := cmd.String(modPreProto)
     modCommit    := cmd.String(modCommitProto)
     modTime      := cmd.String(modTimeProto)
+    modBuild     := cmd.String(modBuildProto)
+    modEpoch     := cmd.Int(modEpochProto)
     includeMods  := cmd.Strings(includeModsProto)
     ignoreMods   := cmd.Strings(ignoreModsProto)
     output       := cmd.String(outputProto)
@@ -366,7 +388,8 @@ modules in a single pass is not supported.`
       var err error
       var ver *version.Version
       if modVerSetByUser || modPreSetByUser || modCommitSetByUser {
-        ver, err = version.New(*modVer, *modPre, *modCommit, *modTime, *quiet)
+        ver, err = version.New(*modVer, *modPre, *modCommit, *modTime,
+                               *modBuild, *modEpoch, *quiet)
         check(err)
       }
       _, _, err  = list.BuildRequires(*sources, ver, *includeMods, *ignoreMods,
@@ -393,9 +416,11 @@ modules in a single pass is not supported.`
       SetByUser: &onlyVersionSetByUser,
     }
 
-    cmd.Spec =  "[-v|--version=] " +
-                "[-P|--preversion-suffix=] " +
-               "[[-C|--commit=] (-H|--time=)] " +
+    cmd.Spec =  "[-v|--version=] " +
+                "[-P|--pre-release=
] " +
+               "[[-C|--commit=] (-H|--time=)] " +
+                "[-B|--build=] " +
+                "[-Z|--epoch=] " +
                "[[-M|--include-module=] | " +
                 "[-m|--ignore-module=]]... " +
                "[[-F|--include-file=] | " +
@@ -418,6 +443,8 @@ modules in a single pass is not supported.`
     modPre             := cmd.String(modPreProto)
     modCommit          := cmd.String(modCommitProto)
     modTime            := cmd.String(modTimeProto)
+    modBuild           := cmd.String(modBuildProto)
+    modEpoch           := cmd.Int(modEpochProto)
     includeMods        := cmd.Strings(includeModsProto)
     ignoreMods         := cmd.Strings(ignoreModsProto)
     includeFiles       := cmd.Strings(includeFilesProto)
@@ -438,7 +465,8 @@ modules in a single pass is not supported.`
     sources            := cmd.Strings(sourcesProto)
 
     cmd.Action = func() {
-      ver, err := version.New(*modVer, *modPre, *modCommit, *modTime, *quiet)
+      ver, err := version.New(*modVer, *modPre, *modCommit, *modTime,
+                              *modBuild, *modEpoch, *quiet)
       check(err)
       filters      := filter.Default
       filters, err  = filters.AddByType(*includeFiles, *includeExtensions,
@@ -480,25 +508,30 @@ modules in a single pass is not supported.`
 
   app.Command("version", "Compute and convert version strings", func(cmd *cli.Cmd) {
 
-    cmd.Spec = "[-v|--version=] " +
-               "[-P|--preversion-suffix=] " +
-               "[[-C|--commit=] (-H|--time=)] " +
-               "[-O|--format=]" +
-               "[-q|--quiet]"
+    cmd.Spec =  "[-v|--version=] " +
+                "[-P|--pre-release=
] " +
+               "[[-C|--commit=] (-H|--time=)] " +
+                "[-B|--build=] " +
+                "[-Z|--epoch=] " +
+                "[-O|--format=]" +
+                "[-q|--quiet]"
 
     cmd.LongDesc = `
 Compute version strings from individual components, or convert between version
-formats.`
+formats.` + versionHelp
 
     modVer       := cmd.String(modVerProto)
     modPre       := cmd.String(modPreProto)
     modCommit    := cmd.String(modCommitProto)
     modTime      := cmd.String(modTimeProto)
+    modBuild     := cmd.String(modBuildProto)
+    modEpoch     := cmd.Int(modEpochProto)
     outputFormat := cmd.String(outputFormatProto)
     quiet        := cmd.Bool(quietProto)
 
     cmd.Action = func() {
-      ver, err := version.New(*modVer, *modPre, *modCommit, *modTime, *quiet)
+      ver, err := version.New(*modVer, *modPre, *modCommit, *modTime,
+                              *modBuild, *modEpoch, *quiet)
       check(err)
       result, err := ver.Format(*outputFormat)
       check(err)
@@ -625,11 +658,13 @@ formats.`
       SetByUser: &outputFormatSetByUser,
     }
 
-    cmd.LongDesc = "Install the unpacked Go modules located in SOURCE… into DESTDIR." + filterHelp
+    cmd.LongDesc = "Install the unpacked Go modules located in SOURCE… into DESTDIR." + filterHelp + versionHelp
 
-    cmd.Spec =  "[-v|--version=] " +
-                "[-P|--preversion-suffix=] " +
-                "[[-C|--commit=] (-H|--time=)] " +
+    cmd.Spec =  "[-v|--version=] " +
+                "[-P|--pre-release=
] " +
+                "[[-C|--commit=] (-H|--time=)] " +
+                "[-B|--build=] " +
+                "[-Z|--epoch=] " +
                 "[[-M|--include-module=] | " +
                 "[-m|--ignore-module=]]... " +
                "[[-F|--include-file=] | " +
@@ -651,6 +686,8 @@ formats.`
     modPre             := cmd.String(modPreProto)
     modCommit          := cmd.String(modCommitProto)
     modTime            := cmd.String(modTimeProto)
+    modBuild           := cmd.String(modBuildProto)
+    modEpoch           := cmd.Int(modEpochProto)
     includeMods        := cmd.Strings(includeModsProto)
     ignoreMods         := cmd.Strings(ignoreModsProto)
     includeFiles       := cmd.Strings(includeFilesProto)
@@ -673,7 +710,8 @@ formats.`
 
 
     cmd.Action = func() {
-      ver, err := version.New(*modVer, *modPre, *modCommit, *modTime, *quiet)
+      ver, err := version.New(*modVer, *modPre, *modCommit, *modTime,
+                              *modBuild, *modEpoch, *quiet)
       check(err)
       filters      := filter.Default
       filters, err  = filters.AddByType(*includeFiles, *includeExtensions,
@@ -702,10 +740,12 @@ formats.`
 
     cmd.LongDesc = "Pack the unpacked Go modules located in SOURCE… into DESTDIR." + filterHelp + versionHelp
 
-    cmd.Spec =  "[-v|--version=] " +
-                "[-P|--preversion-suffix=] " +
-                "[[-C|--commit=] (-H|--time=)] " +
-                "[[-M|--include-module=] | " +
+    cmd.Spec =  "[-v|--version=] " +
+                "[-P|--pre-release=
] " +
+               "[[-C|--commit=] (-H|--time=)] " +
+                "[-B|--build=] " +
+                "[-Z|--epoch=] " +
+               "[[-M|--include-module=] | " +
                 "[-m|--ignore-module=]]... " +
                "[[-F|--include-file=] | " +
                 "[-E|--include-extension=] | " +
@@ -725,6 +765,8 @@ formats.`
     modPre             := cmd.String(modPreProto)
     modCommit          := cmd.String(modCommitProto)
     modTime            := cmd.String(modTimeProto)
+    modBuild           := cmd.String(modBuildProto)
+    modEpoch           := cmd.Int(modEpochProto)
     includeMods        := cmd.Strings(includeModsProto)
     ignoreMods         := cmd.Strings(ignoreModsProto)
     includeFiles       := cmd.Strings(includeFilesProto)
@@ -745,7 +787,8 @@ formats.`
     dstDir             := cmd.String(dstDirProto)
 
     cmd.Action = func() {
-      ver, err := version.New(*modVer, *modPre, *modCommit, *modTime, *quiet)
+      ver, err := version.New(*modVer, *modPre, *modCommit, *modTime,
+                              *modBuild, *modEpoch, *quiet)
       check(err)
       filters      := filter.Default
       filters, err  = filters.AddByType(*includeFiles, *includeExtensions,
diff --git a/version/version.go b/version/version.go
index d69e544..e5b0c1a 100644
--- a/version/version.go
+++ b/version/version.go
@@ -29,10 +29,12 @@ import (
 // https://godoc.org/cmd/go/internal/modfetch
 // once this is exposed somewhere.
 type Version struct {
+  Epoch     int
   Version   string
   Pre       string
   Commit    string
   Time      time.Time
+  Build     string
 }
 
 // Process raw with the exp regexp expression, containing named groups; return a
@@ -88,7 +90,7 @@ func cleanPre(raw string) (result string) {
 }
 
 func New(version string, pre string, commit string, timestamp string,
-         quiet bool) (result *Version, err error){
+         build string, epoch int, quiet bool) (result *Version, err error){
   var r Version
   result = &r
   if version != "" {
@@ -114,9 +116,6 @@ func New(version string, pre string, commit string, timestamp string,
     fmt.Fprintf(os.Stderr,"   WARNING converting “%s” pre-version suffix to “%s”.\n", pre, result.Pre)
   }
   result.Commit = cleanPre(commit)
-  if result.Commit != "" {
-    result.Commit = string([]rune(result.Commit)[:12])
-  }
   if !quiet && result.Commit != commit {
     fmt.Fprintf(os.Stderr,"   WARNING converting “%s” commit ID to “%s”.\n", commit, result.Commit)
   }
@@ -130,6 +129,8 @@ func New(version string, pre string, commit string, timestamp string,
     }
     result.Time = result.Time.UTC()
   }
+  result.Build = build
+  result.Epoch = epoch
   return result, nil
 }
 
@@ -149,7 +150,7 @@ func FromGo(raw string, quiet bool) (result *Version, err error) {
   pre, _       := vsubs["pre"]
   commit, _    := vsubs["commit"]
   timestamp, _ := vsubs["time"]
-  return New(version, pre, commit, timestamp, quiet)
+  return New(version, pre, commit, timestamp, "", 0, quiet)
 }
 
 // Target ordering and syntax, applying Go and RPM ordering rules
@@ -159,10 +160,10 @@ func FromGo(raw string, quiet bool) (result *Version, err error) {
 // https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/
 // and can be cheched with rpmdev-vercmp
 // Go                                       │ RPM
-// vX.0.0-yyyymmddhhmmss-abcdefabcdef       │ X.0.0~0.yyyymmddhhmmss.abcdefabcdef
-// vX.0.0-pre                               │ X.0.0~1.pre
-// vX.0.0-pre.0.yyyymmddhhmmss-abcdefabcdef │ X.0.0~1.pre.yyyymmddhhmmss-abcdefabcdef
-// vX.0.0                                   │ X.0.0
+// vX.0.0-yyyymmddhhmmss-abcdefabcdef       │ X~0.yyyymmddhhmmss.abcdefabcdef
+// vX.0.0-pre                               │ X~1.pre
+// vX.0.0-pre.0.yyyymmddhhmmss-abcdefabcdef │ X~1.pre.yyyymmddhhmmss-abcdefabcdef
+// vX.0.0                                   │ X
 // vX.Y.Z-0.yyyymmddhhmmss-abcdefabcdef     │ X.Y.Z~0.yyyymmddhhmmss.abcdefabcdef
 // vX.Y.Z-pre                               │ X.Y.Z~1.pre
 // vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef │ X.Y.Z~1.pre.yyyymmddhhmmss.abcdefabcdef
@@ -172,7 +173,10 @@ func FromGo(raw string, quiet bool) (result *Version, err error) {
 func (ver *Version) Go() (result string) {
   version := ver.Version
   if version == "" {
-    version = "0.0.0"
+    version = "0"
+  }
+  for strings.Count(version, ".") < 2 {
+    version = version + ".0"
   }
   time    := ver.Time.Format("20060102150405")
   result = "v" + version
@@ -183,7 +187,8 @@ func (ver *Version) Go() (result string) {
   }
   sup := ""
   if ver.Commit != "" {
-    sup = time + "-" + ver.Commit
+    commit := string([]rune(ver.Commit)[:12])
+    sup = time + "-" + commit
     if !strings.HasSuffix(version,".0.0") {
       sup = "0." + sup
     }
@@ -195,7 +200,10 @@ func (ver *Version) Go() (result string) {
 func (ver *Version) RPM() (result string) {
   version := ver.Version
   if version == "" {
-    version = "0.0.0"
+    version = "0"
+  }
+  for strings.HasSuffix(version, ".0") {
+    version = strings.TrimSuffix(version, ".0")
   }
   time    := ver.Time.Format("20060102150405")
   result = version
@@ -205,12 +213,20 @@ func (ver *Version) RPM() (result string) {
     sep = "."
   }
   if ver.Commit != "" {
-    result = result + sep + time + "." + ver.Commit
+    commit := string([]rune(ver.Commit)[:12])
+    result = result + sep + time + "." + commit
   }
   // rpm separates version from other naming components with hyphen
   // it is therefore forbidden within rpm version strings
   hyphenRe := regexp.MustCompile(`-`)
-  return hyphenRe.ReplaceAllString(result, ".")
+  result = hyphenRe.ReplaceAllString(result, ".")
+  if ver.Epoch > 0 {
+    result = strconv.Itoa(ver.Epoch) + ":" + result
+  }
+  if ver.Build != "" {
+    result = result + "-" + ver.Build
+  }
+  return result
 }
 
 var ErrUnknownOutputFormat = errors.New("unknown output format")
@@ -227,24 +243,32 @@ func (ver *Version) Format(format string) (result string, err error) {
 
 // Taken from https://tip.golang.org/cmd/go/#hdr-Module_proxy_protocol that
 // helpfuly states “this may be expanded in the future”
-// Would be nice to locate where this is actually defined in the upstream code
+// Completed with semver & rpm parts Go developpers forgot to include
 type Info struct {
-    Version string                        // version string
-    Time    time.Time `json:",omitempty"` // commit time that we will usually not have
+  Version string                        // version string, in Go format
+  Time    time.Time `json:",omitempty"` // code timestamp (unreliable)
+  Build   string    `json:",omitempty"` // Build (semver) / Release (RPM)
+  Epoch   int       `json:",omitempty"` // versioning Epoch (RPM)
+
 }
 
-func NewInfo(version string, Time time.Time) *Info {
-  info := Info{Version: version, Time: Time}
+func NewInfo(version string, Time time.Time, build string, epoch int) *Info {
+  info := Info{Version: version,
+               Time: Time,
+               Build: build,
+               Epoch: epoch}
   return &info
 }
 
 func (ver *Version) Info() *Info {
-  return NewInfo(ver.Go(), ver.Time)
+  return NewInfo(ver.Go(), ver.Time, ver.Build, ver.Epoch)
 }
 
 func (ver *Info) Ver(quiet bool) (*Version, error) {
   v, err := FromGo(ver.Version, quiet)
   v.Time  = ver.Time
+  v.Build = ver.Build
+  v.Epoch = ver.Epoch
   return v, err
 }