Skip to main content

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(...)