diff options
author | Tom Smeding <tom@tomsmeding.com> | 2021-02-18 12:11:48 +0100 |
---|---|---|
committer | Tom Smeding <tom@tomsmeding.com> | 2021-02-18 12:11:48 +0100 |
commit | e3ab394665c2c308cab6fffb41b3acc66d0ca989 (patch) | |
tree | ff8a210bbcd4a6bfff57de4d05eb50a80240c6aa /Coolbal | |
parent | 1a7c345d3d530c566840c72f59a932f292cefd09 (diff) |
Second
Diffstat (limited to 'Coolbal')
-rw-r--r-- | Coolbal/FindRoot.hs | 83 | ||||
-rw-r--r-- | Coolbal/FindSpec.hs | 61 | ||||
-rw-r--r-- | Coolbal/Options.hs | 10 | ||||
-rw-r--r-- | Coolbal/Target.hs | 94 | ||||
-rw-r--r-- | Coolbal/Target/Executable.hs | 1 |
5 files changed, 174 insertions, 75 deletions
diff --git a/Coolbal/FindRoot.hs b/Coolbal/FindRoot.hs new file mode 100644 index 0000000..ea6e297 --- /dev/null +++ b/Coolbal/FindRoot.hs @@ -0,0 +1,83 @@ +module Coolbal.FindRoot ( + findCabalSpec, + FoundCabalSpec(..), + findDist, + FoundRootDir(..), +) where + +import Data.List (unfoldr) +import System.Directory +import System.Exit (exitFailure) +import System.FilePath ((</>)) +import System.IO (hPutStrLn, stderr) + + +data FoundCabalSpec = FoundCabalSpec + { foundcsRootDir :: FilePath + , foundcsCabal :: FilePath } + deriving (Show) + +findCabalSpec :: IO FoundCabalSpec +findCabalSpec = do + cwd <- getCurrentDirectory >>= makeAbsolute + mfound <- findThingInDirs "cabal file" (`endsWith` ".cabal") doesFileExist FoundCabalSpec (ancestors cwd) + case mfound of + Just found -> do + putStrLn ("Found .cabal file: " ++ foundcsCabal found) + return found + Nothing -> do + hPutStrLn stderr (".cabal file not found in ancestors of PWD: " ++ cwd) + exitFailure + +data FoundRootDir = FoundRootDir FilePath + deriving (Show) + +findDist :: IO FoundRootDir +findDist = do + cwd <- getCurrentDirectory >>= makeAbsolute + mfound <- findThingInDirs "dist-coolbal directory" (== "dist-coolbal") doesDirectoryExist (\p _ -> FoundRootDir p) (ancestors cwd) + case mfound of + Just found -> return found + Nothing -> do + hPutStrLn stderr ("dist-coolbal directory not found in ancestors of PWD: " ++ cwd) + exitFailure + +-- | Argument semantics are the same as 'findThingInDir'. +findThingInDirs :: String -> (FilePath -> Bool) -> (FilePath -> IO Bool) -> (FilePath -> FilePath -> a) -> [FilePath] -> IO (Maybe a) +findThingInDirs _ _ _ _ [] = return Nothing +findThingInDirs description namePred typePred constructor (p:ps) = + findThingInDir description namePred typePred constructor p + >>= maybe (findThingInDirs description namePred typePred constructor ps) + (return . Just) + +-- | 'namePred' gets the file name, 'typePred' gets the full path. +-- 'constructor' gets the root dir path and the name of the file found. +findThingInDir :: String -> (FilePath -> Bool) -> (FilePath -> IO Bool) -> (FilePath -> FilePath -> a) -> FilePath -> IO (Maybe a) +findThingInDir description namePred typePred constructor dir = do + files <- filter namePred <$> listDirectory dir + case files of + [fname] -> do + ok <- typePred (dir </> fname) + return (if ok then Just (constructor dir (dir </> fname)) + else Nothing) + [] -> return Nothing + _ -> do + hPutStrLn stderr ("Ambiguity when searching for " ++ description ++ "! Found files:") + mapM_ (\p -> hPutStrLn stderr ("- " ++ (dir </> p))) files + exitFailure + +parentDir :: FilePath -> Maybe FilePath +parentDir s = case reverse s of + "/" -> Nothing + '/' : s' -> Just (reverse (dropWhile (/= '/') s')) + s' -> case dropWhile (/= '/') s' of + "/" -> Just "/" + _ : s'' -> Just (reverse s'') + "" -> Nothing + +-- Includes the path itself as first element +ancestors :: FilePath -> [FilePath] +ancestors p = p : unfoldr (fmap (\x -> (x,x)) . parentDir) p + +endsWith :: String -> String -> Bool +s `endsWith` s' = reverse (take (length s') (reverse s)) == s' diff --git a/Coolbal/FindSpec.hs b/Coolbal/FindSpec.hs deleted file mode 100644 index ce9ab45..0000000 --- a/Coolbal/FindSpec.hs +++ /dev/null @@ -1,61 +0,0 @@ -module Coolbal.FindSpec (findCabalSpec, FoundCabalSpec(..)) where - -import Data.List (unfoldr) -import System.Directory -import System.Exit (exitFailure) -import System.FilePath ((</>)) -import System.IO (hPutStrLn, stderr) - - -data FoundCabalSpec = FoundCabalSpec - { foundcsRootDir :: FilePath - , foundcsCabal :: FilePath } - deriving (Show) - -findCabalSpec :: IO FoundCabalSpec -findCabalSpec = do - cwd <- getCurrentDirectory >>= makeAbsolute - mfound <- findCabalSpecInDirs (ancestors cwd) - case mfound of - Just found -> do - putStrLn ("Found .cabal file: " ++ foundcsCabal found) - return found - Nothing -> do - hPutStrLn stderr (".cabal file not found in ancestors of PWD: " ++ cwd) - exitFailure - -findCabalSpecInDirs :: [FilePath] -> IO (Maybe FoundCabalSpec) -findCabalSpecInDirs [] = return Nothing -findCabalSpecInDirs (p:ps) = - findCabalSpecInDir p >>= maybe (findCabalSpecInDirs ps) (return . Just) - -findCabalSpecInDir :: FilePath -> IO (Maybe FoundCabalSpec) -findCabalSpecInDir dir = do - files <- filter (`endsWith` ".cabal") <$> listDirectory dir - case files of - [fname] -> do - exists <- doesFileExist (dir </> fname) - return (if exists then Just (FoundCabalSpec { foundcsRootDir = dir - , foundcsCabal = dir </> fname }) - else Nothing) - [] -> return Nothing - _ -> do - hPutStrLn stderr "Ambiguous cabal file! Found files:" - mapM_ (\p -> hPutStrLn stderr ("- " ++ (dir </> p))) files - exitFailure - -parentDir :: FilePath -> Maybe FilePath -parentDir s = case reverse s of - "/" -> Nothing - '/' : s' -> Just (reverse (dropWhile (/= '/') s')) - s' -> case dropWhile (/= '/') s' of - "/" -> Just "/" - _ : s'' -> Just (reverse s'') - "" -> Nothing - --- Includes the path itself as first element -ancestors :: FilePath -> [FilePath] -ancestors p = p : unfoldr (fmap (\x -> (x,x)) . parentDir) p - -endsWith :: String -> String -> Bool -s `endsWith` s' = reverse (take (length s') (reverse s)) == s' diff --git a/Coolbal/Options.hs b/Coolbal/Options.hs index 35b774c..8b1f807 100644 --- a/Coolbal/Options.hs +++ b/Coolbal/Options.hs @@ -1,6 +1,7 @@ module Coolbal.Options ( Options(..), BuildOptions(..), + RunOptions(..), optionParser, ) where @@ -11,11 +12,15 @@ data Options = Build BuildOptions | Clean | Configure + | Run RunOptions deriving (Show) data BuildOptions = BuildOptions (Maybe String) deriving (Show) +data RunOptions = RunOptions (Maybe String) [String] + deriving (Show) + optionParser :: ParserInfo Options optionParser = info (root <**> helper) @@ -33,6 +38,8 @@ root = hsubparser ( command "build" (info (Build <$> buildOptions) (progDesc "Build the project")) + <> command "run" (info (Run <$> runOptions) + (progDesc "Run an executable from the project")) <> command "clean" (info (pure Clean) (progDesc "Clean coolbal's files for this project")) <> command "configure" (info (pure Configure) @@ -40,3 +47,6 @@ root = buildOptions :: Parser BuildOptions buildOptions = BuildOptions <$> optional (argument str (metavar "TARGET")) + +runOptions :: Parser RunOptions +runOptions = RunOptions <$> optional (argument str (metavar "TARGET")) <*> many (argument str (metavar "ARGS...")) diff --git a/Coolbal/Target.hs b/Coolbal/Target.hs index c698ceb..761f19d 100644 --- a/Coolbal/Target.hs +++ b/Coolbal/Target.hs @@ -1,6 +1,6 @@ -{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE RankNTypes #-} -{-# LANGUAGE StandaloneDeriving #-} module Coolbal.Target ( IsTarget(..), AnyTarget(..), @@ -8,12 +8,16 @@ module Coolbal.Target ( useAnyTarget, ) where +import Data.Binary (Binary) import Data.Char (ord) import Data.List (intercalate) +import Data.Time.Clock (UTCTime) +import GHC.Generics (Generic) import Numeric (showHex) -import System.Directory (createDirectoryIfMissing) -import System.Exit (ExitCode(..), die) +import System.Directory (createDirectoryIfMissing, doesFileExist, getModificationTime) +import System.Exit (ExitCode(..), die, exitWith) import System.FilePath ((</>)) +import System.IO.Error (catchIOError) import System.Process (rawSystem) import Coolbal.Util @@ -40,12 +44,18 @@ class IsTarget a where -- | Recompile the target. Argument is the root directory of the project. targetBuild :: FilePath -> a -> IO () -data AnyTarget = forall a. (Show a, IsTarget a) => AnyTarget a + -- | If the target is an executable target, return an IO action that runs + -- the executable with the specified arguments. The 'FilePath' is the root + -- directory of the project. + targetExecute :: a -> Maybe (FilePath -> [String] -> IO ()) -deriving instance Show AnyTarget +data AnyTarget = AnyTargetExe ExeTarget + deriving (Show, Generic) -useAnyTarget :: (forall a. (Show a, IsTarget a) => a -> r) -> AnyTarget -> r -useAnyTarget f (AnyTarget x) = f x +instance Binary AnyTarget + +useAnyTarget :: (forall a. (Show a, Binary a, IsTarget a) => a -> r) -> AnyTarget -> r +useAnyTarget f (AnyTargetExe x) = f x data ExeTarget = ExeTarget { exeTargetName :: String @@ -58,18 +68,29 @@ data ExeTarget = ExeTarget -- ^ Haskell language (e.g. Haskell2010) , exeTargetMain :: FilePath -- ^ Main file + , exeTargetSrcDirs :: [FilePath] + -- ^ Source directories , exeTargetModules :: [[String]] -- ^ Other modules in the target , exeTargetFlags :: [String] -- ^ User-specified compiler flags } - deriving (Show) + deriving (Show, Generic) + +instance Binary ExeTarget instance IsTarget ExeTarget where targetName = exeTargetName targetNameQualified e = "exe:" ++ targetName e - targetCheckOld _ _ = return True + targetCheckOld projdir tg = do + mbinTm <- maybeModificationTime (projdir </> "dist-coolbal/bin" </> escapeFileName (exeTargetName tg)) + case mbinTm of + Just binTm -> do + anyNewerThan binTm (findFile' (exeTargetSrcDirs tg) (exeTargetMain tg) + : [findSourceFile' (exeTargetSrcDirs tg) m | m <- exeTargetModules tg]) + Nothing -> + return True targetBuild projdir tg = do let buildDir = projdir </> "dist-coolbal/build" @@ -81,7 +102,7 @@ instance IsTarget ExeTarget where [["--make", "-static"] ,concat [[flag, buildDir] | flag <- ["-outputdir", "-odir", "-hidir", "-stubdir"]] - ,["-i" ++ buildDir, "-I" ++ buildDir] + ,["-i" ++ dir | dir <- exeTargetSrcDirs tg] ,["-hide-all-packages", "-Wmissing-home-modules", "-no-user-package-db"] ,["-package-db", exeTargetPkgDbDir tg] ,concat [["-package-id", dep] | dep <- exeTargetDeps tg] @@ -92,6 +113,11 @@ instance IsTarget ExeTarget where ,exeTargetFlags tg]) >>= checkExitCode "ghc" + targetExecute tg = Just $ \projdir args -> do + let filename = escapeFileName (exeTargetName tg) + rawSystem (projdir </> "dist-coolbal/bin" </> filename) args + >>= exitWith + checkExitCode :: String -> ExitCode -> IO () checkExitCode _ ExitSuccess = return () checkExitCode procname (ExitFailure c) = @@ -106,7 +132,47 @@ escapeFileName = in n <= 0x1F || n >= 0x7F || c `elem` ":/\"*<>?\\|!") (\c -> case (c, ord c) of ('!', _) -> "!!" - (_, n) | n <= 0xFF -> '!' : showHex n "" - | n <= 0xFFFF -> '!' : 'u' : showHex n "" - | n <= 0xFFFFFF -> '!' : 'U' : showHex n "" + (_, n) | n <= 0xFF -> '!' : leftPad 2 '0' (showHex n "") + | n <= 0xFFFF -> '!' : 'u' : leftPad 4 '0' (showHex n "") + | n <= 0xFFFFFF -> '!' : 'U' : leftPad 6 '0' (showHex n "") | otherwise -> error "Super-high unicode?") + where + leftPad n c s = replicate (max 0 (n - length s)) c ++ s + +maybeModificationTime' :: FilePath -> IO UTCTime +maybeModificationTime' path = maybeModificationTime path >>= \case + Just tm -> return tm + Nothing -> die ("File not found: '" ++ path ++ "'") + +maybeModificationTime :: FilePath -> IO (Maybe UTCTime) +maybeModificationTime path = + catchIOError (Just <$> getModificationTime path) (\_ -> return Nothing) + +findSourceFile' :: [FilePath] -> [String] -> IO FilePath +findSourceFile' ds m = findSourceFile ds m >>= \case + Just fp -> return fp + Nothing -> die ("Module not found in source directories: " ++ intercalate "." m) + +findSourceFile :: [FilePath] -> [String] -> IO (Maybe FilePath) +findSourceFile dirs modname = findFile dirs (foldr (</>) "" modname ++ ".hs") + +findFile' :: [FilePath] -> FilePath -> IO FilePath +findFile' ds fname = findFile ds fname >>= \case + Just fp -> return fp + Nothing -> die ("File not found in source directories: '" ++ fname ++ "'") + +findFile :: [FilePath] -> FilePath -> IO (Maybe FilePath) +findFile [] _ = return Nothing +findFile (dir:ds) fname = do + ok <- doesFileExist (dir </> fname) + if ok then return (Just (dir </> fname)) + else findFile ds fname + +anyNewerThan :: UTCTime -> [IO FilePath] -> IO Bool +anyNewerThan _ [] = return False +anyNewerThan reftm (fp:fps) = do + fp' <- fp + tm <- maybeModificationTime' fp' + if tm > reftm + then return True + else anyNewerThan reftm fps diff --git a/Coolbal/Target/Executable.hs b/Coolbal/Target/Executable.hs index 2e62953..6358ab7 100644 --- a/Coolbal/Target/Executable.hs +++ b/Coolbal/Target/Executable.hs @@ -34,6 +34,7 @@ makeExeTarget exe plan , exeTargetDeps = ppDepends planpkg , exeTargetLanguage = show language , exeTargetMain = modulePath exe + , exeTargetSrcDirs = hsSourceDirs bi , exeTargetModules = map Module.components (otherModules bi) , exeTargetFlags = flags }) |