-----------------------------------------------------------------------------
-- |
-- Module      :  Distribution.Simple.Build.Macros
-- Copyright   :  Simon Marlow 2008
--
-- Maintainer  :  cabal-devel@haskell.org
-- Portability :  portable
--
-- Generate cabal_macros.h - CPP macros for package version testing
--
-- When using CPP you get
--
-- > VERSION_<package>
-- > MIN_VERSION_<package>(A,B,C)
--
-- for each /package/ in @build-depends@, which is true if the version of
-- /package/ in use is @>= A.B.C@, using the normal ordering on version
-- numbers.
--
-- TODO Figure out what to do about backpack and internal libraries. It is very
-- suspecious that this stuff works with munged package identifiers
module Distribution.Simple.Build.Macros (
    generateCabalMacrosHeader,
    generatePackageVersionMacros,
  ) where

import Prelude ()
import Distribution.Compat.Prelude

import Distribution.Version
import Distribution.PackageDescription
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Program.Db
import Distribution.Simple.Program.Types
import Distribution.Types.MungedPackageId
import Distribution.Types.MungedPackageName
import Distribution.Types.PackageId
import Distribution.Pretty

-- ------------------------------------------------------------
-- * Generate cabal_macros.h
-- ------------------------------------------------------------

-- Invariant: HeaderLines always has a trailing newline
type HeaderLines = String

line :: String -> HeaderLines
line :: String -> String
line String
str = String
str String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\n"

ifndef :: String -> HeaderLines -> HeaderLines
ifndef :: String -> String -> String
ifndef String
macro String
body =
    String -> String
line (String
"#ifndef " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
macro) String -> String -> String
forall a. [a] -> [a] -> [a]
++
    String
body String -> String -> String
forall a. [a] -> [a] -> [a]
++
    String -> String
line (String
"#endif /* " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
macro String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" */")

define :: String -> Maybe [String] -> String -> HeaderLines
define :: String -> Maybe [String] -> String -> String
define String
macro Maybe [String]
params String
val =
    String -> String
line (String
"#define " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
macro String -> String -> String
forall a. [a] -> [a] -> [a]
++ Maybe [String] -> String
f Maybe [String]
params String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
val)
  where
    f :: Maybe [String] -> String
f Maybe [String]
Nothing = String
""
    f (Just [String]
xs) = String
"(" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"," [String]
xs String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
")"

defineStr :: String -> String -> HeaderLines
defineStr :: String -> String -> String
defineStr String
macro String
str = String -> Maybe [String] -> String -> String
define String
macro Maybe [String]
forall a. Maybe a
Nothing (String -> String
forall a. Show a => a -> String
show String
str)

ifndefDefine :: String -> Maybe [String] -> String -> HeaderLines
ifndefDefine :: String -> Maybe [String] -> String -> String
ifndefDefine String
macro Maybe [String]
params String
str =
    String -> String -> String
ifndef String
macro (String -> Maybe [String] -> String -> String
define String
macro Maybe [String]
params String
str)

ifndefDefineStr :: String -> String -> HeaderLines
ifndefDefineStr :: String -> String -> String
ifndefDefineStr String
macro String
str =
    String -> String -> String
ifndef String
macro (String -> String -> String
defineStr String
macro String
str)

-- | The contents of the @cabal_macros.h@ for the given configured package.
--
generateCabalMacrosHeader :: PackageDescription -> LocalBuildInfo -> ComponentLocalBuildInfo -> String
generateCabalMacrosHeader :: PackageDescription
-> LocalBuildInfo -> ComponentLocalBuildInfo -> String
generateCabalMacrosHeader PackageDescription
pkg_descr LocalBuildInfo
lbi ComponentLocalBuildInfo
clbi =
  String
"/* DO NOT EDIT: This file is automatically generated by Cabal */\n\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++
  [PackageId] -> String
