diff options
35 files changed, 206 insertions, 153 deletions
@@ -14,3 +14,4 @@ node_modules *fullopt* examples/dotty-example/_site examples/scalajs-plain-example/server/public/generated +examples/scalajs-react-example/server/public/generated diff --git a/build/build.scala b/build/build.scala index 70fc268..b4a39ea 100644 --- a/build/build.scala +++ b/build/build.scala @@ -6,7 +6,6 @@ class Build(val context: Context) extends Publish{ super.dependencies ++ Resolver(mavenCentral).bind( MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0"), MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"), - MavenDependency("com.typesafe.zinc","zinc",constants.zincVersion), ScalaDependency("org.scala-lang.modules","scala-xml","1.0.5") ) } @@ -16,8 +15,8 @@ class Build(val context: Context) extends Publish{ def groupId: String = "org.cvogt" - def version: String = "0.1" - def name: String = "cbt" + def version: String = "0.9" + override def name: String = "cbt" // Members declared in cbt.Publish def description: String = "Fast, intuitive Build Tool for Scala" @@ -13,7 +13,7 @@ dependencies: test: override: - rm ~/.gitconfig # avoid url replacement breaking jgit - - ./cbt direct test: + - ./cbt direct test.run: timeout: 1800 - - ./cbt test: + - ./cbt test.run: timeout: 1800 diff --git a/doc/design.txt b/doc/design.txt new file mode 100644 index 0000000..9478d31 --- /dev/null +++ b/doc/design.txt @@ -0,0 +1,5 @@ + + +## Related gitter chats +### Why CBT uses inheritance +https://gitter.im/cvogt/cbt?at=58a95663de50490822e869e5 diff --git a/examples/build-info-example/build/build.scala b/examples/build-info-example/build/build.scala index 4d28598..b5ea86b 100644 --- a/examples/build-info-example/build/build.scala +++ b/examples/build-info-example/build/build.scala @@ -2,7 +2,7 @@ import cbt._ import java.nio.file.Files._ class Build(val context: Context) extends PackageJars{ - def name = "build-info-example" + override def name = "build-info-example" def groupId = "cbt.examples" override def defaultScalaVersion = "2.11.8" def version = "0.1" diff --git a/examples/scalajs-plain-example/js/build/build.scala b/examples/scalajs-plain-example/js/build/build.scala index 06e4876..4c251cd 100644 --- a/examples/scalajs-plain-example/js/build/build.scala +++ b/examples/scalajs-plain-example/js/build/build.scala @@ -1,6 +1,6 @@ import cbt._ class Build(val context: Context) extends ScalaJsBuild{ - override def projectName = "my-project" + override def name = "my-project" override def sources = super.sources ++ Seq( projectDirectory.getParentFile ++ "/shared" @@ -14,5 +14,5 @@ class Build(val context: Context) extends ScalaJsBuild{ ) override def scalaJsTargetFile = - projectDirectory.getParentFile ++ ("/server/public/generated/" ++ projectName ++ ".js") + projectDirectory.getParentFile ++ ("/server/public/generated/" ++ name ++ ".js") } diff --git a/examples/scalajs-react-example/js/build/build.scala b/examples/scalajs-react-example/js/build/build.scala index 6db866b..d8e8c16 100644 --- a/examples/scalajs-react-example/js/build/build.scala +++ b/examples/scalajs-react-example/js/build/build.scala @@ -1,6 +1,6 @@ import cbt._ class Build(val context: Context) extends ScalaJsBuild{ - override def projectName = "my-project" + override def name = "my-project" override def sources = super.sources ++ Seq( projectDirectory.getParentFile ++ "/shared" @@ -17,5 +17,5 @@ class Build(val context: Context) extends ScalaJsBuild{ ) override def scalaJsTargetFile = - projectDirectory.getParentFile ++ ("/server/public/generated/" ++ projectName ++ ".js") + projectDirectory.getParentFile ++ ("/server/public/generated/" ++ name ++ ".js") } diff --git a/examples/scalatest-example/build/build.scala b/examples/scalatest-example/build/build.scala index 48248fd..2c0ec21 100644 --- a/examples/scalatest-example/build/build.scala +++ b/examples/scalatest-example/build/build.scala @@ -1,9 +1,14 @@ import cbt._ class Build(val context: Context) extends SbtLayoutMain { outer => - override def test: Option[ExitCode] = Some{ + /* FIXME: calling `cbt rt` for `examples/scalatest-example` leads to +java.lang.Exception: This should never happend. Could not find (org.scala-lang,scala-reflect) in +(org.scala-lang,scala-library) -> BoundMavenDependency(1488121318000,cbt/cache/maven,MavenDependency(org.scala-lang,scala-library,2.11.8,Classifier(None)),Vector(https://repo1.maven.org/maven2)) + at cbt.Stage1Lib$$anonfun$actual$1.apply(Stage1Lib.scala:425) + */ + override def test: Dependency = { new BasicBuild(context) with ScalaTest with SbtLayoutTest{ override def dependencies = outer +: super.dependencies - }.run - } + } + } } diff --git a/examples/uber-jar-example/README.md b/examples/uber-jar-example/README.md index 2460084..e4f8da0 100644 --- a/examples/uber-jar-example/README.md +++ b/examples/uber-jar-example/README.md @@ -4,7 +4,7 @@ This example shows how to build uber jar(aka fat jar) with `UberJar` plugin. In order to create uber jar: execute `cbt uberJar`. Produced jar will be in target folder. -By default, jar name is your `cbt projectName`, you can provide other name via overriding `uberJarName` task. +By default, jar name is your `cbt name`, you can provide other name via overriding `uberJarName` task. By default, main class is `Main`. You can provide custom main class via overriding `uberJarMainClass` task. diff --git a/examples/uber-jar-example/build/build.scala b/examples/uber-jar-example/build/build.scala index fec58ae..181098f 100644 --- a/examples/uber-jar-example/build/build.scala +++ b/examples/uber-jar-example/build/build.scala @@ -2,7 +2,7 @@ import cbt._ class Build(val context: Context) extends BaseBuild with UberJar { - override def projectName: String = "uber-jar-example" + override def name: String = "uber-jar-example" override def dependencies = super.dependencies ++ Resolver( mavenCentral ).bind( @@ -11,6 +11,6 @@ class Build(val context: Context) extends BaseBuild with UberJar { ScalaDependency("org.typelevel", "cats", "0.6.0") ) - override def uberJarName = projectName + "-0.0.1" + ".jar" + override def uberJarName = name + "-0.0.1" + ".jar" } diff --git a/libraries/eval/build/build.scala b/libraries/eval/build/build.scala index a869ace..29c6d6f 100644 --- a/libraries/eval/build/build.scala +++ b/libraries/eval/build/build.scala @@ -4,11 +4,11 @@ class Build(val context: Context) extends BaseBuild{ override def dependencies = super.dependencies :+ new ScalaCompilerDependency( context.cbtLastModified, context.paths.mavenCache, scalaVersion ) - override def test: Option[ExitCode] = Some{ + override def test: Dependency = { new BasicBuild(context.copy(workingDirectory = projectDirectory ++ "/test")) with ScalaTest{ override def dependencies = super.dependencies ++ Seq( DirectoryDependency(projectDirectory++"/..") ) - }.run - } + } + } } diff --git a/nailgun_launcher/CbtURLClassLoader.java b/nailgun_launcher/CbtURLClassLoader.java index 10add77..b7eb366 100644 --- a/nailgun_launcher/CbtURLClassLoader.java +++ b/nailgun_launcher/CbtURLClassLoader.java @@ -11,7 +11,7 @@ public class CbtURLClassLoader extends java.net.URLClassLoader{ + "(\n " + Arrays.toString(getURLs()) + ",\n " - + join("\n ",(getParent() == null?"":getParent().toString()).split("\n")) + + mkString("\n ",(getParent() == null?"":getParent().toString()).split("\n")) + "\n)" ); } diff --git a/nailgun_launcher/ClassLoaderCache.java b/nailgun_launcher/ClassLoaderCache.java index 6bffad0..4898687 100644 --- a/nailgun_launcher/ClassLoaderCache.java +++ b/nailgun_launcher/ClassLoaderCache.java @@ -55,7 +55,7 @@ final public class ClassLoaderCache{ for( Object key: hashMap.keySet() ){ if( key instanceof String ) res.append( - join( "\n", key.toString().split(":") ) + " -> " + hashMap.get( hashMap.get(key) ) + mkString( "\n", key.toString().split(":") ) + " -> " + hashMap.get( hashMap.get(key) ) + "\n\n" ); } diff --git a/nailgun_launcher/Stage0Lib.java b/nailgun_launcher/Stage0Lib.java index 0880bbd..6057c21 100644 --- a/nailgun_launcher/Stage0Lib.java +++ b/nailgun_launcher/Stage0Lib.java @@ -45,7 +45,7 @@ public class Stage0Lib{ public static String classpath( String... files ){ Arrays.sort(files); - return join( pathSeparator, files ); + return mkString( pathSeparator, files ); } public static long lastModified( String... files ){ @@ -216,10 +216,10 @@ public class Stage0Lib{ return (new HexBinaryAdapter()).marshal(sha1.digest()).toLowerCase(); } - public static String join(String separator, String[] parts){ - String result = parts[0]; + public static String mkString(String separator, Object[] parts){ + String result = parts[0].toString(); for(int i = 1; i < parts.length; i++){ - result += separator + parts[i]; + result += separator + parts[i].toString(); } return result; } @@ -229,4 +229,10 @@ public class Stage0Lib{ copy[array.length] = item; return copy; } + + public static String[] concat( String[] left, String[] right ){ + String[] result = Arrays.copyOf(left, left.length + right.length); + System.arraycopy(right, 0, result, left.length, right.length); + return result; + } } diff --git a/plugins/essentials/build/build.scala b/plugins/essentials/build/build.scala index 91c3a0f..eef4921 100644 --- a/plugins/essentials/build/build.scala +++ b/plugins/essentials/build/build.scala @@ -1,4 +1,6 @@ import cbt._ +// TODO: maybe move this back into stage2 to avoid having to call zinc separately for this as a plugin +// and to avoid the special casing "BuildBuildWithoutEssentials" class Build(val context: Context) extends BaseBuild{ override def dependencies = ( super.dependencies diff --git a/plugins/sbt_layout/SbtLayout.scala b/plugins/sbt_layout/SbtLayout.scala index 5cd7a03..3e8706d 100644 --- a/plugins/sbt_layout/SbtLayout.scala +++ b/plugins/sbt_layout/SbtLayout.scala @@ -1,5 +1,5 @@ package cbt - +// TODO: move this into stage2 to avoid having to call zinc separately for this as a plugin trait SbtLayoutTest extends BaseBuild{ override def sources = Seq(projectDirectory ++ "/src/test/scala") override def compileTarget = super.compileTarget.getParentFile ++ "/test-classes" diff --git a/plugins/scalafmt/Scalafmt.scala b/plugins/scalafmt/Scalafmt.scala index 1f8bf2d..e3c3c3e 100644 --- a/plugins/scalafmt/Scalafmt.scala +++ b/plugins/scalafmt/Scalafmt.scala @@ -6,7 +6,7 @@ import org.scalafmt.cli.StyleCache import org.scalafmt.config.ScalafmtConfig import java.io.File import java.nio.file.Files._ -import java.nio.file.{ FileSystems, Path, Paths } +import java.nio.file._ /** * This plugin provides scalafmt support for cbt. @@ -18,10 +18,7 @@ trait Scalafmt extends BaseBuild { * * @return always returns `ExitCode.Success` */ - final def scalafmt: ExitCode = { - Scalafmt.format(sourceFiles, scalafmtConfig) - ExitCode.Success - } + final def scalafmt: ExitCode = Scalafmt.format(sourceFiles, scalafmtConfig) /** * Scalafmt formatting config. @@ -54,46 +51,31 @@ object Scalafmt { customStyle.getOrElse(ScalafmtConfig.default) } - def format(files: Seq[File], style: ScalafmtConfig): Unit = { - var reformattedCount: Int = 0 - scalaSourceFiles(files) foreach { path => - handleFormatted(path, style) { case (original, result) => - result match { - case Formatted.Success(formatted) => - if (original != formatted) { - write(path, formatted.getBytes) - reformattedCount += 1 - } - case Formatted.Failure(e: Incomplete) => - System.err.println(s"Couldn't complete file reformat: $path") - case Formatted.Failure(e) => - System.err.println(s"Failed to format file: $path, cause: ${e}") - } + def format(files: Seq[File], style: ScalafmtConfig): ExitCode = { + val results = files.filter(_.string endsWith ".scala").map(_.toPath).map{ path => + val original = new String(readAllBytes(path)) + org.scalafmt.Scalafmt.format(original, style) match { + case Formatted.Success(formatted) => + if (original != formatted) { + val tmpPath = Paths.get(path.toString ++ ".scalafmt-tmp") + write(tmpPath, formatted.getBytes) + move(tmpPath, path, StandardCopyOption.REPLACE_EXISTING) + Some(1) + } + Some(0) + case Formatted.Failure(e) => + System.err.println(s"Scalafmt failed for $path\nCause: $e\n") + None } } - if (reformattedCount > 0) System.err.println(s"Formatted $reformattedCount Scala sources") - } - - private val scalaFileMatcher = FileSystems.getDefault.getPathMatcher("glob:**.scala") - - private def scalaSourceFiles(files: Seq[File]): Seq[Path] = { - files collect { - case f if f.exists - && scalaFileMatcher.matches(f.toPath) => f.toPath - } - } - - private def handleFormatted[T](path: Path, style: ScalafmtConfig)(handler: (String, Formatted) => T): T = { - val original = new String(readAllBytes(path)) - val result = org.scalafmt.Scalafmt.format(original, style) - handler(original, result) + if(results.forall(_.nonEmpty)){ + System.err.println(s"Formatted ${results.flatten.sum} Scala sources") + ExitCode.Success + } else ExitCode.Failure } private def getConfigPath(base: Path): Option[Path] = { - val location = base.resolve(".scalafmt.conf").toFile - Option(location.exists && location.isFile) collect { - case true => location.toPath.toAbsolutePath - } + Some( base.resolve(".scalafmt.conf").toFile ) + .collect{ case f if f.exists && f.isFile => f.toPath.toAbsolutePath } } - } diff --git a/plugins/scalafmt/build/build.scala b/plugins/scalafmt/build/build.scala index 2631908..b37d769 100644 --- a/plugins/scalafmt/build/build.scala +++ b/plugins/scalafmt/build/build.scala @@ -1,12 +1,11 @@ import cbt._ class Build(val context: Context) extends Plugin { - private val ScalafmtVersion = "0.4.2" + private val ScalafmtVersion = "0.5.7" override def dependencies = super.dependencies ++ Resolver( mavenCentral ).bind( - ScalaDependency("com.geirsson", "scalafmt", ScalafmtVersion), ScalaDependency("com.geirsson", "scalafmt-cli", ScalafmtVersion) ) } diff --git a/plugins/scalajs/ScalaJsBuild.scala b/plugins/scalajs/ScalaJsBuild.scala index 0c7222c..b6df9e9 100644 --- a/plugins/scalajs/ScalaJsBuild.scala +++ b/plugins/scalajs/ScalaJsBuild.scala @@ -2,6 +2,7 @@ package cbt import java.io.File import java.net.URL +// TODO: maybe move this into stage2 to avoid having to call zinc separately for this as a plugin trait ScalaJsBuild extends DynamicOverrides{ final protected val scalaJsLib = ScalaJsLib( scalaJsVersion, scalaVersion, context.cbtLastModified, context.paths.mavenCache @@ -23,17 +24,17 @@ trait ScalaJsBuild extends DynamicOverrides{ } override def compile = { - super.compile - scalaJsLib.link( - scalaJsTargetFile, scalaJsOptions, target +: dependencies.collect{case d: BoundMavenDependency => d.jar} - ) - None // FIXME: we need to rethink the concept of a "compile" task I think. There is no time to return here. + val res = super.compile + scalaJsLib.link( scalaJsTargetFile, scalaJsOptions, target +: dependencyClasspath.files ) + res + // FIXME: we need to rethink the concept of a "compile" task I think. + // An exit code would probably be more appropriate here. } def scalaJsOptions: Seq[String] = Seq() /** Where to put the generated js file */ - def scalaJsTargetFile: File + def scalaJsTargetFile: File = target / "app.js" override def cleanFiles = super.cleanFiles :+ scalaJsTargetFile :+ (scalaJsTargetFile ++ ".map") diff --git a/plugins/scalajs/ScalaJsLib.scala b/plugins/scalajs/ScalaJsLib.scala index ae37bde..7886478 100644 --- a/plugins/scalajs/ScalaJsLib.scala +++ b/plugins/scalajs/ScalaJsLib.scala @@ -4,28 +4,15 @@ import java.io.File case class ScalaJsLib( scalaJsVersion: String, scalaVersion: String, cbtLastModified: Long, mavenCache: File )(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef], classLoaderCache: ClassLoaderCache){ - sealed trait ScalaJsOutputMode { - def option: String - def fileSuffix: String - } - case object FastOptJS extends ScalaJsOutputMode{ - override val option = "--fastOpt" - override val fileSuffix = "fastopt" - } - case object FullOptJS extends ScalaJsOutputMode{ - override val option = "--fullOpt" - override val fileSuffix = "fullopt" - } - val lib = new Lib(logger) def dep(artifactId: String) = MavenResolver( cbtLastModified, mavenCache, mavenCentral ).bindOne( MavenDependency("org.scala-js", artifactId, scalaJsVersion) ) def link( - outputPath: File, - scalaJsOptions: Seq[String], entriesToLink: Seq[File] - ) = { + outputPath: File, scalaJsOptions: Seq[String], entriesToLink: Seq[File] + ): ExitCode = { + outputPath.getParentFile.mkdirs val scalaJsCliDep = dep( "scalajs-cli_"++lib.libMajorVersion(scalaVersion) ) outputPath.getParentFile.mkdirs lib.runMain( diff --git a/plugins/sonatype-release/src/SonatypeRelease.scala b/plugins/sonatype-release/src/SonatypeRelease.scala index 32303ad..cc2932a 100644 --- a/plugins/sonatype-release/src/SonatypeRelease.scala +++ b/plugins/sonatype-release/src/SonatypeRelease.scala @@ -1,4 +1,5 @@ package cbt +// TODO: maybe move this into stage2 to avoid having to call zinc separately for this as a plugin import cbt.sonatype.SonatypeLib diff --git a/plugins/uber-jar/src/UberJar.scala b/plugins/uber-jar/src/UberJar.scala index 3783367..79b87a1 100644 --- a/plugins/uber-jar/src/UberJar.scala +++ b/plugins/uber-jar/src/UberJar.scala @@ -15,7 +15,7 @@ trait UberJar extends BaseBuild { def uberJarMainClass: Option[String] = runClass - def uberJarName: String = projectName + ".jar" + def uberJarName: String = name + ".jar" } diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index 7f8f600..71e6ee5 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -12,7 +12,10 @@ import java.util.{Set=>_,Map=>_,List=>_,_} import javax.xml.bind.annotation.adapters.HexBinaryAdapter // CLI interop -case class ExitCode(integer: Int) +case class ExitCode(integer: Int){ + def ||( other: => ExitCode ) = if( this == ExitCode.Success ) this else other + def &&( other: => ExitCode ) = if( this != ExitCode.Success ) this else other +} object ExitCode{ val Success = ExitCode(0) val Failure = ExitCode(1) @@ -71,7 +74,7 @@ class Stage1Lib( logger: Logger ) extends BaseLib{ try{ Files.copy(stream, incomplete, StandardCopyOption.REPLACE_EXISTING) } finally { - stream.close() + stream.close } sha1.foreach{ hash => @@ -86,17 +89,11 @@ class Stage1Lib( logger: Logger ) extends BaseLib{ } } - def listFilesRecursive(f: File): Seq[File] = { - f +: ( - if( f.isDirectory ) f.listFiles.flatMap(listFilesRecursive).toVector else Seq[File]() - ) - } - // ========== compilation / execution ========== def runMain( cls: String, args: Seq[String], classLoader: ClassLoader, fakeInstance: Boolean = false ): ExitCode = { import java.lang.reflect.Modifier - logger.lib(s"Running $cls.main($args) with classLoader: " ++ classLoader.toString) + logger.run(s"Running $cls.main($args) with classLoader: " ++ classLoader.toString) trapExitCode{ val c = classLoader.loadClass(cls) val m = c.getMethod( "main", classOf[Array[String]] ) @@ -149,7 +146,8 @@ class Stage1Lib( logger: Logger ) extends BaseLib{ /** Given a directory corresponding to the root package, iterate the names of all classes derived from the class files found */ def iterateClassNames( classesRootDirectory: File ): Seq[String] = - listFilesRecursive(classesRootDirectory) + classesRootDirectory + .listRecursive .filter(_.isFile) .map(_.getPath) .collect{ @@ -270,7 +268,7 @@ class Stage1Lib( logger: Logger ) extends BaseLib{ _class, dualArgs ++ singleArgs ++ ( if(cp.isEmpty) Nil else Seq("-cp", cp) - ) ++ sourceFiles.map(_.toString), + ) ++ sourceFiles.map(_.string), zinc.classLoader ) } catch { diff --git a/stage1/URLClassLoader.scala b/stage1/URLClassLoader.scala index ff8d2a1..e93b1a4 100644 --- a/stage1/URLClassLoader.scala +++ b/stage1/URLClassLoader.scala @@ -8,6 +8,10 @@ case class URLClassLoader( classPath: ClassPath, parent: ClassLoader )( implicit classPath.strings.map( p => new URL("file:" ++ p) ).toArray, parent ) with CachingClassLoader{ + override def loadClass(name: String) = { + logger.log("classloader","loadClass " + name) + super.loadClass(name) + } val id = Math.abs( new java.util.Random().nextInt ) override def toString = ( scala.Console.BLUE diff --git a/stage1/cbt.scala b/stage1/cbt.scala index 4caa085..bc0e944 100644 --- a/stage1/cbt.scala +++ b/stage1/cbt.scala @@ -28,6 +28,28 @@ object `package`{ def /(s: String): File = new File( file, s ) def parent = lib.realpath(file ++ "/..") def string = file.toString + /* recursively deletes folders*/ + def deleteRecursive: Unit = { + val s = file.string + // some desperate attempts to keep people from accidentally deleting their hard drive + assert( file == file.getCanonicalFile, "deleteRecursive requires previous .getCanonicalFile" ) + assert( file.isAbsolute, "deleteRecursive requires absolute path" ) + assert( file.string != "", "deleteRecursive requires non-empty file path" ) + assert( s.split("/").size > 4, "deleteRecursive requires absolute path of at least depth 4" ) + assert( s.split("\\").size > 4, "deleteRecursive requires absolute path of at least depth 4" ) + assert( !listRecursive.exists(_.isHidden), "deleteRecursive requires no files to be hidden" ) + assert( listRecursive.forall(_.canWrite), "deleteRecursive requires all files to be writable" ) + if( file.isDirectory ){ + file.listFiles.map(_.deleteRecursive) + } + //file.delete + } + + def listRecursive: Seq[File] = { + file +: ( + if( file.isDirectory ) file.listFiles.flatMap(_.listRecursive).toVector else Seq[File]() + ) + } } implicit class URLExtensionMethods( url: URL ){ def ++( s: String ): URL = new URL( url.toString ++ s ) @@ -38,6 +60,16 @@ object `package`{ case e:java.lang.UnsupportedOperationException if e.getMessage === "empty.max" => None } } + implicit class ClassLoaderExtensions(classLoader: ClassLoader){ + def canLoad(className: String) = { + try{ + classLoader.loadClass(className) + true + } catch { + case e: ClassNotFoundException => false + } + } + } implicit class BuildInterfaceExtensions(build: BuildInterface){ import build._ // TODO: if every build has a method triggers a callback if files change diff --git a/stage1/logger.scala b/stage1/logger.scala index 8c8431a..373a954 100644 --- a/stage1/logger.scala +++ b/stage1/logger.scala @@ -20,7 +20,7 @@ case class Logger(enabledLoggers: Set[String], start: Long) { def log(name: String, msg: => String) = { if( ( - (enabledLoggers contains name) + (enabledLoggers contains name) || (enabledLoggers contains "all") ) && !(disabledLoggers contains name) ){ @@ -41,6 +41,7 @@ case class Logger(enabledLoggers: Set[String], start: Long) { final def git(msg: => String) = log(names.git, msg) final def pom(msg: => String) = log(names.pom, msg) final def dynamic(msg: => String) = log(names.dynamic, msg) + final def run(msg: => String) = log(names.run, msg) final def transientCache(msg: => String) = log(names.transientCache, msg) private object names{ @@ -53,6 +54,7 @@ case class Logger(enabledLoggers: Set[String], start: Long) { val lib = "lib" val test = "test" val pom = "pom" + val run = "run" val git = "git" val dynamic = "dynamic" val transientCache = "transientCache" diff --git a/stage1/resolver.scala b/stage1/resolver.scala index e02f931..ab3196a 100644 --- a/stage1/resolver.scala +++ b/stage1/resolver.scala @@ -89,6 +89,10 @@ trait DependencyImplementation extends Dependency{ def run( args: String* ): ExitCode = { runClass.map( runMain( _, args: _* ) ).getOrElse{ + // FIXME: this just doing nothing when class is not found has been repeatedly + // surprising. Let's try to make this more visible than just logging an error. + // Currently blocked on task `recursive` trying every subbuild and would error + // for all that don't have a run class. Maybe that's ok actually. logger.task( "No main class found for " ++ show ) ExitCode.Success } @@ -205,13 +209,6 @@ object Classifier{ abstract class DependenciesProxy{ } -class BoundMavenDependencies( - cbtLastModified: Long, mavenCache: File, urls: Seq[URL], mavenDependencies: Seq[MavenDependency] -)( - implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef], classLoaderCache: ClassLoaderCache -) extends Dependencies( - mavenDependencies.map( BoundMavenDependency(cbtLastModified,mavenCache,_,urls) ) -) case class MavenDependency( groupId: String, artifactId: String, version: String, classifier: Classifier = Classifier.none ){ @@ -229,6 +226,11 @@ case class BoundMavenDependency( implicit val logger: Logger, val transientCache: java.util.Map[AnyRef,AnyRef], val classLoaderCache: ClassLoaderCache ) extends ArtifactInfo with DependencyImplementation{ def moduleKey = this.getClass.getName ++ "(" ++ mavenDependency.serialize ++ ")" + override def hashCode = mavenDependency.hashCode + override def equals(other: Any) = other match{ + case o: BoundMavenDependency => o.mavenDependency == mavenDependency && o.repositories == repositories + case _ => false + } val MavenDependency( groupId, artifactId, version, classifier ) = mavenDependency assert( Option(groupId).collect{ @@ -261,7 +263,7 @@ case class BoundMavenDependency( import scala.collection.JavaConversions._ private def resolve(suffix: String, hash: Option[String], useClassifier: Boolean): File = { - logger.resolver("Resolving "+this) + logger.resolver(lib.blue("Resolving ")+this) val file = mavenCache ++ basePath(useClassifier) ++ "." ++ suffix val urls = repositories.map(_ ++ basePath(useClassifier) ++ "." ++ suffix) urls.find( @@ -284,7 +286,10 @@ case class BoundMavenDependency( def jar: File = taskCache[BoundMavenDependency]("jar").memoize{ resolve("jar", Some(jarSha1), true) } def pom: File = taskCache[BoundMavenDependency]("pom").memoize{ resolve("pom", Some(pomSha1), false) } - private def pomXml = XML.loadFile(pom.string) + private def pomXml = { + logger.resolver( "Loading pom file: " ++ pom.string ) + XML.loadFile(pom.string) + } // ========== pom traversal ========== private lazy val transitivePom: Seq[BoundMavenDependency] = { diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index 8d72f9a..3c3cbec 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -32,7 +32,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge s"You need to extend ${lib.buildBuildClassName} in: " + projectDirectory + "/" ++ lib.buildDirectoryName ) - final def usage: String = lib.usage(this.getClass, show) + final def help: String = lib.usage(this.getClass, show) final def taskNames: String = lib.taskNames(this.getClass).sorted.mkString("\n") @@ -41,7 +41,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge def defaultScalaVersion: String = constants.scalaVersion final def scalaVersion = context.scalaVersion getOrElse defaultScalaVersion final def scalaMajorVersion: String = lib.libMajorVersion(scalaVersion) - def projectName = "default" + def name = projectDirectory.getName // TODO: get rid of this in favor of newBuild. // currently blocked on DynamicOverride being not parts @@ -85,7 +85,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge 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") + def sourceFileFilter(file: File) = lib.sourceFileFilter(file) /** Absolute path names for all individual files found in sources directly or contained in directories. */ final def sourceFiles: Seq[File] = lib.sourceFiles(sources, sourceFileFilter) @@ -187,16 +187,22 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge } def run: ExitCode = run( context.args: _* ) - - def test: Any = - lib.callReflective( - DirectoryDependency(projectDirectory++"/test").dependency, - Some("run"), - context - ) - - def t = test - def rt = recursiveUnsafe(Some("test")) + def test: Dependency = { + val testDirectory = projectDirectory / "test" + if( (testDirectory / lib.buildDirectoryName / lib.buildFileName).exists ){ + // FIYME: maybe we can make loadRoot(...).finalBuild an Option some + DirectoryDependency( testDirectory ).dependency + } else { + new BasicBuild( context.copy(workingDirectory = testDirectory) ){ + override def dependencies = Seq( + DirectoryDependency(projectDirectory++"/..") + ) + def apply = run + } + } + } + def t: Any = lib.callReflective( test, Some("run"), context ) + def rt = recursiveUnsafe(Some("test.run")) def recursiveSafe(_run: BuildInterface => Any): ExitCode = { val builds = (this +: transitiveDependencies).collect{ @@ -265,6 +271,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge } override def show = this.getClass.getSimpleName ++ "(" ++ projectDirectory.string ++ ")" + override def toString = show // a method that can be called only to trigger any side-effects final def `void` = () diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala index 0162791..9a2918a 100644 --- a/stage2/BuildDependency.scala +++ b/stage2/BuildDependency.scala @@ -25,6 +25,7 @@ final case class DirectoryDependency(context: Context, pathToNestedBuild: String def transientCache = context.transientCache private lazy val root = lib.loadRoot( context ) lazy val dependency: Dependency = { + // TODO: move this into finalBuild probably def selectNestedBuild( build: Dependency, names: Seq[String], previous: Seq[String] ): Dependency = { names.headOption.map{ name => if( lib.taskNames(build.getClass).contains(name) ){ diff --git a/stage2/Lib.scala b/stage2/Lib.scala index b6187ce..33bfe1a 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -140,22 +140,19 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ ).flatMap(lib.taskNames).distinct.sorted val thisTasks = lib.taskNames(buildClass) diff baseTasks ( - ( + s"Methods provided by $show\n\n" + ++ ( if( thisTasks.nonEmpty ){ - s"""Methods provided by ${show} - - ${thisTasks.mkString(" ")} - -""" - } else "" - ) ++ s"""Methods provided by CBT (but possibly overwritten) - - ${baseTasks.mkString(" ")}""" - ) ++ "\n" + thisTasks.mkString(" ") ++ "\n\n" + } else "<none>" + ) + ++ s"\n\nMethods provided by CBT (but possibly overwritten)\n\n" + ++ baseTasks.mkString(" ") + "\n" + ) } def callReflective[T <: AnyRef]( obj: T, code: Option[String], context: Context ): ExitCode = { - callInternal( obj, code.toSeq.flatMap(_.split("\\.").map( NameTransformer.encode )), Nil, context ) match { + callInternal( obj, code.toSeq.flatMap(_.split("\\.").map( NameTransformer.encode )), Nil, context ).map { case (obj, code, None) => val s = render(obj) if(s.nonEmpty) @@ -168,7 +165,7 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ if(s.nonEmpty) System.err.println(s) code getOrElse ExitCode.Failure - } + }.reduceOption(_ && _).getOrElse( ExitCode.Failure ) } private def render[T]( obj: T ): String = { @@ -177,12 +174,12 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ case None => "" case d: Dependency => lib.usage(d.getClass, d.show()) case c: ClassPath => c.string - case t:ToolsStage2.type => "Available methods: " ++ lib.taskNames(t.getClass).mkString(" ") + case ExitCode(int) => System.err.println(int); System.exit(int); ??? case _ => obj.toString } } - private def callInternal[T <: AnyRef]( obj: T, members: Seq[String], previous: Seq[String], context: Context ): (Option[Object], Option[ExitCode], Option[String]) = { + private def callInternal[T <: AnyRef]( obj: T, members: Seq[String], previous: Seq[String], context: Context ): Seq[(Option[Object], Option[ExitCode], Option[String])] = { members.headOption.map{ taskName => logger.lib("Calling task " ++ taskName.toString) taskMethods(obj.getClass).get(taskName).map{ method => @@ -190,12 +187,12 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ result match { case code if code.getClass.getSimpleName == "ExitCode" => // FIXME: ExitCode needs to be part of the compatibility interfaces - (None, Some(ExitCode(Stage0Lib.get(code,"integer").asInstanceOf[Int])), None) - case Seq(bs @ _*) if bs.forall(_.isInstanceOf[BaseBuild]) => - bs.map( b => callInternal(b.asInstanceOf[BaseBuild], members.tail, previous :+ taskName, context) ).head + Seq((None, Some(ExitCode(Stage0Lib.get(code,"integer").asInstanceOf[Int])), None)) + case bs: Seq[_] if bs.size > 0 && bs.forall(_.isInstanceOf[BaseBuild]) => + bs.flatMap( b => callInternal(b.asInstanceOf[BaseBuild], members.tail, previous :+ taskName, context) ) case _ => callInternal(result, members.tail, previous :+ taskName, context) } - }.getOrElse( (None, None, None) ) + }.getOrElse( Seq( (None, None, None) ) ) }.getOrElse{ val folder = NameTransformer.decode(taskName) if( context != null && (context.workingDirectory / folder).exists ){ @@ -209,11 +206,19 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ newContext ) } else { - ( Some(obj), None, Some("\nMethod not found: " ++ (previous :+ taskName).mkString(".") ++ "\n") ) + Seq( ( Some(obj), None, Some("\nMethod not found: " ++ (previous :+ taskName).mkString(".") ++ "\n") ) ) } } }.getOrElse{ - ( Some(obj), None, None ) + Seq(( + Some( + obj.getClass.getMethods.find(m => m.getName == "apply" && m.getParameterCount == 0).map( + _.invoke(obj) + ).getOrElse( obj ) + ), + None, + None + )) } } @@ -280,10 +285,13 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ def dirname(path: File): File = new File(realpath(path).string.stripSuffix("/").split("/").dropRight(1).mkString("/")) def nameAndContents(file: File) = basename(file) -> readAllBytes(file.toPath) - def sourceFiles( sources: Seq[File], sourceFileFilter: File => Boolean ): Seq[File] = { + /** Which file endings to consider being source files. */ + def sourceFileFilter(file: File): Boolean = file.toString.endsWith(".scala") || file.toString.endsWith(".java") + + def sourceFiles( sources: Seq[File], sourceFileFilter: File => Boolean = sourceFileFilter ): Seq[File] = { for { base <- sources.filter(_.exists).map(lib.realpath) - file <- lib.listFilesRecursive(base) if file.isFile && sourceFileFilter(file) + file <- base.listRecursive if file.isFile && sourceFileFilter(file) } yield file } @@ -309,7 +317,7 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ try{ val names = for { base <- files.filter(_.exists).map(realpath) - file <- listFilesRecursive(base) if file.isFile + file <- base.listRecursive if file.isFile } yield { val strip = Some( base ).filter(_.isDirectory) ++ stripBaseCanonical val name = strip.foldLeft( file.getCanonicalPath )( diff --git a/stage2/ToolsTasks.scala b/stage2/ToolsTasks.scala index 0cfd501..b5e94d4 100644 --- a/stage2/ToolsTasks.scala +++ b/stage2/ToolsTasks.scala @@ -10,6 +10,8 @@ class ToolsTasks( cbtHome: File, cbtLastModified: Long )(implicit classLoaderCache: ClassLoaderCache){ + def apply: String = "Available methods: " ++ lib.taskNames(getClass).mkString(" ") + private val paths = CbtPaths(cbtHome, cache) import paths._ implicit val logger: Logger = lib.logger diff --git a/test/build/build.scala b/test/build/build.scala index 5a138fb..9e02144 100644 --- a/test/build/build.scala +++ b/test/build/build.scala @@ -1,4 +1,5 @@ import cbt._ class Build(val context: cbt.Context) extends BaseBuild{ override def dependencies = super.dependencies :+ context.cbtDependency + def apply = run } diff --git a/test/test.scala b/test/test.scala index 9a70b6c..3305926 100644 --- a/test/test.scala +++ b/test/test.scala @@ -331,9 +331,14 @@ object Main{ assert(res.exit0) assert(res.out startsWith "Bar2: Some(DynamicBuild", res.out ++ res.err) } + { + val res = runCbt("../examples/cross-build-example", Seq("cross.scalaVersion")) + assert(res.exit0) + assert(res.out == "2.10.5\n2.11.7\n", res.out) + } { - val res = runCbt("../libraries/eval", Seq("test")) + val res = runCbt("../libraries/eval", Seq("test.run")) assert(res.exit0) assert(res.out.contains("All tests passed"), res.out) } diff --git a/tools/gui/resources/template-project/build/build.scala b/tools/gui/resources/template-project/build/build.scala index 3ec5db9..b632d97 100644 --- a/tools/gui/resources/template-project/build/build.scala +++ b/tools/gui/resources/template-project/build/build.scala @@ -2,4 +2,4 @@ import cbt._ class Build(val context: Context) extends BaseBuild##with## { -##projectName####dependencies##} +##name####dependencies##} diff --git a/tools/gui/src/ProjectBuilder.scala b/tools/gui/src/ProjectBuilder.scala index ceba376..4204942 100644 --- a/tools/gui/src/ProjectBuilder.scala +++ b/tools/gui/src/ProjectBuilder.scala @@ -95,7 +95,7 @@ class ProjectBuilder( writeTemplate( templateDir / "build" / "build.scala", buildDir / "build.scala", - (name, s""" override def projectName = "$name"$blankLine""", "##projectName##"), + (name, s""" override def name = "$name"$blankLine""", "##name##"), (dependencyString, dependencies + blankLine, "##dependencies##"), (plugins, plugins, "##with##") ) |