From 24036ed191dce7360143db9a35215ddf78f81b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= <46607934+lwronski@users.noreply.github.com> Date: Mon, 16 Jan 2023 23:23:16 +0100 Subject: [PATCH] Add toolkit using directive and cli options (#1768) * Add toolkit using directive and cli options * Add intergration test --- build.sc | 3 ++ .../preprocessing/ScalaPreprocessor.scala | 3 +- .../scala/build/tests/DirectiveTests.scala | 22 +++++++- .../cli/commands/shared/SharedOptions.scala | 11 +++- .../cli/commands/tests/RunOptionsTests.scala | 16 ++++++ .../preprocessing/directives/Toolkit.scala | 47 ++++++++++++++++ .../cli/integration/RunTestDefinitions.scala | 15 ++++++ project/deps.sc | 1 + website/docs/reference/cli-options.md | 6 +++ website/docs/reference/directives.md | 11 ++++ .../reference/scala-command/cli-options.md | 8 +++ .../reference/scala-command/directives.md | 11 ++++ .../scala-command/runner-specification.md | 54 +++++++++++++++++++ 13 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 modules/directives/src/main/scala/scala/build/preprocessing/directives/Toolkit.scala diff --git a/build.sc b/build.sc index 9aef3dfe12..3725aaef94 100644 --- a/build.sc +++ b/build.sc @@ -394,6 +394,9 @@ trait Core extends ScalaCliSbtModule with ScalaCliPublishModule with HasTests | def scalafmtName = "${Deps.scalafmtCli.dep.module.name.value}" | def defaultScalafmtVersion = "${Deps.scalafmtCli.dep.version}" | + | def toolkitOrganization = "${Deps.toolkit.dep.module.organization.value}" + | def toolkitName = "${Deps.toolkit.dep.module.name.value}" + | | def defaultScalaVersion = "${Scala.defaultUser}" | def defaultScala212Version = "${Scala.scala212}" | def defaultScala213Version = "${Scala.scala213}" diff --git a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala index 7b603e0add..49516c43aa 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala @@ -70,7 +70,8 @@ case object ScalaPreprocessor extends Preprocessor { directives.ScalaNative.handler, directives.ScalaVersion.handler, directives.Sources.handler, - directives.Tests.handler + directives.Tests.handler, + directives.Toolkit.handler ).map(_.mapE(_.buildOptions)) val requireDirectiveHandlers: Seq[DirectiveHandler[BuildRequirements]] = diff --git a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala index a120f45059..181019e7db 100644 --- a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala @@ -3,11 +3,12 @@ package scala.build.tests import com.eed3si9n.expecty.Expecty.expect import java.io.IOException -import scala.build.{BuildThreads, Directories, LocalRepo} +import scala.build.{BuildThreads, Directories, LocalRepo, Position, Positioned} import scala.build.options.{BuildOptions, InternalOptions, MaybeScalaVersion} import scala.build.tests.util.BloopServer import build.Ops.EitherThrowOps import scala.build.Position +import dependency.AnyDependency class DirectiveTests extends munit.FunSuite { @@ -87,4 +88,23 @@ class DirectiveTests extends munit.FunSuite { } } + test("resolve toolkit dependency") { + val testInputs = TestInputs( + os.rel / "simple.sc" -> + """//> using toolkit "latest" + |""".stripMargin + ) + testInputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { + (_, _, maybeBuild) => + val build = maybeBuild.orThrow + val dep = build.options.classPathOptions.extraDependencies.toSeq.headOption + assert(dep.nonEmpty) + + val toolkitDep = dep.get.value + expect(toolkitDep.organization == "org.virtuslab") + expect(toolkitDep.name == "toolkit") + expect(toolkitDep.version == "latest.release") + } + } + } diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala index 535176f220..29772606cd 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala @@ -16,6 +16,7 @@ import scala.build.EitherCps.{either, value} import scala.build.* import scala.build.blooprifle.BloopRifleConfig import scala.build.compiler.{BloopCompilerMaker, ScalaCompilerMaker, SimpleScalaCompilerMaker} +import scala.build.directives.DirectiveDescription import scala.build.errors.{AmbiguousPlatformError, BuildException} import scala.build.input.{Element, Inputs, ResourceDirectory} import scala.build.interactive.Interactive @@ -24,6 +25,7 @@ import scala.build.internal.CsLoggerUtil.* import scala.build.internal.{Constants, FetchExternalBinary, OsLibc, Util} import scala.build.options.ScalaVersionUtil.fileWithTtl0 import scala.build.options.{Platform, ScalacOpt, ShadowingSeq} +import scala.build.preprocessing.directives.Toolkit import scala.build.options as bo import scala.cli.ScalaCli import scala.cli.commands.publish.ConfigUtil.* @@ -189,6 +191,10 @@ final case class SharedOptions( @ValueDescription("/example/path") @Tag(tags.must) compilationOutput: Option[String] = None, + @HelpMessage("Add toolkit to classPath") + @ValueDescription("version|latest") + @Name("toolkit") + withToolkit: Option[String] = None ) extends HasLoggingOptions { // format: on @@ -322,7 +328,7 @@ final case class SharedOptions( SharedOptions.parseDependencies( dependencies.dependency.map(Positioned.none), ignoreErrors - ) + ) ++ SharedOptions.resolveToolkitDependency(withToolkit) ) ), internal = bo.InternalOptions( @@ -647,4 +653,7 @@ object SharedOptions { } } + private def resolveToolkitDependency(toolkitVersion: Option[String]) + : Seq[Positioned[AnyDependency]] = + toolkitVersion.toList.map(Positioned.commandLine).map(Toolkit.resolveDependency) } diff --git a/modules/cli/src/test/scala/cli/commands/tests/RunOptionsTests.scala b/modules/cli/src/test/scala/cli/commands/tests/RunOptionsTests.scala index 7d08074e4a..6a433c19fa 100644 --- a/modules/cli/src/test/scala/cli/commands/tests/RunOptionsTests.scala +++ b/modules/cli/src/test/scala/cli/commands/tests/RunOptionsTests.scala @@ -20,4 +20,20 @@ class RunOptionsTests extends munit.FunSuite { expect(buildOptions.notForBloopOptions.scalaPyVersion.contains(ver)) } + test("resolve toolkit dependency") { + val runOptions = RunOptions( + shared = SharedOptions( + withToolkit = Some("latest") + ) + ) + val buildOptions = Run.buildOptions(runOptions).value + val dep = buildOptions.classPathOptions.extraDependencies.toSeq.headOption + assert(dep.nonEmpty) + + val toolkitDep = dep.get.value + expect(toolkitDep.organization == "org.virtuslab") + expect(toolkitDep.name == "toolkit") + expect(toolkitDep.version == "latest.release") + } + } diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/Toolkit.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Toolkit.scala new file mode 100644 index 0000000000..ba2e6d6c39 --- /dev/null +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Toolkit.scala @@ -0,0 +1,47 @@ +package scala.build.preprocessing.directives + +import coursier.core.{Repository, Version} +import dependency.* + +import scala.annotation.tailrec +import scala.build.EitherCps.{either, value} +import scala.build.directives.* +import scala.build.errors.BuildException +import scala.build.internal.Constants +import scala.build.options.{BuildOptions, ClassPathOptions, JavaOpt, Scope, ShadowingSeq} +import scala.build.{Artifacts, Logger, Positioned, options} +import scala.cli.commands.SpecificationLevel + +@DirectiveGroupName("Toolkit") +@DirectiveExamples("//> using toolkit \"0.1.0\"") +@DirectiveExamples("//> using toolkit \"latest\"") +@DirectiveUsage( + "//> using toolkit _version_", + "`//> using toolkit` _version_" +) +@DirectiveDescription("Use a toolkit as dependency") +@DirectiveLevel(SpecificationLevel.SHOULD) +// format: off +final case class Toolkit( + toolkit: Option[Positioned[String]] = None +) extends HasBuildOptions { + // format: on + def buildOptions: Either[BuildException, BuildOptions] = { + val toolkitDep = + toolkit.toList.map(Toolkit.resolveDependency) + val buildOpt = BuildOptions( + classPathOptions = ClassPathOptions( + extraDependencies = ShadowingSeq.from(toolkitDep) + ) + ) + Right(buildOpt) + } +} + +object Toolkit { + def resolveDependency(toolkitVersion: Positioned[String]) = toolkitVersion.map(version => + val v = if version == "latest" then "latest.release" else version + dep"${Constants.toolkitOrganization}::${Constants.toolkitName}:$v" + ) + val handler: DirectiveHandler[Toolkit] = DirectiveHandler.derive +} diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala index cf92457c20..033f5026cf 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -1112,4 +1112,19 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) expect(output.contains(exceptionMsg)) } } + + test("should add toolkit to classpath") { + val inputs = TestInputs( + os.rel / "Hello.scala" -> + s"""object Hello extends App { + | println(os.pwd) // os lib should be added to classpath by toolkit + |}""".stripMargin + ) + inputs.fromRoot { root => + val output = os.proc(TestUtil.cli, ".", "--toolkit", "0.1.4") + .call(cwd = root).out.trim() + + expect(output == root.toString()) + } + } } diff --git a/project/deps.sc b/project/deps.sc index 0d3700db80..3ccc5d820a 100644 --- a/project/deps.sc +++ b/project/deps.sc @@ -177,6 +177,7 @@ object Deps { def svm = ivy"org.graalvm.nativeimage:svm:$graalVmVersion" def swoval = ivy"com.swoval:file-tree-views:2.1.9" def testInterface = ivy"org.scala-sbt:test-interface:1.0" + def toolkit = ivy"org.virtuslab:toolkit:0.1.0" def usingDirectives = ivy"org.virtuslab:using_directives:0.0.10" // Lives at https://github.com/scala-cli/no-crc32-zip-input-stream, see #865 // This provides a ZipInputStream that doesn't verify CRC32 checksums, that users diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index bb0cf63eca..bc27f18e35 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -1414,6 +1414,12 @@ Aliases: `--compile-out`, `--compile-output`, `-d`, `--destination`, `--output-d Copy compilation results to output directory using either relative or absolute path +### `--with-toolkit` + +Aliases: `--toolkit` + +Add toolkit to classPath + ## Snippet options Available in commands: diff --git a/website/docs/reference/directives.md b/website/docs/reference/directives.md index ee1b559f81..2a9af6cdf9 100644 --- a/website/docs/reference/directives.md +++ b/website/docs/reference/directives.md @@ -324,6 +324,17 @@ Set the test framework #### Examples `//> using testFramework "utest.runner.Framework"` +### Toolkit + +Use a toolkit as dependency + +`//> using toolkit` _version_ + +#### Examples +`//> using toolkit "0.1.0"` + +`//> using toolkit "latest"` + ## target directives diff --git a/website/docs/reference/scala-command/cli-options.md b/website/docs/reference/scala-command/cli-options.md index 2708d3bbc3..e4b6dda6e6 100644 --- a/website/docs/reference/scala-command/cli-options.md +++ b/website/docs/reference/scala-command/cli-options.md @@ -970,6 +970,14 @@ Aliases: `--compile-out`, `--compile-output`, `-d`, `--destination`, `--output-d Copy compilation results to output directory using either relative or absolute path +### `--with-toolkit` + +Aliases: `--toolkit` + +`IMPLEMENTATION specific` per Scala Runner specification + +Add toolkit to classPath + ## Snippet options Available in commands: diff --git a/website/docs/reference/scala-command/directives.md b/website/docs/reference/scala-command/directives.md index 4eb6c5a38e..b61c564327 100644 --- a/website/docs/reference/scala-command/directives.md +++ b/website/docs/reference/scala-command/directives.md @@ -254,3 +254,14 @@ Set the test framework #### Examples `//> using testFramework "utest.runner.Framework"` +### Toolkit + +Use a toolkit as dependency + +`//> using toolkit` _version_ + +#### Examples +`//> using toolkit "0.1.0"` + +`//> using toolkit "latest"` + diff --git a/website/docs/reference/scala-command/runner-specification.md b/website/docs/reference/scala-command/runner-specification.md index f7408f4ba6..48ec83d1b7 100644 --- a/website/docs/reference/scala-command/runner-specification.md +++ b/website/docs/reference/scala-command/runner-specification.md @@ -556,6 +556,12 @@ Aliases: `--help-fmt` ,`--fmt-help` ,`--scalafmt-help` +**--with-toolkit** + +Add toolkit to classPath + +Aliases: `--toolkit` + --- @@ -1075,6 +1081,12 @@ Aliases: `--help-fmt` ,`--fmt-help` ,`--scalafmt-help` +**--with-toolkit** + +Add toolkit to classPath + +Aliases: `--toolkit` + --- @@ -1596,6 +1608,12 @@ Aliases: `--help-fmt` ,`--fmt-help` ,`--scalafmt-help` +**--with-toolkit** + +Add toolkit to classPath + +Aliases: `--toolkit` + **--java-prop-option** Add java properties. Note that options equal `-Dproperty=value` are assumed to be java properties and don't require to be passed after `--java-prop`. @@ -2147,6 +2165,12 @@ Aliases: `--help-fmt` ,`--fmt-help` ,`--scalafmt-help` +**--with-toolkit** + +Add toolkit to classPath + +Aliases: `--toolkit` + **--java-prop-option** Add java properties. Note that options equal `-Dproperty=value` are assumed to be java properties and don't require to be passed after `--java-prop`. @@ -2720,6 +2744,12 @@ Aliases: `--help-fmt` ,`--fmt-help` ,`--scalafmt-help` +**--with-toolkit** + +Add toolkit to classPath + +Aliases: `--toolkit` + **--java-prop-option** Add java properties. Note that options equal `-Dproperty=value` are assumed to be java properties and don't require to be passed after `--java-prop`. @@ -3243,6 +3273,12 @@ Aliases: `--help-fmt` ,`--fmt-help` ,`--scalafmt-help` +**--with-toolkit** + +Add toolkit to classPath + +Aliases: `--toolkit` + **--respect-project-filters** Use project filters defined in the configuration. Turned on by default, use `--respect-project-filters:false` to disable it. @@ -3832,6 +3868,12 @@ Aliases: `--help-fmt` ,`--fmt-help` ,`--scalafmt-help` +**--with-toolkit** + +Add toolkit to classPath + +Aliases: `--toolkit` + **--java-prop-option** Add java properties. Note that options equal `-Dproperty=value` are assumed to be java properties and don't require to be passed after `--java-prop`. @@ -4418,6 +4460,12 @@ Aliases: `--help-fmt` ,`--fmt-help` ,`--scalafmt-help` +**--with-toolkit** + +Add toolkit to classPath + +Aliases: `--toolkit` + **--json-options** Command-line options JSON file @@ -5231,6 +5279,12 @@ Aliases: `--help-fmt` ,`--fmt-help` ,`--scalafmt-help` +**--with-toolkit** + +Add toolkit to classPath + +Aliases: `--toolkit` + **--bsp-directory** Custom BSP configuration location