packages feed

Omega-1.0.3: DoSetup.hs

{- Choose feature sets based on Cabal version.  Some parts of the API changed
   across versions.

OLD_GHC_OPTIONS: Old way of building GHC command lines
NEW_GHC_OPTIONS: New way of building GHC command lines

OLD_LBI_COMPONENTS: Old way of listing components in LocalBuildInfo
NEW_LBI_COMPONENTS: New way of listing components in LocalBuildInfo

LIBRARY_NAME_IS_PACKAGE_ID: Use a PackageIdentifier as a library's name
LIBRARY_NAME_IS_LIBRARY_NAME: Use a LibraryName as a library's name
-}
#if CABAL_MAJOR == 1
# if CABAL_MINOR <= 14
#  define OLD_GHC_OPTIONS
#  define OLD_LBI_COMPONENTS
#  define LIBRARY_NAME_IS_PACKAGE_ID
# elif CABAL_MINOR <= 16
#  define NEW_GHC_OPTIONS
#  define OLD_LBI_COMPONENTS
#  define LIBRARY_NAME_IS_PACKAGE_ID
# else
#  if CABAL_MINOR > 18
#   warning "Building with an unrecognized version of Cabal"
#  endif
#  define NEW_GHC_OPTIONS
#  define NEW_LBI_COMPONENTS
#  define LIBRARY_NAME_IS_LIBRARY_NAME
# endif
#else
# error "Unsupported Cabal version"
#endif
    

import Control.Applicative
import Control.Exception(IOException, catch, bracket)
import Control.Monad
import Data.Char
import Data.List hiding(intercalate)
import Data.Maybe
import Distribution.PackageDescription
import Distribution.Simple
import Distribution.Simple.Build
import Distribution.Simple.BuildPaths
import Distribution.Simple.GHC
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.PreProcess
import Distribution.Simple.Program
#ifdef NEW_GHC_OPTIONS
import Distribution.Simple.Program.GHC
#endif
import Distribution.Simple.Setup
import Distribution.Simple.Utils
import qualified Distribution.Verbosity as Verbosity
import System.Cmd
import System.Directory
import System.Exit(ExitCode(..))
import System.IO
import System.FilePath((</>), (<.>), takeExtension, takeDirectory)
import System.Process

-- Recover from IO exceptions
recover :: IO a -> IO a -> IO a
f `recover` h = f `Control.Exception.catch` handler
  where
    handler e = let _ = e :: IOException
                in h

-------------------------------------------------------------------------------
-- Filenames and constants

-- Record whether we're building the Omega library here
useInstalledOmegaFlagPath = "build" </> "UseInstalledOmega"

-- We will call 'make'
makeProgram = simpleProgram "make"

-- Our single C++ source file and corresponding object file are here
cppSourceName = "src" </> "C_omega.cc"
cppObjectName = "build" </> "C_omega.o"

-- If we're building the Omega library, it's here
omegaLibPath = "src" </> "the-omega-project" </> "omega_lib" </> "obj" </> "libomega.a"

-- Unpack the Omega library into this directory
omegaUnpackPath = "build" </> "unpack_omega"

-- Path where Cabal builds files
cabalBuildPath = "dist" </> "build"

-- Main test file
testSourceName = "test" </> "runtests.hs"

-- Extra files produced by configuration
configFiles = ["configure", "config.log", "config.status", "Makefile",
               useInstalledOmegaFlagPath]

-------------------------------------------------------------------------------
-- Helpful IO procedures

noGHCiLib =
    die $ "Sorry, this package does not support GHCi.\n" ++
          "Please configure with --disable-library-for-ghci to disable."

noSharedLib =
    die $ "Sorry, this package does not support shared library output.\n" ++
          "Please configure with --disable-shared to disable."

writeUseInstalledOmegaFlag :: Bool -> IO ()
writeUseInstalledOmegaFlag b = do
  createDirectoryIfMissing False "build"
  writeFile useInstalledOmegaFlagPath (show b)

