@@ -22,8 +22,10 @@ import Data.Array.NonEmpty as NEA
2222import Data.Codec.JSON as CJ
2323import Data.Codec.JSON.Common as CJ.Common
2424import Data.Either as Either
25+ import Data.Filterable (filterMap )
2526import Data.HTTP.Method as Method
2627import Data.Int as Int
28+ import Data.List as List
2729import Data.Map as Map
2830import Data.Newtype (wrap )
2931import Data.Set as Set
@@ -279,16 +281,40 @@ type LockfileBuilderResult =
279281 , packages :: Map PackageName Lock.LockEntry
280282 }
281283
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+
282290writeNewLockfile :: forall a . String -> PackageTransitiveDeps -> Spago (FetchEnv a ) PackageTransitiveDeps
283291writeNewLockfile reason allTransitiveDeps = do
284292 logInfo $ reason <> " , generating it..."
285293 { 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
286299 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
291314 let
315+ getDeps = (Array .fromFoldable <<< Map .keys <<< fromMaybe Map .empty)
316+ <$> memoisedGetPackageDependencies dependencyName dependencyPackage
317+
292318 updatePackage r package = (updateWorkspacePackage r)
293319 { packages = Map .insert dependencyName package r.packages }
294320 updateWorkspacePackage r = r
@@ -303,28 +329,84 @@ writeNewLockfile reason allTransitiveDeps = do
303329
304330 case dependencyPackage of
305331 WorkspacePackage _pkg -> pure $ updateWorkspacePackage result
332+
306333 GitPackage gitPackage -> do
307334 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+
311349 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+
318363 LocalPackage { path } -> do
319- pure $ updatePackage result $ FromPath { path, dependencies: packageDependencies }
364+ dependencies <- getDeps
365+ pure $ updatePackage result $ FromPath { path, dependencies }
320366
321367 let
322368 toArray :: forall k v . Map k v -> Array (Tuple k v )
323369 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+
324402 ({ 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
328410
329411 let
330412 lockfile =
0 commit comments