Skip to content

Commit

Permalink
removed the discoverProjects boilerplate
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Janusz committed Mar 18, 2023
1 parent 09160ba commit 6835b79
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 48 deletions.
15 changes: 0 additions & 15 deletions README.md
Expand Up @@ -66,9 +66,6 @@ object MyProj extends ProjectGroup("myproj") {
// settings in Global scope (optional)
override def globalSettings: Seq[Def.Setting[_]] =
Seq(/* settings that you wish to be Global scope */)

// mandatory boilerplate that collects the subprojects
protected def enumerateSubprojects: Seq[Project] = discoverProjects
}
```

Expand Down Expand Up @@ -146,39 +143,27 @@ object MyProj extends ProjectGroup("myproj") {
lazy val commons: Project = Commons.root
lazy val fooservice: Project = FooService.root
lazy val barservice: Project = BarService.root

// mandatory boilerplate that collects the subprojects
protected def enumerateSubprojects: Seq[Project] = discoverProjects
}

object Commons extends ProjectGroup("commons", MyProj) {
lazy val root: Project = mkRootProject

lazy val db: Project = mkSubProject
lazy val api: Project = mkSubProject

// mandatory boilerplate that collects the subprojects
protected def enumerateSubprojects: Seq[Project] = discoverProjects
}

object FooService extends ProjectGroup("fooservice", MyProj) {
lazy val root: Project = mkRootProject

lazy val api: Project = mkSubProject.dependsOn(Commons.api)
lazy val impl: Project = mkSubProject.depensOn(api, Commons.db)

// mandatory boilerplate that collects the subprojects
protected def enumerateSubprojects: Seq[Project] = discoverProjects
}

object BarService extends ProjectGroup("barservice", MyProj) {
lazy val root: Project = mkRootProject

lazy val api: Project = mkSubProject.dependsOn(Commons.api)
lazy val impl: Project = mkSubProject.depensOn(api, Commons.db, FooService.api)

// mandatory boilerplate that collects the subprojects
protected def enumerateSubprojects: Seq[Project] = discoverProjects
}
```

