Skip to content

Commit 13ea77c

Browse files
committed
Implement extensible snapshots #863
1 parent 0b9de70 commit 13ea77c

File tree

10 files changed

+145
-81
lines changed

10 files changed

+145
-81
lines changed

src/Stack/Build/Source.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ parseTargetsFromBuildOpts needTargets boptscli = do
211211
}
212212
ResolverCustom _ url -> do
213213
stackYamlFP <- asks $ bcStackYaml . getBuildConfig
214-
parseCustomMiniBuildPlan stackYamlFP url
214+
parseCustomMiniBuildPlan (Just stackYamlFP) url
215215
rawLocals <- getLocalPackageViews
216216
workingDir <- getCurrentDir
217217

src/Stack/BuildPlan.hs

Lines changed: 104 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ data BuildPlanException
9595
(Map PackageName (Maybe Version, Set PackageName)) -- truly unknown
9696
(Map PackageName (Set PackageIdentifier)) -- shadowed
9797
| SnapshotNotFound SnapName
98+
| FilepathInDownloadedSnapshot T.Text
9899
deriving (Typeable)
99100
instance Exception BuildPlanException
100101
instance Show BuildPlanException where
@@ -174,6 +175,11 @@ instance Show BuildPlanException where
174175
$ Set.toList
175176
$ Set.unions
176177
$ Map.elems shadowed
178+
show (FilepathInDownloadedSnapshot url) = unlines
179+
[ "Downloaded snapshot specified a 'resolver: { location: filepath }' "
180+
, "field, but filepaths are not allowed in downloaded snapshots.\n"
181+
, "Filepath specified: " ++ T.unpack url
182+
]
177183

