packages feed

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 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++![GitHub Action Badge](https://github.com/kokobd/cabal-hoogle/actions/workflows/test.yml/badge.svg?branch=main)++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 #-}