Skip to content

Commit 53f18b8

Browse files
authored
Merge pull request #853 from alexarchambault/write-bloop-output-to-file
Send the Bloop server output to a file when possible
2 parents 11a67bf + c868eba commit 53f18b8

File tree

11 files changed

+165
-47
lines changed

11 files changed

+165
-47
lines changed

.github/workflows/ci.yml

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,6 @@ concurrency:
1313
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
1414

1515
jobs:
16-
migration-tests:
17-
timeout-minutes: 120
18-
runs-on: "ubuntu-latest"
19-
steps:
20-
- uses: actions/checkout@v3
21-
with:
22-
fetch-depth: 0
23-
submodules: true
24-
- uses: coursier/cache-action@v6.3
25-
- uses: VirtusLab/scala-cli-setup@267af2f1ed4911180b4bb25619ca4a586753cbd1
26-
with:
27-
jvm: "temurin:17"
28-
- name: Unit 3.x tests
29-
run: |
30-
./mill -i cli3.compile
31-
./mill -i cli3.test
32-
./mill -i _[3.1.1].test
3316
jvm-tests:
3417
timeout-minutes: 120
3518
runs-on: ${{ matrix.OS }}
@@ -203,6 +186,24 @@ jobs:
203186
- name: Check examples
204187
run: bash ./scala-cli --jvm temurin:17 .github/scripts/check_examples.sc
205188

189+
migration-tests:
190+
timeout-minutes: 120
191+
runs-on: "ubuntu-latest"
192+
steps:
193+
- uses: actions/checkout@v3
194+
with:
195+
fetch-depth: 0
196+
submodules: true
197+
- uses: coursier/cache-action@v6.3
198+
- uses: VirtusLab/scala-cli-setup@267af2f1ed4911180b4bb25619ca4a586753cbd1
199+
with:
200+
jvm: "temurin:17"
201+
- name: Unit 3.x tests
202+
run: |
203+
./mill -i cli3.compile
204+
./mill -i cli3.test
205+
./mill -i _[3.1.1].test
206+
206207
checks:
207208
timeout-minutes: 15
208209
runs-on: ubuntu-latest

