diff options
author | Daniel Gröber <dxld@darkboxed.org> | 2019-01-22 00:34:05 +0100 |
---|---|---|
committer | Daniel Gröber <dxld@darkboxed.org> | 2019-01-26 02:59:23 +0100 |
commit | 541d219dbcf097c0c50b4ee0216f270c9c8c1342 (patch) | |
tree | d4c15bf12e74d3bc4be880c20b176045e1d961f1 /src | |
parent | a6a20f17279e31e35861d52a16232897915918fc (diff) |
Add support and test coverage for mulit-pkg projects
Diffstat (limited to 'src')
-rw-r--r-- | src/CabalHelper/Compiletime/Program/Stack.hs | 63 | ||||
-rw-r--r-- | src/CabalHelper/Compiletime/Types.hs | 90 | ||||
-rw-r--r-- | src/CabalHelper/Compiletime/Types/RelativePath.hs | 1 |
3 files changed, 105 insertions, 49 deletions
diff --git a/src/CabalHelper/Compiletime/Program/Stack.hs b/src/CabalHelper/Compiletime/Program/Stack.hs index f4ada8f..33ba031 100644 --- a/src/CabalHelper/Compiletime/Program/Stack.hs +++ b/src/CabalHelper/Compiletime/Program/Stack.hs @@ -24,43 +24,48 @@ License : GPL-3 module CabalHelper.Compiletime.Program.Stack where +import Control.Exception (handle, throwIO) import Control.Monad +import Control.Monad.Trans.Maybe +import Control.Monad.IO.Class import Data.Char import Data.List hiding (filter) import Data.String import Data.Maybe import Data.Function +import Data.Version +import System.Directory (findExecutable) import System.FilePath hiding ((<.>)) +import System.IO (hPutStrLn, stderr) +import Text.Printf (printf) +import Text.Show.Pretty import Prelude import CabalHelper.Compiletime.Types import CabalHelper.Compiletime.Types.RelativePath +import CabalHelper.Shared.Common getUnit :: QueryEnvI c 'Stack -> CabalFile -> IO (Unit 'Stack) -getUnit qe cabal_file@(CabalFile cabal_file_path) = do +getUnit + qe@QueryEnv{qeProjLoc=ProjLocStackYaml stack_yaml} + cabal_file@(CabalFile cabal_file_path) + = do + let projdir = takeDirectory stack_yaml let pkgdir = takeDirectory cabal_file_path let pkg_name = dropExtension $ takeFileName cabal_file_path look <- paths qe pkgdir - let distdirv1 = look "dist-dir:" + let distdirv1_rel = look "dist-dir:" return $ Unit { uUnitId = UnitId pkg_name , uPackageDir = pkgdir , uCabalFile = cabal_file - , uDistDir = DistDirLib distdirv1 + , uDistDir = DistDirLib $ pkgdir </> distdirv1_rel , uImpl = UnitImplStack } --- TODO: patch ghc/ghc-pkg program paths like in ghc-mod when using stack so --- compilation logic works even if no system compiler is installed - -packageDistDir :: QueryEnvI c 'Stack -> FilePath -> IO FilePath -packageDistDir qe pkgdir = do - look <- paths qe pkgdir - return $ look "dist-dir:" - projPaths :: QueryEnvI c 'Stack -> IO StackProjPaths -projPaths qe@QueryEnv {qeProjLoc=ProjLocStackDir projdir} = do - look <- paths qe projdir +projPaths qe@QueryEnv {qeProjLoc=ProjLocStackYaml stack_yaml} = do + look <- paths qe $ takeDirectory stack_yaml return StackProjPaths { sppGlobalPkgDb = PackageDbDir $ look "global-pkg-db:" , sppSnapPkgDb = PackageDbDir $ look "snapshot-pkg-db:" @@ -68,20 +73,20 @@ projPaths qe@QueryEnv {qeProjLoc=ProjLocStackDir projdir} = do , sppCompExe = look "compiler-exe:" } -paths :: QueryEnvI c 'Stack - -> FilePath - -> IO (String -> FilePath) -paths qe dir = do - out <- qeReadProcess qe (Just dir) (stackProgram $ qePrograms qe) - (workdirArg qe ++ [ "path" ]) "" - return $ \k -> let Just x = lookup k $ map split $ lines out in x +paths :: QueryEnvI c 'Stack -> FilePath -> IO (String -> FilePath) +paths qe@QueryEnv{qeProjLoc=ProjLocStackYaml stack_yaml} cwd + = do + out <- readStackCmd qe (Just cwd) $ + workdirArg qe ++ [ "path", "--stack-yaml="++stack_yaml ] + return $ \k -> let Just x = lookup k $ map split $ lines out in x where split l = let (key, ' ' : val) = span (not . isSpace) l in (key, val) listPackageCabalFiles :: QueryEnvI c 'Stack -> IO [CabalFile] -listPackageCabalFiles qe@QueryEnv{qeProjLoc=ProjLocStackDir projdir} = do - out <- qeReadProcess qe (Just projdir) (stackProgram $ qePrograms qe) - [ "ide", "packages", "--cabal-files" ] "" +listPackageCabalFiles qe@QueryEnv{qeProjLoc=ProjLocStackYaml stack_yaml} = do + let projdir = takeDirectory stack_yaml + out <- readStackCmd qe (Just projdir) + [ "ide", "packages", "--cabal-files", "--stdout" ] return $ map CabalFile $ lines out workdirArg :: QueryEnvI c 'Stack -> [String] @@ -91,3 +96,15 @@ workdirArg QueryEnv{qeDistDir=DistDirStack mworkdir} = patchCompPrograms :: StackProjPaths -> CompPrograms -> CompPrograms patchCompPrograms StackProjPaths{sppCompExe} cprogs = cprogs { ghcProgram = sppCompExe } + +doStackCmd :: (QueryEnvI c 'Stack -> CallProcessWithCwd a) + -> QueryEnvI c 'Stack -> Maybe FilePath -> [String] -> IO a +doStackCmd procfn qe mcwd args = + let Programs{..} = qePrograms qe in + procfn qe mcwd stackProgram $ stackArgsBefore ++ args ++ stackArgsAfter + +readStackCmd :: QueryEnvI c 'Stack -> Maybe FilePath -> [String] -> IO String +callStackCmd :: QueryEnvI c 'Stack -> Maybe FilePath -> [String] -> IO () + +readStackCmd = doStackCmd (\qe -> qeReadProcess qe "") +callStackCmd = doStackCmd qeCallProcess diff --git a/src/CabalHelper/Compiletime/Types.hs b/src/CabalHelper/Compiletime/Types.hs index 56f2468..185725d 100644 --- a/src/CabalHelper/Compiletime/Types.hs +++ b/src/CabalHelper/Compiletime/Types.hs @@ -30,7 +30,6 @@ import Cabal.Plan import Data.IORef import Data.Version import Data.Typeable -import Data.Map.Strict (Map) import GHC.Generics import System.FilePath import System.Posix.Types @@ -39,32 +38,41 @@ import CabalHelper.Shared.InterfaceTypes import Data.List.NonEmpty (NonEmpty) --import qualified Data.List.NonEmpty as NonEmpty - +import Data.Map.Strict (Map) +--import qualified Data.Map.Strict as Strict -- | The kind of project being managed by a 'QueryEnv' (pun intended). data ProjType = V1 -- ^ @cabal v1-build@ project, see 'DistDirV1' | V2 -- ^ @cabal v2-build@ project, see 'DistDirV2' | Stack -- ^ @stack@ project. + deriving (Eq, Ord, Show, Read) + +data SProjType pt where + SV1 :: SProjType 'V1 + SV2 :: SProjType 'V2 + SStack :: SProjType 'Stack --- | The location of a project. The kind of location marker given determines the --- 'ProjType'. The project type of a given directory can be determined by trying --- to access a set of marker files. See below. +-- | Location of project sources. The project type of a given directory can be +-- determined by trying to access a set of marker files. See below. data ProjLoc (pt :: ProjType) where -- | A @cabal v1-build@ project directory can be identified by one file -- ending in @.cabal@ existing in the directory. More than one such files -- existing is a user error. Note: For this project type the concepts of -- project and package coincide. - ProjLocCabalFile :: { plCabalFile :: FilePath } -> ProjLoc 'V1 + ProjLocCabalFile :: { plCabalFile :: !FilePath } -> ProjLoc 'V1 -- | A @cabal v2-build@ project\'s marker file is called -- @cabal.project@. This configuration file points to the packages that make -- up this project. - ProjLocV2Dir :: { plV2Dir :: FilePath } -> ProjLoc 'V2 + ProjLocV2File :: { plCabalProjectFile :: !FilePath } -> ProjLoc 'V2 + ProjLocV2Dir :: { plV2Dir :: !FilePath } -> ProjLoc 'V2 -- | A @stack@ project\'s marker file is called @stack.yaml@. This -- configuration file points to the packages that make up this project. - ProjLocStackDir :: { plStackDir :: FilePath } -> ProjLoc 'Stack + ProjLocStackYaml :: { plStackYaml :: !FilePath } -> ProjLoc 'Stack + +deriving instance Show (ProjLoc pt) plV1Dir :: ProjLoc 'V1 -> FilePath plV1Dir (ProjLocCabalFile cabal_file) = takeDirectory cabal_file @@ -76,7 +84,7 @@ data DistDir (pt :: ProjType) where -- -- You can tell a builddir is a /v1/ builddir by looking for a file -- called @setup-config@ directly underneath it. - DistDirV1 :: FilePath -> DistDir 'V1 + DistDirV1 :: !FilePath -> DistDir 'V1 -- | Build directory for cabal /new-build/ aka. /v2-build/, as of the time -- of this writing it is usually called @dist-newstyle/@ but this will @@ -84,11 +92,13 @@ data DistDir (pt :: ProjType) where -- -- You can tell a builddir is a /v2/ builddir by trying to access the path -- @cache/plan.json@ directly underneath it. - DistDirV2 :: FilePath -> DistDir 'V2 + DistDirV2 :: !FilePath -> DistDir 'V2 -- | Build directory for stack, aka. /work-dir/. Optionally override Stack's -- /work-dir/. If you just want to use Stack's default set to @Nothing@ - DistDirStack :: Maybe RelativePath -> DistDir 'Stack + DistDirStack :: !(Maybe RelativePath) -> DistDir 'Stack + +deriving instance Show (DistDir pt) -- | Environment for running a 'Query' value. The constructor is not exposed in -- the API to allow extending the environment without breaking user code. @@ -100,12 +110,13 @@ type QueryEnv (pt :: ProjType) = QueryEnvI QueryCache pt data QueryEnvI c (pt :: ProjType) = QueryEnv - { qeReadProcess - :: !(Maybe FilePath -> FilePath -> [String] -> String -> IO String) + { qeReadProcess :: !ReadProcessWithCwd -- ^ Field accessor for 'QueryEnv'. Function used to to start -- processes. Useful if you need to, for example, redirect standard error -- output of programs started by cabal-helper. + , qeCallProcess :: !(CallProcessWithCwd ()) + , qePrograms :: !Programs -- ^ Field accessor for 'QueryEnv'. @@ -126,6 +137,9 @@ data QueryEnvI c (pt :: ProjType) = QueryEnv -- 'QueryEnv' is used. } +type ReadProcessWithCwd = String -> CallProcessWithCwd String +type CallProcessWithCwd a = Maybe FilePath -> FilePath -> [String] -> IO a + data QueryCache pt = QueryCache { qcProjInfo :: !(Maybe (ProjInfo pt)) , qcUnitInfos :: !(Map DistDirLib UnitInfo) @@ -144,7 +158,7 @@ data Unit pt = Unit , uCabalFile :: !CabalFile , uDistDir :: !DistDirLib , uImpl :: !(UnitImpl pt) - } + } deriving (Show) data UnitImpl pt where UnitImplV1 :: UnitImpl 'V1 @@ -156,6 +170,8 @@ data UnitImpl pt where UnitImplStack :: UnitImpl 'Stack +deriving instance Show (UnitImpl pt) + -- | This returns the component a 'Unit' corresponds to. This information is -- only available if the correspondence happens to be unique and known before -- querying setup-config for the respective project type. Currently this only @@ -228,7 +244,7 @@ data ProjConf pt where -- these are supposed to be opaque, as they are meant to be used only for cache -- invalidation newtype ProjConfModTimes = ProjConfModTimes [(FilePath, EpochTime)] - deriving (Eq) + deriving (Eq, Show) -- | Project-scope information cache. data ProjInfo pt = ProjInfo @@ -238,7 +254,7 @@ data ProjInfo pt = ProjInfo , piProjConfModTimes :: !ProjConfModTimes -- ^ Key for cache invalidation. When this is not equal to the return -- value of 'getProjConfModTime' this 'ProjInfo' is considered invalid. - } + } deriving (Show) data ProjInfoImpl pt where ProjInfoV1 :: ProjInfoImpl 'V1 @@ -253,6 +269,21 @@ data ProjInfoImpl pt where { piStackProjPaths :: !StackProjPaths } -> ProjInfoImpl 'Stack +instance Show (ProjInfoImpl pt) where + show ProjInfoV1 = "ProjInfoV1" + show ProjInfoV2 {..} = concat + [ "ProjInfoV2 {" + , "piV2Plan = ", show piV2Plan, ", " -- + , "piV2PlanModTime = ", show piV2PlanModTime, ", " + , "piV2CompilerId = ", show piV2CompilerId + , "}" + ] + show ProjInfoStack {..} = concat + [ "ProjInfoStack {" + , "piStackProjPaths = ", show piStackProjPaths + , "}" + ] + data UnitModTimes = UnitModTimes { umtPkgYaml :: !(Maybe (FilePath, EpochTime)) , umtCabalFile :: !(FilePath, EpochTime) @@ -260,13 +291,14 @@ data UnitModTimes = UnitModTimes } deriving (Eq, Ord, Read, Show) newtype CabalFile = CabalFile FilePath + deriving (Show) data StackProjPaths = StackProjPaths { sppGlobalPkgDb :: !PackageDbDir , sppSnapPkgDb :: !PackageDbDir , sppLocalPkgDb :: !PackageDbDir , sppCompExe :: !FilePath - } + } deriving (Show) -- Beware: GHC 8.0.2 doesn't like these being recursively defined for some @@ -277,19 +309,23 @@ type Progs = (?cprogs :: CompPrograms, ?progs :: Programs) type CProgs = (?cprogs :: CompPrograms) -- | Configurable paths to various programs we use. -data Programs = Programs { - -- | The path to the @cabal@ program. - cabalProgram :: FilePath, - - -- | The path to the @stack@ program. - stackProgram :: FilePath +data Programs = Programs + { cabalProgram :: !FilePath + -- ^ The path to the @cabal@ program. + , cabalArgsBefore :: ![String] + , cabalArgsAfter :: ![String] + + , stackProgram :: !FilePath + -- ^ The path to the @stack@ program. + , stackArgsBefore :: ![String] + , stackArgsAfter :: ![String] } deriving (Eq, Ord, Show, Read, Generic, Typeable) data CompPrograms = CompPrograms - { ghcProgram :: FilePath + { ghcProgram :: !FilePath -- ^ The path to the @ghc@ program. - , ghcPkgProgram :: FilePath + , ghcPkgProgram :: !FilePath -- ^ The path to the @ghc-pkg@ program. If not changed it will be derived -- from the path to 'ghcProgram'. } deriving (Eq, Ord, Show, Read, Generic, Typeable) @@ -297,7 +333,7 @@ data CompPrograms = CompPrograms -- | By default all programs use their unqualified names, i.e. they will be -- searched for on @PATH@. defaultPrograms :: Programs -defaultPrograms = Programs "cabal" "stack" +defaultPrograms = Programs "cabal" [] [] "stack" [] [] defaultCompPrograms :: CompPrograms defaultCompPrograms = CompPrograms "ghc" "ghc-pkg" @@ -317,4 +353,6 @@ defaultCompileOptions = CompileOptions False Nothing Nothing defaultPrograms newtype PackageDbDir = PackageDbDir { unPackageDbDir :: FilePath } + deriving (Show) newtype PackageEnvFile = PackageEnvFile { unPackageEnvFile :: FilePath } + deriving (Show) diff --git a/src/CabalHelper/Compiletime/Types/RelativePath.hs b/src/CabalHelper/Compiletime/Types/RelativePath.hs index bfc29bf..107a8ce 100644 --- a/src/CabalHelper/Compiletime/Types/RelativePath.hs +++ b/src/CabalHelper/Compiletime/Types/RelativePath.hs @@ -30,6 +30,7 @@ import System.FilePath -- | A path guaranteed to be relative. The constructor is not exposed, use the -- 'mkRelativePath' smart constructor. newtype RelativePath = RelativePath { unRelativePath :: FilePath } + deriving (Show) -- | Smart constructor for 'RelativePath'. Checks if the given path is absolute -- and throws 'UserError' if not. |