aboutsummaryrefslogtreecommitdiff
path: root/stage2
diff options
context:
space:
mode:
authorChristopher Vogt <oss.nsp@cvogt.org>2016-02-06 13:03:36 -0500
committerChristopher Vogt <oss.nsp@cvogt.org>2016-03-04 15:06:30 -0500
commit974942db43ff2d1fa7ba71ad60f9bb9eae2d8631 (patch)
treed7235df9d4d6a67753dc2a20ab6bfcb7a24dc74c /stage2
downloadcbt-974942db43ff2d1fa7ba71ad60f9bb9eae2d8631.tar.gz
cbt-974942db43ff2d1fa7ba71ad60f9bb9eae2d8631.tar.bz2
cbt-974942db43ff2d1fa7ba71ad60f9bb9eae2d8631.zip
CBT Version 1.0-BETA
Diffstat (limited to 'stage2')
-rw-r--r--stage2/AdminStage2.scala11
-rw-r--r--stage2/AdminTasks.scala12
-rw-r--r--stage2/BuildBuild.scala17
-rw-r--r--stage2/DefaultBuild.scala233
-rw-r--r--stage2/Lib.scala436
-rw-r--r--stage2/Scaffold.scala148
-rw-r--r--stage2/Stage2.scala47
-rw-r--r--stage2/dependencies.scala36
-rw-r--r--stage2/mixins.scala35
9 files changed, 975 insertions, 0 deletions
diff --git a/stage2/AdminStage2.scala b/stage2/AdminStage2.scala
new file mode 100644
index 0000000..e7e2284
--- /dev/null
+++ b/stage2/AdminStage2.scala
@@ -0,0 +1,11 @@
+package cbt
+object AdminStage2{
+ def main(args: Array[String]) = {
+ val init = new Stage1.Init(args.drop(3))
+ val lib = new Lib(init.logger)
+ val adminTasks = new AdminTasks(lib, args.drop(3))
+ new lib.ReflectObject(adminTasks){
+ def usage = "Available methods: " + lib.taskNames(subclassType)
+ }.callNullary(args.lift(2))
+ }
+}
diff --git a/stage2/AdminTasks.scala b/stage2/AdminTasks.scala
new file mode 100644
index 0000000..2f7efe1
--- /dev/null
+++ b/stage2/AdminTasks.scala
@@ -0,0 +1,12 @@
+package cbt
+class AdminTasks(lib: Lib, args: Array[String]){
+ def resolve = {
+ ClassPath.flatten(
+ args(0).split(",").toVector.map{
+ d =>
+ val v = d.split(":")
+ new MavenDependency(v(0),v(1),v(2))(lib.logger).classpath
+ }
+ )
+ }
+}
diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala
new file mode 100644
index 0000000..41589db
--- /dev/null
+++ b/stage2/BuildBuild.scala
@@ -0,0 +1,17 @@
+package cbt
+import scala.collection.immutable.Seq
+
+class BuildBuild(context: Context) extends Build(context){
+ override def dependencies = Seq( CbtDependency(context.logger) ) ++ super.dependencies
+ def managedBuildDirectory = lib.realpath(projectDirectory + "/../")
+ val managedBuild = {
+ val managedContext = context.copy( cwd = managedBuildDirectory )
+ val cl = new cbt.URLClassLoader(
+ classpath,
+ classOf[BuildBuild].getClassLoader // FIXME: this looks wrong. Should be ClassLoader.getSystemClassLoader but that crashes
+ )
+ lib.create( lib.buildClassName )( managedContext )( cl ).asInstanceOf[Build]
+ }
+ override def triggerLoopFiles = super.triggerLoopFiles ++ managedBuild.triggerLoopFiles
+ override def finalBuild = managedBuild.finalBuild
+}
diff --git a/stage2/DefaultBuild.scala b/stage2/DefaultBuild.scala
new file mode 100644
index 0000000..c0072ff
--- /dev/null
+++ b/stage2/DefaultBuild.scala
@@ -0,0 +1,233 @@
+package cbt
+import cbt.paths._
+
+import java.io._
+import java.lang.reflect.InvocationTargetException
+import java.net._
+import java.nio.file.{Path =>_,_}
+import java.nio.file.Files.readAllBytes
+import java.security.MessageDigest
+import java.util.jar._
+
+import scala.collection.immutable.Seq
+import scala.reflect.runtime.{universe => ru}
+import scala.util._
+
+import ammonite.ops.{cwd => _,_}
+
+
+
+
+abstract class PackageBuild(context: Context) extends Build(context) with ArtifactInfo{
+ def `package`: Seq[File] = lib.concurrently( enableConcurrency )(
+ Seq(() => jar, () => docJar, () => srcJar)
+ )( _() )
+
+ private object cacheJarBasicBuild extends Cache[File]
+ def jar: File = cacheJarBasicBuild{
+ lib.jar( artifactId, version, compile, jarTarget )
+ }
+
+ private object cacheSrcJarBasicBuild extends Cache[File]
+ def srcJar: File = cacheSrcJarBasicBuild{
+ lib.srcJar(sources, artifactId, version, scalaTarget)
+ }
+
+ private object cacheDocBasicBuild extends Cache[File]
+ def docJar: File = cacheDocBasicBuild{
+ lib.docJar( sources, dependencyClasspath, apiTarget, jarTarget, artifactId, version, scalacOptions )
+ }
+
+ override def jars = jar +: dependencyJars
+ override def exportedJars: Seq[File] = Seq(jar)
+}
+abstract class PublishBuild(context: Context) extends PackageBuild(context){
+ def name = artifactId
+ def description: String
+ def url: URL
+ def developers: Seq[Developer]
+ def licenses: Seq[License]
+ def scmUrl: String
+ def scmConnection: String
+ def pomExtra: Seq[scala.xml.Node] = Seq()
+
+ // ========== package ==========
+
+ /** put additional xml that should go into the POM file in here */
+ def pom: File = lib.pom(
+ groupId = groupId,
+ artifactId = artifactId,
+ version = version,
+ name = name,
+ description = description,
+ url = url,
+ developers = developers,
+ licenses = licenses,
+ scmUrl = scmUrl,
+ scmConnection = scmConnection,
+ dependencies = dependencies,
+ pomExtra = pomExtra,
+ jarTarget = jarTarget
+ )
+
+ // ========== publish ==========
+ final protected def releaseFolder = s"/${groupId.replace(".","/")}/$artifactId/$version/"
+ def snapshotUrl = new URL("https://oss.sonatype.org/content/repositories/snapshots")
+ def releaseUrl = new URL("https://oss.sonatype.org/service/local/staging/deploy/maven2")
+ def publishSnapshot: Unit = lib.publishSnapshot(sourceFiles, pom +: `package`, new URL(snapshotUrl + releaseFolder) )
+ def publishSigned: Unit = lib.publishSigned(sourceFiles, pom +: `package`, new URL(releaseUrl + releaseFolder) )
+}
+
+
+class BasicBuild(context: Context) extends Build(context)
+class Build(val context: Context) extends Dependency with TriggerLoop{
+ // library available to builds
+ final val logger = context.logger
+ override final protected val lib: Lib = new Lib(logger)
+ // ========== general stuff ==========
+
+ def enableConcurrency = false
+ final def projectDirectory: File = new File(context.cwd)
+ assert( projectDirectory.exists, "projectDirectory does not exist: "+projectDirectory )
+ final def usage: Unit = new lib.ReflectBuild(this).usage
+/*
+ def scaffold: Unit = lib.generateBasicBuildFile(
+ projectDirectory, scalaVersion, groupId, artifactId, version
+ )
+*/
+ // ========== meta data ==========
+
+ def scalaVersion: String = constants.scalaVersion
+ final def scalaMajorVersion: String = scalaVersion.split("\\.").take(2).mkString(".")
+ def zincVersion = "0.3.9"
+
+ def dependencies: Seq[Dependency] = Seq(
+ "org.scala-lang" % "scala-library" % scalaVersion
+ )
+
+ // ========== paths ==========
+ final private val defaultSourceDirectory = new File(projectDirectory+"/src/")
+
+ /** base directory where stuff should be generated */
+ def target = new File(projectDirectory+"/target")
+ /** base directory where stuff should be generated for this scala version*/
+ def scalaTarget = new File(target + s"/scala-$scalaMajorVersion")
+ /** directory where jars (and the pom file) should be put */
+ def jarTarget = scalaTarget
+ /** directory where the scaladoc should be put */
+ def apiTarget = new File(scalaTarget + "/api")
+ /** directory where the class files should be put (in package directories) */
+ def compileTarget = new File(scalaTarget + "/classes")
+
+ /** Source directories and files. Defaults to .scala and .java files in src/ and top-level. */
+ def sources: Seq[File] = Seq(defaultSourceDirectory) ++ projectDirectory.listFiles.toVector.filter(sourceFileFilter)
+
+ /** Which file endings to consider being source files. */
+ def sourceFileFilter(file: File): Boolean = file.toString.endsWith(".scala") || file.toString.endsWith(".java")
+
+ /** Absolute path names for all individual files found in sources directly or contained in directories. */
+ final def sourceFiles: Seq[File] = for {
+ base <- sources.filter(_.exists).map(lib.realpath)
+ file <- lib.listFilesRecursive(base) if file.isFile && sourceFileFilter(file)
+ } yield file
+
+ protected def assertSourceDirectories(): Unit = {
+ val nonExisting =
+ sources
+ .filterNot( _.exists )
+ .diff( Seq(defaultSourceDirectory) )
+ assert(
+ nonExisting.isEmpty,
+ "Some sources do not exist: \n"+nonExisting.mkString("\n")
+ )
+ }
+ assertSourceDirectories()
+
+
+
+
+ /** SBT-like dependency builder DSL */
+ class GroupIdAndArtifactId( groupId: String, artifactId: String ){
+ def %(version: String) = new MavenDependency(groupId, artifactId, version)(lib.logger)
+ }
+ implicit class DependencyBuilder(groupId: String){
+ def %%(artifactId: String) = new GroupIdAndArtifactId( groupId, artifactId+"_"+scalaMajorVersion )
+ def %(artifactId: String) = new GroupIdAndArtifactId( groupId, artifactId )
+ }
+
+ final def BuildDependency(path: String) = cbt.BuildDependency(
+ context.copy(
+ cwd = path,
+ args = Seq()
+ )
+ )
+
+ def triggerLoopFiles: Seq[File] = sources ++ transitiveDependencies.collect{ case b: TriggerLoop => b.triggerLoopFiles }.flatten
+
+
+ def localJars : Seq[File] =
+ Seq(projectDirectory + "/lib/")
+ .map(new File(_))
+ .filter(_.exists)
+ .flatMap(_.listFiles)
+ .filter(_.toString.endsWith(".jar"))
+
+ //def cacheJar = false
+ override def dependencyClasspath : ClassPath = ClassPath(localJars) ++ super.dependencyClasspath
+ override def dependencyJars : Seq[File] = localJars ++ super.dependencyJars
+
+ def exportedClasspath : ClassPath = ClassPath(Seq(compile))
+ def exportedJars: Seq[File] = Seq()
+ // ========== compile, run, test ==========
+
+ /** scalac options used for zinc and scaladoc */
+ def scalacOptions: Seq[String] = Seq( "-feature", "-deprecation", "-unchecked" )
+
+ val updated: Boolean = {
+ val existingClassFiles = lib.listFilesRecursive(compileTarget)
+ val sourcesChanged = existingClassFiles.nonEmpty && {
+ val oldestClassFile = existingClassFiles.sortBy(_.lastModified).head
+ val oldestClassFileAge = oldestClassFile.lastModified
+ val changedSourceFiles = sourceFiles.filter(_.lastModified > oldestClassFileAge)
+ if(changedSourceFiles.nonEmpty){
+ /*
+ println(changedSourceFiles)
+ println(changedSourceFiles.map(_.lastModified))
+ println(changedSourceFiles.map(_.lastModified > oldestClassFileAge))
+ println(oldestClassFile)
+ println(oldestClassFileAge)
+ println("-"*80)
+ */
+ }
+ changedSourceFiles.nonEmpty
+ }
+ sourcesChanged || transitiveDependencies.map(_.updated).fold(false)(_ || _)
+ }
+
+ private object cacheCompileBasicBuild extends Cache[File]
+ def compile: File = cacheCompileBasicBuild{
+ //println(transitiveDependencies.filter(_.updated).mkString("\n"))
+ lib.compile(
+ updated,
+ sourceFiles, compileTarget, dependencyClasspath, scalacOptions,
+ zincVersion = zincVersion, scalaVersion = scalaVersion
+ )
+ }
+
+ def runClass: String = "Main"
+ def run: Unit = lib.runMainIfFound( runClass, Seq(), classLoader )
+
+ def test: Unit = lib.test(context)
+
+ context.logger.composition(">"*80)
+ context.logger.composition("class "+this.getClass)
+ context.logger.composition("dir "+context.cwd)
+ context.logger.composition("sources "+sources.toList.mkString(" "))
+ context.logger.composition("target "+target)
+ context.logger.composition("dependencyTree\n"+dependencyTree)
+ context.logger.composition("<"*80)
+
+ // ========== cbt internals ==========
+ private[cbt] def finalBuild = this
+ override def show = this.getClass.getSimpleName + "("+context.cwd+")"
+}
diff --git a/stage2/Lib.scala b/stage2/Lib.scala
new file mode 100644
index 0000000..b92e0b3
--- /dev/null
+++ b/stage2/Lib.scala
@@ -0,0 +1,436 @@
+package cbt
+import cbt.paths._
+
+import java.io._
+import java.net._
+import java.lang.reflect.InvocationTargetException
+import java.nio.file.{Path =>_,_}
+import java.nio.file.Files.readAllBytes
+import java.security.MessageDigest
+import java.util.jar._
+
+import scala.collection.immutable.Seq
+import scala.reflect.runtime.{universe => ru}
+import scala.util._
+
+import ammonite.ops.{cwd => _,_}
+
+case class Developer(id: String, name: String, timezone: String, url: URL)
+case class License(name: String, url: URL)
+
+/** Don't extend. Create your own libs :). */
+final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
+ lib =>
+
+ val buildClassName = "Build"
+ val buildBuildClassName = "BuildBuild"
+
+ /** Loads Build for given Context */
+ def loadDynamic(context: Context, default: Context => Build = new Build(_)): Build = {
+ context.logger.composition( context.logger.showInvocation("Build.loadDynamic",context) )
+ loadRoot(context, default).finalBuild
+ }
+ /**
+ Loads whatever Build needs to be executed first in order to eventually build the build for the given context.
+ This can either the Build itself, of if exists a BuildBuild or a BuildBuild for a BuildBuild and so on.
+ */
+ def loadRoot(context: Context, default: Context => Build = new Build(_)): Build = {
+ context.logger.composition( context.logger.showInvocation("Build.loadRoot",context) )
+ def findStartDir(cwd: String): String = {
+ val buildDir = realpath(cwd+"/build")
+ if(new File(buildDir).exists) findStartDir(buildDir) else cwd
+ }
+
+ val start = findStartDir(context.cwd)
+
+ val useBasicBuildBuild = context.cwd == start
+
+ val rootBuildClassName = if( useBasicBuildBuild ) buildBuildClassName else buildClassName
+ try{
+ if(useBasicBuildBuild) default( context ) else new cbt.BuildBuild( context.copy( cwd = start ) )
+ } catch {
+ case e:ClassNotFoundException if e.getMessage == rootBuildClassName =>
+ throw new Exception(s"no class $rootBuildClassName found in "+start)
+ }
+ }
+
+ def compile(
+ updated: Boolean,
+ sourceFiles: Seq[File], compileTarget: File, dependenyClasspath: ClassPath,
+ compileArgs: Seq[String], zincVersion: String, scalaVersion: String
+ ): File = {
+ if(sourceFiles.nonEmpty)
+ lib.zinc(
+ updated, sourceFiles, compileTarget, dependenyClasspath, compileArgs
+ )( zincVersion = zincVersion, scalaVersion = scalaVersion )
+ compileTarget
+ }
+
+ def srcJar(sources: Seq[File], artifactId: String, version: String, jarTarget: File): File = {
+ val file = new File(jarTarget+"/"+artifactId+"-"+version+"-sources.jar")
+ lib.jarFile(file, sources)
+ file
+ }
+
+ def jar(artifactId: String, version: String, compileTarget: File, jarTarget: File): File = {
+ val file = new File(jarTarget+"/"+artifactId+"-"+version+".jar")
+ lib.jarFile(file, Seq(compileTarget))
+ file
+ }
+
+ def docJar(
+ sourceFiles: Seq[File],
+ dependenyClasspath: ClassPath,
+ apiTarget: File,
+ jarTarget: File,
+ artifactId: String,
+ version: String,
+ compileArgs: Seq[String]
+ ): File = {
+ class DisableSystemExit extends Exception
+ object DisableSystemExit{
+ def apply(e: Throwable): Boolean = {
+ e match {
+ case i: InvocationTargetException => apply(i.getTargetException)
+ case _: DisableSystemExit => true
+ case _ => false
+ }
+ }
+ }
+
+ // FIXME: get this dynamically somehow, or is this even needed?
+ val javacp = ClassPath(
+ "/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/System/Library/Java/Extensions/MRJToolkit.jar".split(":").toVector.map(new File(_))
+ )
+
+ mkdir(Path(apiTarget))
+ if(sourceFiles.nonEmpty){
+ System.err.println("creating docs")
+ try{
+ System.setSecurityManager(
+ new SecurityManager{
+ override def checkPermission( permission: java.security.Permission ) = {
+ if( permission.getName.startsWith("exitVM") ) throw new DisableSystemExit
+ }
+ }
+ )
+ redirectOutToErr{
+ runMain(
+ "scala.tools.nsc.ScalaDoc",
+ Seq(
+ // FIXME: can we use compiler dependency here?
+ "-cp", /*javacp+":"+*/ScalaDependencies(logger).classpath.string + ":" + dependenyClasspath.string,
+ "-d", apiTarget.toString
+ ) ++ compileArgs ++ sourceFiles.map(_.toString),
+ new URLClassLoader(
+ ScalaDependencies(logger).classpath ++ javacp,
+ ClassLoader.getSystemClassLoader
+ )
+ )
+ }
+ } catch {
+ case e:InvocationTargetException if DisableSystemExit(e) =>
+ } finally {
+ System.setSecurityManager(null)
+ }
+ }
+ val docJar = new File(jarTarget+"/"+artifactId+"-"+version+"-javadoc.jar")
+ lib.jarFile(docJar, Vector(apiTarget))
+ docJar
+ }
+
+ def test( context: Context ) = {
+ logger.lib(s"invoke testDefault( $context )")
+ loadDynamic(
+ context.copy( cwd = context.cwd+"/test/" ),
+ new Build(_) with mixins.Test
+ ).run
+ logger.lib(s"return testDefault( $context )")
+
+ }
+
+ // task reflection helpers
+ import ru._
+ private lazy val anyRefMembers = ru.typeOf[AnyRef].members.toVector.map(taskName)
+ def taskNames(tpe: Type) = tpe.members.toVector.flatMap(lib.toTask).map(taskName).sorted
+ private def taskName(method: Symbol) = method.name.decodedName.toString
+ def toTask(symbol: Symbol): Option[MethodSymbol] = {
+ Option(symbol)
+ .filter(_.isPublic)
+ .filter(_.isMethod)
+ .map(_.asMethod)
+ .filter(_.paramLists.flatten.size == 0)
+ .filterNot(taskName(_) contains "$")
+ .filterNot(t => anyRefMembers contains taskName(t))
+ }
+
+ class ReflectBuild(build: Build) extends ReflectObject(build){
+ def usage = {
+ val baseTasks = lib.taskNames(ru.typeOf[Build])
+ val thisTasks = lib.taskNames(subclassType) diff baseTasks
+ (
+ (
+ if( thisTasks.nonEmpty ){
+ s"""Methods provided by Build ${build.context.cwd}
+
+ ${thisTasks.mkString(" ")}
+
+"""
+ } else ""
+ ) + s"""Methods provided by CBT (but possibly overwritten)
+
+ ${baseTasks.mkString(" ")}"""
+ ) + "\n"
+ }
+ }
+
+ abstract class ReflectObject[T:scala.reflect.ClassTag](obj: T){
+ lazy val mirror = ru.runtimeMirror(obj.getClass.getClassLoader)
+ lazy val subclassType = mirror.classSymbol(obj.getClass).toType
+ def usage: String
+ def callNullary( taskName: Option[String] ): Unit = {
+ taskName
+ .map{ n => subclassType.member(ru.TermName(n).encodedName) }
+ .filter(_ != ru.NoSymbol)
+ .flatMap(toTask _)
+ .map{ methodSymbol =>
+ val result = mirror.reflect(obj).reflectMethod(methodSymbol)()
+ // Try to render console representation. Probably not the best way to do this.
+ val method = scala.util.Try( result.getClass.getDeclaredMethod("toConsole") )
+
+ method.foreach(m => println(m.invoke(result)))
+ method.recover{
+ case e:NoSuchMethodException if e.getMessage contains "toConsole" =>
+ result match {
+ case () => ""
+ case other => println( other.toString ) // no method .toConsole, using to String
+ }
+ }
+ }.getOrElse{
+ taskName.foreach{ n =>
+ System.err.println(s"Method not found: $n")
+ System.err.println("")
+ }
+ System.err.println(usage)
+ System.exit(1)
+ }
+ }
+ }
+
+
+ // file system helpers
+ def basename(path: String) = path.stripSuffix("/").split("/").last
+ def basename(path: File) = path.toString.stripSuffix("/").split("/").last
+ def dirname(path: String) = realpath(path).stripSuffix("/").split("/").dropRight(1).mkString("/")
+ def realpath(name: String) = Paths.get(new File(name).getAbsolutePath).normalize.toString
+ def realpath(name: File) = new File(Paths.get(name.getAbsolutePath).normalize.toString)
+ def nameAndContents(file: File) = basename(file.toString) -> readAllBytes(Paths.get(file.toString))
+
+ def jarFile( jarFile: File, files: Seq[File] ): Unit = {
+ logger.lib("Start packaging "+jarFile)
+ val manifest = new Manifest
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0")
+ val jar = new JarOutputStream(new FileOutputStream(jarFile.toString), manifest)
+
+ val names = for {
+ base <- files.filter(_.exists).map(realpath)
+ file <- listFilesRecursive(base) if file.isFile
+ } yield {
+ val name = if(base.isDirectory){
+ file.toString stripPrefix base.toString
+ } else file.toString
+ val entry = new JarEntry( name )
+ entry.setTime(file.lastModified)
+ jar.putNextEntry(entry)
+ jar.write( Files.readAllBytes( Paths.get(file.toString) ) )
+ jar.closeEntry
+ name
+ }
+
+ val duplicateFiles = (names diff names.distinct).distinct
+ assert(
+ duplicateFiles.isEmpty,
+ s"Conflicting file names when trying to create $jarFile: "+duplicateFiles.mkString(", ")
+ )
+
+ jar.close
+ logger.lib("Done packaging "+jarFile)
+ }
+
+ lazy val passphrase =
+ Option(System.console).getOrElse(
+ throw new Exception("Can't access Console. This probably shouldn't be run through Nailgun.")
+ ).readPassword(
+ "GPG Passphrase please:"
+ ).mkString
+
+ def sign(file: File): File = {
+ //http://stackoverflow.com/questions/16662408/correct-way-to-sign-and-verify-signature-using-bouncycastle
+ val statusCode =
+ new ProcessBuilder( "gpg", "--batch", "--yes", "-a", "-b", "-s", "--passphrase", passphrase, file.toString )
+ .inheritIO.start.waitFor
+
+ if( 0 != statusCode ) throw new Exception("gpg exited with status code "+statusCode)
+
+ new File(file+".asc")
+ }
+
+ //def requiredForPom[T](name: String): T = throw new Exception(s"You need to override `def $name` in order to generate a valid pom.")
+
+ def pom(
+ groupId: String,
+ artifactId: String,
+ version: String,
+ name: String,
+ description: String,
+ url: URL,
+ developers: Seq[Developer],
+ licenses: Seq[License],
+ scmUrl: String, // seems like invalid URLs are used here in pom files
+ scmConnection: String,
+ dependencies: Seq[Dependency],
+ pomExtra: Seq[scala.xml.Node],
+ jarTarget: File
+ ): File = {
+ val xml =
+ <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>{groupId}</groupId>
+ <artifactId>{artifactId}</artifactId>
+ <version>{version}</version>
+ <packaging>jar</packaging>
+ <name>{name}</name>
+ <description>{description}</description>
+ <url>{url}</url>
+ <licenses>
+ {licenses.map{ license =>
+ <license>
+ <name>{license.name}</name>
+ <url>{license.url}</url>
+ <distribution>repo</distribution>
+ </license>
+ }}
+ </licenses>
+ <developers>
+ {developers.map{ developer =>
+ <developer>
+ <id>{developer.id}</id>
+ <name>{developer.name}</name>
+ <timezone>{developer.timezone}</timezone>
+ <url>{developer.url}</url>
+ </developer>
+ }}
+ </developers>
+ <scm>
+ <url>{scmUrl}</url>
+ <connection>{scmConnection}</connection>
+ </scm>
+ {pomExtra}
+ <dependencies>
+ {
+ dependencies.map{
+ case d:ArtifactInfo =>
+ <dependency>
+ <groupId>{d.groupId}</groupId>
+ <artifactId>{d.artifactId}</artifactId>
+ <version>{d.version}</version>
+ </dependency>
+ }
+ }
+ </dependencies>
+ </project>
+ val path = new File(jarTarget+"/"+artifactId+"-"+version+".pom")
+ write.over(Path(path), "<?xml version='1.0' encoding='UTF-8'?>\n" + xml.toString)
+ path
+ }
+
+ def concurrently[T,R]( concurrencyEnabled: Boolean )( items: Seq[T] )( projection: T => R ): Seq[R] = {
+ if(concurrencyEnabled) items.par.map(projection).seq
+ else items.map(projection)
+ }
+
+ def publishSnapshot( sourceFiles: Seq[File], artifacts: Seq[File], url: URL ): Unit = {
+ if(sourceFiles.nonEmpty){
+ val files = artifacts.map(nameAndContents)
+ uploadAll(url, files)
+ }
+ }
+
+ def publishSigned( sourceFiles: Seq[File], artifacts: Seq[File], url: URL ): Unit = {
+ // TODO: make concurrency configurable here
+ if(sourceFiles.nonEmpty){
+ val files = (artifacts ++ artifacts.map(sign)).map(nameAndContents)
+ lazy val checksums = files.flatMap{
+ case (name, content) => Seq(
+ name+".md5" -> md5(content).toArray.map(_.toByte),
+ name+".sha1" -> sha1(content).toArray.map(_.toByte)
+ )
+ }
+ val all = (files ++ checksums)
+ uploadAll(url, all)
+ }
+ }
+
+
+ def uploadAll(url: URL, nameAndContents: Seq[(String, Array[Byte])]): Unit =
+ nameAndContents.map{ case(name, content) => upload(name, content, url) }
+
+ def upload(fileName: String, fileContents: Array[Byte], baseUrl: URL): Unit = {
+ import java.net._
+ import java.io._
+ logger.task("uploading "+fileName)
+ val url = new URL(
+ baseUrl + fileName
+ )
+ val httpCon = url.openConnection.asInstanceOf[HttpURLConnection]
+ httpCon.setDoOutput(true)
+ httpCon.setRequestMethod("PUT")
+ val userPassword = read(Path(sonatypeLogin)).trim
+ val encoding = new sun.misc.BASE64Encoder().encode(userPassword.getBytes)
+ httpCon.setRequestProperty("Authorization", "Basic " + encoding)
+ httpCon.setRequestProperty("Content-Type", "application/binary")
+ httpCon.getOutputStream.write(
+ fileContents
+ )
+ httpCon.getInputStream
+ }
+
+
+ // code for continuous compile
+ def watch(files: Seq[File])(action: PartialFunction[File, Unit]): Unit = {
+ import com.barbarysoftware.watchservice._
+ import scala.collection.JavaConversions._
+ val watcher = WatchService.newWatchService
+
+ files.map{
+ file =>
+ if(file.isFile) new File( dirname(file.toString) )
+ else file
+ }.distinct.map{ file =>
+ val watchableFile = new WatchableFile(file)
+ val key = watchableFile.register(
+ watcher,
+ StandardWatchEventKind.ENTRY_CREATE,
+ StandardWatchEventKind.ENTRY_DELETE,
+ StandardWatchEventKind.ENTRY_MODIFY
+ )
+ }
+
+ scala.util.control.Breaks.breakable{
+ while(true){
+ logger.loop("Waiting for file changes...")
+ Option(watcher.take).map{
+ key =>
+ val changedFiles = key
+ .pollEvents
+ .filterNot(_.kind == StandardWatchEventKind.OVERFLOW)
+ .map(_.context.toString)
+ .map(new File(_))
+ changedFiles.foreach( f => logger.loop("Changed: "+f) )
+ changedFiles.collect(action)
+ key.reset
+ }
+ }
+ }
+ }
+}
diff --git a/stage2/Scaffold.scala b/stage2/Scaffold.scala
new file mode 100644
index 0000000..00c8706
--- /dev/null
+++ b/stage2/Scaffold.scala
@@ -0,0 +1,148 @@
+package cbt
+import java.io._
+import java.net._
+import ammonite.ops.{cwd => _,_}
+
+trait Scaffold{
+ def logger: Logger
+
+ def generateBasicBuildFile(
+ projectDirectory: File,
+ scalaVersion: String,
+ groupId: String,
+ artifactId: String,
+ version: String
+ ): Unit = {
+ /**
+ TODO:
+ - make behavior more user friendly:
+ - not generate half and then throw exception for one thing already existing
+ - maybe not generate all of this, e.g. offer different variants
+ */
+
+ val generatedFiles = Seq(
+ "build/build.scala" -> s"""import cbt._
+import java.net.URL
+import java.io.File
+import scala.collection.immutable.Seq
+
+class Build(context: Context) extends BasicBuild(context) with BuildShared{
+ override def artifactId: String = "$artifactId"
+ override def groupId = "$groupId"
+
+ override def dependencies = super.dependencies ++ Seq( // don't forget super.dependencies here
+ // "org.cvogt" %% "scala-extensions" % "0.4.1"
+ )
+
+ // required for .pom file
+ override def name = artifactId
+ override def description : String = lib.requiredForPom("description")
+}
+""",
+
+ "build/build/build.scala" -> s"""import cbt._
+import java.net.URL
+import java.io.File
+import scala.collection.immutable.Seq
+
+class Build(context: Context) extends BuildBuild(context){
+ override def scalaVersion: String = "2.11.7"
+
+ override def dependencies = super.dependencies ++ Seq(
+ BuildDependency( projectDirectory + "/../build-shared/")
+ // , "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
+ )
+}
+""",
+
+ "test/Main.scala" -> s"""object Main{
+ def main( args: Array[String] ) = {
+ assert( false, "Go. Write some tests :)!" )
+ }
+}
+""",
+
+ "test/build/build.scala" -> s"""import cbt._
+import java.net.URL
+import java.io.File
+import scala.collection.immutable.Seq
+
+class Build(context: cbt.Context) extends BasicBuild(context) with BuildShared/* with cbt.mixins.ScalaTest*/{
+ // def scalaTestVersion = "2.2.6"
+
+ override def dependencies = super.dependencies ++ Seq(
+ // , "org.scalacheck" %% "scalacheck" % "1.13.0"
+ )
+}
+""",
+
+ "test/build/build/build.scala" -> s"""import cbt._
+import java.net.URL
+import java.io.File
+import scala.collection.immutable.Seq
+
+class Build(context: Context) extends BuildBuild(context){
+ override def scalaVersion: String = "2.11.7"
+
+ override def dependencies = super.dependencies ++ Seq(
+ BuildDependency( projectDirectory + "/../../build-shared/")
+ // , "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
+ )
+}
+""",
+
+ "build-shared/build/build.scala" -> s"""import cbt._
+import java.net.URL
+import java.io.File
+import scala.collection.immutable.Seq
+
+class Build(context: Context) extends BasicBuild(context){
+ override def scalaVersion: String = "$scalaVersion"
+
+ override def dependencies = super.dependencies ++ Seq( // don't forget super.dependencies here
+ CbtDependency
+ // , "org.cvogt" %% "scala-extensions" % "0.4.1"
+ )
+}
+""",
+
+ "build-shared/BuildShared.scala" -> s"""import cbt._
+import java.net.URL
+import java.io.File
+import scala.collection.immutable.Seq
+
+trait BuildShared extends BasicBuild{
+ override def scalaVersion: String = "$scalaVersion"
+ override def enableConcurrency = false // enable for speed, disable for debugging
+
+ override def groupId = "$groupId"
+ override def version = "$version"
+
+ // required for .pom file
+ override def url : URL = lib.requiredForPom("url")
+ override def developers: Seq[Developer] = lib.requiredForPom("developers")
+ override def licenses : Seq[License] = lib.requiredForPom("licenses")
+ override def scmUrl : String = lib.requiredForPom("scmUrl")
+ override def scmConnection: String = lib.requiredForPom("scmConnection")
+ override def pomExtra: Seq[scala.xml.Node] = Seq()
+}
+"""
+ )
+
+ generatedFiles.map{
+ case ( fileName, code ) =>
+ scala.util.Try{
+ write( Path(projectDirectory+"/"+fileName), code )
+ import scala.Console._
+ println( GREEN + "Created " + fileName + RESET )
+ }
+ }.foreach(
+ _.recover{
+ case e: java.nio.file.FileAlreadyExistsException =>
+ e.printStackTrace
+ }.get
+ )
+ return ()
+ }
+
+} \ No newline at end of file
diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala
new file mode 100644
index 0000000..05b7c58
--- /dev/null
+++ b/stage2/Stage2.scala
@@ -0,0 +1,47 @@
+package cbt
+import cbt.paths._
+import java.io._
+import scala.collection.immutable.Seq
+
+object Stage2{
+ def main(args: Array[String]) = {
+ import java.time.LocalTime.now
+ val init = new Stage1.Init(args)
+ import java.time._
+ val start = LocalTime.now()
+ def timeTaken = Duration.between(start, LocalTime.now()).toMillis
+ init.logger.stage2(s"[$now] Stage2 start")
+
+ import init._
+ val loop = argsV.lift(1) == Some("loop")
+ val direct = argsV.lift(1) == Some("direct")
+ val taskIndex = if(loop || direct) 2 else 1
+ val task = argsV.lift( taskIndex )
+
+ val lib = new Lib(new Stage1.Init(args).logger)
+
+ val context = Context( cwd, argsV.drop( taskIndex + 1 ), logger )
+ val first = lib.loadRoot( context )
+ val build = first.finalBuild
+
+ val res = if( loop ){
+ // TODO: this should allow looping over task specific files, like test files as well
+ val triggerFiles = first.triggerLoopFiles.map(lib.realpath)
+ val triggerCbtFiles = Seq( nailgun, stage1, stage2 ).map(lib.realpath _)
+ val allTriggerFiles = triggerFiles ++ triggerCbtFiles
+
+ logger.loop("Looping change detection over:\n - "+allTriggerFiles.mkString("\n - "))
+
+ lib.watch(allTriggerFiles){
+ case file if triggerCbtFiles.exists(file.toString startsWith _.toString) =>
+ logger.loop("Change is in CBT' own source code.")
+ logger.loop("Restarting CBT.")
+ scala.util.control.Breaks.break
+ case file if triggerFiles.exists(file.toString startsWith _.toString) =>
+ new lib.ReflectBuild( lib.loadDynamic(context) ).callNullary(task)
+ }
+ } else new lib.ReflectBuild(build).callNullary(task)
+ init.logger.stage2(s"[$now] Stage2 end")
+ res
+ }
+}
diff --git a/stage2/dependencies.scala b/stage2/dependencies.scala
new file mode 100644
index 0000000..8ed36eb
--- /dev/null
+++ b/stage2/dependencies.scala
@@ -0,0 +1,36 @@
+package cbt
+import java.io.File
+import scala.collection.immutable.Seq
+/*
+sealed abstract class ProjectProxy extends Ha{
+ protected def delegate: ProjectMetaData
+ def artifactId: String = delegate.artifactId
+ def groupId: String = delegate.groupId
+ def version: String = delegate.version
+ def exportedClasspath = delegate.exportedClasspath
+ def dependencies = Seq(delegate)
+}
+*/
+trait TriggerLoop extends Dependency{
+ def triggerLoopFiles: Seq[File]
+}
+/** You likely want to use the factory method in the BasicBuild class instead of this. */
+case class BuildDependency(context: Context) extends TriggerLoop{
+ override def show = this.getClass.getSimpleName + "("+context.cwd+")"
+ final override lazy val logger = context.logger
+ final override lazy val lib: Lib = new Lib(logger)
+ private val root = lib.loadRoot( context.copy(args=Seq()) )
+ lazy val build = root.finalBuild
+ def exportedClasspath = ClassPath(Seq())
+ def exportedJars = Seq()
+ def dependencies = Seq(build)
+ def triggerLoopFiles = root.triggerLoopFiles
+ final val updated = build.updated
+}
+/*
+case class DependencyOr(first: BuildDependency, second: MavenDependency) extends ProjectProxy with BuildDependencyBase{
+ val isFirst = new File(first.context.cwd).exists
+ 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/mixins.scala b/stage2/mixins.scala
new file mode 100644
index 0000000..5ed26d8
--- /dev/null
+++ b/stage2/mixins.scala
@@ -0,0 +1,35 @@
+package cbt
+package mixins
+import scala.collection.immutable.Seq
+import java.io._
+trait Test extends Build{
+ lazy val testedBuild = BuildDependency(projectDirectory+"/../")
+ override def dependencies = Seq( testedBuild ) ++ super.dependencies
+ override def scalaVersion = testedBuild.build.scalaVersion
+}
+trait Sbt extends Build{
+ override def sources = Seq(new File(projectDirectory+"/src/main/scala/"))
+}
+trait SbtTest extends Test{
+ override def sources = Vector(new File(projectDirectory+"/../src/test/scala"))
+}
+trait ScalaTest extends Build with Test{
+ def scalaTestVersion: String
+
+ override def dependencies = Seq(
+ "org.scalatest" %% "scalatest" % scalaTestVersion
+ ) ++ super.dependencies
+
+ // workaround probable ScalaTest bug throwing away the outer classloader. Not caching doesn't nest them.
+ override def cacheDependencyClassLoader = false
+
+ override def run = {
+ val discoveryPath = compile.toString+"/"
+ context.logger.lib("discoveryPath: "+discoveryPath)
+ lib.runMain(
+ "org.scalatest.tools.Runner",
+ Seq("-R", discoveryPath, "-oF") ++ context.args.drop(1),
+ classLoader
+ )
+ }
+}