aboutsummaryrefslogtreecommitdiff
path: root/libraries
diff options
context:
space:
mode:
authorChristopher Vogt <oss.nsp@cvogt.org>2017-03-20 22:09:38 -0400
committerChristopher Vogt <oss.nsp@cvogt.org>2017-03-27 19:56:13 -0400
commitbba2abe7ee38b8903822a07578c46466923d13ed (patch)
treea357fb8def6f58a9ea9a37411f3f5640dcb525fe /libraries
parentd2f8cade709b7d55a93e18592b6e38247d648ca9 (diff)
downloadcbt-bba2abe7ee38b8903822a07578c46466923d13ed.tar.gz
cbt-bba2abe7ee38b8903822a07578c46466923d13ed.tar.bz2
cbt-bba2abe7ee38b8903822a07578c46466923d13ed.zip
start modularizing cbt into libraries
this extracts certain parts of cbt into stand-alone libraries, which can be published to maven and used outside of cbt. This also adds scalariform for these parts of the code. This slows down cbt’s own build a lot because of the number of projects involved! So we’ll follow this by a bunch of performance tweak commits.
Diffstat (limited to 'libraries')
-rw-r--r--libraries/capture_args/build/build.scala12
-rw-r--r--libraries/capture_args/build/build/build.scala5
-rw-r--r--libraries/capture_args/package.scala28
-rw-r--r--libraries/common-0/ProxySecurityManager.java130
-rw-r--r--libraries/common-0/TrapSecurityManager.java88
-rw-r--r--libraries/common-0/TrapSystemExit.java35
-rw-r--r--libraries/common-0/build/build.scala7
-rw-r--r--libraries/common-0/build/build/build.scala5
-rw-r--r--libraries/common-1/ExitCode.java24
-rw-r--r--libraries/common-1/ExitCode.scala11
-rw-r--r--libraries/common-1/build/build.scala8
-rw-r--r--libraries/common-1/build/build/build.scala5
-rw-r--r--libraries/common-1/common_1.scala34
-rw-r--r--libraries/file/build/build.scala8
-rw-r--r--libraries/file/build/build/build.scala5
-rw-r--r--libraries/file/file.scala112
-rw-r--r--libraries/interfaces/ExitCode.java4
-rw-r--r--libraries/proguard/Proguard.scala10
-rw-r--r--libraries/proguard/build/build.scala8
-rw-r--r--libraries/proguard/build/build/build.scala8
-rw-r--r--libraries/reflect/StaticMethod.scala4
-rw-r--r--libraries/reflect/build/build.scala8
-rw-r--r--libraries/reflect/build/build/build.scala5
-rw-r--r--libraries/reflect/reflect.scala177
24 files changed, 710 insertions, 31 deletions
diff --git a/libraries/capture_args/build/build.scala b/libraries/capture_args/build/build.scala
index 24c1faa..fab36bf 100644
--- a/libraries/capture_args/build/build.scala
+++ b/libraries/capture_args/build/build.scala
@@ -1,11 +1,21 @@
package cbt_build.cbt.capture_args
import cbt._
-class Build(val context: Context) extends BaseBuild{
+import cbt_internal._
+class Build(val context: Context) extends Library{
+ def description = (
+ "macro that allows you to extract a functions arguments"
+ ++" as strings in order to programmatically pass them to a stringly typed"
+ ++" api such as a process call, http or a .main method"
+ )
+
+ def inceptionYear = 2017
+
override def dependencies = (
super.dependencies ++ // don't forget super.dependencies here for scala-library, etc.
Resolver( mavenCentral ).bind(
MavenDependency( "org.scala-lang", "scala-reflect", scalaVersion )
)
)
+
override def scalacOptions = super.scalacOptions :+ "-language:experimental.macros"
}
diff --git a/libraries/capture_args/build/build/build.scala b/libraries/capture_args/build/build/build.scala
new file mode 100644
index 0000000..6fabf47
--- /dev/null
+++ b/libraries/capture_args/build/build/build.scala
@@ -0,0 +1,5 @@
+package cbt_build.cbt.capture_args.build
+import cbt._
+class Build(val context: Context) extends BuildBuild with CbtInternal{
+ override def dependencies = super.dependencies :+ cbtInternal.library
+}
diff --git a/libraries/capture_args/package.scala b/libraries/capture_args/package.scala
index f5cd219..5c96a8d 100644
--- a/libraries/capture_args/package.scala
+++ b/libraries/capture_args/package.scala
@@ -2,29 +2,29 @@ package cbt.capture_args
import scala.reflect._
import scala.reflect.macros.blackbox.Context
-case class Argument( annotations: Seq[annotation.Annotation], name: String, values: Option[Seq[String]] ){
+case class Argument( annotations: Seq[annotation.Annotation], name: String, values: Option[Seq[String]] ) {
def toSeqOption = values.map( name +: _ )
}
case class Signature( name: String, args: Seq[Argument] )
-object `package`{
- def captureArgsImplementation(c: Context): c.Tree = {
+object `package` {
+ def captureArgsImplementation( c: Context ): c.Tree = {
import c.universe._
- def literal( a: Any ) = Literal(Constant(a))
- def ident( name: String ) = Ident(TermName(name))
+ def literal( a: Any ) = Literal( Constant( a ) )
+ def ident( name: String ) = Ident( TermName( name ) )
- def findOwnerRecursive(symbol: Symbol, predicate: Symbol => Boolean): Option[Symbol] = {
- Option(symbol).flatMap{
+ def findOwnerRecursive( symbol: Symbol, predicate: Symbol => Boolean ): Option[Symbol] = {
+ Option( symbol ).flatMap {
s =>
- if(s == NoSymbol) None else if(predicate(s)) Some(s) else findOwnerRecursive(s.owner, predicate)
+ if ( s == NoSymbol ) None else if ( predicate( s ) ) Some( s ) else findOwnerRecursive( s.owner, predicate )
}
}
val method: MethodSymbol = (
- findOwnerRecursive(c.internal.enclosingOwner, _.isMethod).map(_.asMethod)
+ findOwnerRecursive( c.internal.enclosingOwner, _.isMethod ).map( _.asMethod )
orElse
- findOwnerRecursive(c.internal.enclosingOwner, _.isClass).map(_.asClass.primaryConstructor.asMethod)
+ findOwnerRecursive( c.internal.enclosingOwner, _.isClass ).map( _.asClass.primaryConstructor.asMethod )
getOrElse {
c.error(
c.enclosingPosition,
@@ -33,14 +33,14 @@ object `package`{
???
}
)
- val name = literal(method.name.decodedName.toString)
+ val name = literal( method.name.decodedName.toString )
// Note: method.paramLists requires explicitly annotated result type
- val params = method.paramLists.flatten.map(_.asTerm)
+ val params = method.paramLists.flatten.map( _.asTerm )
- val args = params.map{ s =>
+ val args = params.map { s =>
val name = literal( s.name.decodedName.toString )
val i = ident( s.name.toString )
- q"_root_.cbt.capture_args.Argument( _root_.scala.Seq( ..${s.annotations.map(_.tree)} ), $name, valueToStrings($i) )"
+ q"_root_.cbt.capture_args.Argument( _root_.scala.Seq( ..${s.annotations.map( _.tree )} ), $name, valueToStrings($i) )"
}
val tree = q"""
_root_.cbt.capture_args.Signature( name = $name, args = Seq( ..$args ) )
diff --git a/libraries/common-0/ProxySecurityManager.java b/libraries/common-0/ProxySecurityManager.java
new file mode 100644
index 0000000..4669add
--- /dev/null
+++ b/libraries/common-0/ProxySecurityManager.java
@@ -0,0 +1,130 @@
+package cbt.reflect;
+
+import java.security.*;
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+
+/*
+SecurityManager proxy that forwards all calls to the provided target if != null.
+Useful to replace a previously installed SecurityManager, overriding some methods
+but forwarding the rest.
+*/
+class ProxySecurityManager extends SecurityManager {
+ private SecurityManager target;
+
+ protected ProxySecurityManager(SecurityManager target) {
+ this.target = target;
+ }
+
+ public Object getSecurityContext() {
+ if (target != null) return target.getSecurityContext();
+ else return super.getSecurityContext();
+ }
+
+ public void checkPermission(Permission perm) {
+ if (target != null) target.checkPermission(perm);
+ }
+
+ public void checkPermission(Permission perm, Object context) {
+ if (target != null) target.checkPermission(perm, context);
+ }
+
+ public void checkCreateClassLoader() {
+ if (target != null) target.checkCreateClassLoader();
+ }
+
+ public void checkAccess(Thread t) {
+ if (target != null) target.checkAccess(t);
+ }
+
+ public void checkAccess(ThreadGroup g) {
+ if (target != null) target.checkAccess(g);
+ }
+
+ public void checkExit(int status) {
+ if (target != null) target.checkExit(status);
+ }
+
+ public void checkExec(String cmd) {
+ if (target != null) target.checkExec(cmd);
+ }
+
+ public void checkLink(String lib) {
+ if (target != null) target.checkLink(lib);
+ }
+
+ /*
+ public void checkRead(FileDescriptor fd) {
+ if (target != null) target.checkRead(fd);
+ }
+
+ public void checkRead(String file) {
+ if (target != null) target.checkRead(file);
+ }
+
+ public void checkRead(String file, Object context) {
+ if (target != null) target.checkRead(file, context);
+ }
+ */
+
+ public void checkWrite(FileDescriptor fd) {
+ if (target != null) target.checkWrite(fd);
+ }
+
+ public void checkWrite(String file) {
+ if (target != null) target.checkWrite(file);
+ }
+
+ public void checkDelete(String file) {
+ if (target != null) target.checkDelete(file);
+ }
+
+ public void checkConnect(String host, int port) {
+ if (target != null) target.checkConnect(host, port);
+ }
+
+ public void checkConnect(String host, int port, Object context) {
+ if (target != null) target.checkConnect(host, port, context);
+ }
+
+ public void checkListen(int port) {
+ if (target != null) target.checkListen(port);
+ }
+
+ public void checkAccept(String host, int port) {
+ if (target != null) target.checkAccept(host, port);
+ }
+
+ public void checkMulticast(InetAddress maddr) {
+ if (target != null) target.checkMulticast(maddr);
+ }
+
+ public void checkPropertiesAccess() {
+ if (target != null) target.checkPropertiesAccess();
+ }
+
+ public void checkPropertyAccess(String key) {
+ if (target != null) target.checkPropertyAccess(key);
+ }
+
+ public void checkPrintJobAccess() {
+ if (target != null) target.checkPrintJobAccess();
+ }
+
+ public void checkPackageAccess(String pkg) {
+ if (target != null) target.checkPackageAccess(pkg);
+ }
+
+ public void checkPackageDefinition(String pkg) {
+ if (target != null) target.checkPackageDefinition(pkg);
+ }
+
+ public void checkSetFactory() {
+ if (target != null) target.checkSetFactory();
+ }
+
+ public ThreadGroup getThreadGroup() {
+ if (target != null) return target.getThreadGroup();
+ else return super.getThreadGroup();
+ }
+}
diff --git a/libraries/common-0/TrapSecurityManager.java b/libraries/common-0/TrapSecurityManager.java
new file mode 100644
index 0000000..161b74f
--- /dev/null
+++ b/libraries/common-0/TrapSecurityManager.java
@@ -0,0 +1,88 @@
+package cbt.reflect;
+
+import java.security.*;
+/*
+When enabled, this SecurityManager turns System.exit(...) calls into exceptions that can be caught and handled.
+Installing a SecurityManager is a global side-effect and thus needs extra care in a persistent
+background process like CBT's. The current approach is install it once during JVM-startup.
+When disabled this delegates to the SecurityManager installed before if any, which
+would be Nailgun's if running on Nailgun. If we do not delegate to Nailgun, it seems we
+could in some cases kill the server process
+*/
+public class TrapSecurityManager extends ProxySecurityManager {
+ public static ThreadLocal<Boolean> trapExitCode() {
+ // storing the flag in the installed security manager
+ // instead of e.g. a static member is necessary because
+ // we run multiple versions of CBT with multiple TrapSecurityManager classes
+ // but we need to affect the installed one
+ SecurityManager sm = System.getSecurityManager();
+ if (sm instanceof TrapSecurityManager) {
+ return ((TrapSecurityManager) sm)._trapExitCode;
+ } else {
+ try {
+ @SuppressWarnings("unchecked")
+ ThreadLocal<Boolean> res =
+ (ThreadLocal<Boolean>) sm.getClass().getMethod("trapExitCode").invoke(null);
+ return res;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private final ThreadLocal<Boolean> _trapExitCode =
+ new ThreadLocal<Boolean>() {
+ @Override
+ protected Boolean initialValue() {
+ return false;
+ }
+ };
+
+ protected TrapSecurityManager(SecurityManager parent) {
+ super(parent);
+ }
+
+ public void checkPermission(Permission permission) {
+ /*
+ NOTE: is it actually ok, to just make these empty?
+ Calling .super leads to ClassNotFound exteption for a lambda.
+ Calling to the previous SecurityManager leads to a stack overflow
+ */
+ if (!TrapSecurityManager.trapExitCode().get()) {
+ super.checkPermission(permission);
+ }
+ }
+
+ public void checkPermission(Permission permission, Object context) {
+ /* Does this methods need to be overidden? */
+ if (!TrapSecurityManager.trapExitCode().get()) {
+ super.checkPermission(permission, context);
+ }
+ }
+
+ // FIXME: we should probably choose a more unique name for this
+ private static final String prefix = "[TrappedExit] ";
+
+ @Override
+ public void checkExit(int status) {
+ if (TrapSecurityManager.trapExitCode().get()) {
+ // using a RuntimeException and a prefix here instead of a custom
+ // exception type because this is thrown by the installed TrapSecurityManager
+ // but other versions of cbt need to be able to catch it, that do not have access
+ // to that version of the TrapSecurityManager class
+ throw new RuntimeException(prefix + status);
+ }
+ super.checkExit(status);
+ }
+
+ public static boolean isTrappedExit(Throwable t) {
+ return t instanceof RuntimeException
+ && t.getMessage() != null
+ && t.getMessage().startsWith(prefix);
+ }
+
+ public static int exitCode(Throwable t) {
+ assert (isTrappedExit(t));
+ return Integer.parseInt(t.getMessage().substring(prefix.length()));
+ }
+}
diff --git a/libraries/common-0/TrapSystemExit.java b/libraries/common-0/TrapSystemExit.java
new file mode 100644
index 0000000..86bc880
--- /dev/null
+++ b/libraries/common-0/TrapSystemExit.java
@@ -0,0 +1,35 @@
+package cbt.reflect;
+
+import java.security.*;
+import java.lang.reflect.InvocationTargetException;
+
+public abstract class TrapSystemExit<T> {
+ public static SecurityManager createSecurityManager(SecurityManager delegateTo) {
+ return new TrapSecurityManager(delegateTo);
+ }
+
+ public static <T> T run(TrapSystemExit<T> runnable) throws Throwable {
+ boolean trapExitCodeBefore = TrapSecurityManager.trapExitCode().get();
+ try {
+ TrapSecurityManager.trapExitCode().set(true);
+ return runnable.run();
+ } catch (InvocationTargetException exception) {
+ Throwable cause = exception.getCause();
+ if (TrapSecurityManager.isTrappedExit(cause)) {
+ return runnable.wrap(TrapSecurityManager.exitCode(cause));
+ }
+ throw exception;
+ } catch (Exception exception) {
+ if (TrapSecurityManager.isTrappedExit(exception)) {
+ return runnable.wrap(TrapSecurityManager.exitCode(exception));
+ }
+ throw exception;
+ } finally {
+ TrapSecurityManager.trapExitCode().set(trapExitCodeBefore);
+ }
+ }
+
+ public abstract T run() throws Throwable;
+
+ public abstract T wrap(int exitCode);
+}
diff --git a/libraries/common-0/build/build.scala b/libraries/common-0/build/build.scala
new file mode 100644
index 0000000..0a4e5cb
--- /dev/null
+++ b/libraries/common-0/build/build.scala
@@ -0,0 +1,7 @@
+package cbt_build.common_0
+import cbt._
+import cbt_internal._
+class Build(val context: Context) extends Library{
+ override def inceptionYear = 2017
+ override def description = "classes shared by multiple cbt libraries and needed in stage 0"
+}
diff --git a/libraries/common-0/build/build/build.scala b/libraries/common-0/build/build/build.scala
new file mode 100644
index 0000000..d3f98ce
--- /dev/null
+++ b/libraries/common-0/build/build/build.scala
@@ -0,0 +1,5 @@
+package cbt_build.reflect.build
+import cbt._
+class Build(val context: Context) extends BuildBuild with CbtInternal{
+ override def dependencies = super.dependencies :+ cbtInternal.library
+}
diff --git a/libraries/common-1/ExitCode.java b/libraries/common-1/ExitCode.java
new file mode 100644
index 0000000..1c16f67
--- /dev/null
+++ b/libraries/common-1/ExitCode.java
@@ -0,0 +1,24 @@
+package cbt;
+/*
+public class ExitCode{
+ public int integer;
+ public ExitCode(int integer){
+ this.integer = integer;
+ }
+ public static ExitCode apply(int integer){
+ return new ExitCode( integer );
+ }
+ public static ExitCode Success = new ExitCode(0);
+ public static ExitCode Failure = new ExitCode(1);
+
+ @Override
+ public boolean equals(Object other){
+ return (other instanceof ExitCode) && ((ExitCode) other).integer == integer;
+ }
+ @Override
+ public int hashCode(){
+ return integer;
+ }
+}
+
+*/
diff --git a/libraries/common-1/ExitCode.scala b/libraries/common-1/ExitCode.scala
new file mode 100644
index 0000000..41d9f3f
--- /dev/null
+++ b/libraries/common-1/ExitCode.scala
@@ -0,0 +1,11 @@
+package cbt
+// CLI interop
+case class ExitCode( integer: Int ) extends interfaces.ExitCode {
+ 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 )
+}
+
diff --git a/libraries/common-1/build/build.scala b/libraries/common-1/build/build.scala
new file mode 100644
index 0000000..247fd01
--- /dev/null
+++ b/libraries/common-1/build/build.scala
@@ -0,0 +1,8 @@
+package cbt_build.common_1
+import cbt._
+import cbt_internal._
+class Build(val context: Context) extends Library{
+ override def inceptionYear = 2017
+ override def description = "classes shared by multiple cbt libraries and needed in stage 1"
+ override def dependencies = super.dependencies :+ libraries.common_0 :+ libraries.interfaces
+}
diff --git a/libraries/common-1/build/build/build.scala b/libraries/common-1/build/build/build.scala
new file mode 100644
index 0000000..d3f98ce
--- /dev/null
+++ b/libraries/common-1/build/build/build.scala
@@ -0,0 +1,5 @@
+package cbt_build.reflect.build
+import cbt._
+class Build(val context: Context) extends BuildBuild with CbtInternal{
+ override def dependencies = super.dependencies :+ cbtInternal.library
+}
diff --git a/libraries/common-1/common_1.scala b/libraries/common-1/common_1.scala
new file mode 100644
index 0000000..66da224
--- /dev/null
+++ b/libraries/common-1/common_1.scala
@@ -0,0 +1,34 @@
+package cbt.common_1
+import cbt.ExitCode
+object `package` extends Module {
+ implicit class CbtExitCodeOps( val exitCode: ExitCode ) extends AnyVal with ops.CbtExitCodeOps
+ implicit class TypeInferenceSafeEquals[T]( val value: T ) extends AnyVal with ops.TypeInferenceSafeEquals[T]
+ implicit class CbtBooleanOps( val condition: Boolean ) extends AnyVal with ops.CbtBooleanOps
+ implicit class CbtStringOps( val string: String ) extends AnyVal with ops.CbtStringOps
+}
+
+package ops {
+ trait CbtExitCodeOps extends Any {
+ def exitCode: ExitCode
+ def ||( other: => ExitCode ) = if ( exitCode == ExitCode.Success ) exitCode else other
+ def &&( other: => ExitCode ) = if ( exitCode != ExitCode.Success ) exitCode else other
+ }
+ trait TypeInferenceSafeEquals[T] extends Any {
+ def value: T
+ /** if you don't manually upcast, this will catch comparing different types */
+ def ===( other: T ) = value == other
+ def =!=( other: T ) = value != other // =!= instead of !==, because it has better precedence
+ }
+ trait CbtBooleanOps extends Any {
+ def condition: Boolean
+ def option[T]( value: => T ): Option[T] = if ( condition ) Some( value ) else None
+ }
+ trait CbtStringOps extends Any {
+ def string: String
+ def escape = string.replace( "\\", "\\\\" ).replace( "\"", "\\\"" )
+ def quote = s""""$escape""""
+ def ~( right: String ): String = string + right
+ }
+}
+
+trait Module
diff --git a/libraries/file/build/build.scala b/libraries/file/build/build.scala
new file mode 100644
index 0000000..d9017a1
--- /dev/null
+++ b/libraries/file/build/build.scala
@@ -0,0 +1,8 @@
+package cbt_build.reflect
+import cbt._
+import cbt_internal._
+class Build(val context: Context) extends Library{
+ override def inceptionYear = 2017
+ override def description = "helpers to work with java io and nio"
+ override def dependencies = super.dependencies :+ libraries.common_1
+}
diff --git a/libraries/file/build/build/build.scala b/libraries/file/build/build/build.scala
new file mode 100644
index 0000000..d3f98ce
--- /dev/null
+++ b/libraries/file/build/build/build.scala
@@ -0,0 +1,5 @@
+package cbt_build.reflect.build
+import cbt._
+class Build(val context: Context) extends BuildBuild with CbtInternal{
+ override def dependencies = super.dependencies :+ cbtInternal.library
+}
diff --git a/libraries/file/file.scala b/libraries/file/file.scala
new file mode 100644
index 0000000..f20c9a8
--- /dev/null
+++ b/libraries/file/file.scala
@@ -0,0 +1,112 @@
+package cbt.file
+import java.io.File
+import java.nio.file.Files._
+import java.nio.file.StandardCopyOption._
+import cbt.common_1._
+object `package` extends Module {
+ implicit class CbtFileOps( val file: File ) extends ops.CbtFileOps
+}
+
+package ops {
+ trait CbtFileOps extends Any {
+ def file: File
+ def ++( s: String ): File = {
+ if ( s endsWith "/" ) throw new Exception(
+ """Trying to append a String that ends in "/" to a File would loose the trailing "/". Use .stripSuffix("/") if you need to."""
+ )
+ new File( string + s ) // PERFORMANCE HOTSPOT
+ }
+ def /( s: String ): File = {
+ new File( file, s )
+ }
+ def parent = realpath( file / ".." )
+ def string = file.toString
+
+ def listOrFail: Seq[File] = Option( file.listFiles ).getOrElse( throw new Exception( "no such file: " + file ) ).toVector
+ def listRecursive: Seq[File] =
+ file +: (
+ if ( file.isDirectory ) file.listOrFail.flatMap( _.listRecursive ).toVector else Vector[File]()
+ )
+
+ def lastModifiedRecursive = listRecursive.map( _.lastModified ).max
+
+ def readAsString = new String( readAllBytes( file.toPath ) )
+
+ def quote = s"""new _root_.java.io.File(${string.quote})"""
+ }
+}
+
+trait Module {
+ def realpath( name: File ) = new File( java.nio.file.Paths.get( name.getAbsolutePath ).normalize.toString )
+ def transformFiles( files: Seq[File], transform: String => String ): Seq[File] = {
+ transformFilesOrError( files, s => Right( transform( s ) ) )._1
+ }
+
+ def transformFilesOrError[T]( files: Seq[File], transform: String => Either[T, String] ): ( Seq[File], Seq[( File, T )] ) = {
+ val results = files.map { file =>
+ val string = file.readAsString
+ transform( string ).left.map(
+ file -> _
+ ).right.map(
+ replaced =>
+ if ( string != replaced ) {
+ val tmpFile = file ++ ".cbt-tmp"
+ assert( !tmpFile.exists )
+ write( tmpFile.toPath, replaced.getBytes )
+ move( tmpFile.toPath, file.toPath, REPLACE_EXISTING )
+ Some( file )
+ } else None
+ )
+ }
+
+ ( results.map( _.right.toOption ).flatten.flatten, results.map( _.left.toOption ).flatten )
+ }
+
+ def autoRelative(
+ files: Seq[File], collector: PartialFunction[( File, String ), String] = { case ( _, r ) => r },
+ allowDuplicates: Boolean = false
+ ): Seq[( File, String )] = {
+ val map = files.sorted.flatMap { base =>
+ val b = base.getCanonicalFile.string
+ if ( base.isDirectory ) {
+ base.listRecursive.map { f =>
+ f -> f.getCanonicalFile.string.stripPrefix( b ).stripPrefix( File.separator )
+ }
+ } else {
+ Seq( base -> base.getName )
+ }
+ }.collect {
+ case v @ ( file, _ ) if collector.isDefinedAt( v ) => file -> collector( v )
+ }
+ if ( !allowDuplicates ) {
+ val relatives = map.unzip._2
+ val duplicateFiles = ( relatives diff relatives.distinct ).distinct
+ assert(
+ duplicateFiles.isEmpty, {
+ val rs = relatives.toSet
+ "Conflicting:\n\n" +
+ map.filter( rs contains _._2 ).groupBy( _._2 ).mapValues( _.map( _._1 ).sorted ).toSeq.sortBy( _._1 ).map {
+ case ( name, files ) => s"$name:\n" ++ files.mkString( "\n" )
+ }.mkString( "\n\n" )
+ }
+ )
+ }
+ map
+ }
+
+ /* recursively deletes folders*/
+ def deleteRecursive( file: File ): 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( File.separator.replace( "\\", "\\\\" ) ).size > 4, "deleteRecursive requires absolute path of at least depth 4" )
+ assert( !file.listRecursive.exists( _.isHidden ), "deleteRecursive requires no files to be hidden" )
+ assert( file.listRecursive.forall( _.canWrite ), "deleteRecursive requires all files to be writable" )
+ if ( file.isDirectory ) {
+ file.listOrFail.map( deleteRecursive )
+ }
+ file.delete
+ }
+}
diff --git a/libraries/interfaces/ExitCode.java b/libraries/interfaces/ExitCode.java
new file mode 100644
index 0000000..f29d8c0
--- /dev/null
+++ b/libraries/interfaces/ExitCode.java
@@ -0,0 +1,4 @@
+package cbt.interfaces;
+public interface ExitCode{
+ public int integer();
+}
diff --git a/libraries/proguard/Proguard.scala b/libraries/proguard/Proguard.scala
index 7d57a77..ec5c2e9 100644
--- a/libraries/proguard/Proguard.scala
+++ b/libraries/proguard/Proguard.scala
@@ -29,8 +29,8 @@ object ProGuard {
}
case class ProGuard[T](
main: Seq[String] => Int,
- T: Seq[File] => T,
- log: String => Unit = _ => ()
+ T: Seq[File] => T,
+ log: String => Unit = _ => ()
) {
/**
@@ -160,9 +160,9 @@ case class ProGuard[T](
private object argsFor {
def apply[T: argsFor]( value: T ) = implicitly[argsFor[T]].apply( value )
implicit object SeqFile extends argsFor[Seq[File]]( v => Some( Seq( v.map( _.getPath ).mkString( ":" ) ) ) )
- implicit object File extends argsFor[File]( v => Some( Seq( v.getPath ) ) )
- implicit object String extends argsFor[String]( v => Some( Seq( v ) ) )
- implicit object Int extends argsFor[Int]( i => Some( Seq( i.toString ) ) )
+ implicit object File extends argsFor[File]( v => Some( Seq( v.getPath ) ) )
+ implicit object String extends argsFor[String]( v => Some( Seq( v ) ) )
+ implicit object Int extends argsFor[Int]( i => Some( Seq( i.toString ) ) )
implicit object Boolean
extends argsFor[Boolean]( {
case false => None
diff --git a/libraries/proguard/build/build.scala b/libraries/proguard/build/build.scala
index 3ca38b5..754de20 100644
--- a/libraries/proguard/build/build.scala
+++ b/libraries/proguard/build/build.scala
@@ -1,11 +1,12 @@
package cbt_build.proguard
import cbt._
+import cbt_internal._
import java.nio.file.Files._
import java.net._
import java.io._
import scala.xml._
-class Build(val context: Context) extends Scalafmt{
+class Build(val context: Context) extends Library{
def description: String = "Type-safe scala wrapper to interfaces with ProGuard.main runner"
def inceptionYear = 2017
@@ -14,11 +15,6 @@ class Build(val context: Context) extends Scalafmt{
compile
}
- override def scalafmt = super.scalafmt.copy(
- config = Scalafmt.cbtRecommendedConfig,
- whiteSpaceInParenthesis = true
- )
-
override def compile = {
// currently suffers from non-deterministic formatting. Try a few times to reproduce commit state.
val formatted = scalafmt.apply.map(_.string).mkString("\n")
diff --git a/libraries/proguard/build/build/build.scala b/libraries/proguard/build/build/build.scala
index 7928cfa..5057404 100644
--- a/libraries/proguard/build/build/build.scala
+++ b/libraries/proguard/build/build/build.scala
@@ -1,13 +1,11 @@
-package cbt_build.proguard.build
+package proguard_build.build
import cbt._
-class Build(val context: Context) extends BuildBuild{
+class Build(val context: Context) extends BuildBuild with CbtInternal{
override def dependencies = (
super.dependencies ++ // don't forget super.dependencies here for scala-library, etc.
Resolver( mavenCentral, sonatypeReleases ).bind(
ScalaDependency("org.scala-lang.modules","scala-xml","1.0.5"),
"org.ccil.cowan.tagsoup" % "tagsoup" % "1.2.1"
- ) ++ Seq(
- plugins.scalafmt
- )
+ ) ++ Seq( cbtInternal.library )
)
}
diff --git a/libraries/reflect/StaticMethod.scala b/libraries/reflect/StaticMethod.scala
new file mode 100644
index 0000000..e2a0d07
--- /dev/null
+++ b/libraries/reflect/StaticMethod.scala
@@ -0,0 +1,4 @@
+package cbt.reflect
+case class StaticMethod[Arg, Result]( function: Arg => Result, name: String ) extends ( Arg => Result ) {
+ def apply( arg: Arg ): Result = function( arg )
+}
diff --git a/libraries/reflect/build/build.scala b/libraries/reflect/build/build.scala
new file mode 100644
index 0000000..5c27090
--- /dev/null
+++ b/libraries/reflect/build/build.scala
@@ -0,0 +1,8 @@
+package cbt_build.reflect
+import cbt._
+import cbt_internal._
+class Build(val context: Context) extends Library{
+ override def inceptionYear = 2017
+ override def description = "discover classes on your classpath and invoke methods reflectively, preventing System.exit"
+ override def dependencies = super.dependencies :+ libraries.file
+}
diff --git a/libraries/reflect/build/build/build.scala b/libraries/reflect/build/build/build.scala
new file mode 100644
index 0000000..d3f98ce
--- /dev/null
+++ b/libraries/reflect/build/build/build.scala
@@ -0,0 +1,5 @@
+package cbt_build.reflect.build
+import cbt._
+class Build(val context: Context) extends BuildBuild with CbtInternal{
+ override def dependencies = super.dependencies :+ cbtInternal.library
+}
diff --git a/libraries/reflect/reflect.scala b/libraries/reflect/reflect.scala
new file mode 100644
index 0000000..c18d926
--- /dev/null
+++ b/libraries/reflect/reflect.scala
@@ -0,0 +1,177 @@
+package cbt.reflect
+
+import java.io.File
+import java.lang.reflect.{ Constructor, Method, InvocationTargetException, Modifier }
+
+import scala.reflect.ClassTag
+
+import cbt.ExitCode
+import cbt.file._
+
+object `package` extends Module {
+ implicit class CbtClassOps( val c: Class[_] ) extends AnyVal with ops.CbtClassOps
+ implicit class CbtConstructorOps( val c: Constructor[_] ) extends AnyVal with ops.CbtConstructorOps
+ implicit class CbtMethodOps( val m: Method ) extends AnyVal with ops.CbtMethodOps
+}
+
+package ops {
+ trait CbtClassOps extends Any {
+ def c: Class[_]
+ def name = c.getName
+ def method( name: String, parameterTypes: Class[_]* ) = c.getMethod( name, parameterTypes: _* )
+ def methods = c.getMethods
+ def modifiers = c.getModifiers
+ def constructors = c.getConstructors
+ def declaredMethods = c.getDeclaredMethods
+ def isInterface = Modifier.isInterface( c.getModifiers )
+ def isAbstract = Modifier.isAbstract( c.getModifiers )
+ def isPrivate = Modifier.isPrivate( c.getModifiers )
+ def isProtected = Modifier.isProtected( c.getModifiers )
+ def isPublic = Modifier.isPublic( c.getModifiers )
+ def isFinal = Modifier.isFinal( c.getModifiers )
+ }
+ trait CbtConstructorOps extends Any {
+ def c: Constructor[_]
+ def parameterTypes = c.getParameterTypes
+ }
+ trait CbtMethodOps extends Any {
+ def m: Method
+ def name = m.getName
+ def declaringClass = m.getDeclaringClass
+ def modifiers = m.getModifiers
+ def parameters = m.getParameters
+ def parameterTypes = m.getParameterTypes
+ def returnType = m.getReturnType
+
+ def isAbstract = Modifier.isAbstract( m.getModifiers )
+ def isFinal = Modifier.isFinal( m.getModifiers )
+ def isNative = Modifier.isNative( m.getModifiers )
+ def isPrivate = Modifier.isPrivate( m.getModifiers )
+ def isProtected = Modifier.isProtected( m.getModifiers )
+ def isPublic = Modifier.isPublic( m.getModifiers )
+ def isStatic = Modifier.isStatic( m.getModifiers )
+ def isStrict = Modifier.isStrict( m.getModifiers )
+ def isSynchronized = Modifier.isSynchronized( m.getModifiers )
+ def isTransient = Modifier.isTransient( m.getModifiers )
+ def isVolatile = Modifier.isVolatile( m.getModifiers )
+ }
+}
+trait Module {
+ def runMain( cls: Class[_], args: Seq[String] ): ExitCode =
+ discoverStaticExitMethodForced[Array[String]]( cls, "main" ).apply( args.to )
+
+ def discoverMain( cls: Class[_] ): Option[StaticMethod[Seq[String], ExitCode]] = {
+ discoverStaticExitMethod[Array[String]]( cls, "main" )
+ .map( f =>
+ f.copy(
+ function = ( arg: Seq[String] ) => f.function( arg.to )
+ ) )
+ }
+
+ /** ignoreMissingClasses allows ignoring other classes root directories which are subdirectories of this one */
+ def iterateClasses(
+ classesRootDirectory: File,
+ classLoader: ClassLoader,
+ ignoreMissingClasses: Boolean
+ ): Seq[Class[_]] =
+ iterateClassNames( classesRootDirectory )
+ .map { name =>
+ try {
+ classLoader.loadClass( name )
+ } catch {
+ case e: ClassNotFoundException if ignoreMissingClasses => null
+ case e: NoClassDefFoundError if ignoreMissingClasses => null
+ }
+ }
+ .filterNot( ignoreMissingClasses && _ == null )
+
+ /** 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] =
+ classesRootDirectory.listRecursive
+ .filter( _.isFile )
+ .map( _.getPath )
+ .collect {
+ // no $ to avoid inner classes
+ case path if !path.contains( "$" ) && path.endsWith( ".class" ) =>
+ path
+ .stripSuffix( ".class" )
+ .stripPrefix( classesRootDirectory.getPath )
+ .stripPrefix( File.separator ) // 1 for the slash
+ .replace( File.separator, "." )
+ }
+
+ def discoverStaticExitMethodForced[Arg: ClassTag](
+ cls: Class[_], name: String
+ ): StaticMethod[Arg, ExitCode] = {
+ val f = discoverStaticMethodForced[Arg, Unit]( cls, name )
+ f.copy(
+ function = arg => trapExitCode { f.function( arg ); ExitCode.Success }
+ )
+ }
+
+ def discoverStaticMethodForced[Arg, Result](
+ cls: Class[_], name: String
+ )(
+ implicit
+ Result: ClassTag[Result], Arg: ClassTag[Arg]
+ ): StaticMethod[Arg, Result] = {
+ val m = cls.method( name, Arg.runtimeClass )
+ assert( Result.runtimeClass.isAssignableFrom( m.returnType ) )
+ typeStaticMethod( m )
+ }
+
+ def discoverStaticExitMethod[Arg: ClassTag](
+ cls: Class[_], name: String
+ ): Option[StaticMethod[Arg, ExitCode]] =
+ discoverStaticMethod[Arg, Unit]( cls, name ).map( f =>
+ f.copy(
+ function = arg => trapExitCode { f.function( arg ); ExitCode.Success }
+ ) )
+
+ def discoverStaticMethod[Arg, Result](
+ cls: Class[_], name: String
+ )(
+ implicit
+ Result: ClassTag[Result], Arg: ClassTag[Arg]
+ ): Option[StaticMethod[Arg, Result]] = {
+ Some( cls )
+ .filterNot( _.isAbstract )
+ .filterNot( _.isInterface )
+ .flatMap( _
+ .getMethods
+ .find( m =>
+ !m.isAbstract
+ && m.isPublic
+ && m.name == name
+ && m.parameterTypes.toList == List( Arg.runtimeClass )
+ && Result.runtimeClass.isAssignableFrom( m.returnType ) ) )
+ .map( typeStaticMethod )
+ }
+
+ def typeStaticMethod[Arg, Result]( method: Method ): StaticMethod[Arg, Result] = {
+ val m = method
+ val instance =
+ if ( m.isStatic ) null
+ else m.declaringClass.newInstance // Dottydoc needs this. It's main method is not static.
+ StaticMethod(
+ arg => m.invoke( instance, arg.asInstanceOf[AnyRef] ).asInstanceOf[Result],
+ m.getClass.name.stripSuffix( "$" ) ++ "." ++ m.name ++ "( "
+ ++ m.parameters.map( _.getType.name ).mkString( ", " )
+ ++ " )"
+ )
+ }
+
+ def trapExitCodeOrValue[T]( result: => T, i: Int = 5 ): Either[ExitCode, T] = {
+ TrapSystemExit.run(
+ new TrapSystemExit[Either[ExitCode, T]] {
+ def run = Right( result )
+ def wrap( exitCode: Int ) = Left( new ExitCode( exitCode ) )
+ }
+ )
+ }
+
+ def trapExitCode( code: => ExitCode ): ExitCode =
+ trapExitCodeOrValue( code ).merge
+}