Skip to content

Commit 88981b6

Browse files
committed
Add --offline option
1 parent 1d24921 commit 88981b6

File tree

9 files changed

+211
-33
lines changed

9 files changed

+211
-33
lines changed

modules/build/src/main/scala/scala/build/compiler/BloopCompilerMaker.scala

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,24 @@ import bloop.rifle.{BloopRifleConfig, BloopServer, BloopThreads}
44
import ch.epfl.scala.bsp4j.BuildClient
55

66
import scala.build.Logger
7+
import scala.build.errors.{FetchingDependenciesError, Severity}
78
import scala.build.internal.Constants
9+
import scala.build.internal.util.WarningMessages
810
import scala.concurrent.duration.DurationInt
11+
import scala.util.Try
912

1013
final class BloopCompilerMaker(
1114
config: BloopRifleConfig,
1215
threads: BloopThreads,
13-
strictBloopJsonCheck: Boolean
16+
strictBloopJsonCheck: Boolean,
17+
offline: Boolean
1418
) extends ScalaCompilerMaker {
1519
def create(
1620
workspace: os.Path,
1721
classesDir: os.Path,
1822
buildClient: BuildClient,
1923
logger: Logger
20-
): BloopCompiler = {
24+
): ScalaCompiler = {
2125
val createBuildServer =
2226
() =>
2327
BloopServer.buildServer(
@@ -30,6 +34,23 @@ final class BloopCompilerMaker(
3034
threads,
3135
logger.bloopRifleLogger
3236
)
33-
new BloopCompiler(createBuildServer, 20.seconds, strictBloopJsonCheck)
37+
38+
Try(new BloopCompiler(createBuildServer, 20.seconds, strictBloopJsonCheck))
39+
.toEither
40+
.left.flatMap {
41+
case e if offline =>
42+
e.getCause match
43+
case _: FetchingDependenciesError =>
44+
logger.diagnostic(
45+
WarningMessages.offlineModeBloopNotFound,
46+
Severity.Warning
47+
)
48+
Right(
49+
SimpleScalaCompilerMaker("java", Nil)
50+
.create(workspace, classesDir, buildClient, logger)
51+
)
52+
case _ => Left(e)
53+
case e => Left(e)
54+
}.fold(t => throw t, identity)
3455
}
3556
}

modules/build/src/main/scala/scala/build/internal/util/WarningMessages.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,7 @@ object WarningMessages {
9898

9999
val chainingUsingFileDirective: String =
100100
"Chaining the 'using file' directive is not supported, the source won't be included in the build."
101+
102+
val offlineModeBloopNotFound =
103+
"Offline mode is ON and Bloop could not be fetched from the local cache, using scalac as fallback"
101104
}

modules/build/src/test/scala/scala/build/tests/TestInputs.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,12 @@ final case class TestInputs(
8686
withCustomInputs(fromDirectory, None, skipCreatingSources) { (root, inputs) =>
8787
val compilerMaker = bloopConfigOpt match {
8888
case Some(bloopConfig) =>
89-
new BloopCompilerMaker(bloopConfig, buildThreads.bloop, strictBloopJsonCheck = true)
89+
new BloopCompilerMaker(
90+
bloopConfig,
91+
buildThreads.bloop,
92+
strictBloopJsonCheck = true,
93+
offline = false
94+
)
9095
case None =>
9196
SimpleScalaCompilerMaker("java", Nil)
9297
}

modules/cli/src/main/scala/scala/cli/commands/shared/CoursierOptions.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package scala.cli.commands.shared
33
import caseapp.*
44
import com.github.plokhotnyuk.jsoniter_scala.core.*
55
import com.github.plokhotnyuk.jsoniter_scala.macros.*
6-
import coursier.cache.{CacheLogger, FileCache}
6+
import coursier.cache.{CacheLogger, CachePolicy, FileCache}
77

88
import scala.cli.commands.tags
99
import scala.concurrent.duration.Duration
@@ -26,7 +26,12 @@ final case class CoursierOptions(
2626
@HelpMessage("Enable checksum validation of artifacts downloaded by coursier")
2727
@Tag(tags.implementation)
2828
@Hidden
29-
coursierValidateChecksums: Option[Boolean] = None
29+
coursierValidateChecksums: Option[Boolean] = None,
30+
31+
@Group(HelpGroup.Dependency.toString)
32+
@HelpMessage("Disable using the network to download artifacts, use the local cache only")
33+
@Tag(tags.experimental)
34+
offline: Option[Boolean] = None
3035
) {
3136
// format: on
3237

@@ -42,6 +47,9 @@ final case class CoursierOptions(
4247
baseCache = baseCache.withTtl(ttl0)
4348
for (loc <- cache.filter(_.trim.nonEmpty))
4449
baseCache = baseCache.withLocation(loc)
50+
for (isOffline <- offline if isOffline)
51+
baseCache = baseCache.withCachePolicies(Seq(CachePolicy.LocalOnly))
52+
4553
baseCache
4654
}
4755
}

modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,8 @@ final case class SharedOptions(
401401
verbosity = Some(logging.verbosity),
402402
strictBloopJsonCheck = strictBloopJsonCheck,
403403
interactive = Some(() => interactive),
404-
exclude = exclude.map(Positioned.commandLine)
404+
exclude = exclude.map(Positioned.commandLine),
405+
offline = coursier.offline
405406
),
406407
notForBloopOptions = bo.PostBuildOptions(
407408
scalaJsLinkerOptions = linkerOptions(js),
@@ -543,7 +544,8 @@ final case class SharedOptions(
543544
new BloopCompilerMaker(
544545
value(bloopRifleConfig()),
545546
threads.bloop,
546-
strictBloopJsonCheckOrDefault
547+
strictBloopJsonCheckOrDefault,
548+
coursier.offline.getOrElse(false)
547549
)
548550
else
549551
SimpleScalaCompilerMaker("java", Nil)

modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,4 +1881,136 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
18811881
))
18821882
}
18831883
}
1884+
1885+
test("offline mode should fail on missing artifacts") {
1886+
// Kill bloop deamon to test scalac fallback
1887+
os.proc(TestUtil.cli, "--power", "bloop", "exit")
1888+
.call(cwd = os.pwd)
1889+
1890+
val depScalaVersion = actualScalaVersion match {
1891+
case sv if sv.startsWith("2.12") => "2.12"
1892+
case sv if sv.startsWith("2.13") => "2.13"
1893+
case _ => "3"
1894+
}
1895+
1896+
val dep = s"com.lihaoyi:os-lib_$depScalaVersion:0.9.1"
1897+
val inputs = TestInputs(
1898+
os.rel / "NoDeps.scala" ->
1899+
"""//> using jvm zulu:11
1900+
|object NoDeps extends App {
1901+
| println("Hello from NoDeps")
1902+
|}
1903+
|""".stripMargin,
1904+
os.rel / "WithDeps.scala" ->
1905+
s"""//> using jvm zulu:11
1906+
|//> using dep $dep
1907+
|
1908+
|object WithDeps extends App {
1909+
| println("Hello from WithDeps")
1910+
|}
1911+
|""".stripMargin
1912+
)
1913+
inputs.fromRoot { root =>
1914+
val cachePath = root / ".cache"
1915+
os.makeDir(cachePath)
1916+
1917+
val extraEnv = Map("COURSIER_CACHE" -> cachePath.toString)
1918+
1919+
val emptyCacheWalkSize = os.walk(cachePath).size
1920+
1921+
val noArtifactsRes = os.proc(
1922+
TestUtil.cli,
1923+
"--power",
1924+
"NoDeps.scala",
1925+
extraOptions,
1926+
"--offline",
1927+
"--cache",
1928+
cachePath.toString
1929+
)
1930+
.call(cwd = root, check = false, mergeErrIntoOut = true)
1931+
expect(noArtifactsRes.exitCode == 1)
1932+
1933+
// Cache unchanged
1934+
expect(emptyCacheWalkSize == os.walk(cachePath).size)
1935+
1936+
// Download the artifacts for scala
1937+
os.proc(TestUtil.cs, "install", s"scala:$actualScalaVersion")
1938+
.call(cwd = root, env = extraEnv)
1939+
os.proc(TestUtil.cs, "install", s"scalac:$actualScalaVersion")
1940+
.call(cwd = root, env = extraEnv)
1941+
1942+
// Download JVM that won't suit Bloop, also no Bloop artifacts are present
1943+
os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:11")
1944+
.call(cwd = root, env = extraEnv)
1945+
1946+
val scalaJvmCacheWalkSize = os.walk(cachePath).size
1947+
1948+
val scalaAndJvmRes = os.proc(
1949+
TestUtil.cli,
1950+
"--power",
1951+
"NoDeps.scala",
1952+
extraOptions,
1953+
"--offline",
1954+
"--cache",
1955+
cachePath.toString,
1956+
"-v",
1957+
"-v"
1958+
)
1959+
.call(cwd = root, mergeErrIntoOut = true)
1960+
expect(scalaAndJvmRes.exitCode == 0)
1961+
expect(scalaAndJvmRes.out.trim().contains(
1962+
"Offline mode is ON and Bloop could not be fetched from the local cache, using scalac as fallback"
1963+
))
1964+
expect(scalaAndJvmRes.out.trim().contains("Hello from NoDeps"))
1965+
1966+
// Cache unchanged
1967+
expect(scalaJvmCacheWalkSize == os.walk(cachePath).size)
1968+
1969+
// Missing dependencies
1970+
val missingDepsRes = os.proc(
1971+
TestUtil.cli,
1972+
"--power",
1973+
"WithDeps.scala",
1974+
extraOptions,
1975+
"--offline",
1976+
"--cache",
1977+
cachePath.toString
1978+
)
1979+
.call(cwd = root, check = false, mergeErrIntoOut = true)
1980+
expect(missingDepsRes.exitCode == 1)
1981+
expect(missingDepsRes.out.trim().contains("Error downloading com.lihaoyi:os-lib"))
1982+
1983+
// Cache unchanged
1984+
expect(scalaJvmCacheWalkSize == os.walk(cachePath).size)
1985+
1986+
// Download dependencies
1987+
os.proc(TestUtil.cs, "fetch", dep)
1988+
.call(cwd = root, env = extraEnv)
1989+
1990+
val withDependencyCacheWalkSize = os.walk(cachePath).size
1991+
1992+
val depsRes = os.proc(
1993+
TestUtil.cli,
1994+
"--power",
1995+
"WithDeps.scala",
1996+
extraOptions,
1997+
"--offline",
1998+
"--cache",
1999+
cachePath.toString,
2000+
"-v",
2001+
"-v"
2002+
)
2003+
.call(cwd = root, mergeErrIntoOut = true)
2004+
expect(depsRes.exitCode == 0)
2005+
expect(
2006+
depsRes.out.trim().contains(
2007+
"Offline mode is ON and Bloop could not be fetched from the local cache, using scalac as fallback"
2008+
)
2009+
)
2010+
expect(depsRes.out.trim().contains("Hello from WithDeps"))
2011+
2012+
// Cache changed
2013+
expect(withDependencyCacheWalkSize == os.walk(cachePath).size)
2014+
}
2015+
}
18842016
}

