From 4f4c34f3f52b87057626682dd43a21cd83e2ff7a Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Wed, 15 Feb 2017 03:30:46 -0500 Subject: add package to generated Build in build.scala and in-package discovery This should allow for build to add other builds to their dependencies and interact with them in a type-safe way. And ever regardless it seems like good practice to never have the same class existing in the same package or the top-level package even if they don’t end up on the same classpath. This might also help make stack traces easier to understand. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also improve error messages for mistakes with the build class, e.g. constructor, super classes, etc. --- stage1/Stage1Lib.scala | 54 +++++++++++++--------- stage2/BuildBuild.scala | 30 ++++++++---- stage2/Scaffold.scala | 3 +- .../build/build.scala | 3 ++ .../build/build.scala | 2 + test/broken-build/empty-build-file/Main.scala | 5 ++ .../empty-build-file/build/build.scala | 0 test/broken-build/no-build-file/Main.scala | 5 ++ test/broken-build/no-build-file/build/foo.scala | 0 test/empty-build-file/Main.scala | 5 -- test/empty-build-file/build/build.scala | 0 test/forgot-extend/build/build.scala | 2 - test/no-build-file/Main.scala | 5 -- test/no-build-file/build/foo.scala | 0 test/test.scala | 19 +++++--- 15 files changed, 83 insertions(+), 50 deletions(-) create mode 100644 test/broken-build/build-class-with-wrong-arguments/build/build.scala create mode 100644 test/broken-build/build-class-with-wrong-parent/build/build.scala create mode 100644 test/broken-build/empty-build-file/Main.scala create mode 100644 test/broken-build/empty-build-file/build/build.scala create mode 100644 test/broken-build/no-build-file/Main.scala create mode 100644 test/broken-build/no-build-file/build/foo.scala delete mode 100644 test/empty-build-file/Main.scala delete mode 100644 test/empty-build-file/build/build.scala delete mode 100644 test/forgot-extend/build/build.scala delete mode 100644 test/no-build-file/Main.scala delete mode 100644 test/no-build-file/build/foo.scala diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index 505b298..79df450 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -146,35 +146,43 @@ class Stage1Lib( logger: Logger ) extends BaseLib{ pickOne( "Which one do you want to run?", mainClasses )( _.toString ) } - def mainClasses( targetDirectory: File, classLoader : ClassLoader ): Seq[Class[_]] = { - val arrayClass = classOf[Array[String]] - val unitClass = classOf[Unit] - - listFilesRecursive(targetDirectory) + /** 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) .filter(_.isFile) .map(_.getPath) .collect{ // no $ to avoid inner classes case path if !path.contains("$") && path.endsWith(".class") => - try{ - classLoader.loadClass( - path - .stripSuffix(".class") - .stripPrefix(targetDirectory.getPath) - .stripPrefix(File.separator) // 1 for the slash - .replace(File.separator, ".") - ) - } catch { - case e: ClassNotFoundException => null - case e: NoClassDefFoundError => null - } - }.filterNot(_ == null).filter( - _.getDeclaredMethods().exists( m => - m.getName == "main" - && m.getParameterTypes.toList == List(arrayClass) - && m.getReturnType == unitClass - ) + path.stripSuffix(".class") + .stripPrefix(classesRootDirectory.getPath) + .stripPrefix(File.separator) // 1 for the slash + .replace(File.separator, ".") + } + + /** ignoreMissingClasses allows ignoring other classes root directories which are subdirectories of this one */ + def iterateClasses( classesRootDirectory: File, classLoader: ClassLoader, ignoreMissingClasses: Boolean ) = + iterateClassNames(classesRootDirectory).map{ name => + try{ + classLoader.loadClass(name) + } catch { + case e: ClassNotFoundException if ignoreMissingClasses => null + case e: NoClassDefFoundError if ignoreMissingClasses => null + } + }.filterNot(ignoreMissingClasses && _ == null) + + def mainClasses( classesRootDirectory: File, classLoader: ClassLoader ): Seq[Class[_]] = { + val arrayClass = classOf[Array[String]] + val unitClass = classOf[Unit] + + iterateClasses( classesRootDirectory, classLoader, true ).filter( + _.getDeclaredMethods().exists( m => + m.getName == "main" + && m.getParameterTypes.toList == List(arrayClass) + && m.getReturnType == unitClass ) + ) } implicit class ClassLoaderExtensions(classLoader: ClassLoader){ diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala index 798bd7d..2eebcbc 100644 --- a/stage2/BuildBuild.scala +++ b/stage2/BuildBuild.scala @@ -63,15 +63,29 @@ trait BuildBuildWithoutEssentials extends BaseBuild{ .invoke( null, ctx ) } }.getOrElse{ - try{ - classLoader - .loadClass(lib.buildClassName) - .getConstructors.head - .newInstance(managedContext) - } catch { - case e: ClassNotFoundException if e.getMessage == lib.buildClassName => - throw new Exception("You need to define a class Build in build.scala in: "+projectDirectory) + val buildClasses = + lib.iterateClasses( compileTarget, classLoader, false ) + .filter(_.getSimpleName == lib.buildClassName) + .filter(classOf[BaseBuild] isAssignableFrom _) + if( buildClasses.size == 0 ){ + throw new Exception( + s"You need to define a class ${lib.buildClassName} extending an appropriate super class in\n" + + (projectDirectory / lib.buildFileName) ++ "\nbut none found." + ) + } else if( buildClasses.size > 1 ){ + throw new Exception( + s"You need to define exactly one class ${lib.buildClassName} extending an appropriate build super class, but multiple found in " + projectDirectory + ":\n" + buildClasses.mkString("\n") + ) + } else { + val buildClass = buildClasses.head + if( !buildClass.getConstructors.exists(_.getParameterTypes.toList == List(classOf[Context])) ){ + throw new Exception( + s"Expected class ${lib.buildClassName}(val context: Context), but found different constructor in\n" + + projectDirectory ++ "\n" + + buildClass ++ "(" ++ buildClass.getConstructors.map(_.getParameterTypes.mkString(", ")).mkString("; ") + ")" ) } + buildClass.getConstructors.head.newInstance(managedContext) + } } } ) diff --git a/stage2/Scaffold.scala b/stage2/Scaffold.scala index b110258..68a966b 100644 --- a/stage2/Scaffold.scala +++ b/stage2/Scaffold.scala @@ -59,7 +59,8 @@ object Main{ def createBuild( projectDirectory: File ): Unit = { - createFile(projectDirectory, "build/build.scala", s"""import cbt._ + createFile(projectDirectory, lib.buildDirectoryName++"/"++lib.buildFileName, s"""package cbt_build.${packageFromDirectory(projectDirectory)} +import cbt._ class Build(val context: Context) extends BaseBuild{ override def dependencies = super.dependencies ++ // don't forget super.dependencies here for scala-library, etc. diff --git a/test/broken-build/build-class-with-wrong-arguments/build/build.scala b/test/broken-build/build-class-with-wrong-arguments/build/build.scala new file mode 100644 index 0000000..cf040a1 --- /dev/null +++ b/test/broken-build/build-class-with-wrong-arguments/build/build.scala @@ -0,0 +1,3 @@ +package cbt_build.build_class_with_wrong_arguments +import cbt._ +class Build(i: Int, val context: Context) extends BaseBuild diff --git a/test/broken-build/build-class-with-wrong-parent/build/build.scala b/test/broken-build/build-class-with-wrong-parent/build/build.scala new file mode 100644 index 0000000..9181a5d --- /dev/null +++ b/test/broken-build/build-class-with-wrong-parent/build/build.scala @@ -0,0 +1,2 @@ +import cbt._ +class Build(val context: Context) diff --git a/test/broken-build/empty-build-file/Main.scala b/test/broken-build/empty-build-file/Main.scala new file mode 100644 index 0000000..19d4beb --- /dev/null +++ b/test/broken-build/empty-build-file/Main.scala @@ -0,0 +1,5 @@ +object Main{ + def main( args: Array[String] ): Unit = { + println( Console.GREEN ++ "Hello World" ++ Console.RESET ) + } +} diff --git a/test/broken-build/empty-build-file/build/build.scala b/test/broken-build/empty-build-file/build/build.scala new file mode 100644 index 0000000..e69de29 diff --git a/test/broken-build/no-build-file/Main.scala b/test/broken-build/no-build-file/Main.scala new file mode 100644 index 0000000..19d4beb --- /dev/null +++ b/test/broken-build/no-build-file/Main.scala @@ -0,0 +1,5 @@ +object Main{ + def main( args: Array[String] ): Unit = { + println( Console.GREEN ++ "Hello World" ++ Console.RESET ) + } +} diff --git a/test/broken-build/no-build-file/build/foo.scala b/test/broken-build/no-build-file/build/foo.scala new file mode 100644 index 0000000..e69de29 diff --git a/test/empty-build-file/Main.scala b/test/empty-build-file/Main.scala deleted file mode 100644 index 19d4beb..0000000 --- a/test/empty-build-file/Main.scala +++ /dev/null @@ -1,5 +0,0 @@ -object Main{ - def main( args: Array[String] ): Unit = { - println( Console.GREEN ++ "Hello World" ++ Console.RESET ) - } -} diff --git a/test/empty-build-file/build/build.scala b/test/empty-build-file/build/build.scala deleted file mode 100644 index e69de29..0000000 diff --git a/test/forgot-extend/build/build.scala b/test/forgot-extend/build/build.scala deleted file mode 100644 index 9181a5d..0000000 --- a/test/forgot-extend/build/build.scala +++ /dev/null @@ -1,2 +0,0 @@ -import cbt._ -class Build(val context: Context) diff --git a/test/no-build-file/Main.scala b/test/no-build-file/Main.scala deleted file mode 100644 index 19d4beb..0000000 --- a/test/no-build-file/Main.scala +++ /dev/null @@ -1,5 +0,0 @@ -object Main{ - def main( args: Array[String] ): Unit = { - println( Console.GREEN ++ "Hello World" ++ Console.RESET ) - } -} diff --git a/test/no-build-file/build/foo.scala b/test/no-build-file/build/foo.scala deleted file mode 100644 index e69de29..0000000 diff --git a/test/test.scala b/test/test.scala index 778fcda..d8714c0 100644 --- a/test/test.scala +++ b/test/test.scala @@ -240,21 +240,28 @@ object Main{ } { - val res = runCbt("forgot-extend", Seq("run")) + val res = runCbt("broken-build/build-class-with-wrong-arguments", Seq("run")) assert(!res.exit0) - assert(res.err contains s"${lib.buildClassName} cannot be cast to cbt.BuildInterface", res.err) + assert(res.err contains s"Expected class ${lib.buildClassName}(val context: Context), but found different constructor", res.err) + assert(res.err contains s"${lib.buildClassName}(int, interface cbt.Context)", res.err) } { - val res = runCbt("no-build-file", Seq("run")) + val res = runCbt("broken-build/build-class-with-wrong-parent", Seq("run")) assert(!res.exit0) - assert(res.err contains s"No file ${lib.buildFileName} (lower case) found in", res.err) + assert(res.err contains s"You need to define a class ${lib.buildClassName} extending an appropriate super class", res.err) } { - val res = runCbt("empty-build-file", Seq("run")) + val res = runCbt("broken-build/no-build-file", Seq("run")) assert(!res.exit0) - assert(res.err contains s"You need to define a class ${lib.buildClassName} in", res.err) + assert(res.err contains s"No file ${lib.buildFileName} (lower case) found", res.err) + } + + { + val res = runCbt("broken-build/empty-build-file", Seq("run")) + assert(!res.exit0) + assert(res.err contains s"You need to define a class ${lib.buildClassName}", res.err) } { -- cgit v1.2.3