packages feed

cabal-pkg-config-version-hook (empty) → 0.1.0.0

raw patch · 4 files changed

+200/−0 lines, 4 filesdep +Cabaldep +basedep +lens

Dependencies added: Cabal, base, lens, process

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for cabal-pkg-config-version-hook++## 0.1.0.0 -- 2022-03-07++* First version. Main feature is room for improvement.
+ README.md view
@@ -0,0 +1,4 @@++# A Cabal setup hook for pkg-config version info++Cabal doesn't natively give access to version information from `pkg-config` (yet?). This setup hook gets the version and makes it available via Haskell CPP and C/C++ macros, as well as setting Cabal flags automatically.
+ cabal-pkg-config-version-hook.cabal view
@@ -0,0 +1,27 @@+cabal-version:      2.4+name:               cabal-pkg-config-version-hook+version:            0.1.0.0+synopsis:           Make Cabal aware of pkg-config package versions+description:        A setup hook for Cabal that determines the compile-time version of a pkg-config package and adds CPP macros and enables flags.+-- A URL where users can report bugs.+-- bug-reports:+homepage:           https://github.com/hercules-ci/hercules-ci-agent/tree/master/cabal-pkg-config-version-hook#readme+license:            BSD-3-Clause+author:             Robert Hensing+maintainer:         info@hercules-ci.com+copyright:          cabal-pkg-config-version-hook contributors+category:           Distribution+extra-source-files:+  CHANGELOG.md+  README.md++library+    exposed-modules:+      Distribution.PkgConfigVersionHook+    build-depends:+      base ==4.*+      , Cabal >= 2.2.0.0+      , process+      , lens+    hs-source-dirs:   src+    default-language: Haskell2010
+ src/Distribution/PkgConfigVersionHook.hs view
@@ -0,0 +1,164 @@+-- |+--+--  Example:+--+--  @+--      main :: IO ()+--      main =+--        defaultMainWithHooks $+--          simpleUserHooks+--            & addHook+--              (mkSettings "nix-store")+--                { macroName = "NIX",+--                  flagPrefixName = "nix"+--                }+--  @+--+--  The above will look for a pkg-config package @nix-store@, and then+--+--    * Define CPP, C and C++ macros+--+--        * @NIX_MAJOR@, an integer+--        * @NIX_MINOR@, an integer+--        * @NIX_PATCH@, an integer; 0 if missing+--        * @NIX_IS_AT_LEAST(major,minor,patch)@, returning true when the discovered version @>=@ the specified version+--+--    * Set or unset flags like `nix-2_4` so that the flag is true when the+--      discovered version is at least the version in the flag's name.+module Distribution.PkgConfigVersionHook+  ( addHook,+    mkSettings,+    Settings (..),+    composeConfHook,+  )+where++import Control.Lens ((%~), (^.))+import Control.Monad (when)+import qualified Data.Char as C+import Data.Foldable (toList)+import Data.Function ((&))+import qualified Data.List as L+import Distribution.Simple+import Distribution.Simple.Setup (configConfigurationsFlags)+import Distribution.Types.BuildInfo.Lens (ccOptions, cppOptions, cxxOptions)+import Distribution.Types.Flag (flagName, mkFlagAssignment, mkFlagName, unFlagName)+import Distribution.Types.GenericPackageDescription.Lens+  ( allCondTrees,+    condBenchmarks,+    condExecutables,+    condForeignLibs,+    condLibrary,+    condSubLibraries,+    condTestSuites,+    genPackageFlags,+  )+import System.IO (hPutStrLn, stderr)+import System.Process (readProcess)+import qualified Text.ParserCombinators.ReadP as P+import Prelude hiding (log)++-- | Hook into Cabal to provide pkg-config metadata. Can be applied multiple+-- times to support multiple packages.+addHook :: Settings -> UserHooks -> UserHooks+addHook settings hooks = hooks {confHook = composeConfHook settings (confHook hooks)}++-- | How the metadata for a pkg-config package should be made available to the+-- cabal file.+data Settings = Settings+  { -- | Name of the package; used for querying pkg-config.+    pkgConfigName :: String,+    -- | Name to use in the Haskell CPP and C/C++ preprocessor macros.+    --+    -- For example, `pkgConfigName = "FOO"` will set the macros+    --+    --  * @FOO_MAJOR@+    --+    --  * @FOO_MINOR@+    --+    --  * @FOO_PATCH@+    --+    --  * @FOO_IS_AT_LEAST(major, minor, patch)@+    macroName :: String,+    -- | Name to use when setting flag values in the cabal file.+    --+    -- Flags named with this prefix, followed by a dash, followed by a major version number, an underscore and a minor version number will be set when the detected package is at least that version.+    flagPrefixName :: String+  }++-- | Derive a default 'Settings' value from just a pkg-config package name.+mkSettings :: String -> Settings+mkSettings name =+  Settings+    { pkgConfigName = name,+      macroName = map (\x -> case x of '-' -> '_') name,+      flagPrefixName = name+    }++-- | Extend the value of 'confHook'. It's what powers 'addHook'.+composeConfHook settings origHook = \(genericPackageDescription, hookedBuildInfo) confFlags -> do+  (actualMajor, actualMinor, actualPatch) <- getPkgConfigPackageVersion (pkgConfigName settings)++  let defines =+        [ "-D" <> macroName settings <> "_MAJOR=" <> show actualMajor,+          "-D" <> macroName settings <> "_MINOR=" <> show actualMinor,+          "-D" <> macroName settings <> "_PATCH=" <> show actualPatch,+          "-D" <> macroName settings <> "_IS_AT_LEAST(a,b,c)=(" <> show actualMajor <> ">a||(" <> show actualMajor <> "==a&&(" <> show actualMinor <> ">b||(" <> show actualMinor <> "==b&&" <> show actualPatch <> ">=c))))"+        ]+      extraFlags =+        [ (mkFlagName (flagPrefixName settings ++ "-" ++ show major ++ "_" ++ show minor), (actualMajor, actualMinor) >= (major, minor))+          | declaredFlag <- genericPackageDescription ^. genPackageFlags,+            let rawName = unFlagName $ flagName declaredFlag,+            rawVersion <- L.stripPrefix (flagPrefixName settings ++ "-") rawName & toList,+            [major, minor] <- unambiguously parseFlagVersion rawVersion & toList+        ]+      setDefines comp x =+        x+          & comp . cppOptions %~ (<> defines)+          & comp . ccOptions %~ (<> defines)+          & comp . cxxOptions %~ (<> defines)+      genericPackageDescription' =+        genericPackageDescription+          & setDefines (condLibrary . traverse . traverse)+          & setDefines (condSubLibraries . traverse . traverse . traverse)+          & setDefines (condForeignLibs . traverse . traverse . traverse)+          & setDefines (condExecutables . traverse . traverse . traverse)+          & setDefines (condTestSuites . traverse . traverse . traverse)+          & setDefines (condBenchmarks . traverse . traverse . traverse)++      configConfigurationsFlags' = configConfigurationsFlags confFlags `mappend` mkFlagAssignment extraFlags+      confFlags' =+        confFlags+          { configConfigurationsFlags = configConfigurationsFlags'+          }+  origHook (genericPackageDescription', hookedBuildInfo) confFlags'++parseVersion :: P.ReadP [Int]+parseVersion = do+  map read <$> do+    P.many1 (P.satisfy C.isDigit) `P.sepBy` P.char '.'++parseFlagVersion :: P.ReadP [Int]+parseFlagVersion =+  map read <$> do+    P.many1 (P.satisfy C.isDigit) `P.sepBy` P.char '_'++unambiguously :: P.ReadP a -> String -> Maybe a+unambiguously p s =+  case filter (\(a, x) -> x == "") $ P.readP_to_S p s of+    [(v, _)] -> Just v+    _ -> Nothing++getPkgConfigPackageVersion :: String -> IO (Int, Int, Int)+getPkgConfigPackageVersion pkgName = do+  s <- readProcess "pkg-config" ["--modversion", pkgName] ""+  case L.sortOn (\(_, s) -> length s) $ P.readP_to_S parseVersion s of+    [] -> error ("Could not parse version " ++ show s ++ " returned by pkg-config for package " ++ pkgName)+    (v, r) : _ -> do+      when (L.dropWhile C.isSpace r /= "") $ do+        log ("ignoring trailing text " ++ show r ++ " in version " ++ show s ++ " of pkg-config package " ++ pkgName)+      let v' = v ++ L.repeat 0+      pure (v' L.!! 0, v' L.!! 1, v' L.!! 2)++-- Should probably use a Cabal function?+log = hPutStrLn stderr