packages feed

hackport 0.8.3.0 → 0.8.4.0

raw patch · 10 files changed

+390/−76 lines, 10 filesdep +dlistdep +ghc-compactdep +prettyprinterdep ~binarydep ~processdep ~transformers

Dependencies added: dlist, ghc-compact, prettyprinter

Dependency ranges changed: binary, process, transformers

Files

.github/workflows/haskell.yml view
@@ -1,60 +1,76 @@ name: Haskell CI- on:   push:-    branches: [ master ]+    branches: [main, master]   pull_request:-    branches: [ master ]+    branches: [main, master] +permissions:+  contents: read+ jobs:   build:--    runs-on: ubuntu-latest+    name: GHC ${{ matrix.ghc-version }} on ${{ matrix.os }}+    runs-on: ${{ matrix.os }}     strategy:+      fail-fast: false       matrix:-        cabal:-          [ "3.4"-          , "3.6"-          , "3.8"-          ]-        ghc:+        os: [ubuntu-latest]+        ghc-version:           [ "8.8"           , "8.10"           , "9.0"           , "9.2"+          , "9.4"           ]-        exclude:-          - cabal: "3.4"-            ghc: "9.2"+     steps:-    - uses: actions/checkout@v3-      with:-        submodules: 'recursive'-    - uses: haskell/actions/setup@v2-      with:-        ghc-version: ${{ matrix.ghc }}-        cabal-version: ${{ matrix.cabal }}+      - uses: actions/checkout@v3+        with:+          submodules: 'recursive' -    - name: Cache-      uses: actions/cache@v3-      env:-        cache-name: cache-cabal-      with:-        path: ~/.cabal-        key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project') }}-        restore-keys: |-          ${{ runner.os }}-build-${{ env.cache-name }}--          ${{ runner.os }}-build--          ${{ runner.os }}-+      - name: Set up GHC ${{ matrix.ghc-version }}+        uses: haskell/actions/setup@v2+        id: setup+        with:+          ghc-version: ${{ matrix.ghc-version }}+          # Defaults, added for clarity:+          cabal-version: 'latest'+          cabal-update: true -    - name: Configure-      run: |-        cabal update-        cabal configure --enable-tests --enable-benchmarks --flag=pedantic-    - name: Install dependencies-      run: |-        cabal build --only-dependencies-    - name: Build-      run: cabal build-    - name: Run spec test-      run: cabal test --test-option=--color --test-show-details=streaming spec+      - name: Check cabal file+        run: cabal check++      - name: Configure the build+        run: |+          cabal configure --enable-tests --enable-benchmarks --disable-documentation --flag=pedantic+          cabal build --dry-run+        # The last step generates dist-newstyle/cache/plan.json for the cache key.++      - name: Restore cached dependencies+        uses: actions/cache/restore@v3+        id: cache+        env:+          key: ${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-cabal-${{ steps.setup.outputs.cabal-version }}+        with:+          path: ${{ steps.setup.outputs.cabal-store }}+          key: ${{ env.key }}-plan-${{ hashFiles('**/plan.json') }}+          restore-keys: ${{ env.key }}-++      - name: Install dependencies+        run: cabal build all --only-dependencies++      # Cache dependencies already here, so that we do not have to rebuild them should the subsequent steps fail.+      - name: Save cached dependencies+        uses: actions/cache/save@v3+        # Caches are immutable, trying to save with the same key would error.+        if: ${{ steps.cache.outputs.cache-primary-key != steps.cache.outputs.cache-matched-key }}+        with:+          path: ${{ steps.setup.outputs.cabal-store }}+          key: ${{ steps.cache.outputs.cache-primary-key }}++      - name: Build+        run: cabal build all++      - name: Run tests+        run: cabal test all --test-option=--color --test-show-details=streaming --jobs=1
CHANGELOG.md view
@@ -1,3 +1,32 @@+## v0.8.4.0 (2023-06-03)++Release v0.8.4.0++- Do not add `[profile]` to dev-lang/ghc++  The needed changes have been addressed in haskell-cabal.eclass and+  should not be handled by hackport++- KEYWORDs are now automatically passed forward between version bumps++  This functionality was removed in the last version and has been added+  back. Any "stable" keywords should automatically become "testing"+  when passed forward.++- Add shell completion functionality++  Use optparse-applicative's functionality to generate bash, zsh, and+  fish completion scripts. Because autocompletion for `hackport merge`+  requires searching the list of package/version strings from Hackage,+  this data is stored in a trie which is cached in+  ~/.hackport/packages.cache (which is removed when `hackport update`+  is run).++- Fix doctests++  Doctests should now work with cabal-v1 (Setup.hs), cabal-v2, and+  GitHub CI setups.+ ## v0.8.3.0 (2023-05-06)  Release v0.8.3.0
exe/Main.hs view
@@ -6,10 +6,11 @@ import Control.Applicative.Combinators import qualified Control.Applicative.Combinators.NonEmpty as CNE import Control.Monad-import Data.Bitraversable+import qualified Data.List as L import Data.Monoid (Endo(..)) import qualified Options.Applicative as Opt import qualified Options.Applicative.Help as Opt+import qualified Prettyprinter as PP import qualified Text.Parsec as P import qualified Text.Parsec.String as P import qualified Distribution.Verbosity as V@@ -22,6 +23,7 @@ import Hackport.Command.Update import Hackport.Command.Status import Hackport.Command.Merge+import Hackport.Completion  import Data.Version (showVersion) import Distribution.Pretty (prettyShow)@@ -143,7 +145,7 @@                 $  Opt.long "verbosity"                 <> helpMulti                   [ "Set verbosity level "-                  , "(0-3 or (silent,normal,verbose,deafening))"+                  , "(0-3) or (" ++ L.intercalate "," (show . fst <$> verbs) ++ ")"                   ]               where                 readm :: Opt.ReadM V.Verbosity@@ -152,21 +154,18 @@                   . P.runParser verbosityString () "command line option"                  err :: P.ParseError -> String-                err _ = ($ "") $ Opt.displayS $ Opt.renderCompact $ Opt.extractChunk $ Opt.vsepChunks-                  [ Opt.stringChunk "Takes a number (0-3) or one of the following values:"-                  , Opt.tabulate-#if MIN_VERSION_optparse_applicative(0,17,0)-                    (Opt.prefColumns Opt.defaultPrefs)-#endif-                    =<< traverse (bitraverse Opt.stringChunk Opt.stringChunk)-                      [ ("silent", "No output")-                      , ("normal", "Default verbosity")-                      , ("verbose", "Increased verbosity")-                      , ("deafening", "Maximum verbosity")-                      ]-                  ]-+                err _ = show $ PP.vsep $+                    PP.pretty "Takes a number (0-3) or one of the following values:" :+                    ((\(k,v) -> PP.hsep [PP.pretty (show k), PP.pretty "=", PP.pretty v])+                        <$> verbs+                    ) +                verbs =+                    [ ("silent", "No output")+                    , ("normal", "Default verbosity")+                    , ("verbose", "Increased verbosity")+                    , ("deafening", "Maximum verbosity")+                    ]  ----------------------------------------------------------------------- -- List@@ -192,8 +191,12 @@   where     parser :: Opt.Parser MakeEbuildEnv     parser = do-      cat <- Opt.strArgument $ Opt.metavar "<CATEGORY>"-      cabalFiles <- CNE.some $ Opt.strArgument $ Opt.metavar "[EBUILD FILE]"+      cat <- Opt.strArgument+        $  Opt.metavar "<CATEGORY>"+        <> Opt.completer categoryCompleter+      cabalFiles <- CNE.some $ Opt.strArgument+        $  Opt.metavar "[EBUILD FILE]"+        <> Opt.completer (Opt.bashCompleter "file")       flags <- optional cabalFlagsParser       notOnHackage <- Opt.switch         $  Opt.long "not-on-hackage"@@ -259,7 +262,9 @@     parser :: Opt.Parser MergeEnv     parser = do       flags <- optional $ cabalFlagsParser-      pkg <- Opt.strArgument $ Opt.metavar "PACKAGE"+      pkg <- Opt.strArgument+        $  Opt.metavar "PACKAGE"+        <> Opt.completer cabalPackageCompleter        pure $ MergeEnv flags pkg @@ -285,3 +290,4 @@           [ "Set cabal flags to certain state. Example:"           , "--flags=-all_extensions"           ]+
hackport.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name:          hackport-version:       0.8.3.0+version:       0.8.4.0 license:       GPL-3.0-or-later license-file:  LICENSE author:        Henning Günther, Duncan Coutts, Lennart Kolmodin@@ -17,7 +17,8 @@   , GHC == 8.8.4   , GHC == 8.10.7   , GHC == 9.0.2-  , GHC == 9.2.4+  , GHC == 9.2.7+  , GHC == 9.4.5  source-repository head   type:     git@@ -782,16 +783,20 @@     , hackport-external-libs-cabal-install     , base     , async+    , binary     , bytestring     , containers     , deepseq     , directory+    , dlist     , extensible-exceptions     , filepath+    , ghc-compact     , lifted-base     , monad-control     , mtl     , network-uri+    , optparse-applicative     , parallel                              >=3.2.1.0     , parsec     , pretty@@ -801,6 +806,7 @@     , template-haskell     , text     , time+    , transformers     , xml    -- Needed for doctests-v2 to work@@ -818,12 +824,14 @@   exposed-modules:     AnsiColor     Cabal2Ebuild+    Data.Trie     Error     Hackport.Command.List     Hackport.Command.MakeEbuild     Hackport.Command.Merge     Hackport.Command.Status     Hackport.Command.Update+    Hackport.Completion     Hackport.Env     Hackport.Util     Merge@@ -861,7 +869,7 @@   import:           warnings   ghc-options:      -threaded +RTS -N -RTS -with-rtsopts=-N   if flag(profile)-    ghc-prof-options: -caf-all -auto-all -rtsopts+    ghc-prof-options: -fprof-cafs -fprof-auto -rtsopts   main-is:          Main.hs   default-language: Haskell2010   hs-source-dirs:   exe@@ -876,7 +884,8 @@     , optparse-applicative     , parsec     , parser-combinators-+    , prettyprinter+    , process   other-modules:    Paths_hackport   other-extensions:     ApplicativeDo@@ -1017,9 +1026,6 @@  test-suite doctests-v2   import:             warnings-  -- doctests-v2 is causing problems, looking for ghc lib and alex on-  -- github CI. This may be fixable.-  buildable:          False   type:               exitcode-stdio-1.0   default-language:   Haskell98   hs-source-dirs:     tests/doctests-v2@@ -1029,7 +1035,7 @@     , process    build-tool-depends:-    , cabal-install:cabal  >=3.4+    -- cabal-install should already be installed if this is being built...     , doctest:doctest      >=0.8    if flag(cabal-v1)
+ src/Data/Trie.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE Strict #-}++module Data.Trie+    ( Trie(..)+    , singleton+    , lookup+    , toList+    , buildCompact+    ) where++import Control.Applicative+import Control.Monad+import Control.Monad.IO.Class+import Data.Binary+import qualified Data.DList as DList+import Data.DList (DList)+import qualified Data.Foldable as F+import qualified Data.Map.Strict as Map+import Data.Map.Strict (Map)+import GHC.Compact+import GHC.Generics++import Prelude hiding (lookup)++-- | A tree with branches keyed by a 'Map', which is useful for storing+--   and searching list prefixes in a compact way. Each node is marked+--   with a type signifying list enedings.+--+--   Example: @"he"@ and @"hi"@ stored in a @'Trie' 'Char' '()'@ would look like:+--+--   @Nothing :< toList [('h', Nothing :< toList [('e', Just () :< toList []),('i', Just () :< toList [])])]@+data Trie k a = Maybe a :< Map k (Trie k a)+    deriving (Show, Eq, Ord, Functor, Foldable, Traversable, Generic)++instance Ord k => Semigroup (Trie k a) where+    ma1 :< f1 <> ma2 :< f2 = (ma2 <|> ma1) :< Map.unionWith (<>) f1 f2++instance Ord k => Monoid (Trie k a) where+    mempty = Nothing :< Map.empty++instance (Binary k, Binary a) => Binary (Trie k a)++-- | Walk the 'Trie' using the given key list looking for a matching node.+lookup :: Ord k => Trie k a -> [k] -> Maybe (Trie k a)+lookup (_ :< mapK) (k:ks) =+    Map.lookup k mapK >>= (\c -> lookup c ks)+lookup t [] = Just t++-- | Return the path and value of every node marked with 'Just'+toList :: Trie k a -> [([k],a)]+toList = DList.toList . go DList.empty DList.empty+  where+    go :: DList ([k], a) -> DList k -> Trie k a -> DList ([k], a)+    go justs path (b :< m) =+        let justs' = case b of+                Just x -> justs `DList.snoc` (DList.toList path, x)+                Nothing -> justs+        in justs' <> F.foldMap'+            (\(x,t) -> go justs' (path `DList.snoc` x) t)+            (Map.toAscList m)++-- | Create a simple 'Trie' from a key list and a value.+singleton :: [k] -> a -> Trie k a+singleton l x = go l+  where+    go (k:ks) = Nothing :< Map.singleton k (go ks)+    go []     = Just x :< Map.empty++-- | Build a 'Trie' within a compact region+buildCompact :: (MonadIO m, Ord k)+    => (a -> ([k], b))+    -> [a]+    -> m (Compact (Trie k b))+buildCompact _ [] = liftIO $ compact mempty+buildCompact f (x:xs) = do+    ct <- buildCompact f xs+    cs <- liftIO $ compactAdd ct (f x)+    let (t,(s,b)) = (getCompact ct, getCompact cs)+    cq <- liftIO $ compactAdd ct $ t <> singleton s b+    pure cq+
src/Hackport/Command/Update.hs view
@@ -2,6 +2,8 @@   ( updateAction   ) where +import System.Directory (removeFile)+ import qualified Distribution.Client.CmdUpdate as CabalInstall  import Distribution.Client.Compat.Prelude (Verbosity)@@ -9,16 +11,18 @@   (NixStyleFlags(configFlags, projectFlags), defaultNixStyleFlags) import Distribution.Client.ProjectFlags (ProjectFlags(flagIgnoreProject)) import Distribution.Client.Setup (ConfigFlags(configVerbosity), GlobalFlags(globalRemoteRepos))+import Distribution.Compat.Parsing (skipOptional) import Distribution.Simple.Flag (Flag(Flag)) import Distribution.Utils.NubList (toNubList) +import Hackport.Completion (trieFile) import Hackport.Util (withHackportContext) import Hackport.Env  updateAction :: Env env () updateAction  = askGlobalEnv >>= \(GlobalEnv verbosity _ _) ->   withHackportContext $ \globalFlags _repoContext -> do-    let nixFlags = ignoreProjectInNixFlags +    let nixFlags = ignoreProjectInNixFlags                         $ addVerbosityToNixFlags verbosity (defaultNixStyleFlags ())      -- We need to unset the globalRemoteRepos set in 'withHackportContext' or@@ -26,8 +30,12 @@     -- TODO: The cabal-install code needs to be examined further to see if     -- there is a better way to do this (starting with Distribution.Client.CmdUpdate)     let globalFlags' = globalFlags { globalRemoteRepos = toNubList [] }+    f <- trieFile -    liftIO $ CabalInstall.updateAction nixFlags [] globalFlags'+    liftIO $ do+      -- Remove the file but ignore any errors+      skipOptional $ removeFile f+      CabalInstall.updateAction nixFlags [] globalFlags'  -- | There is no verbosity argument for 'CabalInstall.updateAction'. It expects --   the verbosity to be passed in via 'NixStyleFlags'.
+ src/Hackport/Completion.hs view
@@ -0,0 +1,150 @@+{-# LANGUAGE TupleSections #-}++module Hackport.Completion+    ( categoryCompleter+    , cabalPackageCompleter+    , trieFile+    ) where++import Control.Applicative+import Control.Monad.Trans.Maybe+import Data.Binary+import qualified Data.List as L+import Data.Maybe (catMaybes)+import qualified Data.Set as Set+import Data.Set (Set)+import GHC.Compact+import qualified Options.Applicative as Opt+import System.Exit (ExitCode(..))+import System.FilePath+import System.IO.Error (tryIOError)+import System.Process hiding (env)+import Text.PrettyPrint (render)++import qualified Data.Trie as Trie+import Data.Trie (Trie)+import Hackport.Env+import Hackport.Util (withHackportContext, hackportDir)+import Util++import Distribution.Client.GlobalFlags (RepoContext(..))+import Distribution.Client.IndexUtils (getSourcePackages)+import Distribution.Client.Types (packageIndex)+import Distribution.Compat.Parsing (choice)+import Distribution.Pretty (Pretty, pretty)+import Distribution.Solver.Types.PackageIndex (allPackages)+import Distribution.Solver.Types.SourcePackage (SourcePackage(..))+import qualified Distribution.Verbosity as V++-- | Read the @profiles/category@ file from:+--+--   * @gentoo@  (determined by portageq)+--+--   The following might be read from in the future but it probably isn't+--   worth it for the extra time taken -- there most likely won't be any+--   extra categories that don't exist in @::gentoo@:+--+--   * @haskell@ (determined by portageq)+--   * The repository reported by 'getOverlayPath'+--+--   This is used exclusively for shell completion for the @make-ebuild@+--   subcommand.+categoryCompleter :: Opt.Completer+categoryCompleter = Opt.mkCompleter $ \s -> do+    let env = GlobalEnv V.silent Nothing Nothing+    gentoo <- runEnv gentooOverlay () env++    let repos = catMaybes [gentoo]++    sets <- traverse repoCategories repos+    pure $ Set.toAscList $ Set.filter (s `L.isPrefixOf`) $ Set.unions sets++  where+    gentooOverlay :: Env () (Maybe FilePath)+    gentooOverlay = getRepoPath "gentoo"++    getRepoPath :: String -> Env () (Maybe FilePath)+    getRepoPath repo =+        runOrDie+            "/usr/bin/portageq"+            ["get_repo_path", "/", repo]+            $ \out -> case out of+                [p] -> Just p+                _   -> Nothing++    repoCategories :: FilePath -> IO (Set String)+    repoCategories repoPath = do+        let catsFile = repoPath </> "profiles" </> "categories"+        foldMap (Set.fromList . filter (not . null) . lines)+            -- Returns a Nothing (wrapped in IO) if `readFile` fails+            <$> optional (readFile catsFile)++-- | Read a list of package names/verisons on Hackage. This requires the user+--   to have run @hackport update@ at least once.+--+--   Internally, this creates a 'Trie' which is cached in+--   @~/.hackport/packages.cache@. The cache is removed when @hackport update@+--   is called, which causes the cache file to be regenerated.+--+--   This is used exclusively for shell completion for the @merge@+--   subcommand.+cabalPackageCompleter :: Opt.Completer+cabalPackageCompleter = Opt.mkCompleter $ \s ->+    runEnv (go s) () $ GlobalEnv V.silent Nothing Nothing+  where+    go s = withHackportContext $ \_ repoContext -> do+        Just trie <- runMaybeT $ choice+            [ -- Check to see if there is an existing cache.+              readTrie+              -- Otherwise, create a new trie from the Hackage database+            , lift $ createTrie repoContext+            ]++        let endings = map fst $ maybe [] Trie.toList+                $ Trie.lookup (getCompact trie) s++        pure $ map (s ++) endings++    -- A (Trie Char ()) is useful for storing/searching a list of strings+    createTrie repoContext = do+        pkgs <- allPV repoContext+        trie <- Trie.buildCompact (,()) pkgs+        writeTrie (getCompact trie)+        pure trie++    readTrie :: MonadIO m+        => MaybeT m (Compact (Trie Char ()))+    readTrie = MaybeT $ liftIO $ optional+        $ trieFile >>= decodeFile >>= compact++    writeTrie :: MonadIO m => Trie Char () -> m ()+    writeTrie t = do+        f <- trieFile+        liftIO $ encodeFile f t++    allPV :: MonadIO m => RepoContext -> m [String]+    allPV repoContext = do+        index <- liftIO $ packageIndex <$> getSourcePackages V.silent repoContext+        let allP = allPackages index+        pure $ (\(SourcePackage i _ _ _) -> showPretty i) <$> allP++trieFile :: MonadIO m => m FilePath+trieFile = do+    d <- hackportDir+    pure $ d </> "packages" <.> "cache"++runOrDie :: FilePath -> [String] -> ([String] -> Maybe a) -> Env env (Maybe a)+runOrDie p ss f = do+    r <- liftIO+        $ tryIOError+        $ readProcessWithExitCode p ss ""+    case r of+        Left  e            -> die (show e)+        Right (ec, out, err) -> do+            case ec of+                ExitSuccess ->+                    pure $ f $ filter (not . null) $ lines $ out+                _ -> die $ unwords [out, err]++showPretty :: Pretty a => a -> String+showPretty = render . pretty
src/Hackport/Util.hs view
@@ -2,12 +2,17 @@   ( getPortageDir   , withHackportContext   , defaultRemoteRepo+  , hackportDir   ) where  import Control.Monad.Trans.Control import Data.Maybe (fromJust) import qualified Network.URI as NU-import System.Directory ( doesDirectoryExist )+import System.Directory+    ( doesDirectoryExist+    , getHomeDirectory+    , createDirectoryIfMissing+    ) import System.FilePath ( (</>) )  import Distribution.Simple.Utils (warn)@@ -54,3 +59,11 @@    where     uri  = fromJust $ NU.parseURI "https://hackage.haskell.org/"     name = "hackage.haskell.org"++-- | Return the path to @~/.hackport/@ and create it if it doesn't exist.+hackportDir :: MonadIO m => m FilePath+hackportDir = do+    h <- liftIO $ getHomeDirectory+    let d = h </> ".hackport"+    liftIO $ createDirectoryIfMissing True d+    pure d
src/Merge.hs view
@@ -548,16 +548,19 @@   liftIO $ createDirectoryIfMissing True edir   now <- liftIO $ TC.getCurrentTime -  let existing_license = EM.license existing_meta+  let (existing_keywords, existing_license) = (EM.keywords existing_meta, EM.license existing_meta)+      new_keywords = maybe (E.keywords ebuild) (map Merge.to_unstable) existing_keywords       new_license  = either (\err -> maybe (Left err)                                            Right                                            existing_license)                             Right                             (E.license ebuild)-      ebuild'      = ebuild { E.license = new_license+      ebuild'      = ebuild { E.keywords = new_keywords+                            , E.license = new_license                             }       s_ebuild'    = E.showEBuild now ebuild' +  notice $ "Current keywords: " ++ show existing_keywords ++ " -> " ++ show new_keywords   notice $ "Current license:  " ++ show existing_license ++ " -> " ++ show new_license    notice $ "Writing " ++ elocal
src/Merge/Dependencies.hs view
@@ -125,7 +125,7 @@     cabal_dep :: Portage.Dependency     cabal_dep = cabalDependency overlay (packageDescription pkg) compiler_info     ghc_dep :: Portage.Dependency-    ghc_dep = add_profile $ compilerInfoToDependency compiler_info+    ghc_dep = compilerInfoToDependency compiler_info     extra_libs :: Portage.Dependency     extra_libs = Portage.DependAllOf $ findCLibs (packageDescription pkg)     pkg_config_libs :: [Portage.Dependency]