Skip to content

Commit 5ece6e7

Browse files
committed
Make custom snaps use hash in dir name #863 #1408
1 parent 6623f9f commit 5ece6e7

File tree

6 files changed

+245
-145
lines changed

6 files changed

+245
-145
lines changed

src/Stack/Build/Source.hs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ import Path.IO
5858
import Prelude
5959
import Stack.Build.Cache
6060
import Stack.Build.Target
61-
import Stack.BuildPlan (loadMiniBuildPlan, shadowMiniBuildPlan,
62-
parseCustomMiniBuildPlan)
61+
import Stack.BuildPlan (shadowMiniBuildPlan)
6362
import Stack.Constants (wiredInPackages)
6463
import Stack.Package
6564
import Stack.PackageIndex (getPackageVersions)
@@ -196,9 +195,6 @@ parseTargetsFromBuildOpts needTargets boptscli = do
196195
bconfig <- asks getBuildConfig
197196
mbp0 <-
198197
case bcResolver bconfig of
199-
ResolverSnapshot snapName -> do
200-
$logDebug $ "Checking resolver: " <> renderSnapName snapName
201-
loadMiniBuildPlan snapName
202198
ResolverCompiler _ -> do
203199
-- We ignore the resolver version, as it might be
204200
-- GhcMajorVersion, and we want the exact version
@@ -209,9 +205,7 @@ parseTargetsFromBuildOpts needTargets boptscli = do
209205
, mbpPackages = Map.empty
210206
, mbpAllowNewer = False
211207
}
212-
ResolverCustom _ url -> do
213-
stackYamlFP <- asks $ bcStackYaml . getBuildConfig
214-
parseCustomMiniBuildPlan (Just stackYamlFP) url
208+
_ -> return (bcWantedMiniBuildPlan bconfig)
215209
rawLocals <- getLocalPackageViews
216210
workingDir <- getCurrentDir
217211

src/Stack/BuildPlan.hs

