Skip to content

Commit 24036ed

Browse files
authored
Add toolkit using directive and cli options (#1768)
* Add toolkit using directive and cli options * Add intergration test
1 parent e4cfd02 commit 24036ed

File tree

13 files changed

+205
-3
lines changed

13 files changed

+205
-3
lines changed

build.sc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ trait Core extends ScalaCliSbtModule with ScalaCliPublishModule with HasTests
394394
| def scalafmtName = "${Deps.scalafmtCli.dep.module.name.value}"
395395
| def defaultScalafmtVersion = "${Deps.scalafmtCli.dep.version}"
396396
|
397+
| def toolkitOrganization = "${Deps.toolkit.dep.module.organization.value}"
398+
| def toolkitName = "${Deps.toolkit.dep.module.name.value}"
399+
|
397400
| def defaultScalaVersion = "${Scala.defaultUser}"
398401
| def defaultScala212Version = "${Scala.scala212}"
399402
| def defaultScala213Version = "${Scala.scala213}"

modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ case object ScalaPreprocessor extends Preprocessor {
7070
directives.ScalaNative.handler,
7171
directives.ScalaVersion.handler,
7272
directives.Sources.handler,
73-
directives.Tests.handler
73+
directives.Tests.handler,
74+
directives.Toolkit.handler
7475
).map(_.mapE(_.buildOptions))
7576

7677
val requireDirectiveHandlers: Seq[DirectiveHandler[BuildRequirements]] =

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ package scala.build.tests
33
import com.eed3si9n.expecty.Expecty.expect
44

55
import java.io.IOException
6-
import scala.build.{BuildThreads, Directories, LocalRepo}
6+
import scala.build.{BuildThreads, Directories, LocalRepo, Position, Positioned}
77
import scala.build.options.{BuildOptions, InternalOptions, MaybeScalaVersion}
88
import scala.build.tests.util.BloopServer
99
import build.Ops.EitherThrowOps
1010
import scala.build.Position
11+
import dependency.AnyDependency
1112

1213
class DirectiveTests extends munit.FunSuite {
1314

@@ -87,4 +88,23 @@ class DirectiveTests extends munit.FunSuite {
8788
}
8889
}
8990

91+
test("resolve toolkit dependency") {
92+
val testInputs = TestInputs(
93+
os.rel / "simple.sc" ->
94+
"""//> using toolkit "latest"
95+
|""".stripMargin
96+
)
97+
testInputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) {
98+
(_, _, maybeBuild) =>
99+
val build = maybeBuild.orThrow
100+
val dep = build.options.classPathOptions.extraDependencies.toSeq.headOption
101+
assert(dep.nonEmpty)
102+
103+
val toolkitDep = dep.get.value
104+
expect(toolkitDep.organization == "org.virtuslab")
105+
expect(toolkitDep.name == "toolkit")
106+
expect(toolkitDep.version == "latest.release")
107+
}
108+
}
109+
90110
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import scala.build.EitherCps.{either, value}
1616
import scala.build.*
1717
import scala.build.blooprifle.BloopRifleConfig
1818
import scala.build.compiler.{BloopCompilerMaker, ScalaCompilerMaker, SimpleScalaCompilerMaker}
19+
import scala.build.directives.DirectiveDescription
1920
import scala.build.errors.{AmbiguousPlatformError, BuildException}
2021
import scala.build.input.{Element, Inputs, ResourceDirectory}
2122
import scala.build.interactive.Interactive
@@ -24,6 +25,7 @@ import scala.build.internal.CsLoggerUtil.*
2425
import scala.build.internal.{Constants, FetchExternalBinary, OsLibc, Util}
2526
import scala.build.options.ScalaVersionUtil.fileWithTtl0
2627
import scala.build.options.{Platform, ScalacOpt, ShadowingSeq}
28+
import scala.build.preprocessing.directives.Toolkit
2729
import scala.build.options as bo
2830
import scala.cli.ScalaCli
2931
import scala.cli.commands.publish.ConfigUtil.*
@@ -189,6 +191,10 @@ final case class SharedOptions(
189191
@ValueDescription("/example/path")
190192
@Tag(tags.must)
191193
compilationOutput: Option[String] = None,
194+
@HelpMessage("Add toolkit to classPath")
195+
@ValueDescription("version|latest")
196+
@Name("toolkit")
197+
withToolkit: Option[String] = None
192198
) extends HasLoggingOptions {
193199
// format: on
194200

@@ -322,7 +328,7 @@ final case class SharedOptions(
322328
SharedOptions.parseDependencies(
323329
dependencies.dependency.map(Positioned.none),
324330
ignoreErrors
325-
)
331+
) ++ SharedOptions.resolveToolkitDependency(withToolkit)
326332
)
327333
),
328334
internal = bo.InternalOptions(
@@ -647,4 +653,7 @@ object SharedOptions {
647653
}
648654
}
649655

656+
private def resolveToolkitDependency(toolkitVersion: Option[String])
657+
: Seq[Positioned[AnyDependency]] =
658+
toolkitVersion.toList.map(Positioned.commandLine).map(Toolkit.resolveDependency)
650659
}