readUseInstalledOmegaFlag :: IO Bool
readUseInstalledOmegaFlag = do
  text <- readFile useInstalledOmegaFlagPath `recover`
          die "Configuration file missing; try reconfiguring"
  return $! read text

-- Run a command if f1 is newer than f2, or if f2 does not exist
ifNewer :: FilePath -> FilePath -> IO () -> IO ()
ifNewer src tgt m = do
  tgt_exists <- doesFileExist tgt
  if not tgt_exists
    then m                      -- Run because target doesn't exist 
    else do
      src_time <- getModificationTime src
      tgt_time <- getModificationTime tgt
      if src_time > tgt_time
        then m                  -- Run because source is newer 
        else return ()

-- Attempt to remove a file, ignoring errors
lenientRemoveFile f = removeFile f `recover` return ()

lenientRemoveFiles = mapM_ lenientRemoveFile

-- Attempt to remove a directory and its contents
-- (one level of recursion only), ignoring errors
lenientRemoveDirectory f = do
  b <- doesDirectoryExist f
  when b $ do lenientRemoveFiles . map (f </>) =<< getDirectoryContents f
              removeDirectory f `recover` return ()

-------------------------------------------------------------------------------
-- Source distribution

-- Configure programs
runAutoconf verbosity = ifNewer "configure.ac" "configure" $ do
  rawSystemExit verbosity "autoconf" []

sDistOmega originalHook pkgDesc lbi hooks flags = do
  runAutoconf verb              -- Ensure that 'configure' is created
  originalHook pkgDesc lbi hooks flags
  where
    verb = fromFlagOrDefault Verbosity.normal $ sDistVerbosity flags

-------------------------------------------------------------------------------
-- Configuration

configureOmega pkgDesc originalFlags = do
  -- Disable unsupported configuratoins
  when (flagToMaybe (configGHCiLib originalFlags) == Just True) $
       notice verbosity $ "** Sorry, this package does not support GHCi.\n" ++
                          "** Disabling GHCi library output."

  when (flagToMaybe (configSharedLib originalFlags) == Just True) $
       notice verbosity $ "** Sorry, this package does not support " ++
                             "shared library output.\n" ++
                          "** Disabling shared library output."

  -- Run Cabal configuration
  lbi <- confHook simpleUserHooks pkgDesc flags

  -- Run autoconf configuration
  runAutoconf verbosity
  runConfigure lbi

  -- Save this flag for later use
  writeUseInstalledOmegaFlag useInstalledOmega

  return lbi
    where
      verbosity = fromFlagOrDefault Verbosity.normal $ configVerbosity flags
      flags = originalFlags { configSharedLib = toFlag False
                            , configGHCiLib = toFlag False
                            }

      -- Will build the Omega library?
      useInstalledOmega = fromMaybe False $
                          lookup (FlagName "useinstalledomega") $
                          configConfigurationsFlags flags

      -- Run 'configure' with the extra arguments that were passed to
      -- Setup.hs
      runConfigure lbi = do
        currentDir <- getCurrentDirectory

        let opts = autoConfigureOptions lbi useInstalledOmega
            configProgramName = currentDir </> "configure"

        rawSystemExit verbosity configProgramName opts

-- Configuration: extract options to pass to 'configure'
autoConfigureOptions :: LocalBuildInfo -> Bool -> [String]
autoConfigureOptions localBuildInfo useInstalledOmega =
    withOmega ++ [libdirs, includedirs]
    where
      withOmega = if useInstalledOmega
                  then ["--with-omega"]
                  else []

      libraryDescr = case library $ localPkgDescr localBuildInfo
                     of Nothing -> error "Library description is missing"
                        Just l -> l

      buildinfo = libBuildInfo libraryDescr

      -- Create a string "-L/usr/foo -L/usr/bar"
      ldflagsString =
          intercalate " " ["-L" ++ dir | dir <- extraLibDirs buildinfo]

      libdirs = "LDFLAGS=" ++ ldflagsString

      -- Create a string "-I/usr/foo -I/usr/bar"
      cppflagsString =
          intercalate " " ["-I" ++ dir | dir <- includeDirs buildinfo]

      includedirs = "CPPFLAGS=" ++ cppflagsString

