forked from commercialhaskell/stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPackageIdentifier.hs
More file actions
290 lines (249 loc) · 10.1 KB
/
PackageIdentifier.hs
File metadata and controls
290 lines (249 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS -fno-warn-unused-do-bind #-}
-- | Package identifier (name-version).
module Stack.Types.PackageIdentifier
( PackageIdentifier(..)
, PackageIdentifierRevision(..)
, CabalHash
, mkCabalHashFromSHA256
, computeCabalHash
, showCabalHash
, CabalFileInfo(..)
, toTuple
, fromTuple
, parsePackageIdentifier
, parsePackageIdentifierFromString
, parsePackageIdentifierRevision
, packageIdentifierParser
, packageIdentifierString
, packageIdentifierRevisionString
, packageIdentifierText
, toCabalPackageIdentifier
, fromCabalPackageIdentifier
, StaticSHA256
, mkStaticSHA256FromText
, mkStaticSHA256FromFile
, mkStaticSHA256FromDigest
, staticSHA256ToText
, staticSHA256ToBase16
, staticSHA256ToRaw
)
where
import Stack.Prelude
import Crypto.Hash.Conduit (hashFile)
import Crypto.Hash as Hash (hashlazy, Digest, SHA256)
import Data.Aeson.Extended
import Data.Attoparsec.Text as A
import qualified Data.ByteArray
import qualified Data.ByteArray.Encoding as Mem
import qualified Data.ByteString.Lazy as L
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import qualified Distribution.Package as C
import Stack.StaticBytes
import Stack.Types.PackageName
import Stack.Types.Version
-- | A parse fail.
data PackageIdentifierParseFail
= PackageIdentifierParseFail Text
| PackageIdentifierRevisionParseFail Text
deriving (Typeable)
instance Show PackageIdentifierParseFail where
show (PackageIdentifierParseFail bs) = "Invalid package identifier: " ++ show bs
show (PackageIdentifierRevisionParseFail bs) = "Invalid package identifier (with optional revision): " ++ show bs
instance Exception PackageIdentifierParseFail
-- | A pkg-ver combination.
data PackageIdentifier = PackageIdentifier
{ -- | Get the name part of the identifier.
packageIdentifierName :: !PackageName
-- | Get the version part of the identifier.
, packageIdentifierVersion :: !Version
} deriving (Eq,Ord,Generic,Data,Typeable)
instance NFData PackageIdentifier where
rnf (PackageIdentifier !p !v) =
seq (rnf p) (rnf v)
instance Hashable PackageIdentifier
instance Store PackageIdentifier
instance Show PackageIdentifier where
show = show . packageIdentifierString
instance Display PackageIdentifier where
display = fromString . packageIdentifierString
instance ToJSON PackageIdentifier where
toJSON = toJSON . packageIdentifierString
instance FromJSON PackageIdentifier where
parseJSON = withText "PackageIdentifier" $ \t ->
case parsePackageIdentifier t of
Left e -> fail $ show (e, t)
Right x -> return x
-- | A 'PackageIdentifier' combined with optionally specified Hackage
-- cabal file revision.
data PackageIdentifierRevision = PackageIdentifierRevision
{ pirIdent :: !PackageIdentifier
, pirRevision :: !CabalFileInfo
} deriving (Eq,Ord,Generic,Data,Typeable)
instance NFData PackageIdentifierRevision where
rnf (PackageIdentifierRevision !i !c) =
seq (rnf i) (rnf c)
instance Hashable PackageIdentifierRevision
instance Store PackageIdentifierRevision
instance Show PackageIdentifierRevision where
show = show . packageIdentifierRevisionString
instance ToJSON PackageIdentifierRevision where
toJSON = toJSON . packageIdentifierRevisionString
instance FromJSON PackageIdentifierRevision where
parseJSON = withText "PackageIdentifierRevision" $ \t ->
case parsePackageIdentifierRevision t of
Left e -> fail $ show (e, t)
Right x -> return x
-- | A cryptographic hash of a Cabal file.
newtype CabalHash = CabalHash { unCabalHash :: StaticSHA256 }
deriving (Generic, Show, Eq, NFData, Data, Typeable, Ord, Store, Hashable)
-- | A SHA256 hash, stored in a static size for more efficient
-- serialization with store.
newtype StaticSHA256 = StaticSHA256 Bytes32
deriving (Generic, Show, Eq, NFData, Data, Typeable, Ord, Hashable, Store)
-- | Generate a 'StaticSHA256' value from a base16-encoded SHA256 hash.
mkStaticSHA256FromText :: Text -> Either SomeException StaticSHA256
mkStaticSHA256FromText t =
mapLeft (toException . stringException) (Mem.convertFromBase Mem.Base16 (encodeUtf8 t))
>>= either (Left . toE) (Right . StaticSHA256)
. toStaticExact
. (id :: ByteString -> ByteString)
where
toE e = toException $ stringException $ concat
[ "Unable to convert "
, show t
, " into SHA256: "
, show e
]
-- | Generate a 'StaticSHA256' value from the contents of a file.
mkStaticSHA256FromFile :: MonadIO m => Path Abs File -> m StaticSHA256
mkStaticSHA256FromFile fp = liftIO $ mkStaticSHA256FromDigest <$> hashFile (toFilePath fp)
mkStaticSHA256FromDigest :: Hash.Digest Hash.SHA256 -> StaticSHA256
mkStaticSHA256FromDigest digest
= StaticSHA256
$ either impureThrow id
$ toStaticExact
(Data.ByteArray.convert digest :: ByteString)
-- | Convert a 'StaticSHA256' into a base16-encoded SHA256 hash.
staticSHA256ToText :: StaticSHA256 -> Text
staticSHA256ToText = decodeUtf8 . staticSHA256ToBase16
-- | Convert a 'StaticSHA256' into a base16-encoded SHA256 hash.
staticSHA256ToBase16 :: StaticSHA256 -> ByteString
staticSHA256ToBase16 (StaticSHA256 x) = Mem.convertToBase Mem.Base16 x
staticSHA256ToRaw :: StaticSHA256 -> ByteString
staticSHA256ToRaw (StaticSHA256 x) = Data.ByteArray.convert x
-- | Generate a 'CabalHash' value from a base16-encoded SHA256 hash.
mkCabalHashFromSHA256 :: Text -> Either SomeException CabalHash
mkCabalHashFromSHA256 = fmap CabalHash . mkStaticSHA256FromText
-- | Convert a 'CabalHash' into a base16-encoded SHA256 hash.
cabalHashToText :: CabalHash -> Text
cabalHashToText = staticSHA256ToText . unCabalHash
-- | Compute a 'CabalHash' value from a cabal file's contents.
computeCabalHash :: L.ByteString -> CabalHash
computeCabalHash = CabalHash . mkStaticSHA256FromDigest . Hash.hashlazy
showCabalHash :: CabalHash -> Text
showCabalHash = T.append (T.pack "sha256:") . cabalHashToText
-- | Information on the contents of a cabal file
data CabalFileInfo
= CFILatest
-- ^ Take the latest revision of the cabal file available. This
-- isn't reproducible at all, but the running assumption (not
-- necessarily true) is that cabal file revisions do not change
-- semantics of the build.
| CFIHash
!(Maybe Int) -- file size in bytes
!CabalHash
-- ^ Identify by contents of the cabal file itself
| CFIRevision !Word
-- ^ Identify by revision number, with 0 being the original and
-- counting upward.
deriving (Generic, Show, Eq, Ord, Data, Typeable)
instance Store CabalFileInfo
instance NFData CabalFileInfo
instance Hashable CabalFileInfo
-- | Convert from a package identifier to a tuple.
toTuple :: PackageIdentifier -> (PackageName,Version)
toTuple (PackageIdentifier n v) = (n,v)
-- | Convert from a tuple to a package identifier.
fromTuple :: (PackageName,Version) -> PackageIdentifier
fromTuple (n,v) = PackageIdentifier n v
-- | A parser for a package-version pair.
packageIdentifierParser :: Parser PackageIdentifier
packageIdentifierParser =
do name <- packageNameParser
char '-'
version <- versionParser
return (PackageIdentifier name version)
-- | Convenient way to parse a package identifier from a 'Text'.
parsePackageIdentifier :: MonadThrow m => Text -> m PackageIdentifier
parsePackageIdentifier x = go x
where go =
either (const (throwM (PackageIdentifierParseFail x))) return .
parseOnly (packageIdentifierParser <* endOfInput)
-- | Convenience function for parsing from a 'String'.
parsePackageIdentifierFromString :: MonadThrow m => String -> m PackageIdentifier
parsePackageIdentifierFromString =
parsePackageIdentifier . T.pack
-- | Parse a 'PackageIdentifierRevision'
parsePackageIdentifierRevision :: MonadThrow m => Text -> m PackageIdentifierRevision
parsePackageIdentifierRevision x = go x
where
go =
either (const (throwM (PackageIdentifierRevisionParseFail x))) return .
parseOnly (parser <* endOfInput)
parser = PackageIdentifierRevision
<$> packageIdentifierParser
<*> (cfiHash <|> cfiRevision <|> pure CFILatest)
cfiHash = do
_ <- string $ T.pack "@sha256:"
hash' <- A.takeWhile (/= ',')
hash'' <- either (\e -> fail $ "Invalid SHA256: " ++ show e) return
$ mkCabalHashFromSHA256 hash'
msize <- optional $ do
_ <- A.char ','
A.decimal
A.endOfInput
return $ CFIHash msize hash''
cfiRevision = do
_ <- string $ T.pack "@rev:"
y <- A.decimal
A.endOfInput
return $ CFIRevision y
-- | Get a string representation of the package identifier; name-ver.
packageIdentifierString :: PackageIdentifier -> String
packageIdentifierString (PackageIdentifier n v) = show n ++ "-" ++ show v
-- | Get a string representation of the package identifier with revision; name-ver[@hashtype:hash[,size]].
packageIdentifierRevisionString :: PackageIdentifierRevision -> String
packageIdentifierRevisionString (PackageIdentifierRevision ident cfi) =
concat $ packageIdentifierString ident : rest
where
rest =
case cfi of
CFILatest -> []
CFIHash msize hash' ->
"@sha256:"
: T.unpack (cabalHashToText hash')
: showSize msize
CFIRevision rev -> ["@rev:", show rev]
showSize Nothing = []
showSize (Just int) = [',' : show int]
-- | Get a Text representation of the package identifier; name-ver.
packageIdentifierText :: PackageIdentifier -> Text
packageIdentifierText = T.pack . packageIdentifierString
toCabalPackageIdentifier :: PackageIdentifier -> C.PackageIdentifier
toCabalPackageIdentifier x =
C.PackageIdentifier
(toCabalPackageName (packageIdentifierName x))
(toCabalVersion (packageIdentifierVersion x))
fromCabalPackageIdentifier :: C.PackageIdentifier -> PackageIdentifier
fromCabalPackageIdentifier (C.PackageIdentifier name version) =
PackageIdentifier
(fromCabalPackageName name)
(fromCabalVersion version)