1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
|
{-# LANGUAGE CPP #-}
module Distribution.Simple.UUAGC.UUAGC(uuagcUserHook,
uuagcUserHook',
uuagc,
uuagcLibUserHook,
uuagcFromString
) where
-- import Distribution.Simple.BuildPaths (autogenComponentModulesDir)
import Debug.Trace
import Distribution.Simple
import Distribution.Simple.PreProcess
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Utils
import Distribution.Simple.Setup
import Distribution.PackageDescription hiding (Flag)
import Distribution.Simple.UUAGC.AbsSyn( AGFileOption(..)
, AGFileOptions
, AGOptionsClass(..)
, lookupFileOptions
, fileClasses
)
import Distribution.Simple.UUAGC.Parser
import Options hiding (verbose)
import Distribution.Verbosity
import System.Process( readProcessWithExitCode )
import System.Directory(getModificationTime
,doesFileExist
,removeFile)
import System.FilePath(pathSeparators,
(</>),
takeFileName,
normalise,
joinPath,
dropFileName,
addExtension,
dropExtension,
replaceExtension,
splitDirectories)
import System.Exit (ExitCode(..))
import System.IO( openFile, IOMode(..),
hFileSize,
hSetFileSize,
hClose,
hGetContents,
hFlush,
Handle(..), stderr, hPutStr, hPutStrLn)
import System.Exit(exitFailure)
import Control.Exception (throwIO)
import Control.Monad (liftM, when, guard, forM_, forM)
import Control.Arrow ((&&&), second)
import Data.Maybe (maybeToList)
import Data.Either (partitionEithers)
import Data.List (nub,intersperse)
import Data.Map (Map)
import qualified Data.Map as Map
{-# DEPRECATED uuagcUserHook, uuagcUserHook', uuagc "Use uuagcLibUserHook instead" #-}
-- | 'uuagc' returns the name of the uuagc compiler
uuagcn = "uuagc"
-- | 'defUUAGCOptions' returns the default names of the uuagc options
defUUAGCOptions :: String
defUUAGCOptions = "uuagc_options"
-- | File used to store de classes defined in the cabal file.
agClassesFile :: String
agClassesFile = "ag_file_options"
-- | The prefix used for the cabal file optionsw
agModule :: String
agModule = "x-agmodule"
-- | The prefix used for the cabal file options used for defining classes
agClass :: String
agClass = "x-agclass"
-- | Deprecated userhook
uuagcUserHook :: UserHooks
uuagcUserHook = uuagcUserHook' uuagcn
-- | Deprecated userhook
uuagcUserHook' :: String -> UserHooks
uuagcUserHook' uuagcPath = uuagcLibUserHook (uuagcFromString uuagcPath)
-- | Create uuagc function using shell (old method)
uuagcFromString :: String -> [String] -> FilePath -> IO (ExitCode, [FilePath])
uuagcFromString uuagcPath args file = do
(ec,out,err) <- readProcessWithExitCode uuagcPath (args ++ [file]) ""
case ec of
ExitSuccess ->
do hPutStr stderr err
return (ExitSuccess, words out)
(ExitFailure exc) ->
do hPutStrLn stderr (uuagcPath ++ ": " ++ show exc)
hPutStr stderr out
hPutStr stderr err
return (ExitFailure exc, [])
-- | Main hook, argument should be uuagc function
uuagcLibUserHook :: ([String] -> FilePath -> IO (ExitCode, [FilePath])) -> UserHooks
uuagcLibUserHook uuagc = hooks where
hooks = simpleUserHooks { hookedPreProcessors = ("ag", ag):("lag",ag):knownSuffixHandlers
, buildHook = uuagcBuildHook uuagc
-- , sDistHook = uuagcSDistHook uuagc
}
ag = uuagc' uuagc
originalPreBuild = preBuild simpleUserHooks
originalBuildHook = buildHook simpleUserHooks
--originalSDistHook = sDistHook simpleUserHooks
putErrorInfo :: Handle -> IO ()
putErrorInfo h = hGetContents h >>= hPutStr stderr
-- | 'updateAGFile' search into the uuagc options file for a list of all
-- AG Files and theirs file dependencies in order to see if the latters
-- are more updated that the formers, and if this is the case to
-- update the AG File
updateAGFile :: ([String] -> FilePath -> IO (ExitCode, [FilePath]))
-> Map FilePath (Options, Maybe (FilePath, [String]))
-> (FilePath, (Options, Maybe (FilePath, [String])))
-> IO ()
updateAGFile _ _ (_,(_,Nothing)) = return ()
updateAGFile uuagc newOptions (file,(opts,Just (gen,sp))) = do
hasGen <- doesFileExist gen
when hasGen $ do
(ec, files) <- uuagc (optionsToString $ opts { genFileDeps = True, searchPath = sp }) file
case ec of
ExitSuccess -> do
let newOpts :: Options
newOpts = maybe noOptions fst $ Map.lookup file newOptions
optRebuild = optionsToString newOpts /= optionsToString opts
modRebuild <-
if null files
then return False
else do
flsmt <- mapM getModificationTime files
let maxModified = maximum flsmt
fmt <- getModificationTime gen
return $ maxModified > fmt
-- When some dependency is newer or options have changed, we should regenerate
when (optRebuild || modRebuild) $ removeFile gen
ex@(ExitFailure _) -> throwIO ex
getAGFileOptions :: [(String, String)] -> IO AGFileOptions
getAGFileOptions extra = do
cabalOpts <- mapM (parseOptionAG . snd) $ filter ((== agModule) . fst) extra
usesOptionsFile <- doesFileExist defUUAGCOptions
if usesOptionsFile
then do r <- parserAG' defUUAGCOptions
case r of
Left e -> dieNoVerbosity (show e)
Right a -> return $ cabalOpts ++ a
else return cabalOpts
getAGClasses :: [(String, String)] -> IO [AGOptionsClass]
getAGClasses = mapM (parseClassAG . snd) . filter ((== agClass) . fst)
writeFileOptions :: FilePath -> Map FilePath (Options, Maybe (FilePath,[String])) -> IO ()
writeFileOptions classesPath opts = do
hClasses <- openFile classesPath WriteMode
hPutStr hClasses $ show $ Map.map (\(opt,gen) -> (optionsToString opt, gen)) opts
hFlush hClasses
hClose hClasses
readFileOptions :: FilePath -> IO (Map FilePath (Options, Maybe (FilePath,[String])))
readFileOptions classesPath = do
isFile <- doesFileExist classesPath
if isFile
then do hClasses <- openFile classesPath ReadMode
sClasses <- hGetContents hClasses
classes <- readIO sClasses :: IO (Map FilePath ([String], Maybe (FilePath,[String])))
hClose hClasses
return $ Map.map (\(opt,gen) -> let (opt',_,_) = getOptions opt in (opt', gen)) classes
else return Map.empty
getOptionsFromClass :: [(String, Options)] -> AGFileOption -> ([String], Options)
getOptionsFromClass classes fOpt =
second (foldl combineOptions (opts fOpt))
. partitionEithers $ do
fClass <- fileClasses fOpt
case fClass `lookup` classes of
Just x -> return $ Right x
Nothing -> return $ Left $ "Warning: The class "
++ show fClass
++ " is not defined."
-- uuagcSDistHook :: ([String] -> FilePath -> IO (ExitCode, [FilePath]))
-- -> PackageDescription
-- -> Maybe LocalBuildInfo
-- -> UserHooks
-- -> SDistFlags
-- -> IO ()
-- uuagcSDistHook uuagc pd mbLbi uh df = do
-- {-
-- case mbLbi of
-- Nothing -> warn normal "sdist: the local buildinfo was not present. Skipping AG initialization. Dist may fail."
-- Just lbi -> let classesPath = buildDir lbi </> agClassesFile
-- in commonHook uuagc classesPath pd lbi (sDistVerbosity df)
-- originalSDistHook pd mbLbi uh df
-- -}
-- originalSDistHook pd mbLbi (uh { hookedPreProcessors = ("ag", nouuagc):("lag",nouuagc):knownSuffixHandlers }) df -- bypass preprocessors
uuagcBuildHook
:: ([String] -> FilePath -> IO (ExitCode, [FilePath]))
-> PackageDescription
-> LocalBuildInfo
-> UserHooks
-> BuildFlags
-> IO ()
uuagcBuildHook uuagc pd lbi uh bf = do
let classesPath = buildDir lbi </> agClassesFile
commonHook uuagc classesPath pd lbi (buildVerbosity bf)
originalBuildHook pd lbi uh bf
commonHook :: ([String] -> FilePath -> IO (ExitCode, [FilePath]))
-> FilePath
-> PackageDescription
-> LocalBuildInfo
-> Flag Verbosity
-> IO ()
commonHook uuagc classesPath pd lbi fl = do
let verbosity = fromFlagOrDefault normal fl
info verbosity $ "commonHook: Assuming AG classesPath: " ++ classesPath
createDirectoryIfMissingVerbose verbosity True (buildDir lbi)
-- Read already existing options
-- Map FilePath (Options, Maybe (FilePath,[String]))
oldOptions <- readFileOptions classesPath
-- Read options from cabal and settings file
let lib = library pd
exes = executables pd
bis = map libBuildInfo (maybeToList lib) ++ map buildInfo exes
classes <- map (className &&& opts') `fmap` (getAGClasses . customFieldsPD $ pd)
configOptions <- getAGFileOptions (bis >>= customFieldsBI)
-- Construct new options map
newOptionsL <- forM configOptions (\ opt ->
let (notFound, opts) = getOptionsFromClass classes $ opt
file = normalise $ filename opt
gen = maybe Nothing snd $ Map.lookup file oldOptions
in do info verbosity $ "options for " ++ file ++ ": " ++ unwords (optionsToString opts)
forM_ notFound (hPutStrLn stderr)
return (file, (opts, gen)))
let newOptions = Map.fromList newOptionsL
writeFileOptions classesPath newOptions
-- Check if files should be regenerated
mapM_ (updateAGFile uuagc newOptions) $ Map.toList oldOptions
getAGFileList :: AGFileOptions -> [FilePath]
getAGFileList = map (normalise . filename)
uuagc :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
uuagc = uuagc' (uuagcFromString uuagcn)
uuagc' :: ([String] -> FilePath -> IO (ExitCode, [FilePath]))
-> BuildInfo
-> LocalBuildInfo
-> ComponentLocalBuildInfo
-> PreProcessor
uuagc' uuagc build lbi _ =
PreProcessor {
platformIndependent = True,
runPreProcessor = mkSimplePreProcessor $ \ inFile outFile verbosity ->
do notice verbosity $ "[UUAGC] processing: " ++ inFile ++ " generating: " ++ outFile
let classesPath = buildDir lbi </> agClassesFile
info verbosity $ "uuagc-preprocessor: Assuming AG classesPath: " ++ classesPath
fileOpts <- readFileOptions classesPath
opts <- case Map.lookup inFile fileOpts of
Nothing -> do warn verbosity $ "No options found for " ++ inFile
return noOptions
Just (opt,gen) -> return opt
let search = dropFileName inFile
options = opts { searchPath = search : hsSourceDirs build ++ searchPath opts
, outputFiles = outFile : (outputFiles opts) }
(eCode,_) <- uuagc (optionsToString options) inFile
case eCode of
ExitSuccess -> writeFileOptions classesPath (Map.insert inFile (opts, Just (outFile, searchPath options)) fileOpts)
ex@(ExitFailure _) -> throwIO ex
}
nouuagc :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
nouuagc build lbi _ =
PreProcessor {
platformIndependent = True,
runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity -> do
info verbosity ("skipping: " ++ outFile)
}
|