-------------------------------------------------------------------------------
-- Building

buildOmega pkgDesc lbi userhooks flags = do

  let verb = fromFlagOrDefault Verbosity.normal $ buildVerbosity flags
  useInstalledOmega <- readUseInstalledOmegaFlag

  -- Build the C++ source file (and Omega library, if configured)
  -- Makefile's behavior is controlled by output of 'configure'
  runDbProgram verb makeProgram (withPrograms lbi) ["all"]

  -- Custom build procedure for test suite
  buildTestSuites useInstalledOmega pkgDesc lbi flags

  -- Default build procedure for hs files in library
  -- Tests are already built, so they won't be built again
  buildHook simpleUserHooks pkgDesc (hideTestComponents lbi) userhooks flags

  -- Get 'ar' and 'ld' programs
  let runAr = runDbProgram verb arProgram (withPrograms lbi)

  let -- Add extra files into an archive file
      addStaticObjectFiles libName = do
          -- Add the C++ interface file
          addStaticObjectFile cppObjectName libName

          -- Add contents of libomega.a
          unless useInstalledOmega $
              transferArFiles verb runAr omegaLibPath libName

          where
            addStaticObjectFile objName libName =
                runAr ["r", libName, objName]

  -- Add other object files to libraries
  libName <- getLibraryName pkgDesc lbi
  
  when (withVanillaLib lbi) $
       let libPath = buildDir lbi </> mkLibName libName
       in addStaticObjectFiles libPath

  when (withProfLib lbi) $
       let libPath = buildDir lbi </> mkProfLibName libName
       in addStaticObjectFiles libPath

  when (withGHCiLib lbi) noGHCiLib
  when (withSharedLib lbi) noSharedLib

  return ()

-- | Get the library name from processing the package description file
#if defined(LIBRARY_NAME_IS_PACKAGE_ID)
getLibraryName :: PackageDescription -> LocalBuildInfo -> IO PackageIdentifier
getLibraryName pkgDesc lbi = return $ package $ localPkgDescr lbi

#elif defined(LIBRARY_NAME_IS_LIBRARY_NAME)
getLibraryName :: PackageDescription -> LocalBuildInfo -> IO LibraryName
getLibraryName pkgDesc lbi =
  case find (\(component_name, _, _) -> component_name == CLibName) $
       componentsConfigs lbi
  of Just (_, clbi, _) ->
       case componentLibraries clbi
       of [nm] -> return nm
          _ -> die "Missing library name"
#else
#error
#endif

-- | Hide the test suite so Cabal doesn't try to use its default build 
--   procedure with it.
hideTestComponents :: LocalBuildInfo -> LocalBuildInfo

hideTestComponents lbi =
#if defined(OLD_LBI_COMPONENTS)
  lbi {compBuildOrder = filter (not . isTestComponent) $ compBuildOrder lbi
      , testSuiteConfigs = []}
#elif defined(NEW_LBI_COMPONENTS)
  lbi {componentsConfigs = mapMaybe removeTests $ componentsConfigs lbi}
  where
    removeTests (cname, clbi, deps)
      | isTestComponent cname = Nothing
      | otherwise = Just (cname, clbi, filter (not . isTestComponent) deps)
#else
#error
#endif

isTestComponent :: ComponentName -> Bool
isTestComponent (CTestName {}) = True
isTestComponent _              = False

-- | Get command line options for invoking GHC
genericGhcOptions :: Version -> Verbosity.Verbosity -> LocalBuildInfo
                  -> BuildInfo -> ComponentLocalBuildInfo -> FilePath
                  -> [String]

genericGhcOptions ver verb lbi bi clbi build_path =
#if defined(OLD_GHC_OPTIONS)
  ghcOptions lbi bi clbi cabalBuildPath
