Calling a task from a plug-in that may be not present - scala

Depending on a setting I need to call either package or assembly but obviously the assembly task is from sbt-assembly plugin that may not be added. My task looks something like this
lazy val useAssembly = settingKey[Boolean]("Use assembly")
lazy val myTask = Def.task {
val file = if (useAssembly.value) {
// somehow call assembly here
} else {
(`package` in Compile).value
}
// Do other stuff
}

PluginsDebug.autoPluginMap may be used to get available auto plugins. Create project/AssemblyIsAvailable.scala like so
package sbt.internal
/** If sbt-assembly is loaded, then it should be listed as sbtassembly.AssemblyPlugin **/
object AssemblyIsAvailable {
def apply(state: sbt.State): Boolean = {
PluginsDebug
.autoPluginMap(state)
.values
.toList
.map(_.label)
.exists(_.contains("ssembly"))
}
}
then define dynamic task assemblyOrDefaultPackage to be able to use conditional task evaluation within a task:
lazy val assemblyOrDefaultPackage = Def.taskDyn {
if (AssemblyIsAvailable(state.value))
Def.task { assembly.value }
else
Def.task { (Compile / Keys.`package`).value }
}
Now assemblyOrDefaultPackage can be evaluated within another task like so
lazy val myTask = Def.task {
val file = assemblyOrDefaultPackage.value
// Do other stuff
}
Alternatively, define custom command assemblyOrDefaultPackage in build.sbt like so
commands += Command.command("assemblyOrDefaultPackage") { state =>
(if (AssemblyIsAvailable(state)) "assembly" else "package") :: state
}
Now executing sbt assemblyOrDefaultPackage should package with sbt-assembly if available, otherwise fallback to default packaging.

Related

How do I make sbt run a task only once even though it is indirectly specified multiple times on the cli?

Here is my plugin code. It defines a master lint task that is triggered from the CLI like so: sbt api/lint jobs/lint. It calls out to some project-specific linters and some build-wide linters. The build-wide linter runs scalafix, but if I call lint multiple times from the CLI, as above (for multiple projects), then scalafix is run multiple times.
How do I make scalafix (and scalafixLinter) run only one time for a given sbt invocation? I thought sbt caches task results, but it seems to not be working here.
object LinterPlugin extends AutoPlugin {
object autoImport {
lazy val scalafixLinter = taskKey[Unit]("Run scalafix on all scala code")
lazy val lint = taskKey[Unit]("Run all linters")
}
override val buildSettings = Seq(
scalafixLinter := {
Def.taskDyn {
if (...) {
Def.task {
// run scalafix
(Compile / scalafix).toTask("").value
(Test / scalafix).toTask("").value
}
} else {
Def.task {}
}
}.all(ScopeFilter(inAnyProject)).value // run on all projects
}
)
override val projectSettings = Seq(
lint := {
// run all the linters
otherLinters.value
scalafixLinter.value
}
)
}
You could use a FileFunction.cache method. Though that only works for files.

sbt autoplugin: add javaagent for task

I have a sbt autoplugin and when the user runs a task I want to fork a new JVM with a -javaagent. The task should measure memory using jamm.
object SbtMemory extends AutoPlugin {
object autoImport {
val agentTest = inputKey[Unit]("Run task with javaagent")
}
def makeAgentOptions(classpath: Classpath) : String = {
val jammJar = classpath.map(_.data).filter(_.toString.contains("jamm")).head
s"-javaagent:$jammJar"
}
override lazy val projectSettings =
Seq(
agentTest := agentTask.value,
fork in agentTest := true,
javaOptions in agentTest += (dependencyClasspath in Test).map(makeAgentOptions).value
)
lazy val agentTask = Def.task {
val o = new Array[Byte](1024*1024)
val mm = new MemoryMeter()
println("Size of new Array[Byte](1024*1024): " + mm.measureDeep(o))
}
}
When I run sbt perf from the command line, I get the following exception:
java.lang.IllegalStateException: Instrumentation is not set; Jamm must be set as -javaagent
I also tried printing the javaOptions and the -javaagent option was not set.
How can I add the -javaagent javaOption inside the plugin to run the task with jamm?
Thanks!
Apparently, fork is only available for the run and test task. I added my own forking code and moved the measure code to a separate class MemoryMeasure:
val mainClass: String = "MemoryMeasure"
val forkOptions = ForkOptions(
bootJars = (fullClasspath in Test).value.files,
runJVMOptions = Seq(
(dependencyClasspath in Test).map(makeAgentOptions).value
)
)
val process = Fork.java.fork(forkOptions, mainClass +: arguments)
def cancel() = {
process.destroy()
1
}
val exitCode = try process.exitValue() catch { case e: InterruptedException => cancel() }

