But my build does much more!
Yes, your build does other things.
It generates code, it distributes artifacts, it builds websites. Probably a lot more
But none of this has to be in the build itself!
Introducing scripts
To take care of all these other tasks, Bleep introduces scripts
.
Defined in your build file as the scripts
in a package.json
,
this mechanism allows you to run any class with a main
method.
projects:
myscripts:
dependencies:
- build.bleep::bleep-plugin-mdoc:${BLEEP_VERSION}
scala:
version: 3.2.0
scripts:
generate-docs: myscripts/bleep.scripts.GenDocumentation
Running a script
You can run the script like this, it can be tab-completed to save some keystrokes
$ bleep generate-docs
Complete freedom:
It might already be clear to you, but anyway:
- you can start this program from bleep (
bleep generate-docs
), from your IDE, from anywhere - you can debug it like a normal program
- you have all the structured info from the build, without having to express code in your build
- you can include any dependencies you want, from any scala version. No class loader issues.
- you can even write your scripts for any platform
- Most interesting sbt plugins can be ported to Bleep quite easily! Bleep comes with some plugins already ported.
An example script
This script uses mdoc
and docusaurus
plugins. Look how it composes the plugins, and how it parses command line arguments to determine which method (which used to be sbt task) to call.
Also note that it refers to scriptsProject
, compiles it, gives it to mdoc
in order for it to compile snippets with classpath for that project.
package bleep
package scripts
import bleep.plugin.mdoc.{DocusaurusPlugin, MdocPlugin}
import java.io.File
import java.nio.file.Path
object GenDocumentation extends BleepScript("GenDocumentation") {
override def run(started: Started, commands: Commands, args: List[String]): Unit = {
val scriptsProject = model.CrossProjectName(model.ProjectName("scripts"), crossId = None)
commands.compile(List(scriptsProject))
val mdoc = new MdocPlugin(started, scriptsProject) {
override def mdocIn: Path = started.buildPaths.buildDir / "bleep-site-in"
override def mdocOut: Path = started.buildPaths.buildDir / "bleep-site" / "docs"
}
val nodeBinPath = started.pre.fetchNode(constants.Node).getParent
started.logger.withContext(nodeBinPath).info("Using node")
val env = sys.env.collect {
case x @ ("SSH_AUTH_SOCK", _) => x
case ("PATH", value) => "PATH" -> s"$nodeBinPath${File.pathSeparator}$value"
}.toList
val docusaurus = new DocusaurusPlugin(
website = started.buildPaths.buildDir / "bleep-site",
mdoc = mdoc,
docusaurusProjectName = "bleep-site",
env = env,
logger = started.logger,
isDocusaurus2 = true
)
args.headOption match {
case Some("dev") =>
docusaurus.dev(started.executionContext)
case Some("deploy") =>
docusaurus.docusaurusPublishGhpages(mdocArgs = Nil)
case Some(other) =>
sys.error(s"Expected argument to be dev or deploy, not $other")
case None =>
val path = docusaurus.doc(mdocArgs = args)
started.logger.info(s"Created documentation at $path")
}
}
}
Composing plugins
You can pass an instance of a plugin to another plugin. It's super clear and just normal code. This is what it looks like at use-site
val dynVer = new DynVerPlugin(baseDirectory = started.buildPaths.buildDir.toFile, dynverSonatypeSnapshots = true)
val pgp = new PgpPlugin(
logger = started.logger,
maybeCredentials = None,
interactionService = InteractionService.DoesNotMaskYourPasswordExclamationOneOne
)
val sonatype = new Sonatype(
logger = started.logger,
sonatypeBundleDirectory = started.buildPaths.dotBleepDir / "sonatype-bundle",
sonatypeProfileName = "build.bleep",
bundleName = "bleep",
version = dynVer.version,
sonatypeCredentialHost = Sonatype.sonatype01
)
val ciRelease = new CiReleasePlugin(started.logger, sonatype, dynVer, pgp)
ciRelease.ciRelease(...)