From 069225e2e61562c8166a446d201457425b91ce57 Mon Sep 17 00:00:00 2001 From: Daniel Gröber Date: Mon, 22 Oct 2018 01:20:25 +0200 Subject: Refactor Unit handling --- lib/Distribution/Helper.hs | 416 +++++++++++++++------------ src/CabalHelper/Compiletime/Compile.hs | 55 ++-- src/CabalHelper/Compiletime/Program/Stack.hs | 11 +- src/CabalHelper/Compiletime/Types.hs | 79 +++-- src/CabalHelper/Runtime/Main.hs | 11 +- tests/GhcSession.hs | 32 ++- 6 files changed, 333 insertions(+), 271 deletions(-) diff --git a/lib/Distribution/Helper.hs b/lib/Distribution/Helper.hs index 90935c9..0190129 100644 --- a/lib/Distribution/Helper.hs +++ b/lib/Distribution/Helper.hs @@ -54,13 +54,13 @@ module Distribution.Helper ( , mkQueryEnv , qeReadProcess , qePrograms - , qeProjectDir + , qeProjLoc , qeDistDir -- * GADTs , DistDir(..) , ProjType(..) - , ProjDir(..) + , ProjLoc(..) , Programs(..) , defaultPrograms @@ -163,99 +163,70 @@ instance Monad (Query pt) where return a = Query $ const $ return a -- | @mkQueryEnv projdir distdir@. Smart constructor for 'QueryEnv'. --- Sets fields 'qeProjectDir' and 'qeDistDir' to @projdir@ and @distdir@ +-- Sets fields 'qeProjLoc' and 'qeDistDir' to @projdir@ and @distdir@ -- respectively and provides sensible defaults for the other fields. mkQueryEnv - :: ProjDir pt - -- ^ Path to the project directory + :: ProjLoc pt + -- ^ Location of the project. -> DistDir pt -- ^ Path to the @dist/@ or @dist-newstyle/@ directory, called -- /builddir/ in Cabal terminology. -> IO (QueryEnv pt) -mkQueryEnv projdir distdir = do +mkQueryEnv projloc distdir = do cr <- newIORef $ QueryCache Nothing Map.empty return $ QueryEnv { qeReadProcess = \mcwd exe args stdin -> readCreateProcess (proc exe args){ cwd = mcwd } stdin , qePrograms = defaultPrograms - , qeProjectDir = projdir , qeDistDir = distdir , qeCacheRef = cr + , qeProjLoc = projloc } -piProjConfModTimes :: ProjInfo pt -> ProjConfModTimes pt -piProjConfModTimes ProjInfoV1 {piV1ProjConfModTimes} = - piV1ProjConfModTimes -piProjConfModTimes ProjInfoV2 {piV2ProjConfModTimes} = - piV2ProjConfModTimes -piProjConfModTimes ProjInfoStack {piStackProjConfModTimes} = - piStackProjConfModTimes - -piUnits :: DistDir pt -> ProjInfo pt -> [Unit] -piUnits (DistDirV1 distdir) (ProjInfoV1 (ProjConfModTimesV1 (cabal_file, _))) = - (:[]) $ Unit - { uUnitId = UnitId "" - , uPackageDir = takeDirectory cabal_file - , uDistDir = DistDirLib distdir +-- | Construct paths to project configuration files. +projConf :: ProjLoc pt -> ProjConf pt +projConf (ProjLocCabalFile cabal_file) = + ProjConfV1 cabal_file +projConf (ProjLocV2Dir projdir_path) = + ProjConfV2 + { pcV2CabalProjFile = projdir_path "cabal.project" + , pcV2CabalProjLocalFile = projdir_path "cabal.project.local" + , pcV2CabalProjFreezeFile = projdir_path "cabal.project.freeze" } -piUnits _ ProjInfoV2{..} = - case lefts units of - [] -> rights units - us@(_:_) -> panic $ - msg ++ (concat $ map (unlines . map (" "++) . lines . ppShow) us) - where - msg = "\ -\plan.json doesn't contain 'dist-dir' key for the following local units:\n" - units = catMaybes $ map takeunit $ Map.elems $ pjUnits piV2Plan - takeunit u@CP.Unit - { uType=UnitTypeLocal - , uDistDir=Just distdirv1 - , uPkgSrc=Just (LocalUnpackedPackage pkgdir) - } = Just $ Right $ Unit - { uUnitId = UnitId $ Text.unpack (coerce (uId u)) - , uPackageDir = pkgdir - , uDistDir = DistDirLib distdirv1 - } - takeunit u@CP.Unit {uType=UnitTypeLocal} = - Just $ Left u - takeunit _ = - Nothing -piUnits DistDirStack{} ProjInfoStack{..} = piStackUnits - - --- | Find files relevant to the project-scope configuration. Depending on the --- 'ProjType' this could be (for example) just a cabal file, one of the --- @caba.project*@ files or @stack.yaml@. --- --- The returned paths include the project-dir path. -projConfModTimes :: ProjDir pt -> IO (ProjConfModTimes pt) -projConfModTimes pd@(ProjDirV1 _) = - ProjConfModTimesV1 <$> (getFileModTime =<< findCabalFile pd) -projConfModTimes (ProjDirV2 projdir) = do - ex_files <- filterM doesFileExist (map (projdir ) additional_files) - let files = [ projdir "cabal.project" ] ++ ex_files - ProjConfModTimesV2 <$> mapM getFileModTime files - where - additional_files = - [ "cabal.project.local" - , "cabal.project.freeze" - ] -projConfModTimes (ProjDirStack projdir) = do - ProjConfModTimesStack <$> getFileModTime (projdir "stack.yml") +projConf (ProjLocStackDir projdir_path) = + ProjConfStack + { pcStackYaml = projdir_path "stack.yml" } + +getProjConfModTime :: ProjConf pt -> IO ProjConfModTimes +getProjConfModTime ProjConfV1{pcV1CabalFile} = + fmap ProjConfModTimes $ mapM getFileModTime + [ pcV1CabalFile + ] +getProjConfModTime ProjConfV2{..} = + fmap ProjConfModTimes $ mapM getFileModTime + [ pcV2CabalProjFile + , pcV2CabalProjLocalFile + , pcV2CabalProjFreezeFile + ] +getProjConfModTime ProjConfStack{..} = + fmap ProjConfModTimes $ mapM getFileModTime + [ pcStackYaml + ] getUnitModTimes :: Unit -> IO UnitModTimes -getUnitModTimes Unit { uDistDir=DistDirLib distdirv1, uPackageDir=pkgdir } = do - cabal_file <- findCabalFile (ProjDirV1 pkgdir) - cabal_file_mtime <- getFileModTime cabal_file - - let setup_config = distdirv1 "setup-config" - setup_config_mtime <- getFileModTime setup_config - - return UnitModTimes - { umtCabalFile = cabal_file_mtime - , umtSetupConfig = setup_config_mtime +getUnitModTimes + Unit + { uDistDir=DistDirLib distdirv1 + , uCabalFile=CabalFile cabal_file_path } - + = do + cabal_file_mtime <- getFileModTime cabal_file_path + let setup_config = distdirv1 "setup-config" + setup_config_mtime <- getFileModTime setup_config + return UnitModTimes + { umtCabalFile = cabal_file_mtime + , umtSetupConfig = setup_config_mtime + } -- | The version of GHC the project is configured to use compilerVersion :: Query pt (String, Version) @@ -263,8 +234,7 @@ compilerVersion = undefined -- | List of units in a project projectUnits :: Query pt [Unit] -projectUnits = Query $ \qe@QueryEnv{qeDistDir} -> - piUnits qeDistDir <$> getProjInfo qe +projectUnits = Query $ \qe -> piUnits <$> getProjInfo qe -- | Run a 'UnitQuery' on a given unit. To get a a unit see 'projectUnits'. unitQuery :: Unit -> Query pt UnitInfo @@ -296,39 +266,36 @@ reconfigure readProc progs cabalOpts = do getProjInfo :: QueryEnv pt -> IO (ProjInfo pt) getProjInfo qe@QueryEnv{..} = do cache@QueryCache{qcProjInfo, qcUnitInfos} <- readIORef qeCacheRef - proj_info <- checkUpdateProj qe qcProjInfo - let active_units = piUnits qeDistDir proj_info + proj_info <- checkUpdateProjInfo qe qcProjInfo + let active_units = piUnits proj_info writeIORef qeCacheRef $ cache { qcProjInfo = Just proj_info , qcUnitInfos = discardInactiveUnitInfos active_units qcUnitInfos } return proj_info -checkUpdateProj +checkUpdateProjInfo :: QueryEnvI c pt -> Maybe (ProjInfo pt) -> IO (ProjInfo pt) -checkUpdateProj qe mproj_info = do - mtime <- projConfModTimes (qeProjectDir qe) - +checkUpdateProjInfo qe mproj_info = do + let proj_conf = projConf (qeProjLoc qe) + mtime <- getProjConfModTime proj_conf case mproj_info of - Nothing -> reconf mtime + Nothing -> reconf proj_conf mtime Just proj_info | piProjConfModTimes proj_info /= mtime - -> reconf mtime + -> reconf proj_conf mtime | otherwise -> return proj_info where - reconf mtime = do + reconf proj_conf mtime = do shallowReconfigureProject qe - readProjInfo qe mtime - - + readProjInfo qe proj_conf mtime getUnitInfo :: QueryEnv pt -> Unit -> IO UnitInfo getUnitInfo qe@QueryEnv{..} unit@Unit{uDistDir} = do proj_info <- getProjInfo qe - cache@QueryCache{qcUnitInfos} <- readIORef qeCacheRef let munit_info = Map.lookup uDistDir qcUnitInfos unit_info <- checkUpdateUnitInfo qe proj_info unit munit_info @@ -373,17 +340,17 @@ discardInactiveUnitInfos active_units uis0 = -- system (@cabal@ or @stack@). shallowReconfigureProject :: QueryEnvI c pt -> IO () shallowReconfigureProject QueryEnv - { qeProjectDir = ProjDirV1 _projdir + { qeProjLoc = ProjLocCabalFile _cabal_file , qeDistDir = DistDirV1 _distdirv1 } = return () shallowReconfigureProject QueryEnv - { qeProjectDir = ProjDirV2 projdir + { qeProjLoc = ProjLocV2Dir projdir , qeDistDir = DistDirV2 _distdirv2, .. } = do _ <- liftIO $ qeReadProcess (Just projdir) (cabalProgram qePrograms) ["v2-build", "--dry-run", "all"] "" return () shallowReconfigureProject QueryEnv - { qeProjectDir = ProjDirStack _projdir, .. } = + { qeProjLoc = ProjLocStackDir _projdir, .. } = -- TODO: do we need to do anything here? Maybe package.yaml support needs to -- do stuff here? return () @@ -398,44 +365,117 @@ reconfigureUnit QueryEnv{qeDistDir=DistDirStack{}, ..} Unit{uPackageDir} = do ["stack", "build", "--only-configure", "."] "" return () -findCabalFile :: ProjDir 'V1 -> IO FilePath -findCabalFile (ProjDirV1 pkgdir) = do - [cfile] <- filter isCabalFile <$> getDirectoryContents pkgdir - return cfile +findCabalFile :: FilePath -> IO FilePath +findCabalFile pkgdir = do + [cfile] <- filter isCabalFile <$> getDirectoryContents pkgdir + return cfile + where + isCabalFile :: FilePath -> Bool + isCabalFile f = takeExtension' f == ".cabal" + + takeExtension' :: FilePath -> String + takeExtension' p = + if takeFileName p == takeExtension p + then "" -- just ".cabal" is not a valid cabal file + else takeExtension p getFileModTime :: FilePath -> IO (FilePath, EpochTime) getFileModTime f = do t <- modificationTime <$> getFileStatus f return (f, t) -readProjInfo :: QueryEnvI c pt -> ProjConfModTimes pt -> IO (ProjInfo pt) -readProjInfo qe conf_files = do - case (qeProjectDir qe, qeDistDir qe) of - (ProjDirV1 _projdir, DistDirV1 _) -> - return $ ProjInfoV1 { piV1ProjConfModTimes = conf_files } - (ProjDirV2 _projdir, DistDirV2 distdirv2) -> do +readProjInfo + :: QueryEnvI c pt -> ProjConf pt -> ProjConfModTimes -> IO (ProjInfo pt) +readProjInfo qe pc pcm = join $ withVerbosity $ do + case (qeProjLoc qe, qeDistDir qe, pc) of + ((,,) + projloc + (DistDirV1 distdir) + ProjConfV1{pcV1CabalFile}) -> do + let projdir = plV1Dir projloc + setup_config_path <- canonicalizePath (distdir "setup-config") + mhdr <- getCabalConfigHeader setup_config_path + case mhdr of + Nothing -> + panicIO $ printf "Could not read '%s' header" setup_config_path + Just (hdrCabalVersion, _) -> + return ProjInfo + { piCabalVersion = hdrCabalVersion + , piProjConfModTimes = pcm + , piUnits = (:[]) $ Unit + { uUnitId = UnitId "" + , uPackageDir = projdir + , uCabalFile = CabalFile pcV1CabalFile + , uDistDir = DistDirLib distdir + } + , piImpl = ProjInfoV1 + } + (ProjLocV2Dir _projdir, DistDirV2 distdirv2, _) -> do let plan_path = distdirv2 "cache" "plan.json" plan_mtime <- modificationTime <$> getFileStatus plan_path - plan <- decodePlanJson plan_path - return $ ProjInfoV2 - { piV2ProjConfModTimes = conf_files - , piV2Plan = plan - , piV2PlanModTime = plan_mtime + plan@PlanJson { pjCabalLibVersion=Ver pjCabalLibVersion + , pjCompilerId=PkgId (PkgName compName) (Ver compVer) + } + <- decodePlanJson plan_path + units <- planUnits plan + return ProjInfo + { piCabalVersion = makeDataVersion pjCabalLibVersion + , piProjConfModTimes = pcm + , piUnits = units + , piImpl = ProjInfoV2 + { piV2Plan = plan + , piV2PlanModTime = plan_mtime + , piV2CompilerId = (Text.unpack compName, makeDataVersion compVer) + } } - (ProjDirStack{} , DistDirStack{}) -> do + (ProjLocStackDir{} , DistDirStack{}, _) -> do cabal_files <- Stack.listPackageCabalFiles qe units <- mapM (Stack.getUnit qe) cabal_files proj_paths <- Stack.projPaths qe - return $ ProjInfoStack - { piStackProjConfModTimes = conf_files - , piStackUnits = units - , piStackProjPaths = proj_paths + Just (cabalVer:_) <- runMaybeT $ + let ?progs = qePrograms qe in + listCabalVersions' (Just (sppGlobalPkgDb proj_paths)) + -- ^ See [Note Stack Cabal Version] + return ProjInfo + { piCabalVersion = cabalVer + , piProjConfModTimes = pcm + , piUnits = units + , piImpl = ProjInfoStack + { piStackProjPaths = proj_paths + } } +planUnits :: CP.PlanJson -> IO [Unit] +planUnits plan = do + units <- fmap catMaybes $ mapM takeunit $ Map.elems $ CP.pjUnits plan + case lefts units of + [] -> return $ rights units + us@(_:_) -> panicIO $ + msg ++ (concat $ map (unlines . map (" "++) . lines . ppShow) us) + where + msg = "\ +\plan.json doesn't contain 'dist-dir' key for the following local units:\n" + takeunit u@CP.Unit + { uType=CP.UnitTypeLocal + , uDistDir=Just distdirv1 + , uPkgSrc=Just (CP.LocalUnpackedPackage pkgdir) + } = do + cabal_file <- findCabalFile pkgdir + return $ Just $ Right $ Unit + { uUnitId = UnitId $ Text.unpack (coerce (CP.uId u)) + , uPackageDir = pkgdir + , uCabalFile = CabalFile cabal_file + , uDistDir = DistDirLib distdirv1 + } + takeunit u@CP.Unit {uType=CP.UnitTypeLocal} = + return $ Just $ Left u + takeunit _ = + return $ Nothing + readUnitInfo :: QueryEnvI c pt -> FilePath -> Unit -> IO UnitInfo readUnitInfo - qe exe unit@Unit {uUnitId=uiUnitId, uPackageDir=pkgdir, uDistDir=distdir} = do - res <- readHelper qe exe pkgdir distdir + qe exe unit@Unit {uUnitId=uiUnitId, uCabalFile, uDistDir} = do + res <- readHelper qe exe uCabalFile uDistDir [ "package-id" , "package-db-stack" , "flags" @@ -458,7 +498,7 @@ readUnitInfo readHelper :: QueryEnvI c pt -> FilePath - -> FilePath + -> CabalFile -> DistDirLib -> [String] -> IO [Maybe ChResponse] @@ -476,25 +516,25 @@ readHelper qe exe cabal_file distdir args = do invokeHelper :: QueryEnvI c pt -> FilePath - -> FilePath + -> CabalFile -> DistDirLib -> [String] -> IO String -invokeHelper QueryEnv {..} exe cabal_file (DistDirLib distdir) args0 = do - let args1 = cabal_file : distdir : args0 - evaluate =<< qeReadProcess Nothing exe args1 "" `E.catch` - \(_ :: E.IOException) -> - panicIO $ concat - ["invokeHelper", ": ", exe, " " - , intercalate " " (map show args1) - , " failed!" - ] - --- getPackageId :: QueryEnv pt -> IO (String, Version) --- getPackageId QueryEnv{..} = do --- [cfile] <- filter isCabalFile <$> getDirectoryContents qeProjectDir --- gpd <- readPackageDescription silent (qeProjectDir cfile) --- return $ (display (packageName gpd), toDataVersion (packageVersion gpd)) +invokeHelper + QueryEnv {..} + exe + (CabalFile cabal_file_path) + (DistDirLib distdir) + args0 + = do + let args1 = cabal_file_path : distdir : args0 + evaluate =<< qeReadProcess Nothing exe args1 "" `E.catch` + \(_ :: E.IOException) -> + panicIO $ concat + ["invokeHelper", ": ", exe, " " + , intercalate " " (map show args1) + , " failed!" + ] -- | Make sure the appropriate helper executable for the given project is -- installed and ready to run queries. @@ -504,12 +544,12 @@ prepare qe = do void $ wrapper proj_info qe -- | Create @cabal_macros.h@ and @Paths_\@ possibly other generated files --- in the usual place. -writeAutogenFiles :: QueryEnv pt -> IO () -writeAutogenFiles qe = do +-- in the usual place. See 'Distribution.Simple.Build.initialBuildSteps'. +writeAutogenFiles :: Unit -> Query pt () +writeAutogenFiles Unit{uCabalFile, uDistDir} = Query $ \qe -> do proj_info <- getProjInfo qe - _exe <- wrapper proj_info qe - undefined -- void $ invokeHelper qe exe ["write-autogen-files"] + exe <- wrapper proj_info qe + void $ invokeHelper qe exe uCabalFile uDistDir ["write-autogen-files"] -- | Get the path to the sandbox package-db in a project getSandboxPkgDb @@ -573,63 +613,59 @@ wrapper wrapper proj_info QueryEnv{..} = do join $ withVerbosity $ do let ?progs = qePrograms - guessProgramPaths $ wrapper' qeProjectDir qeDistDir proj_info + let comp = wrapper' qeProjLoc qeDistDir proj_info + eexe <- compileHelper comp + case eexe of + Left rv -> + panicIO $ "compileHelper': compiling helper failed! exit code "++ show rv + Right exe -> + return exe wrapper' - :: Env - => ProjDir pt + :: Verbose + => ProjLoc pt -> DistDir pt -> ProjInfo pt - -> IO FilePath -wrapper' (ProjDirV1 projdir) (DistDirV1 distdir) _ = do - cfgf <- canonicalizePath (distdir "setup-config") - mhdr <- getCabalConfigHeader cfgf - case mhdr of - Nothing -> panicIO $ printf "\ -\Could not read Cabal's persistent setup configuration header\n\ -\- Check first line of: %s\n\ -\- Maybe try: $ cabal configure" cfgf - Just (hdrCabalVersion, _) -> do - compileHelper' hdrCabalVersion Nothing projdir Nothing distdir + -> CompHelperEnv wrapper' - (ProjDirV2 projdir) + projloc + (DistDirV1 distdir) + ProjInfo{piCabalVersion} + = CompHelperEnv + { cheCabalVer = piCabalVersion + , cheProjDir = plV1Dir projloc + , cheCacheDir = distdir + , chePkgDb = Nothing + , cheNewstyle = Nothing + } +wrapper' + (ProjLocV2Dir projdir) (DistDirV2 distdir) - ProjInfoV2{piV2Plan=plan} - = do - let PlanJson {pjCabalLibVersion=Ver (makeDataVersion -> pjCabalLibVersion) } - = plan - compileHelper' pjCabalLibVersion - Nothing - projdir - (Just (plan, distdir)) - (distdir "cache") + ProjInfo{piImpl=ProjInfoV2{piV2Plan=plan}} + = CompHelperEnv + { cheCabalVer = makeDataVersion pjCabalLibVersion + , cheProjDir = projdir + , cheCacheDir = distdir "cache" + , chePkgDb = Nothing + , cheNewstyle = Just (plan, distdir) + } + where + PlanJson {pjCabalLibVersion=Ver pjCabalLibVersion } = plan wrapper' - (ProjDirStack projdir) + (ProjLocStackDir projdir) (DistDirStack mworkdir) - ProjInfoStack{piStackProjPaths=StackProjPaths{sppGlobalPkgDb}} - = do - -- Stack also just picks whatever version ghc-pkg spits out, see - -- Stack.GhcPkg.getCabalPkgVer. - Just (cabalVer:_) <- runMaybeT $ listCabalVersions' (Just sppGlobalPkgDb) - let workdir = fromMaybe ".stack-work" $ unRelativePath <$> mworkdir - compileHelper' cabalVer - (Just sppGlobalPkgDb) - projdir - Nothing - (projdir workdir) - -compileHelper' - :: Env - => Version - -> Maybe PackageDbDir - -> FilePath - -> Maybe (PlanJson, FilePath) - -> FilePath - -> IO FilePath -compileHelper' pjCabalLibVersion cabalPkgDb projdir mnewstyle distdirv1 = do - eexe <- compileHelper pjCabalLibVersion cabalPkgDb projdir mnewstyle distdirv1 - case eexe of - Left rv -> - panicIO $ "compileHelper': compiling helper failed! (exit code "++ show rv - Right exe -> - return exe + ProjInfo + { piCabalVersion + , piImpl = ProjInfoStack + { piStackProjPaths=StackProjPaths + { sppGlobalPkgDb } + } + } + = let workdir = fromMaybe ".stack-work" $ unRelativePath <$> mworkdir in + CompHelperEnv + { cheCabalVer = piCabalVersion + , cheProjDir = projdir + , cheCacheDir = projdir workdir + , chePkgDb = Just sppGlobalPkgDb + , cheNewstyle = Nothing + } diff --git a/src/CabalHelper/Compiletime/Compile.hs b/src/CabalHelper/Compiletime/Compile.hs index 3126128..3f8a771 100644 --- a/src/CabalHelper/Compiletime/Compile.hs +++ b/src/CabalHelper/Compiletime/Compile.hs @@ -96,18 +96,19 @@ data CompPaths = CompPaths -- executable. data CompilationProductScope = CPSGlobal | CPSProject -compileHelper - :: Env - => Version - -> Maybe PackageDbDir - -> FilePath - -> Maybe (PlanJson, FilePath) - -> FilePath - -> IO (Either ExitCode FilePath) -compileHelper hdrCabalVersion cabalPkgDb projdir mnewstyle cachedir = do +data CompHelperEnv = CompHelperEnv + { cheCabalVer :: Version + , chePkgDb :: Maybe PackageDbDir + , cheProjDir :: FilePath + , cheNewstyle :: Maybe (PlanJson, FilePath) + , cheCacheDir :: FilePath + } + +compileHelper :: Env => CompHelperEnv -> IO (Either ExitCode FilePath) +compileHelper CompHelperEnv{..} = do ghcVer <- ghcVersion Just (prepare, comp) <- runMaybeT $ msum $ - case cabalPkgDb of + case chePkgDb of Nothing -> [ compileCabalSource , compileNewBuild ghcVer @@ -116,12 +117,12 @@ compileHelper hdrCabalVersion cabalPkgDb projdir mnewstyle cachedir = do , MaybeT $ Just <$> compileWithCabalInPrivatePkgDb ] Just db -> - [ pure $ (pure (), compileWithPkg (Just db) hdrCabalVersion CPSProject) + [ pure $ (pure (), compileWithPkg (Just db) cheCabalVer CPSProject) ] appdir <- appCacheDir - let cp@CompPaths {compExePath} = compPaths appdir cachedir comp + let cp@CompPaths {compExePath} = compPaths appdir cheCacheDir comp exists <- doesFileExist compExePath if exists then do @@ -134,32 +135,32 @@ compileHelper hdrCabalVersion cabalPkgDb projdir mnewstyle cachedir = do where logMsg = "using helper compiled with Cabal from " --- for relaxed deps: find (sameMajorVersionAs hdrCabalVersion) . reverse . sort +-- for relaxed deps: find (sameMajorVersionAs cheCabalVer) . reverse . sort -- | Check if this version is globally available compileGlobal :: Env => MaybeT IO (IO (), Compile) compileGlobal = do cabal_versions <- listCabalVersions' Nothing - ver <- MaybeT $ return $ find (== hdrCabalVersion) cabal_versions + ver <- MaybeT $ return $ find (== cheCabalVer) cabal_versions vLog $ logMsg ++ "user/global package-db" return $ (return (), compileWithPkg Nothing ver CPSGlobal) -- | Check if this version is available in the project sandbox compileSandbox :: Env => Version -> MaybeT IO (IO (), Compile) compileSandbox ghcVer = do - let mdb_path = getSandboxPkgDb (display buildPlatform) ghcVer projdir + let mdb_path = getSandboxPkgDb (display buildPlatform) ghcVer cheProjDir sandbox <- PackageDbDir <$> MaybeT mdb_path cabal_versions <- listCabalVersions' (Just sandbox) - ver <- MaybeT $ return $ find (== hdrCabalVersion) cabal_versions + ver <- MaybeT $ return $ find (== cheCabalVer) cabal_versions vLog $ logMsg ++ "sandbox package-db" return $ (return (), compileWithPkg (Just sandbox) ver CPSProject) compileNewBuild :: Env => Version -> MaybeT IO (IO (), Compile) compileNewBuild ghcVer = do - (PlanJson {pjUnits}, distdir_newstyle) <- maybe mzero pure mnewstyle + (PlanJson {pjUnits}, distdir_newstyle) <- maybe mzero pure cheNewstyle let cabal_pkgid = PkgId (PkgName (Text.pack "Cabal")) - (Ver $ versionBranch hdrCabalVersion) + (Ver $ versionBranch cheCabalVer) mcabal_unit = listToMaybe $ Map.elems $ Map.filter (\CP.Unit{..} -> uPId == cabal_pkgid) pjUnits CP.Unit {} <- maybe mzero pure mcabal_unit @@ -167,7 +168,7 @@ compileHelper hdrCabalVersion cabalPkgDb projdir mnewstyle cachedir = do "packagedb" ("ghc-" ++ showVersion ghcVer) inplace_db = PackageDbDir inplace_db_path cabal_versions <- listCabalVersions' (Just inplace_db) - ver <- MaybeT $ return $ find (== hdrCabalVersion) cabal_versions + ver <- MaybeT $ return $ find (== cheCabalVer) cabal_versions vLog $ logMsg ++ "v2-build package-db " ++ inplace_db_path return $ (return (), compileWithPkg (Just inplace_db) ver CPSProject) @@ -176,22 +177,22 @@ compileHelper hdrCabalVersion cabalPkgDb projdir mnewstyle cachedir = do compileWithCabalInPrivatePkgDb :: Env => IO (IO (), Compile) compileWithCabalInPrivatePkgDb = do db@(PackageDbDir db_path) - <- getPrivateCabalPkgDb (CabalVersion hdrCabalVersion) + <- getPrivateCabalPkgDb (CabalVersion cheCabalVer) vLog $ logMsg ++ "private package-db in " ++ db_path - return (prepare db, compileWithPkg (Just db) hdrCabalVersion CPSGlobal) + return (prepare db, compileWithPkg (Just db) cheCabalVer CPSGlobal) where prepare db = do - db_exists <- liftIO $ cabalVersionExistsInPkgDb hdrCabalVersion db + db_exists <- liftIO $ cabalVersionExistsInPkgDb cheCabalVer db when (not db_exists) $ - void $ installCabal (Right hdrCabalVersion) `E.catch` - \(SomeException _) -> errorInstallCabal hdrCabalVersion + void $ installCabal (Right cheCabalVer) `E.catch` + \(SomeException _) -> errorInstallCabal cheCabalVer -- | See if we're in a cabal source tree compileCabalSource :: Env => MaybeT IO (IO (), Compile) compileCabalSource = do - let cabalFile = projdir "Cabal.cabal" + let cabalFile = cheProjDir "Cabal.cabal" cabalSrc <- liftIO $ doesFileExist cabalFile - let projdir' = CabalSourceDir projdir + let projdir = CabalSourceDir cheProjDir case cabalSrc of False -> mzero True -> do @@ -206,7 +207,7 @@ compileHelper hdrCabalVersion cabalPkgDb projdir mnewstyle cachedir = do mzero "custom" -> do vLog $ "compiling helper with local Cabal source tree" - return $ (return (), compileWithCabalSource projdir' ver) + return $ (return (), compileWithCabalSource projdir ver) _ -> error $ "compileCabalSource: unknown build-type: '"++buildType++"'" compileWithCabalSource srcDir ver = diff --git a/src/CabalHelper/Compiletime/Program/Stack.hs b/src/CabalHelper/Compiletime/Program/Stack.hs index 4751f0a..e7f280d 100644 --- a/src/CabalHelper/Compiletime/Program/Stack.hs +++ b/src/CabalHelper/Compiletime/Program/Stack.hs @@ -37,14 +37,15 @@ import CabalHelper.Compiletime.Types import CabalHelper.Compiletime.Types.RelativePath getUnit :: QueryEnvI c 'Stack -> CabalFile -> IO Unit -getUnit qe (CabalFile cabal_file) = do - let pkgdir = takeDirectory cabal_file - let pkg_name = dropExtension $ takeFileName cabal_file +getUnit qe cabal_file@(CabalFile cabal_file_path) = do + let pkgdir = takeDirectory cabal_file_path + let pkg_name = dropExtension $ takeFileName cabal_file_path look <- paths qe pkgdir let distdirv1 = look "dist-dir:" return $ Unit { uUnitId = UnitId pkg_name , uPackageDir = pkgdir + , uCabalFile = cabal_file , uDistDir = DistDirLib distdirv1 } @@ -57,7 +58,7 @@ packageDistDir qe pkgdir = do return $ look "dist-dir:" projPaths :: QueryEnvI c 'Stack -> IO StackProjPaths -projPaths qe@QueryEnv {qeProjectDir=ProjDirStack projdir} = do +projPaths qe@QueryEnv {qeProjLoc=ProjLocStackDir projdir} = do look <- paths qe projdir return StackProjPaths { sppGlobalPkgDb = PackageDbDir $ look "global-pkg-db:" @@ -76,7 +77,7 @@ paths qe dir = do split l = let (key, ' ' : val) = span (not . isSpace) l in (key, val) listPackageCabalFiles :: QueryEnvI c 'Stack -> IO [CabalFile] -listPackageCabalFiles qe@QueryEnv{qeProjectDir=ProjDirStack projdir} = do +listPackageCabalFiles qe@QueryEnv{qeProjLoc=ProjLocStackDir projdir} = do out <- qeReadProcess qe (Just projdir) (stackProgram $ qePrograms qe) [ "ide", "packages", "--cabal-files" ] "" return $ map CabalFile $ lines out diff --git a/src/CabalHelper/Compiletime/Types.hs b/src/CabalHelper/Compiletime/Types.hs index e803ae6..cc8561f 100644 --- a/src/CabalHelper/Compiletime/Types.hs +++ b/src/CabalHelper/Compiletime/Types.hs @@ -33,6 +33,7 @@ import Data.Version import Data.Typeable import Data.Map.Strict (Map) import GHC.Generics +import System.FilePath import System.Posix.Types import CabalHelper.Compiletime.Types.RelativePath import CabalHelper.Shared.InterfaceTypes @@ -44,23 +45,27 @@ data ProjType | V2 -- ^ @cabal v2-build@ project, see 'DistDirV2' | Stack -- ^ @stack@ project. --- | A project directory. The project type of a given directory can be --- determined by trying to access a set of marker files. See below. -data ProjDir (pt :: ProjType) where +-- | 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. +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. - ProjDirV1 :: FilePath -> ProjDir '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. - ProjDirV2 :: FilePath -> ProjDir '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. - ProjDirStack :: FilePath -> ProjDir 'Stack + ProjLocStackDir :: { plStackDir :: FilePath } -> ProjLoc 'Stack + +plV1Dir :: ProjLoc 'V1 -> FilePath +plV1Dir (ProjLocCabalFile cabal_file) = takeDirectory cabal_file data DistDir (pt :: ProjType) where -- | Build directory for cabal /old-build/ aka. /v1-build/ aka. just @@ -99,7 +104,7 @@ data QueryEnvI cache (proj_type :: ProjType) = QueryEnv , qePrograms :: Programs -- ^ Field accessor for 'QueryEnv'. - , qeProjectDir :: ProjDir proj_type + , qeProjLoc :: !(ProjLoc proj_type) -- ^ Field accessor for 'QueryEnv'. Defines path to the project directory, -- i.e. a directory containing a @cabal.project@ file @@ -135,6 +140,7 @@ newtype DistDirLib = DistDirLib FilePath data Unit = Unit { uUnitId :: !UnitId , uPackageDir :: !FilePath + , uCabalFile :: !CabalFile , uDistDir :: !DistDirLib } @@ -173,32 +179,47 @@ data UnitInfo = UnitInfo , uiModTimes :: !UnitModTimes } deriving (Eq, Ord, Read, Show) -data ProjInfo pt where - ProjInfoV1 :: - { piV1ProjConfModTimes :: !(ProjConfModTimes 'V1) - } -> ProjInfo 'V1 +-- | Files relevant to the project-scope configuration of a project. We gather +-- them here so we can refer to their paths conveniently. +data ProjConf pt where + ProjConfV1 :: + { pcV1CabalFile :: !FilePath + } -> ProjConf 'V1 + + ProjConfV2 :: + { pcV2CabalProjFile :: !FilePath + , pcV2CabalProjLocalFile :: !FilePath + , pcV2CabalProjFreezeFile :: !FilePath + } -> ProjConf 'V2 + + ProjConfStack :: + { pcStackYaml :: !FilePath + } -> ProjConf 'Stack + +-- these are supposed to be opaque, as they are meant to be used only for cache +-- invalidation +newtype ProjConfModTimes = ProjConfModTimes [(FilePath, EpochTime)] + deriving (Eq) + +data ProjInfo pt = ProjInfo + { piCabalVersion :: !Version + , piProjConfModTimes :: !ProjConfModTimes + , piUnits :: ![Unit] + , piImpl :: !(ProjInfoImpl pt) + } + +data ProjInfoImpl pt where + ProjInfoV1 :: ProjInfoImpl 'V1 ProjInfoV2 :: - { piV2ProjConfModTimes :: !(ProjConfModTimes 'V2) - , piV2Plan :: !PlanJson - , piV2PlanModTime :: !EpochTime - } -> ProjInfo 'V2 + { piV2Plan :: !PlanJson + , piV2PlanModTime :: !EpochTime + , piV2CompilerId :: !(String, Version) + } -> ProjInfoImpl 'V2 ProjInfoStack :: - { piStackProjConfModTimes :: !(ProjConfModTimes 'Stack) - , piStackUnits :: ![Unit] - , piStackProjPaths :: !StackProjPaths - } -> ProjInfo 'Stack - -data ProjConfModTimes pt where - ProjConfModTimesV1 - :: !(FilePath, EpochTime) -> ProjConfModTimes 'V1 - ProjConfModTimesV2 - :: !([(FilePath, EpochTime)]) -> ProjConfModTimes 'V2 - ProjConfModTimesStack - :: !(FilePath, EpochTime) -> ProjConfModTimes 'Stack - -deriving instance Eq (ProjConfModTimes pt) + { piStackProjPaths :: !StackProjPaths + } -> ProjInfoImpl 'Stack data UnitModTimes = UnitModTimes { umtCabalFile :: !(FilePath, EpochTime) diff --git a/src/CabalHelper/Runtime/Main.hs b/src/CabalHelper/Runtime/Main.hs index d0eea1a..70a0cc5 100644 --- a/src/CabalHelper/Runtime/Main.hs +++ b/src/CabalHelper/Runtime/Main.hs @@ -263,20 +263,19 @@ main :: IO () main = do args <- getArgs - projdir:distdir:args' <- case args of - [] -> usage >> exitFailure - _ -> return args + cfile : distdir : args' + <- case args of + [] -> usage >> exitFailure + _ -> return args ddexists <- doesDirectoryExist distdir when (not ddexists) $ do errMsg $ "distdir '"++distdir++"' does not exist" exitFailure - [cfile] <- filter isCabalFile <$> getDirectoryContents projdir - v <- maybe silent (const deafening) . lookup "CABAL_HELPER_DEBUG" <$> getEnvironment lbi <- unsafeInterleaveIO $ getPersistBuildConfig distdir - gpd <- unsafeInterleaveIO $ readPackageDescription v (projdir cfile) + gpd <- unsafeInterleaveIO $ readPackageDescription v cfile let pd = localPkgDescr lbi let lvd = (lbi, v, distdir) diff --git a/tests/GhcSession.hs b/tests/GhcSession.hs index ca01abd..ca91dd4 100644 --- a/tests/GhcSession.hs +++ b/tests/GhcSession.hs @@ -13,7 +13,7 @@ import Data.Version import qualified Data.Map as Map import System.Environment (getArgs) import System.Exit -import System.FilePath (()) +import System.FilePath ((), takeFileName, takeDirectory) import System.Directory import System.IO import System.IO.Temp @@ -29,11 +29,11 @@ main = do args <- getArgs topdir <- getCurrentDirectory res <- mapM (setup topdir test) $ case args of - [] -> [ ("tests/exelib" , parseVer "1.10", parseVer "0") - , ("tests/exeintlib", parseVer "2.0", parseVer "0") - , ("tests/fliblib" , parseVer "2.0", parseVer "0") - , ("tests/bkpregex" , parseVer "2.0", parseVer "8.1") - -- min Cabal lib ver -^ min GHC ver -^ + [] -> [ ("tests/exelib/exelib.cabal", parseVer "1.10", parseVer "0") + , ("tests/exeintlib/exeintlib.cabal", parseVer "2.0", parseVer "0") + , ("tests/fliblib/fliblib.cabal", parseVer "2.0", parseVer "0") + , ("tests/bkpregex/bkpregex.cabal", parseVer "2.0", parseVer "8.1") + -- min Cabal lib ver -^ min GHC ver -^ ] xs -> map (, parseVer "0", parseVer "0") xs @@ -55,7 +55,8 @@ cabalInstallBuiltinCabalVersion = ["act-as-setup", "--", "--numeric-version"] "" setup :: FilePath -> (FilePath -> IO [Bool]) -> (FilePath, Version, Version) -> IO [Bool] -setup topdir act (srcdir, min_cabal_ver, min_ghc_ver) = do +setup topdir act (cabal_file, min_cabal_ver, min_ghc_ver) = do + let projdir = takeDirectory cabal_file ci_ver <- cabalInstallVersion c_ver <- cabalInstallBuiltinCabalVersion g_ver <- ghcVersion @@ -73,18 +74,18 @@ setup topdir act (srcdir, min_cabal_ver, min_ghc_ver) = do case mreason of Just reason -> do - putStrLn $ "Skipping test '" ++ srcdir ++ "' because " ++ reason ++ "." + putStrLn $ "Skipping test '" ++ projdir ++ "' because " ++ reason ++ "." return [] Nothing -> do - putStrLn $ "Running test '" ++ srcdir ++ "' ------------------------------" + putStrLn $ "Running test '" ++ projdir ++ "'-------------------------" withSystemTempDirectory "cabal-helper.ghc-session.test" $ \dir -> do - setCurrentDirectory $ topdir srcdir + setCurrentDirectory $ topdir projdir run "cabal" [ "sdist", "-v0", "--output-dir", dir ] setCurrentDirectory dir run "cabal" [ "configure" ] - act dir + act $ dir takeFileName cabal_file run :: String -> [String] -> IO () run x xs = do @@ -93,9 +94,12 @@ run x xs = do return () test :: FilePath -> IO [Bool] -test dir = do - qe <- mkQueryEnv (ProjDirV1 dir) (DistDirV1 $ dir "dist") - cs <- runQuery (concat <$> allUnits (Map.elems . uiComponents)) qe +test cabal_file = do + let projdir = takeDirectory cabal_file + qe <- mkQueryEnv + (ProjLocCabalFile cabal_file) + (DistDirV1 $ projdir "dist") + cs <- concat <$> runQuery (allUnits (Map.elems . uiComponents)) qe forM cs $ \ChComponentInfo{..} -> do putStrLn $ "\n" ++ show ciComponentName ++ ":::: " ++ show ciNeedsBuildOutput -- cgit v1.2.3