From 6835b79dfc96d99cd20db6aa238ae18c00fd2583 Mon Sep 17 00:00:00 2001 From: Roman Janusz Date: Sat, 18 Mar 2023 15:47:29 +0100 Subject: [PATCH] removed the discoverProjects boilerplate --- README.md | 15 ------ .../com/github/ghik/plainsbt/Macros.scala | 21 ++++++-- .../github/ghik/plainsbt/ProjectGroup.scala | 49 ++++++++----------- 3 files changed, 37 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index e790861..1d09679 100644 --- a/README.md +++ b/README.md @@ -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 } ``` @@ -146,9 +143,6 @@ 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) { @@ -156,9 +150,6 @@ object Commons extends ProjectGroup("commons", MyProj) { 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) { @@ -166,9 +157,6 @@ object FooService extends ProjectGroup("fooservice", MyProj) { 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) { @@ -176,9 +164,6 @@ object BarService extends ProjectGroup("barservice", MyProj) { 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 } ``` diff --git a/src/main/scala/com/github/ghik/plainsbt/Macros.scala b/src/main/scala/com/github/ghik/plainsbt/Macros.scala index 96a8651..3d65d91 100644 --- a/src/main/scala/com/github/ghik/plainsbt/Macros.scala +++ b/src/main/scala/com/github/ghik/plainsbt/Macros.scala @@ -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 @@ -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 = diff --git a/src/main/scala/com/github/ghik/plainsbt/ProjectGroup.scala b/src/main/scala/com/github/ghik/plainsbt/ProjectGroup.scala index ded9432..5e50bc5 100644 --- a/src/main/scala/com/github/ghik/plainsbt/ProjectGroup.scala +++ b/src/main/scala/com/github/ghik/plainsbt/ProjectGroup.scala @@ -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" @@ -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 @@ -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)) @@ -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 }