Use sbt settingKey in function call - scala

We have a build.sbt file like this, which is working fine:
name := "Foo"
version := "0.1"
scalaVersion := "2.12.8"
def aws(module: String): ModuleID = "com.amazonaws" % module % "1.11.250"
lazy val Core = project
.settings(
libraryDependencies ++= Seq(
aws("aws-java-sdk-s3"),
aws("aws-java-sdk-dynamodb"),
)
)
Basically, the project has a few AWS SDK library dependencies and we want to avoid typing the groupID (e.g. "com.amazonaws") and the revision (e.g. "1.11.250") multiple times and that's why we have this line:
def aws(module: String): ModuleID = "com.amazonaws" % module % "1.11.250"
However, since we have many repos like this and we want to move this definition to a custom sbt-plugin. To begin with, we try this:
name := "Foo"
version := "0.1"
scalaVersion := "2.12.8"
val awsVersion = settingKey[String]("The version of aws SDK used for building.") // line 5
def aws(module: String): ModuleID = "com.amazonaws" % module % awsVersion.value // line 6
awsVersion := "1.11.250"
lazy val Core = project
.settings(
libraryDependencies ++= Seq(
aws("aws-java-sdk-s3"),
aws("aws-java-sdk-dynamodb"),
)
)
However, line 6 is producing an error:
error: value can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.
The idea is that we'll move line 5 and 6 above to our plugin eventually so that we can use it like this:
name := "Foo"
version := "0.1"
scalaVersion := "2.12.8"
awsVersion := "1.11.250"
lazy val Core = project
.settings(
libraryDependencies ++= Seq(
aws("aws-java-sdk-s3"),
aws("aws-java-sdk-dynamodb"),
)
)
Any solution or work around for the error above?
We've also tried this:
def aws(module: String, version: String): ModuleID = "com.amazonaws" % module % version
... which is then used like this:
awsVersion := "1.11.250"
lazy val Core = project
.settings(
libraryDependencies ++= Seq(
aws("aws-java-sdk-s3", awsVersion.value),
aws("aws-java-sdk-dynamodb", awsVersion.value),
)
)
That works fine though a bit annoying to use and it defeats the purpose of using a settingKey to begin with.

You cannot use setting or task values in locally defined methods like aws. The values can be used only within other setting or task definitions, ie the error message such as :=, +=, ++=, Def.task, or Def.setting.
This is what you could do.
Create AutoPlugin in project folder.
import sbt.{AutoPlugin, Def, ModuleID, settingKey}
import sbt.PluginTrigger.AllRequirements
import sbt._
object AwsPlugin extends AutoPlugin {
override def trigger = AllRequirements
type GetAWS = String => ModuleID
object autoImport {
val awsVersion =
settingKey[String]("The version of aws SDK used for building.")
val awsLibrary = settingKey[GetAWS]("Builds given AWS library")
}
import autoImport._
override def projectSettings: Seq[Def.Setting[_]] = Seq(
awsLibrary := { id =>
"com.amazonaws" % id % awsVersion.value
}
)
}
Use it in this way in build.sbt
awsVersion in ThisBuild := "1.11.250"
lazy val Core = project
.settings(
libraryDependencies ++= Seq(
awsLibrary.value("aws-java-sdk-s3"),
awsLibrary.value("aws-java-sdk-dynamodb"),
)
)

Related

SBT test:compile is not satisfied by libraryDependencies in Test

I have the snippet of test case below under src/test/scala:
package example.module
import utest._
object ModuleSpec extends TestSuite { ... }
... which compiles just fine with this snippet of build.sbt below:
val testDependencies: Seq[ModuleID] =
Seq(
"com.lihaoyi" %% "utest" % "0.6.5" % "test",
)
def testSettings: Seq[Setting[_]] =
Seq(
libraryDependencies ++= testDependencies)
lazy val root =
(project in file("."))
.aggregate(subproject)
lazy val subproject =
(project in file("subproject"))
.settings(testSettings: _*)
My question is:
Why the test code does not compile anymore if I add Test axis to libraryDependencies, like shown below?
def testSettings: Seq[Setting[_]] =
Seq(
libraryDependencies in Test ++= testDependencies)
In more detail:
The line containing import utest._ fails to compile, meaning that the dependency which is now declared as part of libraryDependencies in Test was ignored.
Argumentation:
Since I'm compiling a class under src/main/test, I would expect that libraryDependencies in Test would be the necessary and the sufficient piece of information in this case. In order words: I would not expect that a wider scope would be needed.

