{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE DeriveDataTypeable #-} -- | -- Module: $HEADER$ -- Description: Create pkg-config configuration files -- Copyright: (c) 2014 Peter Trsko -- License: BSD3 -- -- Maintainer: peter.trsko@gmail.com -- Stability: experimental -- Portability: DeriveDataTypeable, NoImplicitPrelude -- -- Create /pkg-config/ configuration file from Haskell code using combinators -- specialized for this purpose. To learn more about /pkg-config/ please read -- one or both following articles: -- -- * <http://people.freedesktop.org/~dbn/pkg-config-guide.html Guide to pkg-config> -- by Dan Nicholson -- -- * <https://autotools.io/ Autotools Mythbuster> by Diego Elio Pettenò: -- <https://autotools.io/pkgconfig/index.html Chapter 4. Dependency discovery -- pkg-config> module Data.PkgConfig ( -- * Usage -- $usage -- * PkgConfig -- $pkgConfig PkgConfig -- ** Lenses , pkgVariables , pkgName , pkgDescription , pkgUrl , pkgVersion , pkgRequires , pkgRequiresPrivate , pkgConflicts , pkgCflags , pkgLibs , pkgLibsPrivate -- ** Type Aliases -- -- | These are used to make type signatures easier to read. , PkgDescription , PkgName , PkgUrl , PkgVariable , PkgVersion -- ** Serialization , toStrictText -- ** I/O , writePkgConfig -- * PkgTemplate -- $pkgTemplate , PkgTemplate -- ** Smart Constructors , var , lit , strLit , singletonLit -- ** Combinators , quote -- ** FilePath-like Combinators , (</>) , (<.>) -- ** Version Combinators , version , (~=), (~/=), (~<), (~>), (~<=), (~>=) -- ** Options Combinators , option , strOption , includes , libraries , libraryPath -- ** Specialized Folds , list , options , separatedBy -- ** Queries , variables ) where import Data.Function ((.), ($)) import Data.List as List (map) import Data.Monoid (Monoid(mempty), (<>)) import Data.String (String) import Data.Word (Word) import System.IO (IO, FilePath) import Text.Show (Show(show)) import qualified Data.Text as Strict (Text) import qualified Data.Text as Strict.Text (pack, singleton) import qualified Data.Text.IO as Strict.Text (writeFile) import Data.PkgConfig.Internal.Template ( PkgTemplate , lit , singletonLit , strLit , var , variables ) import Data.PkgConfig.Internal.PkgConfig ( PkgConfig , PkgDescription , PkgName , PkgUrl , PkgVariable , PkgVersion , pkgCflags , pkgConflicts , pkgDescription , pkgLibs , pkgLibsPrivate , pkgName , pkgRequires , pkgRequiresPrivate , pkgUrl , pkgVariables , pkgVersion , toStrictText ) -- {{{ PkgTemplate Combinators ------------------------------------------------ -- | Put quotation marks (\'\"\') around a template. -- -- >>> quote $ var "prefix" </> "include" -- "${prefix}/include" -- -- >>> var "prefix" </> quote "dir with spaces" -- ${prefix}/"dir with spaces" quote :: PkgTemplate -> PkgTemplate quote t = singletonLit '"' <> t <> singletonLit '"' -- {{{ File Path Combinators -------------------------------------------------- -- | Put literal \"/\" between two templates. -- -- >>> var "prefix" </> lit "foo" <.> lit "pc" -- ${prefix}/foo.pc (</>) :: PkgTemplate -> PkgTemplate -> PkgTemplate t1 </> t2 = t1 <> singletonLit '/' <> t2 -- | Put literal \".\" between two templates. -- -- >>> var "prefix" </> lit "foo" <.> lit "pc" -- ${prefix}/foo.pc -- >>> var "major" <.> var "minor" <.> var "patch" -- ${major}.${minor}.${patch} (<.>) :: PkgTemplate -> PkgTemplate -> PkgTemplate t1 <.> t2 = t1 <> singletonLit '.' <> t2 -- }}} File Path Combinators -------------------------------------------------- -- {{{ Version Combinators ---------------------------------------------------- -- | Treat list of integers as version number and construct template literal -- out of it. -- -- >>> version [1, 2, 3] -- 1.2.3 -- >>> version [] == mempty -- True version :: [Word] -> PkgTemplate version [] = mempty version (v : vs) = case vs of [] -> wordLit v _ -> wordLit v <.> version vs where wordLit :: Word -> PkgTemplate wordLit = strLit . show -- | Dependency on a package of exact version. -- -- >>> "sqlite" ~= [3, 8, 7, 1] -- sqlite = 3.8.7.1 (~=) :: Strict.Text -> [Word] -> PkgTemplate pkg ~= ver = lit pkg <> strLit " = " <> version ver -- | Dependency on a package not of a specific version. -- -- >>> "alpha" ~/= [7, 2] -- alpha != 7.2 (~/=) :: Strict.Text -> [Word] -> PkgTemplate pkg ~/= ver = lit pkg <> strLit " != " <> version ver -- | Dependency on a package with version greater or less then specified -- value. -- -- >>> "alpha" ~< [7, 3] -- alpha < 7.3 (~<) :: Strict.Text -> [Word] -> PkgTemplate pkg ~< ver = lit pkg <> strLit " < " <> version ver -- | Dependency on a package with version greater then specified value. -- -- >>> "sqlite" ~> [3, 8] -- sqlite3 > 3.8 (~>) :: Strict.Text -> [Word] -> PkgTemplate pkg ~> ver = lit pkg <> strLit " > " <> version ver -- | Dependency on a package with version greater or less or equal then -- specified value. (~<=) :: Strict.Text -> [Word] -> PkgTemplate pkg ~<= ver = lit pkg <> strLit " <= " <> version ver -- | Dependency on a package with version greater or equal then specified -- value. (~>=) :: Strict.Text -> [Word] -> PkgTemplate pkg ~>= ver = lit pkg <> strLit " >= " <> version ver -- }}} Version Combinators ---------------------------------------------------- -- {{{ Specialized Folds for Template ----------------------------------------- -- | Put specified text between templates. -- -- Following properties hold: -- -- @ -- forall s. 'separatedBy' s [] === 'mempty' -- forall s t. 'separatedBy' s [t] === t -- @ -- -- Example: -- -- >>> separatedBy ", " ["foo", "bar", "baz"] -- foo, bar, baz separatedBy :: Strict.Text -> [PkgTemplate] -> PkgTemplate separatedBy _ [] = mempty separatedBy _ (x : []) = x separatedBy s (x : xs) = x <> lit s <> separatedBy s xs -- | Concatenate templates by inserting coma (\',\') in between. -- -- >>> list ["foo" .= [1,2,3], "bar" .> [0], "bar" .< [3,1]] -- foo = 1.2.3, bar > 0, bar < 3.1 -- -- Following properties hold: -- -- @ -- 'list' [] === 'mempty' -- forall t. 'list' [t] === t -- @ list :: [PkgTemplate] -> PkgTemplate list = separatedBy $ Strict.Text.pack ", " -- | Concatenate templates by inserting space (\' \') in between. -- -- >>> options ["-I" <> var "prefix" </> "lib", "-I" <> var "extra"] -- -I${prefix}/lib -I${extra} -- -- Following properties hold: -- -- @ -- 'options' [] === 'mempty' -- forall t. 'options' [t] === t -- @ options :: [PkgTemplate] -> PkgTemplate options = separatedBy $ Strict.Text.singleton ' ' -- }}} Specialized Folds for Template ----------------------------------------- -- {{{ Options Combinators ---------------------------------------------------- -- | Create template starting with option followed by its argument. Argument -- is quoted using 'quote' function to prevent problems with spaces in -- directory names. -- -- >>> option "--foo=" $ var "prefix" </> "some dir" -- --foo="${prefix}/some dir" -- -- Following property holds: -- -- @ -- forall t. option \"\" t === 'quote' t -- @ option :: Strict.Text -> PkgTemplate -> PkgTemplate option opt = (lit opt <>) . quote -- | Same as 'option', but takes 'String' instead of strict 'Strict.Text'. strOption :: String -> PkgTemplate -> PkgTemplate strOption = option . Strict.Text.pack -- | Take list of templates and make compiler include options. Template for -- include directory is wrapped in quotes (see 'quote' and 'option' functions). -- -- >>> let d = var "prefix" </> "include" in includes [d, d </> var "arch"] -- -I"${prefix}/include" -I"${prefix}/include/${arch}" -- >>> includes [var "prefix" </> "some dir"] -- -I"${prefix}/some dir" includes :: [PkgTemplate] -> PkgTemplate includes = options . List.map (strOption "-I") -- | Take list of templates and make compiler library options. -- -- >>> libraries ["m", "rt", "foo"] -- -lm -lrt -lfoo libraries :: [PkgTemplate] -> PkgTemplate libraries = options . List.map (strLit "-l" <>) -- | Take list of templates and make compiler library path options. Template for -- include directory is wrapped in quotes (see 'quote' and 'option' functions). -- -- >>> let l = var "prefix" </> lit "lib" in libraryPath [l, l </> var "arch"] -- -L"${prefix}/lib" -L"${prefix}/lib/${arch}" libraryPath :: [PkgTemplate] -> PkgTemplate libraryPath = options . List.map (strOption "-L") -- }}} Options Combinators ---------------------------------------------------- -- }}} PkgTemplate Combinators ------------------------------------------------ -- {{{ I/O -------------------------------------------------------------------- -- | Serialize 'PkgConfig' in to strict 'Strict.Text' and write it in to a -- specified file. writePkgConfig :: FilePath -> PkgConfig -> IO () writePkgConfig file = Strict.Text.writeFile file . toStrictText -- }}} I/O -------------------------------------------------------------------- -- $usage -- -- Following code is able to generate @foo.pc@, a /pkg-config/ configuration -- file for library named @foo@: -- -- @ -- {-\# LANGUAGE OverloadedStrings \#-} -- module Main (main) -- where -- -- import Data.String ('IsString') -- -- import Data.Default.Class ('Default'('def')) -- -- From data-default-class library: -- -- <http://hackage.haskell.org/package/data-default-class> -- -- import Control.Lens -- -- From lens library: -- -- <http://hackage.haskell.org/package/lens> -- -- import Data.PkgConfig -- -- -- libraryBaseName :: 'IsString' a => a -- libraryBaseName = \"foo\" -- -- main :: IO () -- main = 'writePkgConfig' (libraryBaseName '++' \".pc\") libPkgConfig -- where -- libPkgConfig = 'def' -- & 'pkgVariables' .~ -- [ (\"prefix\", \"\/usr\/local\" ) -- , (\"includedir\", 'var' \"prefix\" '</>' \"include\") -- , (\"libdir\", 'var' \"prefix\" '</>' \"lib\" ) -- , (\"arch\", \"i386\" ) -- ] -- & 'pkgName' .~ libraryBaseName -- & 'pkgDescription' .~ \"Example pkg-config.\" -- & 'pkgVersion' .~ 'version' [1, 2, 3] -- & 'pkgCflags' .~ 'includes' ['var' \"includedir\"] -- & 'pkgRequires' .~ 'list' -- [ \"bar\" '~>' [0], \"bar\" '~<=' [3, 1] -- , \"baz\" '~=' [1, 2, 3] -- ] -- & 'pkgLibs' .~ 'options' -- [ 'libraryPath' ['var' \"libdir\", 'var' \"libdir\" '</>' 'var' \"arch\"] -- , 'libraries' [libraryBaseName] -- ] -- @ -- -- Content of generated @foo.pc@: -- -- > prefix=/usr/local -- > includedir=${prefix}/include -- > libdir=${prefix}/lib -- > arch=i386 -- > -- > Name: foo -- > Description: Example pkg-config. -- > Version: 1.2.3 -- > Requires: bar > 0, bar <= 3.1, baz = 1.2.3 -- > Cflags: -I"${includedir}" -- > Libs: -L"${libdir}" -L"${libdir}/${arch}" -lfoo -- -- Note that functions '&' and '.~', used in the example, are from -- <http://hackage.haskell.org/package/lens lens> library. Please consult its -- documentation for details. -- $pkgConfig -- -- Data type that describes whole /pkg-config/ configuration file for one -- specific library. It also tries to preserve as much of /pkg-config/ -- philosophy as possible. -- -- Lenses are used for accessing individual fields of 'PkgConfig' data type. -- Example: -- -- @ -- 'def' & 'pkgVariables' .~ [(\"prefix\", \"\/usr\/local\")] -- & 'pkgName' .~ \"some library\" -- -- ... -- & 'pkgLibs' .~ 'includes' -- [ 'var' \"prefix\" '</>' \"include\" '</>' \"foo\" -- ] -- @ -- $pkgTemplate -- -- The /pkg-config/ tool allows variable declaration so that they can later be -- used in other parts of its configuration file. To give Haskell programmer -- the same power, this library provides 'PkgTemplate'. One can think of it as -- a string with named holes, i.e. places where variables will be expanded. -- Following is example of how two variables, namely @prefix@ and @includedir@, -- can be defined inside /pkg-config/ configuration file: -- -- > prefix=/usr/local -- > includedir=${prefix}/include -- -- 'PkgConfig' has a field 'pkgVariables' that is used to define variables and -- above example can be translated in to: -- -- @ -- 'def' & 'pkgVariables' .~ -- [ (\"prefix\", \"\/usr\/local\") -- , (\"includedir\", 'var' \"prefix\" '</>' \"include\") -- ] -- @ -- -- Lot of similar properties of 'String' hold for 'Template' as well. -- Including the fact that 'Template' is monoid and therefore can be -- concatenated using monoid operations: -- -- >>> strLit "foo" <> strLit "bar" -- foobar -- -- Since 'Template' has 'IsString' instance, then, if @OverloadedStrings@ -- language extension is enabled, it is possible to simplify above example in -- to: -- -- >>> "foo" <> "bar" :: PkgTemplate -- foobar -- -- For consistency instance for 'Default' type class is also provided and it -- holds following property: -- -- @ -- 'def' === 'mempty' -- @ -- -- Additionally following properties hold: -- -- @ -- 'lit' \"\" === 'mempty' -- 'var' \"\" =/= 'mempty' -- @