forked from commercialhaskell/stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBuildPlan.hs
More file actions
403 lines (370 loc) · 15.8 KB
/
BuildPlan.hs
File metadata and controls
403 lines (370 loc) · 15.8 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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
-- | Shared types for various stackage packages.
module Stack.Types.BuildPlan
( -- * Types
SnapshotDef (..)
, snapshotDefVC
, sdRawPathName
, PackageLocation (..)
, PackageLocationIndex (..)
, RepoType (..)
, Subdirs (..)
, Repo (..)
, Archive (..)
, ExeName (..)
, LoadedSnapshot (..)
, loadedSnapshotVC
, LoadedPackageInfo (..)
, ModuleName (..)
, fromCabalModuleName
, ModuleInfo (..)
, moduleInfoVC
, setCompilerVersion
, sdWantedCompilerVersion
) where
import Data.Aeson (ToJSON (..), FromJSON (..), withText, object, (.=))
import Data.Aeson.Extended (WithJSONWarnings (..), (..:), (..:?), withObjectWarnings, noJSONWarnings, (..!=))
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Store.Version
import Data.Store.VersionTagged
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import qualified Distribution.ModuleName as C
import qualified Distribution.Version as C
import Network.HTTP.Client (parseRequest)
import Stack.Prelude
import Stack.Types.Compiler
import Stack.Types.FlagName
import Stack.Types.GhcPkgId
import Stack.Types.PackageIdentifier
import Stack.Types.PackageName
import Stack.Types.Resolver
import Stack.Types.Version
import Stack.Types.VersionIntervals
-- | A definition of a snapshot. This could be a Stackage snapshot or
-- something custom. It does not include information on the global
-- package database, this is added later.
--
-- It may seem more logic to attach flags, options, etc, directly with
-- the desired package. However, this isn't possible yet: our
-- definition may contain tarballs or Git repos, and we don't actually
-- know the package names contained there. Therefore, we capture all
-- of this additional information by package name, and later in the
-- snapshot load step we will resolve the contents of tarballs and
-- repos, figure out package names, and assigned values appropriately.
data SnapshotDef = SnapshotDef
{ sdParent :: !(Either (CompilerVersion 'CVWanted) SnapshotDef)
-- ^ The snapshot to extend from. This is either a specific
-- compiler, or a @SnapshotDef@ which gives us more information
-- (like packages). Ultimately, we'll end up with a
-- @CompilerVersion@.
, sdResolver :: !LoadedResolver
-- ^ The resolver that provides this definition.
, sdResolverName :: !Text
-- ^ A user-friendly way of referring to this resolver.
, sdLocations :: ![PackageLocationIndex Subdirs]
-- ^ Where to grab all of the packages from.
, sdDropPackages :: !(Set PackageName)
-- ^ Packages present in the parent which should not be included
-- here.
, sdFlags :: !(Map PackageName (Map FlagName Bool))
-- ^ Flag values to override from the defaults
, sdHidden :: !(Map PackageName Bool)
-- ^ Packages which should be hidden when registering. This will
-- affect, for example, the import parser in the script
-- command. We use a 'Map' instead of just a 'Set' to allow
-- overriding the hidden settings in a parent snapshot.
, sdGhcOptions :: !(Map PackageName [Text])
-- ^ GHC options per package
, sdGlobalHints :: !(Map PackageName (Maybe Version))
-- ^ Hints about which packages are available globally. When
-- actually building code, we trust the package database provided
-- by GHC itself, since it may be different based on platform or
-- GHC install. However, when we want to check the compatibility
-- of a snapshot with some codebase without installing GHC (e.g.,
-- during stack init), we would use this field.
}
deriving (Show, Eq, Data, Generic, Typeable)
instance Store SnapshotDef
instance NFData SnapshotDef
snapshotDefVC :: VersionConfig SnapshotDef
snapshotDefVC = storeVersionConfig "sd-v1" "CKo7nln8EXkw07Gq-4ATxszNZiE="
-- | A relative file path including a unique string for the given
-- snapshot.
sdRawPathName :: SnapshotDef -> String
sdRawPathName sd =
T.unpack $ go $ sdResolver sd
where
go (ResolverStackage name) = renderSnapName name
go (ResolverCompiler version) = compilerVersionText version
go (ResolverCustom _ hash) = "custom-" <> sdResolverName sd <> "-" <> trimmedSnapshotHash hash
-- | Modify the wanted compiler version in this snapshot. This is used
-- when overriding via the `compiler` value in a custom snapshot or
-- stack.yaml file. We do _not_ need to modify the snapshot's hash for
-- this: all binary caches of a snapshot are stored in a filepath that
-- encodes the actual compiler version in addition to the
-- hash. Therefore, modifications here will not lead to any invalid
-- data.
setCompilerVersion :: CompilerVersion 'CVWanted -> SnapshotDef -> SnapshotDef
setCompilerVersion cv =
go
where
go sd =
case sdParent sd of
Left _ -> sd { sdParent = Left cv }
Right sd' -> sd { sdParent = Right $ go sd' }
-- | Where to get the contents of a package (including cabal file
-- revisions) from.
--
-- A GADT may be more logical than the index parameter, but this plays
-- more nicely with Generic deriving.
data PackageLocation subdirs
= PLFilePath !FilePath
-- ^ Note that we use @FilePath@ and not @Path@s. The goal is: first parse
-- the value raw, and then use @canonicalizePath@ and @parseAbsDir@.
| PLArchive !(Archive subdirs)
| PLRepo !(Repo subdirs)
-- ^ Stored in a source control repository
deriving (Generic, Show, Eq, Ord, Data, Typeable, Functor)
instance (Store a) => Store (PackageLocation a)
instance (NFData a) => NFData (PackageLocation a)
-- | Add in the possibility of getting packages from the index
-- (including cabal file revisions). We have special handling of this
-- case in many places in the codebase, and therefore represent it
-- with a separate data type from 'PackageLocation'.
data PackageLocationIndex subdirs
= PLIndex !PackageIdentifierRevision
-- ^ Grab the package from the package index with the given
-- version and (optional) cabal file info to specify the correct
-- revision.
| PLOther !(PackageLocation subdirs)
deriving (Generic, Show, Eq, Ord, Data, Typeable, Functor)
instance (Store a) => Store (PackageLocationIndex a)
instance (NFData a) => NFData (PackageLocationIndex a)
-- | A package archive, could be from a URL or a local file
-- path. Local file path archives are assumed to be unchanging
-- over time, and so are allowed in custom snapshots.
data Archive subdirs = Archive
{ archiveUrl :: !Text
, archiveSubdirs :: !subdirs
, archiveHash :: !(Maybe StaticSHA256)
}
deriving (Generic, Show, Eq, Ord, Data, Typeable, Functor)
instance Store a => Store (Archive a)
instance NFData a => NFData (Archive a)
-- | The type of a source control repository.
data RepoType = RepoGit | RepoHg
deriving (Generic, Show, Eq, Ord, Data, Typeable)
instance Store RepoType
instance NFData RepoType
data Subdirs
= DefaultSubdirs
| ExplicitSubdirs ![FilePath]
deriving (Generic, Show, Eq, Data, Typeable)
instance Store Subdirs
instance NFData Subdirs
instance FromJSON Subdirs where
parseJSON = fmap ExplicitSubdirs . parseJSON
-- | Information on packages stored in a source control repository.
data Repo subdirs = Repo
{ repoUrl :: !Text
, repoCommit :: !Text
, repoType :: !RepoType
, repoSubdirs :: !subdirs
}
deriving (Generic, Show, Eq, Ord, Data, Typeable, Functor)
instance Store a => Store (Repo a)
instance NFData a => NFData (Repo a)
instance subdirs ~ Subdirs => ToJSON (PackageLocationIndex subdirs) where
toJSON (PLIndex ident) = toJSON ident
toJSON (PLOther loc) = toJSON loc
instance subdirs ~ Subdirs => ToJSON (PackageLocation subdirs) where
toJSON (PLFilePath fp) = toJSON fp
toJSON (PLArchive (Archive t DefaultSubdirs Nothing)) = toJSON t
toJSON (PLArchive (Archive t subdirs msha)) = object $ concat
[ ["location" .= t]
, case subdirs of
DefaultSubdirs -> []
ExplicitSubdirs x -> ["subdirs" .= x]
, case msha of
Nothing -> []
Just sha -> ["sha256" .= staticSHA256ToText sha]
]
toJSON (PLRepo (Repo url commit typ subdirs)) = object $ concat
[ case subdirs of
DefaultSubdirs -> []
ExplicitSubdirs x -> ["subdirs" .= x]
, [urlKey .= url]
, ["commit" .= commit]
]
where
urlKey =
case typ of
RepoGit -> "git"
RepoHg -> "hg"
instance subdirs ~ Subdirs => FromJSON (WithJSONWarnings (PackageLocationIndex subdirs)) where
parseJSON v
= (noJSONWarnings . PLIndex <$> parseJSON v)
<|> (fmap PLOther <$> parseJSON v)
instance subdirs ~ Subdirs => FromJSON (WithJSONWarnings (PackageLocation subdirs)) where
parseJSON v
= (noJSONWarnings <$> withText "PackageLocation" (\t -> http t <|> file t) v)
<|> repo v
<|> archiveObject v
<|> github v
where
file t = pure $ PLFilePath $ T.unpack t
http t =
case parseRequest $ T.unpack t of
Left _ -> fail $ "Could not parse URL: " ++ T.unpack t
Right _ -> return $ PLArchive $ Archive t DefaultSubdirs Nothing
repo = withObjectWarnings "PLRepo" $ \o -> do
(repoType, repoUrl) <-
((RepoGit, ) <$> o ..: "git") <|>
((RepoHg, ) <$> o ..: "hg")
repoCommit <- o ..: "commit"
repoSubdirs <- o ..:? "subdirs" ..!= DefaultSubdirs
return $ PLRepo Repo {..}
archiveObject = withObjectWarnings "PLArchive" $ \o -> do
url <- o ..: "archive"
subdirs <- o ..:? "subdirs" ..!= DefaultSubdirs
msha <- o ..:? "sha256"
msha' <-
case msha of
Nothing -> return Nothing
Just t ->
case mkStaticSHA256FromText t of
Left e -> fail $ "Invalid SHA256: " ++ T.unpack t ++ ", " ++ show e
Right x -> return $ Just x
return $ PLArchive Archive
{ archiveUrl = url
, archiveSubdirs = subdirs :: Subdirs
, archiveHash = msha'
}
github = withObjectWarnings "PLArchive:github" $ \o -> do
GitHubRepo ghRepo <- o ..: "github"
commit <- o ..: "commit"
subdirs <- o ..:? "subdirs" ..!= DefaultSubdirs
return $ PLArchive Archive
{ archiveUrl = "https://github.com/" <> ghRepo <> "/archive/" <> commit <> ".tar.gz"
, archiveSubdirs = subdirs
, archiveHash = Nothing
}
-- An unexported newtype wrapper to hang a 'FromJSON' instance off of. Contains
-- a GitHub user and repo name separated by a forward slash, e.g. "foo/bar".
newtype GitHubRepo = GitHubRepo Text
instance FromJSON GitHubRepo where
parseJSON = withText "GitHubRepo" $ \s -> do
case T.split (== '/') s of
[x, y] | not (T.null x || T.null y) -> return (GitHubRepo s)
_ -> fail "expecting \"user/repo\""
-- | Name of an executable.
newtype ExeName = ExeName { unExeName :: Text }
deriving (Show, Eq, Ord, Hashable, IsString, Generic, Store, NFData, Data, Typeable)
-- | A fully loaded snapshot combined , including information gleaned from the
-- global database and parsing cabal files.
--
-- Invariant: a global package may not depend upon a snapshot package,
-- a snapshot may not depend upon a local or project, and all
-- dependencies must be satisfied.
data LoadedSnapshot = LoadedSnapshot
{ lsCompilerVersion :: !(CompilerVersion 'CVActual)
, lsGlobals :: !(Map PackageName (LoadedPackageInfo GhcPkgId))
, lsPackages :: !(Map PackageName (LoadedPackageInfo (PackageLocationIndex FilePath)))
}
deriving (Generic, Show, Data, Eq, Typeable)
instance Store LoadedSnapshot
instance NFData LoadedSnapshot
loadedSnapshotVC :: VersionConfig LoadedSnapshot
loadedSnapshotVC = storeVersionConfig "ls-v4" "a_ljrJRo8hA_-gcIDP9c6NXJ2pE="
-- | Information on a single package for the 'LoadedSnapshot' which
-- can be installed.
--
-- Note that much of the information below (such as the package
-- dependencies or exposed modules) can be conditional in the cabal
-- file, which means it will vary based on flags, arch, and OS.
data LoadedPackageInfo loc = LoadedPackageInfo
{ lpiVersion :: !Version
-- ^ This /must/ match the version specified within 'rpiDef'.
, lpiLocation :: !loc
-- ^ Where to get the package from. This could be a few different
-- things:
--
-- * For a global package, it will be the @GhcPkgId@. (If we end
-- up needing to rebuild this because we've changed a
-- dependency, we will take it from the package index with no
-- @CabalFileInfo@.
--
-- * For a dependency, it will be a @PackageLocation@.
--
-- * For a project package, it will be a @Path Abs Dir@.
, lpiFlags :: !(Map FlagName Bool)
-- ^ Flags to build this package with.
, lpiGhcOptions :: ![Text]
-- ^ GHC options to use when building this package.
, lpiPackageDeps :: !(Map PackageName VersionIntervals)
-- ^ All packages which must be built/copied/registered before
-- this package.
, lpiProvidedExes :: !(Set ExeName)
-- ^ The names of executables provided by this package, for
-- performing build tool lookups.
, lpiNeededExes :: !(Map ExeName VersionIntervals)
-- ^ Executables needed by this package.
, lpiExposedModules :: !(Set ModuleName)
-- ^ Modules exposed by this package's library
, lpiHide :: !Bool
-- ^ Should this package be hidden in the database. Affects the
-- script interpreter's module name import parser.
}
deriving (Generic, Show, Eq, Data, Typeable, Functor)
instance Store a => Store (LoadedPackageInfo a)
instance NFData a => NFData (LoadedPackageInfo a)
data DepInfo = DepInfo
{ _diComponents :: !(Set Component)
, _diRange :: !VersionIntervals
}
deriving (Generic, Show, Eq, Data, Typeable)
instance Store DepInfo
instance NFData DepInfo
instance Monoid DepInfo where
mempty = DepInfo mempty (fromVersionRange C.anyVersion)
DepInfo a x `mappend` DepInfo b y = DepInfo
(mappend a b)
(intersectVersionIntervals x y)
data Component = CompLibrary
| CompExecutable
| CompTestSuite
| CompBenchmark
deriving (Generic, Show, Eq, Ord, Data, Typeable, Enum, Bounded)
instance Store Component
instance NFData Component
newtype ModuleName = ModuleName { unModuleName :: ByteString }
deriving (Show, Eq, Ord, Generic, Store, NFData, Typeable, Data)
fromCabalModuleName :: C.ModuleName -> ModuleName
fromCabalModuleName = ModuleName . encodeUtf8 . T.intercalate "." . map T.pack . C.components
newtype ModuleInfo = ModuleInfo
{ miModules :: Map ModuleName (Set PackageName)
}
deriving (Show, Eq, Ord, Generic, Typeable, Data)
instance Store ModuleInfo
instance NFData ModuleInfo
instance Monoid ModuleInfo where
mempty = ModuleInfo mempty
mappend (ModuleInfo x) (ModuleInfo y) =
ModuleInfo (Map.unionWith Set.union x y)
moduleInfoVC :: VersionConfig ModuleInfo
moduleInfoVC = storeVersionConfig "mi-v2" "8ImAfrwMVmqoSoEpt85pLvFeV3s="
-- | Determined the desired compiler version for this 'SnapshotDef'.
sdWantedCompilerVersion :: SnapshotDef -> CompilerVersion 'CVWanted
sdWantedCompilerVersion = either id sdWantedCompilerVersion . sdParent