diff --git a/.hlint.yaml b/.hlint.yaml index fd2ee18..e45453b 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -33,6 +33,7 @@ - DerivingVia - FlexibleContexts - OverloadedStrings + - QuasiQuotes - RecordWildCards - TemplateHaskell - TypeApplications diff --git a/build-static.sh b/build-static.sh index 92f8c6a..442dc21 100644 --- a/build-static.sh +++ b/build-static.sh @@ -39,6 +39,9 @@ cabal freeze ## Run the Docker container: docker run -i --detach -v "$(pwd):/app" --name "${CONTAINER_NAME}" "${DOCKER_IMAGE}" /bin/bash +## Whitelist codebase directory for Git queries: +docker exec "${CONTAINER_NAME}" git config --global --add safe.directory /app + ## Update cabal database: docker exec "${CONTAINER_NAME}" cabal update diff --git a/package.yaml b/package.yaml index 4316cf2..5fbb846 100644 --- a/package.yaml +++ b/package.yaml @@ -24,11 +24,14 @@ library: - autodocodec-schema - bytestring - file-embed + - githash - monad-parallel - mtl - optparse-applicative - path - scientific + - string-interpolate + - template-haskell - text - time - typed-process diff --git a/src/Lhp/Cli.hs b/src/Lhp/Cli.hs index 5d7ae74..7e715fa 100644 --- a/src/Lhp/Cli.hs +++ b/src/Lhp/Cli.hs @@ -11,6 +11,7 @@ import Control.Monad.Except (runExceptT) import qualified Data.Aeson as Aeson import qualified Data.ByteString.Lazy.Char8 as BLC import qualified Data.Text as T +import qualified Data.Text.IO as TIO import qualified Lhp.Config as Config import qualified Lhp.Meta as Meta import Lhp.Remote (compileReport) @@ -46,6 +47,7 @@ optProgram :: OA.Parser (IO ExitCode) optProgram = commandCompile <|> commandSchema + <|> commandVersion -- * Commands @@ -93,6 +95,24 @@ commandSchema = OA.hsubparser (OA.command "schema" (OA.info parser infomod) <> O parser = pure (BLC.putStrLn (Aeson.encode (ADC.Schema.jsonSchemaViaCodec @Report)) >> pure ExitSuccess) +-- ** version + + +-- | Definition for @version@ CLI command. +commandVersion :: OA.Parser (IO ExitCode) +commandVersion = OA.hsubparser (OA.command "version" (OA.info parser infomod) <> OA.metavar "version") + where + infomod = OA.fullDesc <> infoModHeader <> OA.progDesc "Show version and build information." <> OA.footer "This command shows version and build information." + parser = + doVersion + <$> OA.switch (OA.short 'j' <> OA.long "json" <> OA.help "Format output in JSON.") + doVersion json = do + if json + then BLC.putStrLn (Aeson.encode Meta.buildInfo) + else TIO.putStrLn (Meta.prettyBuildInfo Meta.buildInfo) + pure ExitSuccess + + -- * Helpers diff --git a/src/Lhp/Meta.hs b/src/Lhp/Meta.hs index b4f700c..cc4c7fd 100644 --- a/src/Lhp/Meta.hs +++ b/src/Lhp/Meta.hs @@ -1,11 +1,22 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} -- | This module provides project metadata information definitions. module Lhp.Meta where +import Data.Aeson (ToJSON (toEncoding)) +import qualified Data.Aeson as Aeson +import Data.Maybe (fromMaybe) +import Data.String.Interpolate (__i) import qualified Data.Text as T +import qualified Data.Time as Time import Data.Version (Version, showVersion) +import qualified GitHash as Githash +import qualified Language.Haskell.TH as TH import qualified Paths_lhp as Paths +import qualified System.Info -- | Application name. @@ -46,3 +57,79 @@ versionString = showVersion version -- "0.0.0" versionText :: T.Text versionText = T.pack versionString + + +-- | Data definition for build information. +data BuildInfo = BuildInfo + { _buildInfoName :: !T.Text + , _buildInfoTitle :: !T.Text + , _buildInfoVersion :: !T.Text + , _buildInfoTimestamp :: !T.Text + , _buildInfoGitTag :: !(Maybe T.Text) + , _buildInfoGitHash :: !(Maybe T.Text) + , _buildInfoCompilerName :: !T.Text + , _buildInfoCompilerVersion :: !T.Text + } + deriving (Eq, Show) + + +instance Aeson.ToJSON BuildInfo where + toJSON BuildInfo {..} = + Aeson.object + [ "name" Aeson..= _buildInfoName + , "title" Aeson..= _buildInfoTitle + , "version" Aeson..= _buildInfoVersion + , "timestamp" Aeson..= _buildInfoTimestamp + , "gitTag" Aeson..= _buildInfoGitTag + , "gitHash" Aeson..= _buildInfoGitHash + , "compilerName" Aeson..= _buildInfoCompilerName + , "compilerVersion" Aeson..= _buildInfoCompilerVersion + ] + + + toEncoding BuildInfo {..} = + Aeson.pairs + ( "name" Aeson..= _buildInfoName + <> "title" Aeson..= _buildInfoTitle + <> "version" Aeson..= _buildInfoVersion + <> "timestamp" Aeson..= _buildInfoTimestamp + <> "gitTag" Aeson..= _buildInfoGitTag + <> "gitHash" Aeson..= _buildInfoGitHash + <> "compilerName" Aeson..= _buildInfoCompilerName + <> "compilerVersion" Aeson..= _buildInfoCompilerVersion + ) + + +-- | Returns the build information generated as per compile time. +buildInfo :: BuildInfo +buildInfo = + BuildInfo + { _buildInfoName = name + , _buildInfoTitle = title + , _buildInfoVersion = versionText + , _buildInfoTimestamp = $(TH.stringE . Time.formatTime Time.defaultTimeLocale "%Y-%m-%dT%H:%M:%SZ" =<< TH.runIO Time.getCurrentTime) + , _buildInfoGitTag = either (const Nothing) (Just . T.pack . Githash.giTag) gitInfo + , _buildInfoGitHash = either (const Nothing) (Just . T.pack . Githash.giHash) gitInfo + , _buildInfoCompilerName = T.pack System.Info.compilerName + , _buildInfoCompilerVersion = T.pack (showVersion System.Info.fullCompilerVersion) + } + + +-- | Builds a pretty text from a given 'BuildInfo' value. +prettyBuildInfo :: BuildInfo -> T.Text +prettyBuildInfo BuildInfo {..} = + [__i| +Name: #{_buildInfoName} +Title: #{_buildInfoTitle} +Version: #{_buildInfoVersion} +Timestamp: #{_buildInfoTimestamp} +Git Tag: #{fromMaybe "N/A" _buildInfoGitTag} +Git Hash: #{fromMaybe "N/A" _buildInfoGitHash} +Compiler Name: #{_buildInfoCompilerName} +Compiler Version: #{_buildInfoCompilerVersion} +|] + + +-- | Git information if any. +gitInfo :: Either String Githash.GitInfo +gitInfo = $$Githash.tGitInfoCwdTry