178184
-- | Determine the necessary packages to install to have the given set of
179185
-- packages available.
@@ -221,8 +227,6 @@ toMiniBuildPlan :: (MonadIO m, MonadLogger m, MonadReader env m, HasHttpManager
221227
-> Map PackageName (Version, Map FlagName Bool, [Text]) -- ^ non-core packages
222228
-> m MiniBuildPlan
223229
toMiniBuildPlan compilerVersion requireAllowNewer corePackages packages = do
224-
$logInfo "Caching build plan"
225-
226230
-- Determine the dependencies of all of the packages in the build plan. We
227231
-- handle core packages specially, because some of them will not be in the
228232
-- package index. For those, we allow missing packages to exist, and then
@@ -404,6 +408,24 @@ getToolMap mbp =
404408
$ Set.toList
405409
$ mpiExes mpi
406410

411+
loadResolver
412+
:: (MonadIO m, MonadThrow m, MonadLogger m, MonadReader env m, HasHttpManager env, HasConfig env, HasGHCVariant env, MonadBaseControl IO m, MonadCatch m)
413+
=> Maybe (Path Abs File)
414+
-> Resolver
415+
-> m MiniBuildPlan
416+
loadResolver mconfigPath resolver =
417+
case resolver of
418+
ResolverSnapshot snap -> loadMiniBuildPlan snap
419+
-- TODO(mgsloan): Not sure what this FIXME means
420+
-- FIXME instead of passing the stackYaml dir we should maintain
421+
-- the file URL in the custom resolver always relative to stackYaml.
422+
ResolverCustom _ url -> parseCustomMiniBuildPlan mconfigPath url
423+
ResolverCompiler compiler -> return MiniBuildPlan
424+
{ mbpCompilerVersion = compiler
425+
, mbpPackages = mempty
426+
, mbpAllowNewer = False
427+
}
428+
407429
-- | Load up a 'MiniBuildPlan', preferably from cache
408430
loadMiniBuildPlan
409431
:: (MonadIO m, MonadThrow m, MonadLogger m, MonadReader env m, HasHttpManager env, HasConfig env, HasGHCVariant env, MonadBaseControl IO m, MonadCatch m)
@@ -883,11 +905,20 @@ shadowMiniBuildPlan (MiniBuildPlan cv pkgs0 allowNewer) shadowed =
883905
Just False -> Right
884906
Nothing -> assert False Right
885907

886-
parseCustomMiniBuildPlan :: (MonadIO m, MonadCatch m, MonadLogger m, MonadReader env m, HasHttpManager env, HasConfig env, MonadBaseControl IO m)
887-
=> Path Abs File -- ^ stack.yaml file location
888-
-> T.Text -> m MiniBuildPlan
889-
parseCustomMiniBuildPlan stackYamlFP url0 = do
890-
yamlFP <- getYamlFP url0
908+
parseCustomMiniBuildPlan
909+
:: (MonadIO m, MonadCatch m, MonadLogger m, MonadReader env m, HasHttpManager env, HasConfig env, HasGHCVariant env, MonadBaseControl IO m)
910+
=> Maybe (Path Abs File) -- ^ Root directory for when url is a filepath
911+
-> T.Text
912+
-> m MiniBuildPlan
913+
parseCustomMiniBuildPlan mconfigPath url0 = do
914+
$logDebug $ "Loading " <> url0 <> " build plan"
915+
eyamlFP <- getYamlFP url0
916+
let yamlFP = either id id eyamlFP
917+
918+
-- FIXME: determine custom snapshot path based on contents. Ideally,
919+
-- use a hash scheme that ignores formatting differences (works on
920+
-- the data), so that an implicit snapshot (TBD) will hash to the
921+
-- same thing as a custom snapshot.
891922

892923
yamlBS <- liftIO $ S.readFile $ toFilePath yamlFP
893924
let yamlHash = S8.unpack $ B16.encode $ SHA256.hash yamlBS
@@ -896,36 +927,33 @@ parseCustomMiniBuildPlan stackYamlFP url0 = do
896927
let binaryFP = customPlanDir </> $(mkRelDir "bin") </> binaryFilename
897928

898929
taggedDecodeOrLoad binaryFP $ do
899-
WithJSONWarnings result warnings <-
930+
WithJSONWarnings (cs0, mresolver) warnings <-
900931
either (throwM . ParseCustomSnapshotException url0) return $
901932
decodeEither' yamlBS
902-
logJSONWarnings (toFilePath yamlFP) warnings
903-
let (CustomSnapshot
904-
mcompilerVersion
905-
packages
906-
(PackageFlags flags)
907-
ghcOptions
908-
allowNewer) = result
909-
let addFlags :: PackageIdentifier -> (PackageName, (Version, Map FlagName Bool, [Text]))
910-
addFlags (PackageIdentifier name ver) =
911-
( name
912-
, ( ver
913-
, Map.findWithDefault Map.empty name flags
914-
-- NOTE: similar to 'allGhcOptions' in Stack.Types.Build
915-
, ghcOptionsFor name ghcOptions
916-
)
917-
)
918-
case mcompilerVersion of
919-
Just compilerVersion ->
920-
toMiniBuildPlan
921-
compilerVersion
922-
(fromMaybe False allowNewer)
923-
Map.empty
924-
(Map.fromList $ map addFlags $ Set.toList packages)
925-
Nothing -> do
926-
-- TODO: proper exception type
933+
logJSONWarnings (T.unpack url0) warnings
934+
case (mresolver, csCompilerVersion cs0) of
935+
(Nothing, Nothing) ->
927936
fail $ "Failed to load custom snapshot at " ++
928-
T.unpack url0 ++ ", because no compiler is specified."
937+
T.unpack url0 ++
938+
", because no 'compiler' or 'resolver' is specified."
939+
(Nothing, Just cv) ->
940+
applyCustomSnapshot cs0 MiniBuildPlan
941+
{ mbpCompilerVersion = cv
942+
, mbpPackages = mempty
943+
, mbpAllowNewer = False
944+
}
945+
-- Even though we ignore the compiler version here, it gets
946+
-- used due to applyCustomSnapshot
947+
(Just resolver, _) -> do
948+
-- Load referenced resolver. If the custom snapshot is
949+
-- stored at a user location, then allow relative
950+
-- filepath custom snapshots.
951+
mbp <- loadResolver customFile resolver
952+
applyCustomSnapshot cs0 mbp
953+
where
954+
customFile = case eyamlFP of
955+
Left _ -> Nothing
956+
Right fp -> Just fp
929957
where
930958
getCustomPlanDir = do
931959
root <- asks $ configStackRoot . getConfig
@@ -944,9 +972,45 @@ parseCustomMiniBuildPlan stackYamlFP url0 = do
944972

945973
let cacheFP = customPlanDir </> $(mkRelDir "yaml") </> hashFP
946974
_ <- download req cacheFP
947-
return cacheFP
948-
949-
getYamlFPFromFile url = do
950-
fp <- liftIO $ D.canonicalizePath $ toFilePath (parent stackYamlFP) FP.</> T.unpack (fromMaybe url $
951-
T.stripPrefix "file://" url <|> T.stripPrefix "file:" url)
952-
parseAbsFile fp
975+
return (Left cacheFP)
976+
977+
getYamlFPFromFile url =
978+
case mconfigPath of
979+
Nothing -> throwM $ FilepathInDownloadedSnapshot url
980+
Just configPath -> do
981+
fp <- liftIO $ D.canonicalizePath $ toFilePath (parent configPath) FP.</> T.unpack (fromMaybe url $
982+
T.stripPrefix "file://" url <|> T.stripPrefix "file:" url)
983+
Right <$> parseAbsFile fp
984+
985+
applyCustomSnapshot
986+
:: (MonadIO m, MonadLogger m, MonadReader env m, HasHttpManager env, MonadThrow m, HasConfig env, MonadBaseControl IO m, MonadCatch m)
987+
=> CustomSnapshot
988+
-> MiniBuildPlan
989+
-> m MiniBuildPlan
990+
applyCustomSnapshot cs mbp0 = do
991+
let CustomSnapshot mcompilerVersion
992+
packages
993+
dropPackages
994+
(PackageFlags flags)
995+
ghcOptions
996+
mallowNewer
997+
= cs
998+
addFlagsAndOpts :: PackageIdentifier -> (PackageName, (Version, Map FlagName Bool, [Text]))
999+
addFlagsAndOpts (PackageIdentifier name ver) =
1000+
( name
1001+
, ( ver
1002+
, Map.findWithDefault Map.empty name flags
1003+
-- NOTE: similar to 'allGhcOptions' in Stack.Types.Build
1004+
, ghcOptionsFor name ghcOptions
1005+
)
1006+
)
1007+
packageMap = Map.fromList $ map addFlagsAndOpts $ Set.toList packages
1008+
cv = fromMaybe (mbpCompilerVersion mbp0) mcompilerVersion
1009+
packages0 =
1010+
mbpPackages mbp0 `Map.difference` (Map.fromSet (\_ -> ()) dropPackages)
1011+
mbp1 <- toMiniBuildPlan cv False mempty packageMap
1012+
return $ MiniBuildPlan
1013+
{ mbpCompilerVersion = cv
1014+
, mbpPackages = Map.union (mbpPackages mbp1) packages0
1015+
, mbpAllowNewer = fromMaybe (mbpAllowNewer mbp0) mallowNewer
1016+
}

src/Stack/Config.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ loadBuildConfig mproject config mresolver mcompiler = do
520520
mbp <- runReaderT (loadMiniBuildPlan snapName) miniConfig
521521
return $ mbpCompilerVersion mbp
522522
ResolverCustom _name url -> do
523-
mbp <- runReaderT (parseCustomMiniBuildPlan stackYamlFP url) miniConfig
523+
mbp <- runReaderT (parseCustomMiniBuildPlan (Just stackYamlFP) url) miniConfig
524524
return $ mbpCompilerVersion mbp
525525
ResolverCompiler wantedCompiler -> return wantedCompiler
526526

src/Stack/Solver.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ getResolverConstraints stackYaml resolver =
482482
ResolverCustom _ url -> do
483483
-- FIXME instead of passing the stackYaml dir we should maintain
484484
-- the file URL in the custom resolver always relative to stackYaml.
485-
mbp <- parseCustomMiniBuildPlan stackYaml url
485+
mbp <- parseCustomMiniBuildPlan (Just stackYaml) url
486486
return (mbpCompilerVersion mbp, mbpConstraints mbp)
487487
ResolverCompiler compiler ->
488488
return (compiler, Map.empty)

src/Stack/Types/Config.hs

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,34 +1719,22 @@ data DockerUser = DockerUser
17191719
data CustomSnapshot = CustomSnapshot
17201720
{ csCompilerVersion :: !(Maybe CompilerVersion)
17211721
, csPackages :: !(Set PackageIdentifier)
1722+
, csDropPackages :: !(Set PackageName)
17221723
, csFlags :: !PackageFlags
17231724
, csGhcOptions :: !GhcOptions
17241725
, csAllowNewer :: !(Maybe Bool)
17251726
}
17261727

1727-
instance FromJSON (WithJSONWarnings CustomSnapshot) where
1728-
parseJSON = withObjectWarnings "CustomSnapshot" $ \o -> CustomSnapshot
1729-
<$> o ..:? "compiler"
1730-
<*> o ..:? "packages" ..!= mempty
1731-
<*> o ..:? "flags" ..!= mempty
1732-
<*> o ..:? configMonoidGhcOptionsName ..!= mempty
1733-
<*> o ..:? configMonoidAllowNewerName
1734-
1735-
instance Monoid CustomSnapshot where
1736-
mempty = CustomSnapshot
1737-
{ csCompilerVersion = Nothing
1738-
, csPackages = mempty
1739-
, csFlags = mempty
1740-
, csGhcOptions = mempty
1741-
, csAllowNewer = Nothing
1742-
}
1743-
mappend l r = CustomSnapshot
1744-
{ csCompilerVersion = csCompilerVersion l <|> csCompilerVersion r
1745-
, csPackages = csPackages l <> csPackages r
1746-
, csFlags = csFlags l <> csFlags r
1747-
, csGhcOptions = csGhcOptions l <> csGhcOptions r
1748-
, csAllowNewer = csAllowNewer l <|> csAllowNewer r
1749-
}
1728+
instance FromJSON (WithJSONWarnings (CustomSnapshot, Maybe Resolver)) where
1729+
parseJSON = withObjectWarnings "CustomSnapshot" $ \o -> (,)
1730+
<$> (CustomSnapshot
1731+
<$> o ..:? "compiler"
1732+
<*> o ..:? "packages" ..!= mempty
1733+
<*> o ..:? "drop-packages" ..!= mempty
1734+
<*> o ..:? "flags" ..!= mempty
1735+
<*> o ..:? configMonoidGhcOptionsName ..!= mempty
1736+
<*> o ..:? configMonoidAllowNewerName)
1737+
<*> jsonSubWarningsT (o ..:? "resolver")
17501738

17511739
newtype GhcOptions = GhcOptions
17521740
{ unGhcOptions :: Map (Maybe PackageName) [Text] }
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import StackTest
22

33
main :: IO ()
4-
main = stack ["build", "SHA"]
4+
main = do
5+
stack ["build", "async"]
6+
stackErr ["build", "zlib-bindings"]
7+
stack ["build", "--stack-yaml", "stack-modify-lts.yaml", "async"]
8+
stackErr ["build", "--stack-yaml", "stack-modify-lts.yaml", "zlib-bindings"]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
resolver: ghc-7.10
2+
packages:
3+
- stm-2.4.4.1
4+
- async-2.1.0
5+
- zlib-0.6.1.1
6+
# FIXME: test these here
7+
flags: {}
8+
ghc-options: {}
9+
allow-newer: true
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
resolver: lts-5.11
2+
drop-packages:
3+
- zlib
Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
compiler: ghc-7.10
1+
resolver:
2+
name: test-snapshot-2
3+
location: snapshot-2.yaml
24
packages:
3-
# Just the first thing I found via github search that conditionally adds exports
4-
# based on flags.
5-
#
6-
# TODO: check that the decoder interface is present (and that this flag matters)
7-
- SHA-1.6.4
8-
- binary-0.8.0.0
9-
flags:
10-
SHA:
11-
DecoderInterface: true
12-
# FIXME: test this better
13-
ghc-options:
14-
SHA: "-Wall"
15-
allow-newer: true
5+
- microlens-0.4.3.0
6+
drop-packages:
7+
- zlib
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
resolver:
2+
name: snapshot-modify-lts
3+
location: snapshot-modify-lts.yaml
4+
packages: []

0 commit comments

Comments
 (0)