1+ package scala .tools .nsc
2+
3+ import java .io ._
4+ import java .net .URL
5+ import java .nio .file ._
6+ import java .nio .file .attribute .BasicFileAttributes
7+ import java .util .concurrent .TimeUnit
8+
9+ import com .typesafe .config .ConfigFactory
10+ import org .openjdk .jmh .annotations .Mode .SampleTime
11+ import org .openjdk .jmh .annotations ._
12+
13+ import scala .collection .JavaConverters ._
14+
15+ @ State (Scope .Benchmark )
16+ @ BenchmarkMode (Array (SampleTime ))
17+ @ OutputTimeUnit (TimeUnit .MILLISECONDS )
18+ @ Warmup (iterations = 10 , time = 10 , timeUnit = TimeUnit .SECONDS )
19+ @ Measurement (iterations = 10 , time = 10 , timeUnit = TimeUnit .SECONDS )
20+ @ Fork (value = 3 )
21+ class HotSbtBenchmark {
22+ @ Param (value = Array ())
23+ var source : String = _
24+
25+ @ Param (value = Array (" " ))
26+ var extraArgs : String = _
27+
28+ @ Param (value = Array (" 0.13.15" ))
29+ var sbtVersion : String = _
30+
31+ // This parameter is set by ScalacBenchmarkRunner / UploadingRunner based on the Scala version.
32+ // When running the benchmark directly the "latest" symlink is used.
33+ @ Param (value = Array (" latest" ))
34+ var corpusVersion : String = _
35+
36+ var sbtProcess : Process = _
37+ var inputRedirect : ProcessBuilder .Redirect = _
38+ var outputRedirect : ProcessBuilder .Redirect = _
39+ var tempDir : Path = _
40+ var scalaHome : Path = _
41+ var processOutputReader : BufferedReader = _
42+ var processInputReader : BufferedWriter = _
43+ var output = new java.lang.StringBuilder ()
44+
45+ def buildDef =
46+ s """
47+ |scalaHome := Some(file(" ${scalaHome.toAbsolutePath.toString}"))
48+ |
49+ |val cleanClasses = taskKey[Unit]("clean the classes directory")
50+ |
51+ |cleanClasses := IO.delete((classDirectory in Compile).value)
52+ |
53+ |scalaSource in Compile := file(" ${corpusSourcePath.toAbsolutePath.toString}")
54+ |
55+ |libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
56+ |libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
57+ |
58+ |// TODO support .java sources
59+ """ .stripMargin
60+
61+ @ Setup (Level .Trial ) def spawn (): Unit = {
62+ tempDir = Files .createTempDirectory(" sbt-" )
63+ scalaHome = Files .createTempDirectory(" scalaHome-" )
64+ initDepsClasspath()
65+ Files .createDirectory(tempDir.resolve(" project" ))
66+ Files .write(tempDir.resolve(" project/build.properties" ), java.util.Arrays .asList(" sbt.version=" + sbtVersion))
67+ Files .write(tempDir.resolve(" build.sbt" ), buildDef.getBytes(" UTF-8" ))
68+ val sbtLaucherPath = System .getProperty(" sbt.launcher" )
69+ if (sbtLaucherPath == null ) sys.error(" System property -Dsbt.launcher absent" )
70+ val builder = new ProcessBuilder (sys.props(" java.home" ) + " /bin/java" , " -Xms2G" , " -Xmx2G" , " -Dsbt.log.format=false" , " -jar" , sbtLaucherPath)
71+ builder.directory(tempDir.toFile)
72+ inputRedirect = builder.redirectInput()
73+ outputRedirect = builder.redirectOutput()
74+ sbtProcess = builder.start()
75+ processOutputReader = new BufferedReader (new InputStreamReader (sbtProcess.getInputStream))
76+ processInputReader = new BufferedWriter (new OutputStreamWriter (sbtProcess.getOutputStream))
77+ awaitPrompt()
78+ }
79+
80+ @ Benchmark
81+ def compile (): Unit = {
82+ issue(" ;cleanClasses;compile" )
83+ awaitPrompt()
84+ }
85+
86+ def issue (str : String ) = {
87+ processInputReader.write(str + " \n " )
88+ processInputReader.flush()
89+ }
90+
91+ def awaitPrompt (): Unit = {
92+ output.setLength(0 )
93+ var line = " "
94+ val buffer = new Array [Char ](128 )
95+ var read : Int = - 1
96+ while (true ) {
97+ read = processOutputReader.read(buffer)
98+ if (read == - 1 ) sys.error(" EOF: " + output.toString)
99+ else {
100+ output.append(buffer, 0 , read)
101+ if (output.toString.contains(" \n > " )) {
102+ if (output.toString.contains(" [error" )) sys.error(output.toString)
103+ return
104+ }
105+ }
106+ }
107+
108+ }
109+
110+ private def corpusSourcePath = Paths .get(s " ../corpus/ $source/ $corpusVersion" )
111+
112+ def initDepsClasspath (): Unit = {
113+ val libDir = tempDir.resolve(" lib" )
114+ Files .createDirectories(libDir)
115+ for (depFile <- BenchmarkUtils .initDeps(corpusSourcePath)) {
116+ val libDirFile = libDir.resolve(depFile.getFileName)
117+ Files .copy(depFile, libDir)
118+ }
119+
120+ val scalaHomeLibDir = scalaHome.resolve(" lib" )
121+ Files .createDirectories(scalaHomeLibDir)
122+ for (elem <- sys.props(" java.class.path" ).split(File .pathSeparatorChar)) {
123+ val jarFile = Paths .get(elem)
124+ var name = jarFile.getFileName.toString
125+ if (name.startsWith(" scala" ) && name.endsWith(" .jar" )) {
126+ if (name.startsWith(" scala-library" ))
127+ name = " scala-library.jar"
128+ else if (name.startsWith(" scala-reflect" ))
129+ name = " scala-reflect.jar"
130+ else if (name.startsWith(" scala-compiler" ))
131+ name = " scala-compiler.jar"
132+ Files .copy(jarFile, scalaHomeLibDir.resolve(name))
133+ }
134+ }
135+
136+ }
137+
138+ @ TearDown (Level .Trial ) def terminate (): Unit = {
139+ processOutputReader.close()
140+ sbtProcess.destroyForcibly()
141+ BenchmarkUtils .deleteRecursive(tempDir)
142+ BenchmarkUtils .deleteRecursive(scalaHome)
143+ }
144+ }
0 commit comments