sscalaJSModuleKind := ModuleKind.CommonJSModule - cannot invoke main method anymore :(

I am trying to build a new facade that uses a lot of JSImport statements. I wanted to put it in a subfolder of a project I am currently working on, to improve it while at it.
Before my root build.sbt looked like this for the scala.js part:
lazy val client = (project in file("modules/client"))
.enablePlugins(ScalaJSPlugin, ScalaJSWeb)
.settings(generalSettings: _*)
.settings(
name := "client",
libraryDependencies += CrossDependencies.scalaTags,
persistLauncher := true
)
now I added this: scalaJSModuleKind := ModuleKind.CommonJSModule, which is incompatible with the persistLauncher setting, so I removed persistLauncher := true
Of course in my view I could no longer just add client-launcher.js. So I tried to wrap the main-method call manually, like this:
<script type="text/javascript">
tld.test.Test().main()
</script>
Now, this does NOT work IF scalaJSModuleKind := ModuleKind.CommonJSModule is added to my build.sbt. If I remove that setting everything works just fine.
This is my Test
package tld.test
import org.scalajs.dom
import scala.scalajs.js.JSApp
object Test extends JSApp
{
import scalatags.JsDom.all._
def main(): Unit =
{
// Add js script dynamically
val s = script(
"alert('Hello World!')"
)
dom.document.getElementsByTagName("head")(0).appendChild(s.render)
}
}
Now, if I remove that ModuleKind-setting an alert pops up with 'Hello World', but if it's there nope. What is causing this and how can I prevent it?
edit
After answer from #sjrd I tried the following:
plugins.sbt:
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.5.0")
addSbtPlugin("ch.epfl.scala" % "sbt-web-scalajs-bundler" % "0.5.0")
build.sbt:
lazy val client = (project in file("modules/client"))
.enablePlugins(ScalaJSBundlerPlugin, ScalaJSWeb) // ScalaJSBundlerPlugin automatically enables ScalaJSPlugin
.settings(generalSettings: _*)
.settings(
name := "client"
, libraryDependencies += CrossDependencies.scalaTags
//, scalaJSModuleKind := ModuleKind.CommonJSModule // ScalaJSBundlerPlugin implicitly sets moduleKind to CommonJSModule enables ScalaJSPlugin
)
lazy val server = (project in file("modules/server"))
.enablePlugins(PlayScala, WebScalaJSBundlerPlugin)
.settings(generalSettings: _*)
.settings(
name := "server"
,libraryDependencies ++= Seq(
CrossDependencies.scalaTest,
CrossDependencies.scalactic,
CrossDependencies.scalaTags,
"com.typesafe.play" %% "play-json" % "2.6.0-M1")
,scalaJSProjects := Seq(client)
,pipelineStages in Assets := Seq(scalaJSPipeline)
//,pipelineStages := Seq(digest, gzip)
,compile in Compile := ((compile in Compile) dependsOn scalaJSPipeline).value
)
But during compilation I get:
ERROR in ./fastopt-launcher.js
[info] Module not found: Error: Cannot resolve 'file' or 'directory' /home/sorona/scalajstestbed/modules/client/target/scala-2.12/scalajs-bundler/main/client-fastopt.js in /home/sorona/scalajstestbed/modules/client/target/scala-2.12/scalajs-bundler/main
edit: Solution is to then include client-fastopt-bundle.js et voila
Changing the module kind significantly changes the shape of the output file, include its external "specification". In particular, it is not a script that can be embedded in Web page anymore. Instead, it is a CommonJS module.
To be able to include it in a Web page, you will need to bundle it. The best way to do so is too use scalajs-bundler.

How to write a plugin which depends on a task from another plugin?

There is a great sbt plugin sbt-dependency-graph, which provides a dependencyTree task to show the dependencies.
I want to write a sbt plugin which depends on it, but always fails.
build.sbt
sbtPlugin := true
name := "my-sbt-plugin-depends-on-another"
version := "0.1.2.1"
organization := "test20140913"
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.7.5")
src/main/scala/MySbtPlugin.scala
import sbt._
object MySbtPlugin extends AutoPlugin {
object autoImport {
lazy val hello = taskKey[Unit]("hello task from my plugin")
lazy val hello2 = taskKey[Unit]("hello task from my plugin2")
}
import autoImport._
override def trigger = allRequirements
override def requires = plugins.JvmPlugin
val helloSetting = hello := println("Hello from my plugin")
val helloSetting2 = hello2 := {
println("hello2, task result from another plugins:")
println(net.virtualvoid.sbt.graph.Plugin.dependencyTree.value)
println("=========================================")
}
override def projectSettings = Seq(
helloSetting, helloSetting2
)
}
Then I published it to local, and use it in another project:
build.sbt
name := "sbt--plugin-test"
version := "1.0"
scalaVersion := "2.11.6"
net.virtualvoid.sbt.graph.Plugin.graphSettings
project/plugins.scala
logLevel := Level.Info
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.7.5")
addSbtPlugin("test20140913" % "my-sbt-plugin-depends-on-another" % "0.1.2.1")
When I run sbt on the later project, it reports:
Reference to undefined setting:
*:dependencyTree from *:hello2 (/Users/twer/workspace/my-sbt-plugin-depends-on-another/src/main/scala/test20140913/MySbtPlugin.scala:38)
Did you mean provided:dependencyTree ?
at sbt.Init$class.Uninitialized(Settings.scala:262)
at sbt.Def$.Uninitialized(Def.scala:10)
at sbt.Init$class.delegate(Settings.scala:188)
at sbt.Def$.delegate(Def.scala:10)
Where is wrong?
PS: The plugin code is here: https://github.com/freewind/my-sbt-plugin-depends-on-another
dependencyTree is only defined for specific configurations (well all of them), but it automatically delegates to Compile in the shell.
Try defining hello2 like so:
val helloSetting2 = hello2 := {
println("hello2, task result from another plugins:")
import net.virtualvoid.sbt.graph.Plugin.dependencyTree
println((dependencyTree in Compile).value)
println("=========================================")
}

Factoring libraryDependencies in multi project Build.sbt

I'm trying to write a concise multi project Build.sbt, so I tried to put all library dependencies in root project and then make others depends on it. My Build.sbt looks like the following:
object KataBuild extends Build {
lazy val fizzBuzz = Project(
id = "fizzBuzz",
base = file("fizzBuzz"),
settings = Project.defaultSettings ++ Seq(
name := "fizzBuzz",
version := "1.0",
scalaVersion := "2.10.3"
)
)
lazy val kata = Project(
id = "scala-kata",
base = file("."),
settings = Project.defaultSettings ++ Seq(
name := "scala-kata",
version := "1.0",
scalaVersion := "2.10.3",
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "2.1.0" % "test"
)
)
) aggregate(fizzBuzz)
fizzBuzz dependsOn(kata)
}
But running test from the main project (scala-kata) fails to build test for fizzBuzz. What am I missing?
Your question is similar to this one. In short, fizzBuzz.dependsOn(kata) means that its compile configuration depends on the kata's compile configuration, but you want to link the test configurations.
The 'Per-configuration classpath dependencies' section of the sbt docs show you how you can make a test->test dependency instead.
However, if you are not going to use kata's test sources but are just looking for a way to include Scala-Test in fizzBuzz, just add it explicitly to fizzBuzz's library dependencies, too. You can define a helper value
lazy val scalaTest = "org.scalatest" %% "scalatest" % "2.1.0" % "test"
Then you can add it to be sub project's library dependencies (libraryDependencies += scalaTest).