#elif defined(NEW_GHC_OPTIONS)
  renderGhcOptions ver $ componentGhcOptions verb lbi bi clbi cabalBuildPath
#else
#error
#endif

buildTestSuites useInstalledOmega pkgDesc lbi flags =
  withTestLBI pkgDesc lbi $ \test clbi -> do
    let verb = fromFlagOrDefault Verbosity.normal $ buildVerbosity flags

    -- Run preprocessors
    writeAutogenFiles verb pkgDesc lbi
    preprocessComponent pkgDesc (CTest test) lbi False verb knownSuffixHandlers

    -- Run compiler
    (ghcProg, ghcVersion) <- configureGHC verb lbi

    let bi = testBuildInfo test
        opts = genericGhcOptions ghcVersion verb lbi bi clbi cabalBuildPath

        build_opts = ["--make", "-o",
                      cabalBuildPath </> testName test </> testName test]

        -- If building Omega library locally, link to the local archive
        local_link_opt = if useInstalledOmega
                         then []
                         else ["-L" ++ takeDirectory omegaLibPath]

        input_opts = [testSourceName, -- Source file
                      cppObjectName,  -- Compiled C++ file
                      "-lomega",      -- Omega library
                      "-lstdc++"      -- C++ libarry
                     ]
        all_opts = build_opts ++ opts ++ local_link_opt ++ input_opts

    createDirectoryIfMissing True (cabalBuildPath </> testName test)
    runProgram verb ghcProg all_opts

configureGHC verb lbi = do
  (ghcProg, _) <- requireProgram verb ghcProgram (withPrograms lbi)
  ghcVersion <- case programVersion ghcProg
                of Just x  -> return x
                   Nothing -> die "Can't determine GHC vesion"
  return (ghcProg, ghcVersion)

-- Transfer the contents of one archive to another
transferArFiles verb runAr src dst = do
  srcCan <- canonicalizePath src
  dstCan <- canonicalizePath dst

  -- Create/remove a temporary directory
  bracket createUnpackDirectory (\_ -> removeUnpackDirectory) $ \_ ->

    -- Save/restore the current working directory
    bracket getCurrentDirectory setCurrentDirectory $ \_ -> do

      -- Go to temporary directory
      setCurrentDirectory omegaUnpackPath

      -- Unpack source archive
      runAr ["x", srcCan]

      -- Find object files
      objs <- liftM (filter isObjectFile) $ getDirectoryContents "."
      when (null objs) $ warn verb "No object files found in Omega library; build may be incomplete"

      -- Insert into destination archive
      runAr (["r", dstCan] ++ objs)
    where
      isObjectFile f = takeExtension f == ".o"

      createUnpackDirectory = createDirectoryIfMissing True omegaUnpackPath
      removeUnpackDirectory = removeDirectoryRecursive omegaUnpackPath

-------------------------------------------------------------------------------
-- Cleaning

cleanOmega pkgDesc mlbi userhooks flags = do
  let verb = fromFlagOrDefault Verbosity.normal $ cleanVerbosity flags

  -- run 'make clean', which will clean the Omega library if appropriate
  pgmConf <- configureProgram verb makeProgram defaultProgramConfiguration
  makeExists <- doesFileExist "Makefile"
  when makeExists $
    runDbProgram verb makeProgram pgmConf ["clean"]

  -- Clean extra files if we don't need to save configuration
  -- (Other temp files are automatically cleaned)
  unless (fromFlag $ cleanSaveConf flags) $ do
    lenientRemoveFiles configFiles
    lenientRemoveDirectory "autom4te.cache"

  -- Do default clean procedure
  cleanHook simpleUserHooks pkgDesc mlbi userhooks flags

-------------------------------------------------------------------------------
-- Hooks

hooks =
    simpleUserHooks
    { hookedPrograms = [arProgram, makeProgram]
    , confHook = configureOmega
    , buildHook = buildOmega
    , cleanHook = cleanOmega
    , sDistHook = sDistOmega (sDistHook simpleUserHooks)
    }

main = defaultMainWithHooks hooks