From 1ef8f329c6208a14fa7de09b2d186b1cbb25989b Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Fri, 10 Dec 2021 08:19:38 -1000 Subject: [PATCH] lib: simpler more robust version strings, with date/without patchlevel We no longer rely on git tags and git describe output, since it's hard to reliably select the correct tag eg for minor releases. We might use them again in future for dev builds, but it requires adding git describe --match support to githash. For now, * Program name, OS and architecture are always shown. * The package version is always shown. * If there is git info at build time, the latest commit hash and commit date are shown. Example outputs: A homebrew binary, not built in git repo: hledger-ui 1.24, mac-aarch64 A CI release build, built in git repo: hledger 1.24.1-g455b35293-20211210, mac-x86_64 API changes: * new type synonyms ProgramName, PackageVersion, VersionString * versionStringForProgname -> versionString with extra argument * versionStringFor -> versionStringWith with extra argument --- Makefile | 7 -- hledger-ui/Hledger/UI/UIOptions.hs | 13 +-- hledger-web/Hledger/Web/WebOptions.hs | 13 +-- hledger/Hledger/Cli.hs | 22 +++-- hledger/Hledger/Cli/Version.hs | 118 ++++++++++++++------------ 5 files changed, 96 insertions(+), 77 deletions(-) diff --git a/Makefile b/Makefile index 03766788b..a6c3302e8 100644 --- a/Makefile +++ b/Makefile @@ -193,12 +193,6 @@ DOCSOURCEFILES:= \ # We use hledger CLI's current version (XXX for all packages, which isn't quite right). VERSION=$(shell cat hledger/.version) -# the number of commits since the last tag -PATCHLEVEL=$(shell git describe --tags --match 'hledger-[0-9]*' --long | awk -F- '{print $$3}') -#PATCHLEVEL:=$(shell git describe --tags --match 'hledger-web-[0-9]*' --long | awk -F- '{print $$4}') -# the number of commits since the last_release tag -#PATCHLEVEL:=$(shell git rev-list last_release..HEAD | wc -l) - # flags for ghc builds WARNINGS:=\ @@ -223,7 +217,6 @@ BUILDFLAGS=\ $(WARNINGS) \ $(INCLUDEPATHS) \ $(GHCLOWMEMFLAGS) $(CABALMACROSFLAGS) \ - -DPATCHLEVEL=$(PATCHLEVEL) \ -DDEVELOPMENT \ -DVERSION="\"$(VERSION)\"" \ # -fhide-source-paths \ diff --git a/hledger-ui/Hledger/UI/UIOptions.hs b/hledger-ui/Hledger/UI/UIOptions.hs index daf401770..81d049298 100644 --- a/hledger-ui/Hledger/UI/UIOptions.hs +++ b/hledger-ui/Hledger/UI/UIOptions.hs @@ -18,18 +18,19 @@ import Hledger.UI.Theme (themes, themeNames) -- cf Hledger.Cli.Version -packageversion :: String +packageversion :: PackageVersion +packageversion = #ifdef VERSION -packageversion = VERSION + VERSION #else -packageversion = "" + "" #endif -progname :: String +progname :: ProgramName progname = "hledger-ui" -prognameandversion :: String -prognameandversion = versionStringForProgname progname +prognameandversion :: VersionString +prognameandversion = versionString progname packageversion uiflags = [ -- flagNone ["debug-ui"] (setboolopt "rules-file") "run with no terminal output, showing console" diff --git a/hledger-web/Hledger/Web/WebOptions.hs b/hledger-web/Hledger/Web/WebOptions.hs index 35a1ac348..0ca73604c 100644 --- a/hledger-web/Hledger/Web/WebOptions.hs +++ b/hledger-web/Hledger/Web/WebOptions.hs @@ -20,18 +20,19 @@ import Hledger.Web.Settings (defhost, defport, defbaseurl) -- cf Hledger.Cli.Version -packageversion :: String +packageversion :: PackageVersion +packageversion = #ifdef VERSION -packageversion = VERSION + VERSION #else -packageversion = "" + "" #endif -progname :: String +progname :: ProgramName progname = "hledger-web" -prognameandversion :: String -prognameandversion = versionStringForProgname progname +prognameandversion :: VersionString +prognameandversion = versionString progname packageversion webflags :: [Flag RawOpts] diff --git a/hledger/Hledger/Cli.hs b/hledger/Hledger/Cli.hs index fea48cfee..4df9f4b27 100644 --- a/hledger/Hledger/Cli.hs +++ b/hledger/Hledger/Cli.hs @@ -17,7 +17,7 @@ module Hledger.Cli ( module Hledger, module System.Console.CmdArgs.Explicit, prognameandversion, - versionStringForProgname + versionString ) where @@ -31,9 +31,21 @@ import Hledger.Cli.DocFiles import Hledger.Cli.Utils import Hledger.Cli.Version --- | The program name and the best version information we can obtain --- from git describe or build variables. -prognameandversion = versionStringForProgname progname -versionStringForProgname = versionStringFor $$tGitInfoCwdTry +-- | The program name and version string for this build of the hledger tool, +-- including any git info available at build time. +prognameandversion :: String +prognameandversion = versionString progname packageversion + +-- | A helper to generate the best version string we can from the given +-- program name and package version strings, current os and architecture, +-- and any git info available at build time (commit hash, commit date, branch +-- name, patchlevel since latest release tag for that program's package). +-- Typically called for programs "hledger", "hledger-ui", or "hledger-web". +-- +-- The git info changes whenever any file in the repository changes. +-- Keeping this template haskell call here and not down in Hledger.Cli.Version +-- helps reduce the number of modules recompiled. +versionString :: ProgramName -> PackageVersion -> String +versionString = versionStringWith $$tGitInfoCwdTry -- unit tests (tests_Hledger_Cli) are defined in Hledger.Cli.Commands diff --git a/hledger/Hledger/Cli/Version.hs b/hledger/Hledger/Cli/Version.hs index 56aef68ae..74636ebed 100644 --- a/hledger/Hledger/Cli/Version.hs +++ b/hledger/Hledger/Cli/Version.hs @@ -4,79 +4,91 @@ Version number-related utilities. See also the Makefile. -} module Hledger.Cli.Version ( + ProgramName, + PackageVersion, + VersionString, packageversion, progname, - versionStringFor, + versionStringWith, ) where -import GitHash (GitInfo, giDescribe) +import GitHash (GitInfo, giHash, giCommitDate) import System.Info (os, arch) -import Hledger.Utils +import Data.List (intercalate) +import Data.Maybe (fromMaybe) --- | This package's version, passed in as VERSION build variable, or a generic description. -packageversion :: String +type ProgramName = String +type PackageVersion = String +type VersionString = String + +-- | The VERSION string defined with -D in this package's package.yaml/.cabal file +-- (by Shake setversion), if any. Normally a dotted number string with 1-3 components. +packageversion :: PackageVersion +packageversion = #ifdef VERSION -packageversion = VERSION + VERSION #else -packageversion = "dev build" + "" #endif --- | A period and the patch level (number of patches added since the package version), --- passed in as PATCHLEVEL build variable, or the empty string. --- If PATCHLEVEL is defined it must be a number, or this will fail. -patchlevel :: String -#ifdef PATCHLEVEL -patchlevel = "." ++ show (PATCHLEVEL :: Int) -#else -patchlevel = "" -#endif - --- | The version and patchlevel passed in as build variables, combined and prettified. --- This will raise an error if VERSION is has <1 or >3 components, --- or if PATCHLEVEL is defined but not a number. --- Used as a fallback if git describe is unavailable. -buildversion :: String -buildversion = prettify . splitAtElement '.' $ packageversion ++ patchlevel - where - prettify [major,minor,bugfix,patches] = - major ++ "." ++ minor ++ bugfix' ++ patches' - where - bugfix' = if bugfix == "0" then "" else '.' : bugfix - patches' = if patches == "0" then "" else '+' : patches - prettify [major,minor,bugfix] = prettify [major,minor,bugfix,"0"] - prettify [major,minor] = prettify [major,minor,"0","0"] - prettify [major] = prettify [major,"0","0","0"] - prettify [] = error' "VERSION is empty, please fix" -- PARTIAL: - prettify _ = error' "VERSION has too many components, please fix" - -- | The name of this package's main executable. -progname :: String +progname :: ProgramName progname = "hledger" --- | Given a program name, make a version string consisting of: +-- | Given possible git state info from the build directory (or an error message, which is ignored), +-- the name of a program (executable) in the currently building package, +-- and the package's version, make a complete version string. Here is the logic: +-- +-- * Program name, OS and architecture are always shown. +-- * The package version is always shown. +-- * If there is git info at build time, the latest commit hash and commit date are shown. +-- * (TODO, requires adding --match support to githash: +-- If there are tags matching THISPKG-[0-9]*, the latest one is used to calculate patch level +-- (number of commits since tag), and if non-zero, it and the branch name are shown.) -- --- * the program name --- * the output of "git describe" in the current repo at build time --- (last tag, commit count since then, HEAD's git hash); --- or if that fails, buildversion --- * the platform (OS) name --- * the processor architecture name. +-- Some example outputs: -- -versionStringFor :: Either String GitInfo -> String -> String -versionStringFor gitinfo progname = concat [ - progname - , " " - , either (const buildversion) giDescribe gitinfo - , ", " - , os' - , "-" - , arch - ] +-- * A homebrew binary, not built in git repo: hledger-ui 1.24, mac-aarch64 +-- * A CI release build, built in git repo at release tag: hledger-ui 1.24.1-g455b35293-20211210, mac-x86_64 +-- * (TODO) A dev build, built in git repo: hledger-ui 1.24.1+1-g4abd8ef10-20211210 (1.24-branch), mac-x86_64 +-- +-- This function requires git log to show the default (rfc2822-style) date format, +-- so that must not be overridden by a log.date git config variable. +-- +versionStringWith :: Either String GitInfo -> ProgramName -> PackageVersion -> VersionString +versionStringWith egitinfo progname packageversion = + concat [ progname , " " , version , ", " , os' , "-" , arch ] where os' | os == "darwin" = "mac" | os == "mingw32" = "windows" | otherwise = os + version = case egitinfo of + Left _err -> packageversion + Right gitinfo -> intercalate "-" [packageversion , hash, date] + where + hash = 'g' : take 9 (giHash gitinfo) -- like git describe + date = concat [year,mm,dd] + where + -- XXX PARTIAL: depends on git log's date format, which by default + -- is --date=default ("similar to --date=rfc2822"), but could be + -- overridden by a log.date config variable in repo or user git config. + _weekday:mon:day:_localtime:year:_offset:_ = words $ giCommitDate gitinfo + mm = fromMaybe mon $ lookup mon $ [ + ("Jan","01") + ,("Feb","02") + ,("Mar","03") + ,("Apr","04") + ,("May","05") + ,("Jun","06") + ,("Jul","07") + ,("Aug","08") + ,("Sep","09") + ,("Oct","10") + ,("Nov","11") + ,("Dec","12") + ] + dd = (if length day < 2 then ('0':) else id) day -- -- | Given a program name, return a precise platform-specific executable -- -- name suitable for naming downloadable binaries. Can raise an error if