Skip to content

Commit 92d014a

Browse files
authored
compiler-versions script: Compute supported compiler versions for all packages (#639)
1 parent 38fc5d5 commit 92d014a

File tree

8 files changed

+199
-45
lines changed

8 files changed

+199
-45
lines changed

app/src/App/CLI/Purs.purs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,15 @@ printCompilerErrors errors = do
7272
String.joinWith "\n" printed
7373
where
7474
printCompilerError :: CompilerError -> String
75-
printCompilerError { moduleName, filename, message, errorLink } =
75+
printCompilerError { moduleName, filename, message, errorLink, position } =
7676
String.joinWith "\n"
77-
[ foldMap (\name -> " Module: " <> name <> "\n") moduleName <> " File: " <> filename
77+
[ foldMap (\name -> " Module: " <> name <> "\n") moduleName <> " File: " <> filename <> "\n"
7878
, " Message:"
7979
, ""
80-
, " " <> message
80+
, message
81+
-- The message has a newline, so no need for another.
82+
, " Position:"
83+
, " " <> show position.startLine <> ":" <> show position.startColumn <> " - " <> show position.endLine <> ":" <> show position.endColumn
8184
, ""
8285
, " Error details:"
8386
, " " <> errorLink

app/src/App/CLI/PursVersions.purs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Run as Run
1212
import Run.Except (EXCEPT)
1313
import Run.Except as Except
1414

15+
-- | Returns a sorted array of PureScript compilers supported by the Registry
1516
pursVersions :: forall r. Run (EXCEPT String + AFF + r) (NonEmptyArray Version)
1617
pursVersions = do
1718
result <- Run.liftAff $ _.result =<< Execa.execa "purs-versions" [] identity
@@ -23,4 +24,4 @@ pursVersions = do
2324

2425
case NEA.fromArray success of
2526
Nothing -> Except.throw "No purs versions"
26-
Just arr -> pure arr
27+
Just arr -> pure $ NEA.sort arr

app/src/App/Effect/PackageSets.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ handle env = case _ of
126126
index <- Registry.readAllManifests
127127

128128
let
129-
sortedPackages = ManifestIndex.toSortedArray index
129+
sortedPackages = ManifestIndex.toSortedArray ManifestIndex.IgnoreRanges index
130130
sortedBatch = sortedPackages # Array.mapMaybe \(Manifest { name, version }) -> do
131131
update <- Map.lookup name changes
132132
case update of

app/test/App/CLI/Purs.purs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ spec = do
4646
FS.Aff.writeTextFile UTF8 file "<contents>"
4747
result <- Purs.callCompiler { command: Purs.Compile { globs: [ file ] }, cwd: Nothing, version }
4848
case result of
49-
Left (CompilationError [ { position: { startLine: 1, startColumn: 1 } } ]) -> pure unit
49+
Left (CompilationError [ { position: { startLine: 1, startColumn: 1 } } ]) ->
50+
pure unit
5051
_ -> Assert.fail "Should have failed with CompilationError"

lib/src/ManifestIndex.purs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module Registry.ManifestIndex
2323
, toMap
2424
, toSortedArray
2525
, topologicalSort
26+
, IncludeRanges(..)
2627
, writeEntryFile
2728
) where
2829

@@ -68,6 +69,7 @@ import Registry.Manifest as Manifest
6869
import Registry.PackageName (PackageName)
6970
import Registry.PackageName as PackageName
7071
import Registry.Range (Range)
72+
import Registry.Range as Range
7173
import Registry.Version (Version)
7274

7375
-- | An index of package manifests, keyed by package name and version. The index
@@ -86,8 +88,8 @@ toMap :: ManifestIndex -> Map PackageName (Map Version Manifest)
8688
toMap (ManifestIndex index) = index
8789

8890
-- | Produce an array of manifests topologically sorted by dependencies.
89-
toSortedArray :: ManifestIndex -> Array Manifest
90-
toSortedArray (ManifestIndex index) = topologicalSort $ Set.fromFoldable do
91+
toSortedArray :: IncludeRanges -> ManifestIndex -> Array Manifest
92+
toSortedArray includeRanges (ManifestIndex index) = topologicalSort includeRanges $ Set.fromFoldable do
9193
Tuple _ versions <- Map.toUnfoldableUnordered index
9294
Tuple _ manifest <- Map.toUnfoldableUnordered versions
9395
[ manifest ]
@@ -163,12 +165,16 @@ maximalIndex manifests = do
163165
Left errors -> Tuple (Map.insertWith Map.union name (Map.singleton version errors) failed) index
164166
Right newIndex -> Tuple failed newIndex
165167

166-
Array.foldl insertManifest (Tuple Map.empty empty) (topologicalSort manifests)
168+
Array.foldl insertManifest (Tuple Map.empty empty) (topologicalSort IgnoreRanges manifests)
169+
170+
data IncludeRanges
171+
= ConsiderRanges
172+
| IgnoreRanges
167173

168174
-- | Topologically sort a set of manifests so that each manifest in the array
169175
-- | depends only on package versions that have already been encountered.
170-
topologicalSort :: Set Manifest -> Array Manifest
171-
topologicalSort manifests =
176+
topologicalSort :: IncludeRanges -> Set Manifest -> Array Manifest
177+
topologicalSort includeRanges manifests =
172178
Array.fromFoldable
173179
$ List.reverse
174180
$ List.mapMaybe (flip Graph.lookup graph)
@@ -191,16 +197,13 @@ topologicalSort manifests =
191197
resolveDependencies :: Manifest -> Tuple (Tuple PackageName Version) (Tuple Manifest (List (Tuple PackageName Version)))
192198
resolveDependencies manifest@(Manifest { name, version, dependencies }) =
193199
Tuple (Tuple name version) $ Tuple manifest $ List.fromFoldable do
194-
Tuple dependency _ <- Map.toUnfoldable dependencies
200+
Tuple dependency range <- Map.toUnfoldable dependencies
195201
-- This case should not be possible: it means that the manifest indicates
196202
-- a dependency that does not exist at all. (TODO: Explain)
197203
let versions = Maybe.fromMaybe [] $ Map.lookup dependency allPackageVersions
198-
-- Technically, we should restrict the sort to only apply to package
199-
-- versions admitted by the given range. This is faster and correct, but
200-
-- fails in the case where we want to produce a maximal index while
201-
-- ignoring version bounds.
202-
-- included <- Array.filter (Range.includes range) versions
203-
included <- versions
204+
included <- case includeRanges of
205+
ConsiderRanges -> Array.filter (Range.includes range) versions
206+
IgnoreRanges -> versions
204207
[ Tuple dependency included ]
205208

206209
-- | Calculate the directory containing this package in the registry index,

lib/test/Registry/ManifestIndex.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ testIndex { satisfied, unsatisfied } = case ManifestIndex.maximalIndex (Set.from
201201

202202
testSorted :: forall m. MonadThrow Error m => Array Manifest -> m Unit
203203
testSorted input = do
204-
let sorted = ManifestIndex.topologicalSort (Set.fromFoldable input)
204+
let sorted = ManifestIndex.topologicalSort ManifestIndex.IgnoreRanges (Set.fromFoldable input)
205205
unless (input == sorted) do
206206
Assert.fail $ String.joinWith "\n"
207207
[ Argonaut.stringifyWithIndent 2 $ CA.encode (CA.array manifestCodec') input

scripts/src/CompilerVersions.purs

Lines changed: 171 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ import ArgParse.Basic (ArgParser)
66
import ArgParse.Basic as Arg
77
import Data.Array as Array
88
import Data.Array.NonEmpty as NEA
9+
import Data.Codec.Argonaut as CA
10+
import Data.Codec.Argonaut.Record as CA.Record
11+
import Data.Codec.Argonaut.Variant as CA.Variant
912
import Data.Formatter.DateTime as Formatter.DateTime
1013
import Data.Map as Map
14+
import Data.Maybe as Maybe
15+
import Data.Profunctor as Profunctor
16+
import Data.Semigroup.Foldable as Semigroup.Foldable
1117
import Data.String as String
12-
import Data.Tuple (uncurry)
18+
import Data.Variant as Variant
1319
import Effect.Class.Console as Console
1420
import Node.FS.Aff as FS.Aff
1521
import Node.Path as Path
@@ -30,33 +36,42 @@ import Registry.App.Effect.Storage as Storage
3036
import Registry.Foreign.FSExtra as FS.Extra
3137
import Registry.Foreign.Octokit as Octokit
3238
import Registry.Foreign.Tmp as Tmp
39+
import Registry.Internal.Codec as Internal.Codec
3340
import Registry.Internal.Format as Internal.Format
3441
import Registry.Manifest (Manifest(..))
42+
import Registry.Manifest as Manifest
3543
import Registry.ManifestIndex as ManifestIndex
3644
import Registry.PackageName as PackageName
45+
import Registry.Solver (DependencyIndex)
46+
import Registry.Solver as Solver
3747
import Registry.Version as Version
3848
import Run (AFF, EFFECT, Run)
3949
import Run as Run
4050
import Run.Except (EXCEPT)
4151
import Run.Except as Except
4252

43-
data InputMode
44-
= File FilePath
45-
| Package PackageName Version
46-
| AllPackages
47-
48-
parser :: ArgParser InputMode
49-
parser = Arg.choose "input (--file or --package or --all)"
50-
[ Arg.argument [ "--file" ]
51-
"""Compute supported compiler versions for packages from a JSON file like: [ "prelude", "console" ]"""
52-
# Arg.unformat "FILE_PATH" pure
53-
# map File
54-
, Arg.argument [ "--package" ]
55-
"Compute supported compiler versions for the indicated package"
56-
# Arg.unformat "NAME@VERSION" parsePackage
57-
# map (uncurry Package)
58-
, Arg.flag [ "--all" ] "Compute supported compiler versions for all packages" $> AllPackages
59-
]
53+
type Arguments =
54+
{ package :: Maybe (Tuple PackageName Version)
55+
, compiler :: Maybe Version
56+
}
57+
58+
parser :: ArgParser Arguments
59+
parser = Arg.fromRecord
60+
{ package: Arg.choose "input (--all-packages or --package)"
61+
[ Arg.flag [ "--all-packages" ] "Check compiler versions for all packages" $> Nothing
62+
, Arg.argument [ "--package" ]
63+
"Check compiler versions for specific package"
64+
# Arg.unformat "NAME@VERSION" parsePackage
65+
# map Just
66+
]
67+
, compiler: Arg.choose "input (--all-compilers or --compiler)"
68+
[ Arg.flag [ "--all-compilers" ] "Check all compiler versions" $> Nothing
69+
, Arg.argument [ "--compiler" ]
70+
"Check compiler versions for specific package"
71+
# Arg.unformat "VERSION" Version.parse
72+
# map Just
73+
]
74+
}
6075
where
6176
parsePackage :: String -> Either String (Tuple PackageName Version)
6277
parsePackage input = do
@@ -123,13 +138,21 @@ main = launchAff_ do
123138
>>> Log.interpret (\log -> Log.handleTerminal Normal log *> Log.handleFs Verbose logPath log)
124139
>>> Run.runBaseAff'
125140

126-
case arguments of
127-
File _ -> Console.log "Unsupported at this time." *> liftEffect (Process.exit 1)
128-
Package package version -> interpret $ determineCompilerVersionsForPackage package version
129-
AllPackages -> Console.log "Unsupported at this time." *> liftEffect (Process.exit 1)
141+
case arguments.package of
142+
Just (Tuple package version) -> interpret $ determineCompilerVersionsForPackage package version arguments.compiler
143+
Nothing -> do
144+
{ failures, results } <- interpret $ determineAllCompilerVersions arguments.compiler
145+
let resultsDir = Path.concat [ scratchDir, "results" ]
146+
FS.Extra.ensureDirectory resultsDir
147+
let
148+
resultsFile = "compiler-versions-results-" <> String.take 19 (Formatter.DateTime.format Internal.Format.iso8601DateTime now) <> ".json"
149+
failuresFile = "compiler-versions-failures-" <> String.take 19 (Formatter.DateTime.format Internal.Format.iso8601DateTime now) <> ".json"
150+
151+
writeJsonFile (Internal.Codec.packageMap (Internal.Codec.versionMap (CA.array Version.codec))) (Path.concat [ resultsDir, resultsFile ]) results
152+
writeJsonFile (Internal.Codec.versionMap (CA.array failureCodec)) (Path.concat [ resultsDir, failuresFile ]) failures
130153

131-
determineCompilerVersionsForPackage :: forall r. PackageName -> Version -> Run (AFF + EFFECT + REGISTRY + EXCEPT String + LOG + STORAGE + r) Unit
132-
determineCompilerVersionsForPackage package version = do
154+
determineCompilerVersionsForPackage :: forall r. PackageName -> Version -> Maybe Version -> Run (AFF + EFFECT + REGISTRY + EXCEPT String + LOG + STORAGE + r) Unit
155+
determineCompilerVersionsForPackage package version mbCompiler = do
133156
allManifests <- map ManifestIndex.toMap Registry.readAllManifests
134157
compilerVersions <- PursVersions.pursVersions
135158
Log.debug $ "Checking Manifest Index for " <> formatPackageVersion package version
@@ -180,10 +203,133 @@ determineCompilerVersionsForPackage package version = do
180203
else
181204
goCompilerVersions supported tail
182205

183-
supported <- goCompilerVersions [] (Array.sort (NEA.toArray compilerVersions))
206+
supported <- goCompilerVersions [] (Maybe.maybe (Array.sort (NEA.toArray compilerVersions)) Array.singleton mbCompiler)
184207

185208
if Array.null supported then do
186209
Log.error $ "Could not find supported compiler versions for " <> formatPackageVersion package version
187210
Run.liftEffect $ Process.exit 1
188211
else
189212
Log.info $ "Found supported compiler versions for " <> formatPackageVersion package version <> ": " <> Array.intercalate ", " (map Version.print supported)
213+
214+
data FailureReason
215+
= CannotSolve
216+
| CannotCompile
217+
| UnknownReason
218+
219+
failureReasonCodec :: JsonCodec FailureReason
220+
failureReasonCodec = Profunctor.dimap toVariant fromVariant $ CA.Variant.variantMatch
221+
{ cannotSolve: Left unit
222+
, cannotCompile: Left unit
223+
, unknownReason: Left unit
224+
}
225+
where
226+
toVariant = case _ of
227+
CannotSolve -> Variant.inj (Proxy :: _ "cannotSolve") unit
228+
CannotCompile -> Variant.inj (Proxy :: _ "cannotCompile") unit
229+
UnknownReason -> Variant.inj (Proxy :: _ "unknownReason") unit
230+
231+
fromVariant = Variant.match
232+
{ cannotSolve: \_ -> CannotSolve
233+
, cannotCompile: \_ -> CannotCompile
234+
, unknownReason: \_ -> UnknownReason
235+
}
236+
237+
type Failure =
238+
{ name :: PackageName
239+
, version :: Version
240+
, reason :: FailureReason
241+
}
242+
243+
failureCodec :: JsonCodec Failure
244+
failureCodec = CA.Record.object "Failure"
245+
{ name: PackageName.codec
246+
, version: Version.codec
247+
, reason: failureReasonCodec
248+
}
249+
250+
type CompilerVersionResults =
251+
{ results :: Map PackageName (Map Version (Array Version))
252+
, failures :: Map Version (Array Failure)
253+
}
254+
255+
determineAllCompilerVersions :: forall r. Maybe Version -> Run (AFF + EFFECT + REGISTRY + EXCEPT String + LOG + STORAGE + r) CompilerVersionResults
256+
determineAllCompilerVersions mbCompiler = do
257+
allManifests <- Array.mapWithIndex Tuple <<< ManifestIndex.toSortedArray ManifestIndex.ConsiderRanges <$> Registry.readAllManifests
258+
compilerVersions <- PursVersions.pursVersions
259+
let
260+
compilersToCheck = Maybe.maybe compilerVersions NEA.singleton mbCompiler
261+
total = Array.length allManifests
262+
supportedForVersion <- map Map.fromFoldable $ for compilersToCheck \compiler -> do
263+
Log.info $ "Starting checks for " <> Version.print compiler
264+
Tuple compiler <$> Array.foldM (checkCompilation compiler total) { failures: [], results: Map.empty } allManifests
265+
266+
let
267+
results = Map.fromFoldableWith (Map.unionWith append) do
268+
Tuple compiler supported <- Map.toUnfoldable (map _.results supportedForVersion)
269+
Tuple package versions <- Map.toUnfoldable supported
270+
Tuple version _ <- Map.toUnfoldable versions
271+
[ Tuple package (Map.singleton version [ compiler ]) ]
272+
273+
failures = map _.failures supportedForVersion
274+
275+
pure { results, failures }
276+
where
277+
-- Adds packages which compile with `version` to the `DependencyIndex`
278+
checkCompilation :: Version -> Int -> { failures :: Array Failure, results :: DependencyIndex } -> Tuple Int Manifest -> Run _ { failures :: Array Failure, results :: DependencyIndex }
279+
checkCompilation compiler total { failures: prevFailures, results: prevResults } (Tuple index manifest@(Manifest { name, version, dependencies })) = do
280+
let progress = fold [ "[", Version.print compiler, " ", show (1 + index), "/", show total, "]" ]
281+
Log.info $ progress <> " Checking " <> formatPackageVersion name version
282+
Log.debug $ "Solving " <> PackageName.print name <> "@" <> Version.print version
283+
case Solver.solve prevResults dependencies of
284+
Left unsolvable -> do
285+
Log.debug $ "Could not solve " <> formatPackageVersion name version <> " with manifest " <> printJson Manifest.codec manifest
286+
Log.debug $ Semigroup.Foldable.foldMap1 (append "\n" <<< Solver.printSolverError) unsolvable
287+
pure { failures: prevFailures <> [ { name, version, reason: CannotSolve } ], results: prevResults }
288+
Right resolutions -> do
289+
supported <- installAndBuildWithVersion compiler (Map.insert name version resolutions)
290+
case supported of
291+
Nothing -> do
292+
Log.debug $ "Including package version " <> formatPackageVersion name version
293+
pure $ { failures: prevFailures, results: Map.insertWith Map.union name (Map.singleton version dependencies) prevResults }
294+
Just reason -> do
295+
Log.debug $ "Skipping package version " <> formatPackageVersion name version
296+
pure $ { failures: prevFailures <> [ { name, version, reason } ], results: prevResults }
297+
298+
installAndBuildWithVersion :: Version -> Map PackageName Version -> Run _ (Maybe FailureReason)
299+
installAndBuildWithVersion compiler resolutions = do
300+
tmp <- Tmp.mkTmpDir
301+
let dependenciesDir = Path.concat [ tmp, ".registry" ]
302+
FS.Extra.ensureDirectory dependenciesDir
303+
Log.debug $ "Created tmp dir for dependencies: " <> dependenciesDir
304+
let globs = [ Path.concat [ dependenciesDir, "*/src/**/*.purs" ] ]
305+
306+
Log.debug "Downloading dependencies..."
307+
forWithIndex_ resolutions \name version -> do
308+
let
309+
filename = PackageName.print name <> "-" <> Version.print version <> ".tar.gz"
310+
filepath = Path.concat [ dependenciesDir, filename ]
311+
Storage.download name version filepath
312+
Tar.extract { cwd: dependenciesDir, archive: filename }
313+
Run.liftAff $ FS.Aff.unlink filepath
314+
315+
Log.debug $ "Compiling with purs@" <> Version.print compiler <> " and globs " <> String.joinWith " " globs
316+
compilerOutput <- Run.liftAff $ Purs.callCompiler
317+
{ command: Purs.Compile { globs }
318+
, version: Just compiler
319+
, cwd: Just tmp
320+
}
321+
322+
FS.Extra.remove tmp
323+
324+
case compilerOutput of
325+
Left (Purs.UnknownError error) -> do
326+
Log.error $ "Failed to compile because of an unknown compiler error: " <> error
327+
pure $ Just UnknownReason
328+
Left (Purs.MissingCompiler) ->
329+
Except.throw "Failed to compile because the compiler was not found."
330+
Left (Purs.CompilationError errors) -> do
331+
Log.debug $ "Failed to compile with purs@" <> Version.print compiler <> ": " <> Purs.printCompilerErrors errors
332+
pure $ Just CannotCompile
333+
Right _ -> do
334+
Log.debug $ "Successfully compiled with purs@" <> Version.print compiler
335+
pure Nothing

scripts/src/LegacyImporter.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ runLegacyImport mode logs = do
227227
Log.info $ formatImportStats $ calculateImportStats legacyRegistry importedIndex
228228

229229
Log.info "Sorting packages for upload..."
230-
let allIndexPackages = ManifestIndex.toSortedArray importedIndex.registryIndex
230+
let allIndexPackages = ManifestIndex.toSortedArray ManifestIndex.IgnoreRanges importedIndex.registryIndex
231231

232232
Log.info "Removing packages that previously failed publish"
233233
indexPackages <- allIndexPackages # Array.filterA \(Manifest { name, version }) ->

0 commit comments

Comments
 (0)