I'm having an issue with getting SBT Subprojects to recognize commands provided by plugins. I have the following plugin source:
object DemoPlugin extends AutoPlugin {
override lazy val projectSettings = Seq(commands += demoCommand)
lazy val demoCommand =
Command.command("demo") { (state: State) =>
println("Demo Plugin!")
state
}
}
Which is used by a project configured as follows:
lazy val root = project in file(".")
lazy val sub = (project in file("sub")).
enablePlugins(DemoPlugin).
settings(
//...
)
The plugin is, of course, listed in project/plugins.sbt. However, when I open up sbt in the project, I see the following:
> sub/commands
[info] List(sbt.SimpleCommand#413d2cd1)
> sub/demo
[error] Expected ':' (if selecting a configuration)
[error] Not a valid key: demo (similar: doc)
[error] sub/demo
Even stranger, using consoleProject, I can see that the command in the project is the one defined by DemoPlugin!
scala> (commands in sub).eval.map { c => c.getClass.getMethod("name").invoke(c) }
res0: Seq[Object] = List(demo)
I'm looking to be able to type sub/demo, and have it perform the demo command. Any help would be much appreciated!
Commands aren't per-project. They only work for the top-level project.
It's also recommended to try and use tasks, or if needed input tasks where you might want to use a command.
If you really need a command, there's a way to have a sort of "holder" task, see the answer to Can you access a SBT SettingKey inside a Command?
Related
I have an SBT project which pulls in dependencies. I only want to pull in the direct dependencies - not any transitive dependencies. I'd like to find the filename of the dependency that's pulled in, so that I can copy it somewhere.
e.g. given a build.sbt file with the following contents:
libraryDependencies += "org.eclipse.jetty" % "jetty-server" % "9.4.28.v20200408"
I would like to know where is the jetty-server jar on the file system.
I have tried adding the following to my build.sbt file:
lazy val mytaskKey: TaskKey[Unit] = TaskKey[Unit]("mytask")
def mytask: Def.Setting[Task[Unit]] = mytaskKey := {
val updateReport = update.value
updateReport.allFiles foreach { f =>
println(f)
}
}
mytask
When I run this, I get a full list of dependencies:
/Users/dylan/.sbt/boot/scala-2.12.10/lib/scala-library.jar
/Users/dylan/.coursier/cache/v1/https/repo1.maven.org/maven2/org/eclipse/jetty/jetty-server/9.4.28.v20200408/jetty-server-9.4.28.v20200408.jar
/Users/dylan/.sbt/boot/scala-2.12.10/lib/scala-compiler.jar
/Users/dylan/.sbt/boot/scala-2.12.10/lib/scala-reflect.jar
/Users/dylan/.coursier/cache/v1/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-xml_2.12/1.0.6/scala-xml_2.12-1.0.6.jar
/Users/dylan/.coursier/cache/v1/https/repo1.maven.org/maven2/jline/jline/2.14.6/jline-2.14.6.jar
/Users/dylan/.coursier/cache/v1/https/repo1.maven.org/maven2/org/fusesource/jansi/jansi/1.12/jansi-1.12.jar
I don't want that full list - I just want the jetty jar. i.e.
/Users/dylan/.coursier/cache/v1/https/repo1.maven.org/maven2/org/eclipse/jetty/jetty-server/9.4.28.v20200408/jetty-server-9.4.28.v20200408.jar
How might I get this list?
Yes, there is with either intransitive() or notTransitive() classifiers. It's documented here.
The sbt in Action book introduces a concept of Key in Configuration
It then lists the default configurations:
Compile
Test
Runtime
IntegrationTest
Q1) Is it possible to print out a list of all Configurations from a sbt session? If not, can I find information on Configurations in the sbt documentation?
Q2) For a particular Configuration, e.g. 'Compile', is it possible to print out a list of Keys for the Configuration from a sbt session? If not, can I find information on a Configuration's Keys in the sbt documentation?
List of all configurations
For this you can use a setting like so:
val allConfs = settingKey[List[String]]("Returns all configurations for the current project")
val root = (project in file("."))
.settings(
name := "scala-tests",
allConfs := {
configuration.all(ScopeFilter(inAnyProject, inAnyConfiguration)).value.toList
.map(_.name)
}
This shows the name of all configurations. You can access more details about each configuration inside the map.
Output from the interactive sbt console:
> allConfs
[info] * provided
[info] * test
[info] * compile
[info] * runtime
[info] * optional
If all you want is to print them you can have a settingKey[Unit] and use println inside the setting definition.
List of all the keys in a configuration
For this we need a task (there might be other ways, but I haven't explored, in sbt I'm satisfied if something works... ) and a parser to parse user input.
All join the above setting in this snippet:
import sbt._
import sbt.Keys._
import complete.DefaultParsers._
val allConfs = settingKey[List[String]]("Returns all configurations for the current project")
val allKeys = inputKey[List[String]]("Prints all keys of a given configuration")
val root = (project in file("."))
.settings(
name := "scala-tests",
allConfs := {
configuration.all(ScopeFilter(inAnyProject, inAnyConfiguration)).value.toList
.map(_.name)
},
allKeys := {
val configHints = s"One of: ${
configuration.all(ScopeFilter(inAnyProject, inAnyConfiguration)).value.toList.mkString(" ")
}"
val configs = spaceDelimited(configHints).parsed.map(_.toLowerCase).toSet
val extracted: Extracted = Project.extract(state.value)
val l = extracted.session.original.toList
.filter(set => set.key.scope.config.toOption.map(_.name.toLowerCase)
.exists(configs.contains))
.map(_.key.key.label)
l
}
)
Now you can use it like:
$ sbt "allKeys compile"
If you are in interactive mode you can press tab after allKeys to see the prompt:
> allKeys
One of: provided test compile runtime optional
Since allKeys is a task it's output won't appear on the sbt console if you just "return it" but you can print it.
I have a SBT multi project which includes two sub projects. One is an ordinary Scala web server project and the other is just some web files. With my self written SBT plugin I can run Gulp on the web project. This Gulp task runs asynchronous. So with
sbt "web/webAppStart" "server/run"
I can start the Gulp development web server and my Scala backend server in parallel. Now I want to create a new task, that combines them both. So afterwards
sbt dev
for example should do the same. Here is what I tried so far:
// Build.sbt (only the relevant stuff)
object Build extends sbt.Build {
lazy val runServer: InputKey[Unit] = run in server in Compile
lazy val runWeb: TaskKey[Unit] = de.choffmeister.sbt.WebAppPlugin.webAppStart
lazy val dev = InputKey[Unit]("dev", "Starts a development web server")
// Scala backend project
lazy val server = (project in file("project-server"))
// Web frontend project
lazy val web = (project in file("project-web"))
// Root project
lazy val root = (project in file("."))
.settings(dev <<= (runServer) map { (_) => {
// do nothing
})
.aggregate(server, web)
This works so far. Now I don't have any idea, how to make dev also depend on the runWeb task. If I just add the runWeb task like
.settings(dev <<= (runWeb, runServer) map { (_, _) => {
// do nothing
})
then I get the error
[error] /Users/choffmeister/Development/shop/project/Build.scala:59:value map is not a member of (sbt.TaskKey[Unit], sbt.InputKey[Unit])
[error] .settings(dev <<= (runWeb, runServer) map { (_, _) =>
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
Can anyone help me with this please?
The optimal solution would pass the arguments given to dev to the runServer task. But I could also live with making dev a TaskKey[Unit] and then hard code to run runServer with no arguments.
tl;dr Use .value macro to execute dependent tasks or just alias the task sequence.
Using .value macro
Your case seems overly complicated to my eyes because of the pre-0.13 syntax (<<=) and the use of project/Build.scala (that often confuse not help people new to sbt).
You should just execute the two tasks in another as follows:
dev := {
runWeb.value
runServer.value
}
The complete example:
lazy val server = project
lazy val runServer = taskKey[Unit]("runServer")
runServer := {
println("runServer")
(run in server in Compile).value
}
lazy val runWeb = taskKey[Unit]("runWeb")
runWeb := {
println("runWeb")
}
lazy val dev = taskKey[Unit]("dev")
dev := {
println("dev")
}
dev <<= dev dependsOn (runServer, runWeb)
Using alias command
sbt offers alias command that...
[sbt-learning-space]> help alias
alias
Prints a list of defined aliases.
alias name
Prints the alias defined for `name`.
alias name=value
Sets the alias `name` to `value`, replacing any existing alias with that name.
Whenever `name` is entered, the corresponding `value` is run.
If any argument is provided to `name`, it is appended as argument to `value`.
alias name=
Removes the alias for `name`.
Just define what tasks/command you want to execute in an alias as follows:
addCommandAlias("devAlias", ";runServer;runWeb")
Use devAlias as if it were a built-in task:
[sbt-learning-space]> devAlias
runServer
[success] Total time: 0 s, completed Jan 25, 2015 6:30:15 PM
runWeb
[success] Total time: 0 s, completed Jan 25, 2015 6:30:15 PM
val webAssemblyTask = TaskKey[Unit](
"web-assembly",
"assembly web/war like run-time package"
)
var out: TaskStreams = _
val baseSettings: Seq[Setting[_]] = Seq(
webAssemblyOutputDir <<= (sourceManaged) { _ / "build" },
webAssemblyTask <<= (
streams,
target,
sourceDirectory,
outputDirProjectName
) map {
(out_log, targetDir, sourceDir, outputDirProjectName) => {
out_log.log.info("web-assembly start")
out_log.log.info("sourceDir:" + sourceDir.getAbsolutePath)
out_log.log.info("targetDir:" + targetDir.getAbsolutePath)
val sourceAssetsDir = (sourceDir / "webapp" / "assets").toPath
val classesAssetsDir = (targetDir / "scala-2.10" / "classes" / "assets").toPath
Files.createSymbolicLink(classesAssetsDir, sourceAssetsDir)
}
}
)
val webAssemblySettings = inConfig(Runtime)(baseSettings)
I wrote a plugin of sbt.
I type webAssembly in sbt console, the plugin run ok.
But I want to run after compile, before runtime, how can I do it?
how to set sbt plugins invoke scope?
I think you're confusing the configuration (also known as Maven scope) name with tasks like compile and run. They happen to have related configuration, but that doesn't mean compile task is identical to Compile configuration.
I could interpret this question to be how can a plugin setting invoke tasks scoped in some other configuration. For that you use in method like: key in (Config) or key in (Config, task). Another way to interpret it may be how can plugin tasks be scoped in a configuration. You use inConfig(Config)(...), which you're already doing. But you'd typically want plugins to be configuration neutral. See my blog post for more details on this.
I want to run after compile, before run, how can I do it?
This makes much more sense. In sbt you mostly focus on the preconditions of the tasks. One of the useful command is inspect tree key. You can run that for run tasks and get the entire tasks/settings that it depends on. Here's where you see it calling compile:compile (another notation for compile in Compile):
helloworld> inspect tree run
[info] compile:run = InputTask[Unit]
[info] +-runtime:fullClasspath = Task[scala.collection.Seq[sbt.Attributed[java.io.File]]]
[info] | +-runtime:exportedProducts = Task[scala.collection.Seq[sbt.Attributed[java.io.File]]]
[info] | | +-compile:packageBin::artifact = Artifact(sbt-sequential,jar,jar,None,List(compile),None,Map())
[info] | | +-runtime:configuration = runtime
[info] | | +-runtime:products = Task[scala.collection.Seq[java.io.File]]
[info] | | | +-compile:classDirectory = target/scala-2.10/sbt-0.13/classes
[info] | | | +-compile:copyResources = Task[scala.collection.Seq[scala.Tuple2[java.io.File, java.io.File]]]
[info] | | | +-compile:compile = Task[sbt.inc.Analysis]
This is useful in discovering compile:products, which "Build products that get packaged" according to help products command:
helloworld> help products
Build products that get packaged.
Since runtime:products happens before compile:run, if it depended on your task, your task will be called before compile:run (inspect tree also shows that run resolved to that).
To simplify your plugin task, I'm just going to call it sayHello:
val sayHello = taskKey[Unit]("something")
sayHello := {
println("hello")
}
You can rewire products in Runtime as follows:
products in Runtime := {
val old = (products in Runtime).value
sayHello.value
old
}
This will satisfy "before run" part. You want to make sure that this runs after compile. Again, just add task dependency to it:
sayHello := {
(compile in Compile).value
println("hello")
}
When the user runs run task, sbt will correct calculate the dependencies and runs sayHello task somewhere between compile and run.
I want to combine the sbt-release plugin with the Play framework.
The plugins reads the current version number from a file version.sbt. Its content is
version in ThisBuild := "0.41.0-SNAPSHOT"
I would like to use this setting in my main build file but the variable version is of type sbt.SettingKey.
There is an evaluate method but for the life of me I can't figure out what to pass in to get the String I defined in version.sbt.
I tried the accepted answer's solution but it didn't compile. (Play 2.1.5)
[error] (ss: sbt.Project.Setting[_]*)sbt.Project <and>
[error] => Seq[sbt.Project.Setting[_]]
[error] cannot be applied to (Seq[sbt.ModuleID])
[error] val main = play.Project(appName).settings(appDependencies).settings(releaseSettings).settings(
[error] ^
[error] one error found
Instead I came up with this solution:
...
lazy val appSettings = Defaults.defaultSettings ++ ... ++ releaseSettings
val main = play.Project(appName, dependencies = appDependencies, settings = appSettings).settings(
version <<= version in ThisBuild,
...
)
This is a little shortcoming with the play.Project constructor, it excepts a static version number, not one from a setting key.
However, the only required parameter is the application name, so you can switch from something like:
val main = play.Project(appName, appVersion, appDependencies, settings =
Defaults.defaultSettings ++ releaseSettings ).settings(...)
to
val main = play.Project(appName).settings(appDependencies).
settings(releaseSettings).settings(...)
Normally, the version defined in version.sbt should be picked up here automagically. If it isn't, you can always add to the above:
.settings(applicationVersion <<= version in ThisBuild)