modules/cli/src/test/scala/cli/commands/tests/RunOptionsTests.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,20 @@ class RunOptionsTests extends munit.FunSuite {
2020
expect(buildOptions.notForBloopOptions.scalaPyVersion.contains(ver))
2121
}
2222

23+
test("resolve toolkit dependency") {
24+
val runOptions = RunOptions(
25+
shared = SharedOptions(
26+
withToolkit = Some("latest")
27+
)
28+
)
29+
val buildOptions = Run.buildOptions(runOptions).value
30+
val dep = buildOptions.classPathOptions.extraDependencies.toSeq.headOption
31+
assert(dep.nonEmpty)
32+
33+
val toolkitDep = dep.get.value
34+
expect(toolkitDep.organization == "org.virtuslab")
35+
expect(toolkitDep.name == "toolkit")
36+
expect(toolkitDep.version == "latest.release")
37+
}
38+
2339
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package scala.build.preprocessing.directives
2+
3+
import coursier.core.{Repository, Version}
4+
import dependency.*
5+
6+
import scala.annotation.tailrec
7+
import scala.build.EitherCps.{either, value}
8+
import scala.build.directives.*
9+
import scala.build.errors.BuildException
10+
import scala.build.internal.Constants
11+
import scala.build.options.{BuildOptions, ClassPathOptions, JavaOpt, Scope, ShadowingSeq}
12+
import scala.build.{Artifacts, Logger, Positioned, options}
13+
import scala.cli.commands.SpecificationLevel
14+
15+
@DirectiveGroupName("Toolkit")
16+
@DirectiveExamples("//> using toolkit \"0.1.0\"")
17+
@DirectiveExamples("//> using toolkit \"latest\"")
18+
@DirectiveUsage(
19+
"//> using toolkit _version_",
20+
"`//> using toolkit` _version_"
21+
)
22+
@DirectiveDescription("Use a toolkit as dependency")
23+
@DirectiveLevel(SpecificationLevel.SHOULD)
24+
// format: off
25+
final case class Toolkit(
26+
toolkit: Option[Positioned[String]] = None
27+
) extends HasBuildOptions {
28+
// format: on
29+
def buildOptions: Either[BuildException, BuildOptions] = {
30+
val toolkitDep =
31+
toolkit.toList.map(Toolkit.resolveDependency)
32+
val buildOpt = BuildOptions(
33+
classPathOptions = ClassPathOptions(
34+
extraDependencies = ShadowingSeq.from(toolkitDep)
35+
)
36+
)
37+
Right(buildOpt)
38+
}
39+
}
40+
41+
object Toolkit {
42+
def resolveDependency(toolkitVersion: Positioned[String]) = toolkitVersion.map(version =>
43+
val v = if version == "latest" then "latest.release" else version
44+
dep"${Constants.toolkitOrganization}::${Constants.toolkitName}:$v"
45+
)
46+
val handler: DirectiveHandler[Toolkit] = DirectiveHandler.derive
47+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,4 +1112,19 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
11121112
expect(output.contains(exceptionMsg))
11131113
}
11141114
}
1115+
1116+
test("should add toolkit to classpath") {
1117+
val inputs = TestInputs(
1118+
os.rel / "Hello.scala" ->
1119+
s"""object Hello extends App {
1120+
| println(os.pwd) // os lib should be added to classpath by toolkit
1121+
|}""".stripMargin
1122+
)
1123+
inputs.fromRoot { root =>
1124+
val output = os.proc(TestUtil.cli, ".", "--toolkit", "0.1.4")
1125+
.call(cwd = root).out.trim()
1126+
1127+
expect(output == root.toString())
1128+
}
1129+
}
11151130
}

project/deps.sc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ object Deps {
177177
def svm = ivy"org.graalvm.nativeimage:svm:$graalVmVersion"
178178
def swoval = ivy"com.swoval:file-tree-views:2.1.9"
179179
def testInterface = ivy"org.scala-sbt:test-interface:1.0"
180+
def toolkit = ivy"org.virtuslab:toolkit:0.1.0"
180181
def usingDirectives = ivy"org.virtuslab:using_directives:0.0.10"
181182
// Lives at https://github.com/scala-cli/no-crc32-zip-input-stream, see #865
182183
// This provides a ZipInputStream that doesn't verify CRC32 checksums, that users

website/docs/reference/cli-options.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,12 @@ Aliases: `--compile-out`, `--compile-output`, `-d`, `--destination`, `--output-d
14141414

14151415
Copy compilation results to output directory using either relative or absolute path
14161416

1417+
### `--with-toolkit`
1418+
1419+
Aliases: `--toolkit`
1420+
1421+
Add toolkit to classPath
1422+
14171423
## Snippet options
14181424

14191425
Available in commands:

website/docs/reference/directives.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,17 @@ Set the test framework
324324
#### Examples
325325
`//> using testFramework "utest.runner.Framework"`
326326

327+
### Toolkit
328+
329+
Use a toolkit as dependency
330+
331+
`//> using toolkit` _version_
332+
333+
#### Examples
334+
`//> using toolkit "0.1.0"`
335+
336+
`//> using toolkit "latest"`
337+
327338

328339
## target directives
329340

0 commit comments

Comments
 (0)