1 module Distribution
.Client
.Upload
(upload
, uploadDoc
, report
) where
3 import Distribution
.Client
.Compat
.Prelude
4 import qualified Prelude
as Unsafe
(read)
6 import Distribution
.Client
.HttpUtils
8 , remoteRepoTryUpgradeToHttps
10 import Distribution
.Client
.Setup
14 import Distribution
.Client
.Types
.Credentials
20 import Distribution
.Client
.Types
.Repo
(RemoteRepo
(..), Repo
, maybeRepoRemote
)
21 import Distribution
.Client
.Types
.RepoName
(unRepoName
)
23 import Distribution
.Client
.Config
24 import Distribution
.Simple
.Utils
(dieWithException
, info
, notice
, toUTF8BS
, warn
)
25 import Distribution
.Utils
.String (trim
)
27 import Distribution
.Client
.BuildReports
.Anonymous
(parseBuildReport
)
28 import qualified Distribution
.Client
.BuildReports
.Anonymous
as BuildReport
29 import qualified Distribution
.Client
.BuildReports
.Upload
as BuildReport
30 import Distribution
.Client
.Errors
31 import Network
.HTTP
(Header
(..), HeaderName
(..))
32 import Network
.URI
(URI
(uriAuthority
, uriPath
), URIAuth
(uriRegName
))
33 import System
.Directory
34 import System
.FilePath (dropExtension
, takeExtension
, takeFileName
, (</>))
35 import qualified System
.FilePath.Posix
as FilePath.Posix
((</>))
36 import System
.IO (hFlush, stdout)
37 import System
.IO.Echo
(withoutInputEcho
)
39 -- > stripExtensions ["tar", "gz"] "foo.tar.gz"
41 -- > stripExtensions ["tar", "gz"] "foo.gz.tar"
43 stripExtensions
:: [String] -> FilePath -> Maybe String
44 stripExtensions exts path
= foldM f path
(reverse exts
)
47 | takeExtension p
== '.' : e
= Just
(dropExtension p
)
59 upload verbosity repoCtxt mToken mUsername mPassword isCandidate paths
= do
61 repos
= repoContextRepos repoCtxt
62 transport
<- repoContextGetTransport repoCtxt
64 case [remoteRepo | Just remoteRepo
<- map maybeRepoRemote repos
] of
65 [] -> dieWithException verbosity NoRemoteRepositories
66 (r
: rs
) -> remoteRepoTryUpgradeToHttps verbosity transport
(last (r
:| rs
))
67 let targetRepoURI
:: URI
68 targetRepoURI
= remoteRepoURI targetRepo
70 domain
= maybe "Hackage" uriRegName
$ uriAuthority targetRepoURI
71 rootIfEmpty x
= if null x
then "/" else x
76 rootIfEmpty
(uriPath targetRepoURI
)
77 FilePath.Posix
.</> case isCandidate
of
78 IsCandidate
-> "packages/candidates"
79 IsPublished
-> "upload"
84 rootIfEmpty
(uriPath targetRepoURI
)
85 FilePath.Posix
.</> concat
89 IsCandidate
-> "/candidate"
93 auth
<- Just
<$> createAuth domain mToken mUsername mPassword
94 for_ paths
$ \path
-> do
95 notice verbosity
$ "Uploading " ++ path
++ "... "
96 case fmap takeFileName
(stripExtensions
["tar", "gz"] path
) of
106 -- This case shouldn't really happen, since we check in Main that we
107 -- only pass tar.gz files to upload.
108 Nothing
-> dieWithException verbosity
$ NotATarDotGzFile path
119 uploadDoc verbosity repoCtxt mToken mUsername mPassword isCandidate path
= do
120 let repos
= repoContextRepos repoCtxt
121 transport
<- repoContextGetTransport repoCtxt
123 case [remoteRepo | Just remoteRepo
<- map maybeRepoRemote repos
] of
124 [] -> dieWithException verbosity NoRemoteRepositories
125 (r
: rs
) -> remoteRepoTryUpgradeToHttps verbosity transport
(last (r
:| rs
))
126 let targetRepoURI
= remoteRepoURI targetRepo
127 domain
= maybe "Hackage" uriRegName
$ uriAuthority targetRepoURI
128 rootIfEmpty x
= if null x
then "/" else x
132 rootIfEmpty
(uriPath targetRepoURI
)
133 FilePath.Posix
.</> concat
136 , case isCandidate
of
137 IsCandidate
-> "/candidate"
145 rootIfEmpty
(uriPath targetRepoURI
)
146 FilePath.Posix
.</> concat
149 , case isCandidate
of
150 IsCandidate
-> "/candidate"
154 (reverseSuffix
, reversePkgid
) =
157 (reverse (takeFileName path
))
158 pkgid
= reverse $ drop 1 reversePkgid
160 ( reverse reverseSuffix
/= "docs.tar.gz"
161 ||
( case reversePkgid
of
166 $ dieWithException verbosity ExpectedMatchingFileName
168 auth
<- Just
<$> createAuth domain mToken mUsername mPassword
171 [ Header HdrContentType
"application/x-tar"
172 , Header HdrContentEncoding
"gzip"
174 notice verbosity
$ "Uploading documentation " ++ path
++ "... "
175 resp
<- putHttpFile transport verbosity uploadURI path auth headers
177 -- Hackage responds with 204 No Content when docs are uploaded
179 (code
, _
) | code `
elem`
[200, 204] -> do
180 notice verbosity
$ okMessage packageUri
183 "Error uploading documentation "
192 okMessage packageUri
= case isCandidate
of
194 "Documentation successfully uploaded for package candidate. "
195 ++ "You can now preview the result at '"
197 ++ "'. To upload non-candidate documentation, use 'cabal upload --publish'."
199 "Package documentation successfully published. You can now view it at '"
203 promptUsername
:: String -> IO Username
204 promptUsername domain
= do
205 putStr $ domain
++ " username: "
207 fmap Username
getLine
209 promptPassword
:: String -> IO Password
210 promptPassword domain
= do
211 putStr $ domain
++ " password: "
213 -- save/restore the terminal echoing status (no echoing for entering the password)
214 passwd
<- withoutInputEcho
$ fmap Password
getLine
218 report
:: Verbosity
-> RepoContext
-> Maybe Token
-> Maybe Username
-> Maybe Password
-> IO ()
219 report verbosity repoCtxt mToken mUsername mPassword
= do
221 repos
= repoContextRepos repoCtxt
222 remoteRepos
:: [RemoteRepo
]
223 remoteRepos
= mapMaybe maybeRepoRemote repos
224 for_ remoteRepos
$ \remoteRepo
-> do
225 let domain
= maybe "Hackage" uriRegName
$ uriAuthority
(remoteRepoURI remoteRepo
)
226 auth
<- createAuth domain mToken mUsername mPassword
228 reportsDir
<- defaultReportsDir
229 let srcDir
:: FilePath
230 srcDir
= reportsDir
</> unRepoName
(remoteRepoName remoteRepo
)
231 -- We don't want to bomb out just because we haven't built any packages
232 -- from this repo yet.
233 srcExists
<- doesDirectoryExist srcDir
235 contents
<- getDirectoryContents srcDir
236 for_
(filter (\c
-> takeExtension c
== ".log") contents
) $ \logFile
->
238 inp
<- readFile (srcDir
</> logFile
)
239 let (reportStr
, buildLog
) = Unsafe
.read inp
:: (String, String) -- TODO: eradicateNoParse
240 case parseBuildReport
(toUTF8BS reportStr
) of
241 Left errs
-> warn verbosity
$ "Errors: " ++ errs
-- FIXME
245 "Uploading report for "
246 ++ prettyShow
(BuildReport
.package report
')
247 BuildReport
.uploadReports
251 (remoteRepoURI remoteRepo
)
252 [(report
', Just buildLog
)]
264 handlePackage transport verbosity uri packageUri auth isCandidate path
=
266 resp
<- postHttpFile transport verbosity uri path auth
269 | code `
elem`
[200, 204] ->
271 okMessage isCandidate
272 ++ if null warnings
then "" else "\n" ++ formatWarnings
(trim warnings
)
284 okMessage
:: IsCandidate
-> String
285 okMessage IsCandidate
=
286 "Package successfully uploaded as candidate. "
287 ++ "You can now preview the result at '"
289 ++ "'. To publish the candidate, use 'cabal upload --publish'."
290 okMessage IsPublished
=
291 "Package successfully published. You can now view it at '"
295 formatWarnings
:: String -> String
296 formatWarnings x
= "Warnings:\n" ++ (unlines . map ("- " ++) . lines) x
304 createAuth domain mToken mUsername mPassword
= case mToken
of
305 Just token
-> return $ Right
$ unToken token
306 -- Use username and password if no token is provided
308 Username username
<- maybe (promptUsername domain
) return mUsername
309 Password password
<- maybe (promptPassword domain
) return mPassword
310 return $ Left
(username
, password
)