Skip to content

Commit

Permalink
renamed the project to sbt-nosbt
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Janusz committed Mar 18, 2023
1 parent 9fc40dc commit 7e9fce3
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 34 deletions.
69 changes: 47 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,50 @@
# plainsbt
# `sbt-nosbt`

`plainsbt` is a Scala plugin to make your complex, multi-module build definition maintainable like regular Scala code.
`sbt-nosbt` is an `sbt` plugin to make your complex, multi-module build definition more maintainable
by moving build definition from `.sbt` files to plain Scala files and providing a nice convention for
hierarchical organization of subprojects.

## Overview

`sbt` can be intimidating. This is mostly due to various layers of abstraction and "magic" that it uses. However, deep down, `sbt` build definition is ultimately just plain Scala code. This plugin aims to bring that plain Scala to the surface, removing at least some of the `sbt`'s magic.
`sbt` can be intimidating. This is mostly due to various layers of abstraction and "magic" that it uses. However, deep down,
`sbt` build definition is ultimately just plain Scala code. This plugin aims to bring that plain Scala to the surface,
removing at least some of the `sbt`'s magic.

### `.sbt` files

`sbt` requires your build to be defined in `.sbt` files, which are Scala-like files preprocessed in a special way. Most importantly, that preprocessing includes:
`sbt` requires your build to be defined in `.sbt` files, which are Scala-like files preprocessed in a special way.
Most importantly, that preprocessing includes:

* automatic import of keys and other definitions from `sbt` core and plugins
* extracting all project definitions by looking for all `lazy val`s (and `val`s) typed as `Project`

`.sbt` files may also refer to definitions in `project/*.scala` files, which are regular Scala files without any special treatment. While this allows you to move a lot of utility functions out of `.sbt` files, you are still forced to enumerate all your projects in `.sbt` files. Typically, this is a single `build.sbt` file.
`.sbt` files may also refer to definitions in `project/*.scala` files, which are regular Scala files without any special
treatment. While this allows you to move a lot of utility functions out of `.sbt` files, you are still forced to
enumerate all your projects in `.sbt` files. Typically, this is a single `build.sbt` file.

### Moving to plain Scala

`plainsbt` plugin allows you to move all your project definitions into plain Scala files. This removes all the special `.sbt` preprocessing and allows you to organize your build definition like regular Scala code by splitting it into multiple files that explicitly refer to each other. `plainsbt` also establishes a convention for project (and directory) hierarchy that makes it easier to define complex, multi-project builds.
The `nosbt` plugin allows you to move all your project definitions into plain Scala files. This removes all the
special `.sbt` preprocessing and allows you to organize your build definition like regular Scala code by splitting
it into multiple files that explicitly refer to each other. `sbt-nosbt` also establishes a convention for project
(and directory) hierarchy that makes it easier to define complex, multi-project builds.

## Usage example