modules/bloop-rifle/src/main/scala/scala/build/blooprifle/BloopRifle.scala

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,25 @@ object BloopRifle {
4747
): Future[Unit] =
4848
config.classPath(version) match {
4949
case Left(ex) => Future.failed(new Exception("Error getting Bloop class path", ex))
50-
case Right(cp) =>
50+
case Right((cp, isScalaCliBloop)) =>
51+
object IntValue {
52+
def unapply(s: String): Option[Int] =
53+
// no String.toIntOption in Scala 2.12.x
54+
try Some(s.toInt)
55+
catch {
56+
case _: NumberFormatException => None
57+
}
58+
}
59+
val bloopServerSupportsFileTruncating =
60+
isScalaCliBloop && {
61+
version.takeWhile(c => c.isDigit || c == '.').split('.') match {
62+
case Array(IntValue(maj), IntValue(min), IntValue(patch)) =>
63+
import scala.math.Ordering.Implicits._
64+
Seq(maj, min, patch) >= Seq(1, 14, 20)
65+
case _ =>
66+
false
67+
}
68+
}
5169
Operations.startServer(
5270
config.address,
5371
bloopJava,
@@ -57,7 +75,8 @@ object BloopRifle {
5775
scheduler,
5876
config.startCheckPeriod,
5977
config.startCheckTimeout,
60-
logger
78+
logger,
79+
bloopServerSupportsFileTruncating = bloopServerSupportsFileTruncating
6180
)
6281
}
6382

modules/bloop-rifle/src/main/scala/scala/build/blooprifle/BloopRifleConfig.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ final case class BloopRifleConfig(
1111
address: BloopRifleConfig.Address,
1212
javaPath: String,
1313
javaOpts: Seq[String],
14-
classPath: String => Either[Throwable, Seq[File]],
14+
classPath: String => Either[Throwable, (Seq[File], Boolean)],
1515
workingDir: File,
1616
bspSocketOrPort: Option[() => BspConnectionAddress],
1717
bspStdin: Option[InputStream],
@@ -38,6 +38,8 @@ object BloopRifleConfig {
3838
}
3939
final case class DomainSocket(path: Path) extends Address {
4040
def render = path.toString
41+
def outputPath: Path =
42+
path.resolve("output")
4143
}
4244
}
4345

@@ -87,8 +89,9 @@ object BloopRifleConfig {
8789
.getOrElse(hardCodedDefaultJavaOpts)
8890
}
8991

92+
def scalaCliBloopOrg = "io.github.alexarchambault.bleep"
9093
def hardCodedDefaultModule: String =
91-
"io.github.alexarchambault.bleep:bloop-frontend_2.12"
94+
s"$scalaCliBloopOrg:bloop-frontend_2.12"
9295
def hardCodedDefaultVersion: String =
9396
Constants.bloopVersion
9497
def hardCodedDefaultScalaVersion: String =
@@ -118,7 +121,7 @@ object BloopRifleConfig {
118121

119122
def default(
120123
address: Address,
121-
bloopClassPath: String => Either[Throwable, Seq[File]],
124+
bloopClassPath: String => Either[Throwable, (Seq[File], Boolean)],
122125
workingDir: File
123126
): BloopRifleConfig =
124127
BloopRifleConfig(

modules/bloop-rifle/src/main/scala/scala/build/blooprifle/internal/Operations.scala

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,28 @@ object Operations {
9595
scheduler: ScheduledExecutorService,
9696
waitInterval: FiniteDuration,
9797
timeout: Duration,
98-
logger: BloopRifleLogger
98+
logger: BloopRifleLogger,
99+
bloopServerSupportsFileTruncating: Boolean
99100
): Future[Unit] = {
100101

101-
val (addressArgs, mainClass) = address match {
102+
val (addressArgs, mainClass, writeOutputToOpt) = address match {
102103
case BloopRifleConfig.Address.Tcp(host, port) =>
103-
(Seq(host, port.toString), "bloop.Server")
104-
case BloopRifleConfig.Address.DomainSocket(path) =>
105-
(Seq(s"daemon:$path"), "bloop.Bloop")
104+
(Seq(host, port.toString), "bloop.Server", None)
105+
case s: BloopRifleConfig.Address.DomainSocket =>
106+
val writeOutputToOpt0 =
107+
if (bloopServerSupportsFileTruncating) Some(s.outputPath)
108+
else None
109+
(Seq(s"daemon:${s.path}"), "bloop.Bloop", writeOutputToOpt0)
106110
}
111+
112+
val extraJavaOpts =
113+
writeOutputToOpt.toSeq.map { writeOutputTo =>
114+
s"-Dbloop.truncate-output-file-periodically=${writeOutputTo.toAbsolutePath}"
115+
}
116+
107117
val command =
108118
Seq(javaPath) ++
119+
extraJavaOpts ++
109120
javaOpts ++
110121
Seq(
111122
"-cp",
@@ -117,15 +128,25 @@ object Operations {
117128
b.directory(workingDir)
118129
b.redirectInput(ProcessBuilder.Redirect.PIPE)
119130

120-
b.redirectOutput {
121-
if (logger.bloopCliInheritStdout) ProcessBuilder.Redirect.INHERIT
122-
else ProcessBuilder.Redirect.DISCARD
123-
}
131+
if (logger.bloopCliInheritStdout)
132+
b.redirectOutput(ProcessBuilder.Redirect.INHERIT)
133+
else
134+
writeOutputToOpt match {
135+
case Some(writeOutputTo) =>
136+
b.redirectOutput(writeOutputTo.toFile)
137+
case None =>
138+
b.redirectOutput(ProcessBuilder.Redirect.DISCARD)
139+
}
124140

125-
b.redirectError {
126-
if (logger.bloopCliInheritStderr) ProcessBuilder.Redirect.INHERIT
127-
else ProcessBuilder.Redirect.DISCARD
128-
}
141+
if (logger.bloopCliInheritStderr)
142+
b.redirectError(ProcessBuilder.Redirect.INHERIT)
143+
else
144+
writeOutputToOpt match {
145+
case Some(writeOutputTo) =>
146+
b.redirectError(writeOutputTo.toFile)
147+
case None =>
148+
b.redirectError(ProcessBuilder.Redirect.DISCARD)
149+
}
129150

130151
val p = b.start()
131152
p.getOutputStream.close()

modules/build/src/main/scala/scala/build/Bloop.scala

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,28 @@ object Bloop {
6464
res.map(_._2.toIO)
6565
}
6666

67-
def bloopClassPath(logger: Logger, cache: FileCache[Task]): Either[BuildException, Seq[File]] =
67+
def bloopClassPath(
68+
logger: Logger,
69+
cache: FileCache[Task]
70+
): Either[BuildException, (Seq[File], Boolean)] =
6871
bloopClassPath(logger, cache, BloopRifleConfig.defaultVersion)
6972

7073
def bloopClassPath(
7174
logger: Logger,
7275
cache: FileCache[Task],
7376
bloopVersion: String
74-
): Either[BuildException, Seq[File]] = either {
77+
): Either[BuildException, (Seq[File], Boolean)] = either {
7578
val moduleStr = BloopRifleConfig.defaultModule
7679
val mod = value {
7780
ModuleParser.parse(moduleStr)
7881
.left.map(err => new ModuleFormatError(moduleStr, err, Some("Bloop")))
7982
}
80-
val dep = DependencyLike(mod, bloopVersion)
81-
val sv = BloopRifleConfig.defaultScalaVersion
82-
val sbv = ScalaVersion.binary(sv)
83-
val params = ScalaParameters(sv, sbv)
84-
value(bloopClassPath(dep, params, logger, cache))
83+
val dep = DependencyLike(mod, bloopVersion)
84+
val sv = BloopRifleConfig.defaultScalaVersion
85+
val sbv = ScalaVersion.binary(sv)
86+
val params = ScalaParameters(sv, sbv)
87+
val cp = value(bloopClassPath(dep, params, logger, cache))
88+
val isScalaCliBloop = moduleStr.startsWith(BloopRifleConfig.scalaCliBloopOrg + ":")
89+
(cp, isScalaCliBloop)
8590
}
8691
}

modules/build/src/test/scala/scala/build/tests/util/BloopServer.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import coursier.cache.FileCache
44

55
import scala.build.{Bloop, Logger}
66
import scala.build.blooprifle.BloopRifleConfig
7-
import scala.util.Properties
87

98
object BloopServer {
109

@@ -14,10 +13,7 @@ object BloopServer {
1413
// Not sure how to properly shut it down or have it exit after a period
1514
// of inactivity, so we keep using our default global Bloop for now.
1615
private def bloopAddress =
17-
if (Properties.isWin)
18-
BloopRifleConfig.Address.Tcp(BloopRifleConfig.defaultHost, BloopRifleConfig.defaultPort)
19-
else
20-
BloopRifleConfig.Address.DomainSocket(directories.bloopDaemonDir.toNIO)
16+
BloopRifleConfig.Address.DomainSocket(directories.bloopDaemonDir.toNIO)
2117

2218
val bloopConfig = BloopRifleConfig.default(
2319
bloopAddress,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package scala.cli.commands.bloop
2+
3+
import caseapp._
4+
5+
import scala.cli.commands.{LoggingOptions, SharedCompilationServerOptions, SharedDirectoriesOptions}
6+
7+
// format: off
8+
final case class BloopOutputOptions(
9+
@Recurse
10+
logging: LoggingOptions = LoggingOptions(),
11+
@Recurse
12+
compilationServer: SharedCompilationServerOptions = SharedCompilationServerOptions(),
13+
@Recurse
14+
directories: SharedDirectoriesOptions = SharedDirectoriesOptions()
15+
)
16+
// format: on
17+
18+
object BloopOutputOptions {
19+
implicit lazy val parser: Parser[BloopOutputOptions] = Parser.derive
20+
implicit lazy val help: Help[BloopOutputOptions] = Help.derive
21+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package scala.cli.commands.bloop
2+
3+
import caseapp.core.RemainingArgs
4+
5+
import scala.build.blooprifle.BloopRifleConfig
6+
import scala.cli.CurrentParams
7+
import scala.cli.commands.util.CommonOps._
8+
import scala.cli.commands.util.SharedCompilationServerOptionsUtil._
9+
import scala.cli.commands.{CoursierOptions, ScalaCommand}
10+
11+
object BloopOutput extends ScalaCommand[BloopOutputOptions] {
12+
override def hidden = true
13+
override def inSipScala = false
14+
override def names: List[List[String]] = List(
15+
List("bloop", "output")
16+
)
17+
18+
def run(options: BloopOutputOptions, args: RemainingArgs): Unit = {
19+
CurrentParams.verbosity = options.logging.verbosity
20+
val logger = options.logging.logger
21+
val bloopRifleConfig = options.compilationServer.bloopRifleConfig(
22+
logger,
23+
CoursierOptions().coursierCache(logger.coursierLogger("Downloading Bloop")), // unused here
24+
options.logging.verbosity,
25+
"unused-java", // unused here
26+
options.directories.directories
27+
)
28+
val outputFile = bloopRifleConfig.address match {
29+
case s: BloopRifleConfig.Address.DomainSocket =>
30+
logger.debug(s"Bloop server directory: ${s.path}")
31+
logger.debug(s"Bloop server output path: ${s.outputPath}")
32+
os.Path(s.outputPath, os.pwd)
33+
case tcp: BloopRifleConfig.Address.Tcp =>
34+
if (options.logging.verbosity >= 0)
35+
System.err.println(
36+
s"Error: Bloop server is listening on TCP at ${tcp.render}, output not available."
37+
)
38+
sys.exit(1)
39+
}
40+
if (!os.isFile(outputFile)) {
41+
if (options.logging.verbosity >= 0)
42+
System.err.println(s"Error: $outputFile not found")
43+
sys.exit(1)
44+
}
45+
val content = os.read.bytes(outputFile)
46+
logger.debug(s"Read ${content.length} bytes from $outputFile")
47+
System.out.write(content)
48+
}
49+
}

modules/cli/src/main/scala/scala/cli/commands/pgp/PgpCreateExternal.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scala.cli.commands.pgp
33
import scala.cli.signing.commands.PgpCreateOptions
44

55
class PgpCreateExternal extends PgpExternalCommand {
6+
override def hidden = true
67
def actualHelp = PgpCreateOptions.help
78
def externalCommand = Seq("pgp", "create")
89

modules/cli/src/main/scala/scala/cli/commands/pgp/PgpSignExternal.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scala.cli.commands.pgp
33
import scala.cli.signing.commands.PgpSignOptions
44

55
class PgpSignExternal extends PgpExternalCommand {
6+
override def hidden = true
67
def actualHelp = PgpSignOptions.help
78
def externalCommand = Seq("pgp", "sign")
89

0 commit comments

Comments
 (0)