Skip to content

Commit d18c620

Browse files
committed
Don't track hpack run cache separately
Instead: just cache the results of cabal file parsing, and run hpack when doing so. This (as the previous few patches) involved much more overhaul than seems like it should. The best way to do this reliably is to only expose a single function from Stack.Package which can run hpack. In turn, this ended up requiring a conversion of a bunch of parts of the code base from passing around Path Abs File (pointing to the cabal file itself) to instead pass around Path Abs Dir (pointing to the directory). I think this is a good change, once against simplifying things a bit more.
1 parent 49c6cfd commit d18c620

9 files changed

Lines changed: 140 additions & 168 deletions

File tree

src/Stack/BuildPlan.hs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ module Stack.BuildPlan
1818
, DepErrors
1919
, gpdPackageDeps
2020
, gpdPackages
21-
, gpdPackageName
2221
, removeSrcPkgDefaultFlags
2322
, selectBestSnapshot
2423
, getToolMap
@@ -192,12 +191,6 @@ gpdPackages gpds = Map.fromList $
192191
fromCabalIdent (C.PackageIdentifier name version) =
193192
(fromCabalPackageName name, fromCabalVersion version)
194193

195-
gpdPackageName :: GenericPackageDescription -> PackageName
196-
gpdPackageName = fromCabalPackageName
197-
. C.pkgName
198-
. C.package
199-
. C.packageDescription
200-
201194
gpdPackageDeps
202195
:: GenericPackageDescription
203196
-> CompilerVersion 'CVActual

src/Stack/Ghci.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,11 @@ makeGhciPkgInfo buildOptsCLI sourceMap installedMap locals addPkgs mfileTargets
566566
, packageConfigCompilerVersion = compilerVersion
567567
, packageConfigPlatform = view platformL econfig
568568
}
569-
gpkgdesc <- readPackageUnresolved cabalfp True
569+
-- TODO we've already parsed this information, otherwise we
570+
-- wouldn't have figured out the cabalfp already. In the future:
571+
-- retain that GenericPackageDescription in the relevant data
572+
-- structures to avoid reparsing.
573+
(gpkgdesc, _cabalfp) <- readPackageUnresolvedDir (parent cabalfp) True
570574

571575
-- Source the package's *.buildinfo file created by configure if any. See
572576
-- https://www.haskell.org/cabal/users-guide/developing-packages.html#system-dependent-parameters

src/Stack/IDE.hs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import qualified Data.Map as Map
1414
import qualified Data.Set as Set
1515
import qualified Data.Text as T
1616
import Stack.Config (getLocalPackages)
17-
import Stack.Package (findOrGenerateCabalFile)
17+
import Stack.Package (readPackageUnresolvedDir, gpdPackageName)
1818
import Stack.Prelude
1919
import Stack.Types.Config
2020
import Stack.Types.Package
@@ -28,9 +28,8 @@ listPackages = do
2828
-- the directory.
2929
packageDirs <- liftM (map lpvRoot . Map.elems . lpProject) getLocalPackages
3030
forM_ packageDirs $ \dir -> do
31-
cabalfp <- findOrGenerateCabalFile dir
32-
pkgName <- parsePackageNameFromFilePath cabalfp
33-
(logInfo . packageNameText) pkgName
31+
(gpd, _) <- readPackageUnresolvedDir dir False
32+
(logInfo . packageNameText) (gpdPackageName gpd)
3433

3534
-- | List the targets in the current project.
3635
listTargets :: HasEnvConfig env => RIO env ()