Shapeless example with map won't compile (scala)

I'm trying to map over an HList in shapeless. The following example is derived from here:
import shapeless._
import poly._
object Main {
def main(args: Array[String]) = {
object choose extends (Set ~> Option) {
def apply[T](s : Set[T]) = s.headOption
}
val sets = Set(1) :: Set("foo") :: HNil
val opts = sets map choose // map selects cases of choose for each HList element
}
}
Unfortunately I am unable to compile the example. The compiler says "value map is not a member of HCons[scala.collection.immutable.Set[Int],HCons[scala.collection.immutable.Set[String],HNil]]". I suspect there is a missing import of an implicit that defines the map operation on HLists, but I don't know what that import should be. I'm using sbt with the following build.sbt file:
name := "scala-polymorphism-experiments"
version := "0.1.0"
scalaVersion := "2.10.3"
resolvers ++= Seq(
"Sonatype OSS Releases" at "http://oss.sonatype.org/content/repositories/releases/",
"Sonatype OSS Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"
)
libraryDependencies ++= Seq("org.scalatest" % "scalatest_2.10" % "2.0" % "test",
"com.chuusai" % "shapeless" % "2.0.0-SNAPSHOT" cross CrossVersion.full changing())
I also have this problem if I use the M1 release of 2.0.0. What should I change to make this example compile and run?
The problem was never determined. The solution was to comment out all code in all other scala files in the project, recompile, then uncomment and compile again. No doubt an
sbt clean
would have done just as well.