aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Christopher Vogt <oss.nsp@cvogt.org>2017-03-11 18:58:56 -0500
committerGitHub <noreply@github.com>2017-03-11 18:58:56 -0500
commit4eb753e4d4ef5be7443b99832892bac697b10b50 (patch)
treed9200d33f7d2f9b1ed9884330fcd4c6ca651f44a
parente760ee9e4c4c3b3f39e2bb2dfd47d2b142e12a53 (diff)
parentcf859909fbcf577bac92d9f35f5d11e638a66177 (diff)
downloadcbt-4eb753e4d4ef5be7443b99832892bac697b10b50.tar.gz
cbt-4eb753e4d4ef5be7443b99832892bac697b10b50.tar.bz2
cbt-4eb753e4d4ef5be7443b99832892bac697b10b50.zip
Merge pull request #412 from cvogt/chris
various
-rw-r--r--build/build.scala26
-rw-r--r--build/build/build.scala7
-rw-r--r--circle.yml4
-rw-r--r--doc/cbt-developer/version-compatibility.md50
-rw-r--r--doc/design.md (renamed from doc/design.txt)0
-rw-r--r--doc/plugin-author-guide.md105
-rw-r--r--examples/multi-combined-example/build/build/build.scala4
-rw-r--r--internal/plugins/shared/Shared.scala13
-rw-r--r--internal/plugins/shared/build/build.scala5
-rw-r--r--libraries/proguard/build/build.scala2
-rw-r--r--plugins/google-java-format/GoogleJavaFormat.scala34
-rw-r--r--plugins/google-java-format/Immutable.java5
-rw-r--r--plugins/google-java-format/build/build.scala9
-rw-r--r--plugins/scalafmt/Scalafmt.scala21
-rw-r--r--stage2/BasicBuild.scala2
-rw-r--r--stage2/BuildBuild.scala18
-rw-r--r--stage2/BuildDependency.scala40
-rw-r--r--stage2/GitDependency.scala122
-rw-r--r--stage2/Scaffold.scala7
-rw-r--r--test/simple-fixed/Main.scala2
-rw-r--r--test/simple-fixed/build/build.scala18
-rw-r--r--test/simple/Main.scala3
-rw-r--r--test/simple/build/build.scala5
-rw-r--r--test/test.scala44
24 files changed, 379 insertions, 167 deletions
diff --git a/build/build.scala b/build/build.scala
index b4a39ea..4114bc5 100644
--- a/build/build.scala
+++ b/build/build.scala
@@ -1,6 +1,12 @@
import cbt._
+import cbt_internal._
+
+class Build(val context: Context) extends Shared{
+ override def name: String = "cbt"
+ override def version: String = ???
+ override def description: String = "Fast, intuitive Build Tool for Scala"
+ override def inceptionYear: Int = 2015
-class Build(val context: Context) extends Publish{
// FIXME: somehow consolidate this with cbt's own boot-strapping from source.
override def dependencies = {
super.dependencies ++ Resolver(mavenCentral).bind(
@@ -9,22 +15,8 @@ class Build(val context: Context) extends Publish{
ScalaDependency("org.scala-lang.modules","scala-xml","1.0.5")
)
}
+
override def sources = Seq(
"nailgun_launcher", "stage1", "stage2", "compatibility"
- ).map(d => projectDirectory ++ ("/" + d))
-
- def groupId: String = "org.cvogt"
-
- def version: String = "0.9"
- override def name: String = "cbt"
-
- // Members declared in cbt.Publish
- def description: String = "Fast, intuitive Build Tool for Scala"
- def developers: Seq[cbt.Developer] = Nil
- def inceptionYear: Int = 2016
- def licenses: Seq[cbt.License] = Seq( License.Apache2 )
- def organization: Option[cbt.Organization] = None
- def scmConnection: String = ""
- def scmUrl: String = ""
- def url: java.net.URL = new java.net.URL("http://github.com/cvogt/cbt/")
+ ).map( projectDirectory / _ ).flatMap( _.listFiles )
}
diff --git a/build/build/build.scala b/build/build/build.scala
new file mode 100644
index 0000000..313b2b5
--- /dev/null
+++ b/build/build/build.scala
@@ -0,0 +1,7 @@
+package cbt_build.cbt.build
+import cbt._
+class Build(val context: Context) extends CbtInternal{
+ override def dependencies = (
+ super.dependencies :+ cbtInternal.shared
+ )
+}
diff --git a/circle.yml b/circle.yml
index 0a6f9d5..92b8983 100644
--- a/circle.yml
+++ b/circle.yml
@@ -13,7 +13,7 @@ dependencies:
test:
override:
- rm ~/.gitconfig # avoid url replacement breaking jgit
- - ./cbt direct test.run:
+ - ./cbt direct test.run slow:
timeout: 1800
- - ./cbt test.run:
+ - ./cbt test.run slow:
timeout: 1800
diff --git a/doc/cbt-developer/version-compatibility.md b/doc/cbt-developer/version-compatibility.md
new file mode 100644
index 0000000..986bb37
--- /dev/null
+++ b/doc/cbt-developer/version-compatibility.md
@@ -0,0 +1,50 @@
+## Why does CBT version compatiblity matter?
+CBT allows fixing the version used using the `// cbt: ` annotation.
+This means that the CBT version you installed will download the
+CBT version referenced there and use that instead. CBT also
+allows composing builds, which require different CBT versions.
+In order for all of this to work, different CBT versions need to
+be able to talk to one another.
+
+This chapter is about how the current solution works and about
+the pitfalls involved which can make a new version of CBT
+incompatible with older versions.
+
+## How can compatibility be broken?
+The current solution mostly relies on the Java interfaces in
+the `compability/` folder. Changing the Java interface in a
+non-backwards-compatible way means making the CBT incompatible with
+older versions. Java 8 default methods make this a whole lot easier
+and this is the main reason CBT relies on Java 8. But we also
+want to keep this interfaces as small as possible in order to
+minimize the risk.
+
+However there are more things that can break compatibility when changed:
+- the format of the `// cbt: ` version string
+- the name and format of Build classes (or files) that CBT looks for
+- communication between versions via reflection in particular
+ - how the TrapSecurityManager of each CBT version talks to the
+ installed TrapSecurityManager via reflection
+
+## How to detect accidental breakages?
+
+CBT's tests have a few tests with `// cbt: ` annotations and some
+reference libraries as Git dependencies that use these annotations.
+Many incompatibilities will lead to these tests failing, either with
+a compilation error against the `compatiblity/` interfaces or with
+a runtime exception or unexpected behavior in case other things are
+broken.
+
+## How an we improve the situation in the long run?
+
+In the long run we should think about how to reduce the risk
+or sometimes even unavoidability of incompatibilities.
+The unavoidability mostly stems from limitations of what Java
+interfaces can express. However Java 8 interfaces mostly work
+fairly well. We should consider using them for the cases that
+currently use reflection instead of reflection.
+
+If Java 8 interfaces still turn out to be a problem in the long run,
+we could consider an interface that we control completely, e.g.
+an internal serialization formation, which CBT versions use to talk
+to each other.
diff --git a/doc/design.txt b/doc/design.md
index 28bbdaa..28bbdaa 100644
--- a/doc/design.txt
+++ b/doc/design.md
diff --git a/doc/plugin-author-guide.md b/doc/plugin-author-guide.md
new file mode 100644
index 0000000..db0eede
--- /dev/null
+++ b/doc/plugin-author-guide.md
@@ -0,0 +1,105 @@
+## How to write an idiomatic CBT plugin?
+
+Write a small library that could be fully used outside of CBT's
+build classes and handles all the use cases you need. For example
+
+```
+object MyLibrary{
+ def doSomething( ... ) = // do something here
+}
+```
+
+Publish it as a library and you might be done right here.
+
+If your library requires configuration information commonly found
+in your build, like the sourceFiles, groupId, scalaVersion or else,
+consider offering a mixin trait, that pre-configures your library
+for user convenience. (Consider publishing them separately if that
+allows people to use your library outside of CBT with fewer
+dependencies.) Here is an example of a library with an
+accompanying mixin trait configuring the library for CBT.
+
+```
+package my.library
+object MyLibrary{
+ case class DoSomething( scalaVersion: String, ... ){
+ case class config( targetFile: File, affectBehavior: Boolean = true, ... ){
+ def apply = // really do something here
+ }
+ }
+}
+
+package my.plugin
+trait MyLibrary extends BaseBuild{
+ def doSomething = MyLibrary.DoSomething(scalaVersion, ...).config( scalaTarget / "my.file" )
+}
+```
+
+* Note: Do not override any common method like `compile` or `test` in a public plugin. *
+* Instead document recommendations where users should hook in your custom methods. *
+* This will help users understand their own builds. *
+
+See how we only define a single `def doSomething` in the MyLibrary
+trait? We did not define things like `def doSomethingTargetFile`.
+Instead we have a case class defined in the library which a user
+can .copy as needed to customize configuration. A user build could
+look like this:
+
+```
+class Build(val context: Context) extends MyLibrary{
+ override def doSomething = super.doSomething.copy(
+ affectBehavior = false
+ )
+}
+```
+
+As you can see a user can use .copy to override default behavior
+of your library.
+
+This nesting allows us to keep the global namespace small, which
+helps us lower the risk of global name clashes between different
+libraries. It also makes it clearer that `affectBehavior` is
+something specific to `MyLibary` which should help making builds
+easier to understand. Further this nesting means we don't need to
+encode namespaces in the names themselves, but use Scala's language
+features for that, which allows us to keep names nice and concise.
+
+You might wonder why there is a case class `DoSomething` rather
+than `scalaVersion` just being another parameter in case class
+`config`. Nesting case classes like this is a pattern that given
+the way we use them allows us to make it slightly harder to
+modify some parameters (the ones on the outer case class) than
+others (the ones on the inner case class). Why? Some are likely
+to need user customization, others are likely to break stuff if
+they are touched. Example: The scalaVersion is probably something
+you want to configure once consistently across your entire build.
+Otherwise you might end up accidentally packaging scala-2.11
+compiled class files as a jar with a `_2.10` artifact id.
+Changing which targetFile `doSomething` writes to however is
+something you should be able to safely change. Since we defined
+`doSomething` as
+`def doSomething = MyLibrary.DoSomething(...).config( ... )`
+overriding behavior in user code with .copy only affects
+the inner case class because super.doSomething is an instance
+of that:
+```
+ override def doSomething = super.doSomething.copy(
+ affectBehavior = false
+ )
+```
+Overriding things in class `DoSomething` is possible by creating
+an entire new instance of the outer one, but slightly harder
+preventing users from accidentally doing the wrong thing.
+
+Obviously this decisions what's dangerous to override and what
+is not can be a judgment call and not 100% clear.
+
+A few more conventions for more uniform plugin designs:
+If you only have one outer case class in yur plugin `object`,
+call the case class `apply` instead of `DoSomething`. If you
+need multiple (because you basically have several commands,
+each with their own private configuration), give it a name
+representing the operation, e.g. `compile` or `doc`. If there
+is only one inner case class inside of anothe case class,
+call it `config`, give it a name representing the operation,
+e.g. `compile` or `doc`.
diff --git a/examples/multi-combined-example/build/build/build.scala b/examples/multi-combined-example/build/build/build.scala
index b87351d..a5b0201 100644
--- a/examples/multi-combined-example/build/build/build.scala
+++ b/examples/multi-combined-example/build/build/build.scala
@@ -1,8 +1,8 @@
package cbt_build.cbt_examples.multi_combined_example.build
import cbt._
class Build(val context: Context) extends BuildBuild{
- //println(DirectoryDependency( projectDirectory / ".." / "sub4" / "build" ).dependency.exportedClasspath)
+ //println(DirectoryDependency( projectDirectory / ".." / "sub4" / "build" ).exportedClasspath)
override def dependencies: Seq[cbt.Dependency] =
- super.dependencies :+ DirectoryDependency( projectDirectory / ".." / "sub4" / "build" ).dependency
+ super.dependencies :+ DirectoryDependency( projectDirectory / ".." / "sub4" / "build" )
def foo = DirectoryDependency( projectDirectory / ".." / "sub4" / "build" )
}
diff --git a/internal/plugins/shared/Shared.scala b/internal/plugins/shared/Shared.scala
new file mode 100644
index 0000000..90bc4b2
--- /dev/null
+++ b/internal/plugins/shared/Shared.scala
@@ -0,0 +1,13 @@
+package cbt_internal
+import cbt._
+import java.net.URL
+trait Shared extends 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") ) ) )
+ override def licenses = Seq( License.Apache2 )
+ override def developers = Seq(cvogt)
+ override def githubProject = "cbt"
+
+ def cvogt = Developer("cvogt", "Jan Christopher Vogt", "-5", new URL("https://github.com/cvogt/"))
+}
diff --git a/internal/plugins/shared/build/build.scala b/internal/plugins/shared/build/build.scala
new file mode 100644
index 0000000..1d6fc29
--- /dev/null
+++ b/internal/plugins/shared/build/build.scala
@@ -0,0 +1,5 @@
+package cbt_build.cbt_internal.library_build_plugin
+import cbt._
+class Build(val context: Context) extends Plugin{
+ override def dependencies = super.dependencies :+ plugins.sonatypeRelease
+}
diff --git a/libraries/proguard/build/build.scala b/libraries/proguard/build/build.scala
index c781ce2..3ca38b5 100644
--- a/libraries/proguard/build/build.scala
+++ b/libraries/proguard/build/build.scala
@@ -15,7 +15,7 @@ class Build(val context: Context) extends Scalafmt{
}
override def scalafmt = super.scalafmt.copy(
- config = super.scalafmt.lib.cbtRecommendedConfig,
+ config = Scalafmt.cbtRecommendedConfig,
whiteSpaceInParenthesis = true
)
diff --git a/plugins/google-java-format/GoogleJavaFormat.scala b/plugins/google-java-format/GoogleJavaFormat.scala
new file mode 100644
index 0000000..cbccb94
--- /dev/null
+++ b/plugins/google-java-format/GoogleJavaFormat.scala
@@ -0,0 +1,34 @@
+package cbt
+
+import java.io.File
+import java.nio.file.Files._
+import java.nio.file._
+
+import com.google.googlejavaformat.java._
+
+trait GoogleJavaFormat extends BaseBuild {
+ def googleJavaFormat() = GoogleJavaFormat.apply( lib, sourceFiles.filter(_.string endsWith ".java") ).format
+}
+
+object GoogleJavaFormat{
+ case class apply( lib: Lib, files: Seq[File] ){
+ /** @param whiteSpaceInParenthesis more of a hack to make up for missing support in Scalafmt. Does not respect alignment and maxColumn. */
+ def format = {
+ val (successes, errors) = lib.transformFilesOrError( files, in =>
+ try{
+ Right( new Formatter().formatSource(in) )
+ } catch {
+ case e: FormatterException => Left( e )
+ }
+ )
+ if(errors.nonEmpty)
+ throw new RuntimeException(
+ "Google Java Format failed to parse some files:\n" ++ errors.map{
+ case (file, error) => file.string ++ ":" ++ error.toString
+ }.mkString("\n"),
+ errors.head._2
+ )
+ successes
+ }
+ }
+}
diff --git a/plugins/google-java-format/Immutable.java b/plugins/google-java-format/Immutable.java
new file mode 100644
index 0000000..5b3ff44
--- /dev/null
+++ b/plugins/google-java-format/Immutable.java
@@ -0,0 +1,5 @@
+package com.google.errorprone.annotations;
+// to suppress warning
+// "Class com.google.errorprone.annotations.Immutable not found - continuing with a stub."
+// there probably is a better solution
+public class Immutable{}
diff --git a/plugins/google-java-format/build/build.scala b/plugins/google-java-format/build/build.scala
new file mode 100644
index 0000000..50bc423
--- /dev/null
+++ b/plugins/google-java-format/build/build.scala
@@ -0,0 +1,9 @@
+import cbt._
+
+class Build(val context: Context) extends Plugin {
+ override def dependencies =
+ super.dependencies ++
+ Resolver( mavenCentral ).bind(
+ MavenDependency( "com.google.googlejavaformat", "google-java-format", "1.3" )
+ )
+}
diff --git a/plugins/scalafmt/Scalafmt.scala b/plugins/scalafmt/Scalafmt.scala
index 9d42cbd..5535964 100644
--- a/plugins/scalafmt/Scalafmt.scala
+++ b/plugins/scalafmt/Scalafmt.scala
@@ -12,16 +12,15 @@ import java.nio.file._
trait Scalafmt extends BaseBuild {
/** Reformat scala source code according to `scalafmtConfig` rules */
def scalafmt = {
- val scalafmtLib = new ScalafmtLib(lib)
- scalafmtLib.format( sourceFiles ).config(
- scalafmtLib.loadConfig(
+ Scalafmt.apply( lib, sourceFiles.filter(_.string endsWith ".scala") ).config(
+ Scalafmt.loadConfig(
projectDirectory.toPath
) getOrElse ScalafmtConfig.default
)
}
}
-class ScalafmtLib(lib: Lib){ scalafmtLib =>
+object Scalafmt{
def userHome = Option( System.getProperty("user.home") ).map(Paths.get(_))
/** Tries to load config from .scalafmt.conf in given directory or fallback directory */
@@ -34,22 +33,18 @@ class ScalafmtLib(lib: Lib){ scalafmtLib =>
.flatMap ( file => StyleCache.getStyleForFile(file.toString) )
}
- case class format(files: Seq[File]){
- /**
- * @param whiteSpaceInParenthesis more of a hack to make up for missing support in Scalafmt. Does not respect alignment and maxColumn.
- */
+ case class apply( lib: Lib, files: Seq[File] ){
+ /** @param whiteSpaceInParenthesis more of a hack to make up for missing support in Scalafmt. Does not respect alignment and maxColumn. */
case class config(
- config: ScalafmtConfig,
- whiteSpaceInParenthesis: Boolean = false
+ config: ScalafmtConfig, whiteSpaceInParenthesis: Boolean = false
) extends (() => Seq[File]){
- def lib = scalafmtLib
def apply = {
- val (successes, errors) = scalafmtLib.lib.transformFilesOrError(
+ val (successes, errors) = lib.transformFilesOrError(
files,
org.scalafmt.Scalafmt.format(_, config) match {
case Formatted.Success(formatted) => Right(
if( whiteSpaceInParenthesis ){
- scalafmtLib.whiteSpaceInParenthesis(formatted)
+ Scalafmt.whiteSpaceInParenthesis(formatted)
} else formatted
)
case Formatted.Failure( e ) => Left( e )
diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala
index 0d8017f..68bda15 100644
--- a/stage2/BasicBuild.scala
+++ b/stage2/BasicBuild.scala
@@ -212,7 +212,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge
val testDirectory = projectDirectory / "test"
if( (testDirectory / lib.buildDirectoryName / lib.buildFileName).exists ){
// FIYME: maybe we can make loadRoot(...).finalBuild an Option some
- DirectoryDependency( testDirectory ).dependency
+ DirectoryDependency( testDirectory )
} else {
new BasicBuild( context.copy(workingDirectory = testDirectory) ){
override def dependencies = Seq(
diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala
index c312df9..9d3458d 100644
--- a/stage2/BuildBuild.scala
+++ b/stage2/BuildBuild.scala
@@ -14,8 +14,9 @@ class plugins(implicit context: Context){
context.copy(
workingDirectory = context.cbtHome / "plugins" / dir
)
- ).dependency
+ )
final lazy val essentials = plugin( "essentials" )
+ final lazy val googleJavaFormat = plugin( "google-java-format" )
final lazy val proguard = plugin( "proguard" )
final lazy val sbtLayout = plugin( "sbt_layout" )
final lazy val scalafmt = plugin( "scalafmt" )
@@ -31,7 +32,7 @@ trait BuildBuildWithoutEssentials extends BaseBuild{
assert(
projectDirectory.getName === lib.buildDirectoryName,
- "You can't extend ${lib.buildBuildClassName} in: " + projectDirectory + "/" + lib.buildDirectoryName
+ s"You can't extend ${lib.buildBuildClassName} in: " + projectDirectory + "/" + lib.buildDirectoryName
)
protected final val managedContext = context.copy(
@@ -66,9 +67,9 @@ trait BuildBuildWithoutEssentials extends BaseBuild{
// otherwise we'd have to recursively build all versions since
// the beginning. Instead CBT always needs to build the pure Java
// Launcher in the checkout with itself and then run it via reflection.
- val dep = new GitDependency(base, hash, Some("nailgun_launcher"))
- val ctx = managedContext.copy( cbtHome = dep.checkout )
- dep.classLoader
+ val build = GitDependency(base, hash, Some("nailgun_launcher")).asInstanceOf[BaseBuild]
+ val ctx = managedContext.copy( cbtHome = build.projectDirectory.getParentFile )
+ build.classLoader
.loadClass( "cbt.NailgunLauncher" )
.getMethod( "getBuild", classOf[AnyRef] )
.invoke( null, ctx )
@@ -116,3 +117,10 @@ trait BuildBuildWithoutEssentials extends BaseBuild{
if( c == p ) this else managedBuild.finalBuild( current )
}
}
+
+trait CbtInternal extends BuildBuild{
+ protected object cbtInternal{
+ def shared = DirectoryDependency(context.cbtHome / "/internal/plugins/shared")
+ def library = DirectoryDependency(context.cbtHome / "/internal/plugins/library")
+ }
+}
diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala
index 9a2918a..d7520d8 100644
--- a/stage2/BuildDependency.scala
+++ b/stage2/BuildDependency.scala
@@ -15,43 +15,39 @@ trait TriggerLoop extends DependencyImplementation{
def triggerLoopFiles: Seq[File]
}
/** You likely want to use the factory method in the BasicBuild class instead of this. */
-final case class DirectoryDependency(context: Context, pathToNestedBuild: String*) extends TriggerLoop{
- def classLoaderCache = context.classLoaderCache
- override def toString = show
- override def show = this.getClass.getSimpleName ++ "(" ++ context.workingDirectory.string ++ ")"
- def moduleKey = this.getClass.getName ++ "("+context.workingDirectory.string+")"
- lazy val logger = context.logger
- override lazy val lib: Lib = new Lib(logger)
- def transientCache = context.transientCache
- private lazy val root = lib.loadRoot( context )
- lazy val dependency: Dependency = {
+object DirectoryDependency{
+ def apply(context: Context, pathToNestedBuild: String*): BuildInterface = {
+ val lib: Lib = new Lib(context.logger)
+
// TODO: move this into finalBuild probably
- def selectNestedBuild( build: Dependency, names: Seq[String], previous: Seq[String] ): Dependency = {
+ // TODO: unify this with lib.callReflective
+ def selectNestedBuild( build: BuildInterface, names: Seq[String], previous: Seq[String] ): BuildInterface = {
names.headOption.map{ name =>
if( lib.taskNames(build.getClass).contains(name) ){
val method = build.getClass.getMethod(name)
val returnType = method.getReturnType
- if( classOf[Dependency] isAssignableFrom returnType ){
+ if( classOf[BuildInterface] isAssignableFrom returnType ){
selectNestedBuild(
- method.invoke(build).asInstanceOf[Dependency],
+ method.invoke(build).asInstanceOf[BuildInterface],
names.tail,
previous :+ name
)
} else {
- throw new RuntimeException( s"Expected subtype of Dependency, found $returnType for " + previous.mkString(".") + " in " + show )
+ throw new RuntimeException(
+ s"Expected subtype of BuildInterface, found $returnType for " + previous.mkString(".") + " in " + build
+ )
}
} else {
- throw new RuntimeException( (previous :+ name).mkString(".") + " not found in " + show )
+ throw new RuntimeException( (previous :+ name).mkString(".") + " not found in " + build )
}
}.getOrElse( build )
}
- selectNestedBuild( root.finalBuild(context.workingDirectory), pathToNestedBuild, Nil )
+ selectNestedBuild(
+ lib.loadRoot( context ).finalBuild(context.workingDirectory),
+ pathToNestedBuild,
+ Nil
+ )
}
- def exportedClasspath = ClassPath()
- def dependencies = Seq(dependency)
- def triggerLoopFiles = root.triggerLoopFiles
- def lastModified = dependency.lastModified
- def targetClasspath = ClassPath()
}
/*
case class DependencyOr(first: DirectoryDependency, second: JavaDependency) extends ProjectProxy with DirectoryDependencyBase{
@@ -59,4 +55,4 @@ case class DependencyOr(first: DirectoryDependency, second: JavaDependency) exte
def triggerLoopFiles = if(isFirst) first.triggerLoopFiles else Seq()
protected val delegate = if(isFirst) first else second
}
-*/ \ No newline at end of file
+*/
diff --git a/stage2/GitDependency.scala b/stage2/GitDependency.scala
index f2ac7a6..028401b 100644
--- a/stage2/GitDependency.scala
+++ b/stage2/GitDependency.scala
@@ -9,70 +9,68 @@ import org.eclipse.jgit.lib.Ref
object GitDependency{
val GitUrl = "(git:|https:|file:/)//([^/]+)/(.+)".r
-}
-case class GitDependency(
- url: String, ref: String, subDirectory: Option[String] = None, // example: git://github.com/cvogt/cbt.git#<some-hash>
- pathToNestedBuild: Seq[String] = Seq()
-)(implicit val logger: Logger, classLoaderCache: ClassLoaderCache, context: Context ) extends DependencyImplementation{
- import GitDependency._
- override def lib = new Lib(logger)
- def classLoaderCache = context.classLoaderCache
- def moduleKey = (
- this.getClass.getName
- ++ "(" ++ url ++ subDirectory.map("/" ++ _).getOrElse("") ++ "#" ++ ref
- ++ ", "
- ++ pathToNestedBuild.mkString(", ")
- ++ ")"
- )
- def transientCache = context.transientCache
- // TODO: add support for authentication via ssh and/or https
- // See http://www.codeaffine.com/2014/12/09/jgit-authentication/
- private val GitUrl( _, domain, path ) = url
+ def apply(
+ url: String, ref: String, subDirectory: Option[String] = None, // example: git://github.com/cvogt/cbt.git#<some-hash>
+ pathToNestedBuild: Seq[String] = Seq()
+ )(implicit context: Context ): BuildInterface = {
+ // TODO: add support for authentication via ssh and/or https
+ // See http://www.codeaffine.com/2014/12/09/jgit-authentication/
+ val GitUrl( _, domain, path ) = url
+ val credentialsFile = context.workingDirectory ++ "/git.login"
+ def authenticate(_git: CloneCommand) =
+ if(!credentialsFile.exists){
+ _git
+ } else {
+ val (user, password) = {
+ // TODO: implement safer method than reading credentials from plain text file
+ val c = new String(readAllBytes(credentialsFile.toPath)).split("\n").head.trim.split(":")
+ (c(0), c.drop(1).mkString(":"))
+ }
+ _git.setCredentialsProvider( new UsernamePasswordCredentialsProvider(user, password) )
+ }
- private val credentialsFile = context.workingDirectory ++ "/git.login"
+ val logger = context.logger
- private def authenticate(_git: CloneCommand) =
- if(!credentialsFile.exists){
- _git
- } else {
- val (user, password) = {
- // TODO: implement safer method than reading credentials from plain text file
- val c = new String(readAllBytes(credentialsFile.toPath)).split("\n").head.trim.split(":")
- (c(0), c.drop(1).mkString(":"))
- }
- _git.setCredentialsProvider( new UsernamePasswordCredentialsProvider(user, password) )
- }
+ def moduleKey = (
+ this.getClass.getName
+ ++ "(" ++ url ++ subDirectory.map("/" ++ _).getOrElse("") ++ "#" ++ ref
+ ++ ", "
+ ++ pathToNestedBuild.mkString(", ")
+ ++ ")"
+ )
- def checkout: File = taskCache[GitDependency]("checkout").memoize{
- val checkoutDirectory = context.cache ++ s"/git/$domain/$path/$ref"
- val _git = if(checkoutDirectory.exists){
- logger.git(s"Found existing checkout of $url#$ref in $checkoutDirectory")
- val _git = new Git(new FileRepository(checkoutDirectory ++ "/.git"))
- val actualRef = _git.getRepository.getBranch
- if(actualRef != ref){
- logger.git(s"actual ref '$actualRef' does not match expected ref '$ref' - fetching and checking out")
- _git.fetch().call()
- _git.checkout().setName(ref).call
- }
- _git
- } else {
- logger.git(s"Cloning $url into $checkoutDirectory")
- val _git = authenticate(
- Git
- .cloneRepository()
- .setURI(url)
- .setDirectory(checkoutDirectory)
- ).call()
+ val taskCache = new PerClassCache(context.transientCache, moduleKey)(logger)
- logger.git(s"Checking out ref $ref")
- _git.checkout().setName(ref).call()
- _git
+ def checkout: File = taskCache[Dependency]("checkout").memoize{
+ val checkoutDirectory = context.cache ++ s"/git/$domain/$path/$ref"
+ val _git = if(checkoutDirectory.exists){
+ logger.git(s"Found existing checkout of $url#$ref in $checkoutDirectory")
+ val _git = new Git(new FileRepository(checkoutDirectory ++ "/.git"))
+ val actualRef = _git.getRepository.getBranch
+ if(actualRef != ref){
+ logger.git(s"actual ref '$actualRef' does not match expected ref '$ref' - fetching and checking out")
+ _git.fetch().call()
+ _git.checkout().setName(ref).call
+ }
+ _git
+ } else {
+ logger.git(s"Cloning $url into $checkoutDirectory")
+ val _git = authenticate(
+ Git
+ .cloneRepository()
+ .setURI(url)
+ .setDirectory(checkoutDirectory)
+ ).call()
+
+ logger.git(s"Checking out ref $ref")
+ _git.checkout().setName(ref).call()
+ _git
+ }
+ val actualRef = _git.getRepository.getBranch
+ assert( actualRef == ref, s"actual ref '$actualRef' does not match expected ref '$ref'")
+ checkoutDirectory
}
- val actualRef = _git.getRepository.getBranch
- assert( actualRef == ref, s"actual ref '$actualRef' does not match expected ref '$ref'")
- checkoutDirectory
- }
- def dependency = taskCache[GitDependency]("dependency").memoize{
+
DirectoryDependency(
context.copy(
workingDirectory = checkout ++ subDirectory.map("/" ++ _).getOrElse("")
@@ -80,10 +78,4 @@ case class GitDependency(
pathToNestedBuild: _*
)
}
-
- def dependencies = Seq(dependency)
-
- def exportedClasspath = ClassPath()
- private[cbt] def targetClasspath = exportedClasspath
- def lastModified: Long = dependency.lastModified
}
diff --git a/stage2/Scaffold.scala b/stage2/Scaffold.scala
index 4420866..30ea73b 100644
--- a/stage2/Scaffold.scala
+++ b/stage2/Scaffold.scala
@@ -43,6 +43,11 @@ class Scaffold( logger: Logger ){
)
}
+ private[cbt] def buildPackageFromDirectory(directory: File) = {
+ val parts = packageFromDirectory(directory).split("\\.")
+ ((parts.head ++ "_build") +: parts.tail).mkString(".")
+ }
+
def createMain(
projectDirectory: File
): Unit = {
@@ -59,7 +64,7 @@ object Main{
def createBuild(
projectDirectory: File
): Unit = {
- createFile(projectDirectory, lib.buildDirectoryName++"/"++lib.buildFileName, s"""package cbt_build.${packageFromDirectory(projectDirectory)}
+ createFile(projectDirectory, lib.buildDirectoryName++"/"++lib.buildFileName, s"""package ${buildPackageFromDirectory(projectDirectory)}
import cbt._
class Build(val context: Context) extends BaseBuild{
override def dependencies = (
diff --git a/test/simple-fixed/Main.scala b/test/simple-fixed/Main.scala
index 75f9349..54c764c 100644
--- a/test/simple-fixed/Main.scala
+++ b/test/simple-fixed/Main.scala
@@ -1,6 +1,4 @@
import lib_test.Foo
-import org.eclipse.jgit.lib.Ref
-import com.spotify.missinglink.ArtifactLoader
object Main extends App{
println(Foo.bar)
}
diff --git a/test/simple-fixed/build/build.scala b/test/simple-fixed/build/build.scala
index b46c337..0215c43 100644
--- a/test/simple-fixed/build/build.scala
+++ b/test/simple-fixed/build/build.scala
@@ -7,23 +7,5 @@ class Build(context: cbt.Context) extends BasicBuild(context){
Seq(
GitDependency("https://github.com/cvogt/cbt.git", "f11b8318b85f16843d8cfa0743f64c1576614ad6", Some("test/library-test"))
)
- ++
- Resolver(mavenCentral).bind(
- ScalaDependency("com.typesafe.play", "play-json", "2.4.4"),
- MavenDependency("joda-time", "joda-time", "2.9.2"),
- // the below tests pom inheritance with dependencyManagement and variable substitution for pom properties
- MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"),
- // the below tests pom inheritance with variable substitution for pom xml tag contents
- MavenDependency("com.spotify", "missinglink-core", "0.1.1")
- )
- ++
- Resolver(
- mavenCentral,
- bintray("tpolecat"),
- sonatypeSnapshots
- ).bind(
- "org.cvogt" %% "play-json-extensions" % "0.8.0",
- "ai.x" %% "lens" % "1.0.0"
- )
)
}
diff --git a/test/simple/Main.scala b/test/simple/Main.scala
index 75f9349..c742110 100644
--- a/test/simple/Main.scala
+++ b/test/simple/Main.scala
@@ -1,6 +1,5 @@
-import lib_test.Foo
import org.eclipse.jgit.lib.Ref
import com.spotify.missinglink.ArtifactLoader
object Main extends App{
- println(Foo.bar)
+ println("hello, world")
}
diff --git a/test/simple/build/build.scala b/test/simple/build/build.scala
index a132dd1..517dd83 100644
--- a/test/simple/build/build.scala
+++ b/test/simple/build/build.scala
@@ -4,9 +4,6 @@ class Build(val context: cbt.Context) extends BaseBuild{
override def dependencies = (
super.dependencies
++
- Seq(
- GitDependency("https://github.com/cvogt/cbt.git", "f11b8318b85f16843d8cfa0743f64c1576614ad6", Some("test/library-test"))
- ) ++
// FIXME: make the below less verbose
Resolver( mavenCentral ).bind(
ScalaDependency("com.typesafe.play", "play-json", "2.4.4"),
@@ -38,6 +35,6 @@ class Build(val context: cbt.Context) extends BaseBuild{
"ai.x" %% "lens" % "1.0.0"
)
)
-
+
def printArgs = context.args.mkString(" ")
}
diff --git a/test/test.scala b/test/test.scala
index 59a85ac..45315ce 100644
--- a/test/test.scala
+++ b/test/test.scala
@@ -15,10 +15,20 @@ object Main{
val lib = new Lib(logger)
val cbtHome = new File(System.getenv("CBT_HOME"))
+
+ val slow = (
+ System.getenv("CIRCLECI") != null // enable only on circle
+ || args.args.contains("slow")
+ )
+ val compat = !args.args.contains("no-compat")
+
+ if(!slow) System.err.println( "Skipping slow tests" )
+ if(!compat) System.err.println( "Skipping cbt version compatibility tests" )
+
var successes = 0
var failures = 0
def assertException[T:scala.reflect.ClassTag](msg: String = "")(code: => Unit)(implicit logger: Logger) = {
- try{
+ try{
code
assert(false, msg)
}catch{ case _:AssertionError => }
@@ -187,9 +197,11 @@ object Main{
usage("simple")
compile("simple")
clean("simple")
- usage("simple-fixed")
- compile("simple-fixed")
-
+ if( compat ){
+ usage("simple-fixed")
+ compile("simple-fixed")
+ }
+
compile("../plugins/sbt_layout")
compile("../plugins/scalafmt")
compile("../plugins/scalajs")
@@ -200,8 +212,12 @@ object Main{
compile("../examples/scalafmt-example")
compile("../examples/scalariform-example")
compile("../examples/scalatest-example")
- compile("../examples/scalajs-react-example/js")
- compile("../examples/scalajs-react-example/jvm")
+ if(slow){
+ compile("../examples/scalajs-react-example/js")
+ compile("../examples/scalajs-react-example/jvm")
+ compile("../examples/scalajs-plain-example/js")
+ compile("../examples/scalajs-plain-example/jvm")
+ }
compile("../examples/multi-standalone-example")
compile("../examples/multi-combined-example")
if(sys.props("java.version").startsWith("1.7")){
@@ -209,19 +225,23 @@ object Main{
} else {
compile("../examples/dotty-example")
task("run","../examples/dotty-example")
- task("dottydoc","../examples/dotty-example")
+ if(slow){
+ task("dottydoc","../examples/dotty-example")
+ }
+ }
+ if(slow){
+ task("compile","../examples/scalajs-react-example/js")
+ task("fullOpt.compile","../examples/scalajs-react-example/js")
}
- task("compile","../examples/scalajs-react-example/js")
- task("fullOpt.compile","../examples/scalajs-react-example/js")
compile("../examples/uber-jar-example")
-
- {
+
+ if( compat ){
val res = task("docJar","simple-fixed-cbt")
assert( res.out endsWith "simple-fixed-cbt_2.11-0.1-javadoc.jar\n", res.out )
assert( res.err contains "model contains", res.err )
assert( res.err endsWith "documentable templates\n", res.err )
}
-
+
{
val res = runCbt("simple", Seq("printArgs","1","2","3"))
assert(res.exit0)