@@ -22,8 +22,10 @@ import Data.Array.NonEmpty as NEA
22
22
import Data.Codec.JSON as CJ
23
23
import Data.Codec.JSON.Common as CJ.Common
24
24
import Data.Either as Either
25
+ import Data.Filterable (filterMap )
25
26
import Data.HTTP.Method as Method
26
27
import Data.Int as Int
28
+ import Data.List as List
27
29
import Data.Map as Map
28
30
import Data.Newtype (wrap )
29
31
import Data.Set as Set
@@ -279,16 +281,40 @@ type LockfileBuilderResult =
279
281
, packages :: Map PackageName Lock.LockEntry
280
282
}
281
283
284
+ lookupInCache :: forall a k v . Ord k => k -> Ref.Ref (Map k v ) -> Spago a (Maybe v )
285
+ lookupInCache key cacheRef = liftEffect $ Ref .read cacheRef >>= Map .lookup key >>> pure
286
+
287
+ updateCache :: forall a k v . Ord k => k -> v -> Ref.Ref (Map k v ) -> Spago a Unit
288
+ updateCache key value cacheRef = liftEffect $ Ref .modify_ (Map .insert key value) cacheRef
289
+
282
290
writeNewLockfile :: forall a . String -> PackageTransitiveDeps -> Spago (FetchEnv a ) PackageTransitiveDeps
283
291
writeNewLockfile reason allTransitiveDeps = do
284
292
logInfo $ reason <> " , generating it..."
285
293
{ workspace } <- ask
294
+
295
+ -- All these Refs are needed to memoise Db and file reads
296
+ packageDependenciesCache <- liftEffect $ Ref .new Map .empty
297
+ gitRefCache <- liftEffect $ Ref .new Map .empty
298
+ metadataRefCache <- liftEffect $ Ref .new Map .empty
286
299
let
287
- processPackage :: LockfileBuilderResult -> Tuple PackageName (Tuple PackageName Package ) -> Spago (FetchEnv a ) LockfileBuilderResult
288
- processPackage result (Tuple workspacePackageName (Tuple dependencyName dependencyPackage)) = do
289
- (packageDependencies :: Array PackageName ) <- (Array .fromFoldable <<< Map .keys <<< fromMaybe Map .empty)
290
- <$> getPackageDependencies dependencyName dependencyPackage
300
+ memoisedGetPackageDependencies :: PackageName -> Package -> Spago (FetchEnv a ) (Maybe (Map PackageName Range ))
301
+ memoisedGetPackageDependencies packageName package = do
302
+ lookupInCache packageName packageDependenciesCache >>=
303
+ case _ of
304
+ Just cached -> do
305
+ pure cached
306
+ Nothing -> do
307
+ -- Not cached. Compute it, write to ref, return it
308
+ res <- getPackageDependencies packageName package
309
+ updateCache packageName res packageDependenciesCache
310
+ pure res
311
+
312
+ processPackage :: Map PackageName _ -> LockfileBuilderResult -> Tuple PackageName (Tuple PackageName Package ) -> Spago (FetchEnv a ) LockfileBuilderResult
313
+ processPackage registryIntegrityMap result (Tuple workspacePackageName (Tuple dependencyName dependencyPackage)) = do
291
314
let
315
+ getDeps = (Array .fromFoldable <<< Map .keys <<< fromMaybe Map .empty)
316
+ <$> memoisedGetPackageDependencies dependencyName dependencyPackage
317
+
292
318
updatePackage r package = (updateWorkspacePackage r)
293
319
{ packages = Map .insert dependencyName package r.packages }
294
320
updateWorkspacePackage r = r
@@ -303,28 +329,84 @@ writeNewLockfile reason allTransitiveDeps = do
303
329
304
330
case dependencyPackage of
305
331
WorkspacePackage _pkg -> pure $ updateWorkspacePackage result
332
+
306
333
GitPackage gitPackage -> do
307
334
let packageLocation = Config .getPackageLocation dependencyName dependencyPackage
308
- Git .getRef (Just packageLocation) >>= case _ of
309
- Left err -> die err -- TODO maybe not die here?
310
- Right rev -> pure $ updatePackage result $ FromGit { rev, dependencies: packageDependencies, url: gitPackage.git, subdir: gitPackage.subdir }
335
+ lookupInCache packageLocation gitRefCache >>= case _ of
336
+ Nothing ->
337
+ -- Get the ref and update the cache
338
+ Git .getRef (Just packageLocation) >>= case _ of
339
+ Left err -> die err -- TODO maybe not die here?
340
+ Right rev -> do
341
+ dependencies <- getDeps
342
+ let
343
+ lockEntry =
344
+ FromGit { rev, dependencies, url: gitPackage.git, subdir: gitPackage.subdir }
345
+ updateCache packageLocation lockEntry gitRefCache
346
+ pure $ updatePackage result lockEntry
347
+ Just entry -> pure $ updatePackage result entry
348
+
311
349
RegistryVersion version -> do
312
- metadata <- Registry .getMetadata dependencyName
313
- registryVersion <- case (metadata >>= (\(Metadata meta) -> Either .note " Didn't find version in the metadata file" $ Map .lookup version meta.published)) of
314
- Left err -> die $ " Couldn't read metadata, reason:\n " <> err
315
- Right { hash: integrity } ->
316
- pure { version, integrity, dependencies: packageDependencies }
317
- pure $ updatePackage result $ FromRegistry registryVersion
350
+ lookupInCache dependencyName metadataRefCache >>= case _ of
351
+ Nothing -> do
352
+ registryVersion <- FromRegistry <$> case Map .lookup dependencyName registryIntegrityMap of
353
+ -- This shouldn't be Nothing because it's already handled when building the integrity map below
354
+ Nothing -> die $ " Couldn't read metadata"
355
+ Just integrity -> do
356
+ dependencies <- getDeps
357
+ pure { version, integrity, dependencies }
358
+ updateCache dependencyName registryVersion metadataRefCache
359
+ pure $ updatePackage result registryVersion
360
+ Just entry -> do
361
+ pure $ updatePackage result entry
362
+
318
363
LocalPackage { path } -> do
319
- pure $ updatePackage result $ FromPath { path, dependencies: packageDependencies }
364
+ dependencies <- getDeps
365
+ pure $ updatePackage result $ FromPath { path, dependencies }
320
366
321
367
let
322
368
toArray :: forall k v . Map k v -> Array (Tuple k v )
323
369
toArray = Map .toUnfoldable
370
+ allDependencies = foldMap sequence $ toArray $ map toArray allTransitiveDeps
371
+
372
+ -- Fetch the Registry metadata in one go for all required packages
373
+ let
374
+ uniqueRegistryPackageNames = Array .nub $ filterMap
375
+ ( \(Tuple _ (Tuple dependencyName dependencyPackage)) -> case dependencyPackage of
376
+ RegistryVersion _ -> Just dependencyName
377
+ _ -> Nothing
378
+ )
379
+ allDependencies
380
+ metadataMap <- Registry .getMetadataForPackages uniqueRegistryPackageNames >>= case _ of
381
+ Left err -> die $ " Couldn't read metadata, reason:\n " <> err
382
+ Right ms -> pure ms
383
+
384
+ (registryVersions :: Map PackageName Sha256 ) <- Map .fromFoldable <<< Array .catMaybes <$>
385
+ ( traverse
386
+ ( \(Tuple _ (Tuple dependencyName dependencyPackage)) -> case dependencyPackage of
387
+ RegistryVersion version -> do
388
+ let metadata = Map .lookup dependencyName metadataMap
389
+ case (metadata >>= (\(Metadata meta) -> Map .lookup version meta.published)) of
390
+ Nothing | isNothing metadata ->
391
+ die $ " Couldn't read metadata for " <> PackageName .print dependencyName
392
+ Nothing ->
393
+ die $ " Couldn't read metadata for " <> PackageName .print dependencyName
394
+ <> " : didn't find version in the metadata file"
395
+ Just { hash: integrity } ->
396
+ pure $ Just $ dependencyName /\ integrity
397
+ _ -> pure Nothing
398
+ )
399
+ $ allDependencies
400
+ )
401
+
324
402
({ packages, workspacePackages } :: LockfileBuilderResult ) <-
325
- Array .foldM processPackage
326
- { workspacePackages: Map .fromFoldable $ map Config .workspacePackageToLockfilePackage (Config .getWorkspacePackages workspace.packageSet), packages: Map .empty }
327
- (foldMap sequence $ toArray $ map toArray allTransitiveDeps)
403
+ -- NOTE! We used to have `Array.foldM` here, but it was significantly slower
404
+ -- (~10ms vs 6s on a very large project)
405
+ List .foldM (processPackage registryVersions)
406
+ { workspacePackages: Map .fromFoldable $ map Config .workspacePackageToLockfilePackage (Config .getWorkspacePackages workspace.packageSet)
407
+ , packages: Map .empty
408
+ }
409
+ $ List .fromFoldable allDependencies
328
410
329
411
let
330
412
lockfile =
0 commit comments