    -----------------------------------------------------------------------------
    -- |
    -- 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.
    --
    module Distribution.Simple.Build.Macros (
        generate,
        generatePackageVersionMacros,
      ) where

    import Data.Maybe
             ( isJust )
    import Distribution.Package
             ( PackageIdentifier(PackageIdentifier) )
    import Distribution.Version
             ( Version(versionBranch) )
    import Distribution.PackageDescription
             ( PackageDescription )
    import Distribution.Simple.Compiler
             ( packageKeySupported )
    import Distribution.Simple.LocalBuildInfo
             ( LocalBuildInfo(compiler, pkgKey, withPrograms), externalPackageDeps )
    import Distribution.Simple.Program.Db
             ( configuredPrograms )
    import Distribution.Simple.Program.Types
             ( ConfiguredProgram(programId, programVersion) )
    import Distribution.Text
             ( display )

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

    -- | The contents of the @cabal_macros.h@ for the given configured package.
    --
    generate :: PackageDescription -> LocalBuildInfo -> String
    generate _pkg_descr lbi =
      "/* DO NOT EDIT: This file is automatically generated by Cabal */\n\n" ++
      generatePackageVersionMacros (map snd (externalPackageDeps lbi)) ++
      generateToolVersionMacros (configuredPrograms . withPrograms $ lbi) ++
      generatePackageKeyMacro lbi

    -- | 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 :: [PackageIdentifier] -> String
    generatePackageVersionMacros pkgids = concat
      [ "/* package " ++ display pkgid ++ " */\n"
      ++ generateMacros "" pkgname version
      | pkgid@(PackageIdentifier name version) <- pkgids
      , let pkgname = map fixchar (display 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 progs = concat
      [ "/* tool " ++ progid ++ " */\n"
      ++ generateMacros "TOOL_" progname version
      | prog <- progs
      , isJust . programVersion $ prog
      , let progid       = programId prog ++ "-" ++ display version
            progname     = map fixchar (programId prog)
            Just version = programVersion prog
      ]

    -- | Common implementation of 'generatePackageVersionMacros' and
    -- 'generateToolVersionMacros'.
    --
    generateMacros :: String -> String -> Version -> String
    generateMacros prefix name version =
      concat
      ["#define ", prefix, "VERSION_",name," ",show (display version),"\n"
      ,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"
      ,"  (major1) <  ",major1," || \\\n"
      ,"  (major1) == ",major1," && (major2) <  ",major2," || \\\n"
      ,"  (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")"
      ,"\n\n"
      ]
      where
        (major1:major2:minor:_) = map show (versionBranch version ++ repeat 0)

    -- | Generate the @CURRENT_PACKAGE_KEY@ definition for the package key
    --   of the current package, if supported by the compiler
    generatePackageKeyMacro :: LocalBuildInfo -> String
    generatePackageKeyMacro lbi
      | packageKeySupported (compiler lbi) =
          "#define CURRENT_PACKAGE_KEY \"" ++ display (pkgKey lbi) ++ "\"\n\n"
      | otherwise = ""

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