2 {-# LANGUAGE LambdaCase #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE RecordWildCards #-}
5 {-# LANGUAGE ScopedTypeVariables #-}
6 {-# LANGUAGE TupleSections #-}
7 {-# LANGUAGE ViewPatterns #-}
9 -- | cabal-install CLI command: update
10 module Distribution
.Client
.CmdUpdate
15 import Control
.Exception
16 import Distribution
.Client
.Compat
.Prelude
19 import Distribution
.Client
.Compat
.Directory
22 import Distribution
.Client
.FetchUtils
25 import Distribution
.Client
.HttpUtils
28 import Distribution
.Client
.IndexUtils
30 , currentIndexTimestamp
32 , updatePackageIndexCacheFile
33 , updateRepoIndexCache
36 import Distribution
.Client
.IndexUtils
.IndexState
37 import Distribution
.Client
.JobControl
39 , newParallelJobControl
42 import Distribution
.Client
.NixStyleOptions
44 , defaultNixStyleFlags
47 import Distribution
.Client
.ProjectConfig
49 , ProjectConfigShared
(projectConfigConfigFile
)
50 , projectConfigWithSolverRepoContext
51 , withProjectOrGlobalConfig
53 import Distribution
.Client
.ProjectFlags
56 import Distribution
.Client
.ProjectOrchestration
57 import Distribution
.Client
.Setup
64 import Distribution
.Client
.Types
71 import Distribution
.Simple
.Flag
74 import Distribution
.Simple
.Utils
82 import Distribution
.Verbosity
87 import qualified Data
.Maybe as Unsafe
(fromJust)
88 import qualified Distribution
.Compat
.CharParsing
as P
89 import qualified Text
.PrettyPrint
as Disp
91 import qualified Data
.ByteString
.Lazy
as BS
92 import Data
.Time
(getCurrentTime
)
93 import Distribution
.Client
.GZipUtils
(maybeDecompress
)
94 import Distribution
.Simple
.Command
98 import System
.FilePath (dropExtension
, (<.>))
100 import Distribution
.Client
.Errors
101 import Distribution
.Client
.IndexUtils
.Timestamp
(Timestamp
(NoTimestamp
))
102 import qualified Hackage
.Security
.Client
as Sec
104 updateCommand
:: CommandUI
(NixStyleFlags
())
107 { commandName
= "v2-update"
108 , commandSynopsis
= "Updates list of known packages."
109 , commandUsage
= usageAlternatives
"v2-update" ["[FLAGS] [REPOS]"]
110 , commandDescription
= Just
$ \_
->
112 "For all known remote repositories, download the package list."
113 , commandNotes
= Just
$ \pname
->
114 "REPO has the format <repo-id>[,<index-state>] where index-state follows\n"
115 ++ "the same format and syntax that is supported by the --index-state flag.\n\n"
120 ++ " Download the package list for all known remote repositories.\n\n"
123 ++ " v2-update hackage.haskell.org,@1474732068\n"
126 ++ " v2-update hackage.haskell.org,2016-09-24T17:47:48Z\n"
129 ++ " v2-update hackage.haskell.org,HEAD\n"
132 ++ " v2-update hackage.haskell.org\n"
133 ++ " Download hackage.haskell.org at a specific index state.\n\n"
136 ++ " v2-update hackage.haskell.org head.hackage\n"
137 ++ " Download hackage.haskell.org and head.hackage\n"
138 ++ " head.hackage must be a known repo-id. E.g. from\n"
139 ++ " your cabal.project(.local) file.\n"
140 , commandOptions
= nixStyleOptions
$ const []
141 , commandDefaultFlags
= defaultNixStyleFlags
()
144 data UpdateRequest
= UpdateRequest
145 { _updateRequestRepoName
:: RepoName
146 , _updateRequestRepoState
:: RepoIndexState
150 instance Pretty UpdateRequest
where
151 pretty
(UpdateRequest n s
) = pretty n
<<>> Disp
.comma
<<>> pretty s
153 instance Parsec UpdateRequest
where
156 state
<- P
.char
',' *> parsec
<|
> pure IndexStateHead
157 return (UpdateRequest name state
)
159 updateAction
:: NixStyleFlags
() -> [String] -> GlobalFlags
-> IO ()
160 updateAction flags
@NixStyleFlags
{..} extraArgs globalFlags
= do
161 let ignoreProject
= flagIgnoreProject projectFlags
164 withProjectOrGlobalConfig
168 (projectConfig
<$> establishProjectBaseContext verbosity cliConfig OtherCommand
)
169 (\globalConfig
-> return $ globalConfig
<> cliConfig
)
171 projectConfigWithSolverRepoContext
173 (projectConfigShared projectConfig
)
174 (projectConfigBuildOnly projectConfig
)
177 repos
= repoContextRepos repoCtxt
179 parseArg
:: String -> IO UpdateRequest
180 parseArg s
= case simpleParsec s
of
183 dieWithException verbosity
$ UnableToParseRepo s
185 updateRepoRequests
<- traverse parseArg extraArgs
187 unless (null updateRepoRequests
) $ do
188 let remoteRepoNames
= map repoName repos
190 [ r |
(UpdateRequest r _
) <- updateRepoRequests
, not (r `
elem` remoteRepoNames
)
192 unless (null unknownRepos
) $
193 dieWithException verbosity
$
194 NullUnknownrepos
(map unRepoName unknownRepos
) (map unRepoName remoteRepoNames
)
196 let reposToUpdate
:: [(Repo
, RepoIndexState
)]
197 reposToUpdate
= case updateRepoRequests
of
198 -- If we are not given any specific repository, update all
199 -- repositories to HEAD.
200 [] -> map (,IndexStateHead
) repos
202 let repoMap
= [(repoName r
, r
) | r
<- repos
]
203 lookup' k
= Unsafe
.fromJust (lookup k repoMap
)
204 in [ (lookup' name
, state
)
205 |
(UpdateRequest name state
) <- updateRequests
208 case reposToUpdate
of
210 notice verbosity
"No remote repositories configured"
213 "Downloading the latest package list from "
214 ++ unRepoName
(repoName remoteRepo
)
216 notice verbosity
. unlines $
217 "Downloading the latest package lists from: "
218 : map (("- " ++) . unRepoName
. repoName
. fst) reposToUpdate
220 unless (null reposToUpdate
) $ do
221 jobCtrl
<- newParallelJobControl
(length reposToUpdate
)
223 (spawnJob jobCtrl
. updateRepo verbosity defaultUpdateFlags repoCtxt
)
225 traverse_
(\_
-> collectJob jobCtrl
) reposToUpdate
227 verbosity
= fromFlagOrDefault normal
(configVerbosity configFlags
)
228 cliConfig
= commandLineFlagsToProjectConfig globalFlags flags mempty
-- ClientInstallFlags, not needed here
229 globalConfigFlag
= projectConfigConfigFile
(projectConfigShared cliConfig
)
235 -> (Repo
, RepoIndexState
)
237 updateRepo verbosity _updateFlags repoCtxt
(repo
, indexState
) = do
238 transport
<- repoContextGetTransport repoCtxt
240 RepoLocalNoIndex
{} -> do
241 let index = RepoIndex repoCtxt repo
242 updatePackageIndexCacheFile verbosity
index
250 case downloadResult
of
251 FileAlreadyInCache
->
252 setModificationTime
(indexBaseName repo
<.> "tar")
254 FileDownloaded indexPath
-> do
255 writeFileAtomic
(dropExtension indexPath
) . maybeDecompress
256 =<< BS
.readFile indexPath
257 updateRepoIndexCache verbosity
(RepoIndex repoCtxt repo
)
258 RepoSecure
{} -> repoContextWithSecureRepo repoCtxt repo
$ \repoSecure
-> do
259 let index = RepoIndex repoCtxt repo
260 -- NB: This may be a NoTimestamp if we've never updated before
261 current_ts
<- currentIndexTimestamp
(lessVerbose verbosity
) index
262 -- NB: always update the timestamp, even if we didn't actually
264 writeIndexTimestamp
index indexState
268 if repoContextIgnoreExpiry repoCtxt
269 then Just
<$> getCurrentTime
271 Sec
.uncheckClientErrors
$ Sec
.checkForUpdates repoSecure ce
273 let rname
= remoteRepoName
(repoRemote repo
)
275 -- Update cabal's internal index as well so that it's not out of sync
276 -- (If all access to the cache goes through hackage-security this can go)
279 now
<- getCurrentTime
280 setModificationTime
(indexBaseName repo
<.> "tar") now
282 warn verbosity
$ "Could not set modification time of index tarball -- " ++ displayException e
283 noticeNoWrap verbosity
$
284 "Package list of " ++ prettyShow rname
++ " is up to date."
286 updateRepoIndexCache verbosity
index
287 noticeNoWrap verbosity
$
288 "Package list of " ++ prettyShow rname
++ " has been updated."
290 -- This resolves indexState (which could be HEAD) into a timestamp
291 -- This could be null but should not be, since the above guarantees
292 -- we have an updated index.
293 new_ts
<- currentIndexTimestamp
(lessVerbose verbosity
) index
295 noticeNoWrap verbosity
$
296 "The index-state is set to " ++ prettyShow
(IndexStateTime new_ts
) ++ "."
298 -- TODO: This will print multiple times if there are multiple
299 -- repositories: main problem is we don't have a way of updating
300 -- a specific repo. Once we implement that, update this.
302 -- In case current_ts is a valid timestamp different from new_ts, let
303 -- the user know how to go back to current_ts
304 when (current_ts
/= NoTimestamp
&& new_ts
/= current_ts
) $
305 noticeNoWrap verbosity
$
306 "To revert to previous state run:\n"
307 ++ " cabal v2-update '"
308 ++ prettyShow
(UpdateRequest rname
(IndexStateTime current_ts
))