-
Notifications
You must be signed in to change notification settings - Fork 77
/
Copy pathSources.hs
296 lines (268 loc) · 8.68 KB
/
Sources.hs
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
291
292
293
294
295
296
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
module Niv.Sources where
import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey)
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Extended as Aeson
import qualified Data.Aeson.KeyMap as KM
import Data.Bifunctor (first)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy.Char8 as BL8
import qualified Data.Digest.Pure.MD5 as MD5
import Data.FileEmbed (embedFile)
import qualified Data.HashMap.Strict as HMS
import Data.Hashable (Hashable)
import Data.List
import Data.String.QQ (s)
import qualified Data.Text as T
import Data.Text.Extended
import Niv.Logger
import Niv.Update
import qualified System.Directory as Dir
import System.FilePath ((</>))
import UnliftIO
-------------------------------------------------------------------------------
-- sources.json related
-------------------------------------------------------------------------------
-- | Where to find the sources.json
data FindSourcesJson
= -- | use the default (nix/sources.json)
Auto
| -- | use the specified file path
AtPath FilePath
data SourcesError
= SourcesDoesntExist
| SourceIsntJSON
| SpecIsntAMap
newtype Sources = Sources
{unSources :: HMS.HashMap PackageName PackageSpec}
deriving newtype (FromJSON, ToJSON)
getSourcesEither :: FindSourcesJson -> IO (Either SourcesError Sources)
getSourcesEither fsj = do
Dir.doesFileExist (pathNixSourcesJson fsj) >>= \case
False -> pure $ Left SourcesDoesntExist
True ->
Aeson.decodeFileStrict (pathNixSourcesJson fsj) >>= \case
Just value -> case valueToSources value of
Nothing -> pure $ Left SpecIsntAMap
Just srcs -> pure $ Right srcs
Nothing -> pure $ Left SourceIsntJSON
where
valueToSources :: Aeson.Value -> Maybe Sources
valueToSources = \case
Aeson.Object obj ->
fmap (Sources . mapKeys PackageName . KM.toHashMapText) $
traverse
( \case
Aeson.Object obj' -> Just (PackageSpec obj')
_ -> Nothing
)
obj
_ -> Nothing
mapKeys :: (Eq k2, Hashable k2) => (k1 -> k2) -> HMS.HashMap k1 v -> HMS.HashMap k2 v
mapKeys f = HMS.fromList . map (first f) . HMS.toList
getSources :: FindSourcesJson -> IO Sources
getSources fsj = do
warnIfOutdated
getSourcesEither fsj
>>= either
( \case
SourcesDoesntExist -> (abortSourcesDoesntExist fsj)
SourceIsntJSON -> (abortSourcesIsntJSON fsj)
SpecIsntAMap -> (abortSpecIsntAMap fsj)
)
pure
setSources :: FindSourcesJson -> Sources -> IO ()
setSources fsj sources = Aeson.encodeFilePretty (pathNixSourcesJson fsj) sources
newtype PackageName = PackageName {unPackageName :: T.Text}
deriving newtype (Eq, Hashable, FromJSONKey, ToJSONKey, Show)
newtype PackageSpec = PackageSpec {unPackageSpec :: Aeson.Object}
deriving newtype (FromJSON, ToJSON, Show, Semigroup, Monoid)
-- | Simply discards the 'Freedom'
attrsToSpec :: Attrs -> PackageSpec
attrsToSpec = PackageSpec . KM.fromHashMapText . fmap snd
-- | @nix/sources.json@ or pointed at by 'FindSourcesJson'
pathNixSourcesJson :: FindSourcesJson -> FilePath
pathNixSourcesJson = \case
Auto -> "nix" </> "sources.json"
AtPath f -> f
--
-- ABORT messages
--
abortSourcesDoesntExist :: FindSourcesJson -> IO a
abortSourcesDoesntExist fsj = abort $ T.unlines [line1, line2]
where
line1 = "Cannot use " <> T.pack (pathNixSourcesJson fsj)
line2 =
[s|
The sources file does not exist! You may need to run 'niv init'.
|]
abortSourcesIsntJSON :: FindSourcesJson -> IO a
abortSourcesIsntJSON fsj = abort $ T.unlines [line1, line2]
where
line1 = "Cannot use " <> T.pack (pathNixSourcesJson fsj)
line2 = "The sources file should be JSON."
abortSpecIsntAMap :: FindSourcesJson -> IO a
abortSpecIsntAMap fsj = abort $ T.unlines [line1, line2]
where
line1 = "Cannot use " <> T.pack (pathNixSourcesJson fsj)
line2 =
[s|
The package specifications in the sources file should be JSON maps from
attribute name to attribute value, e.g.:
{ "nixpkgs": { "foo": "bar" } }
|]
-------------------------------------------------------------------------------
-- sources.nix related
-------------------------------------------------------------------------------
-- | All the released versions of nix/sources.nix
data SourcesNixVersion
= V1
| V2
| V3
| V4
| V5
| V6
| V7
| V8
| V9
| V10
| V11
| V12
| V13
| V14
| V15
| V16
| V17
| -- prettify derivation name
-- add 'local' type of sources
V18
| -- add NIV_OVERRIDE_{name}
V19
| -- can be imported when there's no sources.json
V20
| -- Use the source name in fetchurl
V21
| -- Stop setting `ref` and use `branch` and `tag` in sources
V22
| -- Allow to pass custom system to bootstrap niv in pure mode
V23
| -- Fix NIV_OVERRIDE_{name} for sandbox
V24
| -- Add the ability to pass submodules to fetchGit
V25
| -- formatting fix
V26
deriving stock (Bounded, Enum, Eq)
-- | A user friendly version
sourcesVersionToText :: SourcesNixVersion -> T.Text
sourcesVersionToText = \case
V1 -> "1"
V2 -> "2"
V3 -> "3"
V4 -> "4"
V5 -> "5"
V6 -> "6"
V7 -> "7"
V8 -> "8"
V9 -> "9"
V10 -> "10"
V11 -> "11"
V12 -> "12"
V13 -> "13"
V14 -> "14"
V15 -> "15"
V16 -> "16"
V17 -> "17"
V18 -> "18"
V19 -> "19"
V20 -> "20"
V21 -> "21"
V22 -> "22"
V23 -> "23"
V24 -> "24"
V25 -> "25"
V26 -> "26"
latestVersionMD5 :: T.Text
latestVersionMD5 = sourcesVersionToMD5 maxBound
-- | Find a version based on the md5 of the nix/sources.nix
md5ToSourcesVersion :: T.Text -> Maybe SourcesNixVersion
md5ToSourcesVersion md5 =
find (\snv -> sourcesVersionToMD5 snv == md5) [minBound .. maxBound]
-- | The MD5 sum of a particular version
sourcesVersionToMD5 :: SourcesNixVersion -> T.Text
sourcesVersionToMD5 = \case
V1 -> "a7d3532c70fea66ffa25d6bc7ee49ad5"
V2 -> "24cc0719fa744420a04361e23a3598d0"
V3 -> "e01ed051e2c416e0fc7355fc72aeee3d"
V4 -> "f754fe0e661b61abdcd32cb4062f5014"
V5 -> "c34523590ff7dec7bf0689f145df29d1"
V6 -> "8143f1db1e209562faf80a998be4929a"
V7 -> "00a02cae76d30bbef96f001cabeed96f"
V8 -> "e8b860753dd7fa1fd7b805dd836eb607"
V9 -> "87149616c1b3b1e5aa73178f91c20b53"
V10 -> "d8625c0a03dd935e1c79f46407faa8d3"
V11 -> "8a95b7d93b16f7c7515d98f49b0ec741"
V12 -> "2f9629ad9a8f181ed71d2a59b454970c"
V13 -> "5e23c56b92eaade4e664cb16dcac1e0a"
V14 -> "b470e235e7bcbf106d243fea90b6cfc9"
V15 -> "dc11af910773ec9b4e505e0f49ebcfd2"
V16 -> "2d93c52cab8e960e767a79af05ca572a"
V17 -> "149b8907f7b08dc1c28164dfa55c7fad"
V18 -> "bc5e6aefcaa6f9e0b2155ca4f44e5a33"
V19 -> "543621698065cfc6a4a7985af76df718"
V20 -> "ab4263aa63ccf44b4e1510149ce14eff"
V21 -> "c501eee378828f7f49828a140dbdbca3"
V22 -> "935d1d2f0bf95fda977a6e3a7e548ed4"
V23 -> "4111204b613ec688e2669516dd313440"
V24 -> "116c2d936f1847112fef0013771dab28"
V25 -> "6612caee5814670e5e4d9dd1b71b5f70"
V26 -> "937bff93370a064c9000f13cec5867f9"
-- | The MD5 sum of ./nix/sources.nix
sourcesNixMD5 :: IO T.Text
sourcesNixMD5 = T.pack . show . MD5.md5 <$> BL8.readFile pathNixSourcesNix
-- | @nix/sources.nix@
pathNixSourcesNix :: FilePath
pathNixSourcesNix = "nix" </> "sources.nix"
warnIfOutdated :: IO ()
warnIfOutdated = do
tryAny (BL8.readFile pathNixSourcesNix) >>= \case
Left e ->
twarn $
T.unlines
[ T.unwords ["Could not read", T.pack pathNixSourcesNix],
T.unwords [" ", "(", tshow e, ")"]
]
Right content -> do
case md5ToSourcesVersion (T.pack $ show $ MD5.md5 content) of
-- This is a custom or newer version, we don't do anything
Nothing -> pure ()
Just v
-- The file is the latest
| v == maxBound -> pure ()
-- The file is older than than latest
| otherwise -> do
tsay $
T.unlines
[ T.unwords
[ tbold $ tblue "INFO:",
"new sources.nix available:",
sourcesVersionToText v,
"->",
sourcesVersionToText maxBound
],
" Please run 'niv init' or add the following line in the "
<> T.pack pathNixSourcesNix
<> " file:",
" # niv: no_update"
]
-- | Glue code between nix and sources.json
initNixSourcesNixContent :: B.ByteString
initNixSourcesNixContent = $(embedFile "nix/sources.nix")
-- | Empty JSON map
initNixSourcesJsonContent :: B.ByteString
initNixSourcesJsonContent = "{}"