Skip to content

Commit 405fe42

Browse files
committed
Upgrade to Scala v2.13
Key challenges with the upgrade: * The [`scala-io`](https://github.com/jesseeichar/scala-io) library is not available for Scala 2.13 (I had custom-built the Scala 2.12 version, but it was too much effort to get it to 2.13). `scala-io` was providing several useful features: * streaming reading of character data with line-splitting that *preserves line-breaks* (because I don't want the BFG to change the line-breaks in your files while doing `--replace-text`). Finding a replacement library to do this wasn't easy - so much code seems to be based on `java.io.BufferedReader`, which throws line-breaks away. `java.util.Scanner` can _maybe_ do it, but I couldn't get it to work! In the end, I implemented my own solution, a tiny library with a fairly decent set of tests: https://github.com/rtyley/line-break-preserving-line-splitting * resource management - mostly replaced by using Scala 2.13's [`Using`](https://www.scala-lang.org/api/current/scala/util/Using$.html). * File-path types and operations - mostly now performed by java.nio & Guava functions. * Scala 2.13 has a [revised collections framework](https://docs.scala-lang.org/overviews/core/collections-migration-213.html), so: * BFG has its own `ConcurrentSet` which needs to be implemented in the new fashion * `Traversable` becomes `Iterable`, etc
1 parent b696168 commit 405fe42

40 files changed

+362
-274
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: scala
22
dist: xenial
33
scala:
4-
- 2.12.12
4+
- 2.13.4
55

66
jdk:
77
- openjdk8

bfg-benchmark/build.sbt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import Dependencies._
22

3-
libraryDependencies ++= Seq(
3+
libraryDependencies ++= guava ++ Seq(
44
madgagCompress,
5-
scalaIoFile,
65
textmatching,
76
scopt
87
)

bfg-benchmark/src/main/scala/Benchmark.scala

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import lib.Timing.measureTask
22
import lib._
33
import model._
44

5+
import java.nio.file.Files
6+
import java.nio.file.Files.isDirectory
57
import scala.concurrent.ExecutionContext.Implicits.global
68
import scala.concurrent._
79
import scala.concurrent.duration.Duration
10+
import scala.jdk.StreamConverters._
811
import scala.sys.process._
9-
import scalax.file.PathMatcher.IsDirectory
10-
import scalax.io.Codec
1112

1213
/*
1314
* Vary BFG runs by:
@@ -17,24 +18,22 @@ import scalax.io.Codec
1718
*/
1819
object Benchmark extends App {
1920

20-
implicit val codec = Codec.UTF8
21-
2221
BenchmarkConfig.parser.parse(args, BenchmarkConfig()) map {
2322
config =>
24-
println(s"Using resources dir : ${config.resourcesDir.path}")
23+
println(s"Using resources dir : ${config.resourcesDir}")
2524

26-
require(config.resourcesDir.exists, s"Resources dir not found : ${config.resourcesDir.path}")
27-
require(config.jarsDir.exists, s"Jars dir not found : ${config.jarsDir.path}")
28-
require(config.reposDir.exists, s"Repos dir not found : ${config.reposDir.path}")
25+
require(Files.exists(config.resourcesDir), s"Resources dir not found : ${config.resourcesDir}")
26+
require(Files.exists(config.jarsDir), s"Jars dir not found : ${config.jarsDir}")
27+
require(Files.exists(config.reposDir), s"Repos dir not found : ${config.reposDir}")
2928

30-
val missingJars = config.bfgJars.filterNot(_.exists).map(_.toAbsolute.path)
29+
val missingJars = config.bfgJars.filterNot(Files.exists(_))
3130
require(missingJars.isEmpty, s"Missing BFG jars : ${missingJars.mkString(",")}")
3231

3332
val tasksFuture = for {
3433
bfgInvocableEngineSet <- bfgInvocableEngineSet(config)
3534
} yield {
3635
val gfbInvocableEngineSetOpt =
37-
if (config.onlyBfg) None else Some(InvocableEngineSet[GFBInvocation](GitFilterBranch, Seq(InvocableGitFilterBranch)))
36+
Option.when(!config.onlyBfg)(InvocableEngineSet[GFBInvocation](GitFilterBranch, Seq(InvocableGitFilterBranch)))
3837
boogaloo(config, new RepoExtractor(config.scratchDir), Seq(bfgInvocableEngineSet) ++ gfbInvocableEngineSetOpt.toSeq)
3938
}
4039

@@ -59,27 +58,24 @@ object Benchmark extends App {
5958
def boogaloo(config: BenchmarkConfig, repoExtractor: RepoExtractor, invocableEngineSets: Seq[InvocableEngineSet[_ <: EngineInvocation]]) = {
6059

6160
for {
62-
repoSpecDir <- config.repoSpecDirs.toList
63-
availableCommandDirs = (repoSpecDir / "commands").children().filter(IsDirectory).toList
61+
repoSpecDir <- config.repoSpecDirs
62+
availableCommandDirs = Files.list(repoSpecDir.resolve("commands")).toScala(Seq).filter(isDirectory(_))
6463
// println(s"Available commands for $repoName : ${availableCommandDirs.map(_.name).mkString(", ")}")
65-
commandDir <- availableCommandDirs.filter(p => config.commands(p.name))
64+
commandDir <- availableCommandDirs.filter(p => config.commands(p.getFileName.toString))
6665
} yield {
67-
68-
val repoName = repoSpecDir.name
69-
70-
val commandName = commandDir.name
66+
val commandName: String = commandDir.getFileName.toString
7167

7268
commandName -> (for {
7369
invocableEngineSet <- invocableEngineSets
7470
} yield for {
7571
(invocable, processMaker) <- invocableEngineSet.invocationsFor(commandDir)
7672
} yield {
77-
val cleanRepoDir = repoExtractor.extractRepoFrom(repoSpecDir / "repo.git.zip")
78-
commandDir.children().foreach(p => p.copyTo(cleanRepoDir / p.name))
79-
val process = processMaker(cleanRepoDir)
73+
val cleanRepoDir = repoExtractor.extractRepoFrom(repoSpecDir.resolve("repo.git.zip"))
74+
Files.list(commandDir).toScala(Seq).foreach(p => Files.copy(p, cleanRepoDir.resolve(p.getFileName)))
75+
val process = processMaker(cleanRepoDir.toFile)
8076

8177
val duration = measureTask(s"$commandName - $invocable") {
82-
process ! ProcessLogger(_ => Unit)
78+
process ! ProcessLogger(_ => ())
8379
}
8480

8581
if (config.dieIfTaskTakesLongerThan.exists(_ < duration.toMillis)) {
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import java.io.File
2-
32
import com.madgag.textmatching.{Glob, TextMatcher}
43
import scopt.OptionParser
54

6-
import scalax.file.ImplicitConversions._
7-
import scalax.file.Path
8-
import scalax.file.defaultfs.DefaultPath
5+
import java.nio.file.{Path, Paths}
96

107
object BenchmarkConfig {
118
val parser = new OptionParser[BenchmarkConfig]("benchmark") {
129
opt[File]("resources-dir").text("benchmark resources folder - contains jars and repos").action {
13-
(v, c) => c.copy(resourcesDirOption = v)
10+
(v, c) => c.copy(resourcesDirOption = v.toPath)
1411
}
1512
opt[String]("java").text("Java command paths").action {
1613
(v, c) => c.copy(javaCmds = v.split(',').toSeq)
@@ -28,27 +25,27 @@ object BenchmarkConfig {
2825
(v, c) => c.copy(commands = TextMatcher(v, defaultType = Glob))
2926
}
3027
opt[File]("scratch-dir").text("Temp-dir for job runs - preferably ramdisk, eg tmpfs.").action {
31-
(v, c) => c.copy(scratchDir = v)
28+
(v, c) => c.copy(scratchDir = v.toPath)
3229
}
3330
opt[Unit]("only-bfg") action { (_, c) => c.copy(onlyBfg = true) } text "Don't benchmark git-filter-branch"
3431
}
3532
}
36-
case class BenchmarkConfig(resourcesDirOption: Path = Path.fromString(System.getProperty("user.dir")) / "bfg-benchmark" / "resources",
37-
scratchDir: DefaultPath = Path.fromString("/dev/shm/"),
33+
case class BenchmarkConfig(resourcesDirOption: Path = Paths.get(System.getProperty("user.dir"), "bfg-benchmark", "resources"),
34+
scratchDir: Path = Paths.get("/dev/shm/"),
3835
javaCmds: Seq[String] = Seq("java"),
3936
bfgVersions: Seq[String] = Seq.empty,
4037
commands: TextMatcher = Glob("*"),
4138
onlyBfg: Boolean = false,
4239
dieIfTaskTakesLongerThan: Option[Int] = None,
4340
repoNames: Seq[String] = Seq.empty) {
4441

45-
lazy val resourcesDir = Path.fromString(resourcesDirOption.path).toAbsolute
42+
lazy val resourcesDir: Path = resourcesDirOption.toAbsolutePath
4643

47-
lazy val jarsDir = resourcesDir / "jars"
44+
lazy val jarsDir: Path = resourcesDir.resolve("jars")
4845

49-
lazy val reposDir = resourcesDir / "repos"
46+
lazy val reposDir: Path = resourcesDir.resolve("repos")
5047

51-
lazy val bfgJars = bfgVersions.map(version => jarsDir / s"bfg-$version.jar")
48+
lazy val bfgJars: Seq[Path] = bfgVersions.map(version => jarsDir.resolve(s"bfg-$version.jar"))
5249

53-
lazy val repoSpecDirs = repoNames.map(reposDir / _)
50+
lazy val repoSpecDirs: Seq[Path] = repoNames.map(reposDir.resolve)
5451
}

bfg-benchmark/src/main/scala/lib/Repo.scala

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
package lib
22

3+
import com.google.common.io.MoreFiles
4+
import com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE
35
import com.madgag.compress.CompressUtil._
46

5-
import scalax.file.ImplicitConversions._
6-
import scalax.file.Path
7-
import scalax.file.defaultfs.DefaultPath
7+
import java.nio.file.{Files, Path}
8+
import scala.util.Using
89

9-
class RepoExtractor(scratchDir: DefaultPath) {
10+
class RepoExtractor(scratchDir: Path) {
1011

11-
val repoDir = scratchDir / "repo.git"
12+
val repoDir = scratchDir.resolve( "repo.git")
1213

1314
def extractRepoFrom(zipPath: Path) = {
14-
repoDir.deleteRecursively(force = true)
15+
if (Files.exists(repoDir)) MoreFiles.deleteRecursively(repoDir, ALLOW_INSECURE)
1516

16-
repoDir.createDirectory()
17+
Files.createDirectories(repoDir)
1718

18-
println(s"Extracting repo to ${repoDir.toAbsolute.path}")
19+
println(s"Extracting repo to ${repoDir.toAbsolutePath}")
1920

20-
zipPath.inputStream.acquireFor { stream => unzip(stream, repoDir) }
21+
Using(Files.newInputStream(zipPath)) {
22+
stream => unzip(stream, repoDir.toFile)
23+
}
2124

2225
repoDir
2326
}

bfg-benchmark/src/main/scala/model/BFGJar.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package model
22

3-
import scalax.file.Path
3+
import java.nio.file.Path
44

55
object BFGJar {
66
def from(path: Path) = BFGJar(path, Map.empty)
Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package model
22

3+
import com.google.common.io.CharSource
4+
import com.google.common.io.Files.asCharSource
5+
6+
import java.io.File
7+
import java.nio.charset.StandardCharsets.UTF_8
8+
import java.nio.file.{Files, Path}
9+
import scala.jdk.StreamConverters._
310
import scala.sys.process.{Process, ProcessBuilder}
4-
import scalax.file.ImplicitConversions._
5-
import scalax.file.Path
6-
import scalax.file.defaultfs.DefaultPath
7-
import scalax.io.Input
811

912
trait EngineInvocation
1013

@@ -15,19 +18,19 @@ case class GFBInvocation(args: Seq[String]) extends EngineInvocation
1518

1619
trait InvocableEngine[InvocationArgs <: EngineInvocation] {
1720

18-
def processFor(invocation: InvocationArgs)(repoPath: DefaultPath): ProcessBuilder
21+
def processFor(invocation: InvocationArgs)(repoPath: File): ProcessBuilder
1922
}
2023

2124
case class InvocableBFG(java: Java, bfgJar: BFGJar) extends InvocableEngine[BFGInvocation] {
2225

23-
def processFor(invocation: BFGInvocation)(repoPath: DefaultPath) =
24-
Process(s"${java.javaCmd} -jar ${bfgJar.path.path} ${invocation.args}", repoPath)
26+
def processFor(invocation: BFGInvocation)(repoPath: File) =
27+
Process(s"${java.javaCmd} -jar ${bfgJar.path} ${invocation.args}", repoPath)
2528

2629
}
2730

2831
object InvocableGitFilterBranch extends InvocableEngine[GFBInvocation] {
2932

30-
def processFor(invocation: GFBInvocation)(repoPath: DefaultPath) =
33+
def processFor(invocation: GFBInvocation)(repoPath: File) =
3134
Process(Seq("git", "filter-branch") ++ invocation.args, repoPath)
3235
}
3336

@@ -42,24 +45,24 @@ We want to allow the user to vary:
4245
trait EngineType[InvocationType <: EngineInvocation] {
4346
val configName: String
4447

45-
def argsFor(config: Input): InvocationType
48+
def argsFor(config: CharSource): InvocationType
4649

4750
def argsOptsFor(commandDir: Path): Option[InvocationType] = {
48-
val paramsPath = commandDir / s"$configName.txt"
49-
if (paramsPath.exists) Some(argsFor(paramsPath)) else None
51+
val paramsPath = commandDir.resolve(s"$configName.txt")
52+
if (Files.exists(paramsPath)) Some(argsFor(asCharSource(paramsPath.toFile, UTF_8))) else None
5053
}
5154
}
5255

5356
case object BFG extends EngineType[BFGInvocation] {
5457

5558
val configName = "bfg"
5659

57-
def argsFor(config: Input) = BFGInvocation(config.string)
60+
def argsFor(config: CharSource) = BFGInvocation(config.read())
5861
}
5962

6063
case object GitFilterBranch extends EngineType[GFBInvocation] {
6164

6265
val configName = "gfb"
6366

64-
def argsFor(config: Input) = GFBInvocation(config.lines().toSeq)
67+
def argsFor(config: CharSource) = GFBInvocation(config.lines().toScala(Seq))
6568
}

bfg-benchmark/src/main/scala/model/InvocableEngineSet.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package model
22

3-
import scalax.file.Path
4-
import scalax.file.defaultfs.DefaultPath
3+
import java.io.File
4+
import java.nio.file.Path
55

66
case class InvocableEngineSet[InvocationArgs <: EngineInvocation](
77
engineType: EngineType[InvocationArgs],
88
invocableEngines: Seq[InvocableEngine[InvocationArgs]]
99
) {
1010

11-
def invocationsFor(commandDir: Path): Seq[(InvocableEngine[InvocationArgs], DefaultPath => scala.sys.process.ProcessBuilder)] = {
11+
def invocationsFor(commandDir: Path): Seq[(InvocableEngine[InvocationArgs], File => scala.sys.process.ProcessBuilder)] = {
1212
for {
1313
args <- engineType.argsOptsFor(commandDir).toSeq
1414
invocable <- invocableEngines

bfg-benchmark/src/test/scala/JavaVersionSpec.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import org.scalatest.{FlatSpec, Matchers, OptionValues}
1+
import org.scalatest.OptionValues
2+
import org.scalatest.flatspec.AnyFlatSpec
3+
import org.scalatest.matchers.should.Matchers
24

35
object JavaVersionSpec extends AnyFlatSpec with OptionValues with Matchers {
46
"version" should "parse an example line" in {

bfg-library/build.sbt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
import Dependencies._
22

3-
libraryDependencies ++= guava :+ scalaIoFile :+ textmatching :+ scalaGit :+ jgit :+ slf4jSimple :+ scalaGitTest % "test"
3+
libraryDependencies ++= guava ++ Seq(
4+
parCollections,
5+
scalaCollectionPlus,
6+
textmatching,
7+
scalaGit,
8+
jgit,
9+
slf4jSimple,
10+
lineSplitting,
11+
scalaGitTest % Test,
12+
"org.apache.commons" % "commons-text" % "1.9" % Test
13+
)
414

0 commit comments

Comments
 (0)