generatePackageVersionMacros
    (PackageDescription -> PackageId
package PackageDescription
pkg_descr PackageId -> [PackageId] -> [PackageId]
forall a. a -> [a] -> [a]
: ((UnitId, MungedPackageId) -> PackageId)
-> [(UnitId, MungedPackageId)] -> [PackageId]
forall a b. (a -> b) -> [a] -> [b]
map (UnitId, MungedPackageId) -> PackageId
forall a. (a, MungedPackageId) -> PackageId
getPid (ComponentLocalBuildInfo -> [(UnitId, MungedPackageId)]
componentPackageDeps ComponentLocalBuildInfo
clbi)) String -> String -> String
forall a. [a] -> [a] -> [a]
++
  [ConfiguredProgram] -> String
generateToolVersionMacros (ProgramDb -> [ConfiguredProgram]
configuredPrograms (ProgramDb -> [ConfiguredProgram])
-> (LocalBuildInfo -> ProgramDb)
-> LocalBuildInfo
-> [ConfiguredProgram]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LocalBuildInfo -> ProgramDb
withPrograms (LocalBuildInfo -> [ConfiguredProgram])
-> LocalBuildInfo -> [ConfiguredProgram]
forall a b. (a -> b) -> a -> b
$ LocalBuildInfo
lbi) String -> String -> String
forall a. [a] -> [a] -> [a]
++
  LocalBuildInfo -> ComponentLocalBuildInfo -> String
generateComponentIdMacro LocalBuildInfo
lbi ComponentLocalBuildInfo
clbi String -> String -> String
forall a. [a] -> [a] -> [a]
++
  PackageDescription -> String
generateCurrentPackageVersion PackageDescription
pkg_descr
 where
  getPid :: (a, MungedPackageId) -> PackageId
getPid (a
_, MungedPackageId (MungedPackageName PackageName
pn LibraryName
_) Version
v) =
    -- NB: Drop the library name! We're just reporting package versions.
    -- This would have to be revisited if you are allowed to depend
    -- on different versions of the same package
    PackageName -> Version -> PackageId
PackageIdentifier PackageName
pn Version
v

