Skip to content

Commit

Permalink
start modularizing cbt into libraries
Browse files Browse the repository at this point in the history
this extracts certain parts of cbt into stand-alone libraries, which can
be published to maven and used outside of cbt.

This also adds scalariform for these parts of the code.

This slows down cbt’s own build a lot because of the number of projects
involved! So we’ll follow this by a bunch of performance tweak commits.
  • Loading branch information
cvogt committed Mar 27, 2017
1 parent d2f8cad commit bba2abe
Show file tree
Hide file tree
Showing 46 changed files with 819 additions and 370 deletions.
9 changes: 6 additions & 3 deletions build/build.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
package cbt_build.cbt
import cbt._
import cbt_internal._

Expand All @@ -12,8 +13,8 @@ class Build(val context: Context) extends Shared with Scalariform with PublishLo
super.dependencies ++ Resolver(mavenCentral).bind(
MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"),
ScalaDependency("org.scala-lang.modules","scala-xml",constants.scalaXmlVersion)
)
} :+ libraries.eval
) :+ libraries.reflect :+ libraries.eval
}

override def sources = Seq(
"nailgun_launcher", "stage1", "stage2", "compatibility"
Expand All @@ -22,7 +23,9 @@ class Build(val context: Context) extends Shared with Scalariform with PublishLo
override def scalariform = super.scalariform.copy(
Seq(
context.cbtHome / "stage2" / "DirectoryDependency.scala",
context.cbtHome / "stage2" / "LazyDependency.scala"
context.cbtHome / "stage2" / "LazyDependency.scala",
context.cbtHome / "stage2" / "libraries.scala",
context.cbtHome / "stage2" / "plugins.scala"
)
)

Expand Down
2 changes: 1 addition & 1 deletion build/build/build.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cbt_build.cbt.build
import cbt._
class Build(val context: Context) extends CbtInternal{
class Build(val context: Context) extends BuildBuild with CbtInternal{
override def dependencies = (
super.dependencies :+ cbtInternal.shared :+ plugins.scalariform
)
Expand Down
2 changes: 1 addition & 1 deletion cbt
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ stage1 () {
log "Checking for changes in cbt/nailgun_launcher" "$@"
NAILGUN_INDICATOR=$NAILGUN$TARGET../classes.last-success
changed=1
NAILGUN_SOURCES=("$NAILGUN"*.java)
NAILGUN_SOURCES=("$NAILGUN"*.java "$CBT_HOME"/libraries/common-0/*.java)
for file in "${NAILGUN_SOURCES[@]}"; do
if [ "$file" -nt "$NAILGUN_INDICATOR" ]; then changed=0; fi
done
Expand Down
35 changes: 35 additions & 0 deletions doc/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,40 @@ class Build(val context: Context) extends SomePlugin{
Such a simple replacement of `b` while keeping all other arguments would
not be easily possible if doSomething was a def not a case class.

## Why do the libraries have ops packages and Module traits?

Java's and Scala's package system does allow importing things,
but not exporting things. Everything has to be imported
explicitly anywhere it is supposed to be used. It's just a
package system, not a module system. This leads to a lot of
import boiler plate. CBT tries to minimize the imports
necessary for it's use however. So how to we do this while
at the same time allowing modularization? In particular, how
do we do this with stand-alone methods and implicit classes
that have to be in an object, e.g. the package object?

Scala's traits can be used as a module system that supports exports.
This means we can take several modules (traits) and merge them into
something that exports everything defined in any of them. Basically
inheriting a trait means importing names into the scope of the
inheriting class and exporting those names to the class's users.

CBT's libraries define Module traits, which their package objects inherit.
This makes it easy to use the libraries by itself. CBT's core however
also inherits all of the library Module traits in it's package object,
meaning that by a simple `import cbt._` you get everything from all
libraries. This solves the import boiler plate.

For implicit classes it is a little bit trickier as those should
extend AnyVal for performance reasons, but that's only allowed in
an object, not a trait. So what we do instead is put Universal traits
(see http://docs.scala-lang.org/overviews/core/value-classes.html)
containing all the logic into a helper package `ops`. The package
objects that want to offer the implicit classes now define them
extending the Universal traits. This means a little boiler plate
where the package object is define, but also solves the import
boiler plate everywhere else.

## What is newBuild and why do we need it?

Methods in a class can call each other and thereby effectively form a graph.
Expand Down Expand Up @@ -137,3 +171,4 @@ trait CrossVersionPlugin{

Problem solved. In fact this allows for a very, very flexible way of
creating differents variants of your build.

51 changes: 51 additions & 0 deletions internal/plugins/library/Library.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cbt_internal
import cbt._
import java.io._
import scala.concurrent._
import scala.concurrent.duration._
trait Library extends Scalariform with GoogleJavaFormat with DynamicOverrides with AdvancedScala{
def inceptionYear: Int
def description: String
def version = ???
override def compile = {
googleJavaFormat()
scalariform()
super.compile
}

def publishIfChanged = newBuild[PublishIfChanged]({s"""
def inceptionYear = $inceptionYear
def description = ${description.quote}
def apply = if(changedInMaster) publish
"""})
}

trait PublishIfChanged extends PackageJars with DynamicOverrides with Shared{
override def url = super.url ++ "/libraries/" ++ name

def gitHash = {
val p = new ProcessBuilder(
"git rev-parse HEAD".split(" "): _*
)
.directory( projectDirectory )
.start

val sout = new InputStreamReader(p.getInputStream);
import scala.concurrent.ExecutionContext.Implicits.global
val out = Future(blocking(Iterator.continually(sout.read).takeWhile(_ != -1).map(_.toChar).mkString))
p.waitFor
val revision = Await.result( out, Duration.Inf ).trim
revision
}
override def version = "rev-"++gitHash

def changedInMaster = (
0 ===
new ProcessBuilder(
"git diff --exit-code --quiet master..master^ .".split(" "): _*
)
.directory( projectDirectory )
.start
.waitFor
)
}
7 changes: 7 additions & 0 deletions internal/plugins/library/build/build.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cbt_build.cbt_internal.library_build_plugin
import cbt._
class Build(val context: Context) extends Plugin with CbtInternal{
override def dependencies = (
super.dependencies :+ cbtInternal.shared :+ plugins.scalariform :+ plugins.googleJavaFormat
)
}
2 changes: 1 addition & 1 deletion internal/plugins/shared/Shared.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cbt_internal
import cbt._
import java.net.URL
trait Shared extends SonatypeRelease with SnapshotVersion with GithubPom{
trait Shared extends AdvancedScala with SonatypeRelease with SnapshotVersion with GithubPom{
override def user = "cvogt"
override def groupId = "org.cvogt"
override def organization = Some( Organization( "Jan Christopher Vogt", Some( new URL("http://cvogt.org") ) ) )
Expand Down
12 changes: 11 additions & 1 deletion libraries/capture_args/build/build.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package cbt_build.cbt.capture_args
import cbt._
class Build(val context: Context) extends BaseBuild{
import cbt_internal._
class Build(val context: Context) extends Library{
def description = (
"macro that allows you to extract a functions arguments"
++" as strings in order to programmatically pass them to a stringly typed"
++" api such as a process call, http or a .main method"
)

def inceptionYear = 2017

override def dependencies = (
super.dependencies ++ // don't forget super.dependencies here for scala-library, etc.
Resolver( mavenCentral ).bind(
MavenDependency( "org.scala-lang", "scala-reflect", scalaVersion )
)
)

override def scalacOptions = super.scalacOptions :+ "-language:experimental.macros"
}
5 changes: 5 additions & 0 deletions libraries/capture_args/build/build/build.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package cbt_build.cbt.capture_args.build
import cbt._
class Build(val context: Context) extends BuildBuild with CbtInternal{
override def dependencies = super.dependencies :+ cbtInternal.library
}
28 changes: 14 additions & 14 deletions libraries/capture_args/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@ package cbt.capture_args
import scala.reflect._
import scala.reflect.macros.blackbox.Context

case class Argument( annotations: Seq[annotation.Annotation], name: String, values: Option[Seq[String]] ){
case class Argument( annotations: Seq[annotation.Annotation], name: String, values: Option[Seq[String]] ) {
def toSeqOption = values.map( name +: _ )
}
case class Signature( name: String, args: Seq[Argument] )

object `package`{
def captureArgsImplementation(c: Context): c.Tree = {
object `package` {
def captureArgsImplementation( c: Context ): c.Tree = {
import c.universe._

def literal( a: Any ) = Literal(Constant(a))
def ident( name: String ) = Ident(TermName(name))
def literal( a: Any ) = Literal( Constant( a ) )
def ident( name: String ) = Ident( TermName( name ) )

def findOwnerRecursive(symbol: Symbol, predicate: Symbol => Boolean): Option[Symbol] = {
Option(symbol).flatMap{
def findOwnerRecursive( symbol: Symbol, predicate: Symbol => Boolean ): Option[Symbol] = {
Option( symbol ).flatMap {
s =>
if(s == NoSymbol) None else if(predicate(s)) Some(s) else findOwnerRecursive(s.owner, predicate)
if ( s == NoSymbol ) None else if ( predicate( s ) ) Some( s ) else findOwnerRecursive( s.owner, predicate )
}
}

val method: MethodSymbol = (
findOwnerRecursive(c.internal.enclosingOwner, _.isMethod).map(_.asMethod)
findOwnerRecursive( c.internal.enclosingOwner, _.isMethod ).map( _.asMethod )
orElse
findOwnerRecursive(c.internal.enclosingOwner, _.isClass).map(_.asClass.primaryConstructor.asMethod)
findOwnerRecursive( c.internal.enclosingOwner, _.isClass ).map( _.asClass.primaryConstructor.asMethod )
getOrElse {
c.error(
c.enclosingPosition,
Expand All @@ -33,14 +33,14 @@ object `package`{
???
}
)
val name = literal(method.name.decodedName.toString)
val name = literal( method.name.decodedName.toString )
// Note: method.paramLists requires explicitly annotated result type
val params = method.paramLists.flatten.map(_.asTerm)
val params = method.paramLists.flatten.map( _.asTerm )

val args = params.map{ s =>
val args = params.map { s =>
val name = literal( s.name.decodedName.toString )
val i = ident( s.name.toString )
q"_root_.cbt.capture_args.Argument( _root_.scala.Seq( ..${s.annotations.map(_.tree)} ), $name, valueToStrings($i) )"
q"_root_.cbt.capture_args.Argument( _root_.scala.Seq( ..${s.annotations.map( _.tree )} ), $name, valueToStrings($i) )"
}
val tree = q"""
_root_.cbt.capture_args.Signature( name = $name, args = Seq( ..$args ) )
Expand Down
Loading

0 comments on commit bba2abe

Please sign in to comment.