Is there a cleaner way to compose SBT tasks?

There is a useful SBT plugin for formatting license headers:
https://github.com/Banno/sbt-license-plugin
As of 0.1.0, it only formats "scalaSource in Compile":
object Plugin extends sbt.Plugin {
import LicenseKeys._
object LicenseKeys {
lazy val formatLicenseHeaders = TaskKey[Unit]("formatLicenseHeaders", "Includes the license header to source files")
…
}
def licenseSettings = Seq(
formatLicenseHeaders <<= formatLicenseHeadersTask,
…
)
…
private def formatLicenseHeadersTask =
(streams, scalaSource in Compile, license in formatLicenseHeaders, removeExistingHeaderBlock in formatLicenseHeaders) map {
(out, sourceDir, lic, removeHeader) =>
modifySources(sourceDir, lic, removeHeader, out.log)
}
I wrote a pull request where I generalized this to format both java and scala sources used for both compilation and testing here:
https://github.com/Banno/sbt-license-plugin/blob/master/src/main/scala/license/plugin.scala
private def formatLicenseHeadersTask = Def.task[Unit] {
formatLicenseHeadersTask1.value
formatLicenseHeadersTask2.value
formatLicenseHeadersTask3.value
formatLicenseHeadersTask4.value
}
The tasks formatLicenseHeadersTask1234 correspond to the combinations of {scala,java}Source and {Compile,Test}.
This works but it's really ugly. Is there a way to write the same thing with loops as sketched below?
for {
src <- Seq( scalaSource, javaSource )
kind <- Seq( Compile, Test )
…
}

How create osgi manifest for bundle built in task?

I have to create osgi manifest for bundle created by task that uses proguard.
For osgi part I'm using sbtosgi plugin (ver 0.6)
Currently I tried something like this:
object BuilderKeys {
val pkg = TaskKey[File]("package")
val bld = TaskKey[File]("build")
val targetFile = TaskKey[File]("targetFile")
...
}
lazy val settings = ... + BuilderSettings.default(bundle) + BuilderSettings.osgi(bundle)
object BuilderSettings {
def default(configuration: Configuration) // initializes bld (task that creates one uber-jar using proguard)
def osgi(configuration: Configuration) = inConfig(configuration)(SbtOsgi.defaultOsgiSettings ++ Seq(
fullClasspath in Compile := Seq(Attributed.blank((targetFile in configuration).value)),
pkg <<= (OsgiKeys.bundle dependsOn bld) map { f: File =>
f
}
))
}
Running bundle:package creates jar with manifest but not based on jar created by proguard. Is it possible to force it in some way?

sbt 0.11 run task examples needed

My projects are still using sbt 0.7.7 and I find it very convenient to have utility classes that I can run from the sbt prompt. I can also combine this with properties that are separately maintained - typically for environment related values that changes from hosts to hosts. This is an example of my project definition under the project/build directory:
class MyProject(info: ProjectInfo) extends DefaultProject(info) {
//...
lazy val extraProps = new BasicEnvironment {
// use the project's Logger for any properties-related logging
def log = MyProject.this.log
def envBackingPath = path("paths.properties")
// define some properties that will go in paths.properties
lazy val inputFile = property[String]
}
lazy val myTask = task { args =>
runTask(Some("foo.bar.MyTask"),
runClasspath, extraProps.inputFile.value :: args.toList).dependsOn(compile)
describedAs "my-task [options]"
}
}
I can then use my task as my-task option1 option2 under the sbt shell.
I've read the new sbt 0.11 documentation at https://github.com/harrah/xsbt/wiki including the sections on Tasks and TaskInputs and frankly I'm still struggling on how to accomplish what I did on 0.7.7.
It seems the extra properties could simply be replaced a separate environment.sbt, that tasks have to be defined in project/build.scala before being set in build.sbt. It also looks like there is completion support, which looks very interesting.
Beyond that I'm somewhat overwhelmed. How do I accomplish what I did with the new sbt?
You can define a task like this :
val myTask = InputKey[Unit]("my-task")
And your setting :
val inputFile = SettingKey[String]("input-file", "input file description")
You can also define a new configuration like :
lazy val ExtraProps = config("extra-props") extend(Compile)
add this config to your project and use it to set settings for this configuration :
lazy val root = Project("root", file(".")).config( ExtraProps ).settings(
inputFile in ExtraProps := ...
...
myTask in ExtraPops <<= inputTask { (argTask:TaskKey[Seq[String]]) =>
(argTask, inputFile) map { (args:Seq[String], iFile[String]) =>
...
}
}
).dependsOn(compile)
then launch your task with extra-props:my-task