-- | Helper function that generates just the @VERSION_pkg@ and @MIN_VERSION_pkg@
-- macros for a list of package ids (usually used with the specific deps of
-- a configured package).
--
generatePackageVersionMacros :: [PackageId] -> String
generatePackageVersionMacros :: [PackageId] -> String
generatePackageVersionMacros [PackageId]
pkgids = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
  [ String -> String
line (String
"/* package " String -> String -> String
forall a. [a] -> [a] -> [a]
++ PackageId -> String
forall a. Pretty a => a -> String
prettyShow PackageId
pkgid String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" */")
  String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String -> Version -> String
generateMacros String
"" String
pkgname Version
version
  | pkgid :: PackageId
pkgid@(PackageIdentifier PackageName
name Version
version) <- [PackageId]
pkgids
  , let pkgname :: String
pkgname = (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
fixchar (PackageName -> String
forall a. Pretty a => a -> String
prettyShow PackageName
name)
  ]

-- | Helper function that generates just the @TOOL_VERSION_pkg@ and
-- @MIN_TOOL_VERSION_pkg@ macros for a list of configured programs.
--
generateToolVersionMacros :: [ConfiguredProgram] -> String
generateToolVersionMacros :: [ConfiguredProgram] -> String
generateToolVersionMacros [ConfiguredProgram]
progs = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
  [ String -> String
line (String
"/* tool " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
progid String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" */")
  String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String -> Version -> String
generateMacros String
"TOOL_" String
progname Version
version
  | ConfiguredProgram
prog <- [ConfiguredProgram]
progs
  , Maybe Version -> Bool
forall a. Maybe a -> Bool
isJust (Maybe Version -> Bool)
-> (ConfiguredProgram -> Maybe Version)
-> ConfiguredProgram
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ConfiguredProgram -> Maybe Version
programVersion (ConfiguredProgram -> Bool) -> ConfiguredProgram -> Bool
forall a b. (a -> b) -> a -> b
$ ConfiguredProgram
prog
  , let progid :: String
progid       = ConfiguredProgram -> String
programId ConfiguredProgram
prog String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"-" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Version -> String
forall a. Pretty a => a -> String
prettyShow Version
version
        progname :: String
progname     = (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
fixchar (ConfiguredProgram -> String
programId ConfiguredProgram
prog)
        Just Version
version = ConfiguredProgram -> Maybe Version
programVersion ConfiguredProgram
prog
  ]

-- | Common implementation of 'generatePackageVersionMacros' and
-- 'generateToolVersionMacros'.
--
generateMacros :: String -> String -> Version -> String
generateMacros :: String -> String -> Version -> String
generateMacros String
macro_prefix String
name Version
version =
  [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
  [String -> String -> String
ifndefDefineStr (String
macro_prefix String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"VERSION_" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name) (Version -> String
forall a. Pretty a => a -> String
prettyShow Version
version)
  ,String -> Maybe [String] -> String -> String
ifndefDefine (String
"MIN_" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
macro_prefix String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"VERSION_" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name)
                ([String] -> Maybe [String]
forall a. a -> Maybe a
Just [String
"major1",String
"major2",String
"minor"])
    (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [
       String
"(\\\n"
      ,String
"  (major1) <  ",String
major1,String
" || \\\n"
      ,String
"  (major1) == ",String
major1,String
" && (major2) <  ",String
major2,String
" || \\\n"
      ,String
"  (major1) == ",String
major1,String
" && (major2) == ",String
major2,String
" && (minor) <= ",String
minor,String
")"
    ]
  ,String
"\n"]
  where
    (String
major1:String
major2:String
minor:[String]
_) = (Int -> String) -> [Int] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Int -> String
forall a. Show a => a -> String
show (Version -> [Int]
versionNumbers Version
version [Int] -> [Int] -> [Int]
forall a. [a] -> [a] -> [a]
++ Int -> [Int]
forall a. a -> [a]
repeat Int
0)

-- | Generate the @CURRENT_COMPONENT_ID@ definition for the component ID
--   of the current package.
generateComponentIdMacro :: LocalBuildInfo -> ComponentLocalBuildInfo -> String
generateComponentIdMacro :: LocalBuildInfo -> ComponentLocalBuildInfo -> String
generateComponentIdMacro LocalBuildInfo
_lbi ComponentLocalBuildInfo
clbi =
  [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$
      [case ComponentLocalBuildInfo
clbi of
        LibComponentLocalBuildInfo{} ->
          String -> String -> String
ifndefDefineStr String
"CURRENT_PACKAGE_KEY" (ComponentLocalBuildInfo -> String
componentCompatPackageKey ComponentLocalBuildInfo
clbi)
        ComponentLocalBuildInfo
_ -> String
""
      ,String -> String -> String
ifndefDefineStr String
"CURRENT_COMPONENT_ID" (ComponentId -> String
forall a. Pretty a => a -> String
prettyShow (ComponentLocalBuildInfo -> ComponentId
componentComponentId ComponentLocalBuildInfo
clbi))
      ]

-- | Generate the @CURRENT_PACKAGE_VERSION@ definition for the declared version
--   of the current package.
generateCurrentPackageVersion :: PackageDescription -> String
generateCurrentPackageVersion :: PackageDescription -> String
generateCurrentPackageVersion PackageDescription
pd =
  String -> String -> String
ifndefDefineStr String
"CURRENT_PACKAGE_VERSION" (Version -> String
forall a. Pretty a => a -> String
prettyShow (PackageId -> Version
pkgVersion (PackageDescription -> PackageId
package PackageDescription
pd)))

fixchar :: Char -> Char
fixchar :: Char -> Char
fixchar Char
'-' = Char
'_'
fixchar Char
c   = Char
c