codex 0.5.1.2 → 0.5.2.0
raw patch · 8 files changed
+312/−64 lines, 8 filesdep −MissingHdep −eitherdep −monad-loopsdep ~Cabaldep ~basedep ~bytestringnew-uploaderPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies removed: MissingH, either, monad-loops
Dependency ranges changed: Cabal, base, bytestring, hackage-db, process
API changes (from Hackage documentation)
+ Codex.Internal: readStackVersion :: IO Version
+ Codex.Project: allComponentsInBuildOrder' :: LocalBuildInfo -> [ComponentLocalBuildInfo]
+ Codex.Project: findCabalFilePath :: FilePath -> IO (Maybe FilePath)
+ Codex.Project: findLocalPackages :: Int -> FilePath -> IO [WorkspaceProject]
+ Codex.Project: resolveLocalDependencies :: Builder -> FilePath -> [WorkspaceProject] -> IO ProjectDependencies
+ Codex.Project: type Hackage = HackageDB
+ Codex.Project: withinRange' :: Version -> VersionRange -> Bool
- Codex: data Verbosity :: *
+ Codex: data Verbosity
- Codex: type Action = EitherT String IO
+ Codex: type Action = ExceptT String IO
- Codex.Internal: stackListDependencies :: String -> IO [PackageIdentifier]
+ Codex.Internal: stackListDependencies :: String -> String -> IO [PackageIdentifier]
- Codex.Project: type ProjectDependencies = (PackageIdentifier, [PackageIdentifier], [WorkspaceProject])
+ Codex.Project: type ProjectDependencies = (Maybe PackageIdentifier, [PackageIdentifier], [WorkspaceProject])
Files
- CHANGELOG.md +18/−0
- README.md +58/−0
- codex.cabal +6/−9
- codex/Main.hs +35/−14
- codex/Main/Config.hs +17/−0
- src/Codex.hs +4/−4
- src/Codex/Internal.hs +33/−8
- src/Codex/Project.hs +141/−29
+ CHANGELOG.md view
@@ -0,0 +1,18 @@+# CHANGELOG++<!-- +When editing this file, please include a link to the PR and/or issue for+the change. +-->++## 0.5.2.0++This CHANGELOG entry is incomplete, as it is reconstructed from the Git history+since the previous release.++- Fix `codex update` inside of cabal sandbox [#74](https://github.com/aloiscochard/codex/pull/74)+- Look for project packages in sub-directories [#76](https://github.com/aloiscochard/codex/pull/76)+- Improved error message if Hackage path not found [#79](https://github.com/aloiscochard/codex/pull/79)+- Use `stack ls dependencies` to avoid deprecation notice [#81](https://github.com/aloiscochard/codex/pull/81)+- Only use Stack when `.stack-work` is present [#87](https://github.com/aloiscochard/codex/pull/87)+- Support Cabal >= 2.2 [#88](https://github.com/aloiscochard/codex/pull/88)
+ README.md view
@@ -0,0 +1,58 @@+# Codex++[](http://hackage.haskell.org/package/codex)+[](https://travis-ci.org/aloiscochard/codex)+[](https://gitter.im/aloiscochard/codex?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)++This tool download and cache the source code of packages in your local hackage,+it can then use this local cache to generate a `tags` file aggregating the sources of all the dependencies of a given cabal/stack project.++You basically do `codex update` in your project directory and you'll get a file (`codex.tags` by default, or `TAGS` when using+emacs format) that you can use in your favorite text editor.++*By default the generated tags file will include tags of the current project as well, this functionality can be disabled in your `~/.codex` file.*++## Install++Codex is published on [Hackage](http://hackage.haskell.org/package/codex) (and also mirrored on [Stackage](https://www.stackage.org/package/codex) and [NixOS](http://hydra.nixos.org/job/nixpkgs/trunk/haskellPackages.codex.x86_64-linux)).++ cabal install codex++Note: You might have to install binary dependencies manually as cabal does not support them yet!++## Usage++By default `hasktags` will be used, and need to be in the `PATH`, the tagger command can be fully customized in `~/.codex`.++*The configuration file is automatically generated with default values when running the tool for the first time.*++ codex [update] [cache clean] [set tagger [hasktags|ctags]] [set format [vim|emacs|sublime]]++* **update**: Synchronize the tags file in the current project directory (use --force to discard tags file hash)+* **cache clean**: Remove all `tags` file from the local hackage cache+* **set tagger [hasktags|ctags]**: Update the `~/.codex` configuration file for the given tagger+* **set format [vim|emacs|sublime]**: Update the `~/.codex` configuration file for the given format++*Note: codex will browse the parent directory for cabal projects and use them as dependency over hackage when possible.*++## VIM++Put this in your `.vimrc`:++ set tags=tags;/,codex.tags;/+ +**IMPORTANT**: You must use a version >= 7.4 (or you'll get `E431: Format error in tags file "codex.tags"`)++## FAQ++- I get `commitBuffer: invalid argument (invalid character)` when trying to use `codex` under Windows, is this a compatibility problem?++ It's an general issue with codec (http://jaspervdj.be/hakyll/tutorials/faq.html#hgetcontents-invalid-argument-or-commitbuffer-invalid-argument), which can be solved by running the following commands:++ $ /c/Windows/System32/chcp.com 65001+ $ codex cache clean+ $ rm codex.tags+ $ codex update+ + (ref: https://github.com/aloiscochard/codex/issues/36)+
codex.cabal view
@@ -1,5 +1,5 @@ name: codex-version: 0.5.1.2+version: 0.5.2.0 synopsis: A ctags file generator for cabal project dependencies. description: This tool download and cache the source code of packages in your local hackage,@@ -19,6 +19,8 @@ category: Development build-type: Simple cabal-version: >=1.10+extra-source-files: README.md+ CHANGELOG.md library default-language: Haskell2010@@ -33,18 +35,17 @@ ascii-progress >= 0.3 , base >= 4.6.0.1 && < 5 , bytestring >= 0.10.0.2 && < 0.11- , Cabal >= 1.18 && < 1.25+ , Cabal >= 1.18 && < 2.5 , cryptohash >= 0.11 && < 0.12 , containers >= 0.5.0.0 && < 0.6 , directory >= 1.2.0.1 && < 1.4- , either >= 4.3.0.1 && < 4.5 , filepath >= 1.3.0.1 && < 1.5- , hackage-db >= 1.22 && < 2+ , hackage-db >= 1.22 && < 3 , http-client >= 0.4 && < 0.6 , lens >= 4.6 && < 5 , machines >= 0.2 && < 0.7 , machines-directory >= 0.0.0.2 && < 0.3- , process >= 1.2.3 && < 1.5+ , process >= 1.2.3 && < 1.7 , tar >= 0.4.0.1 && < 0.6 , text >= 1.1.1.3 && < 1.3 , transformers >= 0.3.0.0 && < 0.6@@ -68,13 +69,9 @@ ascii-progress , base , Cabal- , bytestring , directory- , either , filepath , hackage-db- , MissingH >= 1.2.1.0 && < 1.5- , monad-loops >= 0.4.2 && < 0.5 , network >= 2.6 && < 2.7 , process , transformers
codex/Main.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE CPP #-} +module Main (main) where+ #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>)) import Data.Traversable (traverse)@@ -9,10 +11,10 @@ import Control.Exception (try, SomeException) import Control.Monad import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Either hiding (left, right)+import Control.Monad.Trans.Except import Data.Either import Data.List-import Data.String.Utils+import qualified Distribution.Hackage.DB as DB import Distribution.Text import Network.Socket (withSocketsDo) import Network.Wreq.Session (withSession)@@ -55,7 +57,7 @@ safe = (try :: IO a -> IO (Either SomeException a)) listDirectory fp = do xs <- getDirectoryContents fp- return . fmap (fp </>) $ filter (not . startswith ".") xs+ return . fmap (fp </>) $ filter (not . isPrefixOf ".") xs removeTagFiles = traverse (safe . removeFile) . fmap (</> "tags") traverseDirectories = fmap rights . traverse (safe . listDirectory) builderOp (Stack _) = traverseDirectories . concat@@ -73,26 +75,39 @@ update :: Bool -> Codex -> Builder -> IO () update force cx bldr = displayConsoleRegions $ do- (project, dependencies, workspaceProjects') <- resolveCurrentProjectDependencies bldr $ hackagePath cx </> "00-index.tar"+#if MIN_VERSION_hackage_db(2,0,0)+ (mpid, dependencies, workspaceProjects') <- case bldr of+ Cabal -> do+ tb <- DB.hackageTarball+ resolveCurrentProjectDependencies bldr tb+ Stack _ -> resolveCurrentProjectDependencies bldr $ hackagePath cx+#else+ (mpid, dependencies, workspaceProjects') <-+ resolveCurrentProjectDependencies bldr (hackagePath cx)+#endif projectHash <- computeCurrentProjectHash cx- shouldUpdate <- if null workspaceProjects' then- either (const True) id <$> (runEitherT $ isUpdateRequired cx dependencies projectHash)+ either (const True) id <$> (runExceptT $ isUpdateRequired cx dependencies projectHash) else return True if force || shouldUpdate then do- let workspaceProjects = if not $ currentProjectIncluded cx then workspaceProjects'- else (WorkspaceProject project ".") : workspaceProjects'+ let workspaceProjects = if currentProjectIncluded cx+ then workspaceProjects'+ else filter (("." /=) . workspaceProjectPath) workspaceProjects' fileExist <- doesFileExist tagsFile when fileExist $ removeFile tagsFile- putStrLn $ concat ["Updating ", display project]+ putStrLn ("Updating: " ++ displayPackages mpid workspaceProjects) results <- withSession $ \s -> do tick' <- newProgressBar' "Loading tags" (length dependencies)- traverse (retrying 3 . runEitherT . getTags tick' s) dependencies+ traverse (retrying 3 . runExceptT . getTags tick' s) dependencies _ <- traverse print . concat $ lefts results- res <- runEitherT $ assembly bldr cx dependencies projectHash workspaceProjects tagsFile- either print (const $ return ()) res+ res <- runExceptT $ assembly bldr cx dependencies projectHash workspaceProjects tagsFile+ case res of+ Left e -> do+ print e+ exitFailure+ Right _ -> pure () else putStrLn "Nothing to update." where@@ -102,12 +117,17 @@ Source Tagged -> tick' >> return () Source Untagged -> tags bldr cx i >> tick' >> getTags tick' s i Archive -> extract hp i >> tick' >> getTags tick' s i- Remote -> liftIO $ eitherT ignore return $ fetch s hp i >> tick' >> getTags tick' s i+ Remote -> liftIO $ either ignore return <=< runExceptT $ fetch s hp i >> tick' >> getTags tick' s i where ignore msg = do putStrLn $ concat ["codex: *warning* unable to fetch an archive for ", display i] putStrLn msg return ()+ displayPackages mpid workspaceProjects =+ case mpid of+ Just p -> display p+ Nothing ->+ unwords (fmap (display . workspaceProjectIdentifier) workspaceProjects) help :: IO () help = putStrLn $@@ -147,7 +167,8 @@ TaggerNotFound -> fail' $ "codex: tagger not found." Ready -> do stackFileExists <- doesFileExist $ "." </> "stack.yaml"- if stackFileExists then do+ stackWorkExists <- doesDirectoryExist $ "." </> ".stack-work"+ if stackFileExists && stackWorkExists then do (ec, _, _) <- readCreateProcessWithExitCode (shell "which stack") "" case ec of ExitSuccess -> do
codex/Main/Config.hs view
@@ -1,5 +1,7 @@+{-# LANGUAGE CPP #-} module Main.Config where +import Control.Exception (catch) import Data.Yaml import System.Directory import System.FilePath@@ -12,6 +14,10 @@ import qualified Main.Config.Codex3 as C3 import qualified Distribution.Hackage.DB as DB +#if MIN_VERSION_hackage_db(2,0,0)+import qualified Distribution.Hackage.DB.Errors as Errors+#endif+ data ConfigState = Ready | TaggerNotFound getConfigPath :: IO FilePath@@ -31,7 +37,18 @@ loadConfig :: IO Codex loadConfig = decodeConfig >>= maybe defaultConfig return where defaultConfig = do+#if MIN_VERSION_hackage_db(2,0,0)+ hp <- DB.hackageTarball+ `catch` \Errors.NoHackageTarballFound -> do+ error $ unlines+ [ "couldn't find a Hackage tarball. This can happen if you use `stack` exclusively,"+ , "or just haven't run `cabal update` yet. To fix it, try running:"+ , ""+ , " cabal update"+ ]+#else hp <- DB.hackagePath+#endif let cx = Codex True (dropFileName hp) defaultStackOpts (taggerCmd Hasktags) True True defaultTagsFileName encodeConfig cx return cx
src/Codex.hs view
@@ -11,7 +11,7 @@ import Control.Lens.Review (bimap) import Control.Monad import Control.Monad.IO.Class-import Control.Monad.Trans.Either+import Control.Monad.Trans.Except import Data.Machine import Data.Maybe import Data.List ((\\))@@ -62,7 +62,7 @@ data Status = Source Tagging | Archive | Remote deriving (Eq, Show) -type Action = EitherT String IO+type Action = ExceptT String IO data Tagger = Ctags | Hasktags | HasktagsEmacs | HasktagsExtended deriving (Eq, Show, Read)@@ -84,7 +84,7 @@ tryIO :: IO a -> Action a tryIO io = do res <- liftIO $ (try :: IO a -> IO (Either SomeException a)) io- either (left . show) return res+ either (throwE . show) return res codexHash :: Codex -> String codexHash cfg = md5hash $ show cfg@@ -129,7 +129,7 @@ bs <- tryIO $ do createDirectoryIfMissing True (packagePath root i) openLazyURI s url- either left write bs where+ either throwE write bs where write bs = fmap (const archivePath) $ tryIO $ BS.writeFile archivePath bs archivePath = packageArchive root i url = packageUrl i
src/Codex/Internal.hs view
@@ -10,11 +10,13 @@ import Data.Char (isSpace) import Data.Yaml import Data.Maybe (mapMaybe)+import Data.Version (versionBranch, Version, parseVersion) import Distribution.Package import Distribution.Text import GHC.Generics import System.FilePath import System.Process (shell, readCreateProcess)+import Text.ParserCombinators.ReadP (readP_to_S) import qualified Data.List as L @@ -80,13 +82,36 @@ s <- readCreateProcess (shell cmd) "" return $ init s -stackListDependencies :: String -> IO [PackageIdentifier]-stackListDependencies opts = do- let cmd = concat ["stack ", opts, " list-dependencies"]- s <- readCreateProcess (shell cmd) ""- return $ mapMaybe parsePackageIdentifier $ lines s+stackListDependencies :: String -> String -> IO [PackageIdentifier]+stackListDependencies opts pname = do+ version <- readStackVersion+ let+ cmd =+ case versionBranch version of+ a : b : _+ | a <= 1+ , b <= 7+ -> concat ["stack ", opts, " list-dependencies", pname]+ _+ -> concat ["stack ", opts, " ls dependencies ", pname]+ s <- readCreateProcess (shell cmd) ""+ return $ mapMaybe parsePackageIdentifier $ lines s where parsePackageIdentifier line =- let line' = map (\c -> if c == ' ' then '-' else c)- line- in simpleParse line'+ let line' = map (\c -> if c == ' ' then '-' else c)+ line+ in simpleParse line'++readStackVersion :: IO Version+readStackVersion = do+ s <- readCreateProcess (shell "stack --version") ""+ let+ versionText =+ takeWhile (/= ',') (drop (length "Version ") s)+ parsed =+ readP_to_S parseVersion versionText+ case parsed of+ (v, _) : _ ->+ pure v+ _ ->+ error $ "Failed to parse stack version. Output: " ++ s
src/Codex/Project.hs view
@@ -6,15 +6,26 @@ import Data.Traversable (traverse) #endif +import Control.Applicative ((<|>)) import Control.Exception (try, SomeException)-import Control.Monad+import Control.Monad (filterM)+import Data.Bool (bool) import Data.Function+import Data.List (delete, isPrefixOf, union) import Data.Maybe import Distribution.InstalledPackageInfo+#if MIN_VERSION_hackage_db(2,0,0)+import Distribution.Hackage.DB (HackageDB, cabalFile, readTarball)+#else import Distribution.Hackage.DB (Hackage, readHackage')+#endif import Distribution.Package import Distribution.PackageDescription+#if MIN_VERSION_Cabal(2,2,0)+import Distribution.PackageDescription.Parsec+#else import Distribution.PackageDescription.Parse+#endif import Distribution.Sandbox.Utils (findSandbox) import Distribution.Simple.Configure import Distribution.Simple.LocalBuildInfo@@ -22,20 +33,29 @@ import Distribution.Verbosity import Distribution.Version import System.Directory+import System.Environment (lookupEnv) import System.FilePath+import Text.Read (readMaybe) import qualified Data.List as List import qualified Data.Map as Map+#if !MIN_VERSION_hackage_db(2,0,0)+import qualified Data.Version as Base+#endif import Codex.Internal (Builder(..), stackListDependencies) +#if MIN_VERSION_hackage_db(2,0,0)+type Hackage = HackageDB+#endif+ newtype Workspace = Workspace [WorkspaceProject] deriving (Eq, Show) data WorkspaceProject = WorkspaceProject { workspaceProjectIdentifier :: PackageIdentifier, workspaceProjectPath :: FilePath } deriving (Eq, Show) -type ProjectDependencies = (PackageIdentifier, [PackageIdentifier], [WorkspaceProject])+type ProjectDependencies = (Maybe PackageIdentifier, [PackageIdentifier], [WorkspaceProject]) identifier :: GenericPackageDescription -> PackageIdentifier identifier = package . packageDescription@@ -50,15 +70,61 @@ findPackageDescription :: FilePath -> IO (Maybe GenericPackageDescription) findPackageDescription root = do- contents <- getDirectoryContents root- files <- filterM (doesFileExist . (</>) root) contents- traverse (readPackageDescription silent) $ fmap (\x -> root </> x) $ List.find (List.isSuffixOf ".cabal") files+ mpath <- findCabalFilePath root+ traverse (+#if MIN_VERSION_Cabal(2,2,0)+ readGenericPackageDescription+#else+ readPackageDescription+#endif+ silent) mpath +-- | Find a regular file ending with ".cabal" within a directory.+findCabalFilePath :: FilePath -> IO (Maybe FilePath)+findCabalFilePath path = do+ paths <- getDirectoryContents path+ case List.find ((&&) <$> dotCabal <*> visible) paths of+ Just p -> do+ let p' = path </> p+ bool Nothing (Just p') <$> doesFileExist p'+ Nothing -> pure Nothing+ where+ dotCabal = (".cabal" ==) . takeExtension+ visible = not . List.isPrefixOf "."+ resolveCurrentProjectDependencies :: Builder -> FilePath -> IO ProjectDependencies resolveCurrentProjectDependencies bldr hackagePath = do- ws <- getWorkspace ".."- resolveProjectDependencies bldr ws hackagePath "."+ mps <- localPackages+ case mps of+ Just ps -> resolveLocalDependencies bldr hackagePath ps+ Nothing -> do+ disableImplicitWorkspace <- isJust <$> lookupEnv "CODEX_DISABLE_WORKSPACE"+ ws <- if disableImplicitWorkspace+ then pure (Workspace [])+ else getWorkspace ".."+ resolveProjectDependencies bldr ws hackagePath "."+ where+ localPackages = do+ mpath <-+ case bldr of+ Cabal -> bool Nothing (Just ".") <$> doesFileExist "cabal.project"+ Stack _ -> pure (Just ".")+ case mpath of+ Nothing -> pure Nothing+ Just path -> Just <$> findLocalPackages 2 path +-- | Resolve the dependencies of each local project package.+resolveLocalDependencies :: Builder -> FilePath -> [WorkspaceProject] -> IO ProjectDependencies+resolveLocalDependencies bldr hackagePath wps = do+ pids <- foldr mergeDependencies mempty <$> traverse resolve wps+ pure (Nothing, pids, wps)+ where+ resolve p@WorkspaceProject{workspaceProjectPath = packagePath} =+ let ws' = Workspace (delete p wps)+ in resolveProjectDependencies bldr ws' hackagePath packagePath+ mergeDependencies (_, pids, _) pids' =+ pids `union` pids'+ -- TODO Optimize resolveProjectDependencies :: Builder -> Workspace -> FilePath -> FilePath -> IO ProjectDependencies resolveProjectDependencies bldr ws hackagePath root = do@@ -67,8 +133,9 @@ ys <- resolveSandboxDependencies root let zs = resolveWorkspaceDependencies ws pd let wsds = List.filter (shouldOverride xs) $ List.nubBy (on (==) prjId) $ concat [ys, zs]- let pjds = List.filter (\x -> List.notElem (pkgName x) $ fmap prjId wsds) xs- return (identifier pd, pjds, wsds) where+ let pjds = List.filter (\x -> (((unPackageName . pkgName) x) /= "rts") && (List.notElem (pkgName x) $ fmap prjId wsds)) xs+ return (Just (identifier pd), pjds, wsds)+ where shouldOverride xs (WorkspaceProject x _) = maybe True (\y -> pkgVersion x >= pkgVersion y) $ List.find (\y -> pkgName x == pkgName y) xs prjId = pkgName . workspaceProjectIdentifier@@ -79,22 +146,53 @@ Cabal -> do lbi <- withCabal let ipkgs = installedPkgs lbi- clbis = snd <$> allComponentsInBuildOrder lbi+ clbis = allComponentsInBuildOrder' lbi pkgs = componentPackageDeps =<< clbis ys = (maybeToList . lookupInstalledPackageId ipkgs) =<< fmap fst pkgs xs = fmap sourcePackageId $ ys return xs where withCabal = getPersistBuildConfig $ root </> "dist"- Stack cmd -> let self = package (packageDescription pd)- in filter (/=self) <$> stackListDependencies cmd+ Stack cmd ->+ filter (/= pid) <$> stackListDependencies cmd pname+ where+ pid = pd & packageDescription & package+ pname = pid & pkgName & unPackageName +allComponentsInBuildOrder' :: LocalBuildInfo -> [ComponentLocalBuildInfo]+allComponentsInBuildOrder' =+#if MIN_VERSION_Cabal(2,0,0)+ allComponentsInBuildOrder+#else+ fmap snd . allComponentsInBuildOrder+#endif+ resolveHackageDependencies :: Hackage -> GenericPackageDescription -> [GenericPackageDescription] resolveHackageDependencies db pd = maybeToList . resolveDependency db =<< allDependencies pd where- resolveDependency _ (Dependency (PackageName name) versionRange) = do- pdsByVersion <- Map.lookup name db- latest <- List.find (\x -> withinRange x versionRange) $ List.reverse $ List.sort $ Map.keys pdsByVersion- Map.lookup latest pdsByVersion+ resolveDependency _ (Dependency name versionRange) = do+ pdsByVersion <- lookupName name+ latest <- List.find (\x -> withinRange' x versionRange) $ List.reverse $ List.sort $ Map.keys pdsByVersion+ lookupVersion latest pdsByVersion+#if MIN_VERSION_hackage_db(2,0,0)+ lookupName name = Map.lookup name db+ lookupVersion latest pdsByVersion = cabalFile <$> Map.lookup latest pdsByVersion+#else+ lookupName name = Map.lookup (unPackageName name) db+ lookupVersion latest pdsByVersion = Map.lookup latest pdsByVersion+#endif +#if MIN_VERSION_hackage_db(2,0,0)+withinRange' :: Version -> VersionRange -> Bool+withinRange' = withinRange+#else+withinRange' :: Base.Version -> VersionRange -> Bool+withinRange' =+#if MIN_VERSION_Cabal(2,0,0)+ withinRange . mkVersion'+#else+ withinRange+#endif+#endif+ resolvePackageDependencies :: Builder -> FilePath -> FilePath -> GenericPackageDescription -> IO [PackageIdentifier] resolvePackageDependencies bldr hackagePath root pd = do xs <- either fallback return =<< resolveInstalledDependencies bldr root pd@@ -104,7 +202,13 @@ putStrLn "codex: *warning* falling back on dependency resolution using hackage" resolveWithHackage resolveWithHackage = do- db <- readHackage' hackagePath+#if MIN_VERSION_hackage_db(2,0,0)+ db <- readTarball Nothing (hackagePath </> "00-index.tar")+ <|> readTarball Nothing (hackagePath </> "01-index.tar")+#else+ db <- readHackage' (hackagePath </> "00-index.tar")+ <|> readHackage' (hackagePath </> "01-index.tar")+#endif return $ identifier <$> resolveHackageDependencies db pd resolveSandboxDependencies :: FilePath -> IO [WorkspaceProject]@@ -123,7 +227,7 @@ projects :: String -> [FilePath] projects x = sources x >>= (\x' -> fst <$> snd x') sources :: String -> [(String, [(FilePath, Int)])]- sources x = read x+ sources x = fromMaybe [] (readMaybe x) resolveWorkspaceDependencies :: Workspace -> GenericPackageDescription -> [WorkspaceProject] resolveWorkspaceDependencies (Workspace ws) pd = maybeToList . resolveDependency =<< allDependencies pd where@@ -136,14 +240,22 @@ return $ fmap (\x -> WorkspaceProject (identifier x) path) pd getWorkspace :: FilePath -> IO Workspace-getWorkspace _root = do- root <- canonicalizePath _root- xs <- listDirectory root- ys <- traverse find xs- return . Workspace $ ys >>= maybeToList where- find path = do- isDirectory <- doesDirectoryExist path- if isDirectory then readWorkspaceProject path else return Nothing- listDirectory fp = do- xs <- getDirectoryContents fp- return . fmap (fp </>) $ filter (not . List.isPrefixOf ".") xs+getWorkspace root =+ Workspace <$> findLocalPackages 1 root++-- | Recursively find local packages in @root@, up to @depth@ layers deep. The+-- @root@ directory has a depth of 0.+findLocalPackages :: Int -> FilePath -> IO [WorkspaceProject]+findLocalPackages depth root =+ catMaybes <$> go depth root+ where+ go n path+ | n < 0 = pure []+ | otherwise =+ (:) <$> readWorkspaceProject path+ <*> fmap mconcat (traverse (go (n - 1)) =<< listDirectories path)+ listDirectories path = do+ paths <- getDirectoryContents =<< canonicalizePath path+ filterM doesDirectoryExist ((path </>) <$> filter visible paths)+ visible path =+ (not . isPrefixOf ".") path && path `notElem` ["dist", "dist-new"]