module Development.Shake.Imports (
importsRule,
importsDefaultHaskell,
importsDefaultCpp,
directImports,
transitiveImports,
) where
import Data.List
import Data.Char
import Data.Maybe
import Control.Applicative ((<$>))
import Control.Arrow
import Development.Shake
import Development.Shake.FilePath
directImports :: FilePath -> Action [FilePath]
directImports file =
lines <$> readFile' (file <.> "directImports")
transitiveImports :: FilePath -> Action [FilePath]
transitiveImports file =
lines <$> readFile' (file <.> "transitiveImports")
importsRule :: (FilePath -> Bool)
-> (FilePath -> Action [FilePath])
-> Rules ()
importsRule ruleApplies getDirectDependencies = do
let isDirectImportKey file =
("//*.directImports" ?== file) &&
ruleApplies (dropExtension file)
isDirectImportKey ?> \ importsFile -> do
let sourceFile = dropExtension importsFile
directImports' <- getDirectDependencies sourceFile
writeFileChanged importsFile (unlines directImports')
let isTransitiveImportKey file =
("//*.transitiveImports" ?== file) &&
ruleApplies (dropExtension file)
isTransitiveImportKey ?> \ transitiveImportsFile -> do
let directImportsFile = replaceExtension transitiveImportsFile ".directImports"
directImports' :: [FilePath] <- readFileLines directImportsFile
transitiveImports' <- concat <$> mapM readFileLines
(map (<.> ".transitiveImports") directImports')
writeFileChanged transitiveImportsFile
(unlines $ nub $ directImports' ++ transitiveImports')
return ()
importsDefaultHaskell :: [FilePath] -> Rules ()
importsDefaultHaskell sourceDirs =
importsRule ("//*.hs" ?==) (getHaskellDependencies sourceDirs)
where
getHaskellDependencies :: [FilePath] -> FilePath -> Action [FilePath]
getHaskellDependencies source file =
readFile' file >>=
return . hsImports >>=
return . map moduleToFile >>=
mapM (searchInPaths source) >>=
return . catMaybes
hsImports :: String -> [String]
hsImports xs = [ takeWhile (\z -> isAlphaNum z || z `elem` "._") $ dropWhile (not . isUpper) x
| x <- lines xs, "import " `isPrefixOf` x]
moduleToFile :: String -> FilePath
moduleToFile =
map (\ c -> if c == '.' then '/' else c) >>>
(<.> "hs")
importsDefaultCpp :: [FilePath] -> Rules ()
importsDefaultCpp sourceDirs =
importsRule ("//*.cpp" ?==) getDependencies
where
getDependencies file =
readFile' file >>=
return . extractLocalIncludes >>=
mapM (searchInPaths sourceDirs) >>=
return . catMaybes
extractLocalIncludes :: String -> [FilePath]
extractLocalIncludes =
lines >>> map localInclude >>> catMaybes
localInclude :: String -> Maybe FilePath
localInclude line |
("#include" : quotedFile : []) <- words line,
"\"" `isPrefixOf` quotedFile,
"\"" `isSuffixOf` quotedFile
= Just $ tail $ init $ quotedFile
localInclude _ = Nothing
searchInPaths :: [FilePath] -> FilePath -> Action (Maybe FilePath)
searchInPaths (path : r) file = do
exists <- doesFileExist (path </> file)
if exists
then return $ Just (path </> file)
else searchInPaths r file
searchInPaths [] _ = return Nothing