Skip to content

Commit

Permalink
Merge pull request #4809 from zw3rk/feature/new-update
Browse files Browse the repository at this point in the history
Adds `new-update`
  • Loading branch information
angerman authored Oct 26, 2017
2 parents dda87d1 + 91c7175 commit 24cd442
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 1 deletion.
14 changes: 14 additions & 0 deletions Cabal/doc/nix-local-build.rst
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,20 @@ to happen if a flag actually applied to every transitive dependency). To
apply options to an external package, use a ``package`` stanza in a
``cabal.project`` file.

cabal new-update
----------------

``cabal new-update`` updates the state of the package index. If the
project contains multiple remote package repositories it will update
the index of all of them (e.g. when using overlays).

Seom examples:

::

$ cabal new-update # update all remote repos
$ cabal new-update head.hackage # update only head.hackage

cabal new-build
---------------

Expand Down
196 changes: 196 additions & 0 deletions cabal-install/Distribution/Client/CmdUpdate.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
{-# LANGUAGE CPP, NamedFieldPuns, RecordWildCards, ViewPatterns, TupleSections #-}

-- | cabal-install CLI command: update
--
module Distribution.Client.CmdUpdate (
updateCommand,
updateAction,
) where

import Distribution.Client.ProjectOrchestration
import Distribution.Client.ProjectConfig
( ProjectConfig(..)
, projectConfigWithSolverRepoContext )
import Distribution.Client.Types
( Repo(..), RemoteRepo(..), isRepoRemote )
import Distribution.Client.HttpUtils
( DownloadResult(..) )
import Distribution.Client.FetchUtils
( downloadIndex )
import Distribution.Client.JobControl
( newParallelJobControl, spawnJob, collectJob )
import Distribution.Client.Setup
( GlobalFlags, ConfigFlags(..), ConfigExFlags, InstallFlags, UpdateFlags
, applyFlagDefaults, defaultUpdateFlags, RepoContext(..) )
import Distribution.Simple.Setup
( HaddockFlags, fromFlagOrDefault )
import Distribution.Simple.Utils
( die', notice, wrapText, writeFileAtomic, noticeNoWrap, intercalate )
import Distribution.Verbosity
( Verbosity, normal, lessVerbose )
import Distribution.Client.IndexUtils.Timestamp
import Distribution.Client.IndexUtils
( updateRepoIndexCache, Index(..), writeIndexTimestamp
, currentIndexTimestamp )
import Distribution.Text
( Text(..), display, simpleParse )

import Data.Maybe (fromJust)
import qualified Distribution.Compat.ReadP as ReadP
import qualified Text.PrettyPrint as Disp

import Control.Monad (unless, when)
import qualified Data.ByteString.Lazy as BS
import Distribution.Client.GZipUtils (maybeDecompress)
import System.FilePath (dropExtension)
import Data.Time (getCurrentTime)
import Distribution.Simple.Command
( CommandUI(..), usageAlternatives )
import qualified Distribution.Client.Setup as Client

import qualified Hackage.Security.Client as Sec

updateCommand :: CommandUI (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags)
updateCommand = Client.installCommand {
commandName = "new-update",
commandSynopsis = "Updates list of known packages.",
commandUsage = usageAlternatives "new-update" [ "[FLAGS] [REPOS]" ],
commandDescription = Just $ \_ -> wrapText $
"For all known remote repositories, download the package list.",
commandNotes = Just $ \pname ->
"REPO has the format <repo-id>[,<index-state>] where index-state follows\n"
++ "the same format and syntax that is supported by the --index-state flag.\n\n"
++ "Examples:\n"
++ " " ++ pname ++ " new-update\n"
++ " Download the package list for all known remote repositories.\n\n"
++ " " ++ pname ++ " new-update hackage.haskell.org,@1474732068\n"
++ " " ++ pname ++ " new-update hackage.haskell.org,2016-09-24T17:47:48Z\n"
++ " " ++ pname ++ " new-update hackage.haskell.org,HEAD\n"
++ " " ++ pname ++ " new-update hackage.haskell.org\n"
++ " Download hackage.haskell.org at a specific index state.\n\n"
++ " " ++ pname ++ " new update hackage.haskell.org head.hackage\n"
++ " Download hackage.haskell.org and head.hackage\n"
++ " head.hackage must be a known repo-id. E.g. from\n"
++ " your cabal.project(.local) file.\n\n"
++ "Note: this command is part of the new project-based system (aka "
++ "nix-style\nlocal builds). These features are currently in beta. "
++ "Please see\n"
++ "http://cabal.readthedocs.io/en/latest/nix-local-build-overview.html "
++ "for\ndetails and advice on what you can expect to work. If you "
++ "encounter problems\nplease file issues at "
++ "https://github.com/haskell/cabal/issues and if you\nhave any time "
++ "to get involved and help with testing, fixing bugs etc then\nthat "
++ "is very much appreciated.\n"
}

data UpdateRequest = UpdateRequest
{ _updateRequestRepoName :: String
, _updateRequestRepoState :: IndexState
} deriving (Show)

instance Text UpdateRequest where
disp (UpdateRequest n s) = Disp.text n Disp.<> Disp.char ',' Disp.<> disp s
parse = parseWithState ReadP.+++ parseHEAD
where parseWithState = do
name <- ReadP.many1 (ReadP.satisfy (\c -> c /= ','))
_ <- ReadP.char ','
state <- parse
return (UpdateRequest name state)
parseHEAD = do
name <- ReadP.manyTill (ReadP.satisfy (\c -> c /= ',')) ReadP.eof
return (UpdateRequest name IndexStateHead)

updateAction :: (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags)
-> [String] -> GlobalFlags -> IO ()
updateAction (applyFlagDefaults -> (configFlags, configExFlags, installFlags, haddockFlags))
extraArgs globalFlags = do

ProjectBaseContext {
projectConfig
} <- establishProjectBaseContext verbosity cliConfig

projectConfigWithSolverRepoContext verbosity (projectConfigShared projectConfig) (projectConfigBuildOnly projectConfig)
$ \repoCtxt -> do
let repos = filter isRepoRemote $ repoContextRepos repoCtxt
repoName = remoteRepoName . repoRemote
parseArg :: String -> IO UpdateRequest
parseArg s = case simpleParse s of
Just r -> pure r

This comment has been minimized.

Copy link
@angerman

angerman Oct 26, 2017

Author Collaborator

pure broke it.

Nothing -> die' verbosity $ "'new-update' unable to parse repo: \"" ++ s ++ "\""
updateRepoRequests <- mapM parseArg extraArgs

unless (null updateRepoRequests) $ do
let remoteRepoNames = map repoName repos
unknownRepos = [r | (UpdateRequest r _) <- updateRepoRequests
, not (r `elem` remoteRepoNames)]
unless (null unknownRepos) $
die' verbosity $ "'new-update' repo(s): \"" ++ intercalate "\", \"" unknownRepos
++ "\" can not be found in known remote repo(s): " ++ intercalate ", " remoteRepoNames

let reposToUpdate :: [(Repo, IndexState)]
reposToUpdate = case updateRepoRequests of
-- if we are not given any speicifc repository. Update all repositories to
-- HEAD.
[] -> map (,IndexStateHead) repos
updateRequests -> let repoMap = [(repoName r, r) | r <- repos]
lookup' k = fromJust (lookup k repoMap)
in [(lookup' name, state) | (UpdateRequest name state) <- updateRequests]

case reposToUpdate of
[] -> return ()
[(remoteRepo, _)] ->
notice verbosity $ "Downloading the latest package list from "
++ repoName remoteRepo
_ -> notice verbosity . unlines
$ "Downloading the latest package lists from: "
: map (("- " ++) . repoName . fst) reposToUpdate

jobCtrl <- newParallelJobControl (length reposToUpdate)
mapM_ (spawnJob jobCtrl . updateRepo verbosity defaultUpdateFlags repoCtxt) reposToUpdate
mapM_ (\_ -> collectJob jobCtrl) reposToUpdate

where
verbosity = fromFlagOrDefault normal (configVerbosity configFlags)
cliConfig = commandLineFlagsToProjectConfig
globalFlags configFlags configExFlags
installFlags haddockFlags

updateRepo :: Verbosity -> UpdateFlags -> RepoContext -> (Repo, IndexState) -> IO ()
updateRepo verbosity _updateFlags repoCtxt (repo, indexState) = do
transport <- repoContextGetTransport repoCtxt
case repo of
RepoLocal{..} -> return ()
RepoRemote{..} -> do
downloadResult <- downloadIndex transport verbosity repoRemote repoLocalDir
case downloadResult of
FileAlreadyInCache -> return ()
FileDownloaded indexPath -> do
writeFileAtomic (dropExtension indexPath) . maybeDecompress
=<< BS.readFile indexPath
updateRepoIndexCache verbosity (RepoIndex repoCtxt repo)
RepoSecure{} -> repoContextWithSecureRepo repoCtxt repo $ \repoSecure -> do
let index = RepoIndex repoCtxt repo
-- NB: This may be a nullTimestamp if we've never updated before
current_ts <- currentIndexTimestamp (lessVerbose verbosity) repoCtxt repo
-- NB: always update the timestamp, even if we didn't actually
-- download anything
writeIndexTimestamp index indexState
ce <- if repoContextIgnoreExpiry repoCtxt
then Just `fmap` getCurrentTime
else return Nothing
updated <- Sec.uncheckClientErrors $ Sec.checkForUpdates repoSecure ce
-- Update cabal's internal index as well so that it's not out of sync
-- (If all access to the cache goes through hackage-security this can go)
case updated of
Sec.NoUpdates ->
return ()
Sec.HasUpdates ->
updateRepoIndexCache verbosity index
-- TODO: This will print multiple times if there are multiple
-- repositories: main problem is we don't have a way of updating
-- a specific repo. Once we implement that, update this.
when (current_ts /= nullTimestamp) $
noticeNoWrap verbosity $
"To revert to previous state run:\n" ++
" cabal new-update '" ++ remoteRepoName (repoRemote repo) ++ "," ++ display current_ts ++ "'\n"

2 changes: 1 addition & 1 deletion cabal-install/Distribution/Client/Setup.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module Distribution.Client.Setup
, installCommand, InstallFlags(..), installOptions, defaultInstallFlags
, defaultSolver, defaultMaxBackjumps
, listCommand, ListFlags(..)
, updateCommand, UpdateFlags(..)
, updateCommand, UpdateFlags(..), defaultUpdateFlags
, upgradeCommand
, uninstallCommand
, infoCommand, InfoFlags(..)
Expand Down
5 changes: 5 additions & 0 deletions cabal-install/Distribution/Client/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ data Repo =
instance Binary Repo

-- | Check if this is a remote repo
isRepoRemote :: Repo -> Bool
isRepoRemote RepoLocal{} = False
isRepoRemote _ = True

-- | Extract @RemoteRepo@ from @Repo@ if remote.
maybeRepoRemote :: Repo -> Maybe RemoteRepo
maybeRepoRemote (RepoLocal _localDir) = Nothing
maybeRepoRemote (RepoRemote r _localDir) = Just r
Expand Down
2 changes: 2 additions & 0 deletions cabal-install/cabal-install.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ library
Distribution.Client.CmdBench
Distribution.Client.CmdBuild
Distribution.Client.CmdConfigure
Distribution.Client.CmdUpdate
Distribution.Client.CmdErrorMessages
Distribution.Client.CmdExec
Distribution.Client.CmdFreeze
Expand Down Expand Up @@ -415,6 +416,7 @@ executable cabal
Distribution.Client.CmdBench
Distribution.Client.CmdBuild
Distribution.Client.CmdConfigure
Distribution.Client.CmdUpdate
Distribution.Client.CmdErrorMessages
Distribution.Client.CmdExec
Distribution.Client.CmdFreeze
Expand Down
3 changes: 3 additions & 0 deletions cabal-install/changelog
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
-*-change-log-*-

2.2.0.0 (current development version)
* Completed the 'new-update' command (#4809), which respects nix-style
cabal.project(.local) files and allows to update from
multiple repositories when using overlays.
* New config file field: 'cxx-options' to specify which options to be
passed to the compiler when compiling C++ sources specified by the
'cxx-sources' field. (#3700)
Expand Down
3 changes: 3 additions & 0 deletions cabal-install/main/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ import Distribution.Client.Targets
import qualified Distribution.Client.List as List
( list, info )


import qualified Distribution.Client.CmdConfigure as CmdConfigure
import qualified Distribution.Client.CmdUpdate as CmdUpdate
import qualified Distribution.Client.CmdBuild as CmdBuild
import qualified Distribution.Client.CmdRepl as CmdRepl
import qualified Distribution.Client.CmdFreeze as CmdFreeze
Expand Down Expand Up @@ -311,6 +313,7 @@ mainWorker args = topHandler $
, hiddenCmd manpageCommand (manpageAction commandSpecs)

, regularCmd CmdConfigure.configureCommand CmdConfigure.configureAction
, regularCmd CmdUpdate.updateCommand CmdUpdate.updateAction
, regularCmd CmdBuild.buildCommand CmdBuild.buildAction
, regularCmd CmdRepl.replCommand CmdRepl.replAction
, regularCmd CmdFreeze.freezeCommand CmdFreeze.freezeAction
Expand Down

1 comment on commit 24cd442

@angerman
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this one broke CI? Why didn't this show up in the PR? Weird.

Please sign in to comment.