src/Stack/Init.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import Data.List (intercalate, intersect,
1919
maximumBy)
2020
import Data.List.NonEmpty (NonEmpty (..))
2121
import qualified Data.List.NonEmpty as NonEmpty
22-
import qualified Data.Map as Map
22+
import qualified Data.Map.Strict as Map
23+
import qualified Data.Set as Set
2324
import qualified Data.Text as T
2425
import qualified Data.Yaml as Yaml
2526
import qualified Distribution.PackageDescription as C
@@ -68,11 +69,11 @@ initProject whichCmd currDir initOpts mresolver = do
6869
dirs <- mapM (resolveDir' . T.unpack) (searchDirs initOpts)
6970
let noPkgMsg = "In order to init, you should have an existing .cabal \
7071
\file. Please try \"stack new\" instead."
71-
find = findCabalFiles (includeSubDirs initOpts)
72+
find = findCabalDirs (includeSubDirs initOpts)
7273
dirs' = if null dirs then [currDir] else dirs
7374
logInfo "Looking for .cabal or package.yaml files to use to init the project."
74-
cabalfps <- liftM concat $ mapM find dirs'
75-
(bundle, dupPkgs) <- cabalPackagesCheck cabalfps noPkgMsg Nothing
75+
cabaldirs <- (Set.toList . Set.unions) <$> mapM find dirs'
76+
(bundle, dupPkgs) <- cabalPackagesCheck cabaldirs noPkgMsg Nothing
7677

7778
(sd, flags, extraDeps, rbundle) <- getDefaultResolver whichCmd dest initOpts
7879
mresolver bundle

src/Stack/Package.hs

Lines changed: 82 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,13 @@
1616
-- | Dealing with Cabal.
1717

1818
module Stack.Package
19-
(readPackage
19+
(readPackageDir
20+
,readPackageUnresolvedDir
21+
,readPackageUnresolvedIndex
2022
,readPackageDescriptionDir
2123
,readDotBuildinfo
22-
,readPackageUnresolved
23-
,readPackageUnresolvedFromIndex
2424
,resolvePackage
25-
,CabalWarnings(..)
2625
,packageFromPackageDescription
27-
,findOrGenerateCabalFile
28-
,hpack
2926
,Package(..)
3027
,PackageDescriptionPair(..)
3128
,GetPackageFiles(..)
@@ -37,12 +34,14 @@ module Stack.Package
3734
,packageDescTools
3835
,packageDependencies
3936
,autogenDir
40-
,cabalFilePackageId)
37+
,cabalFilePackageId
38+
,gpdPackageIdentifier
39+
,gpdPackageName
40+
,gpdVersion)
4141
where
4242

4343
import qualified Data.ByteString as BS
4444
import qualified Data.ByteString.Char8 as C8
45-
import qualified Data.HashSet as HashSet
4645
import Data.List (isSuffixOf, partition, isPrefixOf)
4746
import Data.List.Extra (nubOrd)
4847
import qualified Data.Map.Strict as M
@@ -115,38 +114,44 @@ instance HasBuildConfig Ctx
115114
instance HasEnvConfig Ctx where
116115
envConfigL = lens ctxEnvConfig (\x y -> x { ctxEnvConfig = y })
117116

118-
data CabalWarnings
119-
= CWNoPrint
120-
| CWPrint !(Path Abs File)
121-
122-
-- | Parse a cabal file from the given location. This performs caching
123-
-- (based on the 'PackageLocationIndex'), and will only use the second
124-
-- argument to grab a 'ByteString' on a cache miss. If we actually
125-
-- perform a parse, and there are warnings, and the third argument is
126-
-- 'True', then they will be printed.
127-
cachedCabalFileParse
128-
:: forall env. HasRunner env
117+
-- | A helper function that performs the basic character encoding
118+
-- necessary.
119+
rawParseGPD
120+
:: MonadThrow m
129121
=> Either PackageIdentifierRevision (Path Abs File)
130-
-> CabalWarnings
131-
-> RIO env BS.ByteString -- ^ get the bytestring contents
132-
-> RIO env GenericPackageDescription
133-
cachedCabalFileParse key cw getBS = do
122+
-> BS.ByteString
123+
-> m ([PWarning], GenericPackageDescription)
124+
rawParseGPD key bs =
125+
case parseGenericPackageDescription chars of
126+
ParseFailed e -> throwM $ PackageInvalidCabalFile key e
127+
ParseOk warnings gpkg -> return (warnings,gpkg)
128+
where
129+
chars = T.unpack (dropBOM (decodeUtf8With lenientDecode bs))
130+
131+
-- https://github.com/haskell/hackage-server/issues/351
132+
dropBOM t = fromMaybe t $ T.stripPrefix "\xFEFF" t
133+
134+
-- | Read the raw, unresolved package information from a file.
135+
readPackageUnresolvedDir
136+
:: forall env. HasConfig env
137+
=> Path Abs Dir -- ^ directory holding the cabal file
138+
-> Bool -- ^ print warnings?
139+
-> RIO env (GenericPackageDescription, Path Abs File)
140+
readPackageUnresolvedDir dir printWarnings = do
134141
ref <- view $ runnerL.to runnerParsedCabalFiles
135-
m0 <- readIORef ref
136-
case M.lookup key m0 of
137-
Just val -> return val
142+
(_, m) <- readIORef ref
143+
case M.lookup dir m of
144+
Just x -> return x
138145
Nothing -> do
139-
bs <- getBS
140-
val <-
141-
case rawParseGPD bs of
142-
Left e -> throwM $ PackageInvalidCabalFile key e
143-
Right (warnings, gpd) -> do
144-
case cw of
145-
CWNoPrint -> return ()
146-
CWPrint src -> mapM_ (prettyWarnL . toPretty (toFilePath src)) warnings
147-
return gpd
148-
atomicModifyIORef' ref $ \m -> (M.insert key val m, ())
149-
return val
146+
cabalfp <- findOrGenerateCabalFile dir
147+
bs <- liftIO $ BS.readFile $ toFilePath cabalfp
148+
(warnings, gpd) <- rawParseGPD (Right cabalfp) bs
149+
when printWarnings
150+
$ mapM_ (prettyWarnL . toPretty (toFilePath cabalfp)) warnings
151+
checkCabalFileName (gpdPackageName gpd) cabalfp
152+
let ret = (gpd, cabalfp)
153+
atomicModifyIORef' ref $ \(m1, m2) ->
154+
((m1, M.insert dir ret m2), ret)
150155
where
151156
toPretty :: String -> PWarning -> [Doc AnsiAnn]
152157
toPretty src (PWarning x) =
@@ -160,66 +165,57 @@ cachedCabalFileParse key cw getBS = do
160165
, flow msg
161166
]
162167

