Skip to content

Commit 845105a

Browse files
authored
Universal bisect script (#16398)
Improve bisect script: * Use locally published versions of scala when bisecting commits to unify validation of commits and published nightly versions * Validate compiler versions using custom scripts - e.g scala-cli and/or expect can be used, which allows bisecting errors in compilation, program execution, REPL and more
2 parents 72c4ffd + cdb31bc commit 845105a

File tree

4 files changed

+64
-50
lines changed

4 files changed

+64
-50
lines changed

project/scripts/dottyCompileBisect.scala renamed to project/scripts/bisect.scala

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,42 @@
1-
// Usage
2-
// > scala-cli project/scripts/dottyCompileBisect.scala -- [--run <main.class.name>] [<compiler-option> ...] <file1.scala> [<fileN.scala> ...]
3-
//
4-
// This script will bisect the compilation failure starting with a fast bisection on released nightly builds.
5-
// Then it will bisect the commits between the last nightly that worked and the first nightly that failed.
1+
/*
2+
This script will bisect a problem with the compiler based on success/failure of the validation script passed as an argument.
3+
It starts with a fast bisection on released nightly builds.
4+
Then it will bisect the commits between the last nightly that worked and the first nightly that failed.
5+
Look at the `usageMessage` below for more details.
6+
*/
67

78

89
import sys.process._
910
import scala.io.Source
1011
import Releases.Release
1112
import java.io.File
12-
import java.nio.file.{Files, Paths, StandardCopyOption}
13+
14+
val usageMessage = """
15+
|Usage:
16+
| > scala-cli project/scripts/bisect.scala -- <validation-script>
17+
|
18+
|The validation script should be executable and accept a single parameter, which will be the scala version to validate.
19+
|Look at bisect-cli-example.sh and bisect-expect-example.exp for reference.
20+
|Don't use the example scripts modified in place as they might disappear from the repo during a checkout.
21+
|Instead copy them to a different location first.
22+
|
23+
|Warning: The bisect script should not be run multiple times in parallel because of a potential race condition while publishing artifacts locally.
24+
|
25+
|Tip: Before running the bisect script run the validation script manually with some published versions of the compiler to make sure it succeeds and fails as expected.
26+
""".stripMargin
1327

1428
@main def dottyCompileBisect(args: String*): Unit =
15-
val (mainClass, compilerArgs) = args match
16-
case Seq("--run", mainClass, compilerArgs*) =>
17-
(Some(mainClass), compilerArgs)
29+
val validationScriptPath = args match
30+
case Seq(path) =>
31+
(new File(path)).getAbsolutePath.toString
1832
case _ =>
19-
(None, args)
33+
println("Wrong script parameters.")
34+
println()
35+
println(usageMessage)
36+
System.exit(1)
37+
null
2038

21-
val releaseBisect = ReleaseBisect(mainClass, compilerArgs.toList)
39+
val releaseBisect = ReleaseBisect(validationScriptPath)
2240
val bisectedBadRelease = releaseBisect.bisectedBadRelease(Releases.allReleases)
2341
println("\nFinished bisecting releases\n")
2442

@@ -28,14 +46,14 @@ import java.nio.file.{Files, Paths, StandardCopyOption}
2846
case Some(lastGoodRelease) =>
2947
println(s"Last good release: $lastGoodRelease")
3048
println(s"First bad release: $firstBadRelease")
31-
val commitBisect = CommitBisect(mainClass, compilerArgs.toList)
49+
val commitBisect = CommitBisect(validationScriptPath)
3250
commitBisect.bisect(lastGoodRelease.hash, firstBadRelease.hash)
3351
case None =>
3452
println(s"No good release found")
3553
case None =>
3654
println(s"No bad release found")
3755

38-
class ReleaseBisect(mainClass: Option[String], compilerArgs: List[String]):
56+
class ReleaseBisect(validationScriptPath: String):
3957
def bisectedBadRelease(releases: Vector[Release]): Option[Release] =
4058
Some(bisect(releases: Vector[Release]))
4159
.filter(!isGoodRelease(_))
@@ -52,13 +70,8 @@ class ReleaseBisect(mainClass: Option[String], compilerArgs: List[String]):
5270

5371
private def isGoodRelease(release: Release): Boolean =
5472
println(s"Testing ${release.version}")
55-
val testCommand = mainClass match
56-
case Some(className) =>
57-
s"run --main-class '$className'"
58-
case None =>
59-
"compile"
60-
val res = s"""scala-cli $testCommand -S '${release.version}' ${compilerArgs.mkString(" ")}""".!
61-
val isGood = res == 0
73+
val result = Seq(validationScriptPath, release.version).!
74+
val isGood = result == 0
6275
println(s"Test result: ${release.version} is a ${if isGood then "good" else "bad"} release\n")
6376
isGood
6477

@@ -86,14 +99,16 @@ object Releases:
8699

87100
override def toString: String = version
88101

89-
class CommitBisect(mainClass: Option[String], compilerArgs: List[String]):
102+
class CommitBisect(validationScriptPath: String):
90103
def bisect(lastGoodHash: String, fistBadHash: String): Unit =
91104
println(s"Starting bisecting commits $lastGoodHash..$fistBadHash\n")
92-
val runOption = mainClass.map(className => s"--run $className").getOrElse("")
93-
val scriptFile = Paths.get("project", "scripts", "dottyCompileBisect.sh")
94-
val tempScriptFile = File.createTempFile("dottyCompileBisect", "sh").toPath
95-
Files.copy(scriptFile, tempScriptFile, StandardCopyOption.REPLACE_EXISTING)
105+
val bisectRunScript = s"""
106+
|scalaVersion=$$(sbt "print scala3-compiler-bootstrapped/version" | tail -n1)
107+
|rm -r out
108+
|sbt "clean; scala3-bootstrapped/publishLocal"
109+
|$validationScriptPath "$$scalaVersion"
110+
""".stripMargin
96111
"git bisect start".!
97112
s"git bisect bad $fistBadHash".!
98113
s"git bisect good $lastGoodHash".!
99-
s"git bisect run sh ${tempScriptFile.toAbsolutePath} ${runOption} ${compilerArgs.mkString(" ")}".!
114+
Seq("git", "bisect", "run", "sh", "-c", bisectRunScript).!

project/scripts/dottyCompileBisect.sh

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
3+
# Don't use this example script modified in place as it might disappear from the repo during a checkout.
4+
# Instead copy it to a different location first.
5+
6+
scala-cli compile -S "$1" file1.scala file2.scala
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/local/bin/expect -f
2+
3+
# Don't use this example script modified in place as it might disappear from the repo during a checkout.
4+
# Instead copy it to a different location first.
5+
6+
set scalaVersion [lindex $argv 0] ;# Get the script argument
7+
8+
set timeout 30 ;# Give scala-cli some time to download the compiler
9+
spawn scala-cli repl -S "$scalaVersion" ;# Start the REPL
10+
expect "scala>" ;# REPL has started
11+
set timeout 5
12+
send -- "Seq.empty.len\t" ;# Tab pressed to trigger code completion
13+
expect {
14+
"length" { exit 0 } ;# Exit with success if the expected string appeared somewhere in stdout
15+
}
16+
17+
exit 1 ;# Otherwise fail - the timeout was exceeded or the REPL crashed

0 commit comments

Comments
 (0)