-- Copyright 2012-2013 Samplecount S.L. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. {-| Description: Query build flags with @pkg-config@ This module provides utilities for querying 'BuildFlags' from the database, which is available on many Unix like operating systems. -} module Development.Shake.Language.C.PkgConfig ( Options(..) , defaultOptions , pkgConfig , fromConfig ) where import Control.Applicative import Data.Char (toLower) import Data.Default.Class (Default(..)) import Data.List (intercalate, isPrefixOf) import Development.Shake import Development.Shake.FilePath import Development.Shake.Language.C ( BuildFlags , compilerFlags , libraries , libraryPath , linkerFlags , systemIncludes , userIncludes ) import Development.Shake.Language.C.Label (append) import Development.Shake.Language.C.Util (words') -- ==================================================================== -- PkgConfig -- TODO: -- * Use parsec or attoparsec for more robust parser -- * Parse preprocessor defines -- * Parse framework path (-F) and -framework flags parseCflags :: [String] -> (BuildFlags -> BuildFlags) parseCflags [] = id parseCflags (x:xs) | isPrefixOf "-I" x = parseCflags xs . append systemIncludes [drop 2 x] | isPrefixOf "-i" x = parseCflags xs . append userIncludes [drop 2 x] | otherwise = append compilerFlags [(Nothing, [x])] parseLibs :: [String] -> (BuildFlags -> BuildFlags) parseLibs [] = id parseLibs (x:xs) | isPrefixOf "-l" x = parseLibs xs . append libraries [drop 2 x] | isPrefixOf "-L" x = parseLibs xs . append libraryPath [drop 2 x] | otherwise = parseLibs xs . append linkerFlags [x] parseFlags :: String -> [String] parseFlags = words' . head . lines -- | PkgConfig options. data Options = Options { searchPath :: Maybe [FilePath] -- ^ List of directories where @.pc@ files are searched, corresponding to the @PKG_CONFIG_PATH@ environment variable , static :: Bool -- ^ Return flags appropriate for static linking } deriving (Eq, Show) -- | Default @pkg-config@ options. -- -- This function is an alias for `def`. defaultOptions :: Options defaultOptions = Options{ searchPath = Nothing , static = False } instance Default Options where def = defaultOptions -- | Call @pkg-config@ with options and a package name and return a 'BuildFlags' modification function. -- -- The @pkg-config@ executable must be installed on the build host. pkgConfig :: Options -> String -> Action (BuildFlags -> BuildFlags) pkgConfig options pkg = do env <- case searchPath options of Nothing -> return [] Just path -> do env <- addEnv [("PKG_CONFIG_PATH", intercalate [searchPathSeparator] path)] return [env] let flags = if static options then ["--static"] else [] pkgconfig which = command ([Traced ""] ++ env) "pkg-config" (flags ++ ["--" ++ which, pkg]) Stdout cflags <- pkgconfig "cflags" Stdout libs <- pkgconfig "libs" return ( parseCflags (parseFlags cflags) . parseLibs (parseFlags libs) ) -- | Given an initial 'Options' record and a configuration variable lookup function, call @pkg-config@ based on configuration variable settings and return a 'BuildFlags' modification function. -- -- The following configuration variables are recognised: -- -- [@PkgConfig.packages@] List of package names for which build flags should be queried -- [@PkgConfig.options.searchPath@] Space-separated list of file paths, corresponds to the `searchPath` option -- [@PkgConfig.options.static@] @true@ or @false@, corresponds to the `static` option fromConfig :: Options -> (String -> Action (Maybe String)) -> Action (BuildFlags -> BuildFlags) fromConfig initialOptions cfg = do config_searchPath <- fmap words' <$> cfg "PkgConfig.options.searchPath" config_static <- fmap (bool . words) <$> cfg "PkgConfig.options.static" config_packages <- fmap words <$> cfg "PkgConfig.packages" let options = initialOptions { searchPath = maybe (searchPath initialOptions) Just config_searchPath, static = maybe (static initialOptions) id config_static } flags <- mapM (pkgConfig options) (maybe [] id config_packages) return $ foldl (.) id $ flags where bool (x:_) = map toLower x == "true" bool _ = False