163-
-- | A helper function that performs the basic character encoding
164-
-- necessary.
165-
rawParseGPD :: BS.ByteString
166-
-> Either PError ([PWarning], GenericPackageDescription)
167-
rawParseGPD bs =
168-
case parseGenericPackageDescription chars of
169-
ParseFailed per -> Left per
170-
ParseOk warnings gpkg -> Right (warnings,gpkg)
171-
where
172-
chars = T.unpack (dropBOM (decodeUtf8With lenientDecode bs))
168+
-- | Check if the given name in the @Package@ matches the name of the .cabal file
169+
checkCabalFileName :: MonadThrow m => PackageName -> Path Abs File -> m ()
170+
checkCabalFileName name cabalfp = do
171+
-- Previously, we just use parsePackageNameFromFilePath. However, that can
172+
-- lead to confusing error messages. See:
173+
-- https://github.com/commercialhaskell/stack/issues/895
174+
let expected = packageNameString name ++ ".cabal"
175+
when (expected /= toFilePath (filename cabalfp))
176+
$ throwM $ MismatchedCabalName cabalfp name
173177

174-
-- https://github.com/haskell/hackage-server/issues/351
175-
dropBOM t = fromMaybe t $ T.stripPrefix "\xFEFF" t
178+
gpdPackageIdentifier :: GenericPackageDescription -> PackageIdentifier
179+
gpdPackageIdentifier = fromCabalPackageIdentifier . D.package . D.packageDescription
176180

177-
-- | Read the raw, unresolved package information from a file.
178-
readPackageUnresolved
179-
:: forall env. HasRunner env
180-
=> Path Abs File -- ^ cabal file location
181-
-> Bool -- ^ print warnings?
182-
-> RIO env GenericPackageDescription
183-
readPackageUnresolved cabalfp printWarnings = do
184-
gpd <- cachedCabalFileParse
185-
(Right cabalfp)
186-
(if printWarnings then CWPrint cabalfp else CWNoPrint)
187-
(liftIO (BS.readFile (FL.toFilePath cabalfp)))
188-
let PackageIdentifier name _version =
189-
fromCabalPackageIdentifier
190-
$ D.package
191-
$ D.packageDescription gpd
192-
checkCabalFileName name cabalfp
193-
return gpd
181+
gpdPackageName :: GenericPackageDescription -> PackageName
182+
gpdPackageName = packageIdentifierName . gpdPackageIdentifier
183+
184+
gpdVersion :: GenericPackageDescription -> Version
185+
gpdVersion = packageIdentifierVersion . gpdPackageIdentifier
194186

