Skip to content

Commit 07ba7c7

Browse files
committed
Fix bisect scripts
1 parent 448928f commit 07ba7c7

File tree

2 files changed

+145
-66
lines changed

2 files changed

+145
-66
lines changed

.github/workflows/buildBisect.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jobs:
5252
with:
5353
repository: ${{ inputs.repository-url }}
5454
ref: main
55+
fetch-depth: 0
5556
path: "compiler"
5657

5758
- uses: coursier/cache-action@v6.4
@@ -60,8 +61,10 @@ jobs:
6061
shell: bash
6162
run: |
6263
cd ${{ github.workspace }}/compiler
63-
git --no-pager log -1
64-
${{ github.workspace }}/opencb/scripts/bisect.scala -- \
64+
echo "Last 3 Scala compiler commits:"
65+
git --no-pager log -3
66+
echo "---"
67+
scala-cli ${{ github.workspace }}/opencb/scripts/bisect.scala -- \
6568
--project-name=${{ inputs.project-name }} \
6669
--targets=${{ inputs.project-targets }} \
6770
--releases=${{ inputs.scala-version-start}}..${{ inputs.scala-version-end}} \

scripts/bisect.scala

Lines changed: 140 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ val communityBuildVersion = "v0.2.4"
1515
val config = scopt.OParser
1616
.parse(Config.parser, args, Config())
1717
.getOrElse(sys.error("Failed to parse config"))
18-
18+
1919
val validationScript = config.validationScript
2020
val releases = Releases.fromRange(config.releasesRange)
2121
val releaseBisect = ReleaseBisect(validationScript, shouldFail = config.shouldFail, releases)
@@ -28,37 +28,55 @@ val communityBuildVersion = "v0.2.4"
2828
println(s"First bad release: ${firstBadRelease.version}")
2929
println("\nFinished bisecting releases\n")
3030

31-
val commitBisect = CommitBisect(validationScript, shouldFail = config.shouldFail, lastGoodRelease.hash, firstBadRelease.hash)
31+
val commitBisect = CommitBisect(
32+
validationScript,
33+
compilerDir = config.compilerDir,
34+
openCommunityBuildDir = config.openCommunityBuildDir,
35+
shouldFail = config.shouldFail,
36+
lastGoodRelease.hash,
37+
firstBadRelease.hash
38+
)
3239
commitBisect.bisect()
40+
end run
3341

34-
case class ValidationCommand(projectName: String = "", targets: String = "", extraScalacOptions: String = "", disabledScalacOption: String = "")
42+
case class ValidationCommand(
43+
projectName: String = "",
44+
targets: String = "",
45+
extraScalacOptions: String = "",
46+
disabledScalacOption: String = ""
47+
)
3548
case class Config(
36-
dryRun: Boolean = false,
37-
releasesRange: ReleasesRange = ReleasesRange.all,
38-
shouldFail: Boolean = false,
39-
openCommunityBuildDir: Path = Path.of(""),
40-
compilerDir: Path = Path.of(""),
41-
command: ValidationCommand = ValidationCommand()
42-
){
43-
inline def withCommand(mapping: ValidationCommand => ValidationCommand) = copy(command = mapping(command))
49+
dryRun: Boolean = false,
50+
releasesRange: ReleasesRange = ReleasesRange.all,
51+
shouldFail: Boolean = false,
52+
openCommunityBuildDir: Path = Path.of(""),
53+
compilerDir: Path = Path.of(""),
54+
command: ValidationCommand = ValidationCommand()
55+
) {
56+
inline def withCommand(mapping: ValidationCommand => ValidationCommand) =
57+
copy(command = mapping(command))
4458

4559
lazy val remoteValidationScript: File = ValidationScript.buildProject(
4660
projectName = command.projectName,
4761
targets = Option(command.targets).filter(_.nonEmpty),
4862
extraScalacOptions = command.extraScalacOptions,
49-
disabledScalacOption= command.disabledScalacOption,
63+
disabledScalacOption = command.disabledScalacOption,
5064
runId = s"bisect-${command.projectName}",
51-
buildURL= "",
65+
buildURL = "",
5266
executeTests = false,
5367
openCBDir = openCommunityBuildDir
5468
)
55-
lazy val validationScript: File =
69+
lazy val validationScript: File =
5670
require(Files.exists(openCommunityBuildDir), "Open CB dir does not exist")
5771
require(Files.exists(compilerDir), "Compiler dir does not exist")
58-
ValidationScript.dockerRunBuildProject(command.projectName, remoteValidationScript, openCommunityBuildDir.toFile())
72+
ValidationScript.dockerRunBuildProject(
73+
command.projectName,
74+
remoteValidationScript,
75+
openCommunityBuildDir.toFile()
76+
)
5977
}
6078