Lines changed: 154 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{-# LANGUAGE DeriveGeneric #-}
33
{-# LANGUAGE EmptyDataDecls #-}
44
{-# LANGUAGE FlexibleContexts #-}
5+
{-# LANGUAGE GADTs #-}
56
{-# LANGUAGE OverloadedStrings #-}
67
{-# LANGUAGE TemplateHaskell #-}
78
{-# LANGUAGE TupleSections #-}
@@ -20,6 +21,7 @@ module Stack.BuildPlan
2021
, gpdPackageName
2122
, MiniBuildPlan(..)
2223
, MiniPackageInfo(..)
24+
, loadResolver
2325
, loadMiniBuildPlan
2426
, removeSrcPkgDefaultFlags
2527
, resolveBuildPlan
@@ -43,7 +45,7 @@ import Control.Monad.State.Strict (State, execState, get, modify,
4345
import Control.Monad.Trans.Control (MonadBaseControl)
4446
import qualified Crypto.Hash.SHA256 as SHA256
4547
import Data.Aeson.Extended (WithJSONWarnings(..), logJSONWarnings)
46-
import Data.Binary.VersionTagged (taggedDecodeOrLoad)
48+
import Data.Binary.VersionTagged (taggedDecodeOrLoad, decodeFileOrFailDeep)
4749
import qualified Data.ByteString as S
4850
import qualified Data.ByteString.Base16 as B16
4951
import qualified Data.ByteString.Char8 as S8
@@ -55,13 +57,13 @@ import Data.List.NonEmpty (NonEmpty(..))
5557
import qualified Data.List.NonEmpty as NonEmpty
5658
import Data.Map (Map)
5759
import qualified Data.Map as Map
58-
import Data.Maybe (fromMaybe, mapMaybe)
60+
import Data.Maybe (fromMaybe, mapMaybe, isNothing)
5961
import Data.Monoid
6062
import Data.Set (Set)
6163
import qualified Data.Set as Set
6264
import Data.Text (Text)
6365
import qualified Data.Text as T
64-
import Data.Text.Encoding (encodeUtf8)
66+
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
6567
import qualified Data.Traversable as Tr
6668
import Data.Typeable (Typeable)
6769
import Data.Yaml (decodeEither', decodeFileEither)
@@ -86,8 +88,6 @@ import Stack.Package
8688
import Stack.PackageIndex
8789
import Stack.Types
8890
import Stack.Types.StackT
89-
import qualified System.Directory as D
90-
import qualified System.FilePath as FP
9191

9292
data BuildPlanException
9393
= UnknownPackages
@@ -96,6 +96,7 @@ data BuildPlanException
9696
(Map PackageName (Set PackageIdentifier)) -- shadowed
9797
| SnapshotNotFound SnapName
9898
| FilepathInDownloadedSnapshot T.Text
99+
| NeitherCompilerOrResolverSpecified T.Text
99100
deriving (Typeable)
100101
instance Exception BuildPlanException
101102
instance Show BuildPlanException where
@@ -180,6 +181,10 @@ instance Show BuildPlanException where
180181
, "field, but filepaths are not allowed in downloaded snapshots.\n"
181182
, "Filepath specified: " ++ T.unpack url
182183
]
184+
show (NeitherCompilerOrResolverSpecified url) =
185+
"Failed to load custom snapshot at " ++
186+
T.unpack url ++
187+
", because no 'compiler' or 'resolver' is specified."
183188

184189
-- | Determine the necessary packages to install to have the given set of
185190
-- packages available.
@@ -412,19 +417,25 @@ loadResolver
412417
:: (MonadIO m, MonadThrow m, MonadLogger m, MonadReader env m, HasHttpManager env, HasConfig env, HasGHCVariant env, MonadBaseControl IO m, MonadCatch m)
413418
=> Maybe (Path Abs File)
414419
-> Resolver
415-
-> m MiniBuildPlan
420+
-> m (MiniBuildPlan, LoadedResolver)
416421
loadResolver mconfigPath resolver =
417422
case resolver of
418-
ResolverSnapshot snap -> loadMiniBuildPlan snap
423+
ResolverSnapshot snap ->
424+
liftM (, ResolverSnapshot snap) $ loadMiniBuildPlan snap
419425
-- TODO(mgsloan): Not sure what this FIXME means
420426
-- FIXME instead of passing the stackYaml dir we should maintain
421427
-- 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+
ResolverCustom name url -> do
429+
(mbp, hash) <- parseCustomMiniBuildPlan mconfigPath url
430+
return (mbp, ResolverCustomLoaded name url (decodeUtf8 hash))
431+
ResolverCompiler compiler -> return
432+
( MiniBuildPlan
433+
{ mbpCompilerVersion = compiler
434+
, mbpPackages = mempty
435+
, mbpAllowNewer = False
436+
}
437+
, ResolverCompiler compiler
438+
)
428439

429440
-- | Load up a 'MiniBuildPlan', preferably from cache
430441
loadMiniBuildPlan
@@ -905,82 +916,147 @@ shadowMiniBuildPlan (MiniBuildPlan cv pkgs0 allowNewer) shadowed =
905916
Just False -> Right
906917
Nothing -> assert False Right
907918

919+
-- This works differently for snapshots fetched from URL and those
920+
-- fetched from file:
921+
--
922+
-- 1) If downloading the snapshot from a URL, assume the fetched data is
923+
-- immutable. Hash the URL in order to determine the location of the
924+
-- cached download. The file contents of the snapshot determines the
925+
-- hash for looking up cached MBP.
926+
--
927+
-- 2) If loading the snapshot from a file, load all of the involved
928+
-- snapshot files. The hash used to determine the cached MBP is the hash
929+
-- of the concatenation of the parent's hash with the snapshot contents.
930+
--
931+
-- Why this difference? We want to make it easy to simply edit snapshots
932+
-- in the filesystem, but we want caching for remote snapshots. In order
933+
-- to avoid reparsing / reloading all the yaml for remote snapshots, we
934+
-- need a different hash system.
935+
936+
-- TODO: This could probably be more efficient if it first merged the
937+
-- custom snapshots, and then applied them to the MBP. It is nice to
938+
-- apply directly, because then we have the guarantee that it's
939+
-- semantically identical to snapshot extension. If this optimization is
940+
-- implemented, note that the direct Monoid for CustomSnapshot is not
941+
-- correct. Crucially, if a package is present in the snapshot, its
942+
-- flags and ghc-options are not based on settings from prior snapshots.
943+
-- TODO: This semantics should be discussed / documented more.
944+
945+
-- TODO: allow a hash check in the resolver. This adds safety /
946+
-- correctness, allowing you to ensure that you are indeed getting the
947+
-- right custom snapshot.
948+
949+
-- TODO: Allow custom plan to specify a name.
950+
908951
parseCustomMiniBuildPlan
909952
:: (MonadIO m, MonadCatch m, MonadLogger m, MonadReader env m, HasHttpManager env, HasConfig env, HasGHCVariant env, MonadBaseControl IO m)
910953
=> Maybe (Path Abs File) -- ^ Root directory for when url is a filepath
911954
-> T.Text
912-
-> m MiniBuildPlan
913-
parseCustomMiniBuildPlan mconfigPath url0 = do
955+
-> m (MiniBuildPlan, S8.ByteString)
956+
parseCustomMiniBuildPlan mconfigPath0 url0 = do
914957
$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.
922-
923-
yamlBS <- liftIO $ S.readFile $ toFilePath yamlFP
924-
let yamlHash = S8.unpack $ B16.encode $ SHA256.hash yamlBS
925-
binaryFilename <- parseRelFile $ yamlHash ++ ".bin"
926-
customPlanDir <- getCustomPlanDir
927-
let binaryFP = customPlanDir </> $(mkRelDir "bin") </> binaryFilename
928-
929-
taggedDecodeOrLoad binaryFP $ do
930-
WithJSONWarnings (cs0, mresolver) warnings <-
958+
case parseUrl $ T.unpack url0 of
959+
Just req -> downloadCustom url0 req
960+
Nothing ->
961+
case mconfigPath0 of
962+
Nothing -> throwM $ FilepathInDownloadedSnapshot url0
963+
Just configPath -> do
964+
(getMbp, hash) <- readCustom configPath url0
965+
mbp <- getMbp
966+
return (mbp, hash)
967+
where
968+
downloadCustom url req = do
969+
let urlHash = S8.unpack $ B16.encode $ SHA256.hash $ encodeUtf8 url
970+
hashFP <- parseRelFile $ urlHash ++ ".yaml"
971+
customPlanDir <- getCustomPlanDir
972+
let cacheFP = customPlanDir </> $(mkRelDir "yaml") </> hashFP
973+
_ <- download req cacheFP
974+
yamlBS <- liftIO $ S.readFile $ toFilePath cacheFP
975+
let yamlHash = b16Hash yamlBS
976+
binaryPath <- getBinaryPath yamlHash
977+
liftM (, yamlHash) $ taggedDecodeOrLoad binaryPath $ do
978+
(cs, mresolver) <- decodeYaml yamlBS
979+
parentMbp <- case (csCompilerVersion cs, mresolver) of
980+
(Nothing, Nothing) -> throwM (NeitherCompilerOrResolverSpecified url)
981+
(Just cv, Nothing) -> return (compilerBuildPlan cv)
982+
-- NOTE: ignoring the parent's hash, even though
983+
-- there could be one. URL snapshot's hash are
984+
-- determined just from their contents.
985+
(_, Just resolver) -> liftM fst (loadResolver Nothing resolver)
986+
applyCustomSnapshot cs parentMbp
987+
readCustom configPath path = do
988+
yamlFP <- resolveFile (parent configPath) (T.unpack $ fromMaybe path $
989+
T.stripPrefix "file://" path <|> T.stripPrefix "file:" path)
990+
yamlBS <- liftIO $ S.readFile $ toFilePath yamlFP
991+
(cs, mresolver) <- decodeYaml yamlBS
992+
(getMbp, hash) <- case mresolver of
993+
Just (ResolverCustom _ url ) ->
994+
case parseUrl $ T.unpack url of
995+
Just req -> do
996+
let getMbp = do
997+
-- Ignore custom hash, under the
998+
-- assumption that the URL is sufficient
999+
-- for identity.
1000+
(mbp, _) <- downloadCustom url req
1001+
return mbp
1002+
return (getMbp, b16Hash yamlBS)
1003+
Nothing -> do
1004+
(getMbp0, hash0) <- readCustom yamlFP url
1005+
let hash = b16Hash (hash0 <> yamlBS)
1006+
getMbp = do
1007+
binaryPath <- getBinaryPath hash
1008+
-- Idea here is to not waste time
1009+
-- writing out intermediate cache files,
1010+
-- but check for them.
1011+
exists <- doesFileExist binaryPath
1012+
if exists
1013+
then do
1014+
eres <- decodeFileOrFailDeep binaryPath
1015+
case eres of
1016+
Right (Just mbp) -> return mbp
1017+
-- Invalid format cache file, remove.
1018+
_ -> do
1019+
removeFile binaryPath
1020+
getMbp0
1021+
else getMbp0
1022+
return (getMbp, hash)
1023+
Just resolver -> do
1024+
-- NOTE: in the cases where we don't have a hash, the
1025+
-- normal resolver name is enough. Since this name is
1026+
-- part of the yaml file, it ends up in our hash.
1027+
let hash = b16Hash yamlBS
1028+
getMbp = do
1029+
(mbp, resolver') <- loadResolver (Just configPath) resolver
1030+
let mhash = customResolverHash resolver'
1031+
assert (isNothing mhash) (return mbp)
1032+
return (getMbp, hash)
1033+
Nothing -> do
1034+
case csCompilerVersion cs of
1035+
Nothing -> throwM (NeitherCompilerOrResolverSpecified path)
1036+
Just cv -> do
1037+
let hash = b16Hash yamlBS
1038+
getMbp = return (compilerBuildPlan cv)
1039+
return (getMbp, hash)
1040+
return (applyCustomSnapshot cs =<< getMbp, hash)
1041+
getBinaryPath hash = do
1042+
binaryFilename <- parseRelFile $ S8.unpack hash ++ ".bin"
1043+
customPlanDir <- getCustomPlanDir
1044+
return $ customPlanDir </> $(mkRelDir "bin") </> binaryFilename
1045+
decodeYaml yamlBS = do
1046+
WithJSONWarnings res warnings <-
9311047
either (throwM . ParseCustomSnapshotException url0) return $
9321048
decodeEither' yamlBS
9331049
logJSONWarnings (T.unpack url0) warnings
934-
case (mresolver, csCompilerVersion cs0) of
935-
(Nothing, Nothing) ->
936-
fail $ "Failed to load custom snapshot at " ++
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
957-
where
1050+
return res
1051+
compilerBuildPlan cv = MiniBuildPlan
1052+
{ mbpCompilerVersion = cv
1053+
, mbpPackages = mempty
1054+
, mbpAllowNewer = False
1055+
}
9581056
getCustomPlanDir = do
9591057
root <- asks $ configStackRoot . getConfig
9601058
return $ root </> $(mkRelDir "custom-plan")
961-
962-
-- Get the path to the YAML file
963-
getYamlFP url =
964-
case parseUrl $ T.unpack url of
965-
Just req -> getYamlFPFromReq url req
966-
Nothing -> getYamlFPFromFile url
967-
968-
getYamlFPFromReq url req = do
969-
let hashStr = S8.unpack $ B16.encode $ SHA256.hash $ encodeUtf8 url
970-
hashFP <- parseRelFile $ hashStr ++ ".yaml"
971-
customPlanDir <- getCustomPlanDir
972-
973-
let cacheFP = customPlanDir </> $(mkRelDir "yaml") </> hashFP
974-
_ <- download req cacheFP
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
1059+
b16Hash = B16.encode . SHA256.hash
9841060

9851061
applyCustomSnapshot
9861062
:: (MonadIO m, MonadLogger m, MonadReader env m, HasHttpManager env, MonadThrow m, HasConfig env, MonadBaseControl IO m, MonadCatch m)

src/Stack/Config.hs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -512,24 +512,15 @@ loadBuildConfig mproject config mresolver mcompiler = do
512512
, projectCompiler = mcompiler <|> projectCompiler project'
513513
}
514514

515-
wantedCompiler <-
516-
case projectCompiler project of
517-
Just wantedCompiler -> return wantedCompiler
518-
Nothing -> case projectResolver project of
519-
ResolverSnapshot snapName -> do
520-
mbp <- runReaderT (loadMiniBuildPlan snapName) miniConfig
521-
return $ mbpCompilerVersion mbp
522-
ResolverCustom _name url -> do
523-
mbp <- runReaderT (parseCustomMiniBuildPlan (Just stackYamlFP) url) miniConfig
524-
return $ mbpCompilerVersion mbp
525-
ResolverCompiler wantedCompiler -> return wantedCompiler
515+
(mbp, loadedResolver) <- flip runReaderT miniConfig $
516+
loadResolver (Just stackYamlFP) (projectResolver project)
526517

527518
extraPackageDBs <- mapM resolveDir' (projectExtraPackageDBs project)
528519

529520
return BuildConfig
530521
{ bcConfig = config
531-
, bcResolver = projectResolver project
532-
, bcWantedCompiler = wantedCompiler
522+
, bcResolver = loadedResolver
523+
, bcWantedMiniBuildPlan = mbp
533524
, bcPackageEntries = projectPackages project
534525
, bcExtraDeps = projectExtraDeps project
535526
, bcExtraPackageDBs = extraPackageDBs

src/Stack/ConfigCmd.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ cfgCmdSet (ConfigCmdSetResolver newResolver) = do
4545
(projectYamlConfig :: Yaml.Object) <-
4646
liftIO (Yaml.decodeFileEither stackYamlFp) >>=
4747
either throwM return
48+
-- TODO: custom snapshot support?
4849
newResolverText <- fmap resolverName (makeConcreteResolver newResolver)
4950
-- We checking here that the snapshot actually exists
5051
snap <- parseSnapName newResolverText

0 commit comments

Comments
 (0)