195187
-- | Read the 'GenericPackageDescription' from the given
196188
-- 'PackageIdentifierRevision'.
197-
readPackageUnresolvedFromIndex
189+
readPackageUnresolvedIndex
198190
:: forall env. HasRunner env
199191
=> (PackageIdentifierRevision -> IO ByteString) -- ^ load the raw bytes
200192
-> PackageIdentifierRevision
201193
-> RIO env GenericPackageDescription
202-
readPackageUnresolvedFromIndex loadFromIndex pir@(PackageIdentifierRevision pi' _) = do
203-
gpd <- cachedCabalFileParse
204-
(Left pir)
205-
CWNoPrint
206-
(liftIO $ loadFromIndex pir)
207-
let foundPI =
208-
fromCabalPackageIdentifier
209-
$ D.package
210-
$ D.packageDescription gpd
211-
unless (pi' == foundPI) $ throwM $ MismatchedCabalIdentifier pir foundPI
212-
return gpd
194+
readPackageUnresolvedIndex loadFromIndex pir@(PackageIdentifierRevision pi' _) = do
195+
ref <- view $ runnerL.to runnerParsedCabalFiles
196+
(m, _) <- readIORef ref
197+
case M.lookup pir m of
198+
Just gpd -> return gpd
199+
Nothing -> do
200+
bs <- liftIO $ loadFromIndex pir
201+
(_warnings, gpd) <- rawParseGPD (Left pir) bs
202+
let foundPI =
203+
fromCabalPackageIdentifier
204+
$ D.package
205+
$ D.packageDescription gpd
206+
unless (pi' == foundPI) $ throwM $ MismatchedCabalIdentifier pir foundPI
207+
atomicModifyIORef' ref $ \(m1, m2) ->
208+
((M.insert pir gpd m1, m2), gpd)
213209

214210
-- | Reads and exposes the package information
215-
readPackage
216-
:: forall env. HasRunner env
211+
readPackageDir
212+
:: forall env. HasConfig env
217213
=> PackageConfig
218-
-> Path Abs File
214+
-> Path Abs Dir
219215
-> Bool -- ^ print warnings from cabal file parsing?
220-
-> RIO env Package
221-
readPackage packageConfig cabalfp printWarnings =
222-
resolvePackage packageConfig <$> readPackageUnresolved cabalfp printWarnings
216+
-> RIO env (Package, Path Abs File)
217+
readPackageDir packageConfig dir printWarnings =
218+
first (resolvePackage packageConfig) <$> readPackageUnresolvedDir dir printWarnings
223219

224220
-- | Get 'GenericPackageDescription' and 'PackageDescription' reading info
225221
-- from given directory.
@@ -230,8 +226,7 @@ readPackageDescriptionDir
230226
-> Bool -- ^ print warnings?
231227
-> RIO env (GenericPackageDescription, PackageDescriptionPair)
232228
readPackageDescriptionDir config pkgDir printWarnings = do
233-
cabalfp <- findOrGenerateCabalFile pkgDir
234-
gdesc <- readPackageUnresolved cabalfp printWarnings
229+
(gdesc, _) <- readPackageUnresolvedDir pkgDir printWarnings
235230
return (gdesc, resolvePackageDescription config gdesc)
236231

237232
-- | Read @<package>.buildinfo@ ancillary files produced by some Setup.hs hooks.
@@ -245,16 +240,6 @@ readDotBuildinfo :: MonadIO m
245240
readDotBuildinfo buildinfofp =
246241
liftIO $ readHookedBuildInfo D.silent (toFilePath buildinfofp)
247242

248-
-- | Check if the given name in the @Package@ matches the name of the .cabal file
249-
checkCabalFileName :: MonadThrow m => PackageName -> Path Abs File -> m ()
250-
checkCabalFileName name cabalfp = do
251-
-- Previously, we just use parsePackageNameFromFilePath. However, that can
252-
-- lead to confusing error messages. See:
253-
-- https://github.com/commercialhaskell/stack/issues/895
254-
let expected = packageNameString name ++ ".cabal"
255-
when (expected /= toFilePath (filename cabalfp))
256-
$ throwM $ MismatchedCabalName cabalfp name
257-
258243
-- | Resolve a parsed cabal file into a 'Package', which contains all of
259244
-- the info needed for stack to build the 'Package' given the current
260245
-- configuration.
@@ -1345,7 +1330,7 @@ findOrGenerateCabalFile pkgDir = do
13451330
-- | Generate .cabal file from package.yaml, if necessary.
13461331
hpack :: (MonadIO m, MonadUnliftIO m, MonadLogger m, HasRunner env, HasConfig env, MonadReader env m)
13471332
=> Path Abs Dir -> m ()
1348-
hpack pkgDir = don'tHpackTwice $ do
1333+
hpack pkgDir = do
13491334
let hpackFile = pkgDir </> $(mkRelFile Hpack.packageConfig)
13501335
exists <- liftIO $ doesFileExist hpackFile
13511336
when exists $ do
@@ -1378,14 +1363,6 @@ hpack pkgDir = don'tHpackTwice $ do
13781363
envOverride <- getMinimalEnvOverride
13791364
let cmd = Cmd (Just pkgDir) command envOverride []
13801365
runCmd cmd Nothing
1381-
where
1382-
don'tHpackTwice inner = do
1383-
ref <- view $ runnerL.to runnerHpackRun
1384-
let fp = toFilePath pkgDir
1385-
join $ atomicModifyIORef' ref $ \hs ->
1386-
if fp `HashSet.member` hs
1387-
then (hs, return ())
1388-
else (HashSet.insert fp hs, inner)
13891366

13901367
-- | Path for the package's build log.
13911368
buildLogPath :: (MonadReader env m, HasBuildConfig env, MonadThrow m)

src/Stack/PackageLocation.hs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ parseSingleCabalFileIndex
239239
-- Need special handling of PLIndex for efficiency (just read from the
240240
-- index tarball) and correctness (get the cabal file from the index,
241241
-- not the package tarball itself, yay Hackage revisions).
242-
parseSingleCabalFileIndex loadFromIndex _ (PLIndex pir) = readPackageUnresolvedFromIndex loadFromIndex pir
242+
parseSingleCabalFileIndex loadFromIndex _ (PLIndex pir) = readPackageUnresolvedIndex loadFromIndex pir
243243
parseSingleCabalFileIndex _ root (PLOther loc) = lpvGPD <$> parseSingleCabalFile root False loc
244244

245245
parseSingleCabalFile
@@ -250,8 +250,7 @@ parseSingleCabalFile
250250
-> RIO env LocalPackageView
251251
parseSingleCabalFile root printWarnings loc = do
252252
dir <- resolveSinglePackageLocation root loc
253-
cabalfp <- findOrGenerateCabalFile dir
254-
gpd <- readPackageUnresolved cabalfp printWarnings
253+
(gpd, cabalfp) <- readPackageUnresolvedDir dir printWarnings
255254
return LocalPackageView
256255
{ lpvCabalFP = cabalfp
257256
, lpvGPD = gpd
@@ -268,8 +267,7 @@ parseMultiCabalFiles
268267
parseMultiCabalFiles root printWarnings loc0 =
269268
resolveMultiPackageLocation root loc0 >>=
270269
mapM (\(dir, loc1) -> do
271-
cabalfp <- findOrGenerateCabalFile dir
272-
gpd <- readPackageUnresolved cabalfp printWarnings
270+
(gpd, cabalfp) <- readPackageUnresolvedDir dir printWarnings
273271
return LocalPackageView
274272
{ lpvCabalFP = cabalfp
275273
, lpvGPD = gpd
@@ -285,7 +283,7 @@ parseMultiCabalFilesIndex
285283
-> RIO env [(GenericPackageDescription, PackageLocationIndex FilePath)]
286284
parseMultiCabalFilesIndex loadFromIndex _root (PLIndex pir) =
287285
(pure . (, PLIndex pir)) <$>
288-
readPackageUnresolvedFromIndex loadFromIndex pir
286+
readPackageUnresolvedIndex loadFromIndex pir
289287
parseMultiCabalFilesIndex _ root (PLOther loc0) =
290288
map (\lpv -> (lpvGPD lpv, PLOther $ lpvLoc lpv)) <$>
291289
parseMultiCabalFiles root False loc0

0 commit comments

Comments
 (0)