cabal-hoogle (empty) → 0.1.0.0
raw patch · 8 files changed
+402/−0 lines, 8 filesdep +Cabaldep +basedep +cabal-hoogle
Dependencies added: Cabal, base, cabal-hoogle, containers, directory, extra, filepath, optparse-applicative, regex-tdfa, silently, string-interpolate, tasty, tasty-hunit, text, transformers, typed-process
Files
- CHANGELOG.md +5/−0
- README.md +113/−0
- app/Main.hs +3/−0
- cabal-hoogle.cabal +66/−0
- src/Hoogle/Cabal.hs +131/−0
- src/Hoogle/Cabal/CmdOptions.hs +59/−0
- test/IntegrationTest.hs +24/−0
- test/Main.hs +1/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for cabal-hoogle++## 0.1.0.0 -- 2022-07-22++* First version. Supports `cabal-hoogle generate` and `cabal-hoogle run`
+ README.md view
@@ -0,0 +1,113 @@+# cabal-hoogle++++Like `stack hoogle`, but works for cabal projects. Generates hoogle database of your local packages and all dependencies.++<img src="https://user-images.githubusercontent.com/16440269/180609310-643ff9a1-c1eb-479a-b9ca-0cf69d65a62a.gif" width="600"/>++## Installation++Compiling from source requires a minimum GHC version of 8.10. Having `cabal-hoogle` installed, you may use it on a wider range of GHC,+as long as the `Cabal` used to build `cabal-hoogle` and `cabal-install` match.++Step 1, run `cabal update` to make sure you are using the latest hackage index++Step 2, check the `Cabal` library version used by your cabal+```+cabal --version+```+It shows my cabal is using `3.6.3.0` of Cabal library. **`cabal-hoogle` requires at least Cabal 3.2**. Update it before continue.+```+cabal-install version 3.6.2.0+compiled using version 3.6.3.0 of the Cabal library +```++Step 3, use `cabal install` to install `cabal-hoogle` from Hackage, specifying the `Cabal` library version you fetched above.+```+cabal install --constraint="Cabal == 3.6.3.0" cabal-hoogle+```++## Usage++### Prerequisite++#### Install Hoogle+Make sure `hoogle` is installed in your `$PATH`. You can do this by running `cabal install hoogle`++#### Enable Hoogle for Cabal+Make sure your `~/.cabal/config` has the following entries. (DON'T remove existing entries!) [Check the official docs](https://cabal.readthedocs.io/en/3.6/installing-packages.html) if you don't know where is your cabal's global configuration file ++```+documentation: True+haddock+ hoogle: True+ html: True+```++Or, run this command in bash:+```+cabal user-config update -a "documentation: True+haddock+ hoogle: True+ html: True"+```++#### Build Project with Cabal++Then, run `cabal build all` on your project, so that cabal will build haddock and+hoogle files for your local packages and dependencies.++> NOTE: Adding `--enable-documentation --haddock-hoogle --haddock-html` to your build command won't work for dependencies as I tried.++### Generate+```+cabal-hoogle generate+```++This generates the hoogle database. You will see logs very similar to `stack hoogle --rebuild`++### Run Hoogle++With `cabal-hoogle run --`, extra arguments are passed directly to `hoogle`.++You may start a server like this:+```+cabal-hoogle run -- server --local --port 9000+```++Or directly search in command line like this:+```+cabal-hoogle run -- search catMaybes+```++### More Options++Run `cabal-hoogle --help` to see more options. For example if you are using a non-default build dir (not `dist-newstyle`), or you have multiple GHC verisons on the same project.++Below is a complex example:+```+cabal-hoogle --compiler ghc-9.2.3 --platform "x86_64-linux" --builddir mydist generate+```++## FAQ++### Many packages missing documentation++If you see output like this when running `cabal-hoogle generate`+```+Packages missing documentation: ansi-terminal ansi-wl-pprint async base-orphans base16-bytestring base64-bytestring Cabal cabal-hoogle clock colour extra hashable haskell-src-exts haskell-src-meta optparse-applicative primitive regex-base regex-tdfa safe split string-interpolate syb text-conversions th-abstraction th-compat th-expand-syns th-lift th-lift-instances th-orphans th-reify-many transformers-compat typed-process unliftio-core utf8-string vector vector-stream+Found 27 warnings when processing items+```++Probably you haven't built dependencies with hoogle enabled. Check [prerequisites](#prerequisite) more carefully.++### Cabal version mismatch++Make sure to build `cabal-hoogle` with the exact same `Cabal` library as your `cabal-install`. You can use `cabal --version`+to check the version of `Cabal` library. See [Installation](#installation).++### Core libraries missing documentation++If you are on macOS or Windows, and using `ghcup`, your `ghc` probably doesn't come with docs.+See [this](https://gitlab.haskell.org/ghc/ghc/-/issues/20903) and [this](https://github.com/haskell/haskell-language-server/issues/208#issuecomment-1162169087) for details of the upstream issue.
+ app/Main.hs view
@@ -0,0 +1,3 @@+module Main(main) where++import Hoogle.Cabal (main)
+ cabal-hoogle.cabal view
@@ -0,0 +1,66 @@+cabal-version: 3.0+name: cabal-hoogle+version: 0.1.0.0+synopsis: Just like "stack hoogle", but for cabal projects+description: This tool+bug-reports: https://github.com/kokobd/cabal-hoogle/issues+license: BSD-3-Clause+author: kokobd <contact@zelinf.net>+maintainer: kokobd <contact@zelinf.net>+stability: experimental+copyright: (c) 2022 kokobd+category: Development+build-type: Simple+extra-source-files:+ CHANGELOG.md+ README.md++library+ build-depends:+ base >= 4.14 && < 5+ , Cabal >= 3.2.0.0 && < 4+ , containers ^>= 0.6.2.1+ , directory ^>= 1.3.6+ , filepath ^>= 1.4.2+ , optparse-applicative ^>= 0.17.0+ , regex-tdfa ^>= 1.3.1+ , string-interpolate ^>= 0.3.1.2+ , text ^>= 1.2.4+ , transformers ^>= 0.5.6+ , typed-process ^>= 0.2.10+ , extra ^>= 1.7.10+ default-language: Haskell2010+ ghc-options: -Wall+ hs-source-dirs: src+ default-extensions:+ OverloadedStrings+ QuasiQuotes+ exposed-modules:+ Hoogle.Cabal+ Hoogle.Cabal.CmdOptions++executable cabal-hoogle+ main-is: Main.hs+ hs-source-dirs: app+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts+ build-depends:+ base+ , cabal-hoogle++test-suite tests+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: test+ main-is: Main.hs+ other-modules:+ IntegrationTest+ build-depends:+ base+ , cabal-hoogle+ , tasty ^>= 1.4.2.3+ , tasty-hunit ^>= 0.10.0.3+ , silently ^>= 1.2.5.2+ build-tool-depends:+ , tasty-discover:tasty-discover+ , hoogle:hoogle
+ src/Hoogle/Cabal.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}++module Hoogle.Cabal (main) where++import Control.Exception (catch, throw)+import Control.Monad (unless)+import Data.Bifunctor (Bifunctor (second))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.List.NonEmpty.Extra as NonEmpty+import qualified Data.Map.Strict as Map+import Data.Maybe (catMaybes)+import Data.String.Interpolate (i)+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as T+import Data.Traversable (forM)+import Distribution.InstalledPackageInfo (InstalledPackageInfo (haddockHTMLs, installedUnitId))+import Distribution.Simple.Configure (getPersistBuildConfig)+import Distribution.Simple.PackageIndex (allPackagesByName)+import Distribution.Types.LocalBuildInfo (LocalBuildInfo, installedPkgs, localPkgDescr)+import Distribution.Types.PackageDescription (PackageDescription (package))+import Distribution.Types.PackageId (pkgName)+import Distribution.Types.PackageName (PackageName, unPackageName)+import Hoogle.Cabal.CmdOptions+import System.Directory (createDirectoryIfMissing, createDirectoryLink, listDirectory, makeAbsolute, removeDirectoryLink, removeDirectoryRecursive, withCurrentDirectory)+import System.Exit (exitFailure)+import System.FilePath+import System.IO (stderr)+import System.IO.Error (isDoesNotExistError)+import System.Process.Typed+import Text.Regex.TDFA ((=~))+import Prelude hiding (log)++main :: IO ()+main = do+ CmdOptions {..} <- readCmdOptions+ localPackagesDir <- findLocalPackagesBuildDir cmdOptions_compiler cmdOptions_platform cmdOptions_builddir+ let hoogleDir = localPackagesDir </> ".hoogle"+ hoogleLocalPackagesDir = hoogleDir </> "local"+ hoogleDependenciesDir = hoogleDir </> "dependencies"++ case cmdOptions_command of+ CommandGenerate -> do+ catch (removeDirectoryRecursive hoogleDir) $ \(err :: IOError) ->+ if isDoesNotExistError err then pure () else throw err+ createDirectoryIfMissing True hoogleLocalPackagesDir+ createDirectoryIfMissing True hoogleDependenciesDir+ localPackages <- symlinkLocalPackages localPackagesDir hoogleLocalPackagesDir+ let localPkgsName = fmap (pkgName . package . localPkgDescr) localPackages+ dependenciesName <- symlinkDependencies localPackages hoogleDependenciesDir+ let nameStrs = fmap unPackageName (localPkgsName <> dependenciesName)+ withCurrentDirectory hoogleDir $ do+ runProcess_ . proc "hoogle" $+ ["generate", databaseArg, "--local=local", "--local=dependencies"] ++ nameStrs+ CommandRun hoogleArgs -> do+ let hoogleArgs' = case hoogleArgs of+ (x : xs) -> x : databaseArg : xs+ [] -> [databaseArg]+ withCurrentDirectory hoogleDir . runProcess_ . proc "hoogle" $+ hoogleArgs'+ where+ databaseArg = "--database=all.hoo"++findLocalPackagesBuildDir ::+ -- | compiler+ Maybe Text ->+ -- | platform+ Maybe Text ->+ -- | cabal project build dir (often dist-newstyle)+ FilePath ->+ IO FilePath+findLocalPackagesBuildDir compiler platform projectBuildDir = do+ -- directory layout: dist-newstyle/build/x86_64-linux/ghc-9.2.3/$PACKAGE_NAME+ projectBuildDir' <- makeAbsolute projectBuildDir+ platformDir <- enterSubDir (projectBuildDir' </> "build") "platform" (fmap T.unpack platform)+ enterSubDir platformDir "compiler" (fmap T.unpack compiler)++exitOnError :: Text -> IO a+exitOnError log = do+ T.hPutStrLn stderr log+ exitFailure++enterSubDir :: FilePath -> Text -> Maybe FilePath -> IO FilePath+enterSubDir baseDir realm givenSubDir = do+ subDirs <- listDirectory baseDir+ case givenSubDir >>= (\d -> if d `notElem` subDirs then Just d else Nothing) of+ Just wrongSubDir -> exitOnError [i|specified #{realm} #{wrongSubDir} doesn't not exist, make sure to build first|]+ Nothing -> case selectSubDir subDirs givenSubDir of+ Just subDir -> pure $ baseDir </> subDir+ _ -> exitOnError [i|failed to guess #{realm}, please specify one|]+ where+ selectSubDir :: [FilePath] -> Maybe FilePath -> Maybe FilePath+ selectSubDir [exactlyOne] _ = Just exactlyOne+ selectSubDir _ given = given++symlinkLocalPackages :: FilePath -> FilePath -> IO [LocalBuildInfo]+symlinkLocalPackages localPackagesDir destDir = do+ localPackages <- filter (=~ packageNameRegex) <$> listDirectory localPackagesDir+ forM localPackages $ \pkg -> do+ catch (removeDirectoryLink (destDir </> pkg)) $ \(e :: IOError) ->+ if isDoesNotExistError e then pure () else throw e+ createDirectoryLink (localPackagesDir </> pkg) (destDir </> pkg)+ getPersistBuildConfig (localPackagesDir </> pkg)+ where+ packageNameRegex :: String =+ "[[:digit:]]*[[:alpha:]][[:alnum:]]*(-[[:digit:]]*[[:alpha:]][[:alnum:]]*)*-[0-9]+([.][0-9]+)*"++symlinkDependencies :: [LocalBuildInfo] -> FilePath -> IO [PackageName]+symlinkDependencies localPackages hoogleDependenciesDir = do+ let nameToPkgs =+ fmap (NonEmpty.nubOrdOn installedUnitId) . Map.fromListWith (<>) $+ concatMap collectDependenciesForPkg localPackages+ pkgs <- fmap catMaybes . forM (Map.toList nameToPkgs) $ \(name, allPkgs@(pkg NonEmpty.:| pkgs)) -> do+ unless (null pkgs) $+ T.putStrLn [i|Warning: package #{name} has more than 1 version installed, this should not happen. all pkgs: #{fmap installedUnitId allPkgs}|]+ case haddockHTMLs pkg of+ [htmlDir] -> pure $ Just (name, htmlDir)+ htmlDirs -> do+ T.putStrLn [i|Warning: package #{name} doesn't have exactly one haddock html directory, actual: #{htmlDirs}|]+ pure Nothing+ forM pkgs $ \(name, dir) -> do+ createDirectoryLink dir (hoogleDependenciesDir </> unPackageName name)+ pure name+ where+ collectDependenciesForPkg pkg =+ let depsWithName = allPackagesByName (installedPkgs pkg)+ in fmap (second (NonEmpty.:| []))+ . concatMap (\(name, pkgs) -> fmap (name,) pkgs)+ $ depsWithName
+ src/Hoogle/Cabal/CmdOptions.hs view
@@ -0,0 +1,59 @@+module Hoogle.Cabal.CmdOptions+ ( CmdOptions (..),+ readCmdOptions,+ Command (..),+ )+where++import Data.Text (Text)+import Options.Applicative++data CmdOptions = CmdOptions+ { cmdOptions_compiler :: Maybe Text,+ cmdOptions_platform :: Maybe Text,+ cmdOptions_builddir :: FilePath,+ cmdOptions_command :: Command+ }+ deriving (Show, Eq)++data Command+ = CommandGenerate+ | CommandRun [String]+ deriving (Show, Eq)++parser :: Parser CmdOptions+parser =+ CmdOptions+ <$> (optional . strOption)+ ( long "compiler"+ <> help "compiler name and version, for example 'ghc-9.2.3'"+ )+ <*> (optional . strOption)+ ( long "platform"+ <> help "platform, for example x86_64-linux"+ )+ <*> strOption+ ( long "builddir"+ <> value "dist-newstyle"+ <> help "cabal project build dir"+ )+ <*> hsubparser+ ( command "generate" (info (pure CommandGenerate) (progDesc "generate hoogle database"))+ <> command "run" (info commandRunParser (progDesc "run hoogle, with arbitrary arguments"))+ )++commandRunParser :: Parser Command+commandRunParser = CommandRun <$> (many . strArgument) (metavar "ARGS")++readCmdOptions :: IO CmdOptions+readCmdOptions = execParser parserInfo+ where+ parserInfo =+ info+ (parser <**> helper)+ ( fullDesc+ <> progDesc+ ( "Run hoogle on your local packages and dependencies. "+ <> "See https://github.com/kokobd/cabal-hoogle for more information"+ )+ )
+ test/IntegrationTest.hs view
@@ -0,0 +1,24 @@+module IntegrationTest where++import Data.List (isInfixOf)+import Hoogle.Cabal+import System.Environment (withArgs)+import System.IO.Silently (capture_)+import Test.Tasty+import Test.Tasty.HUnit++test_searchInCommandLine :: IO [TestTree]+test_searchInCommandLine = do+ withArgs ["generate"] main++ let testCase' :: String -> [String] -> String -> TestTree+ testCase' name args wantResult =+ testCase name $ do+ output <- withArgs (["run", "--"] ++ args) (capture_ main)+ wantResult `isInfixOf` output @? "actual output: " <> output++ pure+ [ testCase' "test dependency doesn't work" ["testCase"] "No results found",+ testCase' "core library" ["lift"] "Control.Monad.Trans.Class lift",+ testCase' "non-core library" ["runProcess_"] "System.Process.Typed runProcess_"+ ]
+ test/Main.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --tree-display -j1 #-}