61-
object Config{
79+
object Config {
6280
val parser = {
6381
import scopt.OParser
6482
val builder = OParser.builder[Config]
@@ -103,19 +121,28 @@ object Config{
103121
.action:
104122
(v, c) => c.copy(compilerDir = Path.of(v))
105123
.text("Directory containing Scala compiler repository, required for commit-based bissect")
106-
.required()
107-
,
124+
.required() ,
108125
checkConfig { c =>
109126
if !Files.exists(c.compilerDir) then failure("Compiler directory does not exist")
110-
else if !Files.exists(c.openCommunityBuildDir) then failure("Open Community Build directory does not exist")
127+
else if !Files.exists(c.openCommunityBuildDir) then
128+
failure("Open Community Build directory does not exist")
111129
else success
112130
}
113131
)
114132
}
115133
}
116134

117135
object ValidationScript:
118-
def buildProject(projectName: String, targets: Option[String], extraScalacOptions: String, disabledScalacOption: String, runId: String, buildURL: String, executeTests: Boolean, openCBDir: Path): File = tmpScript(openCBDir){
136+
def buildProject(
137+
projectName: String,
138+
targets: Option[String],
139+
extraScalacOptions: String,
140+
disabledScalacOption: String,
141+
runId: String,
142+
buildURL: String,
143+
executeTests: Boolean,
144+
openCBDir: Path
145+
): File = tmpScript(openCBDir) {
119146
val configPatch =
120147
if executeTests
121148
then ""
@@ -158,9 +185,9 @@ object ValidationScript:
158185
""".stripMargin
159186
}
160187

161-
def dockerRunBuildProject(projectName: String, validationScript: File, openCBDir: File): File =
188+
def dockerRunBuildProject(projectName: String, validationScript: File, openCBDir: File): File = {
162189
val scriptsPath = "/scripts/"
163-
val validationScriptPath="/scripts/validationScript.sh"
190+
val validationScriptPath = "/scripts/validationScript.sh"
164191
assert(Files.exists(validationScript.toPath()))
165192
tmpScript(openCBDir.toPath)(raw"""
166193
|#!/usr/bin/env bash
@@ -175,9 +202,36 @@ object ValidationScript:
175202
| virtuslab/scala-community-build-project-builder:jdk$${javaVersion}-$communityBuildVersion \
176203
| /bin/bash -c "$validationScriptPath $$scalaVersion"
177204
""".stripMargin)
178-
179-
private def tmpScript(openCBDir: Path)(content: String): File =
180-
val executableAttr = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-xr-x"))
205+
}
206+
207+
def buildCompilerAndValidate(
208+
shouldFail: Boolean,
209+
validationScript: File,
210+
openCBDir: Path
211+
): File = {
212+
// Always bootstrapped
213+
val scala3CompilerProject = "scala3-compiler-bootstrapped"
214+
val scala3Project = "scala3-bootstrapped"
215+
val mavenRepo = "https://scala3.westeurope.cloudapp.azure.com/maven2/bisect/"
216+
// invert the process status if failure was expected
217+
val validationCommandStatusModifier = if shouldFail then "! " else ""
218+
219+
tmpScript(openCBDir)(raw"""
220+
|#!/usr/bin/env bash
221+
|export NIGHTLYBUILD=yes
222+
|#Why we need these strange pipe operations instead of `tail -n1`?
223+
|#I don't know - it would work locally, but keeps failing in GithubAction CI
224+
|scalaVersion=$$(sbt "print ${scala3CompilerProject}/version" | grep . | tail -n 2 | head -n 1)
225+
|echo "ScalaVersion=$${scalaVersion}"
226+
|rm -r out
227+
|sbt "clean; set every sonatypePublishToBundle := Some(\"CommunityBuildRepo\" at \"$mavenRepo\"); ${scala3Project}/publish"
228+
|${validationCommandStatusModifier}${validationScript.getAbsolutePath} "$$scalaVersion"
229+
""".stripMargin)
230+
}
231+
232+
private def tmpScript(openCBDir: Path)(content: String): File = {
233+
val executableAttr =
234+
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-xr-x"))
181235
val tmpPath = Files.createTempFile(openCBDir, "scala-bisect-validator", ".sh", executableAttr)
182236
val tmpFile = tmpPath.toFile
183237

@@ -189,7 +243,7 @@ object ValidationScript:
189243
tmpFile.deleteOnExit()
190244
Files.write(tmpPath, content.getBytes(StandardCharsets.UTF_8))
191245
tmpFile
192-
246+
}
193247

194248
case class ReleasesRange(first: Option[String], last: Option[String]):
195249
def filter(releases: Seq[Release]) =
@@ -207,10 +261,13 @@ case class ReleasesRange(first: Option[String], last: Option[String]):
207261
object ReleasesRange:
208262
def all = ReleasesRange(None, None)
209263
def tryParse(range: String): Option[ReleasesRange] = range match
210-
case s"${first}..${last}" => Some(ReleasesRange(
211-
Some(first).filter(_.nonEmpty),
212-
Some(last).filter(_.nonEmpty)
213-
))
264+
case s"${first}..${last}" =>
265+
Some(
266+
ReleasesRange(
267+
Some(first).filter(_.nonEmpty),
268+
Some(last).filter(_.nonEmpty)
269+
)
270+
)
214271
case _ => None
215272

216273
class Releases(val releases: Vector[Release])
@@ -228,36 +285,48 @@ case class Release(version: String):
228285
def date: String =
229286
version match
230287
case re(date, _) => date
231-
case _ => sys.error(s"Could not extract date from release name: $version")
288+
case _ => sys.error(s"Could not extract date from release name: $version")
232289
def hash: String =
233290
version match
234291
case re(_, hash) => hash
235-
case _ => sys.error(s"Could not extract hash from release name: $version")
292+
case _ => sys.error(s"Could not extract hash from release name: $version")
236293

237294
override def toString: String = version
238295

239-
240296
class ReleaseBisect(validationScript: File, shouldFail: Boolean, allReleases: Vector[Release]):
241297
assert(allReleases.length > 1, "Need at least 2 releases to bisect")
242298

243299
private val isGoodReleaseCache = collection.mutable.Map.empty[Release, Boolean]
244300

245301
def verifyEdgeReleases(): Unit =
246302
println(s"Verifying the first release: ${allReleases.head.version}")
247-
assert(isGoodRelease(allReleases.head), s"The evaluation script unexpectedly failed for the first checked release")
303+
assert(
304+
isGoodRelease(allReleases.head),
305+
s"The evaluation script unexpectedly failed for the first checked release"
306+
)
248307
println(s"Verifying the last release: ${allReleases.last.version}")
249-
assert(!isGoodRelease(allReleases.last), s"The evaluation script unexpectedly succeeded for the last checked release")
308+
assert(
309+
!isGoodRelease(allReleases.last),
310+
s"The evaluation script unexpectedly succeeded for the last checked release"
311+
)
250312

251313
def bisectedGoodAndBadReleases(): (Release, Release) =
252314
val firstBadRelease = bisect(allReleases)
253-
assert(!isGoodRelease(firstBadRelease), s"Bisection error: the 'first bad release' ${firstBadRelease.version} is not a bad release")
315+
assert(
316+
!isGoodRelease(firstBadRelease),
317+
s"Bisection error: the 'first bad release' ${firstBadRelease.version} is not a bad release"
318+
)
254319
val lastGoodRelease = firstBadRelease.previous
255-
assert(isGoodRelease(lastGoodRelease), s"Bisection error: the 'last good release' ${lastGoodRelease.version} is not a good release")
320+
assert(
321+
isGoodRelease(lastGoodRelease),
322+
s"Bisection error: the 'last good release' ${lastGoodRelease.version} is not a good release"
323+
)
256324
(lastGoodRelease, firstBadRelease)
257325

258-
extension (release: Release) private def previous: Release =
259-
val idx = allReleases.indexOf(release)
260-
allReleases(idx - 1)
326+
extension (release: Release)
327+
private def previous: Release =
328+
val idx = allReleases.indexOf(release)
329+
allReleases(idx - 1)
261330

262331
private def bisect(releases: Vector[Release]): Release =
263332
if releases.length == 2 then
@@ -269,31 +338,38 @@ class ReleaseBisect(validationScript: File, shouldFail: Boolean, allReleases: Ve
269338
else bisect(releases.take(releases.length / 2 + 1))
270339

271340
private def isGoodRelease(release: Release): Boolean =
272-
isGoodReleaseCache.getOrElseUpdate(release, {
273-
println(s"Testing ${release.version}")
274-
val result = Seq(validationScript.getAbsolutePath, release.version).!
275-
val isGood = if(shouldFail) result != 0 else result == 0 // invert the process status if failure was expected
276-
println(s"Test result: ${release.version} is a ${if isGood then "good" else "bad"} release\n")
277-
isGood
278-
})
279-
280-
class CommitBisect(validationScript: File, shouldFail: Boolean, lastGoodHash: String, fistBadHash: String):
281-
def bisect(): Unit =
341+
isGoodReleaseCache.getOrElseUpdate(
342+
release, {
343+
println(s"Testing ${release.version}")
344+
val result = Seq(validationScript.getAbsolutePath, release.version).!
345+
val isGood =
346+
if (shouldFail) result != 0
347+
else result == 0 // invert the process status if failure was expected
348+
println(
349+
s"Test result: ${release.version} is a ${if isGood then "good" else "bad"} release\n"
350+
)
351+
isGood
352+
}
353+
)
354+
355+
class CommitBisect(
356+
validationScript: File,
357+
compilerDir: Path,
358+
openCommunityBuildDir: Path,
359+
shouldFail: Boolean,
360+
lastGoodHash: String,
361+
fistBadHash: String
362+
):
363+
def bisect(): Unit = {
282364
println(s"Starting bisecting commits $lastGoodHash..$fistBadHash\n")
283-
// Always bootstrapped
284-
val scala3CompilerProject = "scala3-compiler-bootstrapped"
285-
val scala3Project = "scala3-bootstrapped"
286-
val mavenRepo = "https://scala3.westeurope.cloudapp.azure.com/maven2/bisect/"
287-
val validationCommandStatusModifier = if shouldFail then "! " else "" // invert the process status if failure was expected
288-
val bisectRunScript = raw"""
289-
|export NIGHTLYBUILD=yes
290-
|scalaVersion=$$(sbt "print ${scala3CompilerProject}/version" | tail -n1)
291-
|rm -r out
292-
|sbt "clean; set every sonatypePublishToBundle := Some(\"CommunityBuildRepo\" at \"$mavenRepo\"); ${scala3Project}/publish"
293-
|${validationCommandStatusModifier}${validationScript.getAbsolutePath} "$$scalaVersion"
294-
""".stripMargin
365+
val scriptFile = ValidationScript.buildCompilerAndValidate(
366+
shouldFail,
367+
validationScript,
368+
openCBDir = openCommunityBuildDir
369+
)
295370
"git bisect start".!
296371
s"git bisect bad $fistBadHash".!
297372
s"git bisect good $lastGoodHash".!
298-
Seq("git", "bisect", "run", "sh", "-c", bisectRunScript).!
299-
s"git bisect reset".!
373+
Seq("git", "bisect", "run", scriptFile.getAbsolutePath()).!
374+
"git bisect reset".!
375+
}

0 commit comments

Comments
 (0)