@@ -14,6 +14,7 @@ import Registry.App.Prelude
1414
1515import Data.Array as Array
1616import Data.Array.NonEmpty as NonEmptyArray
17+ import Data.Foldable (elem , foldM )
1718import Data.FoldableWithIndex (foldMapWithIndex )
1819import Data.Map as Map
1920import Data.Set as Set
@@ -182,80 +183,98 @@ type MatrixSolverResult =
182183 }
183184
184185solveForAllCompilers :: forall r . MatrixSolverData -> Run (AFF + EXCEPT String + LOG + r ) (Set MatrixSolverResult )
185- solveForAllCompilers { compilerIndex, name, version, compiler, dependencies } = do
186+ solveForAllCompilers solverData@{ compiler } = do
186187 -- remove the compiler we tested with from the set of all of them
187188 compilers <- (Array .filter (_ /= compiler) <<< NonEmptyArray .toArray) <$> PursVersions .pursVersions
188- newJobs <- for compilers \target -> do
189- Log .debug $ " Trying compiler " <> Version .print target <> " for package " <> PackageName .print name
190- case Solver .solveWithCompiler (Range .exact target) compilerIndex dependencies of
191- Left solverErrors -> do
192- Log .info $ " Failed to solve with compiler " <> Version .print target <> " : " <> PackageName .print name <> " @" <> Version .print version
193- Log .debug $ " Solver errors:\n " <> foldMapWithIndex
194- (\i error -> " [Error " <> show (i + 1 ) <> " ]\n " <> Solver .printSolverError error <> " \n " )
195- solverErrors
196- pure Nothing
197- Right (Tuple solvedCompiler resolutions) -> case solvedCompiler == target of
198- true -> do
199- Log .debug $ " Solved with compiler " <> Version .print solvedCompiler
200- pure $ Just { compiler: target, resolutions, name, version }
201- false -> do
189+ newJobs <- for compilers \target ->
190+ trySolveForCompiler (solverData { compiler = target })
191+ pure $ Set .fromFoldable $ Array .catMaybes newJobs
192+
193+ solveDependantsForCompiler :: forall r . MatrixSolverData -> Run (EXCEPT String + LOG + REGISTRY + r ) (Set MatrixSolverResult )
194+ solveDependantsForCompiler { compilerIndex, name, version, compiler } = do
195+ manifestIndex <- Registry .readAllManifests
196+ let seed = Tuple name version
197+ { results, visited } <- go manifestIndex (Set .singleton seed) name version
198+ Log .info $ Array .fold
199+ [ " Cascade from "
200+ , PackageName .print name
201+ , " @"
202+ , Version .print version
203+ , " : "
204+ , show (Set .size results)
205+ , " enqueued out of "
206+ , show (Set .size visited - 1 )
207+ , " dependants visited"
208+ ]
209+ pure results
210+ where
211+ -- Recursively find packages to enqueue. Trivially this includes direct
212+ -- dependants, but we need more than that: when a direct dependant is already
213+ -- compatible with the target compiler, recurse down to its own dependants,
214+ -- and so on.
215+ -- This handles niche cases of transitive version-conflict cascades:
216+ -- if A depends on B which depends on C (wide range), and A's full plan forces
217+ -- C@new (because of other packages) but all versions of B already compiled
218+ -- against C@old, then - if we only propagated direct dependents - B will
219+ -- never be retriggered.
220+ -- With this recursive propagation, when C@new completes we cascade through
221+ -- B (already compiled) and reach A, allowing for a plan to resolve.
222+ go manifestIndex visited pkgName pkgVersion = do
223+ let dependentManifests = ManifestIndex .dependants manifestIndex pkgName pkgVersion
224+ foldM (processManifest manifestIndex) { visited, results: Set .empty } dependentManifests
225+
226+ processManifest manifestIndex acc (Manifest manifest) = do
227+ let pv = Tuple manifest.name manifest.version
228+ if Set .member pv acc.visited then
229+ pure acc
230+ else do
231+ let newVisited = Set .insert pv acc.visited
232+ Registry .readMetadata manifest.name >>= case _ of
233+ Nothing -> do
234+ Log .warn $ " No metadata for dependant " <> PackageName .print manifest.name <> " , skipping"
235+ pure { visited: newVisited, results: acc.results }
236+ Just metadata ->
237+ case Map .lookup manifest.version (un Metadata metadata).published of
238+ Nothing -> do
239+ Log .warn $ " Dependant " <> PackageName .print manifest.name <> " @" <> Version .print manifest.version <> " not in metadata.published, skipping"
240+ pure { visited: newVisited, results: acc.results }
241+ Just { compilers }
242+ | elem compiler compilers -> do
243+ -- Already has compiler: propagate through to find stranded packages
244+ sub <- go manifestIndex newVisited manifest.name manifest.version
245+ pure { visited: sub.visited, results: acc.results <> sub.results }
246+ | otherwise -> do
247+ result <- trySolveForCompiler { compilerIndex, compiler, name: manifest.name, version: manifest.version, dependencies: manifest.dependencies }
248+ pure case result of
249+ Nothing -> { visited: newVisited, results: acc.results }
250+ Just entry -> { visited: newVisited, results: Set .insert entry acc.results }
251+
252+ -- | Try to solve a package's dependencies for a specific compiler. Returns
253+ -- | the solver result if the produced build plan targets the expected compiler,
254+ -- | Nothing otherwise (solver failure or compiler mismatch).
255+ trySolveForCompiler :: forall r . MatrixSolverData -> Run (LOG + r ) (Maybe MatrixSolverResult )
256+ trySolveForCompiler { compilerIndex, compiler, name, version, dependencies } = do
257+ Log .debug $ " Trying compiler " <> Version .print compiler <> " for package " <> PackageName .print name
258+ case Solver .solveWithCompiler (Range .exact compiler) compilerIndex dependencies of
259+ Left solverErrors -> do
260+ Log .info $ " Failed to solve with compiler " <> Version .print compiler <> " : " <> PackageName .print name <> " @" <> Version .print version
261+ Log .debug $ " Solver errors:\n " <> foldMapWithIndex
262+ (\i error -> " [Error " <> show (i + 1 ) <> " ]\n " <> Solver .printSolverError error <> " \n " )
263+ solverErrors
264+ pure Nothing
265+ Right (Tuple solvedCompiler resolutions)
266+ | solvedCompiler == compiler -> do
267+ Log .debug $ " Solved " <> PackageName .print name <> " @" <> Version .print version <> " with compiler " <> Version .print solvedCompiler
268+ pure $ Just { compiler, resolutions, name, version }
269+ | otherwise -> do
202270 Log .debug $ Array .fold
203271 [ " Produced a compiler-derived build plan that selects a compiler ("
204272 , Version .print solvedCompiler
205273 , " ) that differs from the target compiler ("
206- , Version .print target
274+ , Version .print compiler
207275 , " )."
208276 ]
209277 pure Nothing
210- pure $ Set .fromFoldable $ Array .catMaybes newJobs
211-
212- solveDependantsForCompiler :: forall r . MatrixSolverData -> Run (EXCEPT String + LOG + REGISTRY + r ) (Set MatrixSolverResult )
213- solveDependantsForCompiler { compilerIndex, name, version, compiler } = do
214- manifestIndex <- Registry .readAllManifests
215- let dependentManifests = ManifestIndex .dependants manifestIndex name version
216- newJobs <- for dependentManifests \(Manifest manifest) -> do
217- -- We skip if this compiler is already in the package's metadata compilers
218- -- list (meaning it was already successfully tested). Failed compilations
219- -- are not recorded in metadata, but the DB deduplication in insertMatrixJob
220- -- prevents re-enqueuing jobs that already exist.
221- shouldAttemptToCompile <- Registry .readMetadata manifest.name >>= case _ of
222- Nothing -> do
223- Log .debug $ " Skipping " <> PackageName .print manifest.name <> " @" <> Version .print manifest.version <> " : no metadata found"
224- pure false
225- Just metadata -> do
226- let
227- result = case Map .lookup manifest.version (un Metadata metadata).published of
228- Nothing -> false
229- Just { compilers } -> all (_ /= compiler) compilers
230- unless result do
231- Log .debug $ " Skipping " <> PackageName .print manifest.name <> " @" <> Version .print manifest.version <> " : compiler " <> Version .print compiler <> " already tested or version not published"
232- pure result
233- case shouldAttemptToCompile of
234- false -> pure Nothing
235- true -> do
236- -- if all good then run the solver
237- Log .debug $ " Trying compiler " <> Version .print compiler <> " for package " <> PackageName .print manifest.name
238- case Solver .solveWithCompiler (Range .exact compiler) compilerIndex manifest.dependencies of
239- Left solverErrors -> do
240- Log .info $ " Failed to solve with compiler " <> Version .print compiler <> " : " <> PackageName .print manifest.name <> " @" <> Version .print manifest.version
241- Log .debug $ " Solver errors:\n " <> foldMapWithIndex
242- (\i error -> " [Error " <> show (i + 1 ) <> " ]\n " <> Solver .printSolverError error <> " \n " )
243- solverErrors
244- pure Nothing
245- Right (Tuple solvedCompiler resolutions) -> case compiler == solvedCompiler of
246- true -> do
247- Log .debug $ " Solved " <> PackageName .print manifest.name <> " @" <> Version .print manifest.version <> " with compiler " <> Version .print solvedCompiler
248- pure $ Just { compiler, resolutions, name: manifest.name, version: manifest.version }
249- false -> do
250- Log .debug $ Array .fold
251- [ " Produced a compiler-derived build plan that selects a compiler ("
252- , Version .print solvedCompiler
253- , " ) that differs from the target compiler ("
254- , Version .print compiler
255- , " )."
256- ]
257- pure Nothing
258- pure $ Set .fromFoldable $ Array .catMaybes newJobs
259278
260279checkIfNewCompiler :: forall r . Run (EXCEPT String + LOG + REGISTRY + AFF + r ) (Maybe Version )
261280checkIfNewCompiler = do
0 commit comments