Skip to content

Commit bdb471a

Browse files
Merge pull request #15246 from dotty-staging/add-bisection-tool
Add dotty bisection tool
2 parents 29b6427 + d06b611 commit bdb471a

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Usage
2+
// > scala-cli project/scripts/dottyCompileBisect.scala -- File.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.
6+
7+
8+
import sys.process._
9+
import scala.io.Source
10+
import Releases.Release
11+
12+
@main def dottyCompileBisect(file: String): Unit =
13+
val releaseBisect = ReleaseBisect(file)
14+
val fistBadRelease = releaseBisect.bisect(Releases.allReleases)
15+
println("\nFinished bisecting releases\n")
16+
fistBadRelease.previous match
17+
case Some(lastGoodRelease) =>
18+
println(s"Last good release: $lastGoodRelease\nFirst bad release: $fistBadRelease\n")
19+
val commitBisect = CommitBisect(file)
20+
commitBisect.bisect(lastGoodRelease.hash, fistBadRelease.hash)
21+
case None =>
22+
println(s"No good release found")
23+
24+
class ReleaseBisect(file: String):
25+
26+
def bisect(releases: Vector[Release]): Release =
27+
assert(releases.length > 1, "Need at least 2 releases to bisect")
28+
if releases.length == 2 then
29+
if isGoodRelease(releases.head) then releases.last
30+
else releases.head
31+
else
32+
val mid = releases(releases.length / 2)
33+
if isGoodRelease(mid) then bisect(releases.drop(releases.length / 2))
34+
else bisect(releases.take(releases.length / 2 + 1))
35+
36+
private def isGoodRelease(release: Release): Boolean =
37+
println(s"Testing ${release.version}")
38+
val res = s"""scala-cli compile $file -S "${release.version}"""".!
39+
val isGood = res == 0
40+
println(s"Test result: ${release.version} is a ${if isGood then "good" else "bad"} release\n")
41+
isGood
42+
43+
object Releases:
44+
lazy val allReleases: Vector[Release] =
45+
val re = raw"(?<=title=$")(.+-bin-\d{8}-\w{7}-NIGHTLY)(?=/$")".r
46+
val html = Source.fromURL("https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/")
47+
re.findAllIn(html.mkString).map(Release.apply).toVector
48+
49+
case class Release(version: String):
50+
private val re = raw".+-bin-(\d{8})-(\w{7})-NIGHTLY".r
51+
def date: String =
52+
version match
53+
case re(date, _) => date
54+
case _ => sys.error(s"Could not extract date from version $version")
55+
def hash: String =
56+
version match
57+
case re(_, hash) => hash
58+
case _ => sys.error(s"Could not extract hash from version $version")
59+
60+
def previous: Option[Release] =
61+
val idx = allReleases.indexOf(this)
62+
if idx == 0 then None
63+
else Some(allReleases(idx - 1))
64+
65+
override def toString: String = version
66+
67+
class CommitBisect(file: String):
68+
def bisect(lastGoodHash: String, fistBadHash: String): Unit =
69+
println(s"Starting bisecting commits $lastGoodHash..$fistBadHash\n")
70+
"git bisect start".!
71+
s"git bisect bad $fistBadHash".!
72+
s"git bisect good $lastGoodHash".!
73+
s"git bisect run sh project/scripts/dottyCompileBisect.sh $file".!

project/scripts/dottyCompileBisect.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Usage
2+
# > git bisect start
3+
# > git bisect bad <bad-commit>
4+
# > git bisect good <good-commit>
5+
# > git bisect run project/scripts/dottyCompileBisect.sh <file.scala>
6+
#
7+
# Note: Use dottyCompileBisect.scala for faster bisection over commits that spans several days
8+
9+
file="$1"
10+
shift
11+
12+
rm -r out
13+
mkdir out
14+
mkdir out/bisect
15+
16+
sbt "clean; scalac -d out/bisect $file"

0 commit comments

Comments
 (0)