Expand Down
21 changes: 16 additions & 5 deletions src/main/scala/com/github/ghik/plainsbt/Macros.scala
Expand Up @@ -8,14 +8,25 @@ class Macros(val c: blackbox.Context) {

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

private def classBeingConstructed: ClassSymbol = {
val ownerConstr = c.internal.enclosingOwner
if (!ownerConstr.isConstructor) {
c.abort(c.enclosingPosition, s"${c.macroApplication.symbol} can only be used as super constructor argument")
}
ownerConstr.owner.asClass
}

def discoverProjectsImpl: Tree = {
val sbtProjectCls = c.mirror.staticClass("_root_.sbt.Project")

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

val rootProjectSym =
c.mirror.staticClass("_root_.com.github.ghik.plainsbt.ProjectGroup")
.toType.member(TermName("root"))
projectGroupTpe.member(TermName("root"))

val ptpe = c.prefix.actualType
val ptpe = classBeingConstructed.toType
val arg = c.freshName(TermName("pg"))

val projectRefs =
ptpe.members.iterator
Expand All @@ -24,10 +35,10 @@ class Macros(val c: blackbox.Context) {
m.typeSignature.finalResultType.typeSymbol == sbtProjectCls &&
!(m :: m.overrides).contains(rootProjectSym)
}
.map(m => q"${c.prefix}.$m")
.map(m => q"$arg.asInstanceOf[$ptpe].$m")
.toList

q"_root_.scala.Seq(..$projectRefs)"
q"($arg: $projectGroupTpe) => _root_.scala.Seq(..$projectRefs)"
}

def mkFreshProject: Tree =
Expand Down
49 changes: 21 additions & 28 deletions src/main/scala/com/github/ghik/plainsbt/ProjectGroup.scala
Expand Up @@ -5,14 +5,11 @@ import sbt.*

import scala.language.experimental.macros

case class FreshProject(project: Project) extends AnyVal
object FreshProject {
implicit def materialize: FreshProject = macro Macros.mkFreshProject
}

abstract class ProjectGroup(
val groupName: String,
parent: OptArg[ProjectGroup] = OptArg.Empty
)(implicit
discoveredProjects: DiscoveredProjects,
) extends AutoPlugin {
private def rootProjectId: String = parent.fold(groupName)(p => s"${p.rootProjectId}-$groupName")
private def subProjectId(name: String): String = s"$rootProjectId-$name"
Expand All @@ -23,32 +20,32 @@ abstract class ProjectGroup(
* Settings shared by all the projects defined in this [[ProjectGroup]] and its child [[ProjectGroup]]s
* (i.e. those that declare this group as their [[parent]]).
*/
def commonSettings: Seq[Def.Setting[_]] = Seq.empty
def commonSettings: Seq[Def.Setting[?]] = Seq.empty

/**
* Settings shared by all the projects defined in this [[ProjectGroup]] and its child [[ProjectGroup]]s
* (i.e. those that declare this group as their [[parent]]), excluding the root project of this group.
*/
def subprojectSettings: Seq[Def.Setting[_]] = Seq.empty
def subprojectSettings: Seq[Def.Setting[?]] = Seq.empty

/**
* Settings shared by all subprojects defined via [[mkSubProject]] in this [[ProjectGroup]] and all its
* child [[ProjectGroup]]s. This is like [[commonSettings]] but excludes all the intermediate aggregating projects,
* i.e. the root projects of each [[ProjectGroup]].
*/
def leafSubprojectSettings: Seq[Def.Setting[_]] = Seq.empty
def leafSubprojectSettings: Seq[Def.Setting[?]] = Seq.empty

/**
* Settings shared by all the projects defined in this [[ProjectGroup]], including its root project
* (via [[mkRootProject]]) and directly defined subprojects (via [[mkSubProject]]).
*/
def directCommonSettings: Seq[Def.Setting[_]] = Seq.empty
def directCommonSettings: Seq[Def.Setting[?]] = Seq.empty

/**
* Settings shared by all the subprojects defined in this [[ProjectGroup]] via [[mkSubProject]].
* Like [[directCommonSettings]] but excludes the root project of this group.
*/
def directSubprojectSettings: Seq[Def.Setting[_]] = Seq.empty
def directSubprojectSettings: Seq[Def.Setting[?]] = Seq.empty

/**
* A [[ProjectReference]] to the root project of this group. Use this if referring directly
Expand All @@ -72,17 +69,12 @@ abstract class ProjectGroup(
*
* The root project will automatically aggregate all subprojects in the group
* (i.e. tasks invoked on the root project will also be invoked on aggregated subprojects).
*
* Note how `this` is being added as an sbt plugin of this root project. The purpose of this is to make
* sbt see all the subprojects in this project group via [[extraProjects]]. Subprojects themselves
* are listed by the [[enumerateSubprojects]] method (which must always be implemented with [[discoverProjects]]
* macro).
*/
protected def mkRootProject(implicit freshProject: FreshProject): Project =
freshProject.project.in(baseDir)
.withId(rootProjectId)
.enablePlugins(this)
.aggregate(enumerateSubprojects.map(p => p: ProjectReference) *)
.aggregate(subprojects.map(p => p: ProjectReference) *)
.settings(commonSettings)
.settings(directCommonSettings)
.settings(parent.mapOr(Nil, _.commonSettings))
Expand Down Expand Up @@ -111,18 +103,19 @@ abstract class ProjectGroup(
.settings(parent.mapOr(Nil, _.leafSubprojectSettings))
}

/**
* A macro that lists all the subprojects in this group. Subprojects must be assigned to public `lazy val`s.
* This macro must be used to implement [[enumerateSubprojects]] in every implementation of this class.
* The subprojects are then made visible to sbt via [[extraProjects]].
*/
protected final def discoverProjects: Seq[Project] = macro Macros.discoverProjectsImpl
final def subprojects: Seq[Project] = discoveredProjects.get(this)

/**
* Implement this method in subclasses using [[discoverProjects]] macro.
* This is a boilerplate that must be repeated in every implementation.
*/
protected def enumerateSubprojects: Seq[Project]
override final def extraProjects: Seq[Project] = subprojects
}

case class FreshProject(project: Project) extends AnyVal
object FreshProject {
implicit def materialize: FreshProject = macro Macros.mkFreshProject
}

override final def extraProjects: Seq[Project] = enumerateSubprojects
trait DiscoveredProjects {
def get(group: ProjectGroup): Seq[Project]
}
object DiscoveredProjects {
implicit def materialize: DiscoveredProjects = macro Macros.discoverProjectsImpl
}

0 comments on commit 6835b79

Please sign in to comment.