hsdev 0.1.6.6 → 0.1.7.0
raw patch · 34 files changed
+1324/−525 lines, 34 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- HsDev.Cabal: Cabal :: Cabal
- HsDev.Cabal: Sandbox :: FilePath -> Cabal
- HsDev.Cabal: cabalOpt :: Cabal -> [String]
- HsDev.Cabal: data Cabal
- HsDev.Cabal: findPackageDb :: FilePath -> IO (Maybe FilePath)
- HsDev.Cabal: getSandbox :: FilePath -> IO Cabal
- HsDev.Cabal: instance Control.DeepSeq.NFData HsDev.Cabal.Cabal
- HsDev.Cabal: instance Data.Aeson.Types.Class.FromJSON HsDev.Cabal.Cabal
- HsDev.Cabal: instance Data.Aeson.Types.Class.ToJSON HsDev.Cabal.Cabal
- HsDev.Cabal: instance GHC.Classes.Eq HsDev.Cabal.Cabal
- HsDev.Cabal: instance GHC.Classes.Ord HsDev.Cabal.Cabal
- HsDev.Cabal: instance GHC.Show.Show HsDev.Cabal.Cabal
- HsDev.Cabal: isPackageDb :: FilePath -> Bool
- HsDev.Cabal: locateSandbox :: FilePath -> ExceptT String IO Cabal
- HsDev.Cabal: sandbox :: Cabal -> Maybe FilePath
- HsDev.Cabal: searchSandbox :: FilePath -> IO Cabal
- HsDev.Cache: cabalCache :: Cabal -> FilePath
- HsDev.Cache.Structured: loadCabal :: Cabal -> FilePath -> ExceptT String IO Structured
- HsDev.Commands: restrictCabal :: Cabal -> ModuleId -> Bool
- HsDev.Database: [structuredCabals] :: Structured -> Map Cabal Database
- HsDev.Database: cabalDB :: Cabal -> Database -> Database
- HsDev.Display: instance HsDev.Display.Display HsDev.Cabal.Cabal
- HsDev.Display: instance Text.Format.FormatBuild HsDev.Cabal.Cabal
- HsDev.Project: getProjectSandbox :: Project -> IO Cabal
- HsDev.Scan: enumCabal :: [String] -> Cabal -> ExceptT String IO [ModuleLocation]
- HsDev.Scan: type SandboxToScan = Cabal
- HsDev.Symbols: instance System.Directory.Paths.Paths HsDev.Cabal.Cabal
- HsDev.Symbols.Location: CabalModule :: Cabal -> Maybe ModulePackage -> String -> ModuleLocation
- HsDev.Symbols.Location: [_moduleCabal] :: ModuleLocation -> Cabal
- HsDev.Symbols.Location: moduleCabal :: Traversal' ModuleLocation Cabal
- HsDev.Symbols.Util: byCabal :: ModuleId -> Bool
- HsDev.Symbols.Util: cabalOf :: ModuleId -> Maybe Cabal
- HsDev.Symbols.Util: inCabal :: Cabal -> ModuleId -> Bool
- HsDev.Tools.Ghc.Check: Cabal :: Cabal
- HsDev.Tools.Ghc.Check: Sandbox :: FilePath -> Cabal
- HsDev.Tools.Ghc.Check: data Cabal
- HsDev.Watcher: unwatchSandbox :: Watcher -> Cabal -> IO ()
- HsDev.Watcher: watchSandbox :: Watcher -> Cabal -> [String] -> IO ()
- HsDev.Watcher.Types: WatchedSandbox :: Cabal -> [String] -> Watched
- HsDev.Watcher.Types: data Cabal
+ Data.Deps: Deps :: Map a [a] -> Deps a
+ Data.Deps: [_depsMap] :: Deps a -> Map a [a]
+ Data.Deps: data Deps a
+ Data.Deps: dep :: Ord a => a -> a -> Deps a
+ Data.Deps: deps :: Ord a => a -> [a] -> Deps a
+ Data.Deps: depsMap :: (Ord a, Ord b) => Lens (Deps a) (Deps b) (Map a [a]) (Map b [b])
+ Data.Deps: flatten :: Ord a => Deps a -> Deps a
+ Data.Deps: instance GHC.Classes.Ord a => Control.Lens.At.At (Data.Deps.Deps a)
+ Data.Deps: instance GHC.Classes.Ord a => Control.Lens.At.Ixed (Data.Deps.Deps a)
+ Data.Deps: instance GHC.Classes.Ord a => GHC.Base.Monoid (Data.Deps.Deps a)
+ Data.Deps: inverse :: Ord a => Deps a -> Deps a
+ Data.Deps: mapDeps :: (Ord a, Ord b) => (a -> b) -> Deps a -> Deps b
+ HsDev.Cache: packageDbCache :: PackageDb -> FilePath
+ HsDev.Cache: readVersion :: FilePath -> IO (Maybe [Int])
+ HsDev.Cache: versionCache :: FilePath
+ HsDev.Cache: writeVersion :: FilePath -> IO ()
+ HsDev.Cache.Structured: loadPackageDb :: PackageDb -> FilePath -> ExceptT String IO Structured
+ HsDev.Commands: restrictPackageDb :: PackageDb -> ModuleId -> Bool
+ HsDev.Commands: restrictPackageDbStack :: PackageDbStack -> ModuleId -> Bool
+ HsDev.Database: [structuredPackageDbs] :: Structured -> Map PackageDb Database
+ HsDev.Database: databasePackageDbs :: Database -> [PackageDb]
+ HsDev.Database: packageDbDB :: PackageDb -> Database -> Database
+ HsDev.Database: packageDbStackDB :: PackageDbStack -> Database -> Database
+ HsDev.Database.Update: prepareSandbox :: UpdateMonad m => Sandbox -> m ()
+ HsDev.Database.Update: scanContents :: UpdateMonad m => [String] -> ScanContents -> m ()
+ HsDev.Database.Update: scanPackageDb :: UpdateMonad m => [String] -> PackageDbStack -> m ()
+ HsDev.Database.Update: scanProjectStack :: UpdateMonad m => [String] -> FilePath -> m ()
+ HsDev.Database.Update: scanSandbox :: UpdateMonad m => [String] -> Sandbox -> m ()
+ HsDev.Display: instance HsDev.Display.Display HsDev.PackageDb.PackageDb
+ HsDev.Display: instance HsDev.Display.Display HsDev.Sandbox.Sandbox
+ HsDev.Display: instance Text.Format.FormatBuild HsDev.PackageDb.PackageDb
+ HsDev.PackageDb: GlobalDb :: PackageDb
+ HsDev.PackageDb: PackageDb :: FilePath -> PackageDb
+ HsDev.PackageDb: PackageDbStack :: [PackageDb] -> PackageDbStack
+ HsDev.PackageDb: UserDb :: PackageDb
+ HsDev.PackageDb: [_packageDbStack] :: PackageDbStack -> [PackageDb]
+ HsDev.PackageDb: [_packageDb] :: PackageDb -> FilePath
+ HsDev.PackageDb: data PackageDb
+ HsDev.PackageDb: fromPackageDb :: FilePath -> PackageDbStack
+ HsDev.PackageDb: fromPackageDbs :: [FilePath] -> PackageDbStack
+ HsDev.PackageDb: globalDb :: PackageDbStack
+ HsDev.PackageDb: instance Control.DeepSeq.NFData HsDev.PackageDb.PackageDb
+ HsDev.PackageDb: instance Control.DeepSeq.NFData HsDev.PackageDb.PackageDbStack
+ HsDev.PackageDb: instance Data.Aeson.Types.Class.FromJSON HsDev.PackageDb.PackageDb
+ HsDev.PackageDb: instance Data.Aeson.Types.Class.FromJSON HsDev.PackageDb.PackageDbStack
+ HsDev.PackageDb: instance Data.Aeson.Types.Class.ToJSON HsDev.PackageDb.PackageDb
+ HsDev.PackageDb: instance Data.Aeson.Types.Class.ToJSON HsDev.PackageDb.PackageDbStack
+ HsDev.PackageDb: instance GHC.Classes.Eq HsDev.PackageDb.PackageDb
+ HsDev.PackageDb: instance GHC.Classes.Eq HsDev.PackageDb.PackageDbStack
+ HsDev.PackageDb: instance GHC.Classes.Ord HsDev.PackageDb.PackageDb
+ HsDev.PackageDb: instance GHC.Classes.Ord HsDev.PackageDb.PackageDbStack
+ HsDev.PackageDb: instance GHC.Read.Read HsDev.PackageDb.PackageDb
+ HsDev.PackageDb: instance GHC.Read.Read HsDev.PackageDb.PackageDbStack
+ HsDev.PackageDb: instance GHC.Show.Show HsDev.PackageDb.PackageDb
+ HsDev.PackageDb: instance GHC.Show.Show HsDev.PackageDb.PackageDbStack
+ HsDev.PackageDb: instance System.Directory.Paths.Paths HsDev.PackageDb.PackageDb
+ HsDev.PackageDb: instance System.Directory.Paths.Paths HsDev.PackageDb.PackageDbStack
+ HsDev.PackageDb: isSubStack :: PackageDbStack -> PackageDbStack -> Bool
+ HsDev.PackageDb: newtype PackageDbStack
+ HsDev.PackageDb: packageDb :: Traversal' PackageDb FilePath
+ HsDev.PackageDb: packageDbOpt :: PackageDb -> String
+ HsDev.PackageDb: packageDbStack :: Iso' PackageDbStack [PackageDb]
+ HsDev.PackageDb: packageDbStackOpts :: PackageDbStack -> [String]
+ HsDev.PackageDb: packageDbStacks :: PackageDbStack -> [PackageDbStack]
+ HsDev.PackageDb: packageDbs :: PackageDbStack -> [PackageDb]
+ HsDev.PackageDb: topPackageDb :: PackageDbStack -> PackageDb
+ HsDev.PackageDb: userDb :: PackageDbStack
+ HsDev.Project: [_projectVersion] :: ProjectDescription -> String
+ HsDev.Project: instance GHC.Base.Monoid HsDev.Project.Info
+ HsDev.Project: projectVersion :: Lens' ProjectDescription String
+ HsDev.Project: targetOpts :: Info -> [String]
+ HsDev.Sandbox: CabalSandbox :: SandboxType
+ HsDev.Sandbox: Sandbox :: SandboxType -> FilePath -> Sandbox
+ HsDev.Sandbox: StackWork :: SandboxType
+ HsDev.Sandbox: [_sandboxType] :: Sandbox -> SandboxType
+ HsDev.Sandbox: [_sandbox] :: Sandbox -> FilePath
+ HsDev.Sandbox: cabalSandboxLib :: ExceptT String IO FilePath
+ HsDev.Sandbox: cabalSandboxPackageDb :: ExceptT String IO FilePath
+ HsDev.Sandbox: data Sandbox
+ HsDev.Sandbox: data SandboxType
+ HsDev.Sandbox: findSandbox :: FilePath -> IO (Maybe Sandbox)
+ HsDev.Sandbox: guessSandboxType :: FilePath -> Maybe SandboxType
+ HsDev.Sandbox: instance Control.DeepSeq.NFData HsDev.Sandbox.Sandbox
+ HsDev.Sandbox: instance Control.DeepSeq.NFData HsDev.Sandbox.SandboxType
+ HsDev.Sandbox: instance Data.Aeson.Types.Class.FromJSON HsDev.Sandbox.Sandbox
+ HsDev.Sandbox: instance Data.Aeson.Types.Class.ToJSON HsDev.Sandbox.Sandbox
+ HsDev.Sandbox: instance GHC.Classes.Eq HsDev.Sandbox.Sandbox
+ HsDev.Sandbox: instance GHC.Classes.Eq HsDev.Sandbox.SandboxType
+ HsDev.Sandbox: instance GHC.Classes.Ord HsDev.Sandbox.Sandbox
+ HsDev.Sandbox: instance GHC.Classes.Ord HsDev.Sandbox.SandboxType
+ HsDev.Sandbox: instance GHC.Enum.Bounded HsDev.Sandbox.SandboxType
+ HsDev.Sandbox: instance GHC.Enum.Enum HsDev.Sandbox.SandboxType
+ HsDev.Sandbox: instance GHC.Read.Read HsDev.Sandbox.SandboxType
+ HsDev.Sandbox: instance GHC.Show.Show HsDev.Sandbox.Sandbox
+ HsDev.Sandbox: instance GHC.Show.Show HsDev.Sandbox.SandboxType
+ HsDev.Sandbox: instance System.Directory.Paths.Paths HsDev.Sandbox.Sandbox
+ HsDev.Sandbox: isSandbox :: FilePath -> Bool
+ HsDev.Sandbox: restorePackageDbStack :: PackageDb -> IO PackageDbStack
+ HsDev.Sandbox: sandbox :: Lens' Sandbox FilePath
+ HsDev.Sandbox: sandboxFromPath :: FilePath -> Maybe Sandbox
+ HsDev.Sandbox: sandboxPackageDbStack :: Sandbox -> ExceptT String IO PackageDbStack
+ HsDev.Sandbox: sandboxType :: Lens' Sandbox SandboxType
+ HsDev.Sandbox: searchPackageDbStack :: FilePath -> IO PackageDbStack
+ HsDev.Sandbox: searchSandbox :: FilePath -> IO (Maybe Sandbox)
+ HsDev.Scan: class EnumContents a
+ HsDev.Scan: enumContents :: EnumContents a => a -> ExceptT String IO ScanContents
+ HsDev.Scan: enumSandbox :: Sandbox -> ExceptT String IO ScanContents
+ HsDev.Scan: instance GHC.Base.Monoid HsDev.Scan.ScanContents
+ HsDev.Scan: instance HsDev.Scan.EnumContents (HsDev.Project.Extensions HsDev.Symbols.Location.ModuleLocation)
+ HsDev.Scan: instance HsDev.Scan.EnumContents GHC.IO.FilePath
+ HsDev.Scan: instance HsDev.Scan.EnumContents HsDev.PackageDb.PackageDbStack
+ HsDev.Scan: instance HsDev.Scan.EnumContents HsDev.Project.Project
+ HsDev.Scan: instance HsDev.Scan.EnumContents HsDev.Sandbox.Sandbox
+ HsDev.Scan: instance HsDev.Scan.EnumContents HsDev.Server.Types.FileContents
+ HsDev.Scan: instance HsDev.Scan.EnumContents HsDev.Symbols.Location.ModuleLocation
+ HsDev.Scan: instance HsDev.Scan.EnumContents a => HsDev.Scan.EnumContents [a]
+ HsDev.Scan: instance Text.Format.FormatBuild HsDev.Scan.ScanContents
+ HsDev.Scan: type PackageDbToScan = PackageDbStack
+ HsDev.Scan.Browse: browseDb :: [String] -> PackageDbStack -> ExceptT String IO [InspectedModule]
+ HsDev.Scan.Browse: browsePackagesDeps :: [String] -> PackageDbStack -> ExceptT String IO (Deps ModulePackage)
+ HsDev.Scan.Browse: ghcModuleLocation :: PackageDb -> PackageConfig -> Module -> ModuleLocation
+ HsDev.Scan.Browse: ghcPackageDb :: PackageConfig -> IO PackageDb
+ HsDev.Scan.Browse: packageConfigs :: GhcMonad m => m [PackageConfig]
+ HsDev.Scan.Browse: packageDbCandidate :: FilePath -> IO (Maybe PackageDb)
+ HsDev.Scan.Browse: packageDbCandidate_ :: FilePath -> IO PackageDb
+ HsDev.Scan.Browse: readPackage :: PackageConfig -> ModulePackage
+ HsDev.Server.Types: NetworkPort :: Int -> ConnectionPort
+ HsDev.Server.Types: TargetPackageDb :: PackageDb -> TargetFilter
+ HsDev.Server.Types: TargetSandbox :: FilePath -> TargetFilter
+ HsDev.Server.Types: UnixPort :: String -> ConnectionPort
+ HsDev.Server.Types: [removeCabal] :: Command -> Bool
+ HsDev.Server.Types: [scanCabal] :: Command -> Bool
+ HsDev.Server.Types: data ConnectionPort
+ HsDev.Server.Types: instance Data.Default.Class.Default HsDev.Server.Types.ConnectionPort
+ HsDev.Server.Types: instance GHC.Classes.Eq HsDev.Server.Types.ConnectionPort
+ HsDev.Server.Types: instance GHC.Read.Read HsDev.Server.Types.ConnectionPort
+ HsDev.Server.Types: instance GHC.Show.Show HsDev.Server.Types.ConnectionPort
+ HsDev.Server.Types: instance Text.Format.FormatBuild HsDev.Server.Types.ConnectionPort
+ HsDev.Stack: MaybeT :: m (Maybe a) -> MaybeT a
+ HsDev.Stack: StackEnv :: FilePath -> FilePath -> FilePath -> FilePath -> FilePath -> FilePath -> StackEnv
+ HsDev.Stack: [_stackConfig] :: StackEnv -> FilePath
+ HsDev.Stack: [_stackGhc] :: StackEnv -> FilePath
+ HsDev.Stack: [_stackLocal] :: StackEnv -> FilePath
+ HsDev.Stack: [_stackProject] :: StackEnv -> FilePath
+ HsDev.Stack: [_stackRoot] :: StackEnv -> FilePath
+ HsDev.Stack: [_stackSnapshot] :: StackEnv -> FilePath
+ HsDev.Stack: [runMaybeT] :: MaybeT a -> m (Maybe a)
+ HsDev.Stack: build :: [String] -> Maybe FilePath -> MaybeT IO ()
+ HsDev.Stack: buildDeps :: Maybe FilePath -> MaybeT IO ()
+ HsDev.Stack: configure :: Maybe FilePath -> MaybeT IO ()
+ HsDev.Stack: data StackEnv
+ HsDev.Stack: getStackEnv :: Paths -> Maybe StackEnv
+ HsDev.Stack: newtype MaybeT (m :: * -> *) a :: (* -> *) -> * -> *
+ HsDev.Stack: path :: Maybe FilePath -> MaybeT IO Paths
+ HsDev.Stack: pathOf :: String -> Lens' Paths (Maybe FilePath)
+ HsDev.Stack: projectEnv :: FilePath -> MaybeT IO StackEnv
+ HsDev.Stack: stack :: [String] -> MaybeT IO String
+ HsDev.Stack: stackConfig :: Lens' StackEnv FilePath
+ HsDev.Stack: stackGhc :: Lens' StackEnv FilePath
+ HsDev.Stack: stackLocal :: Lens' StackEnv FilePath
+ HsDev.Stack: stackPackageDbStack :: Lens' StackEnv PackageDbStack
+ HsDev.Stack: stackProject :: Lens' StackEnv FilePath
+ HsDev.Stack: stackRoot :: Lens' StackEnv FilePath
+ HsDev.Stack: stackSnapshot :: Lens' StackEnv FilePath
+ HsDev.Stack: yaml :: Maybe FilePath -> [String]
+ HsDev.Symbols.Location: InstalledModule :: PackageDb -> Maybe ModulePackage -> String -> ModuleLocation
+ HsDev.Symbols.Location: [_modulePackageDb] :: ModuleLocation -> PackageDb
+ HsDev.Symbols.Location: modulePackageDb :: Traversal' ModuleLocation PackageDb
+ HsDev.Symbols.Util: inPackageDb :: PackageDb -> ModuleId -> Bool
+ HsDev.Symbols.Util: inPackageDbStack :: PackageDbStack -> ModuleId -> Bool
+ HsDev.Symbols.Util: installed :: ModuleId -> Bool
+ HsDev.Symbols.Util: packageDbOf :: ModuleId -> Maybe PackageDb
+ HsDev.Tools.Ghc.Check: GlobalDb :: PackageDb
+ HsDev.Tools.Ghc.Check: PackageDb :: FilePath -> PackageDb
+ HsDev.Tools.Ghc.Check: PackageDbStack :: [PackageDb] -> PackageDbStack
+ HsDev.Tools.Ghc.Check: UserDb :: PackageDb
+ HsDev.Tools.Ghc.Check: [_packageDbStack] :: PackageDbStack -> [PackageDb]
+ HsDev.Tools.Ghc.Check: [_packageDb] :: PackageDb -> FilePath
+ HsDev.Tools.Ghc.Check: data PackageDb
+ HsDev.Tools.Ghc.Check: newtype PackageDbStack
+ HsDev.Tools.Ghc.Worker: ghcRun :: [String] -> Ghc a -> Ghc a
+ HsDev.Tools.Ghc.Worker: setCmdOpts :: [String] -> Ghc ()
+ HsDev.Util: cutVersion :: Maybe [Int] -> Maybe [Int]
+ HsDev.Util: sameVersion :: Maybe [Int] -> Maybe [Int] -> Bool
+ HsDev.Util: strVersion :: Maybe [Int] -> String
+ HsDev.Util: version :: Maybe [Int]
+ HsDev.Watcher: unwatchPackageDb :: Watcher -> PackageDb -> IO ()
+ HsDev.Watcher: watchPackageDb :: Watcher -> PackageDbStack -> [String] -> IO ()
+ HsDev.Watcher: watchPackageDbStack :: Watcher -> PackageDbStack -> [String] -> IO ()
+ HsDev.Watcher.Types: WatchedPackageDb :: PackageDbStack -> [String] -> Watched
+ HsDev.Watcher.Types: data PackageDbStack
- HsDev.Commands: completions :: Database -> Cabal -> FilePath -> String -> Bool -> ExceptT String IO [Declaration]
+ HsDev.Commands: completions :: Database -> FilePath -> String -> Bool -> ExceptT String IO [Declaration]
- HsDev.Commands: lookupSymbol :: Database -> Cabal -> FilePath -> String -> ExceptT String IO [ModuleDeclaration]
+ HsDev.Commands: lookupSymbol :: Database -> FilePath -> String -> ExceptT String IO [ModuleDeclaration]
- HsDev.Commands: scope :: Database -> Cabal -> FilePath -> Bool -> ExceptT String IO [Declaration]
+ HsDev.Commands: scope :: Database -> FilePath -> Bool -> ExceptT String IO [Declaration]
- HsDev.Commands: scopeModules :: Database -> Cabal -> FilePath -> ExceptT String IO [Module]
+ HsDev.Commands: scopeModules :: Database -> FilePath -> ExceptT String IO [Module]
- HsDev.Commands: whois :: Database -> Cabal -> FilePath -> String -> ExceptT String IO [Declaration]
+ HsDev.Commands: whois :: Database -> FilePath -> String -> ExceptT String IO [Declaration]
- HsDev.Database: Structured :: Map Cabal Database -> Map FilePath Database -> Database -> Structured
+ HsDev.Database: Structured :: Map PackageDb Database -> Map FilePath Database -> Database -> Structured
- HsDev.Database.Update: scanCabal :: UpdateMonad m => [String] -> Cabal -> m ()
+ HsDev.Database.Update: scanCabal :: UpdateMonad m => [String] -> m ()
- HsDev.Project: ProjectDescription :: Maybe Library -> [Executable] -> [Test] -> ProjectDescription
+ HsDev.Project: ProjectDescription :: String -> Maybe Library -> [Executable] -> [Test] -> ProjectDescription
- HsDev.Project: entity :: Lens (Extensions a_aYaU) (Extensions a_a10jh) a_aYaU a_a10jh
+ HsDev.Project: entity :: Lens (Extensions a_aZFP) (Extensions a_a11PZ) a_aZFP a_a11PZ
- HsDev.Project: extensions :: Lens' (Extensions a_aYaU) [Extension]
+ HsDev.Project: extensions :: Lens' (Extensions a_aZFP) [Extension]
- HsDev.Project: ghcOptions :: Lens' (Extensions a_aYaU) [String]
+ HsDev.Project: ghcOptions :: Lens' (Extensions a_aZFP) [String]
- HsDev.Scan: ScanContents :: [ModuleToScan] -> [ProjectToScan] -> [SandboxToScan] -> ScanContents
+ HsDev.Scan: ScanContents :: [ModuleToScan] -> [ProjectToScan] -> [PackageDbStack] -> ScanContents
- HsDev.Scan: [sandboxesToScan] :: ScanContents -> [SandboxToScan]
+ HsDev.Scan: [sandboxesToScan] :: ScanContents -> [PackageDbStack]
- HsDev.Scan: enumProject :: Project -> ExceptT String IO ProjectToScan
+ HsDev.Scan: enumProject :: Project -> ExceptT String IO ScanContents
- HsDev.Scan: scanModify :: ([String] -> Cabal -> Module -> ExceptT String IO Module) -> InspectedModule -> ExceptT String IO InspectedModule
+ HsDev.Scan: scanModify :: ([String] -> PackageDbStack -> Module -> ExceptT String IO Module) -> InspectedModule -> ExceptT String IO InspectedModule
- HsDev.Scan.Browse: browse :: [String] -> Cabal -> ExceptT String IO [InspectedModule]
+ HsDev.Scan.Browse: browse :: [String] -> PackageDbStack -> ExceptT String IO [InspectedModule]
- HsDev.Scan.Browse: browseModules :: [String] -> Cabal -> [ModuleLocation] -> ExceptT String IO [InspectedModule]
+ HsDev.Scan.Browse: browseModules :: [String] -> PackageDbStack -> [ModuleLocation] -> ExceptT String IO [InspectedModule]
- HsDev.Scan.Browse: browsePackages :: [String] -> Cabal -> ExceptT String IO [ModulePackage]
+ HsDev.Scan.Browse: browsePackages :: [String] -> PackageDbStack -> ExceptT String IO [ModulePackage]
- HsDev.Scan.Browse: listModules :: [String] -> Cabal -> ExceptT String IO [ModuleLocation]
+ HsDev.Scan.Browse: listModules :: [String] -> PackageDbStack -> ExceptT String IO [ModuleLocation]
- HsDev.Server.Commands: ClientOpts :: Int -> Bool -> Bool -> Int -> Bool -> ClientOpts
+ HsDev.Server.Commands: ClientOpts :: ConnectionPort -> Bool -> Bool -> Int -> Bool -> ClientOpts
- HsDev.Server.Commands: ServerOpts :: Int -> Int -> Maybe FilePath -> String -> Maybe FilePath -> Bool -> ServerOpts
+ HsDev.Server.Commands: ServerOpts :: ConnectionPort -> Int -> Maybe FilePath -> String -> Maybe FilePath -> Bool -> ServerOpts
- HsDev.Server.Commands: [clientPort] :: ClientOpts -> Int
+ HsDev.Server.Commands: [clientPort] :: ClientOpts -> ConnectionPort
- HsDev.Server.Commands: [serverPort] :: ServerOpts -> Int
+ HsDev.Server.Commands: [serverPort] :: ServerOpts -> ConnectionPort
- HsDev.Server.Message: message :: Lens (Message a_aEsA) (Message a_aEIS) a_aEsA a_aEIS
+ HsDev.Server.Message: message :: Lens (Message a_aG5g) (Message a_aGg3) a_aG5g a_aGg3
- HsDev.Server.Message: messageId :: Lens' (Message a_aEsA) (Maybe String)
+ HsDev.Server.Message: messageId :: Lens' (Message a_aG5g) (Maybe String)
- HsDev.Server.Types: ClientOpts :: Int -> Bool -> Bool -> Int -> Bool -> ClientOpts
+ HsDev.Server.Types: ClientOpts :: ConnectionPort -> Bool -> Bool -> Int -> Bool -> ClientOpts
- HsDev.Server.Types: Remove :: [FilePath] -> [Cabal] -> [FilePath] -> Command
+ HsDev.Server.Types: Remove :: [FilePath] -> Bool -> [FilePath] -> [FilePath] -> Command
- HsDev.Server.Types: Scan :: [FilePath] -> [Cabal] -> [FilePath] -> [FilePath] -> [FileContents] -> [String] -> Bool -> Bool -> Command
+ HsDev.Server.Types: Scan :: [FilePath] -> Bool -> [FilePath] -> [FilePath] -> [FilePath] -> [FileContents] -> [String] -> Bool -> Bool -> Command
- HsDev.Server.Types: ServerOpts :: Int -> Int -> Maybe FilePath -> String -> Maybe FilePath -> Bool -> ServerOpts
+ HsDev.Server.Types: ServerOpts :: ConnectionPort -> Int -> Maybe FilePath -> String -> Maybe FilePath -> Bool -> ServerOpts
- HsDev.Server.Types: TargetCabal :: Cabal -> TargetFilter
+ HsDev.Server.Types: TargetCabal :: TargetFilter
- HsDev.Server.Types: [clientPort] :: ClientOpts -> Int
+ HsDev.Server.Types: [clientPort] :: ClientOpts -> ConnectionPort
- HsDev.Server.Types: [removeSandboxes] :: Command -> [Cabal]
+ HsDev.Server.Types: [removeSandboxes] :: Command -> [FilePath]
- HsDev.Server.Types: [scanSandboxes] :: Command -> [Cabal]
+ HsDev.Server.Types: [scanSandboxes] :: Command -> [FilePath]
- HsDev.Server.Types: [serverPort] :: ServerOpts -> Int
+ HsDev.Server.Types: [serverPort] :: ServerOpts -> ConnectionPort
- HsDev.Symbols.Types: inspectedId :: Lens (Inspected i_a1iUp a_a1iUq) (Inspected i_a1kPY a_a1iUq) i_a1iUp i_a1kPY
+ HsDev.Symbols.Types: inspectedId :: Lens (Inspected i_a1mg4 a_a1mg5) (Inspected i_a1obD a_a1mg5) i_a1mg4 i_a1obD
- HsDev.Symbols.Types: inspection :: Lens' (Inspected i_a1iUp a_a1iUq) Inspection
+ HsDev.Symbols.Types: inspection :: Lens' (Inspected i_a1mg4 a_a1mg5) Inspection
- HsDev.Symbols.Types: inspectionResult :: Lens (Inspected i_a1iUp a_a1iUq) (Inspected i_a1iUp a_a1kPZ) (Either String a_a1iUq) (Either String a_a1kPZ)
+ HsDev.Symbols.Types: inspectionResult :: Lens (Inspected i_a1mg4 a_a1mg5) (Inspected i_a1mg4 a_a1obE) (Either String a_a1mg5) (Either String a_a1obE)
- HsDev.Symbols.Util: preferredModule :: Cabal -> Maybe Project -> [ModuleId] -> Maybe ModuleId
+ HsDev.Symbols.Util: preferredModule :: PackageDb -> Maybe Project -> [ModuleId] -> Maybe ModuleId
- HsDev.Symbols.Util: uniqueModules :: Cabal -> Maybe Project -> [ModuleId] -> [ModuleId]
+ HsDev.Symbols.Util: uniqueModules :: PackageDb -> Maybe Project -> [ModuleId] -> [ModuleId]
- HsDev.Symbols.Util: visibleModule :: Cabal -> Maybe Project -> [Module] -> Maybe Module
+ HsDev.Symbols.Util: visibleModule :: PackageDb -> Maybe Project -> [Module] -> Maybe Module
- HsDev.Tools.Ghc.Check: check :: [String] -> Cabal -> Module -> Maybe String -> ExceptT String Ghc [Note OutputMessage]
+ HsDev.Tools.Ghc.Check: check :: [String] -> PackageDbStack -> Module -> Maybe String -> ExceptT String Ghc [Note OutputMessage]
- HsDev.Tools.Ghc.Check: checkFile :: [String] -> Cabal -> Module -> ExceptT String Ghc [Note OutputMessage]
+ HsDev.Tools.Ghc.Check: checkFile :: [String] -> PackageDbStack -> Module -> ExceptT String Ghc [Note OutputMessage]
- HsDev.Tools.Ghc.Check: checkFiles :: [String] -> Cabal -> [FilePath] -> Maybe Project -> Ghc [Note OutputMessage]
+ HsDev.Tools.Ghc.Check: checkFiles :: [String] -> PackageDbStack -> [FilePath] -> Maybe Project -> Ghc [Note OutputMessage]
- HsDev.Tools.Ghc.Check: checkSource :: [String] -> Cabal -> Module -> String -> ExceptT String Ghc [Note OutputMessage]
+ HsDev.Tools.Ghc.Check: checkSource :: [String] -> PackageDbStack -> Module -> String -> ExceptT String Ghc [Note OutputMessage]
- HsDev.Tools.Ghc.Types: fileTypes :: [String] -> Cabal -> Module -> Maybe String -> ExceptT String Ghc [Note TypedExpr]
+ HsDev.Tools.Ghc.Types: fileTypes :: [String] -> PackageDbStack -> Module -> Maybe String -> ExceptT String Ghc [Note TypedExpr]
- HsDev.Tools.Ghc.Types: inferTypes :: [String] -> Cabal -> Module -> Maybe String -> ExceptT String Ghc Module
+ HsDev.Tools.Ghc.Types: inferTypes :: [String] -> PackageDbStack -> Module -> Maybe String -> ExceptT String Ghc Module
- HsDev.Tools.Ghc.Worker: addCmdOpts :: [String] -> Ghc DynFlags
+ HsDev.Tools.Ghc.Worker: addCmdOpts :: [String] -> Ghc ()
- HsDev.Tools.GhcMod: browse :: [String] -> Cabal -> String -> Maybe ModulePackage -> ExceptT String IO InspectedModule
+ HsDev.Tools.GhcMod: browse :: [String] -> PackageDbStack -> String -> Maybe ModulePackage -> ExceptT String IO InspectedModule
- HsDev.Tools.GhcMod: check :: [String] -> Cabal -> [FilePath] -> Maybe Project -> GhcModT IO [Note OutputMessage]
+ HsDev.Tools.GhcMod: check :: [String] -> PackageDbStack -> [FilePath] -> Maybe Project -> GhcModT IO [Note OutputMessage]
- HsDev.Tools.GhcMod: ghcModEnvPath :: FilePath -> Either Project Cabal -> FilePath
+ HsDev.Tools.GhcMod: ghcModEnvPath :: FilePath -> Either Project PackageDbStack -> FilePath
- HsDev.Tools.GhcMod: ghcModWorker :: Either Project Cabal -> IO (Worker (GhcModT IO))
+ HsDev.Tools.GhcMod: ghcModWorker :: Either Project PackageDbStack -> IO (Worker (GhcModT IO))
- HsDev.Tools.GhcMod: info :: [String] -> Cabal -> FilePath -> String -> GhcModT IO Declaration
+ HsDev.Tools.GhcMod: info :: [String] -> PackageDbStack -> FilePath -> String -> GhcModT IO Declaration
- HsDev.Tools.GhcMod: list :: [String] -> Cabal -> ExceptT String IO [ModuleLocation]
+ HsDev.Tools.GhcMod: list :: [String] -> PackageDbStack -> ExceptT String IO [ModuleLocation]
- HsDev.Tools.GhcMod: locateGhcModEnv :: FilePath -> IO (Either Project Cabal)
+ HsDev.Tools.GhcMod: locateGhcModEnv :: FilePath -> IO (Either Project PackageDbStack)
- HsDev.Tools.GhcMod: typeOf :: [String] -> Cabal -> FilePath -> Int -> Int -> GhcModT IO [TypedRegion]
+ HsDev.Tools.GhcMod: typeOf :: [String] -> PackageDbStack -> FilePath -> Int -> Int -> GhcModT IO [TypedRegion]
- HsDev.Tools.GhcMod.InferType: infer :: [String] -> Cabal -> Module -> ExceptT String IO Module
+ HsDev.Tools.GhcMod.InferType: infer :: [String] -> PackageDbStack -> Module -> ExceptT String IO Module
- HsDev.Tools.GhcMod.InferType: inferType :: [String] -> Cabal -> FilePath -> Declaration -> GhcModT IO Declaration
+ HsDev.Tools.GhcMod.InferType: inferType :: [String] -> PackageDbStack -> FilePath -> Declaration -> GhcModT IO Declaration
- HsDev.Tools.GhcMod.InferType: inferTypes :: [String] -> Cabal -> Module -> GhcModT IO Module
+ HsDev.Tools.GhcMod.InferType: inferTypes :: [String] -> PackageDbStack -> Module -> GhcModT IO Module
- HsDev.Tools.HDocs: hdocsCabal :: Cabal -> [String] -> ExceptT String IO (Map String (Map String String))
+ HsDev.Tools.HDocs: hdocsCabal :: PackageDbStack -> [String] -> ExceptT String IO (Map String (Map String String))
- HsDev.Tools.Types: note :: Lens (Note a_a1HUr) (Note a_a1I3W) a_a1HUr a_a1I3W
+ HsDev.Tools.Types: note :: Lens (Note a_a1Ofd) (Note a_a1OoE) a_a1Ofd a_a1OoE
- HsDev.Tools.Types: noteLevel :: Lens' (Note a_a1HUr) (Maybe Severity)
+ HsDev.Tools.Types: noteLevel :: Lens' (Note a_a1Ofd) (Maybe Severity)
- HsDev.Tools.Types: noteRegion :: Lens' (Note a_a1HUr) Region
+ HsDev.Tools.Types: noteRegion :: Lens' (Note a_a1Ofd) Region
- HsDev.Tools.Types: noteSource :: Lens' (Note a_a1HUr) ModuleLocation
+ HsDev.Tools.Types: noteSource :: Lens' (Note a_a1Ofd) ModuleLocation
- HsDev.Util: withCurrentDirectory :: FilePath -> IO a -> IO a
+ HsDev.Util: withCurrentDirectory :: MonadCatchIO m => FilePath -> m a -> m a
Files
- hsdev.cabal +5/−2
- src/Data/Deps.hs +70/−0
- src/HsDev/Cabal.hs +0/−90
- src/HsDev/Cache.hs +35/−7
- src/HsDev/Cache/Structured.hs +8/−8
- src/HsDev/Client/Commands.hs +103/−64
- src/HsDev/Commands.hs +26/−25
- src/HsDev/Database.hs +25/−18
- src/HsDev/Database/Update.hs +90/−22
- src/HsDev/Display.hs +16/−8
- src/HsDev/PackageDb.hs +106/−0
- src/HsDev/Project.hs +37/−20
- src/HsDev/Sandbox.hs +138/−0
- src/HsDev/Scan.hs +103/−26
- src/HsDev/Scan/Browse.hs +97/−28
- src/HsDev/Server/Base.hs +24/−9
- src/HsDev/Server/Commands.hs +42/−20
- src/HsDev/Server/Types.hs +80/−27
- src/HsDev/Stack.hs +106/−0
- src/HsDev/Symbols.hs +14/−14
- src/HsDev/Symbols/Location.hs +10/−9
- src/HsDev/Symbols/Resolve.hs +4/−4
- src/HsDev/Symbols/Types.hs +3/−3
- src/HsDev/Symbols/Util.hs +33/−24
- src/HsDev/Tools/Ghc/Check.hs +16/−14
- src/HsDev/Tools/Ghc/Types.hs +9/−7
- src/HsDev/Tools/Ghc/Worker.hs +29/−17
- src/HsDev/Tools/GhcMod.hs +25/−21
- src/HsDev/Tools/GhcMod/InferType.hs +11/−11
- src/HsDev/Tools/HDocs.hs +3/−3
- src/HsDev/Util.hs +27/−6
- src/HsDev/Watcher.hs +18/−11
- src/HsDev/Watcher/Types.hs +3/−3
- tools/hsinspect.hs +8/−4
hsdev.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/ name: hsdev -version: 0.1.6.6 +version: 0.1.7.0 synopsis: Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references etc. description: Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references, hayoo search etc. @@ -28,12 +28,12 @@ Control.Concurrent.Worker Control.Concurrent.Util Data.Async + Data.Deps Data.Group Data.Help Data.Lisp Data.Maybe.JustIf HsDev - HsDev.Cabal HsDev.Cache HsDev.Cache.Structured HsDev.Client.Commands @@ -44,6 +44,7 @@ HsDev.Database.Update.Types HsDev.Display HsDev.Inspect + HsDev.PackageDb HsDev.Project HsDev.Scan HsDev.Scan.Browse @@ -51,6 +52,8 @@ HsDev.Server.Commands HsDev.Server.Message HsDev.Server.Types + HsDev.Sandbox + HsDev.Stack HsDev.Symbols HsDev.Symbols.Class HsDev.Symbols.Location
+ src/Data/Deps.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE FlexibleContexts, TypeFamilies #-} + +module Data.Deps ( + Deps(..), depsMap, + mapDeps, + dep, deps, + inverse, flatten + ) where + +import Control.Lens +import Control.Monad.State +import Data.List (nub) +import Data.Map (Map) +import qualified Data.Map as M +import Data.Maybe (fromMaybe) + +-- | Dependency map +data Deps a = Deps { + _depsMap :: Map a [a] } + +depsMap :: (Ord a, Ord b) => Lens (Deps a) (Deps b) (Map a [a]) (Map b [b]) +depsMap = lens _depsMap (const Deps) + +instance Ord a => Monoid (Deps a) where + mempty = Deps mempty + mappend (Deps l) (Deps r) = Deps $ M.unionWith nubConcat l r + +type instance Index (Deps a) = a +type instance IxValue (Deps a) = [a] + +instance Ord a => Ixed (Deps a) where + ix k = depsMap . ix k + +instance Ord a => At (Deps a) where + at k = depsMap . at k + +mapDeps :: (Ord a, Ord b) => (a -> b) -> Deps a -> Deps b +mapDeps f = Deps . M.mapKeys f . M.map (map f) . _depsMap + +-- | Make single dependency +dep :: Ord a => a -> a -> Deps a +dep x y = deps x [y] + +-- | Make dependency for one target, note that order of dependencies is matter +deps :: Ord a => a -> [a] -> Deps a +deps x ys = Deps $ M.singleton x ys + +-- | Inverse dependencies, i.e. make map where keys are dependencies and elements are targets depends on it +inverse :: Ord a => Deps a -> Deps a +inverse = mconcat . map (uncurry dep) . concatMap inverse' . M.toList . _depsMap where + inverse' :: (a, [a]) -> [(a, a)] + inverse' (m, ds) = zip ds (repeat m) + +-- | Flatten dependencies so that there will be no indirect dependencies +flatten :: Ord a => Deps a -> Deps a +flatten (Deps ds) = flip execState mempty . mapM_ flatten' . M.keys $ ds where + -- flatten' :: a -> State (Deps a) [a] + flatten' n = do + d <- gets (M.lookup n . _depsMap) + case d of + Just d' -> return d' + Nothing -> do + let + deps' = fromMaybe [] $ M.lookup n ds + d'' <- (nub . concat . (++ [deps'])) <$> mapM flatten' deps' + modify $ mappend (deps n d'') + return d'' + +nubConcat :: Ord a => [a] -> [a] -> [a] +nubConcat xs ys = nub $ xs ++ ys
− src/HsDev/Cabal.hs
@@ -1,90 +0,0 @@-{-# LANGUAGE OverloadedStrings, MultiWayIf, FlexibleContexts #-} - -module HsDev.Cabal ( - Cabal(..), sandbox, - isPackageDb, findPackageDb, locateSandbox, getSandbox, searchSandbox, - cabalOpt - ) where - -import Control.Applicative -import Control.DeepSeq (NFData(..)) -import Control.Monad.Except -import Data.Aeson -import Data.List -import System.Directory -import System.FilePath - -import HsDev.Util (searchPath, liftE) - --- | Cabal or sandbox -data Cabal = Cabal | Sandbox FilePath deriving (Eq, Ord) - --- | Get sandbox -sandbox :: Cabal -> Maybe FilePath -sandbox Cabal = Nothing -sandbox (Sandbox f) = Just f - -instance NFData Cabal where - rnf Cabal = () - rnf (Sandbox p) = rnf p - -instance Show Cabal where - show Cabal = "<cabal>" - show (Sandbox p) = p - -instance ToJSON Cabal where - toJSON Cabal = toJSON ("cabal" :: String) - toJSON (Sandbox p) = toJSON $ object [ - "sandbox" .= p] - -instance FromJSON Cabal where - parseJSON v = cabalP v <|> sandboxP v where - cabalP = withText "cabal" cabalText where - cabalText "cabal" = return Cabal - cabalText _ = fail "Unknown cabal string" - sandboxP = withObject "sandbox" sandboxPath where - sandboxPath obj = fmap Sandbox $ obj .: "sandbox" - --- | Is -package-db file -isPackageDb :: FilePath -> Bool -isPackageDb p = cabalDev p || cabalSandbox p where - cabalDev dir = "packages-" `isPrefixOf` dir && ".conf" `isSuffixOf` dir - cabalSandbox dir = "-packages.conf.d" `isSuffixOf` dir - --- | Is .cabal-sandbox -cabalSandboxDir :: FilePath -> Bool -cabalSandboxDir p = takeFileName p == ".cabal-sandbox" - --- | Find -package-db path for sandbox directory or package-db file itself -findPackageDb :: FilePath -> IO (Maybe FilePath) -findPackageDb sand = do - sand' <- canonicalizePath sand - isDir <- doesDirectoryExist sand' - if - | isDir && isPackageDb sand' -> return $ Just sand' - | isDir -> do - cts <- getDirectoryContents sand' - (dir', cts') <- case find cabalSandboxDir cts of - Nothing -> return (sand', cts) - Just sbox -> (,) <$> pure (sand' </> sbox) <*> getDirectoryContents (sand' </> sbox) - return $ fmap (dir' </>) $ find isPackageDb cts' - | otherwise -> return Nothing - --- | Create sandbox by directory or package-db file -locateSandbox :: FilePath -> ExceptT String IO Cabal -locateSandbox p = liftE (findPackageDb p) >>= maybe - (throwError $ "Can't locate package-db in sandbox: " ++ p) - (return . Sandbox) - --- | Try find sandbox by parent directory -getSandbox :: FilePath -> IO Cabal -getSandbox = liftM (either (const Cabal) id) . runExceptT . locateSandbox - --- | Search sandbox -searchSandbox :: FilePath -> IO Cabal -searchSandbox p = runExceptT (searchPath p locateSandbox) >>= either (const $ return Cabal) return - --- | Cabal ghc option -cabalOpt :: Cabal -> [String] -cabalOpt Cabal = [] -cabalOpt (Sandbox p) = ["-package-db " ++ p]
src/HsDev/Cache.hs view
@@ -2,37 +2,49 @@ module HsDev.Cache ( escapePath, - cabalCache, + versionCache, + packageDbCache, projectCache, standaloneCache, dump, load, + writeVersion, + readVersion, -- * Reexports Database ) where import Control.DeepSeq (force) +import Control.Exception import Control.Lens (view) -import Data.Aeson (eitherDecode) +import Data.Aeson (encode, eitherDecode) import Data.Aeson.Encode.Pretty (encodePretty) import qualified Data.ByteString.Lazy.Char8 as BS import Data.Char (isAlphaNum) import Data.List (intercalate) import System.FilePath +import Text.Read (readMaybe) -import HsDev.Symbols (Cabal(..)) +import HsDev.PackageDb import HsDev.Project import HsDev.Database (Database) +import HsDev.Version +import HsDev.Util (split, version) -- | Escape path escapePath :: FilePath -> FilePath escapePath = intercalate "." . map (filter isAlphaNum) . splitDirectories +-- | Name of cache for version +versionCache :: FilePath +versionCache = "version" <.> "json" + -- | Name of cache for cabal -cabalCache :: Cabal -> FilePath -cabalCache Cabal = "cabal" <.> "json" -cabalCache (Sandbox p) = escapePath p <.> "json" +packageDbCache :: PackageDb -> FilePath +packageDbCache GlobalDb = "global" <.> "json" +packageDbCache UserDb = "user" <.> "json" +packageDbCache (PackageDb p) = escapePath p <.> "json" -- | Name of cache for projects projectCache :: Project -> FilePath @@ -48,6 +60,22 @@ -- | Load database from file, strict load :: FilePath -> IO (Either String Database) -load file = do +load file = handle onIO $ do cts <- BS.readFile file return $ force $ eitherDecode cts + where + onIO :: IOException -> IO (Either String Database) + onIO _ = return $ Left $ "IO exception while reading cache from " ++ file + +-- | Write version +writeVersion :: FilePath -> IO () +writeVersion file = BS.writeFile file $ encode version + +-- | Read version +readVersion :: FilePath -> IO (Maybe [Int]) +readVersion file = handle onIO $ do + cts <- BS.readFile file + return $ either (const Nothing) id $ eitherDecode cts + where + onIO :: IOException -> IO (Maybe [Int]) + onIO _ = return Nothing
src/HsDev/Cache/Structured.hs view
@@ -1,6 +1,6 @@ module HsDev.Cache.Structured ( dump, load, - loadCabal, loadProject, loadFiles + loadPackageDb, loadProject, loadFiles ) where import Control.DeepSeq @@ -22,8 +22,8 @@ dump dir db = do createDirectoryIfMissing True (dir </> "cabal") createDirectoryIfMissing True (dir </> "projects") - forM_ (M.assocs $ structuredCabals db) $ \(c, cdb) -> Cache.dump - (dir </> "cabal" </> Cache.cabalCache c) + forM_ (M.assocs $ structuredPackageDbs db) $ \(c, cdb) -> Cache.dump + (dir </> "cabal" </> Cache.packageDbCache c) cdb forM_ (M.assocs $ structuredProjects db) $ \(p, pdb) -> Cache.dump (dir </> "projects" </> Cache.projectCache (project p)) @@ -38,8 +38,8 @@ -- | Load all cache load :: FilePath -> IO (Either String Structured) -load dir = runExceptT $ join $ either throwError return <$> (structured <$> loadCabals <*> loadProjects <*> loadStandaloneFiles) where - loadCabals = loadDir (dir </> "cabal") +load dir = runExceptT $ join $ either throwError return <$> (structured <$> loadPackageDbs <*> loadProjects <*> loadStandaloneFiles) where + loadPackageDbs = loadDir (dir </> "cabal") loadProjects = loadDir (dir </> "projects") loadStandaloneFiles = ExceptT $ Cache.load (dir </> Cache.standaloneCache) @@ -52,9 +52,9 @@ loadData = liftExceptionM . ExceptT . Cache.load -- | Load cabal from cache -loadCabal :: Cabal -> FilePath -> ExceptT String IO Structured -loadCabal c dir = do - dat <- loadData (dir </> "cabal" </> Cache.cabalCache c) +loadPackageDb :: PackageDb -> FilePath -> ExceptT String IO Structured +loadPackageDb c dir = do + dat <- loadData (dir </> "cabal" </> Cache.packageDbCache c) ExceptT $ return $ structured [dat] [] mempty -- | Load project from cache
src/HsDev/Client/Commands.hs view
@@ -12,7 +12,7 @@ import Control.Monad import Control.Monad.Except import Control.Monad.Reader -import Control.Monad.State (gets) +import qualified Control.Monad.State as State import Control.Monad.Catch (try, SomeException(..)) import Data.Aeson hiding (Result, Error) import Data.List @@ -37,6 +37,9 @@ import qualified HsDev.Database.Async as DB import HsDev.Server.Message as M import HsDev.Server.Types +import HsDev.Sandbox hiding (findSandbox) +import qualified HsDev.Sandbox as S (findSandbox) +import HsDev.Stack import HsDev.Symbols import HsDev.Symbols.Resolve (resolveOne, scopeModule, exportsModule) import HsDev.Symbols.Util @@ -52,6 +55,8 @@ import HsDev.Util import HsDev.Watcher +import qualified HsDev.Scan as Scan +import qualified HsDev.Scan.Browse as Scan import qualified HsDev.Database.Update as Update runClient :: (ToJSON a, ServerMonadBase m) => CommandOptions -> ClientM m a -> ServerM m Result @@ -75,14 +80,15 @@ updateData (AddedDatabase db) = toValue $ serverUpdateDB db updateData (AddedModule m) = toValue $ serverUpdateDB $ fromModule m updateData (AddedProject p) = toValue $ serverUpdateDB $ fromProject p -runCommand (Scan projs cabals fs paths' fcts ghcs' docs' infer') = toValue $ do - sboxes <- getSandboxes cabals +runCommand (Scan projs cabal sboxes fs paths' fcts ghcs' docs' infer') = toValue $ do + sboxes' <- getSandboxes sboxes updateProcess (Update.UpdateOptions [] ghcs' docs' infer') $ concat [ map (\(FileContents f cts) -> Update.scanFileContents ghcs' f (Just cts)) fcts, map (Update.scanProject ghcs') projs, map (Update.scanFile ghcs') fs, map (Update.scanDirectory ghcs') paths', - map (Update.scanCabal ghcs') sboxes] + if cabal then [Update.scanCabal ghcs'] else [], + map (Update.scanSandbox ghcs') sboxes'] runCommand (RefineDocs projs fs ms) = toValue $ do projects <- traverse findProject projs dbval <- getDb @@ -103,22 +109,45 @@ map inModule ms] mods = selectModules (filters . view moduleId) dbval updateProcess (Update.UpdateOptions [] [] False False) [Update.inferModTypes $ map (getInspected dbval) mods] -runCommand (Remove projs cabals files) = toValue $ do +runCommand (Remove projs cabal sboxes files) = toValue $ do db <- askSession sessionDatabase dbval <- getDb w <- askSession sessionWatcher projects <- traverse findProject projs + sboxes' <- getSandboxes sboxes forM_ projects $ \proj -> do DB.clear db (return $ projectDB proj dbval) liftIO $ unwatchProject w proj - forM_ cabals $ \cabal -> do - DB.clear db (return $ cabalDB cabal dbval) - liftIO $ unwatchSandbox w cabal + dbPDbs <- liftIO $ mapM restorePackageDbStack $ databasePackageDbs dbval + flip State.evalStateT dbPDbs $ do + when cabal $ removePackageDbStack userDb + forM_ sboxes' $ \sbox -> do + pdbs <- lift $ mapCommandIO $ sandboxPackageDbStack sbox + removePackageDbStack pdbs forM_ files $ \file -> do DB.clear db (return $ filterDB (inFile file) (const False) dbval) let mloc = fmap (view moduleLocation) $ lookupFile file dbval maybe (return ()) (liftIO . unwatchModule w) mloc + where + -- We can safely remove package-db from db iff doesn't used by some of other package-dbs + -- For example, we can't remove global-db if there are any other package-dbs, because all of them uses global-db + -- We also can't remove stack snapshot package-db if there are some local package-db not yet removed + canRemove pdbs = do + from <- State.get + return $ null $ filter (pdbs `isSubStack`) $ delete pdbs from + -- Remove top of package-db stack if possible + removePackageDb pdbs = do + db <- lift $ askSession sessionDatabase + dbval <- lift getDb + w <- lift $ askSession sessionWatcher + can <- canRemove pdbs + when can $ do + State.modify (delete pdbs) + DB.clear db (return $ packageDbDB (topPackageDb pdbs) dbval) + liftIO $ unwatchPackageDb w $ topPackageDb pdbs + -- Remove package-db stack when possible + removePackageDbStack = mapM_ removePackageDb . packageDbStacks runCommand RemoveAll = toValue $ do db <- askSession sessionDatabase liftIO $ A.modifyAsync db A.Clear @@ -131,7 +160,7 @@ return $ map (view moduleId) $ newestPackage $ selectModules (filter' . view moduleId) dbval runCommand InfoPackages = toValue $ (ordNub . sort . mapMaybe (preview (moduleLocation . modulePackage . _Just)) . allModules) <$> getDb runCommand InfoProjects = toValue $ (toList . databaseProjects) <$> getDb -runCommand InfoSandboxes = toValue $ (ordNub . sort . mapMaybe (cabalOf . view moduleId) . allModules) <$> getDb +runCommand InfoSandboxes = toValue $ databasePackageDbs <$> getDb runCommand (InfoSymbol sq fs locals') = toValue $ do dbval <- liftM (localsDatabase locals') $ getDb filter' <- targetFilters fs @@ -141,39 +170,32 @@ filter' <- targetFilters fs return $ newestPackage $ filterMatch sq $ filter (filter' . view moduleId) $ allModules dbval runCommand (InfoResolve fpath exports) = toValue $ do - dbval <- getDb - cabal <- liftIO $ getSandbox fpath + dbval <- getSDb fpath let - cabaldb = filterDB (restrictCabal cabal) (const True) dbval getScope | exports = exportsModule | otherwise = scopeModule case lookupFile fpath dbval of Nothing -> commandError "File not found" [] - Just m -> return $ getScope $ resolveOne cabaldb m + Just m -> return $ getScope $ resolveOne dbval m runCommand (InfoProject (Left projName)) = toValue $ findProject projName runCommand (InfoProject (Right projPath)) = toValue $ liftIO $ searchProject projPath runCommand (InfoSandbox sandbox') = toValue $ liftIO $ searchSandbox sandbox' runCommand (Lookup nm fpath) = toValue $ do - dbval <- getDb - cabal <- liftIO $ getSandbox fpath - mapCommandIO $ lookupSymbol dbval cabal fpath nm + dbval <- getSDb fpath + mapCommandIO $ lookupSymbol dbval fpath nm runCommand (Whois nm fpath) = toValue $ do - dbval <- getDb - cabal <- liftIO $ getSandbox fpath - mapCommandIO $ whois dbval cabal fpath nm + dbval <- getSDb fpath + mapCommandIO $ whois dbval fpath nm runCommand (ResolveScopeModules sq fpath) = toValue $ do - dbval <- getDb - cabal <- liftIO $ getSandbox fpath - liftM (filterMatch sq . map (view moduleId)) $ mapCommandIO $ scopeModules dbval cabal fpath + dbval <- getSDb fpath + liftM (filterMatch sq . map (view moduleId)) $ mapCommandIO $ scopeModules dbval fpath runCommand (ResolveScope sq global fpath) = toValue $ do - dbval <- getDb - cabal <- liftIO $ getSandbox fpath - liftM (filterMatch sq) $ mapCommandIO $ scope dbval cabal fpath global + dbval <- getSDb fpath + liftM (filterMatch sq) $ mapCommandIO $ scope dbval fpath global runCommand (Complete input wide fpath) = toValue $ do - dbval <- getDb - cabal <- liftIO $ getSandbox fpath - mapCommandIO $ completions dbval cabal fpath input wide + dbval <- getSDb fpath + mapCommandIO $ completions dbval fpath input wide runCommand (Hayoo hq p ps) = toValue $ liftM concat $ forM [p .. p + pred ps] $ \i -> liftM (mapMaybe Hayoo.hayooAsDeclaration . Hayoo.resultResult) $ mapCommandIO $ Hayoo.hayoo hq (Just i) @@ -182,67 +204,68 @@ mapCommandIO $ liftM2 (++) (liftM concat $ mapM HLint.hlintFile fs) (liftM concat $ mapM (\(FileContents f c) -> HLint.hlintSource f c) fcts) -runCommand (Check fs fcts ghcs') = toValue $ do - db <- getDb +runCommand (Check fs fcts ghcs') = toValue $ Log.scope "check" $ do + dbval <- getDb ghc <- askSession sessionGhc liftIO $ restartWorker ghc let - checkSome file fn = do - cabal <- liftIO $ getSandbox file + checkSome file fn = Log.scope "checkSome" $ do + pdbs <- liftIO $ searchPackageDbStack file m <- maybe (commandError_ $ "File '{}' not found" ~~ file) return - (lookupFile file db) + (lookupFile file dbval) notes <- inWorkerWith (commandError_ . show) ghc - (runExceptT $ fn cabal m) + (runExceptT $ fn pdbs m) either commandError_ return notes liftM concat $ mapM (uncurry checkSome) $ [(f, Check.checkFile ghcs') | f <- fs] ++ - [(f, \cabal m -> Check.checkSource ghcs' cabal m src) | FileContents f src <- fcts] + [(f, \pdbs m -> Check.checkSource ghcs' pdbs m src) | FileContents f src <- fcts] runCommand (CheckLint fs fcts ghcs') = toValue $ do - db <- getDb + dbval <- getDb ghc <- askSession sessionGhc liftIO $ restartWorker ghc let checkSome file fn = do - cabal <- liftIO $ getSandbox file + pdbs <- liftIO $ searchPackageDbStack file m <- maybe (commandError_ $ "File '" ++ file ++ "' not found") return - (lookupFile file db) + (lookupFile file dbval) notes <- inWorkerWith (commandError_ . show) ghc - (runExceptT $ fn cabal m) + (runExceptT $ fn pdbs m) either commandError_ return notes checkMsgs <- liftM concat $ mapM (uncurry checkSome) $ [(f, Check.checkFile ghcs') | f <- fs] ++ - [(f, \cabal m -> Check.checkSource ghcs' cabal m src) | FileContents f src <- fcts] + [(f, \pdbs m -> Check.checkSource ghcs' pdbs m src) | FileContents f src <- fcts] lintMsgs <- mapCommandIO $ liftM2 (++) (liftM concat $ mapM HLint.hlintFile fs) (liftM concat $ mapM (\(FileContents f src) -> HLint.hlintSource f src) fcts) return $ checkMsgs ++ lintMsgs runCommand (Types fs fcts ghcs') = toValue $ do - db <- getDb + dbval <- getDb ghc <- askSession sessionGhc let cts = [(f, Nothing) | f <- fs] ++ [(f, Just src) | FileContents f src <- fcts] liftM concat $ forM cts $ \(file, msrc) -> do - cabal <- liftIO $ getSandbox file + pdbs <- liftIO $ searchPackageDbStack file m <- maybe (commandError_ $ "File '" ++ file ++ "' not found") return - (lookupFile file db) + (lookupFile file dbval) notes <- inWorkerWith (commandError_ . show) ghc - (runExceptT $ Types.fileTypes ghcs' cabal m msrc) + (runExceptT $ Types.fileTypes ghcs' pdbs m msrc) either commandError_ return notes runCommand (GhcMod GhcModLang) = toValue $ mapCommandIO $ GhcMod.langs runCommand (GhcMod GhcModFlags) = toValue $ mapCommandIO $ GhcMod.flags runCommand (GhcMod (GhcModType (Position line column) fpath ghcs')) = toValue $ do ghcmod <- askSession sessionGhcMod dbval <- getDb - cabal <- liftIO $ getSandbox fpath + pdbs <- liftIO $ searchPackageDbStack fpath + pkgs <- mapCommandIO $ Scan.browsePackages ghcs' pdbs (fpath', m', _) <- mapCommandIO $ fileCtx dbval fpath mapCommandIO $ GhcMod.waitMultiGhcMod ghcmod fpath' $ - GhcMod.typeOf (ghcs' ++ moduleOpts (allPackages dbval) m') cabal fpath' line column + GhcMod.typeOf (ghcs' ++ moduleOpts pkgs m') pdbs fpath' line column runCommand (GhcMod (GhcModLint fs hlints')) = toValue $ do ghcmod <- askSession sessionGhcMod mapCommandIO $ liftM concat $ forM fs $ \file -> @@ -253,19 +276,21 @@ dbval <- getDb mapCommandIO $ liftM concat $ forM fs $ \file -> do mproj <- liftIO $ locateProject file - cabal <- liftIO $ getSandbox file + pdbs <- liftIO $ searchPackageDbStack file + pkgs <- Scan.browsePackages ghcs' pdbs (_, m', _) <- fileCtx dbval file GhcMod.waitMultiGhcMod ghcmod file $ - GhcMod.check (ghcs' ++ moduleOpts (allPackages dbval) m') cabal [file] mproj + GhcMod.check (ghcs' ++ moduleOpts pkgs m') pdbs [file] mproj runCommand (GhcMod (GhcModCheckLint fs ghcs' hlints')) = toValue $ do ghcmod <- askSession sessionGhcMod dbval <- getDb mapCommandIO $ liftM concat $ forM fs $ \file -> do mproj <- liftIO $ locateProject file - cabal <- liftIO $ getSandbox file + pdbs <- liftIO $ searchPackageDbStack file + pkgs <- Scan.browsePackages ghcs' pdbs (_, m', _) <- fileCtx dbval file GhcMod.waitMultiGhcMod ghcmod file $ do - checked <- GhcMod.check (ghcs' ++ moduleOpts (allPackages dbval) m') cabal [file] mproj + checked <- GhcMod.check (ghcs' ++ moduleOpts pkgs m') pdbs [file] mproj linted <- GhcMod.lint hlints' file return $ checked ++ linted runCommand (AutoFix (AutoFixShow ns)) = toValue $ return $ AutoFix.corrections ns @@ -275,7 +300,7 @@ doFix :: FilePath -> String -> ([Tools.Note AutoFix.Correction], String) doFix file cts = AutoFix.edit cts fUpCorrs $ do AutoFix.autoFix fCorrs - gets (view AutoFix.regions) + State.gets (view AutoFix.regions) where findCorrs :: FilePath -> [Tools.Note AutoFix.Correction] -> [Tools.Note AutoFix.Correction] findCorrs f = filter ((== Just f) . preview (Tools.noteSource . moduleFile)) @@ -313,18 +338,15 @@ TargetFile file -> return $ inFile file TargetModule mname -> return $ inModule mname TargetDepsOf dep -> liftM inDeps $ findDep dep - TargetCabal cabal -> liftM inCabal $ findSandbox cabal + TargetPackageDb pdb -> return $ inPackageDb pdb + TargetCabal -> return $ inPackageDbStack userDb + TargetSandbox sbox -> liftM inPackageDbStack $ findSandbox sbox >>= mapCommandIO . sandboxPackageDbStack TargetPackage pack -> return $ inPackage pack TargetSourced -> return byFile TargetStandalone -> return standalone -- Helper functions --- | Find sandbox by path -findSandbox :: CommandMonad m => Cabal -> m Cabal -findSandbox Cabal = return Cabal -findSandbox (Sandbox f) = (findPath >=> mapCommandErrorStr . liftIO . getSandbox) f - -- | Canonicalize paths findPath :: (CommandMonad m, Paths a) => a -> m a findPath = paths findPath' where @@ -333,9 +355,19 @@ r <- commandRoot liftIO $ canonicalizePath (normalise $ if isRelative f then r </> f else f) +-- | Find sandbox by path +findSandbox :: (CommandMonad m, Functor m) => FilePath -> m Sandbox +findSandbox fpath = do + fpath' <- findPath fpath + sbox <- liftIO $ S.findSandbox fpath' + maybe + (commandError ("Sandbox {} not found" ~~ fpath') ["sandbox" .= fpath']) + return + sbox + -- | Get list of enumerated sandboxes -getSandboxes :: (CommandMonad m, Functor m) => [Cabal] -> m [Cabal] -getSandboxes = traverse findSandbox +getSandboxes :: (CommandMonad m, Functor m) => [FilePath] -> m [Sandbox] +getSandboxes = traverse (findPath >=> findSandbox) -- | Find project by name or path findProject :: CommandMonad m => String -> m Project @@ -353,7 +385,7 @@ | otherwise = p </> (takeBaseName p <.> "cabal") -- | Find dependency: it may be source, project file or project name, also returns sandbox found -findDep :: CommandMonad m => String -> m (Project, Maybe FilePath, Cabal) +findDep :: CommandMonad m => String -> m (Project, Maybe FilePath, PackageDbStack) findDep depName = do depPath <- findPath depName proj <- msum [ @@ -367,13 +399,13 @@ src | takeExtension depPath == ".hs" = Just depPath | otherwise = Nothing - sbox <- liftIO $ searchSandbox $ view projectPath proj - return (proj, src, sbox) + pdbs <- liftIO $ searchPackageDbStack $ view projectPath proj + return (proj, src, pdbs) -- FIXME: Doesn't work for file without project -- | Check if project or source depends from this module -inDeps :: (Project, Maybe FilePath, Cabal) -> ModuleId -> Bool -inDeps (proj, src, cabal) = liftM2 (&&) (restrictCabal cabal) deps' where +inDeps :: (Project, Maybe FilePath, PackageDbStack) -> ModuleId -> Bool +inDeps (proj, src, pdbs) = liftM2 (&&) (restrictPackageDbStack pdbs) deps' where deps' = case src of Nothing -> inDepsOfProject proj Just src' -> inDepsOfFile proj src' @@ -386,6 +418,13 @@ -- | Get actual DB state getDb :: SessionMonad m => m Database getDb = askSession sessionDatabase >>= liftIO . DB.readAsync + +-- | Get DB with filtered sanxboxes for file +getSDb :: SessionMonad m => FilePath -> m Database +getSDb fpath = do + dbval <- getDb + pdbs <- liftIO $ searchPackageDbStack fpath + return $ filterDB (restrictPackageDbStack pdbs) (const True) dbval mapCommandErrorStr :: CommandMonad m => ExceptT String m a -> m a mapCommandErrorStr act = runExceptT act >>= either commandError_ return
src/HsDev/Commands.hs view
@@ -11,7 +11,7 @@ moduleCompletions, -- * Filters - checkModule, checkDeclaration, restrictCabal, visibleFrom, + checkModule, checkDeclaration, restrictPackageDb, restrictPackageDbStack, visibleFrom, splitIdentifier, -- * Helpers @@ -71,12 +71,11 @@ refineProject db p' -- | Lookup visible within project/cabal symbol -lookupSymbol :: Database -> Cabal -> FilePath -> String -> ExceptT String IO [ModuleDeclaration] -lookupSymbol db cabal file ident = do +lookupSymbol :: Database -> FilePath -> String -> ExceptT String IO [ModuleDeclaration] +lookupSymbol db file ident = do (_, mthis, mproj) <- fileCtx db file liftM (filter $ checkModule $ allOf [ - restrictCabal cabal, visibleFrom mproj mthis, maybe (const True) inModule qname]) (newestPackage <$> findDeclaration db iname) @@ -84,23 +83,23 @@ (qname, iname) = splitIdentifier ident -- | Whois symbol in scope -whois :: Database -> Cabal -> FilePath -> String -> ExceptT String IO [Declaration] -whois db cabal file ident = do +whois :: Database -> FilePath -> String -> ExceptT String IO [Declaration] +whois db file ident = do (_, mthis, mproj) <- fileCtx db file return $ newestPackage $ filter checkDecl $ - view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file cabal mproj db) $ + view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) $ moduleLocals mthis where (qname, iname) = splitIdentifier ident checkDecl d = fmap fromString qname `elem` scopes d && view declarationName d == fromString iname -- | Accessible modules -scopeModules :: Database -> Cabal -> FilePath -> ExceptT String IO [Module] -scopeModules db cabal file = do +scopeModules :: Database -> FilePath -> ExceptT String IO [Module] +scopeModules db file = do (file', mthis, mproj) <- fileCtxMaybe db file newestPackage <$> case mproj of - Nothing -> return $ maybe id (:) mthis $ selectModules (inCabal cabal . view moduleId) db + Nothing -> return $ maybe id (:) mthis $ selectModules (installed . view moduleId) db Just proj -> let deps' = deps file' proj in return $ concatMap (\p -> selectModules (p . view moduleId) db) [ inProject proj, @@ -109,19 +108,19 @@ deps f p = delete (view projectName p) $ concatMap (view infoDepends) $ fileTargets p f -- | Symbols in scope -scope :: Database -> Cabal -> FilePath -> Bool -> ExceptT String IO [Declaration] -scope db cabal file False = do +scope :: Database -> FilePath -> Bool -> ExceptT String IO [Declaration] +scope db file False = do (_, mthis, mproj) <- fileCtx db file - return $ view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file cabal mproj db) mthis -scope db cabal file True = concatMap (view moduleDeclarations) <$> scopeModules db cabal file + return $ view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) mthis +scope db file True = concatMap (view moduleDeclarations) <$> scopeModules db file -- | Completions -completions :: Database -> Cabal -> FilePath -> String -> Bool -> ExceptT String IO [Declaration] -completions db cabal file prefix wide = do +completions :: Database -> FilePath -> String -> Bool -> ExceptT String IO [Declaration] +completions db file prefix wide = do (_, mthis, mproj) <- fileCtx db file return $ toListOf (each . minimalDecl) $ newestPackage $ filter checkDecl $ - view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file cabal mproj db) $ + view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) $ dropImportLists mthis where (qname, iname) = splitIdentifier prefix @@ -147,13 +146,17 @@ checkDeclaration = (. view moduleDeclaration) -- | Allow only selected cabal sandbox -restrictCabal :: Cabal -> ModuleId -> Bool -restrictCabal cabal m = inCabal cabal m || not (byCabal m) +restrictPackageDb :: PackageDb -> ModuleId -> Bool +restrictPackageDb pdb m = inPackageDb pdb m || not (installed m) +-- | Allow only selected cabal sandboxes +restrictPackageDbStack :: PackageDbStack -> ModuleId -> Bool +restrictPackageDbStack pdbs m = any (`inPackageDb` m) (packageDbs pdbs) || not (installed m) + -- | Check whether module is visible from source file visibleFrom :: Maybe Project -> Module -> ModuleId -> Bool visibleFrom (Just p) this m = visible p (view moduleId this) m -visibleFrom Nothing this m = view moduleId this == m || byCabal m +visibleFrom Nothing this m = view moduleId this == m || installed m -- | Split identifier into module name and identifier itself splitIdentifier :: String -> (Maybe String, String) @@ -183,10 +186,8 @@ return (file', Nothing, mproj') -- | Restrict only modules file depends on -fileDeps :: FilePath -> Cabal -> Maybe Project -> Database -> Database -fileDeps file cabal mproj = filterDB fileDeps' (const True) where +fileDeps :: FilePath -> Maybe Project -> Database -> Database +fileDeps file mproj = filterDB fileDeps' (const True) where fileDeps' = liftM2 (||) (maybe (const True) inProject mproj) - (liftM2 (&&) - (restrictCabal cabal) - (\m -> any (`inDepsOfTarget` m) (fromMaybe [] $ fileTargets <$> mproj <*> pure file))) + (\m -> any (`inDepsOfTarget` m) (fromMaybe [] $ fileTargets <$> mproj <*> pure file))
src/HsDev/Database.hs view
@@ -2,10 +2,10 @@ module HsDev.Database ( Database(..), - databaseIntersection, nullDatabase, databaseLocals, allModules, allDeclarations, allPackages, + databaseIntersection, nullDatabase, databaseLocals, databasePackageDbs, allModules, allDeclarations, allPackages, fromModule, fromProject, filterDB, - projectDB, cabalDB, standaloneDB, + projectDB, packageDbDB, packageDbStackDB, standaloneDB, selectModules, selectDeclarations, lookupModule, lookupInspected, lookupFile, refineProject, getInspected, @@ -17,7 +17,7 @@ Map ) where -import Control.Lens (set, view, preview, _Just, each, (^..)) +import Control.Lens (set, view, preview, _Just, _Right, each, (^..)) import Control.Monad (msum, join) import Control.DeepSeq (NFData(..)) import Data.Aeson @@ -84,6 +84,10 @@ databaseLocals db = db { databaseModules = fmap (fmap moduleLocals) (databaseModules db) } +-- | All scanned sandboxes +databasePackageDbs :: Database -> [PackageDb] +databasePackageDbs db = ordNub $ databaseModules db ^.. each . inspectionResult . _Right . moduleLocation . modulePackageDb + -- | All modules allModules :: Database -> [Module] allModules = rights . fmap (view inspectionResult) . databaseModules @@ -118,10 +122,13 @@ projectDB :: Project -> Database -> Database projectDB proj = filterDB (inProject proj) (((==) `on` view projectCabal) proj) --- | Cabal database -cabalDB :: Cabal -> Database -> Database -cabalDB cabal = filterDB (inCabal cabal) (const False) +-- | Package-db database +packageDbDB :: PackageDb -> Database -> Database +packageDbDB pdb = filterDB (inPackageDb pdb) (const False) +packageDbStackDB :: PackageDbStack -> Database -> Database +packageDbStackDB pdbs = filterDB (inPackageDbStack pdbs) (const False) + -- | Standalone database standaloneDB :: Database -> Database standaloneDB = filterDB check' (const False) where @@ -168,7 +175,7 @@ -- | Structured database data Structured = Structured { - structuredCabals :: Map Cabal Database, + structuredPackageDbs :: Map PackageDb Database, structuredProjects :: Map FilePath Database, structuredFiles :: Database } deriving (Eq, Ord) @@ -178,11 +185,11 @@ instance Group Structured where add old new = Structured { - structuredCabals = structuredCabals new `M.union` structuredCabals old, + structuredPackageDbs = structuredPackageDbs new `M.union` structuredPackageDbs old, structuredProjects = structuredProjects new `M.union` structuredProjects old, structuredFiles = structuredFiles old `add` structuredFiles new } sub old new = Structured { - structuredCabals = structuredCabals old `M.difference` structuredCabals new, + structuredPackageDbs = structuredPackageDbs old `M.difference` structuredPackageDbs new, structuredProjects = structuredProjects old `M.difference` structuredProjects new, structuredFiles = structuredFiles old `sub` structuredFiles new } zero = Structured zero zero zero @@ -210,14 +217,14 @@ mkMap key dbs = do keys <- mapM key dbs return $ M.fromList $ zip keys dbs - keyCabal :: Database -> Either String Cabal + keyCabal :: Database -> Either String PackageDb keyCabal db = unique "No cabal" "Different module cabals" - (ordNub <$> mapM getCabal (allModules db)) + (ordNub <$> mapM getPackageDb (allModules db)) where - getCabal m = case view moduleLocation m of - CabalModule c _ _ -> Right c + getPackageDb m = case view moduleLocation m of + InstalledModule c _ _ -> Right c _ -> Left "Module have no cabal" keyProj :: Database -> Either String FilePath keyProj db = unique @@ -233,14 +240,14 @@ structurize :: Database -> Structured structurize db = Structured cs ps fs where - cs = M.fromList [(c, cabalDB c db) | c <- ordNub (mapMaybe modCabal (allModules db))] - ps = M.fromList [(pname, projectDB (project pname) db) | pname <- (databaseProjects db ^.. each . projectCabal)] + cs = M.fromList [(c, packageDbDB c db) | c <- ordNub (mapMaybe modPackageDb (allModules db))] + ps = M.fromList [(pname, projectDB (project pname) db) | pname <- databaseProjects db ^.. each . projectCabal] fs = standaloneDB db merge :: Structured -> Database merge (Structured cs ps fs) = mconcat $ M.elems cs ++ M.elems ps ++ [fs] -modCabal :: Module -> Maybe Cabal -modCabal m = case view moduleLocation m of - CabalModule c _ _ -> Just c +modPackageDb :: Module -> Maybe PackageDb +modPackageDb m = case view moduleLocation m of + InstalledModule c _ _ -> Just c _ -> Nothing
src/HsDev/Database/Update.hs view
@@ -13,7 +13,7 @@ postStatus, waiter, updater, loadCache, getCache, runTask, runTasks, readDB, - scanModule, scanModules, scanFile, scanFileContents, scanCabal, scanProjectFile, scanProject, scanDirectory, + scanModule, scanModules, scanFile, scanFileContents, scanCabal, prepareSandbox, scanSandbox, scanPackageDb, scanProjectFile, scanProjectStack, scanProject, scanDirectory, scanContents, scanDocs, inferModTypes, scan, updateEvent, processEvent, @@ -35,11 +35,14 @@ import Control.Monad.Writer import Data.Aeson import Data.Aeson.Types +import Data.List ((\\)) import Data.Foldable (toList) import qualified Data.Map as M -import Data.Maybe (mapMaybe, isJust, fromMaybe) +import Data.Maybe (mapMaybe, isJust, fromMaybe, catMaybes) +import Data.Maybe.JustIf import qualified Data.Text as T (unpack) import System.Directory (canonicalizePath, doesFileExist) +import System.FilePath import qualified System.Log.Simple as Log import Control.Concurrent.Worker (inWorker) @@ -49,6 +52,8 @@ import HsDev.Display import HsDev.Inspect (inspectDocs, inspectDocsGhc) import HsDev.Project +import HsDev.Sandbox +import HsDev.Stack import HsDev.Symbols import HsDev.Tools.Ghc.Worker (ghcWorker) import HsDev.Tools.Ghc.Types (inferTypes) @@ -78,12 +83,12 @@ wait db dbval <- liftIO $ readAsync db let - cabals = ordNub $ mapMaybe (preview moduleCabal) updatedMods + dbs = ordNub $ mapMaybe (preview modulePackageDb) updatedMods projs = ordNub $ mapMaybe (preview $ moduleProject . _Just) updatedMods stand = any moduleStandalone updatedMods modifiedDb = mconcat $ concat [ - map (`cabalDB` dbval) cabals, + map (`packageDbDB` dbval) dbs, map (`projectDB` dbval) projs, [standaloneDB dbval | stand]] serverWriteCache modifiedDb @@ -115,10 +120,10 @@ s <- getSession im' <- liftExceptT $ S.scanModify (infer' s) im updater $ return $ fromModule im' - infer' :: Session -> [String] -> Cabal -> Module -> ExceptT String IO Module - infer' s opts cabal m = case preview (moduleLocation . moduleFile) m of + infer' :: Session -> [String] -> PackageDbStack -> Module -> ExceptT String IO Module + infer' s opts pdbs m = case preview (moduleLocation . moduleFile) m of Nothing -> return m - Just _ -> inWorkerT (sessionGhc s) $ inferTypes opts cabal m Nothing + Just _ -> inWorkerT (sessionGhc s) $ inferTypes opts pdbs m Nothing inWorkerT w = ExceptT . inWorker w . runExceptT -- | Post status @@ -242,14 +247,51 @@ where inFile f = maybe False (== f) . preview (moduleIdLocation . moduleFile) --- | Scan cabal modules -scanCabal :: UpdateMonad m => [String] -> Cabal -> m () -scanCabal opts cabalSandbox = runTask "scanning" cabalSandbox $ Log.scope "cabal" $ do - watch (\w -> watchSandbox w cabalSandbox opts) - mlocs <- liftExceptT $ listModules opts cabalSandbox - scan (Cache.loadCabal cabalSandbox) (cabalDB cabalSandbox) ((,,) <$> mlocs <*> pure [] <*> pure Nothing) opts $ \mlocs' -> do - ms <- liftExceptT $ browseModules opts cabalSandbox (mlocs' ^.. each . _1) - docs <- liftExceptT $ hdocsCabal cabalSandbox opts +-- | Scan cabal modules, doesn't rescan if already scanned +scanCabal :: UpdateMonad m => [String] -> m () +scanCabal opts = Log.scope "cabal" $ do + dbval <- readDB + let + scannedDbs = databasePackageDbs dbval + unscannedDbs = filter ((`notElem` scannedDbs) . topPackageDb) $ reverse $ packageDbStacks userDb + if null unscannedDbs + then do + Log.log Log.Trace $ "cabal (global-db and user-db) already scanned" + else runTasks $ map (scanPackageDb opts) unscannedDbs + +-- | Prepare sandbox for scanning. This is used for stack project to build & configure. +prepareSandbox :: UpdateMonad m => Sandbox -> m () +prepareSandbox sbox@(Sandbox StackWork fpath) = Log.scope "prepare" $ runTasks [ + runTask "building dependencies" sbox $ void $ liftIO $ runMaybeT $ buildDeps myaml, + runTask "configuring" sbox $ void $ liftIO $ runMaybeT $ configure myaml] + where + myaml = Just $ takeDirectory fpath </> "stack.yaml" +prepareSandbox _ = return () + +-- | Scan sandbox modules, doesn't rescan if already scanned +scanSandbox :: UpdateMonad m => [String] -> Sandbox -> m () +scanSandbox opts sbox = Log.scope "sandbox" $ do + dbval <- readDB + prepareSandbox sbox + pdbs <- liftExceptT $ sandboxPackageDbStack sbox + let + scannedDbs = databasePackageDbs dbval + unscannedDbs = filter ((`notElem` scannedDbs) . topPackageDb) $ reverse $ packageDbStacks pdbs + if null unscannedDbs + then do + Log.log Log.Trace $ "sandbox already scanned" + else runTasks $ map (scanPackageDb opts) unscannedDbs + +-- | Scan top of package-db stack, usable for rescan +scanPackageDb :: UpdateMonad m => [String] -> PackageDbStack -> m () +scanPackageDb opts pdbs = runTask "scanning" (topPackageDb pdbs) $ Log.scope "package-db" $ do + watch (\w -> watchPackageDb w pdbs opts) + mlocs <- liftM + (filter (\mloc -> preview modulePackageDb mloc == Just (topPackageDb pdbs))) $ + liftExceptT $ listModules opts pdbs + scan (Cache.loadPackageDb (topPackageDb pdbs)) (packageDbDB (topPackageDb pdbs)) ((,,) <$> mlocs <*> pure [] <*> pure Nothing) opts $ \mlocs' -> do + ms <- liftExceptT $ browseModules opts pdbs (mlocs' ^.. each . _1) + docs <- liftExceptT $ hdocsCabal pdbs opts updater $ return $ mconcat $ map (fromModule . fmap (setDocs' docs)) ms where setDocs' :: Map String (Map String String) -> Module -> Module @@ -259,12 +301,20 @@ scanProjectFile :: UpdateMonad m => [String] -> FilePath -> m Project scanProjectFile opts cabal = runTask "scanning" cabal $ liftExceptT $ S.scanProjectFile opts cabal +-- | Scan project and related package-db stack +scanProjectStack :: UpdateMonad m => [String] -> FilePath -> m () +scanProjectStack opts cabal = do + proj <- scanProjectFile opts cabal + scanProject opts cabal + sbox <- liftIO $ searchSandbox (view projectPath proj) + maybe (scanCabal opts) (scanSandbox opts) sbox + -- | Scan project scanProject :: UpdateMonad m => [String] -> FilePath -> m () scanProject opts cabal = runTask "scanning" (project cabal) $ Log.scope "project" $ do proj <- scanProjectFile opts cabal watch (\w -> watchProject w proj opts) - (_, sources) <- liftExceptT $ S.enumProject proj + S.ScanContents _ [(_, sources)] _ <- liftExceptT $ S.enumProject proj scan (Cache.loadProject $ view projectCabal proj) (projectDB proj) sources opts $ \ms -> do scanModules opts ms updater $ return $ fromProject proj @@ -272,14 +322,32 @@ -- | Scan directory for source files and projects scanDirectory :: UpdateMonad m => [String] -> FilePath -> m () scanDirectory opts dir = runTask "scanning" dir $ Log.scope "directory" $ do - S.ScanContents standSrcs projSrcs sboxes <- liftExceptT $ S.enumDirectory dir + S.ScanContents standSrcs projSrcs pdbss <- liftExceptT $ S.enumDirectory dir runTasks [scanProject opts (view projectCabal p) | (p, _) <- projSrcs] - runTasks $ map (scanCabal opts) sboxes + runTasks $ map (scanPackageDb opts) pdbss -- TODO: Don't rescan mapMOf_ (each . _1) (watch . flip watchModule) standSrcs scan (Cache.loadFiles (dir `isParent`)) (filterDB inDir (const False) . standaloneDB) standSrcs opts $ scanModules opts where inDir = maybe False (dir `isParent`) . preview (moduleIdLocation . moduleFile) +scanContents :: UpdateMonad m => [String] -> S.ScanContents -> m () +scanContents opts (S.ScanContents standSrcs projSrcs pdbss) = do + dbval <- readDB + let + projs = databaseProjects dbval ^.. each . projectCabal + pdbs = databasePackageDbs dbval + files = allModules (standaloneDB dbval) ^.. each . moduleLocation . moduleFile + srcs = standSrcs ^.. each . _1 . moduleFile + inSrcs src = src `elem` srcs && src `notElem` files + inFiles = maybe False inSrcs . preview (moduleIdLocation . moduleFile) + mapMOf_ (each . _1 . projectCabal) (\p -> Log.log Log.Trace ("scanning project: {}" ~~ p)) projSrcs + runTasks [scanProject opts (view projectCabal p) | (p, _) <- projSrcs, view projectCabal p `notElem` projs] + mapMOf_ (each . _1 . moduleFile) (\f -> Log.log Log.Trace ("scanning file: {}" ~~ f)) standSrcs + mapMOf_ (each . _1) (watch . flip watchModule) standSrcs + scan (Cache.loadFiles inSrcs) (filterDB inFiles (const False) . standaloneDB) standSrcs opts $ scanModules opts + mapMOf_ each (\s -> Log.log Log.Trace ("scanning package-db: {}" ~~ topPackageDb s)) pdbss + runTasks [scanPackageDb opts pdbs' | pdbs' <- pdbss, topPackageDb pdbs' `notElem` pdbs] + -- | Scan docs for inspected modules scanDocs :: UpdateMonad m => [InspectedModule] -> m () scanDocs ims = do @@ -341,11 +409,11 @@ ~~ ("proj" %= view projectName proj) scanProject projOpts $ view projectCabal proj | otherwise = return () -updateEvent (WatchedSandbox cabal cabalOpts) e +updateEvent (WatchedPackageDb pdbs opts) e | isConf e = do - Log.log Log.Info $ "Sandbox {cabal} changed" - ~~ ("cabal" %= cabal) - scanCabal cabalOpts cabal + Log.log Log.Info $ "Package db {package} changed" + ~~ ("package" %= topPackageDb pdbs) + scanPackageDb opts pdbs | otherwise = return () updateEvent WatchedModule e | isSource e = do
src/HsDev/Display.hs view
@@ -10,22 +10,24 @@ import Text.Format -import HsDev.Cabal -import HsDev.Symbols.Location +import HsDev.PackageDb import HsDev.Project +import HsDev.Sandbox +import HsDev.Symbols.Location class Display a where display :: a -> String displayType :: a -> String -instance Display Cabal where - display Cabal = "cabal" - display (Sandbox p) = "sandbox " ++ p - displayType _ = "cabal" +instance Display PackageDb where + display GlobalDb = "global-db" + display UserDb = "user-db" + display (PackageDb p) = "package-db " ++ p + displayType _ = "package-db" instance Display ModuleLocation where display (FileModule f _) = f - display (CabalModule _ _ n) = n + display (InstalledModule _ _ n) = n display (ModuleSource s) = fromMaybe "" s displayType _ = "module" @@ -33,11 +35,16 @@ display = view projectName displayType _ = "project" +instance Display Sandbox where + display (Sandbox _ fpath) = fpath + displayType (Sandbox CabalSandbox _) = "cabal-sandbox" + displayType (Sandbox StackWork _) = "stack-work" + instance Display FilePath where display = id displayType _ = "path" -instance FormatBuild Cabal where +instance FormatBuild PackageDb where formatBuild = formatBuild . display instance FormatBuild ModuleLocation where @@ -45,3 +52,4 @@ instance FormatBuild Project where formatBuild = formatBuild . display +
+ src/HsDev/PackageDb.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} + +module HsDev.PackageDb ( + PackageDb(..), packageDb, + PackageDbStack(..), packageDbStack, globalDb, userDb, fromPackageDb, fromPackageDbs, + topPackageDb, packageDbs, packageDbStacks, + isSubStack, + + packageDbOpt, packageDbStackOpts + ) where + +import Control.Applicative +import Control.Monad (guard) +import Control.Lens (makeLenses, each) +import Control.DeepSeq (NFData(..)) +import Data.Aeson +import Data.List (tails, isSuffixOf) + +import System.Directory.Paths +import HsDev.Util ((.::)) + +data PackageDb = GlobalDb | UserDb | PackageDb { _packageDb :: FilePath } deriving (Eq, Ord, Read, Show) + +makeLenses ''PackageDb + +instance NFData PackageDb where + rnf GlobalDb = () + rnf UserDb = () + rnf (PackageDb p) = rnf p + +instance ToJSON PackageDb where + toJSON GlobalDb = "global-db" + toJSON UserDb = "user-db" + toJSON (PackageDb p) = object ["package-db" .= p] + +instance FromJSON PackageDb where + parseJSON v = globalP v <|> userP v <|> dbP v where + globalP = withText "global-db" (\s -> guard (s == "global-db") >> return GlobalDb) + userP = withText "user-db" (\s -> guard (s == "user-db") >> return UserDb) + dbP = withObject "package-db" pathP where + pathP obj = PackageDb <$> obj .:: "package-db" + +instance Paths PackageDb where + paths _ GlobalDb = pure GlobalDb + paths _ UserDb = pure UserDb + paths f (PackageDb p) = PackageDb <$> f p + +-- | Stack of PackageDb in reverse order +newtype PackageDbStack = PackageDbStack { _packageDbStack :: [PackageDb] } deriving (Eq, Ord, Read, Show) + +makeLenses ''PackageDbStack + +instance NFData PackageDbStack where + rnf (PackageDbStack ps) = rnf ps + +instance ToJSON PackageDbStack where + toJSON (PackageDbStack ps) = toJSON ps + +instance FromJSON PackageDbStack where + parseJSON = fmap PackageDbStack . parseJSON + +instance Paths PackageDbStack where + paths f (PackageDbStack ps) = PackageDbStack <$> (each . paths) f ps + +-- | Global db stack +globalDb :: PackageDbStack +globalDb = PackageDbStack [] + +-- | User db stack +userDb :: PackageDbStack +userDb = PackageDbStack [UserDb] + +-- | Make package-db from one package-db +fromPackageDb :: FilePath -> PackageDbStack +fromPackageDb = PackageDbStack . return . PackageDb + +-- | Make package-db stack from paths +fromPackageDbs :: [FilePath] -> PackageDbStack +fromPackageDbs = PackageDbStack . map PackageDb . reverse + +-- | Get top package-db for package-db stack +topPackageDb :: PackageDbStack -> PackageDb +topPackageDb (PackageDbStack []) = GlobalDb +topPackageDb (PackageDbStack (d:_)) = d + +-- | Get list of package-db in stack, adds additional global-db at bottom +packageDbs :: PackageDbStack -> [PackageDb] +packageDbs = (GlobalDb :) . reverse . _packageDbStack + +-- | Get stacks for each package-db in stack +packageDbStacks :: PackageDbStack -> [PackageDbStack] +packageDbStacks = map PackageDbStack . tails . _packageDbStack + +-- | Is one package-db stack substack of another +isSubStack :: PackageDbStack -> PackageDbStack -> Bool +isSubStack (PackageDbStack l) (PackageDbStack r) = l `isSuffixOf` r + +-- | Get ghc options for package-db +packageDbOpt :: PackageDb -> String +packageDbOpt GlobalDb = "-global-package-db" +packageDbOpt UserDb = "-user-package-db" +packageDbOpt (PackageDb p) = "-package-db " ++ p + +-- | Get ghc options for package-db stack +packageDbStackOpts :: PackageDbStack -> [String] +packageDbStackOpts (PackageDbStack ps) = "-no-user-package-db" : map packageDbOpt (reverse ps)
src/HsDev/Project.hs view
@@ -3,17 +3,18 @@ module HsDev.Project ( Project(..), ProjectDescription(..), Target(..), Library(..), Executable(..), Test(..), Info(..), infoSourceDirsDef, - readProject, loadProject, getProjectSandbox, + readProject, loadProject, project, Extensions(..), withExtensions, infos, inTarget, fileTargets, findSourceDir, sourceDirs, - projectName, projectPath, projectCabal, projectDescription, projectLibrary, projectExecutables, projectTests, + projectName, projectPath, projectCabal, projectDescription, projectVersion, projectLibrary, projectExecutables, projectTests, libraryModules, libraryBuildInfo, executableName, executablePath, executableBuildInfo, testName, testEnabled, testBuildInfo, infoDepends, infoLanguage, infoExtensions, infoGHCOptions, infoSourceDirs, extensions, ghcOptions, entity, + targetOpts, -- * Helpers showExtension, flagExtension, extensionFlag, @@ -22,14 +23,16 @@ import Control.Arrow import Control.DeepSeq (NFData(..)) -import Control.Lens (makeLenses, Simple, Lens, view, lens) +import Control.Lens (makeLenses, Simple, Lens, view, lens, at) import Control.Exception import Control.Monad.Except import Data.Aeson import Data.Aeson.Types (Parser) import Data.List import Data.Maybe +import Data.Monoid import Data.Ord +import Data.Version (showVersion) import Distribution.Compiler (CompilerFlavor(GHC)) import qualified Distribution.Package as P import qualified Distribution.PackageDescription as PD @@ -40,7 +43,6 @@ import Language.Haskell.Extension import System.FilePath -import HsDev.Cabal (Cabal, getSandbox) import HsDev.Util -- | Cabal project @@ -81,6 +83,7 @@ v .:: "description" data ProjectDescription = ProjectDescription { + _projectVersion :: String, _projectLibrary :: Maybe Library, _projectExecutables :: [Executable], _projectTests :: [Test] } @@ -94,12 +97,14 @@ instance ToJSON ProjectDescription where toJSON d = object [ + "version" .= _projectVersion d, "library" .= _projectLibrary d, "executables" .= _projectExecutables d, "tests" .= _projectTests d] instance FromJSON ProjectDescription where parseJSON = withObject "project description" $ \v -> ProjectDescription <$> + v .:: "version" <*> v .:: "library" <*> v .:: "executables" <*> v .:: "tests" @@ -195,6 +200,15 @@ _infoSourceDirs :: [FilePath] } deriving (Eq, Read) +instance Monoid Info where + mempty = Info [] Nothing [] [] [] + mappend l r = Info + (ordNub $ _infoDepends l ++ _infoDepends r) + (getFirst $ First (_infoLanguage l) `mappend` First (_infoLanguage r)) + (_infoExtensions l ++ _infoExtensions r) + (_infoGHCOptions l ++ _infoGHCOptions r) + (ordNub $ _infoSourceDirs l ++ _infoSourceDirs r) + -- | infoSourceDirs lens with default infoSourceDirsDef :: Simple Lens Info [FilePath] infoSourceDirsDef = lens get' set' where @@ -235,6 +249,7 @@ analyzeCabal :: String -> Either String ProjectDescription analyzeCabal source = case liftM flattenDescr $ parsePackageDescription source of ParseOk _ r -> Right ProjectDescription { + _projectVersion = showVersion $ P.pkgVersion $ PD.package r, _projectLibrary = fmap toLibrary $ PD.library r, _projectExecutables = fmap toExecutable $ PD.executables r, _projectTests = fmap toTest $ PD.testSuites r } @@ -265,7 +280,7 @@ (\(n, t) -> t { PD.testName = n }) } where insertInfo :: (a -> PD.BuildInfo) -> (PD.BuildInfo -> a -> a) -> [P.Dependency] -> a -> a - insertInfo f s deps x = s ((f x) { PD.targetBuildDepends = deps }) x + insertInfo f s deps' x = s ((f x) { PD.targetBuildDepends = deps' }) x flattenTree :: Monoid a => (c -> a -> a) -> PD.CondTree v c a -> a flattenTree f (PD.CondNode x cs cmps) = f cs x `mappend` mconcat (concatMap flattenBranch cmps) where @@ -286,23 +301,18 @@ | isJust (_projectDescription p) = return p | otherwise = readProject (_projectCabal p) --- | Find project sandbox -getProjectSandbox :: Project -> IO Cabal -getProjectSandbox = getSandbox . _projectPath - -- | Make project by .cabal file project :: FilePath -> Project -project file - | takeExtension file == ".cabal" = Project { - _projectName = takeBaseName (takeDirectory file), - _projectPath = takeDirectory file, - _projectCabal = file, - _projectDescription = Nothing } - | otherwise = Project { - _projectName = takeBaseName file, - _projectPath = file, - _projectCabal = file </> (takeBaseName file <.> "cabal"), - _projectDescription = Nothing } +project file = Project { + _projectName = takeBaseName (takeDirectory cabal), + _projectPath = takeDirectory cabal, + _projectCabal = cabal, + _projectDescription = Nothing } + where + file' = dropTrailingPathSeparator $ normalise file + cabal + | takeExtension file' == ".cabal" = file' + | otherwise = file' </> (takeBaseName file' <.> "cabal") -- | Entity with project extensions data Extensions a = Extensions { @@ -369,6 +379,13 @@ parseDT :: Distribution.Text.Text a => String -> String -> Parser a parseDT typeName v = maybe err return (simpleParse v) where err = fail $ "Can't parse " ++ typeName ++ ": " ++ v + +-- | Get options for specific target +targetOpts :: Info -> [String] +targetOpts info' = concat [ + ["-i" ++ s | s <- _infoSourceDirs info'], + extensionsOpts $ withExtensions () info', + ["-package " ++ p | p <- _infoDepends info']] -- | Extension as flag name showExtension :: Extension -> String
+ src/HsDev/Sandbox.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} + +module HsDev.Sandbox ( + SandboxType(..), Sandbox(..), sandboxType, sandbox, + isSandbox, guessSandboxType, sandboxFromPath, + findSandbox, searchSandbox, sandboxPackageDbStack, searchPackageDbStack, restorePackageDbStack, + + -- * cabal-sandbox util + cabalSandboxLib, cabalSandboxPackageDb + -- * stack-work util + ) where + +import Control.Arrow +import Control.Applicative +import Control.DeepSeq (NFData(..)) +import Control.Monad.Trans.Maybe +import Control.Monad.Except +import Control.Lens (view, makeLenses, Lens', lens) +import Data.Aeson +import Data.Maybe (isJust, fromMaybe) +import Data.List ((\\)) +import qualified Data.Text as T (unpack) +import Distribution.Compiler +import Distribution.System +import qualified Distribution.Text as T (display) +import System.FilePath +import System.Directory + +import System.Directory.Paths +import HsDev.PackageDb +import HsDev.Scan.Browse (withPackages) +import HsDev.Stack +import HsDev.Util (searchPath) + +import qualified GHC +import qualified Packages as GHC + +data SandboxType = CabalSandbox | StackWork deriving (Eq, Ord, Read, Show, Enum, Bounded) + +data Sandbox = Sandbox { _sandboxType :: SandboxType, _sandbox :: FilePath } deriving (Eq, Ord) + +makeLenses ''Sandbox + +instance NFData SandboxType where + rnf CabalSandbox = () + rnf StackWork = () + +instance NFData Sandbox where + rnf (Sandbox t p) = rnf t `seq` rnf p + +instance Show Sandbox where + show (Sandbox _ p) = p + +instance ToJSON Sandbox where + toJSON (Sandbox _ p) = toJSON p + +instance FromJSON Sandbox where + parseJSON = withText "sandbox" sandboxPath where + sandboxPath = maybe (fail "Not a sandbox") return . sandboxFromPath . T.unpack + +instance Paths Sandbox where + paths f (Sandbox st p) = Sandbox st <$> f p + +isSandbox :: FilePath -> Bool +isSandbox = isJust . guessSandboxType + +guessSandboxType :: FilePath -> Maybe SandboxType +guessSandboxType fpath + | takeFileName fpath == ".cabal-sandbox" = Just CabalSandbox + | takeFileName fpath == ".stack-work" = Just StackWork + | otherwise = Nothing + +sandboxFromPath :: FilePath -> Maybe Sandbox +sandboxFromPath fpath = Sandbox <$> guessSandboxType fpath <*> pure fpath + +-- | Find sandbox in path +findSandbox :: FilePath -> IO (Maybe Sandbox) +findSandbox fpath = do + fpath' <- canonicalize fpath + isDir <- doesDirectoryExist fpath' + if isDir + then do + dirs <- liftM ((fpath' :) . map (fpath' </>) . (\\ [".", ".."])) $ getDirectoryContents fpath' + return $ msum $ map sandboxFromDir dirs + else return Nothing + where + sandboxFromDir :: FilePath -> Maybe Sandbox + sandboxFromDir fpath + | takeFileName fpath == "stack.yaml" = sandboxFromPath (takeDirectory fpath </> ".stack-work") + | otherwise = sandboxFromPath fpath + +-- | Search sandbox by parent directory +searchSandbox :: FilePath -> IO (Maybe Sandbox) +searchSandbox p = runMaybeT $ searchPath p (MaybeT . findSandbox) + +-- | Get package-db stack for sandbox +sandboxPackageDbStack :: Sandbox -> ExceptT String IO PackageDbStack +sandboxPackageDbStack (Sandbox CabalSandbox fpath) = do + dir <- cabalSandboxPackageDb + return $ PackageDbStack [PackageDb $ fpath </> dir] +sandboxPackageDbStack (Sandbox StackWork fpath) = maybeToExceptT "Can't locate stack environment" $ + liftM (view stackPackageDbStack) $ projectEnv $ takeDirectory fpath + +-- | Search package-db stack with user-db as default +searchPackageDbStack :: FilePath -> IO PackageDbStack +searchPackageDbStack p = do + mbox <- searchSandbox p + case mbox of + Nothing -> return userDb + Just sbox -> liftM (either (const userDb) id) $ runExceptT $ sandboxPackageDbStack sbox + where + userDb = PackageDbStack [UserDb] + +-- | Restore package-db stack by package-db +restorePackageDbStack :: PackageDb -> IO PackageDbStack +restorePackageDbStack GlobalDb = return globalDb +restorePackageDbStack UserDb = return userDb +restorePackageDbStack (PackageDb p) = liftM (fromMaybe $ fromPackageDb p) $ runMaybeT $ do + sbox <- MaybeT $ searchSandbox p + exceptToMaybeT $ sandboxPackageDbStack sbox + +-- | Get actual sandbox build path: <arch>-<platform>-<compiler>-<version> +cabalSandboxLib :: ExceptT String IO FilePath +cabalSandboxLib = do + res <- withPackages ["-no-user-package-db"] $ + return . + map (GHC.packageNameString &&& GHC.packageVersion) . + fromMaybe [] . + GHC.pkgDatabase + let + compiler = T.display buildCompilerFlavor + CompilerId _ version = buildCompilerId + ver = maybe (T.display version) T.display $ lookup compiler res + return $ T.display buildPlatform ++ "-" ++ compiler ++ "-" ++ ver + +-- | Get sandbox package-db: <arch>-<platform>-<compiler>-<version>-packages.conf.d +cabalSandboxPackageDb :: ExceptT String IO FilePath +cabalSandboxPackageDb = liftM (++ "-packages.conf.d") cabalSandboxLib
src/HsDev/Scan.hs view
@@ -1,7 +1,10 @@+{-# LANGUAGE FlexibleInstances #-} + module HsDev.Scan ( -- * Enumerate functions - enumCabal, CompileFlag, ModuleToScan, ProjectToScan, SandboxToScan, ScanContents(..), - enumProject, enumDirectory, + CompileFlag, ModuleToScan, ProjectToScan, PackageDbToScan, ScanContents(..), + EnumContents(..), + enumProject, enumSandbox, enumDirectory, -- * Scan scanProjectFile, @@ -15,47 +18,103 @@ import Control.Applicative ((<|>)) import Control.DeepSeq -import Control.Lens (view, preview, set, _Right, _1, _2, _3, (^.)) +import Control.Lens (view, preview, set, over, each, _Right, _1, _2, _3, (^.), (^..), at) import Control.Monad.Except import Data.Maybe (catMaybes, fromMaybe, isJust) +import Data.List (intercalate) import System.Directory +import Text.Format +import Data.Deps import HsDev.Scan.Browse (browsePackages) +import HsDev.Server.Types (FileContents(..)) +import HsDev.Sandbox import HsDev.Symbols import HsDev.Symbols.Types import HsDev.Database +import HsDev.Display import HsDev.Tools.GhcMod import HsDev.Inspect import HsDev.Util --- | Enum cabal modules -enumCabal :: [String] -> Cabal -> ExceptT String IO [ModuleLocation] -enumCabal = list - -- | Compile flags type CompileFlag = String -- | Module with flags ready to scan type ModuleToScan = (ModuleLocation, [CompileFlag], Maybe String) -- | Project ready to scan type ProjectToScan = (Project, [ModuleToScan]) --- | Cabal sandbox to scan -type SandboxToScan = Cabal +-- | Package-db sandbox to scan (top of stack) +type PackageDbToScan = PackageDbStack -- | Scan info data ScanContents = ScanContents { modulesToScan :: [ModuleToScan], projectsToScan :: [ProjectToScan], - sandboxesToScan :: [SandboxToScan] } + sandboxesToScan :: [PackageDbStack] } instance NFData ScanContents where rnf (ScanContents ms ps ss) = rnf ms `seq` rnf ps `seq` rnf ss +instance Monoid ScanContents where + mempty = ScanContents [] [] [] + mappend (ScanContents lm lp ls) (ScanContents rm rp rs) = ScanContents + (uniqueBy (view _1) $ lm ++ rm) + (uniqueBy (view _1) $ lp ++ rp) + (ordNub $ ls ++ rs) + +instance FormatBuild ScanContents where + formatBuild (ScanContents ms ps cs) = formatBuild str where + str :: String + str = format "modules: {}, projects: {}, package-dbs: {}" + ~~ (intercalate ", " $ ms ^.. each . _1 . moduleFile) + ~~ (intercalate ", " $ ps ^.. each . _1 . projectPath) + ~~ (intercalate ", " $ map (display . topPackageDb) $ cs ^.. each) + +class EnumContents a where + enumContents :: a -> ExceptT String IO ScanContents + +instance EnumContents ModuleLocation where + enumContents mloc = return $ ScanContents [(mloc, [], Nothing)] [] [] + +instance EnumContents (Extensions ModuleLocation) where + enumContents ex = return $ ScanContents [(view entity ex, extensionsOpts ex, Nothing)] [] [] + +instance EnumContents Project where + enumContents = enumProject + +instance EnumContents PackageDbStack where + enumContents pdbs = return $ ScanContents [] [] (packageDbStacks pdbs) + +instance EnumContents Sandbox where + enumContents = enumSandbox + +instance {-# OVERLAPPING #-} EnumContents a => EnumContents [a] where + enumContents = liftM mconcat . tries . map enumContents + +instance {-# OVERLAPS #-} EnumContents FilePath where + enumContents f + | haskellSource f = do + mproj <- liftEIO $ locateProject f + case mproj of + Nothing -> enumContents $ FileModule f Nothing + Just proj -> do + ScanContents _ [(_, mods)] _ <- enumContents proj + return $ ScanContents (filter ((== Just f) . preview (_1 . moduleFile)) mods) [] [] + | otherwise = enumDirectory f + +instance EnumContents FileContents where + enumContents (FileContents f cts) + | haskellSource f = do + ScanContents [(m, opts, _)] _ _ <- enumContents f + return $ ScanContents [(m, opts, Just cts)] [] [] + | otherwise = return mempty + -- | Enum project sources -enumProject :: Project -> ExceptT String IO ProjectToScan +enumProject :: Project -> ExceptT String IO ScanContents enumProject p = do p' <- loadProject p - cabal <- liftE $ searchSandbox (view projectPath p') - pkgs <- liftM (map $ view packageName) $ browsePackages [] cabal + pdbs <- liftE $ searchPackageDbStack (view projectPath p') + pkgs <- liftM (map $ view packageName) $ browsePackages [] pdbs let projOpts :: FilePath -> [String] projOpts f = concatMap makeOpts $ fileTargets p' f where @@ -65,8 +124,15 @@ ["-package " ++ view projectName p'], ["-package " ++ dep | dep <- view infoDepends i, dep `elem` pkgs]] srcs <- projectSources p' - return (p', [(FileModule (view entity src) (Just p'), extensionsOpts src ++ projOpts (view entity src), Nothing) | src <- srcs]) + let + mlocs = over each (\src -> over ghcOptions (++ projOpts (view entity src)) . over entity (\f -> FileModule f (Just p')) $ src) srcs + mods <- liftM modulesToScan $ enumContents mlocs + return $ ScanContents [] [(p', mods)] [] -- (sandboxCabals sboxes) +-- | Enum sandbox +enumSandbox :: Sandbox -> ExceptT String IO ScanContents +enumSandbox = sandboxPackageDbStack >=> enumContents + -- | Enum directory modules enumDirectory :: FilePath -> ExceptT String IO ScanContents enumDirectory dir = do @@ -75,15 +141,16 @@ projects = filter cabalFile cts sources = filter haskellSource cts dirs <- liftE $ filterM doesDirectoryExist cts - sboxes <- liftM catMaybes $ triesMap (liftE . findPackageDb) dirs - projs <- triesMap (enumProject . project) projects + sboxes <- liftM catMaybes $ triesMap (liftE . findSandbox) dirs + pdbs <- mapM enumSandbox sboxes + projs <- liftM mconcat $ triesMap (enumProject . project) projects let - projPaths = map (view projectPath . fst) projs + projPaths = map (view projectPath . fst) $ projectsToScan projs standalone = map (`FileModule` Nothing) $ filter (\s -> not (any (`isParent` s) projPaths)) sources - return ScanContents { - modulesToScan = [(s, [], Nothing) | s <- standalone], - projectsToScan = projs, - sandboxesToScan = map Sandbox sboxes } + return $ mconcat [ + ScanContents [(s, [], Nothing) | s <- standalone] [] [], + projs, + mconcat pdbs] -- | Scan project file scanProjectFile :: [String] -> FilePath -> ExceptT String IO Project @@ -101,20 +168,30 @@ -- infer' m = tryInfer <|> return m where -- tryInfer = mapExceptT (withCurrentDirectory (sourceModuleRoot (moduleName m) f)) $ -- runGhcMod defaultOptions $ inferTypes opts Cabal m -scanModule _ opts (CabalModule c p n) _ = browse opts c n p +scanModule _ opts (InstalledModule c p n) _ = do + pdbs <- liftIO $ getDbs c + browse opts pdbs n p + where + getDbs :: PackageDb -> IO PackageDbStack + getDbs = maybe (return userDb) searchPackageDbStack . preview packageDb scanModule _ _ (ModuleSource _) _ = throwError "Can inspect only modules in file or cabal" -- | Scan additional info and modify scanned module. Dones't fail on error, just left module unchanged -scanModify :: ([String] -> Cabal -> Module -> ExceptT String IO Module) -> InspectedModule -> ExceptT String IO InspectedModule +scanModify :: ([String] -> PackageDbStack -> Module -> ExceptT String IO Module) -> InspectedModule -> ExceptT String IO InspectedModule scanModify f im = traverse f' im <|> return im where - -- TODO: Get actual sandbox - f' = f (fromMaybe [] $ preview (inspection . inspectionOpts) im) Cabal + f' m = do + pdbs <- liftIO $ case view moduleLocation m of + -- TODO: Get actual sandbox stack + FileModule fpath _ -> searchPackageDbStack fpath + InstalledModule pdb _ _ -> maybe (return userDb) searchPackageDbStack $ preview packageDb pdb + _ -> return userDb + f (fromMaybe [] $ preview (inspection . inspectionOpts) im) pdbs m -- | Is inspected module up to date? upToDate :: [String] -> InspectedModule -> ExceptT String IO Bool upToDate opts (Inspected insp m _) = case m of FileModule f _ -> liftM (== insp) $ fileInspection f opts - CabalModule _ _ _ -> return $ insp == browseInspection opts + InstalledModule _ _ _ -> return $ insp == browseInspection opts _ -> return False -- | Rescan inspected module
src/HsDev/Scan/Browse.hs view
@@ -1,26 +1,34 @@ module HsDev.Scan.Browse ( -- * List all packages - browsePackages, + browsePackages, browsePackagesDeps, -- * Scan cabal modules - listModules, browseModules, browse, + listModules, browseModules, browse, browseDb, -- * Helpers - withPackages, withPackages_, packageDbModules, lookupModule_, + withPackages, withPackages_, + readPackage, ghcPackageDb, ghcModuleLocation, + packageDbCandidate, packageDbCandidate_, + packageConfigs, packageDbModules, lookupModule_, module Control.Monad.Except ) where +import Control.Arrow import Control.Lens (view, preview, _Just) import Control.Monad.Except +import Data.List (isPrefixOf) import Data.Maybe import Data.String (fromString) +import Data.Version +import System.Directory +import System.FilePath import Text.Read (readMaybe) -import HsDev.Cabal +import Data.Deps +import HsDev.PackageDb import HsDev.Symbols import HsDev.Tools.Base (inspect) import HsDev.Util (liftIOErrors, ordNub) -import Data.Version import qualified ConLike as GHC import qualified DataCon as GHC import qualified DynFlags as GHC @@ -38,29 +46,45 @@ import Pretty -- | Browse packages -browsePackages :: [String] -> Cabal -> ExceptT String IO [ModulePackage] -browsePackages opts cabal = liftIOErrors $ withPackages (cabalOpt cabal ++ opts) $ \dflags -> - return $ mapMaybe readPackage $ fromMaybe [] $ GHC.pkgDatabase dflags +browsePackages :: [String] -> PackageDbStack -> ExceptT String IO [ModulePackage] +browsePackages opts dbs = liftIOErrors $ withPackages_ (packageDbStackOpts dbs ++ opts) $ + liftM (map readPackage) $ lift packageConfigs -listModules :: [String] -> Cabal -> ExceptT String IO [ModuleLocation] -listModules opts cabal = liftIOErrors $ withPackages_ (cabalOpt cabal ++ opts) $ do +-- | Get packages with deps +browsePackagesDeps :: [String] -> PackageDbStack -> ExceptT String IO (Deps ModulePackage) +browsePackagesDeps opts dbs = liftIOErrors $ withPackages (packageDbStackOpts dbs ++ opts) $ \df -> do + cfgs <- lift packageConfigs + return $ mapDeps (toPkg df) $ mconcat $ map (uncurry deps) $ + map (GHC.installedPackageId &&& GHC.depends) cfgs + where + toPkg df' = readPackage . GHC.getPackageDetails df' . GHC.resolveInstalledPackageId df' + +listModules :: [String] -> PackageDbStack -> ExceptT String IO [ModuleLocation] +listModules opts dbs = liftIOErrors $ withPackages_ (packageDbStackOpts dbs ++ opts) $ do ms <- lift packageDbModules - return $ map (uncurry $ ghcModuleLocation cabal) ms + pdbs <- mapM (liftIO . ghcPackageDb . fst) ms + return [ghcModuleLocation pdb p m | (pdb, (p, m)) <- zip pdbs ms] -browseModules :: [String] -> Cabal -> [ModuleLocation] -> ExceptT String IO [InspectedModule] -browseModules opts cabal mlocs = liftIOErrors $ withPackages_ (cabalOpt cabal ++ opts) $ do +browseModules :: [String] -> PackageDbStack -> [ModuleLocation] -> ExceptT String IO [InspectedModule] +browseModules opts dbs mlocs = liftIOErrors $ withPackages_ (packageDbStackOpts dbs ++ opts) $ do ms <- lift packageDbModules - liftM catMaybes $ mapM (uncurry browseModule') [(p, m) | (p, m) <- ms, ghcModuleLocation cabal p m `elem` mlocs] + pdbs <- mapM (liftIO . ghcPackageDb . fst) ms + liftM catMaybes $ sequence [browseModule' pdb p m | (pdb, (p, m)) <- zip pdbs ms, ghcModuleLocation pdb p m `elem` mlocs] where - browseModule' :: GHC.PackageConfig -> GHC.Module -> ExceptT String GHC.Ghc (Maybe InspectedModule) - browseModule' p m = tryT $ inspect (ghcModuleLocation cabal p m) (return $ InspectionAt 0 opts) (browseModule cabal p m) + browseModule' :: PackageDb -> GHC.PackageConfig -> GHC.Module -> ExceptT String GHC.Ghc (Maybe InspectedModule) + browseModule' pdb p m = tryT $ inspect (ghcModuleLocation pdb p m) (return $ InspectionAt 0 opts) (browseModule pdb p m) --- | Browse all modules -browse :: [String] -> Cabal -> ExceptT String IO [InspectedModule] -browse opts cabal = listModules opts cabal >>= browseModules opts cabal +-- | Browse modules, if third argument is True - browse only modules in top of package-db stack +browse :: [String] -> PackageDbStack -> ExceptT String IO [InspectedModule] +browse opts dbs = listModules opts dbs >>= browseModules opts dbs -browseModule :: Cabal -> GHC.PackageConfig -> GHC.Module -> ExceptT String GHC.Ghc Module -browseModule cabal package m = do +-- | Browse modules in top of package-db stack +browseDb :: [String] -> PackageDbStack -> ExceptT String IO [InspectedModule] +browseDb opts dbs = listModules opts dbs >>= browseModules opts dbs . filter inTop where + inTop = (== Just (topPackageDb dbs)) . preview modulePackageDb + +browseModule :: PackageDb -> GHC.PackageConfig -> GHC.Module -> ExceptT String GHC.Ghc Module +browseModule pdb package m = do mi <- lift (GHC.getModuleInfo m) >>= maybe (throwError "Can't find module info") return ds <- mapM (toDecl mi) (GHC.modInfoExports mi) let @@ -75,7 +99,7 @@ where thisLoc = view moduleIdLocation $ mloc m mloc m' = ModuleId (fromString mname') $ - CabalModule cabal (readPackage package) mname' + ghcModuleLocation pdb package m' where mname' = GHC.moduleNameString $ GHC.moduleName m' toDecl minfo n = do @@ -146,17 +170,62 @@ tryT :: Monad m => ExceptT e m a -> ExceptT e m (Maybe a) tryT act = catchError (liftM Just act) (const $ return Nothing) -readPackage :: GHC.PackageConfig -> Maybe ModulePackage -readPackage pc = readMaybe $ GHC.packageNameString pc ++ "-" ++ showVersion (GHC.packageVersion pc) +readPackage :: GHC.PackageConfig -> ModulePackage +readPackage pc = ModulePackage (GHC.packageNameString pc) (showVersion (GHC.packageVersion pc)) -ghcModuleLocation :: Cabal -> GHC.PackageConfig -> GHC.Module -> ModuleLocation -ghcModuleLocation cabal p m = CabalModule cabal (readPackage p) (GHC.moduleNameString $ GHC.moduleName m) +ghcModuleLocation :: PackageDb -> GHC.PackageConfig -> GHC.Module -> ModuleLocation +ghcModuleLocation pdb p m = InstalledModule pdb (Just $ readPackage p) (GHC.moduleNameString $ GHC.moduleName m) +ghcPackageDb :: GHC.PackageConfig -> IO PackageDb +ghcPackageDb = maybe (return GlobalDb) packageDbCandidate_ . listToMaybe . GHC.libraryDirs + +-- | Get package-db for package library directory +-- Haskish way +-- global-db - library is in <path>/lib/<package> and there exists <path>/lib/package.conf.d +-- user-db - library is in cabal user directory +-- package-db +-- cabal-sandbox - library in .../.cabal-sandbox/.../<platform-ghc-ver>/<package> +-- then package-db is .../.cabal-sandbox/<platform-ghc-ver>-package.conf.d +-- stack (snapshots or .stack-work) - library in <path>/lib/<platform-ghc-ver>/<package> +-- then package-db is <path>/pkgdb +packageDbCandidate :: FilePath -> IO (Maybe PackageDb) +packageDbCandidate fpath = liftM Just (msum [global', user', sandbox', stack']) `mplus` return Nothing where + global' = do + guard (takeFileName (takeDirectory fpath') == "lib") + guardExist (takeDirectory fpath' </> "package.conf.d") + return GlobalDb + user' = do + cabalDir <- getAppUserDataDirectory "cabal" + guard (splitDirectories cabalDir `isPrefixOf` splitDirectories fpath') + return UserDb + sandbox' = do + guard (".cabal-sandbox" `elem` splitDirectories fpath') + let + sandboxPath = joinPath $ reverse $ dropWhile (/= ".cabal-sandbox") $ reverse $ splitDirectories fpath' + platform = takeFileName (takeDirectory fpath') + dbPath = sandboxPath </> (platform ++ "-packages.conf.d") + guardExist dbPath + return $ PackageDb dbPath + stack' = do + guard (takeFileName (takeDirectory (takeDirectory fpath')) == "lib") + let + pkgDb = takeDirectory (takeDirectory $ takeDirectory fpath') </> "pkgdb" + guardExist pkgDb + return $ PackageDb pkgDb + guardExist = doesDirectoryExist >=> guard + fpath' = normalise fpath + +-- | Use global as default +packageDbCandidate_ :: FilePath -> IO PackageDb +packageDbCandidate_ = packageDbCandidate >=> maybe (return GlobalDb) return + +packageConfigs :: GHC.GhcMonad m => m [GHC.PackageConfig] +packageConfigs = liftM (fromMaybe [] . GHC.pkgDatabase) GHC.getSessionDynFlags + packageDbModules :: GHC.GhcMonad m => m [(GHC.PackageConfig, GHC.Module)] packageDbModules = do + pkgs <- packageConfigs dflags <- GHC.getSessionDynFlags - let - pkgs = fromMaybe [] $ GHC.pkgDatabase dflags return [(p, m) | p <- pkgs, mn <- map GHC.exposedName (GHC.exposedModules p),
src/HsDev/Server/Base.hs view
@@ -16,20 +16,24 @@ import Control.Monad.Except import Control.Monad.Reader import Data.Default +import Data.List (intercalate) import qualified Data.Map as M import Data.Maybe import Data.String import Data.Text (Text) import qualified Data.Text as T (pack, unpack) -import System.Log.Simple hiding (Level(..), Message(..), Command(..)) +import System.Log.Simple hiding (Level(..), Message(..), Command(..), (%=)) import qualified System.Log.Simple.Base as Log import qualified System.Log.Simple as Log +import System.Directory (removeDirectoryRecursive, createDirectoryIfMissing) +import System.FilePath import qualified Control.Concurrent.FiniteChan as F import System.Directory.Paths (canonicalize) import qualified System.Directory.Watcher as Watcher -import Text.Format ((~~), FormatBuild(..)) +import Text.Format ((~~), FormatBuild(..), (%=)) +import qualified HsDev.Cache as Cache import qualified HsDev.Cache.Structured as SC import qualified HsDev.Client.Commands as Client import HsDev.Database @@ -44,9 +48,6 @@ #if mingw32_HOST_OS import System.Win32.FileMapping.NamePool -#else -import System.Posix.Process -import System.Posix.IO #endif -- | Inits log chan and returns functions (print message, wait channel) @@ -73,11 +74,25 @@ runServer sopts act = bracket (initLog sopts) (\(_, _, _, x) -> x) $ \(logger', outputStr, listenLog, waitOutput) -> Log.scopeLog logger' (T.pack "hsdev") $ Watcher.withWatcher $ \watcher -> do waitSem <- newQSem 0 db <- DB.newAsync + withCache sopts () $ \cdir -> do + outputStr Log.Trace $ "Checking cache version in {}" ~~ cdir + ver <- Cache.readVersion $ cdir </> Cache.versionCache + outputStr Log.Debug $ "Cache version: {}" ~~ strVersion ver + when (not $ sameVersion (cutVersion version) (cutVersion ver)) $ ignoreIO $ do + outputStr Log.Info $ "Cache version ({cache}) is incompatible with hsdev version ({hsdev}), removing cache ({dir})" ~~ + ("cache" %= strVersion ver) ~~ + ("hsdev" %= strVersion version) ~~ + ("dir" %= cdir) + -- drop cache + removeDirectoryRecursive cdir + outputStr Log.Debug $ "Writing new cache version: {}" ~~ strVersion version + createDirectoryIfMissing True cdir + Cache.writeVersion $ cdir </> Cache.versionCache when (serverLoad sopts) $ withCache sopts () $ \cdir -> do - outputStr Log.Info $ "Loading cache from " ++ cdir + outputStr Log.Info $ "Loading cache from {}" ~~ cdir dbCache <- liftA merge <$> SC.load cdir case dbCache of - Left err -> outputStr Log.Error $ "Failed to load cache: " ++ err + Left err -> outputStr Log.Error $ "Failed to load cache: {}" ~~ err Right dbCache' -> DB.update db (return dbCache') #if mingw32_HOST_OS mmapPool <- Just <$> createPool "hsdev" @@ -138,7 +153,7 @@ let sd = structurize db liftIO $ SC.dump cdir sd - forM_ (M.keys (structuredCabals sd)) $ \c -> Log.log Log.Debug ("cache write: cabal {}" ~~ show c) + forM_ (M.keys (structuredPackageDbs sd)) $ \c -> Log.log Log.Debug ("cache write: cabal {}" ~~ show c) forM_ (M.keys (structuredProjects sd)) $ \p -> Log.log Log.Debug ("cache write: project {}" ~~ p) case allModules (structuredFiles sd) of [] -> return () @@ -154,7 +169,7 @@ where cacheErr e = Log.log Log.Error ("Error reading cache: {}" ~~ e) >> return Nothing cacheOk s = do - forM_ (M.keys (structuredCabals s)) $ \c -> Log.log Log.Debug ("cache read: cabal {}" ~~ show c) + forM_ (M.keys (structuredPackageDbs s)) $ \c -> Log.log Log.Debug ("cache read: cabal {}" ~~ show c) forM_ (M.keys (structuredProjects s)) $ \p -> Log.log Log.Debug ("cache read: project {}" ~~ p) case allModules (structuredFiles s) of [] -> return ()
src/HsDev/Server/Commands.hs view
@@ -14,7 +14,7 @@ import Control.Applicative import Control.Concurrent import Control.Concurrent.Async -import Control.Exception (SomeException, handle) +import Control.Exception (SomeException) import Control.Lens (set, traverseOf, view, over, Lens', Lens, _1, _2, _Left) import Control.Monad import Control.Monad.CatchIO @@ -41,7 +41,7 @@ import Control.Concurrent.Util import qualified Control.Concurrent.FiniteChan as F import Data.Lisp -import Text.Format ((~~)) +import Text.Format ((~~), (%=)) import System.Directory.Paths import qualified HsDev.Client.Commands as Client @@ -61,7 +61,9 @@ import System.Win32.FileMapping.NamePool import System.Win32.PowerShell (escape, quote, quoteDouble) #else +import Control.Exception (handle) import System.Posix.Process +import System.Posix.Files (removeLink) import System.Posix.IO #endif @@ -85,9 +87,9 @@ Right v -> return v _ <- traverse parseData input -- FIXME: Not used! - s <- makeSocket + s <- makeSocket (clientPort copts) addr' <- inet_addr "127.0.0.1" - Net.connect s (SockAddrInet (fromIntegral $ clientPort copts) addr') + Net.connect s (sockAddr (clientPort copts) addr') bracket (socketToHandle s ReadWriteMode) hClose $ \h -> do L.hPutStrLn h $ encode $ Message Nothing $ Request c curDir noFile (clientTimeout copts) (clientSilent copts) hFlush h @@ -119,23 +121,23 @@ -- start-process foo 'bar baz' ⇒ foo bar baz -- not expected -- start-process foo '"bar baz"' ⇒ foo "bar baz" -- ok biescape = escape quote . escape quoteDouble - script = "try {{ start-process {} {} -WindowStyle Hidden -WorkingDirectory {} }} catch {{ $_.Exception, $_.InvocationInfo.Line }}" - ~~ escape quote myExe - ~~ intercalate ", " (map biescape args) - ~~ escape quote curDir + script = "try {{ start-process {process} {args} -WindowStyle Hidden -WorkingDirectory {dir} }} catch {{ $_.Exception, $_.InvocationInfo.Line }}" + ~~ ("process" %= escape quote myExe) + ~~ ("args" %= intercalate ", " (map biescape args)) + ~~ ("dir" %= escape quote curDir) r <- readProcess "powershell" [ "-Command", script] "" if all isSpace r - then putStrLn $ "Server started at port " ++ show (serverPort sopts) + then putStrLn $ "Server started at port {}" ~~ serverPort sopts else mapM_ putStrLn [ "Failed to start server", - "\tCommand: " ++ script, - "\tResult: " ++ r] + "\tCommand: {}" ~~ script, + "\tResult: {}" ~~ r] #else let forkError :: SomeException -> IO () - forkError e = putStrLn $ "Failed to start server: " ++ show e + forkError e = putStrLn $ "Failed to start server: {}" ~~ show e proxy :: IO () proxy = do @@ -156,17 +158,18 @@ putStrLn $ "Server started at port {}" ~~ serverPort sopts #endif runServerCommand (Run sopts) = runServer sopts $ do - Log.log Log.Info $ "Server started at port {}" ~~ serverPort sopts + q <- liftIO $ newQSem 0 clientChan <- liftIO F.newChan session <- getSession _ <- liftIO $ async $ withSession session $ Log.scope "listener" $ flip finally serverExit $ - bracket (liftIO makeSocket) (liftIO . close) $ \s -> do + bracket (liftIO $ makeSocket (serverPort sopts)) (liftIO . close) $ \s -> do liftIO $ do setSocketOption s ReuseAddr 1 - bind s $ SockAddrInet (fromIntegral $ serverPort sopts) iNADDR_ANY + bind s $ sockAddr (serverPort sopts) iNADDR_ANY listen s maxListenQueue forever $ logAsync (Log.log Log.Fatal . fromString) $ logIO "exception: " (Log.log Log.Error . fromString) $ do Log.log Log.Trace "accepting connection" + liftIO $ signalQSem q s' <- liftIO $ fst <$> accept s Log.log Log.Trace $ "accepted {}" ~~ show s' void $ liftIO $ forkIO $ withSession session $ Log.scope (T.pack $ show s') $ @@ -186,9 +189,13 @@ liftIO $ F.putChan clientChan timeoutWait processClientSocket s' + Log.log Log.Trace "waiting for starting accept thread" + liftIO $ waitQSem q + Log.log Log.Info $ "Server started at port {}" ~~ serverPort sopts Log.log Log.Trace "waiting for accept thread" serverWait Log.log Log.Trace "accept thread stopped" + liftIO $ unlink (serverPort sopts) askSession sessionDatabase >>= liftIO . DB.readAsync >>= writeCache sopts Log.log Log.Trace "waiting for clients" liftIO (F.stopChan clientChan) >>= sequence_ @@ -196,9 +203,9 @@ runServerCommand (Stop copts) = runServerCommand (Remote copts False Exit) runServerCommand (Connect copts) = do curDir <- getCurrentDirectory - s <- makeSocket + s <- makeSocket $ clientPort copts addr' <- inet_addr "127.0.0.1" - Net.connect s (SockAddrInet (fromIntegral $ clientPort copts) addr') + Net.connect s $ sockAddr (clientPort copts) addr' bracket (socketToHandle s ReadWriteMode) hClose $ \h -> forM_ [(1 :: Integer)..] $ \i -> ignoreIO $ do input' <- hGetLineBS stdin case decodeMsg input' of @@ -215,7 +222,9 @@ Left em -> putStrLn $ "Can't decode response: {}" ~~ view msg em Right m -> do Response r' <- unMmap $ view (msg . message) m - putStrLn $ "{}: {}" ~~ fromMaybe "_" (view (msg . messageId) m) ~~ fromUtf8 (encodeMsg $ set msg (Response r') m) + putStrLn $ "{id}: {response}" + ~~ ("id" %= fromMaybe "_" (view (msg . messageId) m)) + ~~ ("response" %= fromUtf8 (encodeMsg $ set msg (Response r') m)) case unResponse (view (msg . message) m) of Left _ -> waitResp h _ -> return () @@ -412,5 +421,18 @@ #endif unMmap r = return r -makeSocket :: IO Socket -makeSocket = socket AF_INET Stream defaultProtocol +makeSocket :: ConnectionPort -> IO Socket +makeSocket (NetworkPort _) = socket AF_INET Stream defaultProtocol +makeSocket (UnixPort _) = socket AF_UNIX Stream defaultProtocol + +sockAddr :: ConnectionPort -> HostAddress -> SockAddr +sockAddr (NetworkPort p) addr = SockAddrInet (fromIntegral p) addr +sockAddr (UnixPort s) _ = SockAddrUnix s + +unlink :: ConnectionPort -> IO () +unlink (NetworkPort _) = return () +#if mingw32_HOST_OS +unlink (UnixPort _) = return () +#else +unlink (UnixPort s) = removeLink s +#endif
src/HsDev/Server/Types.hs view
@@ -6,7 +6,7 @@ Session(..), SessionMonad(..), askSession, ServerM(..), CommandOptions(..), CommandError(..), commandErrorMsg, commandErrorDetails, commandError, commandError_, CommandMonad(..), askOptions, ClientM(..), withSession, serverListen, serverWait, serverUpdateDB, serverWriteCache, serverReadCache, serverExit, commandRoot, commandNotify, commandLink, commandHold, - ServerCommand(..), ServerOpts(..), ClientOpts(..), serverOptsArgs, Request(..), + ServerCommand(..), ConnectionPort(..), ServerOpts(..), ClientOpts(..), serverOptsArgs, Request(..), Command(..), AddedContents(..), GhcModCommand(..), @@ -32,10 +32,12 @@ import System.Log.Simple hiding (Command) import System.Directory.Paths +import Text.Format (FormatBuild(..)) import HsDev.Database import qualified HsDev.Database.Async as DB import HsDev.Project +import HsDev.Sandbox import HsDev.Symbols import HsDev.Server.Message import HsDev.Watcher.Types (Watcher) @@ -214,9 +216,20 @@ Remote ClientOpts Bool Command deriving (Show) +data ConnectionPort = NetworkPort Int | UnixPort String deriving (Eq, Read) + +instance Default ConnectionPort where + def = NetworkPort 4567 + +instance Show ConnectionPort where + show (NetworkPort p) = show p + show (UnixPort s) = "unix " ++ s + +instance FormatBuild ConnectionPort + -- | Server options data ServerOpts = ServerOpts { - serverPort :: Int, + serverPort :: ConnectionPort, serverTimeout :: Int, serverLog :: Maybe FilePath, serverLogConfig :: String, @@ -225,11 +238,11 @@ deriving (Show) instance Default ServerOpts where - def = ServerOpts 4567 0 Nothing "use default" Nothing False + def = ServerOpts def 0 Nothing "use default" Nothing False -- | Client options data ClientOpts = ClientOpts { - clientPort :: Int, + clientPort :: ConnectionPort, clientPretty :: Bool, clientStdin :: Bool, clientTimeout :: Int, @@ -237,7 +250,7 @@ deriving (Show) instance Default ClientOpts where - def = ClientOpts 4567 False False 0 False + def = ClientOpts def False False 0 False instance FromCmd ServerCommand where cmdP = serv <|> remote where @@ -251,7 +264,7 @@ instance FromCmd ServerOpts where cmdP = ServerOpts <$> - (portArg <|> pure (serverPort def)) <*> + (connectionArg <|> pure (serverPort def)) <*> (timeoutArg <|> pure (serverTimeout def)) <*> optional logArg <*> (logConfigArg <|> pure (serverLogConfig def)) <*> @@ -260,13 +273,14 @@ instance FromCmd ClientOpts where cmdP = ClientOpts <$> - (portArg <|> pure (clientPort def)) <*> + (connectionArg <|> pure (clientPort def)) <*> prettyFlag <*> stdinFlag <*> (timeoutArg <|> pure (clientTimeout def)) <*> silentFlag -portArg :: Parser Int +portArg :: Parser ConnectionPort +connectionArg :: Parser ConnectionPort timeoutArg :: Parser Int logArg :: Parser FilePath logConfigArg :: Parser String @@ -277,7 +291,14 @@ stdinFlag :: Parser Bool silentFlag :: Parser Bool -portArg = option auto (long "port" <> metavar "number" <> help "connection port") +portArg = NetworkPort <$> option auto (long "port" <> metavar "number" <> help "connection port") +#if mingw32_HOST_OS +connectionArg = portArg +#else +unixArg :: Parser ConnectionPort +unixArg = UnixPort <$> strOption (long "unix" <> metavar "name" <> help "unix connection port") +connectionArg = portArg <|> unixArg +#endif timeoutArg = option auto (long "timeout" <> metavar "msec" <> help "query timeout") logArg = strOption (long "log" <> short 'l' <> metavar "file" <> help "log file") logConfigArg = strOption (long "log-config" <> metavar "rule" <> help "log config: low [low], high [high], set [low] [high], use [default/debug/trace/silent/supress]") @@ -290,7 +311,7 @@ serverOptsArgs :: ServerOpts -> [String] serverOptsArgs sopts = concat [ - ["--port", show $ serverPort sopts], + portArgs (serverPort sopts), ["--timeout", show $ serverTimeout sopts], marg "--log" (serverLog sopts), ["--log-config", serverLogConfig sopts], @@ -300,6 +321,9 @@ marg :: String -> Maybe String -> [String] marg n (Just v) = [n, v] marg _ _ = [] + portArgs :: ConnectionPort -> [String] + portArgs (NetworkPort n) = ["--port", show n] + portArgs (UnixPort s) = ["--unix", s] data Request = Request { requestCommand :: Command, @@ -327,7 +351,8 @@ AddData { addedContents :: [AddedContents] } | Scan { scanProjects :: [FilePath], - scanSandboxes :: [Cabal], + scanCabal :: Bool, + scanSandboxes :: [FilePath], scanFiles :: [FilePath], scanPaths :: [FilePath], scanContents :: [FileContents], @@ -344,7 +369,8 @@ inferModules :: [String] } | Remove { removeProjects :: [FilePath], - removeSandboxes :: [Cabal], + removeCabal :: Bool, + removeSandboxes :: [FilePath], removeFiles :: [FilePath] } | RemoveAll | InfoModules [TargetFilter] | @@ -416,7 +442,9 @@ TargetFile FilePath | TargetModule String | TargetDepsOf String | - TargetCabal Cabal | + TargetPackageDb PackageDb | + TargetCabal | + TargetSandbox FilePath | TargetPackage String | TargetSourced | TargetStandalone @@ -425,8 +453,9 @@ data SearchType = SearchExact | SearchPrefix | SearchInfix | SearchSuffix | SearchRegex deriving (Show) instance Paths Command where - paths f (Scan projs cs fs ps fcts ghcs docs infer) = Scan <$> + paths f (Scan projs c cs fs ps fcts ghcs docs infer) = Scan <$> each f projs <*> + pure c <*> (each . paths) f cs <*> each f fs <*> each f ps <*> @@ -436,7 +465,7 @@ pure infer paths f (RefineDocs projs fs ms) = RefineDocs <$> each f projs <*> each f fs <*> pure ms paths f (InferTypes projs fs ms) = InferTypes <$> each f projs <*> each f fs <*> pure ms - paths f (Remove projs cs fs) = Remove <$> each f projs <*> (each . paths) f cs <*> each f fs + paths f (Remove projs c cs fs) = Remove <$> each f projs <*> pure c <*> (each . paths) f cs <*> each f fs paths _ RemoveAll = pure RemoveAll paths f (InfoModules t) = InfoModules <$> paths f t paths f (InfoSymbol q t l) = InfoSymbol <$> pure q <*> paths f t <*> pure l @@ -468,7 +497,8 @@ instance Paths TargetFilter where paths f (TargetFile fpath) = TargetFile <$> f fpath - paths f (TargetCabal c) = TargetCabal <$> paths f c + paths f (TargetPackageDb pdb) = TargetPackageDb <$> paths f pdb + paths f (TargetSandbox c) = TargetSandbox <$> paths f c paths _ t = pure t instance Paths [TargetFilter] where @@ -481,7 +511,8 @@ cmd "add" "add info to database" (AddData <$> option readJSON idm), cmd "scan" "scan sources" $ Scan <$> many projectArg <*> - many cabalArg <*> + cabalFlag <*> + many sandboxArg <*> many fileArg <*> many (pathArg $ help "path") <*> many cmdP <*> @@ -492,7 +523,8 @@ cmd "infer" "infer types" $ InferTypes <$> many projectArg <*> many fileArg <*> many moduleArg, cmd "remove" "remove modules info" $ Remove <$> many projectArg <*> - many cabalArg <*> + cabalFlag <*> + many sandboxArg <*> many fileArg, cmd "remove-all" "remove all data" (pure RemoveAll), cmd "modules" "list modules" (InfoModules <$> many cmdP), @@ -543,7 +575,17 @@ cmdP = option readJSON (long "contents") instance FromCmd TargetFilter where - cmdP = asum [TargetProject <$> projectArg, TargetFile <$> fileArg, TargetModule <$> moduleArg, TargetDepsOf <$> depsArg, TargetCabal <$> cabalArg, TargetPackage <$> packageArg, flag' TargetSourced (long "src"), flag' TargetStandalone (long "stand")] + cmdP = asum [ + TargetProject <$> projectArg, + TargetFile <$> fileArg, + TargetModule <$> moduleArg, + TargetDepsOf <$> depsArg, + TargetPackageDb <$> packageDbArg, + flag' TargetCabal (long "cabal"), + TargetSandbox <$> sandboxArg, + TargetPackage <$> packageArg, + flag' TargetSourced (long "src"), + flag' TargetStandalone (long "stand")] instance FromCmd SearchQuery where cmdP = SearchQuery <$> (strArgument idm <|> pure "") <*> asum [ @@ -556,7 +598,7 @@ readJSON :: FromJSON a => ReadM a readJSON = str >>= maybe (readerError "Can't parse JSON argument") return . decode . L.pack -cabalArg :: Parser Cabal +cabalFlag :: Parser Bool ctx :: Parser FilePath depsArg :: Parser String docsFlag :: Parser Bool @@ -575,10 +617,10 @@ pathArg :: Mod OptionFields String -> Parser FilePath projectArg :: Parser String pureFlag :: Parser Bool -sandboxArg :: Parser String +sandboxArg :: Parser FilePath wideFlag :: Parser Bool -cabalArg = flag' Cabal (long "cabal") <|> (Sandbox <$> sandboxArg) +cabalFlag = switch (long "cabal") ctx = fileArg depsArg = strOption (long "deps" <> metavar "object" <> help "filter to such that in dependency of specified object (file or project)") docsFlag = switch (long "docs" <> help "scan source file docs") @@ -594,6 +636,10 @@ localsFlag = switch (long "locals" <> short 'l' <> help "look in local declarations") moduleArg = strOption (long "module" <> metavar "name" <> short 'm' <> help "module name") packageArg = strOption (long "package" <> metavar "name" <> help "module package") +packageDbArg = + flag' GlobalDb (long "global-db" <> help "global package-db") <|> + flag' UserDb (long "user-db" <> help "user package-db") <|> + (PackageDb <$> strOption (long "package-db" <> metavar "path" <> help "custom package-db")) pathArg f = strOption (long "path" <> metavar "path" <> short 'p' <> f) projectArg = strOption (long "project" <> long "proj" <> metavar "project") pureFlag = switch (long "pure" <> help "don't modify actual file, just return result") @@ -604,9 +650,10 @@ toJSON Ping = cmdJson "ping" [] toJSON Listen = cmdJson "listen" [] toJSON (AddData cts) = cmdJson "add" ["data" .= cts] - toJSON (Scan projs cabals fs ps contents ghcs docs' infer') = cmdJson "scan" [ + toJSON (Scan projs cabal sboxes fs ps contents ghcs docs' infer') = cmdJson "scan" [ "projects" .= projs, - "sandboxes" .= cabals, + "cabal" .= cabal, + "sandboxes" .= sboxes, "files" .= fs, "paths" .= ps, "contents" .= contents, @@ -615,7 +662,7 @@ "infer" .= infer'] toJSON (RefineDocs projs fs ms) = cmdJson "docs" ["projects" .= projs, "files" .= fs, "modules" .= ms] toJSON (InferTypes projs fs ms) = cmdJson "infer" ["projects" .= projs, "files" .= fs, "modules" .= ms] - toJSON (Remove projs cabals fs) = cmdJson "remove" ["projects" .= projs, "sandboxes" .= cabals, "files" .= fs] + toJSON (Remove projs cabal sboxes fs) = cmdJson "remove" ["projects" .= projs, "cabal" .= cabal, "sandboxes" .= sboxes, "files" .= fs] toJSON RemoveAll = cmdJson "remove-all" [] toJSON (InfoModules tf) = cmdJson "modules" ["filters" .= tf] toJSON InfoPackages = cmdJson "packages" [] @@ -650,6 +697,7 @@ guardCmd "add" v *> (AddData <$> v .:: "data"), guardCmd "scan" v *> (Scan <$> v .::?! "projects" <*> + (v .:: "cabal" <|> pure False) <*> v .::?! "sandboxes" <*> v .::?! "files" <*> v .::?! "paths" <*> @@ -661,6 +709,7 @@ guardCmd "infer" v *> (InferTypes <$> v .::?! "projects" <*> v .::?! "files" <*> v .::?! "modules"), guardCmd "remove" v *> (Remove <$> v .::?! "projects" <*> + (v .:: "cabal" <|> pure False) <*> v .::?! "sandboxes" <*> v .::?! "files"), guardCmd "remove-all" v *> pure RemoveAll, @@ -738,7 +787,9 @@ toJSON (TargetFile fpath) = object ["file" .= fpath] toJSON (TargetModule mname) = object ["module" .= mname] toJSON (TargetDepsOf dep) = object ["deps" .= dep] - toJSON (TargetCabal cabal) = object ["cabal" .= cabal] + toJSON (TargetPackageDb pdb) = object ["db" .= pdb] + toJSON TargetCabal = toJSON ("cabal" :: String) + toJSON (TargetSandbox sbox) = object ["sandbox" .= sbox] toJSON (TargetPackage pname) = object ["package" .= pname] toJSON TargetSourced = toJSON ("sourced" :: String) toJSON TargetStandalone = toJSON ("standalone" :: String) @@ -750,11 +801,13 @@ TargetFile <$> v .:: "file", TargetModule <$> v .:: "module", TargetDepsOf <$> v .:: "deps", - TargetCabal <$> v .:: "cabal", + TargetPackageDb <$> v .:: "db", + TargetSandbox <$> v .:: "sandbox", TargetPackage <$> v .:: "package"] str' = do s <- parseJSON j :: A.Parser String case s of + "cabal" -> return TargetCabal "sourced" -> return TargetSourced "standalone" -> return TargetStandalone _ -> empty
+ src/HsDev/Stack.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE TemplateHaskell, RankNTypes #-} + +module HsDev.Stack ( + stack, yaml, + path, pathOf, + build, buildDeps, configure, + StackEnv(..), stackRoot, stackProject, stackConfig, stackGhc, stackSnapshot, stackLocal, + getStackEnv, projectEnv, + stackPackageDbStack, + + MaybeT(..) + ) where + +import Control.Arrow +import Control.Lens (makeLenses, Lens', at, ix, lens, (^?), (^.), view) +import Control.Monad +import Control.Monad.Trans.Maybe +import Control.Monad.IO.Class +import Data.Char +import Data.Maybe +import Data.Map (Map) +import qualified Data.Map as M +import System.Directory +import System.Environment +import System.FilePath +import System.Process + +import HsDev.PackageDb +import HsDev.Project +import HsDev.Util (withCurrentDirectory) + +-- | Invoke stack command, we are trying to get actual stack near current hsdev executable +stack :: [String] -> MaybeT IO String +stack cmd = do + curExe <- liftIO getExecutablePath + withCurrentDirectory (takeDirectory curExe) $ do + stackExe <- MaybeT $ findExecutable "stack" + liftIO $ readProcess stackExe cmd "" + +-- | Make yaml opts +yaml :: Maybe FilePath -> [String] +yaml Nothing = [] +yaml (Just y) = ["--stack-yaml", y] + +type Paths = Map String FilePath + +-- | Stack path +path :: Maybe FilePath -> MaybeT IO Paths +path mcfg = liftM (M.fromList . map breakPath . lines) $ stack ("path" : yaml mcfg) where + breakPath :: String -> (String, FilePath) + breakPath = second (dropWhile isSpace . drop 1) . break (== ':') + +-- | Get path for +pathOf :: String -> Lens' Paths (Maybe FilePath) +pathOf = at + +-- | Build stack project +build :: [String] -> Maybe FilePath -> MaybeT IO () +build opts mcfg = void $ stack $ "build" : (opts ++ yaml mcfg) + +-- | Build only dependencies +buildDeps :: Maybe FilePath -> MaybeT IO () +buildDeps = build ["--only-dependencies"] + +-- | Configure project +configure :: Maybe FilePath -> MaybeT IO () +configure = build ["--only-configure"] + +data StackEnv = StackEnv { + _stackRoot :: FilePath, + _stackProject :: FilePath, + _stackConfig :: FilePath, + _stackGhc :: FilePath, + _stackSnapshot :: FilePath, + _stackLocal :: FilePath } + +makeLenses ''StackEnv + +getStackEnv :: Paths -> Maybe StackEnv +getStackEnv p = StackEnv <$> + (p ^. pathOf "global-stack-root") <*> + (p ^. pathOf "project-root") <*> + (p ^. pathOf "config-location") <*> + (p ^. pathOf "ghc-paths") <*> + (p ^. pathOf "snapshot-pkg-db") <*> + (p ^. pathOf "local-pkg-db") + +-- | Projects paths +projectEnv :: FilePath -> MaybeT IO StackEnv +projectEnv p = do + hasConfig <- liftIO $ doesFileExist yaml + guard hasConfig + paths' <- path (Just yaml) + MaybeT $ return $ getStackEnv paths' + where + yaml = p </> "stack.yaml" + +-- | Get package-db stack for stack environment +stackPackageDbStack :: Lens' StackEnv PackageDbStack +stackPackageDbStack = lens g s where + g :: StackEnv -> PackageDbStack + g env' = PackageDbStack $ map PackageDb [_stackLocal env', _stackSnapshot env'] + s :: StackEnv -> PackageDbStack -> StackEnv + s env' pdbs = env' { + _stackSnapshot = fromMaybe (_stackSnapshot env') $ pdbs ^? packageDbStack . ix 1 . packageDb, + _stackLocal = fromMaybe (_stackLocal env') $ pdbs ^? packageDbStack . ix 0 . packageDb }
src/HsDev/Symbols.hs view
@@ -37,18 +37,20 @@ import Control.Applicative import Control.Arrow -import Control.Lens (view, set, over) +import Control.Lens import Control.Monad.Trans.Maybe import Control.Monad.Except import Data.Function (on) import Data.List import Data.Maybe (fromMaybe) +import qualified Data.Map as M import Data.Ord (comparing) import Data.Text (Text) import qualified Data.Text as T (concat) import System.Directory import System.FilePath +import Data.Deps import System.Directory.Paths import HsDev.Symbols.Types @@ -166,16 +168,12 @@ merge' [] = error "mergeExported: impossible" merge' ds@(d:_) = ExportedDeclaration (map (view declarationModuleId) ds) (view moduleDeclaration d) -instance Paths Cabal where - paths _ Cabal = pure Cabal - paths f (Sandbox p) = Sandbox <$> f p - instance Paths Project where paths f (Project nm p c desc) = Project nm <$> f p <*> f c <*> pure desc instance Paths ModuleLocation where paths f (FileModule fpath p) = FileModule <$> f fpath <*> traverse (paths f) p - paths f (CabalModule c p n) = CabalModule <$> paths f c <*> pure p <*> pure n + paths f (InstalledModule c p n) = InstalledModule <$> paths f c <*> pure p <*> pure n paths _ (ModuleSource m) = pure $ ModuleSource m -- | Find project file is related to @@ -210,18 +208,20 @@ moduleOpts :: [ModulePackage] -> Module -> [String] moduleOpts pkgs m = case view moduleLocation m of FileModule file proj -> concat [ - ["-i" ++ s | s <- srcDirs], - concatMap extensionsOpts exts, hidePackages, - ["-package " ++ p | p <- deps, p `elem` pkgs']] + targetOpts info'] where infos' = maybe [] (`fileTargets` file) proj - srcDirs = concatMap (view infoSourceDirs) infos' - exts = map (file `withExtensions`) infos' - deps = concatMap (view infoDepends) infos' - pkgs' = map (view packageName) pkgs + info' = over infoDepends (filter validDep) (mconcat $ selfInfo : infos') + selfInfo + | proj ^? _Just . projectName `elem` map Just (infos' ^.. each . infoDepends . each) = fromMaybe mempty $ + proj ^? _Just . projectDescription . _Just . projectLibrary . _Just . libraryBuildInfo + | otherwise = mempty + -- filter out unavailable packages such as unix under windows + validDep d = d `elem` pkgs' + pkgs' = pkgs ^.. each . packageName hidePackages - | null infos' = [] + | null (info' ^. infoDepends) = [] | otherwise = ["-hide-all-packages"] _ -> []
src/HsDev/Symbols/Location.hs view
@@ -6,7 +6,7 @@ Location(..), packageName, packageVersion, - moduleFile, moduleProject, moduleCabal, modulePackage, cabalModuleName, moduleSourceName, + moduleFile, moduleProject, modulePackageDb, modulePackage, cabalModuleName, moduleSourceName, positionLine, positionColumn, regionFrom, regionTo, locationModule, locationPosition, @@ -16,7 +16,7 @@ packageOpt, RecalcTabs(..), - module HsDev.Cabal + module HsDev.PackageDb ) where import Control.Applicative @@ -32,10 +32,11 @@ import System.FilePath import Text.Read (readMaybe) -import HsDev.Cabal +import HsDev.PackageDb import HsDev.Project import HsDev.Util ((.::), (.::?)) +-- | Just package name and version without its location data ModulePackage = ModulePackage { _packageName :: String, _packageVersion :: String } @@ -74,7 +75,7 @@ -- | Location of module data ModuleLocation = FileModule { _moduleFile :: FilePath, _moduleProject :: Maybe Project } | - CabalModule { _moduleCabal :: Cabal, _modulePackage :: Maybe ModulePackage, _cabalModuleName :: String } | + InstalledModule { _modulePackageDb :: PackageDb, _modulePackage :: Maybe ModulePackage, _cabalModuleName :: String } | ModuleSource { _moduleSourceName :: Maybe String } deriving (Eq, Ord) @@ -85,29 +86,29 @@ locationId :: ModuleLocation -> String locationId (FileModule fpath _) = fpath -locationId (CabalModule cabal mpack nm) = intercalate ":" [show cabal, maybe "" show mpack, nm] +locationId (InstalledModule cabal mpack nm) = intercalate ":" [show cabal, maybe "" show mpack, nm] locationId (ModuleSource msrc) = fromMaybe "" msrc instance NFData ModuleLocation where rnf (FileModule f p) = rnf f `seq` rnf p - rnf (CabalModule c p n) = rnf c `seq` rnf p `seq` rnf n + rnf (InstalledModule d p n) = rnf d `seq` rnf p `seq` rnf n rnf (ModuleSource m) = rnf m instance Show ModuleLocation where show (FileModule f p) = f ++ maybe "" (" in " ++) (fmap (view projectPath) p) - show (CabalModule _ p n) = n ++ maybe "" (" in package " ++) (fmap show p) + show (InstalledModule _ p n) = n ++ maybe "" (" in package " ++) (fmap show p) show (ModuleSource m) = fromMaybe "" m instance ToJSON ModuleLocation where toJSON (FileModule f p) = object ["file" .= f, "project" .= fmap (view projectCabal) p] - toJSON (CabalModule c p n) = object ["cabal" .= c, "package" .= fmap show p, "name" .= n] + toJSON (InstalledModule c p n) = object ["db" .= c, "package" .= fmap show p, "name" .= n] toJSON (ModuleSource (Just s)) = object ["source" .= s] toJSON (ModuleSource Nothing) = object [] instance FromJSON ModuleLocation where parseJSON = withObject "module location" $ \v -> (FileModule <$> v .:: "file" <*> (fmap project <$> (v .:: "project"))) <|> - (CabalModule <$> v .:: "cabal" <*> fmap (join . fmap readMaybe) (v .:: "package") <*> v .:: "name") <|> + (InstalledModule <$> v .:: "db" <*> fmap (join . fmap readMaybe) (v .:: "package") <*> v .:: "name") <|> (ModuleSource <$> v .::? "source") noLocation :: ModuleLocation
src/HsDev/Symbols/Resolve.hs view
@@ -65,7 +65,7 @@ resolveModule :: Module -> ResolveM ResolvedModule resolveModule m = gets (M.lookup $ view moduleId m) >>= maybe resolveModule' return where resolveModule' = save $ case view moduleLocation m of - CabalModule {} -> return ResolvedModule { + InstalledModule {} -> return ResolvedModule { _resolvedModule = m, _resolvedScope = view moduleDeclarations m, _resolvedExports = view moduleDeclarations m } @@ -125,12 +125,12 @@ case proj' of Nothing -> selectImport i [ inFile $ importedModulePath (view moduleName m) file (view importModuleName i), - byCabal] + installed] Just p -> selectImport i [ inProject p, inDepsOf' file p] - CabalModule cabal _ _ -> selectImport i [inCabal cabal] - ModuleSource _ -> selectImport i [byCabal] + InstalledModule cabal _ _ -> selectImport i [inPackageDb cabal] + ModuleSource _ -> selectImport i [installed] fromMaybe [] <$> traverse (liftM (filterImportList . view resolvedExports) . resolveModule) ms setImport :: Import -> Declaration -> Declaration setImport i' = set declarationImported (Just [i'])
src/HsDev/Symbols/Types.hs view
@@ -28,7 +28,7 @@ exportedBy, exportedDeclaration, inspectionAt, inspectionOpts, inspection, inspectedId, inspectionResult, - module HsDev.Cabal, + module HsDev.PackageDb, module HsDev.Project, module HsDev.Symbols.Class, module HsDev.Symbols.Documented @@ -46,7 +46,7 @@ import qualified Data.Text as T import Data.Time.Clock.POSIX (POSIXTime) -import HsDev.Cabal +import HsDev.PackageDb import HsDev.Project import HsDev.Symbols.Class import HsDev.Symbols.Documented @@ -487,7 +487,7 @@ showError :: String -> String showError e = unlines $ ("\terror: " ++ e) : case mi of FileModule f p -> ["file: " ++ f, "project: " ++ maybe "" (view projectPath) p] - CabalModule c p n -> ["cabal: " ++ show c, "package: " ++ maybe "" show p, "name: " ++ n] + InstalledModule c p n -> ["cabal: " ++ show c, "package: " ++ maybe "" show p, "name: " ++ n] ModuleSource src -> ["source: " ++ fromMaybe "" src] instance ToJSON InspectedModule where
src/HsDev/Symbols/Util.hs view
@@ -1,6 +1,6 @@ module HsDev.Symbols.Util ( - projectOf, cabalOf, packageOf, - inProject, inDepsOfTarget, inDepsOfFile, inDepsOfProject, inCabal, inPackage, inVersion, inFile, inModuleSource, inModule, byFile, byCabal, standalone, + projectOf, packageDbOf, packageOf, + inProject, inDepsOfTarget, inDepsOfFile, inDepsOfProject, inPackageDb, inPackageDbStack, inPackage, inVersion, inFile, inModuleSource, inModule, byFile, installed, standalone, imports, qualifier, imported, visible, inScope, newestPackage, sourceModule, visibleModule, preferredModule, uniqueModules, @@ -27,15 +27,15 @@ _ -> Nothing -- | Get module cabal -cabalOf :: ModuleId -> Maybe Cabal -cabalOf m = case view moduleIdLocation m of - CabalModule c _ _ -> Just c +packageDbOf :: ModuleId -> Maybe PackageDb +packageDbOf m = case view moduleIdLocation m of + InstalledModule c _ _ -> Just c _ -> Nothing -- | Get module package packageOf :: ModuleId -> Maybe ModulePackage packageOf m = case view moduleIdLocation m of - CabalModule _ package _ -> package + InstalledModule _ package _ -> package _ -> Nothing -- | Check if module in project @@ -56,21 +56,27 @@ anyPackage :: [String] -> ModuleId -> Bool anyPackage = liftM or . mapM inPackage --- | Check if module in cabal -inCabal :: Cabal -> ModuleId -> Bool -inCabal c m = case view moduleIdLocation m of - CabalModule cabal _ _ -> cabal == c +-- | Check if module in package-db +inPackageDb :: PackageDb -> ModuleId -> Bool +inPackageDb c m = case view moduleIdLocation m of + InstalledModule d _ _ -> d == c _ -> False +-- | Check if module in one of sandboxes +inPackageDbStack :: PackageDbStack -> ModuleId -> Bool +inPackageDbStack dbs m = case view moduleIdLocation m of + InstalledModule d _ _ -> d `elem` packageDbs dbs + _ -> False + -- | Check if module in package inPackage :: String -> ModuleId -> Bool inPackage p m = case view moduleIdLocation m of - CabalModule _ package _ -> Just p == fmap (view packageName) package + InstalledModule _ package _ -> Just p == fmap (view packageName) package _ -> False inVersion :: String -> ModuleId -> Bool inVersion v m = case view moduleIdLocation m of - CabalModule _ package _ -> Just v == fmap (view packageVersion) package + InstalledModule _ package _ -> Just v == fmap (view packageVersion) package _ -> False -- | Check if module in file @@ -96,9 +102,9 @@ _ -> False -- | Check if module got from cabal database -byCabal :: ModuleId -> Bool -byCabal m = case view moduleIdLocation m of - CabalModule _ _ _ -> True +installed :: ModuleId -> Bool +installed m = case view moduleIdLocation m of + InstalledModule _ _ _ -> True _ -> False -- | Check if module is standalone @@ -142,7 +148,7 @@ partition (isJust . fst) . map ((mpackage . symbolModuleLocation) &&& id) where - mpackage (CabalModule _ (Just p) _) = Just p + mpackage (InstalledModule _ (Just p) _) = Just p mpackage _ = Nothing pname = fmap (view packageName) . fst pver = fmap (view packageVersion) . fst @@ -159,22 +165,25 @@ sourceModule proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (byFile . view moduleId) ms -- | Select module, visible in project or cabal -visibleModule :: Cabal -> Maybe Project -> [Module] -> Maybe Module -visibleModule cabal proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (inCabal cabal . view moduleId) ms +-- TODO: PackageDbStack? +visibleModule :: PackageDb -> Maybe Project -> [Module] -> Maybe Module +visibleModule d proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (inPackageDb d . view moduleId) ms -- | Select preferred visible module -preferredModule :: Cabal -> Maybe Project -> [ModuleId] -> Maybe ModuleId -preferredModule cabal proj ms = listToMaybe $ concatMap (`filter` ms) order where +-- TODO: PackageDbStack? +preferredModule :: PackageDb -> Maybe Project -> [ModuleId] -> Maybe ModuleId +preferredModule d proj ms = listToMaybe $ concatMap (`filter` ms) order where order = [ maybe (const False) inProject proj, byFile, - inCabal cabal, + inPackageDb d, const True] -- | Remove duplicate modules, leave only `preferredModule` -uniqueModules :: Cabal -> Maybe Project -> [ModuleId] -> [ModuleId] -uniqueModules cabal proj = - mapMaybe (preferredModule cabal proj) . +-- TODO: PackageDbStack? +uniqueModules :: PackageDb -> Maybe Project -> [ModuleId] -> [ModuleId] +uniqueModules d proj = + mapMaybe (preferredModule d proj) . groupBy ((==) `on` view moduleIdName) . sortBy (comparing (view moduleIdName))
src/HsDev/Tools/Ghc/Check.hs view
@@ -6,7 +6,7 @@ Ghc, module HsDev.Tools.Types, module HsDev.Symbols.Types, - Cabal(..), Project(..), + PackageDb(..), PackageDbStack(..), Project(..), recalcNotesTabs, @@ -27,6 +27,8 @@ import qualified ErrUtils as E import System.Directory.Paths +import HsDev.PackageDb +import HsDev.Scan.Browse (browsePackages) import HsDev.Symbols (moduleOpts) import HsDev.Symbols.Location import HsDev.Symbols.Types @@ -35,35 +37,35 @@ import HsDev.Util (readFileUtf8, ordNub) -- | Check files and collect warnings and errors -checkFiles :: [String] -> Cabal -> [FilePath] -> Maybe Project -> Ghc [Note OutputMessage] -checkFiles opts cabal files _ = do +checkFiles :: [String] -> PackageDbStack -> [FilePath] -> Maybe Project -> Ghc [Note OutputMessage] +checkFiles opts pdbs files _ = do ch <- liftIO newChan withFlags $ do modifyFlags (\fs -> fs { log_action = logAction ch }) - _ <- addCmdOpts ("-Wall" : (cabalOpt cabal ++ opts)) + _ <- setCmdOpts ("-Wall" : (packageDbStackOpts pdbs ++ opts)) clearTargets mapM (`makeTarget` Nothing) files >>= loadTargets notes <- liftIO $ stopChan ch liftIO $ recalcNotesTabs notes -- | Check module source -check :: [String] -> Cabal -> Module -> Maybe String -> ExceptT String Ghc [Note OutputMessage] -check opts cabal m msrc = case view moduleLocation m of +check :: [String] -> PackageDbStack -> Module -> Maybe String -> ExceptT String Ghc [Note OutputMessage] +check opts pdbs m msrc = case view moduleLocation m of FileModule file proj -> do ch <- liftIO newChan - pkgs <- lift listPackages + pkgs <- mapExceptT liftIO $ browsePackages opts pdbs let dir = fromMaybe (sourceModuleRoot (view moduleName m) file) $ preview (_Just . projectPath) proj dirExist <- liftIO $ doesDirectoryExist dir lift $ withFlags $ (if dirExist then withCurrentDirectory dir else id) $ do - modifyFlags (\fs -> fs { log_action = logAction ch }) - _ <- addCmdOpts $ concat [ + _ <- setCmdOpts $ concat [ ["-Wall"], - cabalOpt cabal, + packageDbStackOpts pdbs, moduleOpts pkgs m, opts] + modifyFlags (\fs -> fs { log_action = logAction ch }) clearTargets target <- makeTarget (makeRelative dir file) msrc loadTargets [target] @@ -72,12 +74,12 @@ _ -> throwError "Module is not source" -- | Check module and collect warnings and errors -checkFile :: [String] -> Cabal -> Module -> ExceptT String Ghc [Note OutputMessage] -checkFile opts cabal m = check opts cabal m Nothing +checkFile :: [String] -> PackageDbStack -> Module -> ExceptT String Ghc [Note OutputMessage] +checkFile opts pdbs m = check opts pdbs m Nothing -- | Check module and collect warnings and errors -checkSource :: [String] -> Cabal -> Module -> String -> ExceptT String Ghc [Note OutputMessage] -checkSource opts cabal m src = check opts cabal m (Just src) +checkSource :: [String] -> PackageDbStack -> Module -> String -> ExceptT String Ghc [Note OutputMessage] +checkSource opts pdbs m src = check opts pdbs m (Just src) -- | Log ghc warnings and errors as to chan -- You may have to apply recalcTabs on result notes
src/HsDev/Tools/Ghc/Types.hs view
@@ -29,6 +29,8 @@ import Pretty import System.Directory.Paths (canonicalize) +import HsDev.Scan.Browse (browsePackages) +import HsDev.PackageDb import HsDev.Symbols import HsDev.Tools.Ghc.Worker import HsDev.Tools.Types @@ -96,20 +98,20 @@ v .:: "type" -- | Get all types in module -fileTypes :: [String] -> Cabal -> Module -> Maybe String -> ExceptT String Ghc [Note TypedExpr] -fileTypes opts cabal m msrc = case view moduleLocation m of +fileTypes :: [String] -> PackageDbStack -> Module -> Maybe String -> ExceptT String Ghc [Note TypedExpr] +fileTypes opts pdbs m msrc = case view moduleLocation m of FileModule file proj -> do file' <- liftIO $ canonicalize file cts <- maybe (liftIO $ readFileUtf8 file') return msrc - pkgs <- lift listPackages + pkgs <- mapExceptT liftIO $ browsePackages opts pdbs let dir = fromMaybe (sourceModuleRoot (view moduleName m) file') $ preview (_Just . projectPath) proj dirExist <- liftIO $ doesDirectoryExist dir lift $ withFlags $ (if dirExist then withCurrentDirectory dir else id) $ do - _ <- addCmdOpts $ concat [ - cabalOpt cabal, + _ <- setCmdOpts $ concat [ + packageDbStackOpts pdbs, moduleOpts pkgs m, opts] target <- makeTarget (makeRelative dir file') msrc @@ -142,5 +144,5 @@ return $ set (declaration . functionType) (Just $ fromString $ view (note . typedType) tnote) d -- | Infer types in module -inferTypes :: [String] -> Cabal -> Module -> Maybe String -> ExceptT String Ghc Module -inferTypes opts cabal m msrc = liftM (`setModuleTypes` m) $ fileTypes opts cabal m msrc +inferTypes :: [String] -> PackageDbStack -> Module -> Maybe String -> ExceptT String Ghc Module +inferTypes opts pdbs m msrc = liftM (`setModuleTypes` m) $ fileTypes opts pdbs m msrc
src/HsDev/Tools/Ghc/Worker.hs view
@@ -4,7 +4,8 @@ -- * Workers ghcWorker, ghciWorker, -- * Initializers and actions - withFlags, modifyFlags, addCmdOpts, + ghcRun, + withFlags, modifyFlags, addCmdOpts, setCmdOpts, importModules, preludeModules, evaluate, clearTargets, makeTarget, loadTargets, @@ -38,23 +39,27 @@ -- | Ghc worker. Pass options and initializer action ghcWorker :: [String] -> Ghc () -> IO (Worker Ghc) ghcWorker opts initialize = startWorker (runGhc (Just libdir)) ghcInit id where - ghcInit f = do - fs <- getSessionDynFlags - defaultCleanupHandler fs $ do - (fs', _, _) <- parseDynamicFlags fs (map noLoc opts) - let fs'' = fs' { - ghcMode = CompManager, - ghcLink = LinkInMemory, - hscTarget = HscInterpreted } - _ <- setSessionDynFlags fs'' - _ <- liftIO $ initPackages fs'' - initialize - f + ghcInit f = ghcRun opts (initialize >> f) -- | Interpreter worker is worker with @preludeModules@ loaded ghciWorker :: IO (Worker Ghc) ghciWorker = ghcWorker [] (importModules preludeModules) +-- | Run ghc +ghcRun :: [String] -> Ghc a -> Ghc a +ghcRun opts f = do + fs <- getSessionDynFlags + defaultCleanupHandler fs $ do + (fs', _, _) <- parseDynamicFlags fs (map noLoc opts) + let fs'' = fs' { + ghcMode = CompManager, + -- ghcLink = LinkInMemory, + -- hscTarget = HscInterpreted } + ghcLink = NoLink, + hscTarget = HscNothing } + void $ setSessionDynFlags fs'' + f + -- | Alter @DynFlags@ temporary withFlags :: Ghc a -> Ghc a withFlags = gbracket getSessionDynFlags (\fs -> setSessionDynFlags fs >> return ()) . const @@ -69,13 +74,20 @@ _ <- liftIO $ initPackages fs' return () -addCmdOpts :: [String] -> Ghc DynFlags +-- | Add options without reinit session +addCmdOpts :: [String] -> Ghc () addCmdOpts opts = do fs <- getSessionDynFlags (fs', _, _) <- parseDynamicFlags fs (map noLoc opts) - _ <- setSessionDynFlags fs' - (fs'', _) <- liftIO $ initPackages fs' - return fs'' + let fs'' = fs' { + ghcMode = CompManager, + ghcLink = NoLink, + hscTarget = HscNothing } + void $ setSessionDynFlags fs'' + +-- | Set options after session reinit +setCmdOpts :: [String] -> Ghc () +setCmdOpts opts = initGhcMonad (Just libdir) >> addCmdOpts opts -- | Import some modules importModules :: [String] -> Ghc ()
src/HsDev/Tools/GhcMod.hs view
@@ -54,23 +54,26 @@ import qualified Language.Haskell.GhcMod.Types as GhcMod import Control.Concurrent.Worker -import HsDev.Cabal +import HsDev.PackageDb import HsDev.Project +import HsDev.Sandbox (searchPackageDbStack) import HsDev.Symbols import HsDev.Tools.Base import HsDev.Tools.Types import HsDev.Util ((.::), liftIOErrors, liftThrow, readFileUtf8, ordNub) -list :: [String] -> Cabal -> ExceptT String IO [ModuleLocation] -list opts cabal = runGhcMod (GhcMod.defaultOptions { GhcMod.optGhcUserOptions = opts }) $ do +-- FIXME: Pass package-db stack options to ghc-mod +list :: [String] -> PackageDbStack -> ExceptT String IO [ModuleLocation] +list opts pdbs = runGhcMod (GhcMod.defaultOptions { GhcMod.optGhcUserOptions = opts }) $ do ms <- (map splitPackage . lines) <$> GhcMod.modules True - return [CabalModule cabal (readMaybe p) m | (m, p) <- ms] + return [InstalledModule (topPackageDb pdbs) (readMaybe p) m | (m, p) <- ms] where splitPackage :: String -> (String, String) splitPackage = second (drop 1) . break isSpace -browse :: [String] -> Cabal -> String -> Maybe ModulePackage -> ExceptT String IO InspectedModule -browse opts cabal mname mpackage = inspect thisLoc (return $ browseInspection opts) $ runGhcMod +-- FIXME: Pass package-db stack options to ghc-mod +browse :: [String] -> PackageDbStack -> String -> Maybe ModulePackage -> ExceptT String IO InspectedModule +browse opts pdbs mname mpackage = inspect thisLoc (return $ browseInspection opts) $ runGhcMod (GhcMod.defaultOptions { GhcMod.optGhcUserOptions = packageOpt mpackage ++ opts }) $ do ds <- (mapMaybe parseDecl . lines) <$> GhcMod.browse (GhcMod.defaultBrowseOpts { @@ -89,7 +92,7 @@ where mpkgname = maybe mname (\p -> view packageName p ++ ":" ++ mname) mpackage thisLoc = view moduleIdLocation $ mloc mname - mloc mname' = ModuleId (fromString mname') $ CabalModule cabal Nothing mname' + mloc mname' = ModuleId (fromString mname') $ InstalledModule (topPackageDb pdbs) Nothing mname' parseDecl s = do groups <- matchRx rx s let @@ -117,10 +120,11 @@ flags :: ExceptT String IO [String] flags = runGhcMod GhcMod.defaultOptions $ (lines . nullToNL) <$> GhcMod.flags -info :: [String] -> Cabal -> FilePath -> String -> GhcModT IO Declaration -info opts cabal file sname = do +-- FIXME: Detect actual package-db for module +info :: [String] -> PackageDbStack -> FilePath -> String -> GhcModT IO Declaration +info opts pdbs file sname = do fileCts <- liftIO $ readFileUtf8 file - rs <- withOptions (\o -> o { GhcMod.optGhcUserOptions = cabalOpt cabal ++ opts }) $ + rs <- withOptions (\o -> o { GhcMod.optGhcUserOptions = packageDbStackOpts pdbs ++ opts }) $ liftM nullToNL $ GhcMod.info file (GhcMod.Expression sname) toDecl fileCts rs where @@ -152,7 +156,7 @@ parsePos src = case splitRx ":(?=\\d)" src of [_, line, column] -> Position <$> readMaybe line <*> readMaybe column _ -> Nothing - mkMod = CabalModule cabal Nothing + mkMod = InstalledModule (topPackageDb pdbs) Nothing trim = p . p where p = reverse . dropWhile isSpace @@ -177,8 +181,8 @@ v .:: "expr" <*> v .:: "type" -typeOf :: [String] -> Cabal -> FilePath -> Int -> Int -> GhcModT IO [TypedRegion] -typeOf opts cabal file line col = withOptions (\o -> o { GhcMod.optGhcUserOptions = cabalOpt cabal ++ opts }) $ do +typeOf :: [String] -> PackageDbStack -> FilePath -> Int -> Int -> GhcModT IO [TypedRegion] +typeOf opts pdbs file line col = withOptions (\o -> o { GhcMod.optGhcUserOptions = packageDbStackOpts pdbs ++ opts }) $ do fileCts <- liftIO $ readFileUtf8 file let Position line' col' = calcTabs fileCts 8 (Position line col) @@ -220,10 +224,10 @@ '\0' -> '\n' ch -> ch -check :: [String] -> Cabal -> [FilePath] -> Maybe Project -> GhcModT IO [Note OutputMessage] -check opts cabal files _ = do +check :: [String] -> PackageDbStack -> [FilePath] -> Maybe Project -> GhcModT IO [Note OutputMessage] +check opts pdbs files _ = do cts <- liftIO $ mapM readFileUtf8 files - withOptions (\o -> o { GhcMod.optGhcUserOptions = cabalOpt cabal ++ opts }) $ do + withOptions (\o -> o { GhcMod.optGhcUserOptions = packageDbStackOpts pdbs ++ opts }) $ do res <- GhcMod.checkSyntax files return $ map (recalcOutputMessageTabs (zip files cts)) $ parseOutputMessages res @@ -252,16 +256,16 @@ GhcMod.withGhcModEnv cur opts $ \(env, _) -> GhcMod.runGhcModT' env GhcMod.defaultGhcModState act -locateGhcModEnv :: FilePath -> IO (Either Project Cabal) +locateGhcModEnv :: FilePath -> IO (Either Project PackageDbStack) locateGhcModEnv f = do mproj <- locateProject f - maybe (liftM Right $ getSandbox f) (return . Left) mproj + maybe (liftM Right $ searchPackageDbStack f) (return . Left) mproj -ghcModEnvPath :: FilePath -> Either Project Cabal -> FilePath -ghcModEnvPath defaultPath = either (view projectPath) (fromMaybe defaultPath . sandbox) +ghcModEnvPath :: FilePath -> Either Project PackageDbStack -> FilePath +ghcModEnvPath defaultPath = either (view projectPath) (fromMaybe defaultPath . preview packageDb . topPackageDb) -- | Create ghc-mod worker for project or for sandbox -ghcModWorker :: Either Project Cabal -> IO (Worker (GhcModT IO)) +ghcModWorker :: Either Project PackageDbStack -> IO (Worker (GhcModT IO)) ghcModWorker p = do home <- getHomeDirectory startWorker (runGhcModT'' $ ghcModEnvPath home p) id liftThrow
src/HsDev/Tools/GhcMod/InferType.hs view
@@ -12,7 +12,7 @@ import qualified Data.Text as T (unpack) import qualified Language.Haskell.GhcMod as GhcMod -import HsDev.Cabal +import HsDev.PackageDb import HsDev.Symbols import HsDev.Tools.GhcMod import HsDev.Util (withCurrentDirectory) @@ -23,8 +23,8 @@ untyped _ = False -- | Infer type of declaration -inferType :: [String] -> Cabal -> FilePath -> Declaration -> GhcModT IO Declaration -inferType opts cabal src decl' +inferType :: [String] -> PackageDbStack -> FilePath -> Declaration -> GhcModT IO Declaration +inferType opts pdbs src decl' | untyped (view declaration decl') = doInfer | otherwise = return decl' where @@ -32,23 +32,23 @@ inferred <- ((preview $ declaration . functionType . _Just) <$> byInfo) <|> (fmap fromString <$> byTypeOf) return $ set (declaration . functionType) inferred decl' - byInfo = info opts cabal src (T.unpack $ view declarationName decl') + byInfo = info opts pdbs src (T.unpack $ view declarationName decl') byTypeOf = case view declarationPosition decl' of Nothing -> fail "No position" - Just (Position l c) -> (fmap typedType . listToMaybe) <$> typeOf opts cabal src l c + Just (Position l c) -> (fmap typedType . listToMaybe) <$> typeOf opts pdbs src l c -- | Infer types for module -inferTypes :: [String] -> Cabal -> Module -> GhcModT IO Module -inferTypes opts cabal m = case view moduleLocation m of +inferTypes :: [String] -> PackageDbStack -> Module -> GhcModT IO Module +inferTypes opts pdbs m = case view moduleLocation m of FileModule src _ -> do - inferredDecls <- traverse (\d -> inferType opts cabal src d <|> return d) $ + inferredDecls <- traverse (\d -> inferType opts pdbs src d <|> return d) $ view moduleDeclarations m return $ set moduleDeclarations inferredDecls m _ -> fail "Type infer works only for source files" -- | Infer type in module -infer :: [String] -> Cabal -> Module -> ExceptT String IO Module -infer opts cabal m = case view moduleLocation m of +infer :: [String] -> PackageDbStack -> Module -> ExceptT String IO Module +infer opts pdbs m = case view moduleLocation m of FileModule src _ -> mapExceptT (withCurrentDirectory (sourceModuleRoot (view moduleName m) src)) $ - runGhcMod GhcMod.defaultOptions $ inferTypes opts cabal m + runGhcMod GhcMod.defaultOptions $ inferTypes opts pdbs m _ -> throwError "Type infer works only for source files"
src/HsDev/Tools/HDocs.hs view
@@ -37,12 +37,12 @@ hdocs mloc opts = runExceptT (docs' mloc) >>= return . either (const M.empty) (force . HDocs.formatDocs) where docs' :: ModuleLocation -> ExceptT String IO HDocs.ModuleDocMap docs' (FileModule fpath _) = liftM snd $ HDocs.readSource opts fpath - docs' (CabalModule _ _ mname) = HDocs.moduleDocs opts mname + docs' (InstalledModule _ _ mname) = HDocs.moduleDocs opts mname docs' _ = throwError $ "Can't get docs for: " ++ show mloc -- | Get all docs -hdocsCabal :: Cabal -> [String] -> ExceptT String IO (Map String (Map String String)) -hdocsCabal cabal opts = liftM (M.map $ force . HDocs.formatDocs) $ HDocs.installedDocs (cabalOpt cabal ++ opts) +hdocsCabal :: PackageDbStack -> [String] -> ExceptT String IO (Map String (Map String String)) +hdocsCabal pdbs opts = liftM (M.map $ force . HDocs.formatDocs) $ HDocs.installedDocs (packageDbStackOpts pdbs ++ opts) -- | Set docs for module setDocs :: Map String String -> Module -> Module
src/HsDev/Util.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleContexts, OverloadedStrings #-} +{-# LANGUAGE FlexibleContexts, OverloadedStrings, TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module HsDev.Util ( @@ -28,6 +28,8 @@ FromCmd(..), cmdJson, withCmd, guardCmd, withHelp, cmd, parseArgs, + -- * Version stuff + version, cutVersion, sameVersion, strVersion, -- * Reexportss module Control.Monad.Except, @@ -36,6 +38,7 @@ import Control.Arrow (second, left, (&&&)) import Control.Exception +import Control.Concurrent.Async import Control.Monad import Control.Monad.Except import qualified Control.Monad.Catch as C @@ -43,7 +46,7 @@ import Data.Aeson hiding (Result(..), Error) import qualified Data.Aeson.Types as A import Data.Char (isSpace) -import Data.List (isPrefixOf, unfoldr) +import Data.List (isPrefixOf, unfoldr, intercalate) import qualified Data.Map as M import Data.Maybe (catMaybes, fromMaybe) import qualified Data.Set as Set @@ -59,13 +62,14 @@ import System.FilePath import System.IO import qualified System.Log.Simple as Log +import Text.Read (readMaybe) -import Control.Concurrent.Async +import HsDev.Version -- | Run action with current directory set -withCurrentDirectory :: FilePath -> IO a -> IO a -withCurrentDirectory cur act = bracket getCurrentDirectory setCurrentDirectory $ - const (setCurrentDirectory cur >> act) +withCurrentDirectory :: CatchIO.MonadCatchIO m => FilePath -> m a -> m a +withCurrentDirectory cur act = CatchIO.bracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $ + const (liftIO (setCurrentDirectory cur) >> act) -- | Get directory contents safely directoryContents :: FilePath -> IO [FilePath] @@ -288,3 +292,20 @@ instance Log.MonadLog m => Log.MonadLog (ExceptT e m) where askLog = lift Log.askLog + +-- | Get hsdev version as list of integers +version :: Maybe [Int] +version = mapM readMaybe $ split (== '.') $cabalVersion + +-- | Cut version to contain only significant numbers (currently 3) +cutVersion :: Maybe [Int] -> Maybe [Int] +cutVersion = fmap (take 3) + +-- | Check if version is the same +sameVersion :: Maybe [Int] -> Maybe [Int] -> Bool +sameVersion l r = fromMaybe False $ liftA2 (==) l r + +-- | Version to string +strVersion :: Maybe [Int] -> String +strVersion Nothing = "unknown" +strVersion (Just vers) = intercalate "." $ map show vers
src/HsDev/Watcher.hs view
@@ -1,6 +1,6 @@ module HsDev.Watcher ( - watchProject, watchModule, watchSandbox, - unwatchProject, unwatchModule, unwatchSandbox, + watchProject, watchModule, watchPackageDb, watchPackageDbStack, + unwatchProject, unwatchModule, unwatchPackageDb, isSource, isCabal, isConf, module System.Directory.Watcher, @@ -29,14 +29,19 @@ watchModule :: Watcher -> ModuleLocation -> IO () watchModule w (FileModule f Nothing) = watchDir w (takeDirectory f) isSource WatchedModule watchModule w (FileModule _ (Just proj)) = watchProject w proj [] -watchModule w (CabalModule cabal _ _) = watchSandbox w cabal [] watchModule _ _ = return () --- | Watch for sandbox -watchSandbox :: Watcher -> Cabal -> [String] -> IO () -watchSandbox _ Cabal _ = return () -watchSandbox w (Sandbox f) opts = watchTree w f isConf (WatchedSandbox (Sandbox f) opts) +-- | Watch for top of package-db stack +watchPackageDb :: Watcher -> PackageDbStack -> [String] -> IO () +watchPackageDb w pdbs opts = case topPackageDb pdbs of + GlobalDb -> return () -- TODO: Watch for global package-db + UserDb -> return () -- TODO: Watch for user package-db + (PackageDb pdb) -> watchTree w pdb isConf (WatchedPackageDb pdbs opts) +-- | Watch for package-db stack +watchPackageDbStack :: Watcher -> PackageDbStack -> [String] -> IO () +watchPackageDbStack w pdbs opts = mapM_ (\pdbs' -> watchPackageDb w pdbs' opts) $ packageDbStacks pdbs + unwatchProject :: Watcher -> Project -> IO () unwatchProject w proj = do mapM_ (unwatchTree w) dirs @@ -48,12 +53,14 @@ unwatchModule :: Watcher -> ModuleLocation -> IO () unwatchModule w (FileModule f Nothing) = void $ unwatchDir w (takeDirectory f) unwatchModule _ (FileModule _ (Just _)) = return () -unwatchModule _ (CabalModule _ _ _) = return () +unwatchModule _ (InstalledModule _ _ _) = return () unwatchModule _ _ = return () -unwatchSandbox :: Watcher -> Cabal -> IO () -unwatchSandbox _ Cabal = return () -unwatchSandbox w (Sandbox f) = void $ unwatchTree w f +-- | Unwatch package-db +unwatchPackageDb :: Watcher -> PackageDb -> IO () +unwatchPackageDb _ GlobalDb = return () -- TODO: Unwatch global package-db +unwatchPackageDb _ UserDb = return () -- TODO: Unwatch user package-db +unwatchPackageDb w (PackageDb pdb) = void $ unwatchTree w pdb isSource :: Event -> Bool isSource (Event _ f _) = takeExtension f == ".hs"
src/HsDev/Watcher/Types.hs view
@@ -2,13 +2,13 @@ Watched(..), Watcher, - Cabal, Project + PackageDbStack, Project ) where import qualified System.Directory.Watcher as W import HsDev.Project (Project) -import HsDev.Cabal (Cabal) +import HsDev.PackageDb (PackageDbStack) -data Watched = WatchedProject Project [String] | WatchedSandbox Cabal [String] | WatchedModule +data Watched = WatchedProject Project [String] | WatchedPackageDb PackageDbStack [String] | WatchedModule type Watcher = W.Watcher Watched
tools/hsinspect.hs view
@@ -6,15 +6,18 @@ import Control.Monad (liftM, (>=>)) import Control.Monad.IO.Class +import Control.Monad.Except (ExceptT(..), runExceptT) import Data.Aeson (toJSON) import System.Directory (canonicalizePath) import System.FilePath (takeExtension) +import HsDev.PackageDb import HsDev.Project (readProject) import HsDev.Scan (scanModule, scanModify) import HsDev.Inspect (inspectContents, inspectDocs, getDefines) -import HsDev.Symbols.Location (ModuleLocation(..), Cabal(..)) -import HsDev.Tools.GhcMod.InferType (infer) +import HsDev.Symbols.Location (ModuleLocation(..)) +import HsDev.Tools.Ghc.Types (inferTypes) +import HsDev.Tools.Ghc.Worker import Tool @@ -35,12 +38,13 @@ fname' <- liftIO $ canonicalizePath fname defs <- liftIO getDefines im <- scanModule defs ghcs (FileModule fname' Nothing) Nothing + ghc <- liftIO $ ghcWorker ghcs (return ()) let scanAdditional = scanModify (\opts' _ -> inspectDocs opts') >=> - scanModify infer + scanModify (\opts' pdbs m -> ExceptT (inWorker ghc (runExceptT $ inferTypes opts' pdbs m Nothing))) toJSON <$> scanAdditional im inspect' (Opts (Just fcabal@(takeExtension -> ".cabal")) _) = do fcabal' <- liftIO $ canonicalizePath fcabal toJSON <$> readProject fcabal' - inspect' (Opts (Just mname) ghcs) = toJSON <$> scanModule [] ghcs (CabalModule Cabal Nothing mname) Nothing + inspect' (Opts (Just mname) ghcs) = toJSON <$> scanModule [] ghcs (InstalledModule UserDb Nothing mname) Nothing