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
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.
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() }
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 )
…
}
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?
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