The full example is available in an [example project repository](https://github.com/ghik/plainsbt-example)
The full example is available in an [example project repository](https://github.com/ghik/sbt-nosbt-example)

### Simple multi-project build

Add the `plainsbt` plugin to your `project/plugins.sbt`:
Add the `nosbt` plugin to your `project/plugins.sbt`:

```scala
addSbtPlugin("com.github.ghik" % "plainsbt" % "<version>")
addSbtPlugin("com.github.ghik" % "sbt-nosbt" % "<version>")
```

Now create a `project/MyProj.scala` file with definition of a `ProjectGroup`:

```scala
import com.github.ghik.plainsbt.ProjectGroup
import com.github.ghik.sbt.nosbt.ProjectGroup
import sbt.Keys._
import sbt._

Expand Down Expand Up @@ -71,20 +81,29 @@ object MyProj extends ProjectGroup("myproj") {

The above file is a complete definition of an `sbt` multi-project build, in plain Scala:

* The root project must be defined as `lazy val root` and implemented with `mkSubProject`. ID of this project will be the same as name of the `ProjectGroup`, i.e. `myproj`. Base directory of this project is the build root directory.
* All subprojects in the project group must be defined as `lazy val`s, just like you would do in an `.sbt` file. However, usage of `mkSubProject` makes sure that subprojects follow hierarchical naming and directory convention.
For example `lazy val api: Project = mkSubProject` will define a subproject with ID `myproj-api` and base directory `api/`. Note how this is different from the default `sbt` behaviour which would place the project in a directory corresponding directly to its ID (i.e. `myproj-api/`).
* The root project must be defined as `lazy val root` and implemented with `mkSubProject`. ID of this project will be
the same as name of the `ProjectGroup`, i.e. `myproj`. Base directory of this project is the build root directory.
* All subprojects in the project group must be defined as `lazy val`s, just like you would do in an `.sbt` file.
However, usage of `mkSubProject` makes sure that subprojects follow hierarchical naming and directory convention.
For example `lazy val api: Project = mkSubProject` will define a subproject with ID `myproj-api` and base directory
`api/`. Note how this is different from the default `sbt` behaviour which would place the project in a directory
corresponding directly to its ID (i.e. `myproj-api/`).
* Settings shared by all the projects in your build can be defined by overriding `commonSettings`.
Note how this is **not** the same as defining settings in `Global` or `ThisBuild` scopes - `commonSettings` are applied **directly** on each and every project which is more reliable than `Global`/`ThisBuild` and generally more recommended.
There are also variations of `commonSettings`, e.g. `subprojectSettings`, `leafSubprojectSettings`, etc. which allow you to refine the exact set of projects that you want to apply settings on. Refer to `ProjectGroup`s API for details.
Note how this is **not** the same as defining settings in `Global` or `ThisBuild` scopes - `commonSettings` are
applied **directly** on each and every project which is more reliable than `Global`/`ThisBuild` and generally
more recommended. There are also variations of `commonSettings`, e.g. `subprojectSettings`, `leafSubprojectSettings`,
etc. which allow you to refine the exact set of projects that you want to apply settings on. Refer to `ProjectGroup`s
API for details.
* Settings in `Global` scope can be set by overriding `globalSettings`
* Settings in `ThisBuild` scope can be set by overriding `buildSettings`.

Because `MyProj.scala` is a regular Scala file, its contents may be split and reorganized as you wish, e.g. be extracting traits, subclasses, etc. into separate files. It becomes maitainable like plain Scala code.
Because `MyProj.scala` is a regular Scala file, its contents may be split and reorganized as you wish, e.g. be
extracting traits, subclasses, etc. into separate files. It becomes maitainable like plain Scala code.

### Bootstrapping

We also need to tell `sbt` that `MyProj.scala` is the entry point of the entire build definition. In order to do that, we need to create a minimal, "bootstrapping" `build.sbt` file:
We also need to tell `sbt` that `MyProj.scala` is the entry point of the entire build definition. In order to do that,
we need to create a minimal, "bootstrapping" `build.sbt` file:

```scala
lazy val root = MyProj.root
Expand All @@ -94,7 +113,8 @@ _et voila!_

### Complex, multi-level hierarchies

Let's say your build is more complex. It is split into several "services", each one consisting of multiple subprojects. Let's say you want to achieve a project structure like this:
Let's say your build is more complex. It is split into several "services", each one consisting of multiple subprojects.
Let's say you want to achieve a project structure like this:

```
myproj
Expand Down Expand Up @@ -127,7 +147,7 @@ myproj/
You can achieve this with the following set of definitions:

```scala
import com.github.ghik.plainsbt.ProjectGroup
import com.github.ghik.sbt.nosbt.ProjectGroup
import sbt.Keys._
import sbt._

Expand Down Expand Up @@ -166,7 +186,8 @@ object BarService extends ProjectGroup("barservice", MyProj) {
}
```

Note how `Commons`, `FooService` and `BarService` declare `MyProj` as their _parent_ project group. The `MyProj` must also explicitly declare `lazy val`s referring to subgroups' root projects in order for `sbt` to see them.
Note how `Commons`, `FooService` and `BarService` declare `MyProj` as their _parent_ project group. The `MyProj`
must also explicitly declare `lazy val`s referring to subgroups' root projects in order for `sbt` to see them.

Finally, the boostrapping `build.sbt` file:

Expand All @@ -176,12 +197,16 @@ lazy val root = MyProj.root

## Caveats

* Settings defined in `Global` and `ThisBuild` scopes by overriding `globalSettings` and `buildSettings` have lower priority than if they would be defined directly in the `.sbt` file. This means they may get overwritten by settings from other `sbt` plugins in your build. If this is a problem, you can lift their priority back by referring to them explicitly in the `build.sbt` bootstrapping file:
* Settings defined in `Global` and `ThisBuild` scopes by overriding `globalSettings` and `buildSettings` have
lower priority than if they would be defined directly in the `.sbt` file. This means they may get overwritten by
settings from other `sbt` plugins in your build. If this is a problem, you can lift their priority back by
referring to them explicitly in the `build.sbt` bootstrapping file:

```scala
inScope(Global)(MyProj.globalSettings)
inThisBuild(MyProj.buildSettings)
lazy val root = MyProj.root
```

In order to avoid these problems altogether, prefer overriding `ProjectGroup.commonSettings` rather than using `ThisBuild`.
In order to avoid these problems altogether, prefer overriding `ProjectGroup.commonSettings`
rather than using `ThisBuild`.
12 changes: 6 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Global / excludeLintKeys += ideBasePackages

inThisBuild(Seq(
organization := "com.github.ghik",
homepage := Some(url("https://github.com/ghik/plainsbt")),
homepage := Some(url("https://github.com/ghik/sbt-nosbt")),

githubWorkflowTargetTags ++= Seq("v*"),
githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17")),
Expand All @@ -22,7 +22,7 @@ inThisBuild(Seq(
lazy val root = project.in(file("."))
.enablePlugins(SbtPlugin)
.settings(
name := "plainsbt",
name := "sbt-nosbt",
pluginCrossBuild / sbtVersion := {
scalaBinaryVersion.value match {
case "2.12" => "1.8.0"
Expand All @@ -41,17 +41,17 @@ lazy val root = project.in(file("."))
projectInfo := ModuleInfo(
nameFormal = "PlainSBT",
description = "SBT plugin for organizing your build into plain Scala files",
homepage = Some(url("https://github.com/ghik/plainsbt")),
homepage = Some(url("https://github.com/ghik/sbt-nosbt")),
startYear = Some(2023),
licenses = Vector(
"Apache License, Version 2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0")
),
organizationName = "ghik",
organizationHomepage = Some(url("https://github.com/ghik")),
scmInfo = Some(ScmInfo(
browseUrl = url("https://github.com/ghik/plainsbt.git"),
connection = "scm:git:[email protected]:ghik/plainsbt.git",
devConnection = Some("scm:git:[email protected]:ghik/plainsbt.git")
browseUrl = url("https://github.com/ghik/sbt-nosbt.git"),
connection = "scm:git:[email protected]:ghik/sbt-nosbt.git",
devConnection = Some("scm:git:[email protected]:ghik/sbt-nosbt.git")
)),
developers = Vector(
Developer("ghik", "Roman Janusz", "[email protected]", url("https://github.com/ghik"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.github.ghik.plainsbt
package com.github.ghik.sbt.nosbt

import scala.reflect.macros.blackbox

class Macros(val c: blackbox.Context) {

import c.universe.*

private def PlainsbtPkg = q"_root_.com.github.ghik.plainsbt"
private def NosbtPkg = q"_root_.com.github.ghik.sbt.nosbt"

private def classBeingConstructed: ClassSymbol = {
val ownerConstr = c.internal.enclosingOwner
Expand All @@ -20,7 +20,7 @@ class Macros(val c: blackbox.Context) {
val sbtProjectCls = c.mirror.staticClass("_root_.sbt.Project")

val projectGroupTpe =
c.mirror.staticClass("_root_.com.github.ghik.plainsbt.ProjectGroup").toType
c.mirror.staticClass("_root_.com.github.ghik.sbt-nosbt.ProjectGroup").toType

val rootProjectSym =
projectGroupTpe.member(TermName("root"))
Expand All @@ -42,5 +42,5 @@ class Macros(val c: blackbox.Context) {
}

def mkFreshProject: Tree =
q"$PlainsbtPkg.FreshProject(_root_.sbt.project)"
q"$NosbtPkg.FreshProject(_root_.sbt.project)"
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.ghik.plainsbt
package com.github.ghik.sbt.nosbt

import com.avsystem.commons.*
import sbt.{Def, *}
import sbt.*

import scala.language.experimental.macros

Expand Down

0 comments on commit 7e9fce3

Please sign in to comment.