modules/options/src/main/scala/scala/build/options/BuildOptions.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package scala.build.options
22

33
import com.github.plokhotnyuk.jsoniter_scala.core.*
4-
import coursier.cache.{ArchiveCache, FileCache}
4+
import coursier.cache.{ArchiveCache, FileCache, UnArchiver}
55
import coursier.core.{Repository, Version}
66
import coursier.parse.RepositoryParser
77
import coursier.util.{Artifact, Task}
@@ -305,6 +305,8 @@ final case class BuildOptions(
305305
val svOpt: Option[String] = scalaOptions.scalaVersion match {
306306
case Some(MaybeScalaVersion(None)) =>
307307
None
308+
case Some(MaybeScalaVersion(Some(svInput))) if internal.offline.getOrElse(false) =>
309+
Some(svInput)
308310
case Some(MaybeScalaVersion(Some(svInput))) =>
309311
val sv = value {
310312
svInput match {

modules/options/src/main/scala/scala/build/options/InternalOptions.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ final case class InternalOptions(
2525
*/
2626
keepResolution: Boolean = false,
2727
extraSourceFiles: Seq[Positioned[os.Path]] = Nil,
28-
exclude: Seq[Positioned[String]] = Nil
28+
exclude: Seq[Positioned[String]] = Nil,
29+
offline: Option[Boolean] = None
2930
) {
3031
def verbosityOrDefault: Int = verbosity.getOrElse(0)
3132
def strictBloopJsonCheckOrDefault: Boolean =

website/docs/reference/cli-options.md

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,33 @@ Aliases: `-f`
213213

214214
Force overwriting values for key
215215

216+
## Coursier options
217+
218+
Available in commands:
219+
220+
[`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall)
221+
222+
<!-- Automatically generated, DO NOT EDIT MANUALLY -->
223+
224+
### `--ttl`
225+
226+
[Internal]
227+
Specify a TTL for changing dependencies, such as snapshots
228+
229+
### `--cache`
230+
231+
[Internal]
232+
Set the coursier cache location
233+
234+
### `--coursier-validate-checksums`
235+
236+
[Internal]
237+
Enable checksum validation of artifacts downloaded by coursier
238+
239+
### `--offline`
240+
241+
Disable using the network to download artifacts, use the local cache only
242+
216243
## Cross options
217244

218245
Available in commands:
@@ -1848,29 +1875,6 @@ Aliases: `--name`
18481875
[Internal]
18491876
Name of BSP
18501877

1851-
### Coursier options
1852-
1853-
Available in commands:
1854-
1855-
[`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall)
1856-
1857-
<!-- Automatically generated, DO NOT EDIT MANUALLY -->
1858-
1859-
### `--ttl`
1860-
1861-
[Internal]
1862-
Specify a TTL for changing dependencies, such as snapshots
1863-
1864-
### `--cache`
1865-
1866-
[Internal]
1867-
Set the coursier cache location
1868-
1869-
### `--coursier-validate-checksums`
1870-
1871-
[Internal]
1872-
Enable checksum validation of artifacts downloaded by coursier
1873-
18741878
### Default file options
18751879

18761880
Available in commands:

0 commit comments

Comments
 (0)