aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Vogt <oss.nsp@cvogt.org>2016-11-07 02:21:50 -0500
committerChristopher Vogt <oss.nsp@cvogt.org>2016-11-07 02:21:50 -0500
commitc6b9a480879c101028b20b9cc8716b8ffa773630 (patch)
treefff15c1d6595455f6994f5793b63b08c5bf24d4a
parentc89f87c9c9a0c7b256f225e37c55cb34f060aa6c (diff)
parentfd849d293448d55c6bcb6f8440f44838b51fc860 (diff)
downloadcbt-c6b9a480879c101028b20b9cc8716b8ffa773630.tar.gz
cbt-c6b9a480879c101028b20b9cc8716b8ffa773630.tar.bz2
cbt-c6b9a480879c101028b20b9cc8716b8ffa773630.zip
Merge remote-tracking branch 'origin/master' into integrate-eval
-rw-r--r--.gitignore14
-rw-r--r--DEVELOPER_GUIDE.txt41
-rw-r--r--LICENSE.txt13
-rw-r--r--README.md457
-rw-r--r--TODO.txt34
-rw-r--r--build/build.scala31
-rwxr-xr-xcbt240
-rw-r--r--cbt.bat196
-rw-r--r--circle.yml17
-rw-r--r--compatibility/ArtifactInfo.java7
-rw-r--r--compatibility/BuildInterface.java10
-rw-r--r--compatibility/Context.java22
-rw-r--r--compatibility/Dependency.java10
-rw-r--r--compatibility/Result.java11
-rw-r--r--coursier/Coursier.scala47
-rw-r--r--examples/build-info-example/BuildInfo.scala8
-rw-r--r--examples/build-info-example/Main.scala9
-rw-r--r--examples/build-info-example/Readme.md3
-rw-r--r--examples/build-info-example/build/build.scala27
-rw-r--r--examples/dotty-example/README.md3
-rw-r--r--examples/dotty-example/build/build.scala2
-rw-r--r--examples/dotty-example/src/Main.scala12
-rw-r--r--examples/multi-project-example/build/build.scala10
-rw-r--r--examples/multi-project-example/build/build/build.scala10
-rw-r--r--examples/multi-project-example/common/SomeSharedClass.scala1
-rw-r--r--examples/multi-project-example/common/build/build.scala3
-rw-r--r--examples/multi-project-example/common/build/build/build.scala10
-rw-r--r--examples/multi-project-example/shared-build/SharedCbtBuild.scala4
-rw-r--r--examples/multi-project-example/shared-build/build/build.scala6
-rw-r--r--examples/multi-project-example/sub1/SomeConcreteClass.scala1
-rw-r--r--examples/multi-project-example/sub1/build/build.scala10
-rw-r--r--examples/multi-project-example/sub1/build/build/build.scala10
-rw-r--r--examples/multi-project-example/sub2/SomeOtherConcreteClass.scala1
-rw-r--r--examples/multi-project-example/sub2/build/build.scala10
-rw-r--r--examples/multi-project-example/sub2/build/build/build.scala10
-rw-r--r--examples/resources-example/build/build.scala21
-rw-r--r--examples/resources-example/build/build/build.scala20
-rw-r--r--examples/resources-example/my-resources/foo.text1
-rw-r--r--examples/resources-example/resources/foo.text1
-rw-r--r--examples/resources-example/src/Main.scala27
-rw-r--r--examples/scalafmt-example/.scalafmt.conf11
-rw-r--r--examples/scalafmt-example/README.md16
-rw-r--r--examples/scalafmt-example/build/build.scala26
-rw-r--r--examples/scalafmt-example/build/build/build.scala5
-rw-r--r--examples/scalafmt-example/resources/reference.conf8
-rw-r--r--examples/scalafmt-example/src/Main.scala16
-rw-r--r--examples/scalajs-react-example/README.md11
-rw-r--r--examples/scalajs-react-example/js/App.scala15
-rw-r--r--examples/scalajs-react-example/js/Pictures.scala102
-rw-r--r--examples/scalajs-react-example/js/build/build.scala22
-rw-r--r--examples/scalajs-react-example/js/build/build/build.scala4
-rw-r--r--examples/scalajs-react-example/jvm/build/build.scala6
-rw-r--r--examples/scalajs-react-example/server/app.js39
-rw-r--r--examples/scalajs-react-example/server/package.json14
-rw-r--r--examples/scalajs-react-example/server/public/index.html18
-rw-r--r--examples/scalajs-react-example/shared/Picture.scala3
-rw-r--r--examples/scalariform-example/README.md16
-rw-r--r--examples/scalariform-example/build/build.scala29
-rw-r--r--examples/scalariform-example/build/build/build.scala5
-rw-r--r--examples/scalariform-example/resources/reference.conf8
-rw-r--r--examples/scalariform-example/src/Main.scala11
-rw-r--r--examples/scalatest-example/build/build.scala9
-rw-r--r--examples/scalatest-example/build/build/build.scala8
-rw-r--r--examples/scalatest-example/src/main/scala/Hello.scala7
-rw-r--r--examples/scalatest-example/src/test/scala/Test.scala23
-rw-r--r--examples/simple-example/build/build.scala20
-rw-r--r--examples/simple-example/build/build/build.scala20
-rw-r--r--examples/simple-example/src/Main.scala3
-rw-r--r--examples/sonatype-release-example/README.md1
-rw-r--r--examples/sonatype-release-example/build/build.scala31
-rw-r--r--examples/sonatype-release-example/build/build/build.scala5
-rw-r--r--examples/sonatype-release-example/src/Main.scala3
-rw-r--r--examples/uber-jar-example/README.md41
-rw-r--r--examples/uber-jar-example/build/build.scala16
-rw-r--r--examples/uber-jar-example/build/build/build.scala5
-rw-r--r--examples/uber-jar-example/src/Main.scala21
-rw-r--r--examples/uber-jar-example/src/com/github/someguy/ImportantLib.scala11
-rw-r--r--examples/wartremover-example/build/build.scala9
-rw-r--r--examples/wartremover-example/build/build/build.scala5
-rw-r--r--examples/wartremover-example/src/Main.scala5
-rw-r--r--nailgun_launcher/CbtURLClassLoader.java59
-rw-r--r--nailgun_launcher/ClassLoaderCache2.java37
-rw-r--r--nailgun_launcher/EarlyDependencies.java143
-rw-r--r--nailgun_launcher/MultiClassLoader2.java46
-rw-r--r--nailgun_launcher/NailgunLauncher.java200
-rw-r--r--nailgun_launcher/ProxySecurityManager.java102
-rw-r--r--nailgun_launcher/Stage0Lib.java197
-rw-r--r--nailgun_launcher/ThreadLocalOutputStream.java34
-rw-r--r--nailgun_launcher/TrapSecurityManager.java83
-rw-r--r--plugins/readme.txt5
-rw-r--r--plugins/sbt_layout/SbtLayout.scala10
-rw-r--r--plugins/sbt_layout/build/build.scala2
-rw-r--r--plugins/scalafmt/Scalafmt.scala99
-rw-r--r--plugins/scalafmt/build/build.scala12
-rw-r--r--plugins/scalajs/ScalaJsBuild.scala52
-rw-r--r--plugins/scalajs/ScalaJsLib.scala51
-rw-r--r--plugins/scalajs/build/build.scala3
-rw-r--r--plugins/scalariform/Scalariform.scala61
-rw-r--r--plugins/scalariform/build/build.scala9
-rw-r--r--plugins/scalatest/ScalaTest.scala43
-rw-r--r--plugins/scalatest/build/build.scala9
-rw-r--r--plugins/sonatype-release/build/build.scala3
-rw-r--r--plugins/sonatype-release/src/SonatypeRelease.scala51
-rw-r--r--plugins/sonatype-release/src/sonatype/HttpUtils.scala65
-rw-r--r--plugins/sonatype-release/src/sonatype/SonatypeHttpApi.scala215
-rw-r--r--plugins/sonatype-release/src/sonatype/SonatypeLib.scala148
-rw-r--r--plugins/sonatype-release/src/sonatype/models.scala31
-rw-r--r--plugins/uber-jar/build/build.scala3
-rw-r--r--plugins/uber-jar/src/UberJar.scala124
-rw-r--r--plugins/wartremover/WartRemover.scala51
-rw-r--r--plugins/wartremover/build/build.scala9
-rw-r--r--realpath/realpath.c35
-rwxr-xr-xrealpath/realpath.sh18
-rwxr-xr-xshell-integration/cbt-completions.bash13
-rw-r--r--shell-integration/cbt-completions.fish1
-rwxr-xr-xshell-integration/cbt-completions.zsh7
-rw-r--r--stage1/Cache.scala14
-rw-r--r--stage1/CachingClassLoader.scala17
-rw-r--r--stage1/CbtPaths.scala15
-rw-r--r--stage1/ClassLoaderCache.scala24
-rw-r--r--stage1/ClassPath.scala28
-rw-r--r--stage1/ContextImplementation.scala22
-rw-r--r--stage1/KeyLockedLazyCache.scala62
-rw-r--r--stage1/MavenRepository.scala9
-rw-r--r--stage1/MultiClassLoader.scala42
-rw-r--r--stage1/PoorMansProfiler.scala23
-rw-r--r--stage1/Stage1.scala195
-rw-r--r--stage1/Stage1Lib.scala447
-rw-r--r--stage1/URLClassLoader.scala47
-rw-r--r--stage1/cbt.scala99
-rw-r--r--stage1/constants.scala7
-rw-r--r--stage1/logger.scala61
-rw-r--r--stage1/resolver.scala401
-rw-r--r--stage2/BasicBuild.scala247
-rw-r--r--stage2/BuildBuild.scala81
-rw-r--r--stage2/BuildDependency.scala36
-rw-r--r--stage2/GitDependency.scala82
-rw-r--r--stage2/Lib.scala521
-rw-r--r--stage2/License.scala57
-rw-r--r--stage2/NameTransformer.scala161
-rw-r--r--stage2/PackageJars.scala33
-rw-r--r--stage2/Plugin.scala4
-rw-r--r--stage2/Publish.scala49
-rw-r--r--stage2/SbtDependencyDsl.scala15
-rw-r--r--stage2/Scaffold.scala52
-rw-r--r--stage2/Stage2.scala90
-rw-r--r--stage2/ToolsStage2.scala12
-rw-r--r--stage2/ToolsTasks.scala150
-rw-r--r--stage2/plugins/AdvancedFlags.scala10
-rw-r--r--stage2/plugins/Dotty.scala172
-rw-r--r--stage2/plugins/GithubPom.scala11
-rw-r--r--stage2/plugins/ScalaParadise.scala29
-rw-r--r--stage2/plugins/readme.txt4
-rw-r--r--stage2/pom.scala6
-rw-r--r--test/build/build.scala4
-rw-r--r--test/empty-build-file/Main.scala5
-rw-r--r--test/empty-build-file/build/build.scala0
-rw-r--r--test/empty-build/Main.scala5
-rw-r--r--test/empty-build/build/build/dummy0
-rw-r--r--test/forgot-extend/build/build.scala2
-rw-r--r--test/library-test/Foo.scala4
-rw-r--r--test/library-test/build/build.scala25
-rw-r--r--test/multi-build/build/build.scala7
-rw-r--r--test/multi-build/code.scala5
-rw-r--r--test/multi-build/sub1/code.scala4
-rw-r--r--test/multi-build/sub2/code.scala4
-rw-r--r--test/no-build-file/Main.scala5
-rw-r--r--test/no-build-file/build/foo.scala0
-rw-r--r--test/nothing/placeholder0
-rw-r--r--test/simple-fixed-cbt/Main.scala6
-rw-r--r--test/simple-fixed-cbt/build/build.scala14
-rw-r--r--test/simple-fixed/Main.scala6
-rw-r--r--test/simple-fixed/build/build.scala29
-rw-r--r--test/simple/Main.scala6
-rw-r--r--test/simple/build/build.scala43
-rw-r--r--test/test.scala262
-rw-r--r--tools/gui/build/build.scala12
-rw-r--r--tools/gui/resources/template-project/build/build.scala5
-rw-r--r--tools/gui/resources/template-project/build/build/build.scala7
-rw-r--r--tools/gui/resources/template-project/src/main/scala/Main.scala5
-rw-r--r--tools/gui/resources/web/favicon.icobin0 -> 1150 bytes
-rw-r--r--tools/gui/resources/web/index.html42
-rw-r--r--tools/gui/resources/web/jquery-3.1.1.min.js4
-rw-r--r--tools/gui/resources/web/main.js129
-rw-r--r--tools/gui/resources/web/styles.css196
-rw-r--r--tools/gui/src/JettyServer.scala67
-rw-r--r--tools/gui/src/Main.scala85
-rw-r--r--tools/gui/src/ProjectBuilder.scala149
188 files changed, 8501 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..37ff16e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+config
+cache
+classes
+lib
+out
+scala_classes
+target/
+*.login
+cache_
+.idea
+realpath/realpath
+node_modules
+*fastopt*
+*fullopt*
diff --git a/DEVELOPER_GUIDE.txt b/DEVELOPER_GUIDE.txt
new file mode 100644
index 0000000..268852e
--- /dev/null
+++ b/DEVELOPER_GUIDE.txt
@@ -0,0 +1,41 @@
+Welcome developer.
+
+CBT has a very easy code base that's easy to master.
+Don't shy away from submiting PRs :). And because CBT bootstraps from source
+you already have the code there.
+
+The only tricky parts are class loading and cache invalidation. Most changes
+will not need to interfere with this though.
+
+The ./cbt bash script starts the process.
+
+You currently need javac, nailgun, gpg and realpath or gcc installed.
+
+If you have any troubles with class not found, method not found,
+abstract method error, NullPointerException, etc.
+To restart nailgun try `killall -KILL java` or `kill -kill (jps|grep nailgun|cut -f1 -d " " -)`.
+Or try `cbt kill` or `cbt direct <taskname>` to circumvent nailgun.
+It can also help to delete all target folders `find .|grep target\$|xargs rm -rf`
+inside of CBT. Or (almost never) the `cache/` directory.
+
+To edit/debug CBT in IntelliJ, add the whole directory as a new scala project.
+Add the source folders manually and exclude the nested target folders.
+
+CBT's directory structure
+
+cbt Shell script launching cbt. Can be symlinked.
+compatibility/ Java interfaces that all CBT versions are source compatible to. For communication
+ between composed builds of different versions.
+nailgun_launcher/ Self-contained helper that allows using Nailgun with minimal permanent classpath. (Is this actually needed?)
+realpath/ Self-contained realpath source code to correctly figure our CBTs home directory. (Open for replacement ideas.)
+stage1/ CBT's code that only relies only on Scala/Java built-ins. Contains a Maven resolver to download libs for stage2.
+stage2/ CBT's code that requires additional libs, e.g. barbary watchservice.
+test/ Unit tests that can serve as example builds
+sonatype.login Sonatype credentials for deployment. Not in git obviously.
+
+CBT follows an optimistic merging approach. (See http://hintjens.com/blog:106).
+We strongly suggest well polished PRs, but don't want to stall improvements
+by discussions about minor flaws. As long as the PR does not break anything and
+improves the product, we should merge it, polishing remaining things afterwards.
+
+On OSX `brew install coreutils` to have gdate and get nanosecond timings during bash script.
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d2cc0ea
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,13 @@
+Copyright 2016 Jan Christopher Vogt
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3d471bb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,457 @@
+[![Join us on gitter](http://badges.gitter.im/cvogt/cbt.png)](https://gitter.im/cvogt/cbt)
+
+(For a tutorial scroll down.)
+
+Chris' Build Tool (CBT) for Scala
+============================================
+
+Easy to learn and master, lightning fast and backed by a thriving community of enthusiasts and contributors. For talks, development roadmap, projects using cbt, etc see the wiki.
+
+What is CBT?
+----------------------------------------------
+CBT is a build tool meaning it helps orchestrating compilation, code and
+documentation generation, packaging, deployment and custom tooling for
+your project. It mainly targets Scala projects but is not exclusive to them.
+
+CBT builds are full programs written using vanilla Scala code.
+Familiar concepts make you feel right at home - build files are classes,
+tasks are defs, you customize using method overrides. You already know these
+things and everything behaves as expected. That way implementing any
+build related requirement becomes as easy as writing any other Scala code.
+
+CBT is simple in the sense that it uses very few concepts.
+A single build uses classes, defs and inheritance.
+Builds and binary dependencies can be composed to model
+modules depending on each other.
+
+CBT believes good integration with existing tools to be very
+helpful. In that spirit CBT aims for excellent integration with
+the command line and your shell.
+
+CBT considers source files to be an excellent way to distribute code
+and has first class support for source and git dependencies.
+
+How is CBT different from other build tools?
+----------------------------------------------
+Not all build tools allow you to write builds in a full programming language.
+CBT is based on the assumption that builds are complex enough problems to
+warrant this and abstraction and re-use is better handled through libraries
+rather than some restricted, declarative DSL. CBT shares this philosophy with SBT.
+(This also means that integration with external tools such as an IDE better happens
+programmatically through an api rather than a static data representation such as xml.)
+
+Like SBT, CBT chooses Scala as its language of choice, trying to appeal to
+Scala programmers allowing them to re-use their knowledge and the safety of the language.
+
+Unlike SBT 0.11 and later, CBT maps task execution to JVM method invocations.
+SBT implements its own self-contained task graph model and interpreter.
+This allows SBT to have its model exactly fit the requirements.
+CBT instead uses existing JVM concepts for the solution and adds
+custom concepts only when necessary. CBT assumes this to lead to better
+ease of use due to familarity and better integration with existing tools
+such as interactive debuggers or stack traces because CBT's task call stack
+IS the JVM call stack.
+
+SBT 0.7 shared this design decision as many may have forgotten.
+However, CBT is still quite a bit simpler than even SBT 0.7 as
+CBT gets away with fewer concepts. SBT 0.7 had notions of
+main vs test sources, multi-project builds, task-dependencies
+which weren't invocations and other concepts. CBT maps all of
+these to def invocations and build composition instead.
+
+System requirements
+-------------------
+
+CBT is best tested under OSX. People are also using it also under
+Ubuntu and Windows via cygwin. It should be easy to port CBT to
+other systems or drop the cygwin requirement. You will only
+have to touch the launcher bash or .bat scripts.
+Please contribute back if you fixed something :).
+
+You currently need javac and realpath or gcc installed.
+nailgun is optional for speedup. gpg is required only for publishing maven artifacts.
+
+Features
+--------
+
+CBT supports the basic needs for Scala builds right now:
+Compiling, running, testing, packaging, publishing local and to sonatype,
+scaladoc, maven dependencies, source dependencies (e.g. for modularized projects),
+triggering tasks on file changes, cross-compilation, reproducible builds.
+
+There is also a growing number of plugins in `plugins/` and `stage2/plugins/`,
+but some things you'd like may still be missing. Consider writing
+a plugin in that case. It's super easy, just a trait. Share it :).
+
+
+Tutorial
+--------
+
+This section explains how to get started with cbt step-by-step.
+There are also example projects with build files in examples/ and test/.
+
+### Installation
+
+If you haven't cloned cbt yet, clone it now. Cloning is how you install cbt.
+We know that's a bit unusual, but roll with it, there are good reasons :).
+Open a shell, cd to the directory where you want to install cbt and execute:
+
+```
+$ git clone git@github.com:cvogt/cbt.git
+```
+
+There are a bash script `cbt` and a `cbt.bat` in the checkout directory.
+Add one to your `$PATH`, e.g. symlink it from `~/bin/cbt`.
+
+Check that it works by calling `cbt`. You should see CBT compiling itself
+and showing a list of built-in tasks.
+
+Great, you're all set up. Now, let's use cbt for a new example project.
+Follow the below steps. (There is also an experimental GUI described
+later to create a project, but going through the steps this time will help
+you understand what exactly is going on.)
+
+### Creating your first project
+
+Create a new directory and cd into it. E.g. `my-project`.
+
+```
+$ mkdir my-project
+$ cd my-project
+```
+
+Let's create a tiny sample app. CBT can generate it for you. Just run:
+
+```
+$ cbt tools createMain
+```
+
+### Running your code
+
+Now there should be a file `Main.scala`, which prints `Hello World` when run.
+So let's run it:
+
+```
+$ cbt run
+```
+
+You should see how CBT first compiles your project, then runs it and prints
+`Hello World`. CBT created the file `Main.scala` top-level in your directory.
+You can alternatively place `.scala` or `.java` files in `src/`
+or any of its subdirectories.
+
+### Creating a build file
+
+Without a build file, CBT just uses some default build settings.
+Let's make the build more concrete by creating a build file.
+
+CBT can help you with that. Execute:
+
+```
+$ cbt tools createBuild
+```
+
+Now there should be a file `build/build.scala` with a sample `Build` class.
+
+Btw., a build file can have its own build and so on recursively like in SBT.
+When you create a file `build/build/build.scala` and change `Build` class in there
+to extend `BuildBuild`, it will be used to build your `build/build.scala`. You can
+add built-time dependencies like plugins this way.
+
+### Adding dependencies
+
+In the generated `build/build.scala` there are
+several examples for dependencies. We recommend using the constructor syntax
+`ScalaDependency` (for automatically adding the scala version to the artifact id)
+or `MavenDependency` (for leaving the artifact id as is). The SBT-Style `%`-DSL
+syntax is also supported for copy-and-paste convenience, but discouraged.
+
+Alright, let's enable the `override def dependencies`. Make sure to include
+`super.dependencies`, which currently only includes the Scala standard library.
+Add a dependency of your choice, start using it from `Main.scala` and `cbt run` again.
+
+As you can see CBT makes choice of the maven repository explicit. It does so for clarity.
+
+### Calling other tasks
+
+Tasks are just defs. You can call any public zero-arguments method of your
+`Build class or its parents straight from the command line. To see how it works
+let's call the compile task.
+
+```
+$ cbt compile
+```
+
+### Creating custom tasks
+
+In order to create a custom task, simply add a new def to your Build class, e.g.
+
+```
+class Build...{
+ ...
+ def foo = "asdf"
+}
+```
+
+Now call the def from the command line:
+
+```
+$ cbt foo
+```
+
+As you can see it prints `asdf`. Adding tasks is that easy.
+
+### Triggering tasks on file-changes
+
+When you call a task, you can prefix it with `loop`.
+CBT then watches the source files, the build files and even CBT's own
+source code and re-runs the task when anything changes. If necessary,
+this forces CBT to re-build itself, the project's dependencies and the project itself.
+
+Let's try it. Let's loop the run task. Call this from the shell:
+
+```
+$ cbt loop run
+```
+
+Now change `Main.scala and see how cbt picks it up and re-runs it.
+CBT is fast. It may already be done re-compiling and re-running before
+you managed to change windows back from your editor to the shell.
+
+Try changing the build file and see how CBT reacts to it as well.
+
+### Adding tests
+
+The simplest way to add tests is putting a few assertions into the previously
+created Main.scala and be done with it. Alternatively you can add a test
+framework plugin to your build file to use something more sophisticated.
+
+This however means that the class files of your tests will be included in the
+jar should you create one. If that's fine, you are done :). If it is not you
+need to create another project, which depends on your previous project. This
+project will be packaged separately or you can disable packaging there. Let's create
+such a project now.
+
+Your project containing tests can be anywhere but a recommended location is a
+sub-folder called `test/` in your main project. Let's create it and create a
+Main class and build file:
+
+```
+$ mkdir test
+$ cd test
+$ rm ../Main.scala
+$ cbt tools createMain
+$ cbt tools createBuild
+```
+
+We also deleted the main projects Main.scala, because now that we created a new one
+we would have two classes with the same name on the classpath which can be very confusing.
+
+Now that we have a Main file in our test project, we can add some assertions to it.
+In order for them to see the main projects code, we still need to do one more thing -
+add a `DirectoryDependency` to your test project's build file. There is a similar example
+in the generated build.scala. What you need is this:
+
+```
+override def dependencies = super.dependencies ++ Seq(
+ DirectoryDependency( projectDirectory ++ "/.." )
+)
+```
+
+This successfully makes your test project's code see the main projects code.
+Add some class to your main project, e.g. `case class Foo(i: Int = 5)`. Now
+put an assertion into the Main class of your test project, e.g.
+`assert(Foo().i == 5)` and hit `cbt run` inside your test project.
+
+Make sure you deleted your main projects class Main when running your tests.
+
+Congratulations, you successfully created a dependent project and ran your tests.
+
+### Multi-projects Builds
+
+A single build only handles a single project in CBT. So there isn't exactly
+such a things as a Multi-project Build. Instead you can simply write multiple
+projects that depend on each other. We have already done that with tests above,
+but you can do the exact same thing to modularize your project into multiple ones.
+
+### Reproducible builds
+
+To achieve reproducible builds, you'll need to tie your build files to a particular
+CBT-version. It doesn't matter what version of CBT you are actually running,
+as long as the `BuildInterface` is compatible (which should be true for a large number
+of versions and we may find a better solution long term. If you see a compile error
+during compilation of CBT itself that some method in BuildInterface was not
+implemented or incorrectly implemented, you may be running an incompatible CBT
+version. We'll try to fix that later, but for now you might have to checkout
+the required hash of CBT by hand.).
+
+When you specify a particular version, CBT will use that one instead of the installed one.
+
+You can specify one by adding one line right before `class Build`. It looks like this:
+
+```
+// cbt:https://github.com/cvogt/cbt.git#75c32537cd8f29f9d12db37bf06ad942806f02393
+class Build...
+```
+
+The URL points to any git repository containing one of CBT's forks. You currently
+have to use a stable reference - i.e. a hash or tag. (Checkouts are currently not
+updated. If you refer to a branch or tag which is moved on the remote, CBT
+will not realize that and keep using the old version).
+
+### Using CBT like a boss
+
+Do you own your Build Tool or does your Build Tool own you? CBT makes it easy for YOU
+to be in control. We try to work on solid documentation, but even good
+documentation never tells the whole truth. Documentation can tell how to use
+something and why things are happening, but only the code can tell all the
+details of what exactly is happening. Reading the code can be intimidating for
+many Scala libraries, but not so with CBT. The source code is easy to read
+to the point that even Scala beginners will be able to understand it. So
+don't be afraid to actually look under the hood and check out what's happening.
+
+And guess what, you already have the source code on your disk, because
+you installed CBT by cloning its git repository. You can even debug CBT and
+your build files in an interactive debugger like IntelliJ after some minor setup.
+
+Finally, you can easily change CBT's code. Then CBT re-builds itself when you try
+to use it the next time. This means any changes you make are instantly reflected.
+This and the simple code make it super easy to fix bugs or add features yourself
+and feed them back into main line CBT.
+
+
+When debugging things, it can help to enable CBT's debug logging by passing
+`-Dlog=all` to CBT (or a logger name instead of `all`).
+
+
+Other design decisions
+--------------------
+
+CBT tries to couple its code very loosely. OO is used for configuration in build files.
+Interesting logic is in simple supporting library classes/objects, which can be used
+independently. You could even build a different configuration api than OO on top of them.
+
+
+Known limitations
+--------------------------
+- currently CBT supports no generic task scoping. A solution is known, but not implemented.
+ For now manually create intermediate tasks which serve as scoping for
+ known situations and encode the scope in the name, e.g. fastOptScalaJsOptions
+- currently CBT supports no dynamic overrides of tasks. A solution is known, but not implemented.
+ scalaVersion and version are passed through the context instead for dynamic overrides.
+- there is currently no built-in support for resources being added to a jar.
+ Should be simple to add, consider a PR
+- test framework support is currently a bit spotty but actively being worked on
+- concurrent task and build execution is currently disabled
+- CBT uses its own custom built maven resolver, which is really fast,
+ but likely does not work in some edge cases. Those may or may not be easy to fix.
+ We should add optional coursier integration back for a more complete solution.
+
+Known bugs
+-------------
+- currently there is a bug in CBT where dependent builds may miss changes in the things
+ they depend on. Deleting all target directories and starting from scratch helps.
+- There are some yet unknown bugs which can be solved by killing the nailgun background process
+ and/or re-running the desired cbt task multiple times until it succeeds.
+- if you ever see no output from a command but expect some, make sure you are in the right directory
+ and try to see if any of the above recommendations help
+
+
+Shell completions
+-------------------
+
+### Bash completions
+To auto-complete cbt task names in bash do this:
+
+```
+mkdir ~/.bash_completion.d/
+cp shell-integration/cbt-completions.bash ~/.bash_completion.d/
+```
+
+Add this to your .bashrc
+```
+for f in ~/.bash_completion.d/*; do
+ source $f
+done
+```
+
+### Fish shell completions
+
+copy this line into your fish configuration, on OSX: `/.config/fish/config.fish`
+
+```
+complete -c cbt -a '(cbt taskNames)'
+```
+
+### Zsh completions
+
+##### Manual installation
+Add the following to your `.zshrc`
+```
+source /path/to/cbt/shell-integration/cbt-completions.zsh
+```
+##### oh-my-zsh
+If using oh-my-zsh, you can install it as a plugin:
+```
+mkdir ~/.oh-my-zsh/custom/plugins/cbt
+cp shell-integration/cbt-completions.zsh ~/.oh-my-zsh/custom/plugins/cbt/cbt.plugin.zsh
+```
+Then enable it in your `.zshrc`:
+```
+plugins=( ... cbt)
+```
+Experimental GUI
+--------------------
+
+### Creating a project via the GUI
+
+`cd` into the directory inside of which you want to create a new project directory and run `cbt tools gui`.
+
+E.g.
+
+```
+$ cd ~/my-projects
+$ cbt tools gui
+```
+
+This should start UI server at http://localhost:9080. There you can create Main class, CBT build,
+add libraries, plugins, readme and other things. Let's say you choose `my-project` as the project name.
+The GUI will create `~/my-projects/my-project` for you.
+
+
+Plugin-author guide
+--------------------
+
+A CBT plugin is a trait that is mixed into a Build class.
+Only use this trait only for wiring things together.
+Don't put logic in there. Instead simply call methods
+on a separate class or object which serves as a library
+for your actual logic. It should be callable and testable
+outside of a Build class. This way the code of your plugin
+will be easier to test and easier to re-use. Feel free
+to make your logic rely on CBT's logger.
+
+ See `plugins/` for examples.
+
+Scala.js support
+----------------
+
+CBT supports cross-project Scala.js builds.
+It preserves same structure as in SBT (https://www.scala-js.org/doc/project/cross-build.html)
+
+ 1. Example for user scalajs project is in: `$CBT_HOME/cbt/examples/build-scalajs`
+ 2. `$CBT_HOME/cbt compile`
+ Will compile JVM and JS sources
+ `$CBT_HOME/cbt jsCompile`
+ Will compile JS sources
+ `$CBT_HOME/cbt jvmCompile`
+ Will compile JVM sources
+ 3. `$CBT_HOME/cbt fastOptJS` and `$CBT_HOME/cbt fullOptJS`
+ Same as in Scala.js sbt project
+
+ Note: Scala.js support is under ongoing development.
+
+ Currently missing features:
+ * No support for jsDependencies:
+ It means that all 3rd party dependencies should added manually, see scalajs build example
+ * No support for test
diff --git a/TODO.txt b/TODO.txt
new file mode 100644
index 0000000..b6f4c15
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,34 @@
+TODO:
+ - in progress
+ - improve logging
+
+ - immediate features
+ - fix main project main method being run during tests
+ - investigate and solve multiple compilations of the same SourceDependency Build. Maybe introduce global Build map.
+
+ - cleanup
+ - move from java File to nio Path
+
+ - near future features
+ - make cbt's own re-build concurrency safe
+ - unify with sbts key names where sensible
+ - allow updating snapshots
+ - cbt cli options inject add dependencies into default build
+ - dependency exclusion, etc.
+ - use cli friendly responses by default everywhere
+ - class path debugging
+ - broken jars detection
+ - invalid files in lib folder
+ - integrate / build out maven search
+ - use zinc nailgun multi platform nailgun wrapper https://github.com/typesafehub/zinc/tree/7af98ba11d27d7667301c2222c1e702c7092bc44/src/universal/bin
+
+
+ - future features
+ - loop compiling with cancelling running runs/compiles
+ - shell tab completion
+ - maybe scripts for bash/zsh/fish
+ - maybe interactive shell
+ - maybe one that exists immediately after execution
+
+ - potential features
+ - running in-project tasks in parallel using Monad
diff --git a/build/build.scala b/build/build.scala
new file mode 100644
index 0000000..8770ef6
--- /dev/null
+++ b/build/build.scala
@@ -0,0 +1,31 @@
+import cbt._
+
+class Build(val context: Context) extends Publish{
+ // FIXME: somehow consolidate this with cbt's own boot-strapping from source.
+ override def dependencies = {
+ super.dependencies ++ Resolver(mavenCentral).bind(
+ 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","0.3.9"),
+ ScalaDependency("org.scala-lang.modules","scala-xml","1.0.5")
+ )
+ }
+ override def sources = Seq(
+ "nailgun_launcher", "stage1", "stage2", "compatibility"
+ ).map(d => projectDirectory ++ ("/" + d))
+
+ def groupId: String = "org.cvogt"
+
+ def defaultVersion: String = "0.1"
+ def name: String = "cbt"
+
+ // Members declared in cbt.Publish
+ def description: String = "Fast, intuitive Build Tool for Scala"
+ def developers: Seq[cbt.Developer] = Nil
+ def inceptionYear: Int = 2016
+ def licenses: Seq[cbt.License] = Seq( License.Apache2 )
+ def organization: Option[cbt.Organization] = None
+ def scmConnection: String = ""
+ def scmUrl: String = ""
+ def url: java.net.URL = new java.net.URL("http://github.com/cvogt/cbt/")
+}
diff --git a/cbt b/cbt
new file mode 100755
index 0000000..47e41cd
--- /dev/null
+++ b/cbt
@@ -0,0 +1,240 @@
+#!/usr/bin/env bash
+# Launcher bash script that bootstraps CBT from source.
+# (Some of the code for reporting missing dependencies and waiting for nailgun to come up is a bit weird.)
+# This is intentionally kept as small as possible.
+# Welcome improvements to this file:
+# - reduce code size through better ideas
+# - reduce code size by moving more of this into type-checked Java/Scala code (if possible without performance loss).
+# - reduction of dependencies
+# - performance improvements
+shopt -qs extglob
+
+seconds() {
+ date +"%s"
+}
+
+nanos() {
+ n=$(date +"%N")
+ if [ "$n" = "N" ]; then
+ n=$(gdate +"%N" 2>/dev/null)
+ fi
+ if [ "$n" = "" ]; then
+ n="0"
+ fi
+ echo $n
+}
+
+start_seconds=$(seconds)
+start_nanos=1$(nanos)
+
+time_taken() {
+ i=$(( $(seconds) - start_seconds ))
+ n=$(( $(( 1$(nanos) - start_nanos )) / 1000000 ))
+ if [[ ( "$n" < 0 ) ]]; then
+ i=$(( i-1 ))
+ n=$(( n+1000 ))
+ fi
+ echo "$i.$n"
+}
+
+# utility function to log message to stderr with stating the time
+log () {
+ msg=$1
+ enabled=1
+ while test $# -gt 0; do
+ case "$1" in
+ "-Dlog=time") enabled=0 ;;
+ "-Dlog=all") enabled=0 ;;
+ esac
+ shift
+ done
+ if [ $enabled -eq 0 ]; then
+ delta=$(time_taken)
+ echo "[$delta] $msg" 1>&2
+ fi
+}
+
+log "Checking for dependencies" $*
+
+which javac 2>&1 > /dev/null
+javac_installed=$?
+if [ ! $javac_installed -eq 0 ]; then
+ echo "You need to install javac 1.7 or later! CBT needs it to bootstrap from Java sources into Scala." 1>&2
+ exit 1
+fi
+
+# log "cutting javac version" $*
+# javac_version=$(javac -version 2>&1) # e.g. "javac 1.8.0_u60"
+# javac_version_update=${javac_version/javac 1./} # e.g. "8.0_u60"
+# javac_version_minor_pointed=${javac_version_update%_*} # e.g. "8.0"
+# javac_version_minor=${javac_version_minor_pointed%.*} # e.g. "8"
+# log "cutting javac version done" $*
+# if [ ! "$javac_version_minor" -ge "7" ]; then
+# echo "You need to install javac version 1.7 or greater!" 2>&1
+# echo "Current javac version is $javac_version" 2>&1
+# exit 1
+# fi
+
+NG_EXECUTABLE=$(which ng || which ng-nailgun)
+NG_SERVER=$(which ng-server || ls /usr/share/java/nailgun-server-*.jar 2>/dev/null | awk '{print "java -jar " $0}')
+nailgun_installed=0
+if [ "$NG_EXECUTABLE" == "" ] || [ "$NG_SERVER" == "" ]; then
+ nailgun_installed=1
+ echo "(Note: nailgun not found. It makes CBT faster! Try 'brew install nailgun' or 'apt install nailgun'.)" 1>&2
+fi
+which realpath 2>&1 > /dev/null
+realpath_installed=$?
+which gcc 2>&1 > /dev/null
+gcc_installed=$?
+if [ ! $realpath_installed -eq 0 ] && [ ! $gcc_installed -eq 0 ]; then
+ echo "You need realpath or gcc installed! CBT needs it to locate itself reliably." 1>&2
+ exit 1
+fi
+
+which gpg 2>&1 > /dev/null
+gpg_installed=$?
+if [ ! $gpg_installed -eq 0 ]; then
+ echo "(Note: gpg not found. In order to use publishSigned you'll need it.)" 1>&2
+fi
+
+NAILGUN_PORT=4444
+NG="$NG_EXECUTABLE --nailgun-port $NAILGUN_PORT"
+
+CWD=$(pwd)
+_DIR=$(dirname $(readlink "$0") 2>/dev/null || dirname "$0" 2>/dev/null )
+
+log "Find out real path. Build realpath if needed." $*
+
+export CBT_HOME=$(dirname $($_DIR/realpath/realpath.sh $0))
+
+export NAILGUN=$CBT_HOME/nailgun_launcher/
+export TARGET=target/scala-2.11/classes/
+mkdir -p $NAILGUN$TARGET
+
+nailgun_out=$NAILGUN/target/nailgun.stdout.log
+nailgun_err=$NAILGUN/target/nailgun.strerr.log
+foo(){
+ while test $# -gt 0; do
+ case "$1" in
+ "-Dlog=nailgun")
+ nailgun_out=/dev/stderr
+ nailgun_err=/dev/stderr
+ ;;
+ "-Dlog=all")
+ nailgun_out=/dev/stderr
+ nailgun_err=/dev/stderr
+ ;;
+ esac
+ shift
+ done
+}
+
+foo $@
+
+if [ "$1" = "kill" ]; then
+ echo "Stopping background process (nailgun)" 1>&2
+ $NG ng-stop >> $nailgun_out 2>> $nailgun_err &
+ exit 1
+fi
+
+which nc 2>&1 > /dev/null
+nc_installed=$?
+
+log "Check for running nailgun with nc." $*
+
+server_up=1
+if [ $nc_installed -eq 0 ]; then
+ nc -z -n -w 1 127.0.0.1 $NAILGUN_PORT > /dev/null 2>&1
+ server_up=$?
+else
+ echo "(Note: nc not found. It will make slightly startup faster.)" 1>&2
+fi
+
+use_nailgun=0
+if [ $nailgun_installed -eq 1 ] || [ "$1" = "publishSigned" ] || [ "$2" = "publishSigned" ] || [ "$1" = "direct" ] || [ "$2" = "direct" ]; then
+ use_nailgun=1
+fi
+
+if [ $use_nailgun -eq 0 ] && [ ! $server_up -eq 0 ]; then
+ log "Starting background process (nailgun)" $*
+ # try to start nailgun-server, just in case it's not up
+ $NG_SERVER 127.0.0.1:$NAILGUN_PORT >> $nailgun_out 2>> $nailgun_err &
+fi
+
+stage1 () {
+ log "Checking for changes in cbt/nailgun_launcher" $*
+ NAILGUN_INDICATOR=$NAILGUN$TARGET/cbt/NailgunLauncher.class
+ changed=0
+ for file in `ls $NAILGUN/*.java`; do
+ if [ $file -nt $NAILGUN_INDICATOR ]; then changed=1; fi
+ done
+ compiles=0
+ if [ $changed -eq 1 ]; then
+ #rm $NAILGUN$TARGET/cbt/*.class 2>/dev/null # defensive delete of potentially broken class files
+ echo "Compiling cbt/nailgun_launcher" 1>&2
+ javac -Xlint:deprecation -Xlint:unchecked -d $NAILGUN$TARGET `ls $NAILGUN*.java`
+ compiles=$?
+ if [ $compiles -ne 0 ]; then
+ rm $NAILGUN$TARGET/cbt/*.class 2>/dev/null # triggers recompilation next time.
+ break
+ fi
+ if [ $use_nailgun -eq 0 ]; then
+ echo "Stopping background process (nailgun)" 1>&2
+ $NG ng-stop >> $nailgun_out 2>> $nailgun_err &
+ sleep 1
+ echo "Restarting background process (nailgun)" 1>&2
+ ng-server 127.0.0.1:$NAILGUN_PORT >> $nailgun_out 2>> $nailgun_err &
+ sleep 1
+ fi
+ fi
+
+ log "run CBT and loop if desired. This allows recompiling CBT itself as part of compile looping." $*
+
+ if [ $use_nailgun -eq 1 ]
+ then
+ log "Running JVM directly" $*
+ # -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=localhost:5005
+ # JVM options to improve startup time. See https://github.com/cvogt/cbt/pull/262
+ java $JAVA_OPTS -Xmx6072m -Xss10M -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xverify:none -cp $NAILGUN$TARGET cbt.NailgunLauncher $(time_taken) "$CWD" $*
+ else
+ log "Running via background process (nailgun)" $*
+ for i in 0 1 2 3 4 5 6 7 8 9; do
+ log "Adding classpath." $*
+ $NG ng-cp $NAILGUN$TARGET >> $nailgun_out 2>> $nailgun_err
+ log "Checking if nailgun is up yet." $*
+ $NG cbt.NailgunLauncher check-alive >> $nailgun_out 2>> $nailgun_err
+ alive=$?
+ if [ $alive -eq 131 ] || [ $alive -eq 33 ]; then
+ # the 33 is not working right now
+ # echo "Nailgun call failed. Try 'cbt kill' and check the error log cbt/nailgun_launcher/target/nailgun.stderr.log" 1>&2
+ #elif [ $alive -eq 33 ]; then
+ break
+ else
+ log "Nope. Sleeping for 0.5 seconds" $*
+ #if [ "$i" -gt 1 ]; then
+ # echo "Waiting for nailgun to start... (In case of problems try -Dlog=nailgun or check logs in cbt/nailgun_launcher/target/*.log)" 1>&2
+ #fi
+ fi
+ sleep 0.3
+ done
+ log "Running CBT via Nailgun." $*
+ $NG cbt.NailgunLauncher $(time_taken) "$CWD" $*
+ fi
+ exitCode=$?
+ log "Done running CBT." $*
+}
+
+while true; do
+ stage1 $*
+ if [ ! "$1" = "loop" ]; then
+ break
+ fi
+ echo "======= Restarting CBT =======" 1>&2
+done
+
+if [ $compiles -ne 0 ]; then
+ exitCode=1
+fi
+
+log "Exiting CBT" $*
+exit $exitCode
diff --git a/cbt.bat b/cbt.bat
new file mode 100644
index 0000000..676573d
--- /dev/null
+++ b/cbt.bat
@@ -0,0 +1,196 @@
+@ECHO OFF
+REM Launcher bash script that bootstraps CBT from source.
+REM (Some of the code for reporting missing dependencies and waiting for nailgun to come up is a bit weird.)
+REM This is inentionally kept as small as posible.
+REM Welcome improvements to this file:
+REM - reduce code size through better ideas
+REM - reduce code size by moving more of this into type-checked Java/Scala code (if possible without performance loss).
+REM - reduction of dependencies
+REM - performance improvements
+
+REM utility function to log message to stderr with stating the time
+setlocal EnableExtensions EnableDelayedExpansion
+
+where javac > nul 2>&1
+
+if ERRORLEVEL 1 (
+ ECHO You need to install javac! CBT needs it to bootstrap from Java sources into Scala.
+ GOTO :EOF
+)
+
+javac -version > %CBT_HOME%temp.txt 2>&1
+SET /p javac_version_output=<%CBT_HOME%temp.txt
+
+FOR /f "tokens=2" %%G IN ("%javac_version_output%") DO SET javac_version=%%G
+
+FOR /f "tokens=2 delims=." %%G IN ("%javac_version%") DO SET javac_version_minor=%%G
+
+IF javac_version_minor LSS 8 (
+ echo You need to install javac version 1.7 or greater!
+ echo Current javac version is %javac_version
+ GOTO :EOF
+)
+
+SET nailgun_installed=0
+
+where ng > nul 2>&1
+
+if ERRORLEVEL 1 (
+ ECHO You need to install Nailgun to make CBT slightly faster. > nul 2>&1
+) ELSE ( SET nailgun_installed=1 )
+
+where ng-server > nul 2>&1
+
+if ERRORLEVEL 1 (
+ ECHO You need to install Nailgun Server to make CBT slightly faster.
+) ELSE ( SET nailgun_installed=1 )
+
+IF %nailgun_installed%==0 ECHO Note: nailgun not found. It makes CBT faster! > nul 2>&1
+
+where gpg > nul 2>&1
+SET gpg_installed=0
+if ERRORLEVEL 1 (
+ ECHO You need to install gpg for login.
+) ELSE ( SET gpg_installed=0 )
+
+
+
+SET NAILGUN_PORT=4444
+SET NG=ng --nailgun-port %NAILGUN_PORT%
+
+for /f "delims=" %%i in ('chdir') do SET CWD=%%i
+
+SET CBT_HOME=%~dp0
+
+SET SCALA_VERSION=2.11.8
+
+SET NAILGUN=%CBT_HOME%nailgun_launcher\
+SET STAGE1=%CBT_HOME%stage1\
+SET TARGET=target\scala-2.11\classes\
+
+mkdir %NAILGUN%%TARGET% > nul 2>&1
+mkdir %STAGE1%%TARGET% > nul 2>&1
+
+SET nailgun_out=%NAILGUN%\target\nailgun.stdout.log
+SET nailgun_err=%NAILGUN%\target\nailgun.strerr.log
+
+where nc > nul 2>&1
+
+SET nc_installed=1
+SET server_up=0
+
+IF ERRORLEVEL 1 (
+ ECHO Note: nc not found. It will make slightly startup faster.
+) ELSE (
+ nc -z -n -w 1 127.0.0.1 %NAILGUN_PORT% > nul 2>&1
+ SET server_up=1
+)
+
+IF "%1%"=="kill" (
+ echo Stopping nailgun 1>&2
+ %NG% ng-stop >> %nailgun_out 2>> %nailgun_err%
+ GOTO :EOF
+)
+
+SET use_nailgun=1
+
+SET res=0
+IF %nailgun_installed%==0 SET res=1
+IF "%1%"=="publishSigned" SET res=1
+IF "%2%"=="publishSigned" SET res=1
+IF "%1%"=="direct" SET res=1
+IF "%2%"=="direct" SET res=1
+
+IF %res%==1 SET use_nailgun=0
+
+REM IF NOT %server_up%==1
+REM >> %nailgun_out 2>> %nailgun_err
+IF %use_nailgun%==1 (
+ REM try to start nailgun-server, just in case it's not up
+ START ng-server 127.0.0.1:%NAILGUN_PORT%
+)
+
+:RUN
+ CALL :stage1 %*
+ IF NOT "%1%"=="loop" (
+ GOTO :EOF
+ ) ELSE (
+ GOTO :RUN
+ echo "======= Restarting CBT =======" 1>&2
+ )
+
+:stage1
+SETLOCAL
+SET NAILGUN_INDICATOR=%NAILGUN%%TARGET%cbt\NailgunLauncher.class
+SET changed=0
+
+IF EXIST %NAILGUN_INDICATOR% (
+ REM this should recompile nailgun_launcher/ if any .java file is newer than NailgunLauncher.class, FIXME doesn't work
+ FOR %%i IN (%NAILGUN%*.java) DO (
+ FOR /F %%z IN ('DIR /B /O:D %%i%% %NAILGUN_INDICATOR% 2^> nul') DO SET NEWEST=%%z
+ if "%NEWEST:~-4%"=="java" ( SET changed=1 )
+ )
+) ELSE (
+ SET changed=1
+)
+
+IF %changed%==1 (
+ REM defensive delete of potentially broken class files
+ REM DEL /s /q /f %NAILGUN%%TARGET%cbt\*.class > nul 2>&1
+
+ echo Compiling cbt/nailgun_launcher
+
+ dir /B /A:-D %NAILGUN%\*.java > %CBT_HOME%temp.txt
+
+ FOR /F "tokens=*" %%j in (%CBT_HOME%temp.txt) do SET "files=!files! %NAILGUN%%%j"
+
+ javac -Xlint:deprecation -d %NAILGUN%%TARGET% !files!
+
+ IF ERRORLEVEL 1 (
+ REM triggers recompilation next time.
+ DEL %NAILGUN%TARGET/cbt/*.class 2> NUL REM triggers recompilation next time.
+ GOTO :eof
+ )
+
+ IF %use_nailgun%==0 (
+ REM echo "Stopping nailgun" 1>&2
+ %NG% ng-stop >> %nailgun_out% 2>> %nailgun_err%
+ REM echo "Restarting nailgun" 1>&2
+ ng-server 127.0.0.1:%NAILGUN_PORT% >> %nailgun_out% 2>> %nailgun_err%
+ )
+)
+
+REM TODO 0.0 should be replaced by actual spent time, see ./cbt
+IF %use_nailgun%==0 java %JAVA_OPTS% -Xmx6072m -Xss10M -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xverify:none -cp %NAILGUN%%TARGET% cbt.NailgunLauncher 0.0 %CWD% %*
+IF %use_nailgun%==0 GOTO :ENDIF
+
+SET /A counter=0
+:BEGINFOR
+REM ECHO %counter%
+IF /I "%counter%" EQU "10" GOTO :ENDFOR
+%NG% ng-cp %NAILGUN%%TARGET%
+%NG% cbt.NailgunLauncher check-alive
+%NG% ng-cp %NAILGUN%%TARGET% >> %nailgun_out% 2>> %nailgun_err%
+%NG% cbt.NailgunLauncher check-alive >> %nailgun_out% 2>> %nailgun_err%
+
+SET isAlive=0
+IF %errorlevel%==131 SET isAlive=1
+IF %errorlevel%==33 SET isAlive=1
+
+
+REM IF ERRORLEVEL 1 (
+ REM ECHO Waiting for nailgun
+REM ) ELSE (
+ REM GOTO :ENDFOR
+REM )
+
+SLEEP 2
+SET /A counter=%counter% + 1
+REM GOTO :BEGINFOR
+:ENDFOR
+%NG% cbt.NailgunLauncher %CWD% %*
+ENDLOCAL
+:ENDIF
+:ENDPROGRAM
+ENDLOCAL
+REM DEL %CBT_HOME%temp.txt > NUL 1>&2 \ No newline at end of file
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 0000000..1615ad5
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,17 @@
+machine:
+ java:
+ version: oraclejdk7
+
+dependencies:
+ cache_directories:
+ - "cache"
+ override:
+ - ./cbt compile
+ - ./cbt direct
+ - ./cbt -Dlog=all
+
+test:
+ override:
+ - rm ~/.gitconfig # avoid url replacement breaking jgit
+ - ./cbt direct test
+ - ./cbt test
diff --git a/compatibility/ArtifactInfo.java b/compatibility/ArtifactInfo.java
new file mode 100644
index 0000000..a2e6006
--- /dev/null
+++ b/compatibility/ArtifactInfo.java
@@ -0,0 +1,7 @@
+package cbt;
+
+public interface ArtifactInfo extends Dependency{
+ public abstract String artifactId();
+ public abstract String groupId();
+ public abstract String version();
+}
diff --git a/compatibility/BuildInterface.java b/compatibility/BuildInterface.java
new file mode 100644
index 0000000..f061832
--- /dev/null
+++ b/compatibility/BuildInterface.java
@@ -0,0 +1,10 @@
+package cbt;
+import java.io.*;
+
+public abstract class BuildInterface implements Dependency{
+ public abstract BuildInterface copy(Context context); // needed to configure builds
+ public abstract String scalaVersion(); // needed to propagate scalaVersion to dependent builds
+ public abstract String[] crossScalaVersionsArray(); // FIXME: this probably can't use Scala classes
+ public abstract BuildInterface finalBuild(); // needed to propagage through build builds. Maybe we can get rid of this.
+ public abstract File[] triggerLoopFilesArray(); // needed for watching files across composed builds
+}
diff --git a/compatibility/Context.java b/compatibility/Context.java
new file mode 100644
index 0000000..921087f
--- /dev/null
+++ b/compatibility/Context.java
@@ -0,0 +1,22 @@
+package cbt;
+import java.io.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+// TODO: try to reduce the number of members
+public abstract class Context{
+ public abstract File projectDirectory();
+ public abstract File cwd();
+ public abstract String[] argsArray();
+ public abstract String[] enabledLoggersArray();
+ public abstract Long startCompat();
+ public abstract Boolean cbtHasChangedCompat();
+ public abstract String versionOrNull();
+ public abstract String scalaVersionOrNull(); // needed to propagate scalaVersion to dependendee builds
+ public abstract ConcurrentHashMap<String,Object> permanentKeys();
+ public abstract ConcurrentHashMap<Object,ClassLoader> permanentClassLoaders();
+ public abstract File cache();
+ public abstract File cbtHome();
+ public abstract File cbtRootHome();
+ public abstract File compatibilityTarget();
+ public abstract BuildInterface parentBuildOrNull();
+}
diff --git a/compatibility/Dependency.java b/compatibility/Dependency.java
new file mode 100644
index 0000000..d491174
--- /dev/null
+++ b/compatibility/Dependency.java
@@ -0,0 +1,10 @@
+package cbt;
+import java.io.*;
+
+public interface Dependency{
+ public abstract String show();
+ public abstract Boolean needsUpdateCompat();
+ public abstract Dependency[] dependenciesArray();
+ public abstract File[] dependencyClasspathArray();
+ public abstract File[] exportedClasspathArray();
+}
diff --git a/compatibility/Result.java b/compatibility/Result.java
new file mode 100644
index 0000000..220aa3a
--- /dev/null
+++ b/compatibility/Result.java
@@ -0,0 +1,11 @@
+/*
+package cbt;
+import java.io.*;
+public interface Result<T>{
+ public abstract Integer exitCode();
+ public abstract OutputStream out();
+ public abstract OutputStream err();
+ public abstract InputStream in();
+ public abstract T value();
+}
+*/
diff --git a/coursier/Coursier.scala b/coursier/Coursier.scala
new file mode 100644
index 0000000..8a66aee
--- /dev/null
+++ b/coursier/Coursier.scala
@@ -0,0 +1,47 @@
+/*
+package cbt
+object Coursier{
+ implicit class CoursierDependencyResolution(d: JavaDependency){
+ import d._
+ def resolveCoursier = {
+ import coursier._
+ val repositories = Seq(
+ Cache.ivy2Local,
+ MavenResolver("https://repo1.maven.org/maven2")
+ )
+
+ val start = Resolution(
+ Set(
+ JavaDependency(
+ Module(groupId, artifactId), version
+ )
+ )
+ )
+
+ val fetch = Fetch.from(repositories, Cache.fetch())
+
+
+ val resolution = start.process.run(fetch).run
+
+ val errors: Seq[(JavaDependency, Seq[String])] = resolution.errors
+
+ if(errors.nonEmpty) throw new Exception(errors.toString)
+
+ import java.io.File
+ import scalaz.\/
+ import scalaz.concurrent.Task
+
+ val localArtifacts: Seq[FileError \/ File] = Task.gatherUnordered(
+ resolution.artifacts.map(Cache.file(_).run)
+ ).run
+
+ val files = localArtifacts.map(_.toEither match {
+ case Left(error) => throw new Exception(error.toString)
+ case Right(file) => file
+ })
+
+ resolution.dependencies.map( d => cbt.JavaDependency(d.module.organization,d.module.name, d.version)).to[collection.immutable.Seq]
+ }
+ }
+}
+*/ \ No newline at end of file
diff --git a/examples/build-info-example/BuildInfo.scala b/examples/build-info-example/BuildInfo.scala
new file mode 100644
index 0000000..ac0e680
--- /dev/null
+++ b/examples/build-info-example/BuildInfo.scala
@@ -0,0 +1,8 @@
+// generated file
+import java.io._
+object BuildInfo{
+def artifactId = "build-info-example"
+def groupId = "cbt.examples"
+def version = "0.1"
+def scalaVersion = "2.11.8"
+}
diff --git a/examples/build-info-example/Main.scala b/examples/build-info-example/Main.scala
new file mode 100644
index 0000000..cb4ad75
--- /dev/null
+++ b/examples/build-info-example/Main.scala
@@ -0,0 +1,9 @@
+object Main{
+ def main(args: Array[String]): Unit = {
+ import BuildInfo._
+ println("scalaVersion: "+scalaVersion)
+ println("groupId: "+groupId)
+ println("artifactId: "+artifactId)
+ println("version: "+version)
+ }
+} \ No newline at end of file
diff --git a/examples/build-info-example/Readme.md b/examples/build-info-example/Readme.md
new file mode 100644
index 0000000..acbb84e
--- /dev/null
+++ b/examples/build-info-example/Readme.md
@@ -0,0 +1,3 @@
+This is an example how to propagate build-time information
+such as version or scalaVersion to runtime.
+The advantage of the approach taken here is simplicity.
diff --git a/examples/build-info-example/build/build.scala b/examples/build-info-example/build/build.scala
new file mode 100644
index 0000000..f6fc7a4
--- /dev/null
+++ b/examples/build-info-example/build/build.scala
@@ -0,0 +1,27 @@
+import cbt._
+import java.nio.file.Files._
+
+class Build(val context: Context) extends PackageJars{
+ def name = "build-info-example"
+ def groupId = "cbt.examples"
+ def defaultVersion = "0.1"
+ override def defaultScalaVersion = "2.11.8"
+ override def compile = {
+ val file = (projectDirectory ++ "/BuildInfo.scala").toPath
+ val contents = s"""// generated file
+import java.io._
+object BuildInfo{
+def artifactId = "$artifactId"
+def groupId = "$groupId"
+def version = "$version"
+def scalaVersion = "$scalaVersion"
+}
+"""
+ if( exists(file) && contents != new String(readAllBytes(file)) )
+ write(
+ (projectDirectory ++ "/BuildInfo.scala").toPath,
+ contents.getBytes
+ )
+ super.compile
+ }
+}
diff --git a/examples/dotty-example/README.md b/examples/dotty-example/README.md
new file mode 100644
index 0000000..bc0f6b0
--- /dev/null
+++ b/examples/dotty-example/README.md
@@ -0,0 +1,3 @@
+Dotty example project compiling hello world with the next version of Scala.
+
+All you need to do to enable Dotty is `extends Dotty` in your build.scala .
diff --git a/examples/dotty-example/build/build.scala b/examples/dotty-example/build/build.scala
new file mode 100644
index 0000000..eb67d93
--- /dev/null
+++ b/examples/dotty-example/build/build.scala
@@ -0,0 +1,2 @@
+import cbt._
+class Build(val context: Context) extends Dotty
diff --git a/examples/dotty-example/src/Main.scala b/examples/dotty-example/src/Main.scala
new file mode 100644
index 0000000..fdc2068
--- /dev/null
+++ b/examples/dotty-example/src/Main.scala
@@ -0,0 +1,12 @@
+package dotty_example
+object Main extends Foo("Hello Dotty - trait parameters, yay"){
+ def main(args: Array[String]): Unit = {
+ println(hello)
+
+ // Sanity check the classpath: this won't run if the dotty jar is not present.
+ val x: Int => Int = z => z
+ x(1)
+ }
+}
+
+trait Foo(val hello: String)
diff --git a/examples/multi-project-example/build/build.scala b/examples/multi-project-example/build/build.scala
new file mode 100644
index 0000000..9a67488
--- /dev/null
+++ b/examples/multi-project-example/build/build.scala
@@ -0,0 +1,10 @@
+import cbt._
+class Build(val context: Context) extends SharedCbtBuild{
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory ++ "/sub1" ),
+ DirectoryDependency( projectDirectory ++ "/sub2" )
+ )
+}
diff --git a/examples/multi-project-example/build/build/build.scala b/examples/multi-project-example/build/build/build.scala
new file mode 100644
index 0000000..be72a13
--- /dev/null
+++ b/examples/multi-project-example/build/build/build.scala
@@ -0,0 +1,10 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild{
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory.getParentFile ++ "/shared-build" )
+ )
+}
diff --git a/examples/multi-project-example/common/SomeSharedClass.scala b/examples/multi-project-example/common/SomeSharedClass.scala
new file mode 100644
index 0000000..1f32c5a
--- /dev/null
+++ b/examples/multi-project-example/common/SomeSharedClass.scala
@@ -0,0 +1 @@
+class SomeSharedClass \ No newline at end of file
diff --git a/examples/multi-project-example/common/build/build.scala b/examples/multi-project-example/common/build/build.scala
new file mode 100644
index 0000000..0fbea50
--- /dev/null
+++ b/examples/multi-project-example/common/build/build.scala
@@ -0,0 +1,3 @@
+import cbt._
+
+class Build(val context: Context) extends SharedCbtBuild
diff --git a/examples/multi-project-example/common/build/build/build.scala b/examples/multi-project-example/common/build/build/build.scala
new file mode 100644
index 0000000..efeeb77
--- /dev/null
+++ b/examples/multi-project-example/common/build/build/build.scala
@@ -0,0 +1,10 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild{
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory.getParentFile.getParentFile ++ "/shared-build" )
+ )
+}
diff --git a/examples/multi-project-example/shared-build/SharedCbtBuild.scala b/examples/multi-project-example/shared-build/SharedCbtBuild.scala
new file mode 100644
index 0000000..38e4cc1
--- /dev/null
+++ b/examples/multi-project-example/shared-build/SharedCbtBuild.scala
@@ -0,0 +1,4 @@
+import cbt._
+trait SharedCbtBuild extends BaseBuild{
+ override def defaultScalaVersion = "2.10.6"
+} \ No newline at end of file
diff --git a/examples/multi-project-example/shared-build/build/build.scala b/examples/multi-project-example/shared-build/build/build.scala
new file mode 100644
index 0000000..332519e
--- /dev/null
+++ b/examples/multi-project-example/shared-build/build/build.scala
@@ -0,0 +1,6 @@
+import cbt._
+class Build(val context: Context) extends BaseBuild{
+ override def dependencies =
+ super.dependencies :+ // don't forget super.dependencies here
+ context.cbtDependency
+}
diff --git a/examples/multi-project-example/sub1/SomeConcreteClass.scala b/examples/multi-project-example/sub1/SomeConcreteClass.scala
new file mode 100644
index 0000000..2f8f715
--- /dev/null
+++ b/examples/multi-project-example/sub1/SomeConcreteClass.scala
@@ -0,0 +1 @@
+class SomeConcreteClass extends SomeSharedClass
diff --git a/examples/multi-project-example/sub1/build/build.scala b/examples/multi-project-example/sub1/build/build.scala
new file mode 100644
index 0000000..2c39a54
--- /dev/null
+++ b/examples/multi-project-example/sub1/build/build.scala
@@ -0,0 +1,10 @@
+import cbt._
+
+class Build(val context: Context) extends SharedCbtBuild{
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory.getParentFile ++ "/common" )
+ )
+}
diff --git a/examples/multi-project-example/sub1/build/build/build.scala b/examples/multi-project-example/sub1/build/build/build.scala
new file mode 100644
index 0000000..efeeb77
--- /dev/null
+++ b/examples/multi-project-example/sub1/build/build/build.scala
@@ -0,0 +1,10 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild{
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory.getParentFile.getParentFile ++ "/shared-build" )
+ )
+}
diff --git a/examples/multi-project-example/sub2/SomeOtherConcreteClass.scala b/examples/multi-project-example/sub2/SomeOtherConcreteClass.scala
new file mode 100644
index 0000000..56b0aa3
--- /dev/null
+++ b/examples/multi-project-example/sub2/SomeOtherConcreteClass.scala
@@ -0,0 +1 @@
+class SomeOtherConcreteClass extends SomeSharedClass
diff --git a/examples/multi-project-example/sub2/build/build.scala b/examples/multi-project-example/sub2/build/build.scala
new file mode 100644
index 0000000..2c39a54
--- /dev/null
+++ b/examples/multi-project-example/sub2/build/build.scala
@@ -0,0 +1,10 @@
+import cbt._
+
+class Build(val context: Context) extends SharedCbtBuild{
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory.getParentFile ++ "/common" )
+ )
+}
diff --git a/examples/multi-project-example/sub2/build/build/build.scala b/examples/multi-project-example/sub2/build/build/build.scala
new file mode 100644
index 0000000..efeeb77
--- /dev/null
+++ b/examples/multi-project-example/sub2/build/build/build.scala
@@ -0,0 +1,10 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild{
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory.getParentFile.getParentFile ++ "/shared-build" )
+ )
+}
diff --git a/examples/resources-example/build/build.scala b/examples/resources-example/build/build.scala
new file mode 100644
index 0000000..aec5d79
--- /dev/null
+++ b/examples/resources-example/build/build.scala
@@ -0,0 +1,21 @@
+import cbt._
+class Build(val context: Context) extends BaseBuild{
+ /*
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory ++ "/subProject" )
+ ) ++
+ Resolver( mavenCentral ).bind(
+ // CBT-style Scala dependencies
+ ScalaDependency( "com.lihaoyi", "ammonite-ops", "0.5.5" )
+ MavenDependency( "com.lihaoyi", "ammonite-ops_2.11", "0.5.5" )
+
+ // SBT-style dependencies
+ "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
+ "com.lihaoyi" % "ammonite-ops_2.11" % "0.5.5"
+ )
+ */
+ override def resourceClasspath = super.resourceClasspath ++ ClassPath(Seq(projectDirectory ++ "/my-resources"))
+}
diff --git a/examples/resources-example/build/build/build.scala b/examples/resources-example/build/build/build.scala
new file mode 100644
index 0000000..f700060
--- /dev/null
+++ b/examples/resources-example/build/build/build.scala
@@ -0,0 +1,20 @@
+import cbt._
+class Build(val context: Context) extends BuildBuild{
+ /*
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory ++ "/subProject" )
+ ) ++
+ Resolver( mavenCentral ).bind(
+ // CBT-style Scala dependencies
+ ScalaDependency( "com.lihaoyi", "ammonite-ops", "0.5.5" )
+ MavenDependency( "com.lihaoyi", "ammonite-ops_2.11", "0.5.5" )
+
+ // SBT-style dependencies
+ "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
+ "com.lihaoyi" % "ammonite-ops_2.11" % "0.5.5"
+ )
+ */
+}
diff --git a/examples/resources-example/my-resources/foo.text b/examples/resources-example/my-resources/foo.text
new file mode 100644
index 0000000..f82e417
--- /dev/null
+++ b/examples/resources-example/my-resources/foo.text
@@ -0,0 +1 @@
+Hello from a resource in my-resources/ (the additional location manually added here in build.scala)
diff --git a/examples/resources-example/resources/foo.text b/examples/resources-example/resources/foo.text
new file mode 100644
index 0000000..6d7c85a
--- /dev/null
+++ b/examples/resources-example/resources/foo.text
@@ -0,0 +1 @@
+Hello from a resource in resources/ (the default location)
diff --git a/examples/resources-example/src/Main.scala b/examples/resources-example/src/Main.scala
new file mode 100644
index 0000000..3bc0943
--- /dev/null
+++ b/examples/resources-example/src/Main.scala
@@ -0,0 +1,27 @@
+import java.nio.file.{Files, Paths}
+object Main extends App {
+ // Be aware that CBT currently isolates classloaders of dependencies
+ // your dependencies will not see the resources of your project
+ // This means that e.g. spray will not see a application.conf in your project's
+ // resources/ directory. See https://github.com/cvogt/cbt/issues/176
+ println(
+ "foo.text in resources contains: " ++
+ new String(
+ Files.readAllBytes(
+ Paths.get( getClass.getClassLoader.getResource("foo.text").getFile )
+ )
+ )
+ )
+ import scala.collection.JavaConverters._
+ println(
+ "foo.text in resources and my-resources:\n" ++
+ getClass.getClassLoader.getResources("foo.text").asScala.map(
+ resource =>
+ new String(
+ Files.readAllBytes(
+ Paths.get( resource.getFile )
+ )
+ )
+ ).mkString
+ )
+}
diff --git a/examples/scalafmt-example/.scalafmt.conf b/examples/scalafmt-example/.scalafmt.conf
new file mode 100644
index 0000000..4fc0088
--- /dev/null
+++ b/examples/scalafmt-example/.scalafmt.conf
@@ -0,0 +1,11 @@
+style: defaultWithAlign
+danglingParentheses: true
+
+spaces {
+ inImportCurlyBraces: true
+}
+
+rewriteTokens {
+ "->": "→"
+ "=>": "⇒"
+}
diff --git a/examples/scalafmt-example/README.md b/examples/scalafmt-example/README.md
new file mode 100644
index 0000000..0a59f96
--- /dev/null
+++ b/examples/scalafmt-example/README.md
@@ -0,0 +1,16 @@
+This example shows integration with scalafmt plugin.
+
+Reformat executed on every `cbt compile` call, and affects only *.scala source files.
+
+You can provide your custom scalfmt preferences in build via `scalafmtConfig`.
+
+To see formatting in action: execute `cbt breakFormatting` to break formatting and then execute`cbt scalafmt` to get formatting back.
+
+To check if your code is properly formatted(for example as part of CI validation), you can execute:
+
+```
+cbt scalafmt
+git diff --exit-code
+```
+
+Last command will return non-zero code, if your code isn't properly formatted.
diff --git a/examples/scalafmt-example/build/build.scala b/examples/scalafmt-example/build/build.scala
new file mode 100644
index 0000000..4f5545e
--- /dev/null
+++ b/examples/scalafmt-example/build/build.scala
@@ -0,0 +1,26 @@
+import cbt._
+
+class Build(val context: Context) extends BaseBuild with Scalafmt {
+ override def compile = {
+ scalafmt
+ super.compile
+ }
+
+ def breakFormatting = {
+ import java.nio.file._
+ import java.nio.charset.Charset
+ import scala.collection.JavaConverters._
+ val utf8 = Charset.forName("UTF-8")
+ sourceFiles foreach { file =>
+ val path = file.toPath
+ val fileLines = Files.readAllLines(path, utf8).asScala
+ val brokenLines = fileLines map (l =>
+ l.dropWhile(_ == ' ')
+ .replaceAll("⇒", "=>")
+ .replaceAll("→", "->")
+ )
+ Files.write(path, brokenLines.asJava, utf8)
+ }
+ System.err.println("Done breaking formatting")
+ }
+}
diff --git a/examples/scalafmt-example/build/build/build.scala b/examples/scalafmt-example/build/build/build.scala
new file mode 100644
index 0000000..aa70f36
--- /dev/null
+++ b/examples/scalafmt-example/build/build/build.scala
@@ -0,0 +1,5 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild {
+ override def dependencies = super.dependencies :+ plugins.scalafmt
+}
diff --git a/examples/scalafmt-example/resources/reference.conf b/examples/scalafmt-example/resources/reference.conf
new file mode 100644
index 0000000..f3e122d
--- /dev/null
+++ b/examples/scalafmt-example/resources/reference.conf
@@ -0,0 +1,8 @@
+// should not reformat this, cause it is not in source files
+some {
+ inside {
+ foo: 22
+ bar: false
+ baz: "hello"
+ }
+}
diff --git a/examples/scalafmt-example/src/Main.scala b/examples/scalafmt-example/src/Main.scala
new file mode 100644
index 0000000..465e27a
--- /dev/null
+++ b/examples/scalafmt-example/src/Main.scala
@@ -0,0 +1,16 @@
+import scala.concurrent.{ Await, Future }
+import scala.concurrent.duration._
+
+object Main extends App {
+ println("fooo")
+ val futureRes = Await.result(Future.successful(1), 5.seconds)
+ List(1, 2, 4, 5, 6) match {
+ case h :: _ ⇒ println("not empty list")
+ case Nil ⇒ println("empty list")
+ }
+
+ List(1 → 2, 2 → 3, 3 → 4) match {
+ case (1, 2) :: _ ⇒ 90 → 1
+ case (22, 44) :: _ ⇒ 1 → 150
+ }
+}
diff --git a/examples/scalajs-react-example/README.md b/examples/scalajs-react-example/README.md
new file mode 100644
index 0000000..74e015a
--- /dev/null
+++ b/examples/scalajs-react-example/README.md
@@ -0,0 +1,11 @@
+
+Compilation instructions
+-------------------------------------------
+1. `cbt fastOptJS`
+
+Execution instructions
+-------------------------------------------
+1. `cd server`
+2. `npm install`
+3. `node app.js`
+4. Go to http://localhost:3000 in a browser \ No newline at end of file
diff --git a/examples/scalajs-react-example/js/App.scala b/examples/scalajs-react-example/js/App.scala
new file mode 100644
index 0000000..0cd170e
--- /dev/null
+++ b/examples/scalajs-react-example/js/App.scala
@@ -0,0 +1,15 @@
+package prototype
+
+import japgolly.scalajs.react.ReactDOM
+import org.scalajs.dom
+
+import scala.scalajs.js.JSApp
+import scala.scalajs.js.annotation.JSExport
+
+@JSExport("App")
+object App extends JSApp {
+ def main(): Unit = {
+ val doc = dom.document
+ ReactDOM.render(Pictures.PictureComponent(), doc.getElementById("main"))
+ }
+}
diff --git a/examples/scalajs-react-example/js/Pictures.scala b/examples/scalajs-react-example/js/Pictures.scala
new file mode 100644
index 0000000..db1d7ef
--- /dev/null
+++ b/examples/scalajs-react-example/js/Pictures.scala
@@ -0,0 +1,102 @@
+package prototype
+
+import japgolly.scalajs.react.{ReactComponentB, BackendScope, Callback}
+import org.scalajs.dom
+
+import scala.scalajs._
+import japgolly.scalajs.react.vdom.all._
+
+import scala.scalajs.js.JSON
+
+object Pictures {
+
+ case class State(pictures: List[Picture], favourites: List[Picture])
+
+ type PicClick = (String, Boolean) => Callback
+
+ class Backend($: BackendScope[Unit, State]) {
+
+ def onPicClick(id: String, favorite: Boolean) =
+ $.state flatMap { s =>
+ if (favorite) {
+ val newPics = s.pictures.map(p => if (p.id == id) p.copy(favorite = false) else p)
+ val newFavs = s.favourites.filter(p => p.id != id)
+ $.modState(_ => State(newPics, newFavs))
+ } else {
+ var newPic: Picture = null
+ val newPics = s.pictures.map(p => if (p.id == id) {
+ newPic = p.copy(favorite = true); newPic
+ } else p)
+ val newFavs = s.favourites.+:(newPic)
+ $.modState(_ => State(newPics, newFavs))
+ }
+ }
+
+ def render(s: State) =
+ div(
+ h1("Popular Pixabay Pics"),
+ pictureList((s.pictures, onPicClick)),
+ h1("Your favorites"),
+ favoriteList((s.favourites, onPicClick)))
+ }
+
+ val picture = ReactComponentB[(Picture, PicClick)]("picture")
+ .render_P { case (p, b) =>
+ div(if (p.favorite) cls := "picture favorite" else cls := "picture", onClick --> b(p.id, p.favorite))(
+ img(src := p.src, title := p.title)
+ )
+ }
+ .build
+
+ val pictureList = ReactComponentB[(List[Picture], PicClick)]("pictureList")
+ .render_P { case (list, b) =>
+ div(`class` := "pictures")(
+ if (list.isEmpty) span("Loading Pics..")
+ else {
+ list.map(p => picture.withKey(p.id)((p, b)))
+ }
+ )
+ }
+ .build
+
+ val favoriteList = ReactComponentB[(List[Picture], PicClick)]("favoriteList")
+ .render_P { case (list, b) =>
+ div(`class` := "favorites")(
+ if (list.isEmpty) span("Click an image to mark as favorite")
+ else {
+ list.map(p => picture.withKey(p.id)((p, b)))
+ }
+ )
+ }
+ .build
+
+ val PictureComponent = ReactComponentB[Unit]("PictureComponent")
+ .initialState(State(Nil, Nil))
+ .renderBackend[Backend]
+ .componentDidMount(scope => Callback {
+ import scalajs.js.Dynamic.{global => g}
+ def isDefined(g: js.Dynamic): Boolean =
+ g.asInstanceOf[js.UndefOr[AnyRef]].isDefined
+ val url = "http://localhost:3000/data"
+ val xhr = new dom.XMLHttpRequest()
+ xhr.open("GET", url)
+ xhr.onload = { (e: dom.Event) =>
+ if (xhr.status == 200) {
+ val result = JSON.parse(xhr.responseText)
+ if (isDefined(result) && isDefined(result.hits)) {
+ val hits = result.hits.asInstanceOf[js.Array[js.Dynamic]]
+ val pics = hits.toList.map(item => Picture(
+ item.id.toString,
+ item.pageURL.toString,
+ item.previewURL.toString,
+ if (item.tags != null) item.tags.asInstanceOf[js.Array[String]].mkString(",") else ""))
+ //if (item.caption != null) item.caption.text.toString else ""))
+ scope.modState(_ => State(pics, Nil)).runNow()
+ }
+ }
+ }
+ xhr.send()
+ })
+ .buildU
+
+}
diff --git a/examples/scalajs-react-example/js/build/build.scala b/examples/scalajs-react-example/js/build/build.scala
new file mode 100644
index 0000000..29f1c73
--- /dev/null
+++ b/examples/scalajs-react-example/js/build/build.scala
@@ -0,0 +1,22 @@
+import cbt._
+class Build(val context: Context) extends ScalaJsBuild{
+ override val projectName = "my-project"
+
+ override def sources = super.sources ++ Seq(
+ projectDirectory.getParentFile ++ "/shared"
+ )
+
+ override def dependencies = (
+ super.dependencies ++
+ Resolver( mavenCentral ).bind(
+ //"org.scalatest" %%% "scalatest" % "3.0.0-RC2",
+ "com.github.japgolly.scalajs-react" %%% "core" % "0.10.4", // for example
+ // for example if you want explicitely state scala version
+ "org.scala-js" % "scalajs-dom_sjs0.6_2.11" % "0.9.0"
+ )
+ )
+
+ override protected def fastOptJSFile = {
+ projectDirectory.getParentFile ++ "/server/public" ++ ("/"++super.fastOptJSFile.getName)
+ }
+}
diff --git a/examples/scalajs-react-example/js/build/build/build.scala b/examples/scalajs-react-example/js/build/build/build.scala
new file mode 100644
index 0000000..b30e005
--- /dev/null
+++ b/examples/scalajs-react-example/js/build/build/build.scala
@@ -0,0 +1,4 @@
+import cbt._
+class Build(val context: Context) extends BuildBuild{
+ override def dependencies = super.dependencies :+ plugins.scalaJs
+}
diff --git a/examples/scalajs-react-example/jvm/build/build.scala b/examples/scalajs-react-example/jvm/build/build.scala
new file mode 100644
index 0000000..327d705
--- /dev/null
+++ b/examples/scalajs-react-example/jvm/build/build.scala
@@ -0,0 +1,6 @@
+import cbt._
+class Build(val context: Context) extends BaseBuild{
+ override def sources = super.sources ++ Seq(
+ projectDirectory.getParentFile ++ "/shared"
+ )
+}
diff --git a/examples/scalajs-react-example/server/app.js b/examples/scalajs-react-example/server/app.js
new file mode 100644
index 0000000..620c26a
--- /dev/null
+++ b/examples/scalajs-react-example/server/app.js
@@ -0,0 +1,39 @@
+var express = require('express');
+var https = require('https');
+
+var app = express();
+
+app.get('/data', function(req, res){
+
+ var request = https.get(
+ //'https://api.instagram.com/v1/media/popular?client_id=642176ece1e7445e99244cec26f4de1f&callback=?',
+ 'https://pixabay.com/api/?key=2741116-9706ac6d4a58f2b5416225505&q=yellow+flowers&image_type=photo',
+ function(response) {
+
+ var body = "";
+ response.on('data', function(data) {
+ body += data;
+ });
+ response.on('end', function() {
+ console.log(body);
+ try {
+ res.send(JSON.parse(body));
+ } catch (e) {
+ return console.error(e);
+ }
+ });
+ });
+ request.on('error', function(e) {
+ console.log('Problem with request: ' + e.message);
+ });
+ request.end();
+});
+
+app.use(express.static(__dirname + '/public'));
+
+var server = app.listen(3000, function () {
+ var host = server.address().address;
+ var port = server.address().port;
+
+ console.log('Example app listening at http://%s:%s', host, port);
+});
diff --git a/examples/scalajs-react-example/server/package.json b/examples/scalajs-react-example/server/package.json
new file mode 100644
index 0000000..d20ec98
--- /dev/null
+++ b/examples/scalajs-react-example/server/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "server",
+ "version": "1.0.0",
+ "description": "prototype",
+ "main": "app.js",
+ "author": "Katrin",
+ "license": "UNLICENSED",
+ "private": true,
+ "dependencies": {
+ "express": "^4.13.3",
+ "express-ws": "^0.2.6",
+ "body-parser": "^1.14.1"
+ }
+}
diff --git a/examples/scalajs-react-example/server/public/index.html b/examples/scalajs-react-example/server/public/index.html
new file mode 100644
index 0000000..08de20d
--- /dev/null
+++ b/examples/scalajs-react-example/server/public/index.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Prototype</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+</head>
+<body>
+
+ <div id="main"></div>
+
+ <script src="https://fb.me/react-15.1.0.min.js"></script>
+ <script src="https://fb.me/react-dom-15.1.0.min.js"></script>
+ <script type="text/javascript" src="./my-project-fastopt.js"></script>
+ <script type="text/javascript">
+ App().main();
+ </script>
+</body>
+</html>
diff --git a/examples/scalajs-react-example/shared/Picture.scala b/examples/scalajs-react-example/shared/Picture.scala
new file mode 100644
index 0000000..dbb985a
--- /dev/null
+++ b/examples/scalajs-react-example/shared/Picture.scala
@@ -0,0 +1,3 @@
+package prototype
+
+case class Picture(id: String, url: String, src: String, title: String, favorite: Boolean = false)
diff --git a/examples/scalariform-example/README.md b/examples/scalariform-example/README.md
new file mode 100644
index 0000000..28ad226
--- /dev/null
+++ b/examples/scalariform-example/README.md
@@ -0,0 +1,16 @@
+This example shows integration with scalariform plugin.
+
+Reformat executed on every `cbt compile` call, and affects only *.scala source files.
+
+You can provide your custom scalariform preferences in build via `scalariformPreferences`.
+
+To see formatting in action: execute `cbt breakFormatting` to break formatting and then execute `cbt scalariformFormat` to get formatting back.
+
+To check if your code is properly formatted(for example as part of CI validation), you can execute:
+
+```
+cbt scalariformFormat
+git diff --exit-code
+```
+
+Last command will return non-zero code, if your code isn't properly formatted.
diff --git a/examples/scalariform-example/build/build.scala b/examples/scalariform-example/build/build.scala
new file mode 100644
index 0000000..91ff67a
--- /dev/null
+++ b/examples/scalariform-example/build/build.scala
@@ -0,0 +1,29 @@
+import cbt._
+import scalariform.formatter.preferences._
+
+class Build(val context: Context) extends BaseBuild with Scalariform {
+ override def compile = {
+ scalariformFormat
+ super.compile
+ }
+
+ override def scalariformPreferences =
+ FormattingPreferences()
+ .setPreference(SpacesAroundMultiImports, true)
+ .setPreference(DoubleIndentClassDeclaration, true)
+ .setPreference(RewriteArrowSymbols, true)
+
+ final def breakFormatting = {
+ import java.nio.file._
+ import java.nio.charset.Charset
+ import scala.collection.JavaConverters._
+ val utf8 = Charset.forName("UTF-8")
+ sourceFiles foreach { file =>
+ val path = file.toPath
+ val fileLines = Files.readAllLines(path, utf8).asScala
+ val brokenLines = fileLines map (_.dropWhile(_ == ' '))
+ Files.write(path, brokenLines.asJava, utf8)
+ }
+ System.err.println("Done breaking formatting")
+ }
+}
diff --git a/examples/scalariform-example/build/build/build.scala b/examples/scalariform-example/build/build/build.scala
new file mode 100644
index 0000000..59ab8d1
--- /dev/null
+++ b/examples/scalariform-example/build/build/build.scala
@@ -0,0 +1,5 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild {
+ override def dependencies = super.dependencies :+ plugins.scalariform
+}
diff --git a/examples/scalariform-example/resources/reference.conf b/examples/scalariform-example/resources/reference.conf
new file mode 100644
index 0000000..f3e122d
--- /dev/null
+++ b/examples/scalariform-example/resources/reference.conf
@@ -0,0 +1,8 @@
+// should not reformat this, cause it is not in source files
+some {
+ inside {
+ foo: 22
+ bar: false
+ baz: "hello"
+ }
+}
diff --git a/examples/scalariform-example/src/Main.scala b/examples/scalariform-example/src/Main.scala
new file mode 100644
index 0000000..d299aad
--- /dev/null
+++ b/examples/scalariform-example/src/Main.scala
@@ -0,0 +1,11 @@
+import scala.concurrent.{ Await, Future }
+import scala.concurrent.duration._
+
+object Main extends App {
+ println("fooo")
+ val futureRes = Await.result(Future.successful(1), 5.seconds)
+ List(1, 2, 4, 5, 6) match {
+ case h :: _ ⇒ println("not empty list")
+ case Nil ⇒ println("empty list")
+ }
+}
diff --git a/examples/scalatest-example/build/build.scala b/examples/scalatest-example/build/build.scala
new file mode 100644
index 0000000..48248fd
--- /dev/null
+++ b/examples/scalatest-example/build/build.scala
@@ -0,0 +1,9 @@
+import cbt._
+class Build(val context: Context) extends SbtLayoutMain {
+ outer =>
+ override def test: Option[ExitCode] = Some{
+ new BasicBuild(context) with ScalaTest with SbtLayoutTest{
+ override def dependencies = outer +: super.dependencies
+ }.run
+ }
+}
diff --git a/examples/scalatest-example/build/build/build.scala b/examples/scalatest-example/build/build/build.scala
new file mode 100644
index 0000000..d641b51
--- /dev/null
+++ b/examples/scalatest-example/build/build/build.scala
@@ -0,0 +1,8 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild{
+ override def dependencies = super.dependencies ++ Seq(
+ plugins.scalaTest,
+ plugins.sbtLayout
+ )
+}
diff --git a/examples/scalatest-example/src/main/scala/Hello.scala b/examples/scalatest-example/src/main/scala/Hello.scala
new file mode 100644
index 0000000..099a84d
--- /dev/null
+++ b/examples/scalatest-example/src/main/scala/Hello.scala
@@ -0,0 +1,7 @@
+object Main extends App{
+
+ println("Hello World")
+
+ def square(x: Int) = x * x
+
+}
diff --git a/examples/scalatest-example/src/test/scala/Test.scala b/examples/scalatest-example/src/test/scala/Test.scala
new file mode 100644
index 0000000..48b0a36
--- /dev/null
+++ b/examples/scalatest-example/src/test/scala/Test.scala
@@ -0,0 +1,23 @@
+import collection.mutable.Stack
+import org.scalatest._
+
+class Test extends FlatSpec with Matchers {
+ "square" should "return double" in {
+ Main.square(2) should be (4)
+ }
+
+ "A Stack" should "pop values in last-in-first-out order" in {
+ val stack = new Stack[Int]
+ stack.push(1)
+ stack.push(2)
+ stack.pop() should be (2)
+ stack.pop() should be (1)
+ }
+
+ it should "throw NoSuchElementException if an empty stack is popped" in {
+ val emptyStack = new Stack[Int]
+ a [NoSuchElementException] should be thrownBy {
+ emptyStack.pop()
+ }
+ }
+} \ No newline at end of file
diff --git a/examples/simple-example/build/build.scala b/examples/simple-example/build/build.scala
new file mode 100644
index 0000000..9320990
--- /dev/null
+++ b/examples/simple-example/build/build.scala
@@ -0,0 +1,20 @@
+import cbt._
+class Build(val context: Context) extends BaseBuild{
+ /*
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory ++ "/subProject" )
+ ) ++
+ Resolver( mavenCentral ).bind(
+ // CBT-style Scala dependencies
+ ScalaDependency( "com.lihaoyi", "ammonite-ops", "0.5.5" )
+ MavenDependency( "com.lihaoyi", "ammonite-ops_2.11", "0.5.5" )
+
+ // SBT-style dependencies
+ "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
+ "com.lihaoyi" % "ammonite-ops_2.11" % "0.5.5"
+ )
+ */
+}
diff --git a/examples/simple-example/build/build/build.scala b/examples/simple-example/build/build/build.scala
new file mode 100644
index 0000000..f700060
--- /dev/null
+++ b/examples/simple-example/build/build/build.scala
@@ -0,0 +1,20 @@
+import cbt._
+class Build(val context: Context) extends BuildBuild{
+ /*
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here
+ Seq(
+ // source dependency
+ DirectoryDependency( projectDirectory ++ "/subProject" )
+ ) ++
+ Resolver( mavenCentral ).bind(
+ // CBT-style Scala dependencies
+ ScalaDependency( "com.lihaoyi", "ammonite-ops", "0.5.5" )
+ MavenDependency( "com.lihaoyi", "ammonite-ops_2.11", "0.5.5" )
+
+ // SBT-style dependencies
+ "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
+ "com.lihaoyi" % "ammonite-ops_2.11" % "0.5.5"
+ )
+ */
+}
diff --git a/examples/simple-example/src/Main.scala b/examples/simple-example/src/Main.scala
new file mode 100644
index 0000000..88a18d3
--- /dev/null
+++ b/examples/simple-example/src/Main.scala
@@ -0,0 +1,3 @@
+object Main extends App {
+ println("Hello World")
+}
diff --git a/examples/sonatype-release-example/README.md b/examples/sonatype-release-example/README.md
new file mode 100644
index 0000000..a099036
--- /dev/null
+++ b/examples/sonatype-release-example/README.md
@@ -0,0 +1 @@
+TBD
diff --git a/examples/sonatype-release-example/build/build.scala b/examples/sonatype-release-example/build/build.scala
new file mode 100644
index 0000000..6af452d
--- /dev/null
+++ b/examples/sonatype-release-example/build/build.scala
@@ -0,0 +1,31 @@
+import java.net.URL
+
+import cbt._
+
+class Build(val context: Context) extends SonatypeRelease {
+ def groupId: String = "com.github.rockjam"
+ def defaultVersion: String = "0.0.15"
+ def name: String = "cbt-sonatype"
+
+ def description: String = "Plugin for CBT to release artifacts to sonatype OSS"
+ def developers: Seq[Developer] = Seq(
+ Developer(
+ "rockjam",
+ "Nikolay Tatarinov",
+ "GMT+3",
+ new URL("https://github.com/rockjam")
+ )
+ )
+ def inceptionYear: Int = 2016
+ def licenses: Seq[cbt.License] = Seq(License.Apache2)
+ def organization: Option[cbt.Organization] = None
+ def scmConnection: String = ""
+ def scmUrl: String = "https://github.com/rockjam/cbt-sonatype.git"
+ def url: java.net.URL = new URL("https://github.com/rockjam/cbt-sonatype")
+
+ override def dependencies =
+ super.dependencies ++
+ Resolver( mavenCentral ).bind(
+ ScalaDependency("com.chuusai", "shapeless", "2.3.2")
+ )
+}
diff --git a/examples/sonatype-release-example/build/build/build.scala b/examples/sonatype-release-example/build/build/build.scala
new file mode 100644
index 0000000..a47d3e1
--- /dev/null
+++ b/examples/sonatype-release-example/build/build/build.scala
@@ -0,0 +1,5 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild {
+ override def dependencies = super.dependencies :+ plugins.sonatypeRelease
+}
diff --git a/examples/sonatype-release-example/src/Main.scala b/examples/sonatype-release-example/src/Main.scala
new file mode 100644
index 0000000..5e03d27
--- /dev/null
+++ b/examples/sonatype-release-example/src/Main.scala
@@ -0,0 +1,3 @@
+object Main extends App {
+ println("This is serious app that does nothing, but has shapeless dependency")
+}
diff --git a/examples/uber-jar-example/README.md b/examples/uber-jar-example/README.md
new file mode 100644
index 0000000..2460084
--- /dev/null
+++ b/examples/uber-jar-example/README.md
@@ -0,0 +1,41 @@
+### Uber-jar plugin example
+
+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, main class is `Main`. You can provide custom main class via overriding `uberJarMainClass` task.
+
+To run your main class you can execute `java -jar your-jar-name.jar`.
+
+You can also run scala REPL with your jar classpath and classes with this command: `scala -cp your-jar-name.jar`.
+
+In scala REPL you will have access to all your project classes and dependencies.
+
+```
+scala -cp uber-jar-example-0.0.1.jar
+Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_72).
+Type in expressions for evaluation. Or try :help.
+
+scala> import com.github.someguy.ImportantLib
+import com.github.someguy.ImportantLib
+
+scala> ImportantLib.add(1,2)
+res0: Int = 3
+
+scala> ImportantLib.currentDirectory
+Current directory is: /Users/rockjam/projects/cbt/examples/uber-jar-example/target
+
+scala> Main.main(Array.empty)
+fooo
+Current directory is: /Users/rockjam/projects/cbt/examples/uber-jar-example/target
+not empty list
+
+scala> import shapeless._
+import shapeless._
+
+scala> 1 :: "String" :: 3 :: HNil
+res3: shapeless.::[Int,shapeless.::[String,shapeless.::[Int,shapeless.HNil]]] = 1 :: String :: 3 :: HNil
+```
diff --git a/examples/uber-jar-example/build/build.scala b/examples/uber-jar-example/build/build.scala
new file mode 100644
index 0000000..fec58ae
--- /dev/null
+++ b/examples/uber-jar-example/build/build.scala
@@ -0,0 +1,16 @@
+import cbt._
+
+class Build(val context: Context) extends BaseBuild with UberJar {
+
+ override def projectName: String = "uber-jar-example"
+
+ override def dependencies = super.dependencies ++
+ Resolver( mavenCentral ).bind(
+ ScalaDependency("com.chuusai", "shapeless", "2.3.1"),
+ ScalaDependency("com.lihaoyi", "fansi", "0.1.3"),
+ ScalaDependency("org.typelevel", "cats", "0.6.0")
+ )
+
+ override def uberJarName = projectName + "-0.0.1" + ".jar"
+
+}
diff --git a/examples/uber-jar-example/build/build/build.scala b/examples/uber-jar-example/build/build/build.scala
new file mode 100644
index 0000000..2938ffd
--- /dev/null
+++ b/examples/uber-jar-example/build/build/build.scala
@@ -0,0 +1,5 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild {
+ override def dependencies = super.dependencies :+ plugins.uberJar
+}
diff --git a/examples/uber-jar-example/src/Main.scala b/examples/uber-jar-example/src/Main.scala
new file mode 100644
index 0000000..f60634f
--- /dev/null
+++ b/examples/uber-jar-example/src/Main.scala
@@ -0,0 +1,21 @@
+import scala.concurrent.{ Await, Future }
+import scala.concurrent.duration._
+
+import com.github.someguy.ImportantLib
+
+object Main extends App {
+ println("fooo")
+ val futureRes = Await.result(Future.successful(1), 5.seconds)
+
+ ImportantLib.currentDirectory()
+
+ val hlist = {
+ import shapeless._
+ 1 :: "string" :: 3 :: HNil
+ }
+
+ List(1, 2, 4, 5, 6) match {
+ case h :: _ ⇒ println("not empty list")
+ case Nil ⇒ println("empty list")
+ }
+}
diff --git a/examples/uber-jar-example/src/com/github/someguy/ImportantLib.scala b/examples/uber-jar-example/src/com/github/someguy/ImportantLib.scala
new file mode 100644
index 0000000..34baf2f
--- /dev/null
+++ b/examples/uber-jar-example/src/com/github/someguy/ImportantLib.scala
@@ -0,0 +1,11 @@
+package com.github.someguy
+
+import java.nio.file.Paths
+
+object ImportantLib {
+ def add(a: Int, b: Int): Int = a + b
+ def currentDirectory() = {
+ println(fansi.Color.Green(s"Current directory is: ${Paths.get("").toAbsolutePath}"))
+ }
+
+}
diff --git a/examples/wartremover-example/build/build.scala b/examples/wartremover-example/build/build.scala
new file mode 100644
index 0000000..c715f20
--- /dev/null
+++ b/examples/wartremover-example/build/build.scala
@@ -0,0 +1,9 @@
+import cbt._
+
+import org.wartremover.warts.{ Null, Var }
+import org.wartremover.WartTraverser
+
+class Build(val context: Context) extends BuildBuild with WartRemover {
+
+ override def wartremoverErrors: Seq[WartTraverser] = Seq(Var, Null)
+}
diff --git a/examples/wartremover-example/build/build/build.scala b/examples/wartremover-example/build/build/build.scala
new file mode 100644
index 0000000..eb2f193
--- /dev/null
+++ b/examples/wartremover-example/build/build/build.scala
@@ -0,0 +1,5 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild {
+ override def dependencies = super.dependencies :+ plugins.wartremover
+}
diff --git a/examples/wartremover-example/src/Main.scala b/examples/wartremover-example/src/Main.scala
new file mode 100644
index 0000000..dc4b3da
--- /dev/null
+++ b/examples/wartremover-example/src/Main.scala
@@ -0,0 +1,5 @@
+object Main {
+ def main(args: Array[String]): Unit = {
+ var nastyVar = null
+ }
+}
diff --git a/nailgun_launcher/CbtURLClassLoader.java b/nailgun_launcher/CbtURLClassLoader.java
new file mode 100644
index 0000000..38fc905
--- /dev/null
+++ b/nailgun_launcher/CbtURLClassLoader.java
@@ -0,0 +1,59 @@
+package cbt;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import static cbt.Stage0Lib.*;
+import java.util.concurrent.ConcurrentHashMap;
+public class CbtURLClassLoader extends java.net.URLClassLoader{
+ public String toString(){
+ return (
+ super.toString()
+ + "(\n "
+ + Arrays.toString(getURLs())
+ + ",\n "
+ + join("\n ",(getParent() == null?"":getParent().toString()).split("\n"))
+ + "\n)"
+ );
+ }
+ ClassLoaderCache2<Class> cache = new ClassLoaderCache2<Class>(
+ new ConcurrentHashMap<String, Object>(),
+ new ConcurrentHashMap<Object, Class>()
+ );
+ public Class loadClass(String name) throws ClassNotFoundException{
+ Class _class = super.loadClass(name);
+ if(_class == null) throw new ClassNotFoundException(name);
+ else return _class;
+ }
+ public Class loadClass(String name, Boolean resolve) throws ClassNotFoundException{
+ //System.out.println("loadClass("+name+") on \n"+this);
+ if(!cache.contains(name))
+ try{
+ cache.put(super.loadClass(name, resolve), name);
+ } catch (ClassNotFoundException e){
+ cache.put(Object.class, name);
+ }
+ Class _class = cache.get(name);
+ if(_class == Object.class){
+ if( name == "java.lang.Object" )
+ return Object.class;
+ else return null;
+ } else {
+ return _class;
+ }
+ }
+ void assertExist(URL[] urls){
+ for(URL url: urls){
+ if(!new File(url.getPath()).exists()){
+ throw new AssertionError("File does not exist when trying to create CbtURLClassLoader: "+url);
+ }
+ }
+ }
+ public CbtURLClassLoader(URL[] urls, ClassLoader parent){
+ super(urls, parent);
+ assertExist(urls);
+ }
+ public CbtURLClassLoader(URL[] urls){
+ super(urls, null);
+ assertExist(urls);
+ }
+} \ No newline at end of file
diff --git a/nailgun_launcher/ClassLoaderCache2.java b/nailgun_launcher/ClassLoaderCache2.java
new file mode 100644
index 0000000..bf9ca3b
--- /dev/null
+++ b/nailgun_launcher/ClassLoaderCache2.java
@@ -0,0 +1,37 @@
+package cbt;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import static java.io.File.pathSeparator;
+import static cbt.Stage0Lib.*;
+
+final class ClassLoaderCache2<T>{
+ ConcurrentHashMap<String,Object> keys;
+ ConcurrentHashMap<Object,T> values;
+
+ public ClassLoaderCache2(
+ ConcurrentHashMap<String,Object> keys,
+ ConcurrentHashMap<Object,T> values
+ ){
+ this.keys = keys;
+ this.values = values;
+ }
+
+ public T get( String key ){
+ return values.get(
+ keys.get( key )
+ );
+ }
+
+ public Boolean contains( String key ){
+ return keys.containsKey( key );
+ }
+
+ public T put( T value, String key ){
+ LockableKey2 keyObject = new LockableKey2();
+ keys.put( key, keyObject );
+ values.put( keyObject, value );
+ return value;
+ }
+}
+class LockableKey2{} \ No newline at end of file
diff --git a/nailgun_launcher/EarlyDependencies.java b/nailgun_launcher/EarlyDependencies.java
new file mode 100644
index 0000000..4ffbdfd
--- /dev/null
+++ b/nailgun_launcher/EarlyDependencies.java
@@ -0,0 +1,143 @@
+// This file was auto-generated using `cbt tools cbtEarlyDependencies`
+package cbt;
+import java.io.*;
+import java.nio.file.*;
+import java.net.*;
+import java.security.*;
+import static cbt.Stage0Lib.*;
+import static cbt.NailgunLauncher.*;
+
+class EarlyDependencies{
+
+ /** ClassLoader for stage1 */
+ ClassLoader classLoader;
+ String[] classpathArray;
+ /** ClassLoader for zinc */
+ ClassLoader zinc;
+
+ String scalaReflect_2_11_8_File;
+ String scalaCompiler_2_11_8_File;
+ String scalaXml_1_0_5_File;
+ String scalaLibrary_2_11_8_File;
+ String zinc_0_3_9_File;
+ String incrementalCompiler_0_13_9_File;
+ String compilerInterface_0_13_9_File;
+ String scalaCompiler_2_10_5_File;
+ String sbtInterface_0_13_9_File;
+ String scalaReflect_2_10_5_File;
+ String scalaLibrary_2_10_5_File;
+
+ public EarlyDependencies(
+ String mavenCache, String mavenUrl, ClassLoaderCache2<ClassLoader> classLoaderCache, ClassLoader rootClassLoader
+ ) throws Throwable {
+ scalaReflect_2_11_8_File = mavenCache + "/org/scala-lang/scala-reflect/2.11.8/scala-reflect-2.11.8.jar";
+ scalaCompiler_2_11_8_File = mavenCache + "/org/scala-lang/scala-compiler/2.11.8/scala-compiler-2.11.8.jar";
+ scalaXml_1_0_5_File = mavenCache + "/org/scala-lang/modules/scala-xml_2.11/1.0.5/scala-xml_2.11-1.0.5.jar";
+ scalaLibrary_2_11_8_File = mavenCache + "/org/scala-lang/scala-library/2.11.8/scala-library-2.11.8.jar";
+ zinc_0_3_9_File = mavenCache + "/com/typesafe/zinc/zinc/0.3.9/zinc-0.3.9.jar";
+ incrementalCompiler_0_13_9_File = mavenCache + "/com/typesafe/sbt/incremental-compiler/0.13.9/incremental-compiler-0.13.9.jar";
+ compilerInterface_0_13_9_File = mavenCache + "/com/typesafe/sbt/compiler-interface/0.13.9/compiler-interface-0.13.9-sources.jar";
+ scalaCompiler_2_10_5_File = mavenCache + "/org/scala-lang/scala-compiler/2.10.5/scala-compiler-2.10.5.jar";
+ sbtInterface_0_13_9_File = mavenCache + "/com/typesafe/sbt/sbt-interface/0.13.9/sbt-interface-0.13.9.jar";
+ scalaReflect_2_10_5_File = mavenCache + "/org/scala-lang/scala-reflect/2.10.5/scala-reflect-2.10.5.jar";
+ scalaLibrary_2_10_5_File = mavenCache + "/org/scala-lang/scala-library/2.10.5/scala-library-2.10.5.jar";
+
+ download(new URL(mavenUrl + "/org/scala-lang/scala-reflect/2.11.8/scala-reflect-2.11.8.jar"), Paths.get(scalaReflect_2_11_8_File), "b74530deeba742ab4f3134de0c2da0edc49ca361");
+ download(new URL(mavenUrl + "/org/scala-lang/scala-compiler/2.11.8/scala-compiler-2.11.8.jar"), Paths.get(scalaCompiler_2_11_8_File), "fe1285c9f7b58954c5ef6d80b59063569c065e9a");
+
+ // org.scala-lang:scala-library:2.10.5
+ download(new URL(mavenUrl + "/org/scala-lang/scala-library/2.10.5/scala-library-2.10.5.jar"), Paths.get(scalaLibrary_2_10_5_File), "57ac67a6cf6fd591e235c62f8893438e8d10431d");
+
+ String[] scalaLibrary_2_10_5_ClasspathArray = new String[]{scalaLibrary_2_10_5_File};
+ String scalaLibrary_2_10_5_Classpath = classpath( scalaLibrary_2_10_5_ClasspathArray );
+ ClassLoader scalaLibrary_2_10_5_ =
+ classLoaderCache.contains( scalaLibrary_2_10_5_Classpath )
+ ? classLoaderCache.get( scalaLibrary_2_10_5_Classpath )
+ : classLoaderCache.put( classLoader( scalaLibrary_2_10_5_File, rootClassLoader ), scalaLibrary_2_10_5_Classpath );
+
+ // org.scala-lang:scala-reflect:2.10.5
+ download(new URL(mavenUrl + "/org/scala-lang/scala-reflect/2.10.5/scala-reflect-2.10.5.jar"), Paths.get(scalaReflect_2_10_5_File), "7392facb48876c67a89fcb086112b195f5f6bbc3");
+
+ String[] scalaReflect_2_10_5_ClasspathArray = new String[]{scalaLibrary_2_10_5_File, scalaReflect_2_10_5_File};
+ String scalaReflect_2_10_5_Classpath = classpath( scalaReflect_2_10_5_ClasspathArray );
+ ClassLoader scalaReflect_2_10_5_ =
+ classLoaderCache.contains( scalaReflect_2_10_5_Classpath )
+ ? classLoaderCache.get( scalaReflect_2_10_5_Classpath )
+ : classLoaderCache.put( classLoader( scalaReflect_2_10_5_File, scalaLibrary_2_10_5_ ), scalaReflect_2_10_5_Classpath );
+
+ // com.typesafe.sbt:sbt-interface:0.13.9
+ download(new URL(mavenUrl + "/com/typesafe/sbt/sbt-interface/0.13.9/sbt-interface-0.13.9.jar"), Paths.get(sbtInterface_0_13_9_File), "29848631415402c81b732e919be88f268df37250");
+
+ String[] sbtInterface_0_13_9_ClasspathArray = new String[]{sbtInterface_0_13_9_File, scalaLibrary_2_10_5_File, scalaReflect_2_10_5_File};
+ String sbtInterface_0_13_9_Classpath = classpath( sbtInterface_0_13_9_ClasspathArray );
+ ClassLoader sbtInterface_0_13_9_ =
+ classLoaderCache.contains( sbtInterface_0_13_9_Classpath )
+ ? classLoaderCache.get( sbtInterface_0_13_9_Classpath )
+ : classLoaderCache.put( classLoader( sbtInterface_0_13_9_File, scalaReflect_2_10_5_ ), sbtInterface_0_13_9_Classpath );
+
+ // org.scala-lang:scala-compiler:2.10.5
+ download(new URL(mavenUrl + "/org/scala-lang/scala-compiler/2.10.5/scala-compiler-2.10.5.jar"), Paths.get(scalaCompiler_2_10_5_File), "f0f5bb444ca26a6e489af3dd35e24f7e2d2d118e");
+
+ String[] scalaCompiler_2_10_5_ClasspathArray = new String[]{sbtInterface_0_13_9_File, scalaCompiler_2_10_5_File, scalaLibrary_2_10_5_File, scalaReflect_2_10_5_File};
+ String scalaCompiler_2_10_5_Classpath = classpath( scalaCompiler_2_10_5_ClasspathArray );
+ ClassLoader scalaCompiler_2_10_5_ =
+ classLoaderCache.contains( scalaCompiler_2_10_5_Classpath )
+ ? classLoaderCache.get( scalaCompiler_2_10_5_Classpath )
+ : classLoaderCache.put( classLoader( scalaCompiler_2_10_5_File, sbtInterface_0_13_9_ ), scalaCompiler_2_10_5_Classpath );
+
+ // com.typesafe.sbt:compiler-interface:0.13.9
+ download(new URL(mavenUrl + "/com/typesafe/sbt/compiler-interface/0.13.9/compiler-interface-0.13.9-sources.jar"), Paths.get(compilerInterface_0_13_9_File), "2311addbed1182916ad00f83c57c0eeca1af382b");
+
+ String[] compilerInterface_0_13_9_ClasspathArray = new String[]{compilerInterface_0_13_9_File, sbtInterface_0_13_9_File, scalaCompiler_2_10_5_File, scalaLibrary_2_10_5_File, scalaReflect_2_10_5_File};
+ String compilerInterface_0_13_9_Classpath = classpath( compilerInterface_0_13_9_ClasspathArray );
+ ClassLoader compilerInterface_0_13_9_ =
+ classLoaderCache.contains( compilerInterface_0_13_9_Classpath )
+ ? classLoaderCache.get( compilerInterface_0_13_9_Classpath )
+ : classLoaderCache.put( classLoader( compilerInterface_0_13_9_File, scalaCompiler_2_10_5_ ), compilerInterface_0_13_9_Classpath );
+
+ // com.typesafe.sbt:incremental-compiler:0.13.9
+ download(new URL(mavenUrl + "/com/typesafe/sbt/incremental-compiler/0.13.9/incremental-compiler-0.13.9.jar"), Paths.get(incrementalCompiler_0_13_9_File), "fbbf1cadbed058aa226643e83543c35de43b13f0");
+
+ String[] incrementalCompiler_0_13_9_ClasspathArray = new String[]{compilerInterface_0_13_9_File, incrementalCompiler_0_13_9_File, sbtInterface_0_13_9_File, scalaCompiler_2_10_5_File, scalaLibrary_2_10_5_File, scalaReflect_2_10_5_File};
+ String incrementalCompiler_0_13_9_Classpath = classpath( incrementalCompiler_0_13_9_ClasspathArray );
+ ClassLoader incrementalCompiler_0_13_9_ =
+ classLoaderCache.contains( incrementalCompiler_0_13_9_Classpath )
+ ? classLoaderCache.get( incrementalCompiler_0_13_9_Classpath )
+ : classLoaderCache.put( classLoader( incrementalCompiler_0_13_9_File, compilerInterface_0_13_9_ ), incrementalCompiler_0_13_9_Classpath );
+
+ // com.typesafe.zinc:zinc:0.3.9
+ download(new URL(mavenUrl + "/com/typesafe/zinc/zinc/0.3.9/zinc-0.3.9.jar"), Paths.get(zinc_0_3_9_File), "46a4556d1f36739879f4b2cc19a73d12b3036e9a");
+
+ String[] zinc_0_3_9_ClasspathArray = new String[]{compilerInterface_0_13_9_File, incrementalCompiler_0_13_9_File, sbtInterface_0_13_9_File, zinc_0_3_9_File, scalaCompiler_2_10_5_File, scalaLibrary_2_10_5_File, scalaReflect_2_10_5_File};
+ String zinc_0_3_9_Classpath = classpath( zinc_0_3_9_ClasspathArray );
+ ClassLoader zinc_0_3_9_ =
+ classLoaderCache.contains( zinc_0_3_9_Classpath )
+ ? classLoaderCache.get( zinc_0_3_9_Classpath )
+ : classLoaderCache.put( classLoader( zinc_0_3_9_File, incrementalCompiler_0_13_9_ ), zinc_0_3_9_Classpath );
+
+ // org.scala-lang:scala-library:2.11.8
+ download(new URL(mavenUrl + "/org/scala-lang/scala-library/2.11.8/scala-library-2.11.8.jar"), Paths.get(scalaLibrary_2_11_8_File), "ddd5a8bced249bedd86fb4578a39b9fb71480573");
+
+ String[] scalaLibrary_2_11_8_ClasspathArray = new String[]{scalaLibrary_2_11_8_File};
+ String scalaLibrary_2_11_8_Classpath = classpath( scalaLibrary_2_11_8_ClasspathArray );
+ ClassLoader scalaLibrary_2_11_8_ =
+ classLoaderCache.contains( scalaLibrary_2_11_8_Classpath )
+ ? classLoaderCache.get( scalaLibrary_2_11_8_Classpath )
+ : classLoaderCache.put( classLoader( scalaLibrary_2_11_8_File, rootClassLoader ), scalaLibrary_2_11_8_Classpath );
+
+ // org.scala-lang.modules:scala-xml_2.11:1.0.5
+ download(new URL(mavenUrl + "/org/scala-lang/modules/scala-xml_2.11/1.0.5/scala-xml_2.11-1.0.5.jar"), Paths.get(scalaXml_1_0_5_File), "77ac9be4033768cf03cc04fbd1fc5e5711de2459");
+
+ String[] scalaXml_1_0_5_ClasspathArray = new String[]{scalaXml_1_0_5_File, scalaLibrary_2_11_8_File};
+ String scalaXml_1_0_5_Classpath = classpath( scalaXml_1_0_5_ClasspathArray );
+ ClassLoader scalaXml_1_0_5_ =
+ classLoaderCache.contains( scalaXml_1_0_5_Classpath )
+ ? classLoaderCache.get( scalaXml_1_0_5_Classpath )
+ : classLoaderCache.put( classLoader( scalaXml_1_0_5_File, scalaLibrary_2_11_8_ ), scalaXml_1_0_5_Classpath );
+
+ classLoader = scalaXml_1_0_5_;
+ classpathArray = scalaXml_1_0_5_ClasspathArray;
+
+ zinc = zinc_0_3_9_;
+ }
+}
diff --git a/nailgun_launcher/MultiClassLoader2.java b/nailgun_launcher/MultiClassLoader2.java
new file mode 100644
index 0000000..46e7527
--- /dev/null
+++ b/nailgun_launcher/MultiClassLoader2.java
@@ -0,0 +1,46 @@
+package cbt;
+import java.net.*;
+import java.io.*;
+import java.util.*;
+
+public class MultiClassLoader2 extends ClassLoader{
+ public ClassLoader[] parents;
+ public ClassLoader[] parents(){
+ return this.parents;
+ }
+ public MultiClassLoader2(ClassLoader... parents){
+ super(null);
+ this.parents = parents;
+ }
+ public Class findClass(String name) throws ClassNotFoundException{
+ for(ClassLoader parent: parents){
+ try{
+ return parent.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ if(e.getMessage() != name) throw e;
+ }
+ }
+ // FIXME: have a logger in Java land
+ // System.err.println("NOT FOUND: "+name);
+ return null;
+ }
+ public URL findResource(String name){
+ for(ClassLoader parent: parents){
+ URL res = parent.getResource(name);
+ if(res != null) return res;
+ }
+ return null;
+ }
+ public Enumeration<URL> findResources(String name) throws IOException{
+ ArrayList<URL> resources = new ArrayList<URL>();
+ for(ClassLoader parent: parents){
+ for(URL resource: Collections.list(parent.getResources(name))){
+ resources.add( resource );
+ }
+ }
+ return Collections.enumeration(resources);
+ }
+ public String toString(){
+ return super.toString() + "(" + Arrays.toString(parents) +")";
+ }
+}
diff --git a/nailgun_launcher/NailgunLauncher.java b/nailgun_launcher/NailgunLauncher.java
new file mode 100644
index 0000000..6639218
--- /dev/null
+++ b/nailgun_launcher/NailgunLauncher.java
@@ -0,0 +1,200 @@
+package cbt;
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.security.*;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import static cbt.Stage0Lib.*;
+import static java.io.File.pathSeparator;
+
+/**
+ * This launcher allows to start the JVM without loading anything else permanently into its
+ * classpath except for the launcher itself. That's why it is written in Java without
+ * dependencies outside the JDK.
+ */
+public class NailgunLauncher{
+ /** Persistent cache for caching classloaders for the JVM life time. */
+ private final static ClassLoaderCache2<ClassLoader> classLoaderCache = new ClassLoaderCache2<ClassLoader>(
+ new ConcurrentHashMap<String,Object>(),
+ new ConcurrentHashMap<Object,ClassLoader>()
+ );
+
+ public final static SecurityManager initialSecurityManager
+ = System.getSecurityManager();
+
+ public static String TARGET = System.getenv("TARGET");
+ private static String NAILGUN = "nailgun_launcher/";
+ private static String STAGE1 = "stage1/";
+
+ @SuppressWarnings("unchecked")
+ public static Object getBuild( Object context ) throws Throwable{
+ BuildStage1Result res = buildStage1(
+ (Boolean) get(context, "cbtHasChangedCompat"),
+ (Long) get(context, "startCompat"),
+ ((File) get(context, "cache")).toString() + "/",
+ ((File) get(context, "cbtHome")).toString(),
+ ((File) get(context, "compatibilityTarget")).toString() + "/",
+ new ClassLoaderCache2<ClassLoader>(
+ (ConcurrentHashMap<String,Object>) get(context, "permanentKeys"),
+ (ConcurrentHashMap<Object,ClassLoader>) get(context, "permanentClassLoaders")
+ )
+ );
+ return
+ res
+ .classLoader
+ .loadClass("cbt.Stage1")
+ .getMethod( "getBuild", Object.class, Boolean.class )
+ .invoke(null, context, res.changed);
+ }
+
+ public static void main( String[] args ) throws Throwable {
+ Long _start = System.currentTimeMillis();
+ if(args[0].equals("check-alive")){
+ System.exit(33);
+ return;
+ }
+
+ System.setSecurityManager( new TrapSecurityManager() );
+ installProxySettings();
+ String[] diff = args[0].split("\\.");
+ long start = _start - (Long.parseLong(diff[0]) * 1000L) - Long.parseLong(diff[1]);
+
+ // if nailgun didn't install it's threadLocal stdout/err replacements, install CBT's.
+ // this hack allows to later swap out System.out/err while still affecting things like
+ // scala.Console, which captured them at startup
+ try{
+ System.out.getClass().getDeclaredField("streams"); // nailgun ThreadLocalPrintStream
+ assert(System.out.getClass().getName() == "com.martiansoftware.nailgun.ThreadLocalPrintStream");
+ } catch( NoSuchFieldException e ){
+ System.setOut( new PrintStream(new ThreadLocalOutputStream(System.out), true) );
+ }
+ try{
+ System.err.getClass().getDeclaredField("streams"); // nailgun ThreadLocalPrintStream
+ assert(System.err.getClass().getName() == "com.martiansoftware.nailgun.ThreadLocalPrintStream");
+ } catch( NoSuchFieldException e ){
+ System.setErr( new PrintStream(new ThreadLocalOutputStream(System.err), true) );
+ }
+ // ---------------------
+
+ _assert(System.getenv("CBT_HOME") != null, "environment variable CBT_HOME not defined");
+ String CBT_HOME = System.getenv("CBT_HOME");
+ String cache = CBT_HOME + "/cache/";
+ String compatibilityTarget = CBT_HOME + "/compatibility/" + TARGET;
+ BuildStage1Result res = buildStage1(
+ false, start, cache, CBT_HOME, compatibilityTarget, classLoaderCache
+ );
+
+ try{
+ System.exit(
+ (Integer) res
+ .classLoader
+ .loadClass("cbt.Stage1")
+ .getMethod(
+ "run",
+ String[].class, File.class, File.class, Boolean.class,
+ File.class, Long.class, ConcurrentHashMap.class, ConcurrentHashMap.class
+ )
+ .invoke(
+ null,
+ (Object) args, new File(cache), new File(CBT_HOME), res.changed,
+ new File(compatibilityTarget), start, classLoaderCache.keys, classLoaderCache.values
+ )
+ );
+ } catch (java.lang.reflect.InvocationTargetException e) {
+ throw unwrapInvocationTargetException(e);
+ }
+ }
+
+ public static Throwable unwrapInvocationTargetException(Throwable e){
+ if(e instanceof java.lang.reflect.InvocationTargetException){
+ return unwrapInvocationTargetException(((java.lang.reflect.InvocationTargetException) e).getCause());
+ } else{
+ return e;
+ }
+ }
+
+ public static BuildStage1Result buildStage1(
+ Boolean changed, long start, String cache, String cbtHome, String compatibilityTarget, ClassLoaderCache2<ClassLoader> classLoaderCache
+ ) throws Throwable {
+ _assert(TARGET != null, "environment variable TARGET not defined");
+ String nailgunTarget = cbtHome + "/" + NAILGUN + TARGET;
+ String stage1Sources = cbtHome + "/" + STAGE1;
+ String stage1Target = stage1Sources + TARGET;
+ File compatibilitySources = new File(cbtHome + "/compatibility");
+ String mavenCache = cache + "maven";
+ String mavenUrl = "https://repo1.maven.org/maven2";
+
+ ClassLoader rootClassLoader = new CbtURLClassLoader( new URL[]{}, ClassLoader.getSystemClassLoader().getParent() ); // wrap for caching
+ EarlyDependencies earlyDeps = new EarlyDependencies(mavenCache, mavenUrl, classLoaderCache, rootClassLoader);
+
+ ClassLoader compatibilityClassLoader;
+ if(!compatibilityTarget.startsWith(cbtHome)){
+ compatibilityClassLoader = classLoaderCache.get( compatibilityTarget );
+ } else {
+ List<File> compatibilitySourceFiles = new ArrayList<File>();
+ for( File f: compatibilitySources.listFiles() ){
+ if( f.isFile() && (f.toString().endsWith(".scala") || f.toString().endsWith(".java")) ){
+ compatibilitySourceFiles.add(f);
+ }
+ }
+ changed = compile(changed, start, "", compatibilityTarget, earlyDeps, compatibilitySourceFiles);
+
+ if( classLoaderCache.contains( compatibilityTarget ) ){
+ compatibilityClassLoader = classLoaderCache.get( compatibilityTarget );
+ } else {
+ compatibilityClassLoader = classLoaderCache.put( classLoader(compatibilityTarget, rootClassLoader), compatibilityTarget );
+ }
+ }
+
+ String[] nailgunClasspathArray = append( earlyDeps.classpathArray, nailgunTarget );
+ String nailgunClasspath = classpath( nailgunClasspathArray );
+ ClassLoader nailgunClassLoader = new CbtURLClassLoader( new URL[]{}, NailgunLauncher.class.getClassLoader() ); // wrap for caching
+ if( !classLoaderCache.contains( nailgunClasspath ) ){
+ nailgunClassLoader = classLoaderCache.put( nailgunClassLoader, nailgunClasspath );
+ }
+
+ String[] stage1ClasspathArray =
+ append( append( nailgunClasspathArray, compatibilityTarget ), stage1Target );
+ String stage1Classpath = classpath( stage1ClasspathArray );
+
+ List<File> stage1SourceFiles = new ArrayList<File>();
+ for( File f: new File(stage1Sources).listFiles() ){
+ if( f.isFile() && f.toString().endsWith(".scala") ){
+ stage1SourceFiles.add(f);
+ }
+ }
+ changed = compile(changed, start, stage1Classpath, stage1Target, earlyDeps, stage1SourceFiles);
+
+ ClassLoader stage1classLoader;
+ if( !changed && classLoaderCache.contains( stage1Classpath ) ){
+ stage1classLoader = classLoaderCache.get( stage1Classpath );
+ } else {
+ stage1classLoader =
+ classLoaderCache.put(
+ classLoader(
+ stage1Target,
+ new MultiClassLoader2(
+ nailgunClassLoader,
+ compatibilityClassLoader,
+ earlyDeps.classLoader
+ )
+ ),
+ stage1Classpath
+ );
+ }
+
+ return new BuildStage1Result(
+ changed,
+ stage1classLoader
+ );
+ }
+}
+class BuildStage1Result{
+ Boolean changed;
+ ClassLoader classLoader;
+ BuildStage1Result( Boolean changed, ClassLoader classLoader ){
+ this.changed = changed;
+ this.classLoader = classLoader;
+ }
+}
diff --git a/nailgun_launcher/ProxySecurityManager.java b/nailgun_launcher/ProxySecurityManager.java
new file mode 100644
index 0000000..1a6e49c
--- /dev/null
+++ b/nailgun_launcher/ProxySecurityManager.java
@@ -0,0 +1,102 @@
+package cbt;
+
+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.
+*/
+public class ProxySecurityManager extends SecurityManager{
+ private SecurityManager target;
+ public 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/nailgun_launcher/Stage0Lib.java b/nailgun_launcher/Stage0Lib.java
new file mode 100644
index 0000000..452bdae
--- /dev/null
+++ b/nailgun_launcher/Stage0Lib.java
@@ -0,0 +1,197 @@
+package cbt;
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.nio.*;
+import java.nio.file.*;
+import java.security.*;
+import java.util.*;
+import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
+import static java.io.File.pathSeparator;
+import static cbt.NailgunLauncher.*;
+import java.nio.file.*;
+import java.nio.file.attribute.FileTime;
+
+public class Stage0Lib{
+ public static void _assert(Boolean condition, Object msg){
+ if(!condition){
+ throw new AssertionError("Assertion failed: "+msg);
+ }
+ }
+
+ public static int runMain(String cls, String[] args, ClassLoader cl) throws Throwable{
+ Boolean trapExitCodeBefore = TrapSecurityManager.trapExitCode().get();
+ try{
+ TrapSecurityManager.trapExitCode().set(true);
+ cl.loadClass(cls)
+ .getMethod("main", String[].class)
+ .invoke( null, (Object) args);
+ return 0;
+ }catch( InvocationTargetException exception ){
+ Throwable cause = exception.getCause();
+ if(TrapSecurityManager.isTrappedExit(cause)){
+ return TrapSecurityManager.exitCode(cause);
+ }
+ throw exception;
+ } finally {
+ TrapSecurityManager.trapExitCode().set(trapExitCodeBefore);
+ }
+ }
+
+ public static Object get(Object object, String method) throws Throwable{
+ return object.getClass().getMethod( method ).invoke(object);
+ }
+
+ public static String classpath( String... files ){
+ Arrays.sort(files);
+ return join( pathSeparator, files );
+ }
+
+ public static File write(File file, String content, OpenOption... options) throws Throwable{
+ file.getParentFile().mkdirs();
+ Files.write(file.toPath(), content.getBytes(), options);
+ return file;
+ }
+
+ public static Boolean compile(
+ Boolean changed, Long start, String classpath, String target,
+ EarlyDependencies earlyDeps, List<File> sourceFiles
+ ) throws Throwable{
+ File statusFile = new File( new File(target) + ".last-success" );
+ Long lastSuccessfullCompile = statusFile.lastModified();
+ for( File file: sourceFiles ){
+ if( file.lastModified() > lastSuccessfullCompile ){
+ changed = true;
+ break;
+ }
+ }
+ if(changed){
+ List<String> zincArgs = new ArrayList<String>(
+ Arrays.asList(
+ new String[]{
+ "-scala-compiler", earlyDeps.scalaCompiler_2_11_8_File,
+ "-scala-library", earlyDeps.scalaLibrary_2_11_8_File,
+ "-scala-extra", earlyDeps.scalaReflect_2_11_8_File,
+ "-sbt-interface", earlyDeps.sbtInterface_0_13_9_File,
+ "-compiler-interface", earlyDeps.compilerInterface_0_13_9_File,
+ "-cp", classpath,
+ "-d", target,
+ "-S-deprecation",
+ "-S-feature",
+ "-S-unchecked"
+ }
+ )
+ );
+
+ for( File f: sourceFiles ){
+ zincArgs.add(f.toString());
+ }
+
+ PrintStream oldOut = System.out;
+ try{
+ System.setOut(System.err);
+ int exitCode = runMain( "com.typesafe.zinc.Main", zincArgs.toArray(new String[zincArgs.size()]), earlyDeps.zinc );
+ if( exitCode == 0 ){
+ write( statusFile, "" );
+ Files.setLastModifiedTime( statusFile.toPath(), FileTime.fromMillis(start) );
+ } else {
+ System.exit( exitCode );
+ }
+ } finally {
+ System.setOut(oldOut);
+ }
+ }
+ return changed;
+ }
+
+ public static ClassLoader classLoader( String file ) throws Throwable{
+ return new CbtURLClassLoader(
+ new URL[]{ new URL("file:"+file) }
+ );
+ }
+ public static ClassLoader classLoader( String file, ClassLoader parent ) throws Throwable{
+ return new CbtURLClassLoader(
+ new URL[]{ new URL("file:"+file) }, parent
+ );
+ }
+
+ private static String getVarFromEnv(String envKey) {
+ String value = System.getenv(envKey);
+ if(value==null || value.isEmpty()) {
+ value = System.getenv(envKey.toUpperCase());
+ }
+ return value;
+ }
+
+ private static void setProxyfromPropOrEnv(String envKey, String propKeyH, String propKeyP) {
+ String proxyHost = System.getProperty(propKeyH);
+ String proxyPort = System.getProperty(propKeyP);
+ if((proxyHost==null || proxyHost.isEmpty()) && (proxyPort==null || proxyPort.isEmpty())) {
+ String envVar = getVarFromEnv(envKey);
+ if(envVar != null && !envVar.isEmpty()) {
+ String[] proxy = envVar.replaceFirst("^https?://", "").split(":", 2);
+ System.setProperty(propKeyH, proxy[0]);
+ System.setProperty(propKeyP, proxy[1]);
+ }
+ }
+ }
+
+ public static void installProxySettings() throws URISyntaxException {
+ setProxyfromPropOrEnv("http_proxy", "http.proxyHost", "http.proxyPort");
+ setProxyfromPropOrEnv("https_proxy", "https.proxyHost", "https.proxyPort");
+ String nonHosts = System.getProperty("http.nonProxyHosts");
+ if(nonHosts==null || nonHosts.isEmpty()) {
+ String envVar = getVarFromEnv("no_proxy");
+ if(envVar != null && !envVar.isEmpty()) {
+ System.setProperty("http.nonProxyHosts", envVar.replaceAll(",","|"));
+ }
+ }
+ }
+
+ private static final ProxySelector ps = ProxySelector.getDefault();
+
+ public static HttpURLConnection openConnectionConsideringProxy(URL urlString)
+ throws IOException, URISyntaxException {
+ java.net.Proxy proxy = ps.select(urlString.toURI()).get(0);
+ return (HttpURLConnection) urlString.openConnection(proxy);
+ }
+
+ public static void download(URL urlString, Path target, String sha1) throws Throwable {
+ final Path unverified = Paths.get(target+".unverified");
+ if(!Files.exists(target)) {
+ new File(target.toString()).getParentFile().mkdirs();
+ System.err.println("downloading " + urlString);
+ System.err.println("to " + target);
+ final InputStream stream = openConnectionConsideringProxy(urlString).getInputStream();
+ Files.copy(stream, unverified, StandardCopyOption.REPLACE_EXISTING);
+ stream.close();
+ final String checksum = sha1(Files.readAllBytes(unverified));
+ if(sha1 == null || sha1.toLowerCase().equals(checksum)) {
+ Files.move(unverified, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+ } else {
+ System.err.println(target + " checksum does not match.\nExpected: |" + sha1 + "|\nFound: |" + checksum + "|");
+ System.exit(1);
+ }
+ }
+ }
+
+ public static String sha1(byte[] bytes) throws Throwable {
+ final MessageDigest sha1 = MessageDigest.getInstance("SHA1");
+ sha1.update(bytes, 0, bytes.length);
+ return (new HexBinaryAdapter()).marshal(sha1.digest()).toLowerCase();
+ }
+
+ public static String join(String separator, String[] parts){
+ String result = parts[0];
+ for(int i = 1; i < parts.length; i++){
+ result += separator + parts[i];
+ }
+ return result;
+ }
+
+ public static String[] append( String[] array, String item ){
+ String[] copy = Arrays.copyOf(array, array.length + 1);
+ copy[array.length] = item;
+ return copy;
+ }
+}
diff --git a/nailgun_launcher/ThreadLocalOutputStream.java b/nailgun_launcher/ThreadLocalOutputStream.java
new file mode 100644
index 0000000..0b106d7
--- /dev/null
+++ b/nailgun_launcher/ThreadLocalOutputStream.java
@@ -0,0 +1,34 @@
+package cbt;
+import java.io.*;
+
+public class ThreadLocalOutputStream extends OutputStream{
+ final public ThreadLocal<OutputStream> threadLocal;
+ final private OutputStream initialValue;
+
+ public ThreadLocalOutputStream( OutputStream initialValue ){
+ this.initialValue = initialValue;
+ threadLocal = new ThreadLocal<OutputStream>() {
+ @Override protected OutputStream initialValue() {
+ return ThreadLocalOutputStream.this.initialValue;
+ }
+ };
+ }
+
+ public OutputStream get(){
+ return threadLocal.get();
+ }
+
+ public void set( OutputStream outputStream ){
+ threadLocal.set( outputStream );
+ }
+
+ public void write( int b ) throws IOException{
+ // after implementing this I realized NailgunLauncher uses the same hack,
+ // so probably this is not a problem performance
+ get().write(b);
+ }
+
+ public void flush() throws IOException{
+ get().flush();
+ }
+}
diff --git a/nailgun_launcher/TrapSecurityManager.java b/nailgun_launcher/TrapSecurityManager.java
new file mode 100644
index 0000000..48e152b
--- /dev/null
+++ b/nailgun_launcher/TrapSecurityManager.java
@@ -0,0 +1,83 @@
+package cbt;
+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;
+ }
+ };
+
+ public TrapSecurityManager(){
+ super(NailgunLauncher.initialSecurityManager);
+ }
+
+ 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);
+ }
+ }
+
+ 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/plugins/readme.txt b/plugins/readme.txt
new file mode 100644
index 0000000..c3f561f
--- /dev/null
+++ b/plugins/readme.txt
@@ -0,0 +1,5 @@
+This directory is for plugins plugins which are shipped with CBT,
+but need to be explicitly dependended upon in the BuildBuild.
+This is nice for plugins, which themselves have dependencies
+that we do not want CBT to depend on.
+See stage2/plugins/ for built-in plugins, that have no dependencies.
diff --git a/plugins/sbt_layout/SbtLayout.scala b/plugins/sbt_layout/SbtLayout.scala
new file mode 100644
index 0000000..5cd7a03
--- /dev/null
+++ b/plugins/sbt_layout/SbtLayout.scala
@@ -0,0 +1,10 @@
+package cbt
+
+trait SbtLayoutTest extends BaseBuild{
+ override def sources = Seq(projectDirectory ++ "/src/test/scala")
+ override def compileTarget = super.compileTarget.getParentFile ++ "/test-classes"
+}
+
+trait SbtLayoutMain extends BaseBuild{
+ override def sources = Seq( projectDirectory ++ "/src/main/scala" )
+}
diff --git a/plugins/sbt_layout/build/build.scala b/plugins/sbt_layout/build/build.scala
new file mode 100644
index 0000000..80ff3ba
--- /dev/null
+++ b/plugins/sbt_layout/build/build.scala
@@ -0,0 +1,2 @@
+import cbt._
+class Build(val context: Context) extends Plugin
diff --git a/plugins/scalafmt/Scalafmt.scala b/plugins/scalafmt/Scalafmt.scala
new file mode 100644
index 0000000..1f8bf2d
--- /dev/null
+++ b/plugins/scalafmt/Scalafmt.scala
@@ -0,0 +1,99 @@
+package cbt
+
+import org.scalafmt.Error.Incomplete
+import org.scalafmt.Formatted
+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 }
+
+/**
+ * This plugin provides scalafmt support for cbt.
+ *
+ */
+trait Scalafmt extends BaseBuild {
+ /**
+ * Reformat scala source code according to `scalafmtConfig` rules
+ *
+ * @return always returns `ExitCode.Success`
+ */
+ final def scalafmt: ExitCode = {
+ Scalafmt.format(sourceFiles, scalafmtConfig)
+ ExitCode.Success
+ }
+
+ /**
+ * Scalafmt formatting config.
+ *
+ * Tries to get style in following order:
+ * • project local .scalafmt.conf
+ * • global ~/.scalafmt.conf
+ * • default scalafmt config
+ *
+ * Override this task if you want to provide
+ * scalafmt config programmatically on your own.
+ */
+ def scalafmtConfig: ScalafmtConfig =
+ Scalafmt.getStyle(
+ project = projectDirectory.toPath,
+ home = Option(System.getProperty("user.home")) map (p => Paths.get(p))
+ )
+}
+
+object Scalafmt {
+
+ def getStyle(project: Path, home: Option[Path]): ScalafmtConfig = {
+ val local = getConfigPath(project)
+ val global = home flatMap getConfigPath
+ val customStyle = for {
+ configPath <- local.orElse(global)
+ style <- StyleCache.getStyleForFile(configPath.toString)
+ } yield style
+
+ 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}")
+ }
+ }
+ }
+ 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)
+ }
+
+ 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
+ }
+ }
+
+}
diff --git a/plugins/scalafmt/build/build.scala b/plugins/scalafmt/build/build.scala
new file mode 100644
index 0000000..2631908
--- /dev/null
+++ b/plugins/scalafmt/build/build.scala
@@ -0,0 +1,12 @@
+import cbt._
+
+class Build(val context: Context) extends Plugin {
+ private val ScalafmtVersion = "0.4.2"
+
+ 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
new file mode 100644
index 0000000..9374f66
--- /dev/null
+++ b/plugins/scalajs/ScalaJsBuild.scala
@@ -0,0 +1,52 @@
+package cbt
+import java.io.File
+import java.net.URL
+
+trait ScalaJsBuild extends BaseBuild {
+ final protected val scalaJsLib = ScalaJsLib(
+ scalaJsVersion,
+ scalaVersion,
+ context.cbtHasChanged,
+ context.classLoaderCache,
+ context.paths.mavenCache
+ )
+ import scalaJsLib.{link => _,_}
+
+ def scalaJsVersion = "0.6.8"
+ final protected val scalaJsMajorVersion: String = lib.libMajorVersion(scalaJsVersion)
+ final protected val artifactIdSuffix = s"_sjs$scalaJsMajorVersion"
+
+ override def dependencies = super.dependencies :+ scalaJsLibraryDependency
+ override def scalacOptions = super.scalacOptions ++ scalaJsLib.scalacOptions
+
+ /** Note: We make same assumption about scala version.
+ In order to be able to choose different scala version, one has to use %. */
+ implicit class ScalaJsDependencyBuilder(groupId: String){
+ def %%%(artifactId: String) = new DependencyBuilder2(
+ groupId, artifactId + artifactIdSuffix, Some(scalaMajorVersion))
+ }
+
+ private def link(mode: ScalaJsOutputMode, outputPath: File) = scalaJsLib.link(
+ mode, outputPath, scalaJsOptions,
+ target +: dependencies.collect{case d: BoundMavenDependency => d.jar}
+ )
+
+ def scalaJsOptions: Seq[String] = Seq()
+ def scalaJsOptionsFastOpt: Seq[String] = scalaJsOptions
+ def scalaJsOptionsFullOpt: Seq[String] = scalaJsOptions
+
+ private def output(mode: ScalaJsOutputMode) = target ++ s"/$projectName-${mode.fileSuffix}.js"
+ protected def fastOptJSFile: File = output(FastOptJS)
+ protected def fullOptJSFile: File = output(FullOptJS)
+
+ def fastOptJS = {
+ compile
+ link(FastOptJS, fastOptJSFile)
+ fastOptJSFile
+ }
+ def fullOptJS = {
+ compile
+ link(FullOptJS, fullOptJSFile)
+ fullOptJSFile
+ }
+}
diff --git a/plugins/scalajs/ScalaJsLib.scala b/plugins/scalajs/ScalaJsLib.scala
new file mode 100644
index 0000000..0355850
--- /dev/null
+++ b/plugins/scalajs/ScalaJsLib.scala
@@ -0,0 +1,51 @@
+package cbt
+import java.io.File
+
+case class ScalaJsLib(
+ scalaJsVersion: String, scalaVersion: String,
+ cbtHasChanged: Boolean, classLoaderCache: ClassLoaderCache, mavenCache: File
+)(implicit logger: Logger){
+ 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( cbtHasChanged, mavenCache, mavenCentral ).bindOne(
+ MavenDependency("org.scala-js", artifactId, scalaJsVersion)
+ )
+
+ def link(
+ mode: ScalaJsOutputMode, outputPath: File,
+ scalaJsOptions: Seq[String], entriesToLink: Seq[File]
+ ) = {
+ val scalaJsCliDep = dep( "scalajs-cli_"++lib.libMajorVersion(scalaVersion) )
+ lib.runMain(
+ "org.scalajs.cli.Scalajsld",
+ Seq(
+ mode.option,
+ "--sourceMap",
+ "--stdlib", s"${scalaJsLibraryDependency.jar.getAbsolutePath}",
+ "--output", outputPath.string
+ ) ++ scalaJsOptions ++ entriesToLink.map(_.getAbsolutePath),
+ scalaJsCliDep.classLoader(classLoaderCache)
+ )
+ }
+
+ val scalaJsLibraryDependency = dep( "scalajs-library_"++lib.libMajorVersion(scalaVersion) )
+
+ // Has to be full Scala version because the compiler is incompatible between versions
+ val scalaJsCompilerDependency = dep( "scalajs-compiler_"++scalaVersion )
+ val scalacOptions = Seq(
+ "-Xplugin:" ++ scalaJsCompilerDependency.jar.string,
+ "-Xplugin-require:scalajs"
+ )
+}
diff --git a/plugins/scalajs/build/build.scala b/plugins/scalajs/build/build.scala
new file mode 100644
index 0000000..0205cf8
--- /dev/null
+++ b/plugins/scalajs/build/build.scala
@@ -0,0 +1,3 @@
+import cbt._
+
+class Build(val context: Context) extends Plugin
diff --git a/plugins/scalariform/Scalariform.scala b/plugins/scalariform/Scalariform.scala
new file mode 100644
index 0000000..0612469
--- /dev/null
+++ b/plugins/scalariform/Scalariform.scala
@@ -0,0 +1,61 @@
+package cbt
+
+import java.io.File
+import java.nio.file.FileSystems
+import java.nio.file.Files._
+
+import scalariform.formatter.ScalaFormatter
+import scalariform.formatter.preferences.FormattingPreferences
+import scalariform.parser.ScalaParserException
+
+trait Scalariform extends BaseBuild {
+ final def scalariformFormat: ExitCode = {
+ Scalariform.format(sourceFiles, scalariformPreferences, scalaVersion)
+ ExitCode.Success
+ }
+
+ def scalariformPreferences: FormattingPreferences = Scalariform.defaultPreferences
+}
+
+object Scalariform {
+
+ val defaultPreferences: FormattingPreferences = {
+ import scalariform.formatter.preferences._
+ FormattingPreferences()
+ .setPreference(AlignParameters, true)
+ .setPreference(AlignArguments, true)
+ .setPreference(AlignSingleLineCaseStatements, true)
+ .setPreference(MultilineScaladocCommentsStartOnFirstLine, true)
+ .setPreference(SpaceInsideParentheses, true)
+ .setPreference(SpacesWithinPatternBinders, true)
+ .setPreference(SpacesAroundMultiImports, true)
+ .setPreference(DoubleIndentClassDeclaration, false)
+ }
+
+ private val scalaFileMatcher = FileSystems.getDefault.getPathMatcher("glob:**.scala")
+
+ def format(files: Seq[File], preferences: FormattingPreferences, scalaVersion: String): Unit = {
+ var reformattedCount: Int = 0
+ for (file <- files if file.exists) {
+ val path = file.toPath
+ if(scalaFileMatcher.matches(path)) {
+ try {
+ val sourceCode = new String(readAllBytes(path))
+ val formatted = ScalaFormatter.format(
+ sourceCode,
+ preferences,
+ Some(scalaVersion)
+ )
+ if (sourceCode != formatted) {
+ write(path, formatted.getBytes)
+ reformattedCount += 1
+ }
+ } catch {
+ case e: ScalaParserException => System.err.println(s"Scalariform parser error: ${e.getMessage} when formatting: $file")
+ }
+ }
+ }
+ if (reformattedCount > 0) System.err.println(s"Formatted $reformattedCount Scala sources")
+ }
+
+}
diff --git a/plugins/scalariform/build/build.scala b/plugins/scalariform/build/build.scala
new file mode 100644
index 0000000..5910b41
--- /dev/null
+++ b/plugins/scalariform/build/build.scala
@@ -0,0 +1,9 @@
+import cbt._
+
+class Build(val context: Context) extends Plugin {
+ override def dependencies =
+ super.dependencies ++
+ Resolver( mavenCentral ).bind(
+ ScalaDependency("org.scalariform", "scalariform", "0.1.8")
+ )
+}
diff --git a/plugins/scalatest/ScalaTest.scala b/plugins/scalatest/ScalaTest.scala
new file mode 100644
index 0000000..ee96431
--- /dev/null
+++ b/plugins/scalatest/ScalaTest.scala
@@ -0,0 +1,43 @@
+package cbt
+import org.scalatest._
+
+
+trait ScalaTest extends BaseBuild{
+ override def run: ExitCode = {
+ import ScalaTestLib._
+ val _classLoader = classLoader(context.classLoaderCache)
+ val suiteNames = compile.map( d => discoverSuites(d, _classLoader) ).toVector.flatten
+ runSuites( suiteNames.map( loadSuite( _, _classLoader ) ) )
+ ExitCode.Success
+ }
+ override def dependencies = super.dependencies ++ Resolver( mavenCentral ).bind( ScalaDependency("org.scalatest","scalatest","2.2.4") )
+}
+
+object ScalaTestLib{
+ import java.io.File
+ def runSuites(suites: Seq[Suite]) = {
+ def color: Boolean = true
+ def durations: Boolean = true
+ def shortstacks: Boolean = true
+ def fullstacks: Boolean = true
+ def stats: Boolean = true
+ def testName: String = null
+ def configMap: ConfigMap = ConfigMap.empty
+ suites.foreach{
+ _.execute(testName, configMap, color, durations, shortstacks, fullstacks, stats)
+ }
+ }
+
+ def discoverSuites(discoveryPath: File, _classLoader: ClassLoader): Seq[String] = {
+ _classLoader
+ .loadClass("org.scalatest.tools.SuiteDiscoveryHelper")
+ .getMethod("discoverSuiteNames", classOf[List[_]], classOf[ClassLoader], classOf[Option[_]])
+ .invoke(null, List(discoveryPath.string ++ "/"), _classLoader, None)
+ .asInstanceOf[Set[String]]
+ .to
+ }
+ def loadSuite(name: String, _classLoader: ClassLoader) = {
+ _classLoader.loadClass(name).getConstructor().newInstance().asInstanceOf[Suite]
+ }
+}
+
diff --git a/plugins/scalatest/build/build.scala b/plugins/scalatest/build/build.scala
new file mode 100644
index 0000000..dd21898
--- /dev/null
+++ b/plugins/scalatest/build/build.scala
@@ -0,0 +1,9 @@
+import cbt._
+
+class Build(val context: Context) extends Plugin{
+ override def dependencies =
+ super.dependencies ++
+ Resolver( mavenCentral ).bind(
+ ScalaDependency("org.scalatest","scalatest","2.2.4")
+ )
+}
diff --git a/plugins/sonatype-release/build/build.scala b/plugins/sonatype-release/build/build.scala
new file mode 100644
index 0000000..0205cf8
--- /dev/null
+++ b/plugins/sonatype-release/build/build.scala
@@ -0,0 +1,3 @@
+import cbt._
+
+class Build(val context: Context) extends Plugin
diff --git a/plugins/sonatype-release/src/SonatypeRelease.scala b/plugins/sonatype-release/src/SonatypeRelease.scala
new file mode 100644
index 0000000..cb32417
--- /dev/null
+++ b/plugins/sonatype-release/src/SonatypeRelease.scala
@@ -0,0 +1,51 @@
+package cbt
+
+import cbt.sonatype.SonatypeLib
+
+/**
+ * Sonatype release plugin.
+ * It provides ability to release your artifacts to Sonatype OSSRH
+ * and publish to Central repository (aka Maven Central).
+ *
+ * Release proccess is executed in two steps:
+ * • `sonatypePublishSigned`
+ * - creates staging repository to publish artifacts;
+ * - publishes signed artifacts(jars) to staging repository.
+ * • `sonatypeRelease`
+ * - closes staging repository;
+ * - promotes staging repository to Central repository;
+ * - drops staging repository after release.
+ */
+trait SonatypeRelease extends Publish {
+
+ def profileName: String = groupId
+
+ def sonatypeServiceURI: String = SonatypeLib.sonatypeServiceURI
+
+ def sonatypeSnapshotsURI: String = SonatypeLib.sonatypeSnapshotsURI
+
+ def sonatypeCredentials: String = SonatypeLib.sonatypeCredentials
+
+ def sonatypePublishSigned: ExitCode =
+ sonatypeLib.sonatypePublishSigned(
+ sourceFiles,
+ `package` :+ pom,
+ groupId,
+ artifactId,
+ version,
+ isSnapshot,
+ scalaMajorVersion
+ )
+
+ def sonatypePublishSignedSnapshot: ExitCode = {
+ copy(context.copy(version = Some(version + "-SNAPSHOT"))).sonatypePublishSigned
+ }
+
+ def sonatypeRelease: ExitCode =
+ sonatypeLib.sonatypeRelease(groupId, artifactId, version)
+
+ private def sonatypeLib =
+ new SonatypeLib(sonatypeServiceURI, sonatypeSnapshotsURI, sonatypeCredentials, profileName)(lib)
+
+ override def copy(context: Context) = super.copy(context).asInstanceOf[SonatypeRelease]
+}
diff --git a/plugins/sonatype-release/src/sonatype/HttpUtils.scala b/plugins/sonatype-release/src/sonatype/HttpUtils.scala
new file mode 100644
index 0000000..9d23744
--- /dev/null
+++ b/plugins/sonatype-release/src/sonatype/HttpUtils.scala
@@ -0,0 +1,65 @@
+package cbt.sonatype
+
+import java.net.URL
+
+import cbt.Stage0Lib
+
+import scala.annotation.tailrec
+import scala.util.{ Failure, Success, Try }
+
+private[sonatype] object HttpUtils {
+ // Make http GET. On failure request will be retried with exponential backoff.
+ def GET(uri: String, headers: Map[String, String]): (Int, String) =
+ withRetry(httpRequest("GET", uri, headers))
+
+ // Make http POST. On failure request will be retried with exponential backoff.
+ def POST(uri: String, body: Array[Byte], headers: Map[String, String]): (Int, String) =
+ withRetry(httpRequest("POST", uri, headers, body))
+
+ private def httpRequest(method: String, uri: String, headers: Map[String, String], body: Array[Byte] = Array.emptyByteArray): (Int, String) = {
+ val conn = Stage0Lib.openConnectionConsideringProxy(new URL(uri))
+ conn.setReadTimeout(60000) // 1 minute
+ conn.setConnectTimeout(30000) // 30 seconds
+
+ headers foreach { case (k,v) =>
+ conn.setRequestProperty(k, v)
+ }
+ conn.setRequestMethod(method)
+ if(method == "POST" || method == "PUT") { // PATCH too?
+ conn.setDoOutput(true)
+ conn.getOutputStream.write(body)
+ }
+
+ val arr = new Array[Byte](conn.getContentLength)
+ conn.getInputStream.read(arr)
+
+ conn.getResponseCode -> new String(arr)
+ }
+
+ // ============== General utilities
+
+ def withRetry[T](f: => T): T = withRetry(4000, 5)(f)
+
+ /**
+ * Retry execution of `f` `retriesLeft` times
+ * with `delay` doubled between attempts.
+ */
+ @tailrec
+ def withRetry[T](delay: Int, retriesLeft: Int)(f: ⇒ T): T = {
+ Try(f) match {
+ case Success(result) ⇒
+ result
+ case Failure(e) ⇒
+ if (retriesLeft == 0) {
+ throw new Exception(e)
+ } else {
+ val newDelay = delay * 2
+ val newRetries = retriesLeft - 1
+// log(s"Failed with exception: $e, will retry $newRetries times; waiting: $delay")
+ Thread.sleep(delay)
+
+ withRetry(newDelay, newRetries)(f)
+ }
+ }
+ }
+}
diff --git a/plugins/sonatype-release/src/sonatype/SonatypeHttpApi.scala b/plugins/sonatype-release/src/sonatype/SonatypeHttpApi.scala
new file mode 100644
index 0000000..e90b81d
--- /dev/null
+++ b/plugins/sonatype-release/src/sonatype/SonatypeHttpApi.scala
@@ -0,0 +1,215 @@
+package cbt.sonatype
+
+import java.util.Base64
+
+import scala.xml.XML
+
+/**
+ * Interface for Sonatype staging plugin HTTP API.
+ * All resources are described here:
+ * https://oss.sonatype.org/nexus-staging-plugin/default/docs/index.html
+ *
+ * Publish proccess via HTTP API described here:
+ * https://support.sonatype.com/hc/en-us/articles/213465868-Uploading-to-a-Staging-Repository-via-REST-API?page=1#comment_204178478
+ */
+private final class SonatypeHttpApi(sonatypeURI: String, sonatypeCredentials: String, profileName: String)(log: String => Unit) {
+ import HttpUtils._
+
+ private val base64Credentials = new String(Base64.getEncoder.encode(sonatypeCredentials.getBytes))
+
+ // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles.html
+ def getStagingProfile: StagingProfile = {
+ log(s"Retrieving info for profile: $profileName")
+ val (_, response) = GET(
+ uri = s"$sonatypeURI/staging/profiles",
+ headers = Map("Authorization" -> s"Basic $base64Credentials")
+ )
+
+ val currentProfile = (XML.loadString(response) \\ "stagingProfile" find { profile =>
+ (profile \ "name").headOption.exists(_.text == profileName)
+ }).getOrElse(throw new Exception(s"Failed to get profile with name: $profileName"))
+
+ StagingProfile(
+ id = (currentProfile \ "id").head.text,
+ name = (currentProfile \ "name").head.text,
+ repositoryTargetId = (currentProfile \ "repositoryTargetId").head.text,
+ resourceURI = (currentProfile \ "resourceURI").head.text
+ )
+ }
+
+ // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profile_repositories_-profileIdKey-.html
+ def getStagingRepos(profile: StagingProfile): Seq[StagingRepository] = {
+ log(s"Retrieving staging repositories for profile: $profileName")
+ val (_, response) = GET(
+ uri = s"$sonatypeURI/staging/profile_repositories/${profile.id}",
+ headers = Map(
+ "Authorization" -> s"Basic $base64Credentials"
+ )
+ )
+
+ (XML.loadString(response) \\ "stagingProfileRepository") map extractStagingRepository
+ }
+
+ // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_repository_-repositoryIdKey-.html
+ private def getStagingRepoById(repoId: StagingRepositoryId): StagingRepository = {
+ log(s"Retrieving staging repo with id: ${repoId.repositoryId}")
+ val (_, response) = GET(
+ uri = s"$sonatypeURI/staging/repository/${repoId.repositoryId}",
+ headers = Map(
+ "Authorization" -> s"Basic $base64Credentials"
+ )
+ )
+
+ extractStagingRepository(XML.loadString(response))
+ }
+
+ // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_start.html
+ def createStagingRepo(profile: StagingProfile): StagingRepositoryId = {
+ log(s"Creating staging repositories for profile: $profileName")
+ val (responseCode, response) = POST(
+ uri = profile.resourceURI + "/start",
+ body = createRequestBody("Create staging repository [CBT]").getBytes,
+ headers = Map(
+ "Authorization" -> s"Basic $base64Credentials",
+ "Content-Type" -> "application/xml"
+ )
+ )
+
+ require(responseCode == 201, s"Create staging repo response code. Expected: 201, got: $responseCode")
+
+ val optRepositoryId = (XML.loadString(response) \ "data" \ "stagedRepositoryId").headOption.map(e => StagingRepositoryId(e.text))
+
+ optRepositoryId.getOrElse(throw new Exception(s"Malformed response. Failed to get id of created staging repo"))
+ }
+
+ def finishRelease(repo: StagingRepository, profile: StagingProfile): Unit = {
+ val repoId = StagingRepositoryId(repo.repositoryId)
+ repo.state match {
+ case Open =>
+ closeStagingRepo(profile, repoId)
+ promoteStagingRepo(profile, repoId)
+ dropStagingRepo(profile, repoId)
+ case Closed =>
+ promoteStagingRepo(profile, repoId)
+ dropStagingRepo(profile, repoId)
+ case Released =>
+ dropStagingRepo(profile, repoId)
+ case Unknown(status) =>
+ throw new Exception(s"Got repo in status: ${status}, can't finish release.")
+ }
+ }
+
+ // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_finish.html
+ private def closeStagingRepo(profile: StagingProfile, repoId: StagingRepositoryId): Unit = {
+ log(s"Closing staging repo: ${repoId.repositoryId}")
+ val (responseCode, _) = POST(
+ uri = profile.resourceURI + "/finish",
+ body = promoteRequestBody(
+ repoId.repositoryId,
+ "Close staging repository [CBT]",
+ profile.repositoryTargetId
+ ).getBytes,
+ headers = Map(
+ "Authorization" -> s"Basic $base64Credentials",
+ "Content-Type" -> "application/xml"
+ )
+ )
+
+ require(responseCode == 201, s"Close staging repo response code. Expected: 201, got: $responseCode")
+ }
+
+ // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_promote.html
+ // You can promote repository only when it is in "closed" state.
+ private def promoteStagingRepo(profile: StagingProfile, repoId: StagingRepositoryId): Unit = {
+ log(s"Promoting staging repo: ${repoId.repositoryId}")
+ val responseCode = withRetry {
+ // need to get fresh info about this repo
+ val repoState = try getStagingRepoById(repoId) catch {
+ case e: Exception =>
+ throw new Exception(s"Repository with id ${repoId.repositoryId} not found. Maybe it was dropped already", e)
+ }
+
+ if(repoState.state == Closed) {
+ val (code, _) = POST(
+ uri = profile.resourceURI + "/promote",
+ body = promoteRequestBody(
+ repoId.repositoryId,
+ "Promote staging repository [CBT]",
+ profile.repositoryTargetId
+ ).getBytes,
+ headers = Map(
+ "Authorization" -> s"Basic $base64Credentials",
+ "Content-Type" -> "application/xml"
+ )
+ )
+ code
+ } else {
+ throw new Exception(s"Can't promote, repository ${repoId.repositoryId} is not in closed state yet!")
+ }
+ }
+
+ require(responseCode == 201, s"Promote staging repo response code. Expected: 201, got: $responseCode")
+ }
+
+ // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_drop.html
+ // It's safe to drop repository in "released" state.
+ private def dropStagingRepo(profile: StagingProfile, repoId: StagingRepositoryId): Unit = {
+ log(s"Dropping staging repo: ${repoId.repositoryId}")
+ val responseCode = withRetry {
+ // need to get fresh info about this repo
+ val repoState = try getStagingRepoById(repoId) catch {
+ case e: Exception =>
+ throw new Exception(s"Repository with id ${repoId.repositoryId} not found. Maybe it was dropped already", e)
+ }
+
+ if (repoState.state == Released) {
+ val (code, _) = POST(
+ uri = profile.resourceURI + "/drop",
+ body = promoteRequestBody(
+ repoId.repositoryId,
+ "Drop staging repository [CBT]",
+ profile.repositoryTargetId
+ ).getBytes,
+ headers = Map(
+ "Authorization" -> s"Basic $base64Credentials",
+ "Content-Type" -> "application/xml"
+ )
+ )
+ code
+ } else {
+ throw new Exception(s"Can't drop, repository ${repoId.repositoryId} is not in released state yet!")
+ }
+ }
+ require(responseCode == 201, s"Drop staging repo response code. Expected: 201, got: $responseCode")
+ }
+
+ private def promoteRequestBody(repoId: String, description: String, targetRepoId: String) =
+ s"""
+ |<promoteRequest>
+ | <data>
+ | <stagedRepositoryId>$repoId</stagedRepositoryId>
+ | <description>$description</description>
+ | <targetRepositoryId>$targetRepoId</targetRepositoryId>
+ | </data>
+ |</promoteRequest>
+ """.stripMargin
+
+
+ private def createRequestBody(description: String) =
+ s"""
+ |<promoteRequest>
+ | <data>
+ | <description>$description</description>
+ | </data>
+ |</promoteRequest>
+ """.stripMargin
+
+ private def extractStagingRepository(repo: xml.Node): StagingRepository =
+ StagingRepository(
+ (repo \ "profileId").head.text,
+ (repo \ "profileName").head.text,
+ (repo \ "repositoryId").head.text,
+ RepositoryState.fromString((repo \ "type").head.text)
+ )
+}
+
diff --git a/plugins/sonatype-release/src/sonatype/SonatypeLib.scala b/plugins/sonatype-release/src/sonatype/SonatypeLib.scala
new file mode 100644
index 0000000..9aab9f5
--- /dev/null
+++ b/plugins/sonatype-release/src/sonatype/SonatypeLib.scala
@@ -0,0 +1,148 @@
+package cbt.sonatype
+
+import java.io.File
+import java.net.URL
+import java.nio.file.Files._
+import java.nio.file.Paths
+
+import cbt.{ ExitCode, Lib }
+
+/**
+ * Sonatype release process is:
+ * • get your profile info to publish artifacts
+ * • open staging repository to publish artifacts
+ * • publish signed artifacts and signatures to staging repository
+ * • close staging repository
+ * • promote staging repository
+ * • drop staging repository
+ */
+
+object SonatypeLib {
+
+ val sonatypeServiceURI: String = "https://oss.sonatype.org/service/local"
+
+ val sonatypeSnapshotsURI: String = "https://oss.sonatype.org/content/repositories/snapshots"
+
+ /**
+ * login:password for Sonatype access.
+ * Order of credentials lookup:
+ * • environment variables SONATYPE_USERNAME and SONATYPE_PASSWORD
+ * • ~/.cbt/sonatype-credentials
+ */
+ def sonatypeCredentials: String = {
+ def fromEnv = for {
+ username <- Option(System.getenv("SONATYPE_USERNAME"))
+ password <- Option(System.getenv("SONATYPE_PASSWORD"))
+ } yield s"$username:$password"
+
+ def fromFile = {
+ for {
+ home <- Option(System.getProperty("user.home"))
+ credsPath = Paths.get(home, ".cbt", "sonatype-credentials")
+ } yield new String(readAllBytes(credsPath)).trim
+ }
+
+ fromEnv
+ .orElse(fromFile)
+ .getOrElse(throw new Exception(
+ "No Sonatype credentials found! You can provide them via SONATYPE_USERNAME, SONATYPE_PASSWORD env variables, " +
+ "or in ~/.cbt/sonatype-credentials file as login:password"
+ ))
+ }
+}
+
+final class SonatypeLib(
+ sonatypeServiceURI: String,
+ sonatypeSnapshotsURI: String,
+ sonatypeCredentials: String,
+ profileName: String)(lib: Lib) {
+
+ private val sonatypeApi = new SonatypeHttpApi(sonatypeServiceURI, sonatypeCredentials, profileName)(sonatypeLogger)
+
+ /*
+ * Signed publish steps:
+ * • create new staging repo
+ * • create artifacts and sign them
+ * • publish jars to created repo
+ */
+ def sonatypePublishSigned(
+ sourceFiles: Seq[File],
+ artifacts: Seq[File],
+ groupId: String,
+ artifactId: String,
+ version: String,
+ isSnapshot: Boolean,
+ scalaMajorVersion: String
+ ): ExitCode = {
+ if(sourceFiles.nonEmpty) {
+ System.err.println(lib.blue("Staring publishing to Sonatype."))
+
+ val profile = getStagingProfile()
+
+ val deployURI = (if (isSnapshot) {
+ sonatypeSnapshotsURI
+ } else {
+ val repoId = sonatypeApi.createStagingRepo(profile)
+ s"${sonatypeServiceURI}/staging/deployByRepositoryId/${repoId.repositoryId}"
+ }) + s"/${groupId.replace(".", "/")}/${artifactId}_${scalaMajorVersion}/${version}"
+
+ lib.publishSigned(
+ artifacts = artifacts,
+ url = new URL(deployURI),
+ credentials = Some(sonatypeCredentials)
+ )
+ System.err.println(lib.green("Successfully published artifacts to Sonatype."))
+ ExitCode.Success
+ } else {
+ System.err.println(lib.red("Sources are empty, won't publish empty jar."))
+ ExitCode.Failure
+ }
+ }
+
+ /**
+ * Release is:
+ * • find staging repo related to current profile;
+ * • close this staging repo;
+ * • wait until this repo is released;
+ * • drop this repo.
+ */
+ def sonatypeRelease(
+ groupId: String,
+ artifactId: String,
+ version: String
+ ): ExitCode = {
+ val profile = getStagingProfile()
+
+ sonatypeApi.getStagingRepos(profile).toList match {
+ case Nil =>
+ System.err.println(lib.red("No staging repositories found, you need to publish artifacts first."))
+ ExitCode.Failure
+ case repo :: Nil =>
+ sonatypeApi.finishRelease(repo, profile)
+ System.err.println(lib.green(s"Successfully released ${groupId}/${artifactId} v:${version}"))
+ ExitCode.Success
+ case repos =>
+ val showRepo = { r: StagingRepository => s"${r.repositoryId} in state: ${r.state}" }
+ val toRelease = lib.pickOne(lib.blue(s"More than one staging repo found. Select one of them:"), repos)(showRepo)
+
+ toRelease map { repo =>
+ sonatypeApi.finishRelease(repo, profile)
+ System.err.println(lib.green(s"Successfully released ${groupId}/${artifactId} v:${version}"))
+ ExitCode.Success
+ } getOrElse {
+ System.err.println(lib.red("Wrong repository number, try again please."))
+ ExitCode.Failure
+ }
+ }
+ }
+
+ private def getStagingProfile() =
+ try {
+ sonatypeApi.getStagingProfile
+ } catch {
+ case e: Exception => throw new Exception(s"Failed to get info for profile: $profileName", e)
+ }
+
+ private def sonatypeLogger: String => Unit = lib.logger.log("Sonatype", _)
+
+}
diff --git a/plugins/sonatype-release/src/sonatype/models.scala b/plugins/sonatype-release/src/sonatype/models.scala
new file mode 100644
index 0000000..4446c53
--- /dev/null
+++ b/plugins/sonatype-release/src/sonatype/models.scala
@@ -0,0 +1,31 @@
+package cbt.sonatype
+
+case class StagingProfile(
+ id: String,
+ name: String,
+ repositoryTargetId: String,
+ resourceURI: String
+ )
+
+case class StagingRepositoryId(repositoryId: String)
+
+object RepositoryState {
+ val fromString: String => RepositoryState = {
+ case "open" => Open
+ case "closed" => Closed
+ case "released" => Released
+ case other => Unknown(other)
+ }
+}
+sealed trait RepositoryState
+case object Open extends RepositoryState
+case object Closed extends RepositoryState
+case object Released extends RepositoryState
+case class Unknown(state: String) extends RepositoryState
+
+case class StagingRepository(
+ profileId: String,
+ profileName: String,
+ repositoryId: String,
+ state: RepositoryState // stands as `type` in XML response
+ )
diff --git a/plugins/uber-jar/build/build.scala b/plugins/uber-jar/build/build.scala
new file mode 100644
index 0000000..0205cf8
--- /dev/null
+++ b/plugins/uber-jar/build/build.scala
@@ -0,0 +1,3 @@
+import cbt._
+
+class Build(val context: Context) extends Plugin
diff --git a/plugins/uber-jar/src/UberJar.scala b/plugins/uber-jar/src/UberJar.scala
new file mode 100644
index 0000000..3783367
--- /dev/null
+++ b/plugins/uber-jar/src/UberJar.scala
@@ -0,0 +1,124 @@
+package cbt
+
+import java.io.File
+import java.nio.file.{FileSystems, Files, Path}
+import java.util.jar.JarFile
+
+trait UberJar extends BaseBuild {
+
+ final def uberJar: ExitCode = {
+ System.err.println("Creating uber jar...")
+ new UberJarLib(logger).create(target, classpath, uberJarMainClass, uberJarName)
+ System.err.println(lib.green("Creating uber jar - DONE"))
+ ExitCode.Success
+ }
+
+ def uberJarMainClass: Option[String] = runClass
+
+ def uberJarName: String = projectName + ".jar"
+
+}
+
+class UberJarLib(logger: Logger) {
+ private val (jarFileMatcher, excludeFileMatcher) = {
+ val fs = FileSystems.getDefault
+ (fs.getPathMatcher("glob:**.jar"), fs.getPathMatcher("glob:**{.RSA,.DSA,.SF,.MF,META-INF}"))
+ }
+ private val log: String => Unit = logger.log("uber-jar", _)
+ private val lib = new cbt.Lib(logger)
+
+ /**
+ * Creates uber jar for given build.
+ *
+ * @param target build's target directory
+ * @param classpath build's classpath
+ * @param mainClass optional main class
+ * @param jarName name of resulting jar file
+ */
+ def create(target: File,
+ classpath: ClassPath,
+ mainClass: Option[String],
+ jarName: String): Unit = {
+ log(s"Classpath is: $classpath")
+ log(s"Target directory is: $target")
+ log(s"Jar name is: $jarName")
+ mainClass foreach (c => log(s"Main class is is: $c"))
+
+ val (jars, dirs) = classpath.files partition (f => jarFileMatcher.matches(f.toPath))
+ log(s"Found ${jars.length} jar dependencies: \n ${jars mkString "\n"}")
+ log(s"Found ${dirs.length} directories in classpath: \n ${dirs mkString "\n"}")
+
+ log("Extracting jars...")
+ val extractedJarsRoot = extractJars(jars.distinct)(log).toFile
+ log("Extracting jars - DONE")
+
+ log("Writing jar file...")
+ val uberJarPath = target.toPath.resolve(jarName)
+ val uberJar = lib.jarFile(uberJarPath.toFile, dirs :+ extractedJarsRoot, mainClass) getOrElse {
+ throw new Exception("Jar file wasn't created!")
+ }
+ log("Writing jar file - DONE")
+
+ System.err.println(lib.green(s"Uber jar created. You can grab it at $uberJar"))
+ }
+
+ /**
+ * Extracts jars, and writes them on disk. Returns root directory of extracted jars
+ * TODO: in future we probably should save extracted jars in target directory, to reuse them on second run
+ *
+ * @param jars list of *.jar files
+ * @param log logger
+ * @return root directory of extracted jars
+ */
+ private def extractJars(jars: Seq[File])(log: String => Unit): Path = {
+ val destDir = {
+ val path = Files.createTempDirectory("unjars")
+ path.toFile.deleteOnExit()
+ log(s"Extracted jars directory: $path")
+ path
+ }
+ jars foreach { jar => extractJar(jar, destDir)(log) }
+ destDir
+ }
+
+ /**
+ * Extracts content of single jar file to destination directory.
+ * When extracting jar, if same file already exists, we skip(don't write) this file.
+ * TODO: maybe skipping duplicates is not best strategy. Figure out duplicate strategy.
+ *
+ * @param jarFile jar file to extract
+ * @param destDir destination directory
+ * @param log logger
+ */
+ private def extractJar(jarFile: File, destDir: Path)(log: String => Unit): Unit = {
+ log(s"Extracting jar: $jarFile")
+ val jar = new JarFile(jarFile)
+ val enumEntries = jar.entries
+ while (enumEntries.hasMoreElements) {
+ val entry = enumEntries.nextElement()
+ // log(s"Entry name: ${entry.getName}")
+ val entryPath = destDir.resolve(entry.getName)
+ if (excludeFileMatcher.matches(entryPath)) {
+ log(s"Excluded file ${entryPath.getFileName} from jar: $jarFile")
+ } else {
+ val exists = Files.exists(entryPath)
+ if (entry.isDirectory) {
+ if (!exists) {
+ Files.createDirectory(entryPath)
+ // log(s"Created directory: $entryPath")
+ }
+ } else {
+ if (exists) {
+ log(s"File $entryPath already exists, skipping.")
+ } else {
+ val is = jar.getInputStream(entry)
+ Files.copy(is, entryPath)
+ is.close()
+ // log(s"Wrote file: $entryPath")
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/plugins/wartremover/WartRemover.scala b/plugins/wartremover/WartRemover.scala
new file mode 100644
index 0000000..d5bbcd0
--- /dev/null
+++ b/plugins/wartremover/WartRemover.scala
@@ -0,0 +1,51 @@
+package cbt
+
+import org.wartremover.WartTraverser
+import java.io.File
+
+trait WartRemover extends BaseBuild {
+
+ override def scalacOptions =
+ super.scalacOptions ++ wartremoverScalacOptions
+
+ private[this] def wartremoverCompilerDependency: String =
+ MavenResolver(
+ context.cbtHasChanged,
+ context.paths.mavenCache,
+ mavenCentral).bindOne(
+ ScalaDependency("org.wartremover", "wartremover", "1.1.1")
+ ).jar.string
+
+ private[this] def wartremoverScalacOptions: Seq[String] =
+ Seq("-Xplugin:" ++ wartremoverCompilerDependency) ++
+ wartremoverErrorsScalacOptions ++
+ wartremoverWarningsScalacOptions ++
+ wartremoverExcludedScalacOptions ++
+ wartremoverClasspathsScalacOptions
+
+ private[this] def wartremoverErrorsScalacOptions: Seq[String] =
+ wartremoverErrors.distinct.map(w => s"-P:wartremover:traverser:${w.className}")
+
+ private[this] def wartremoverWarningsScalacOptions: Seq[String] =
+ wartremoverWarnings.distinct
+ .filterNot(wartremoverErrors contains _)
+ .map(w => s"-P:wartremover:only-warn-traverser:${w.className}")
+
+ private[this] def wartremoverExcludedScalacOptions: Seq[String] =
+ wartremoverExcluded.distinct.map(c => s"-P:wartremover:excluded:${c.getAbsolutePath}")
+
+ private[this] def wartremoverClasspathsScalacOptions: Seq[String] =
+ wartremoverClasspaths.distinct.map(cp => s"-P:wartremover:cp:$cp")
+
+ /** List of Warts that will be reported as compilation errors. */
+ def wartremoverErrors: Seq[WartTraverser] = Seq.empty
+
+ /** List of Warts that will be reported as compilation warnings. */
+ def wartremoverWarnings: Seq[WartTraverser] = Seq.empty
+
+ /** List of files to be excluded from all checks. */
+ def wartremoverExcluded: Seq[File] = Seq.empty
+
+ /** List of classpaths for custom Warts. */
+ def wartremoverClasspaths: Seq[String] = Seq.empty
+}
diff --git a/plugins/wartremover/build/build.scala b/plugins/wartremover/build/build.scala
new file mode 100644
index 0000000..d62c571
--- /dev/null
+++ b/plugins/wartremover/build/build.scala
@@ -0,0 +1,9 @@
+import cbt._
+
+class Build(val context: Context) extends Plugin {
+ override def dependencies =
+ super.dependencies ++
+ Resolver( mavenCentral ).bind(
+ ScalaDependency("org.wartremover", "wartremover", "1.1.1")
+ )
+}
diff --git a/realpath/realpath.c b/realpath/realpath.c
new file mode 100644
index 0000000..055dbcf
--- /dev/null
+++ b/realpath/realpath.c
@@ -0,0 +1,35 @@
+// http://stackoverflow.com/questions/284662/how-do-you-normalize-a-file-path-in-bash
+// realpath.c: display the absolute path to a file or directory.
+// Adam Liss, August, 2007
+// This program is provided "as-is" to the public domain, without express or
+// implied warranty, for any non-profit use, provided this notice is maintained.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libgen.h>
+#include <limits.h>
+
+static char *s_pMyName;
+void usage(void);
+
+int main(int argc, char *argv[])
+{
+ char
+ sPath[PATH_MAX];
+
+
+ s_pMyName = strdup(basename(argv[0]));
+
+ if (argc < 2)
+ usage();
+
+ printf("%s\n", realpath(argv[1], sPath));
+ return 0;
+}
+
+void usage(void)
+{
+ fprintf(stderr, "usage: %s PATH\n", s_pMyName);
+ exit(1);
+} \ No newline at end of file
diff --git a/realpath/realpath.sh b/realpath/realpath.sh
new file mode 100755
index 0000000..de4d964
--- /dev/null
+++ b/realpath/realpath.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# is there a realiable cross-platform was to do this without relying on compiling C code?
+
+DIR=$(dirname $(readlink "$0") 2>/dev/null || dirname "$0" 2>/dev/null )
+which realpath 2>&1 > /dev/null
+REALPATH_INSTALLED=$?
+
+if [ ! $REALPATH_INSTALLED -eq 0 ]; then
+ if [ ! -f $DIR/realpath ]; then
+ >&2 echo "Compiling realpath"
+ gcc $DIR/realpath.c -o $DIR/realpath
+ chmod u+x $DIR/realpath
+ fi
+ $DIR/realpath $1
+else
+ realpath $1
+fi
diff --git a/shell-integration/cbt-completions.bash b/shell-integration/cbt-completions.bash
new file mode 100755
index 0000000..925ba4b
--- /dev/null
+++ b/shell-integration/cbt-completions.bash
@@ -0,0 +1,13 @@
+#!/bin/bash
+__cbt()
+{
+ local cur prev opts
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ opts="$(cbt taskNames)"
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+}
+
+complete -F __cbt cbt
diff --git a/shell-integration/cbt-completions.fish b/shell-integration/cbt-completions.fish
new file mode 100644
index 0000000..6e543fb
--- /dev/null
+++ b/shell-integration/cbt-completions.fish
@@ -0,0 +1 @@
+complete -c cbt -a (cbt taskNames)
diff --git a/shell-integration/cbt-completions.zsh b/shell-integration/cbt-completions.zsh
new file mode 100755
index 0000000..d11307d
--- /dev/null
+++ b/shell-integration/cbt-completions.zsh
@@ -0,0 +1,7 @@
+#! /usr/bin/env zsh
+
+_cbt() {
+ reply=( "${(ps:\n:)$(cbt taskNames)}" )
+}
+
+compctl -K _cbt cbt
diff --git a/stage1/Cache.scala b/stage1/Cache.scala
new file mode 100644
index 0000000..a8036e5
--- /dev/null
+++ b/stage1/Cache.scala
@@ -0,0 +1,14 @@
+package cbt
+/**
+Caches exactly one value.
+Is there a less boiler-platy way to achieve this, that doesn't
+require creating an instance for each thing you want to cache?
+*/
+class Cache[T]{
+ private var value: Option[T] = None
+ def apply(value: => T) = this.synchronized{
+ if(!this.value.isDefined)
+ this.value = Some(value)
+ this.value.get
+ }
+}
diff --git a/stage1/CachingClassLoader.scala b/stage1/CachingClassLoader.scala
new file mode 100644
index 0000000..4ddebda
--- /dev/null
+++ b/stage1/CachingClassLoader.scala
@@ -0,0 +1,17 @@
+package cbt
+import java.net._
+import java.util.concurrent.ConcurrentHashMap
+import scala.util.Try
+
+trait CachingClassLoader extends ClassLoader{
+ def logger: Logger
+ val cache = new KeyLockedLazyCache[String,Option[Class[_]]]( new ConcurrentHashMap, new ConcurrentHashMap, Some(logger) )
+ override def loadClass(name: String, resolve: Boolean) = {
+ cache.get( name, Try(super.loadClass(name, resolve)).toOption ).getOrElse(null)
+ }
+ override def loadClass(name: String) = {
+ val _class = super.loadClass(name)
+ if(_class == null) throw new ClassNotFoundException(name)
+ else _class
+ }
+}
diff --git a/stage1/CbtPaths.scala b/stage1/CbtPaths.scala
new file mode 100644
index 0000000..71c2ef1
--- /dev/null
+++ b/stage1/CbtPaths.scala
@@ -0,0 +1,15 @@
+package cbt
+import java.io._
+case class CbtPaths(private val cbtHome: File, private val cache: File){
+ val userHome: File = new File(Option(System.getProperty("user.home")).get)
+ val nailgun: File = cbtHome ++ "/nailgun_launcher"
+ val stage1: File = cbtHome ++ "/stage1"
+ val stage2: File = cbtHome ++ "/stage2"
+ val mavenCache: File = cache ++ "/maven"
+ private val target = NailgunLauncher.TARGET.stripSuffix("/")
+ val stage1Target: File = stage1 ++ ("/" ++ target)
+ val stage2Target: File = stage2 ++ ("/" ++ target)
+ val stage2StatusFile: File = stage2Target ++ ".last-success"
+ val compatibility: File = cbtHome ++ "/compatibility"
+ val nailgunTarget: File = nailgun ++ ("/" ++ target)
+}
diff --git a/stage1/ClassLoaderCache.scala b/stage1/ClassLoaderCache.scala
new file mode 100644
index 0000000..e430ee1
--- /dev/null
+++ b/stage1/ClassLoaderCache.scala
@@ -0,0 +1,24 @@
+package cbt
+
+import java.net._
+import java.util.concurrent.ConcurrentHashMap
+import collection.JavaConversions._
+
+case class ClassLoaderCache(
+ logger: Logger,
+ private[cbt] permanentKeys: ConcurrentHashMap[String,AnyRef],
+ private[cbt] permanentClassLoaders: ConcurrentHashMap[AnyRef,ClassLoader]
+){
+ val persistent = new KeyLockedLazyCache(
+ permanentKeys,
+ permanentClassLoaders,
+ Some(logger)
+ )
+ override def toString = (
+ s"ClassLoaderCache("
+ ++
+ persistent.keys.keySet.toVector.map(_.toString.split(":").mkString("\n")).sorted.mkString("\n\n","\n\n","\n\n")
+ ++
+ ")"
+ )
+}
diff --git a/stage1/ClassPath.scala b/stage1/ClassPath.scala
new file mode 100644
index 0000000..6e6f113
--- /dev/null
+++ b/stage1/ClassPath.scala
@@ -0,0 +1,28 @@
+package cbt
+import java.io._
+import java.net._
+
+object ClassPath{
+ def flatten( classPaths: Seq[ClassPath] ): ClassPath = ClassPath( classPaths.map(_.files).flatten )
+}
+case class ClassPath(files: Seq[File] = Seq()){
+ private val duplicates = (files diff files.distinct).distinct
+ assert(
+ duplicates.isEmpty,
+ "Duplicate classpath entries found:\n" ++ duplicates.mkString("\n") ++ "\nin classpath:\n"++string
+ )
+ private val nonExisting = files.distinct.filterNot(_.exists)
+ assert(
+ nonExisting.isEmpty,
+ "Classpath contains entires that don't exist on disk:\n" ++ nonExisting.mkString("\n") ++ "\nin classpath:\n"++string
+ )
+
+ def +:(file: File) = ClassPath(file +: files)
+ def :+(file: File) = ClassPath(files :+ file)
+ def ++(other: ClassPath) = ClassPath(files ++ other.files)
+ def string = strings.mkString( File.pathSeparator )
+ def strings = files.map{
+ f => f.string ++ ( if(f.isDirectory) "/" else "" )
+ }.sorted
+ def toConsole = string
+}
diff --git a/stage1/ContextImplementation.scala b/stage1/ContextImplementation.scala
new file mode 100644
index 0000000..91c54f4
--- /dev/null
+++ b/stage1/ContextImplementation.scala
@@ -0,0 +1,22 @@
+package cbt
+import java.io._
+import java.util.concurrent.ConcurrentHashMap
+import java.lang._
+
+case class ContextImplementation(
+ projectDirectory: File,
+ cwd: File,
+ argsArray: Array[String],
+ enabledLoggersArray: Array[String],
+ startCompat: Long,
+ cbtHasChangedCompat: Boolean,
+ versionOrNull: String,
+ scalaVersionOrNull: String,
+ permanentKeys: ConcurrentHashMap[String,AnyRef],
+ permanentClassLoaders: ConcurrentHashMap[AnyRef,ClassLoader],
+ cache: File,
+ cbtHome: File,
+ cbtRootHome: File,
+ compatibilityTarget: File,
+ parentBuildOrNull: BuildInterface
+) extends Context \ No newline at end of file
diff --git a/stage1/KeyLockedLazyCache.scala b/stage1/KeyLockedLazyCache.scala
new file mode 100644
index 0000000..4eff5b2
--- /dev/null
+++ b/stage1/KeyLockedLazyCache.scala
@@ -0,0 +1,62 @@
+package cbt
+
+import java.util.concurrent.ConcurrentHashMap
+
+private[cbt] class LockableKey
+/**
+A cache that lazily computes values if needed during lookup.
+Locking occurs on the key, so separate keys can be looked up
+simultaneously without a deadlock.
+*/
+final private[cbt] class KeyLockedLazyCache[Key <: AnyRef,Value <: AnyRef](
+ val keys: ConcurrentHashMap[Key,AnyRef],
+ val values: ConcurrentHashMap[AnyRef,Value],
+ logger: Option[Logger]
+){
+ def get( key: Key, value: => Value ): Value = {
+ val lockableKey = keys.synchronized{
+ if( ! (keys containsKey key) ){
+ val lockableKey = new LockableKey
+ //logger.foreach(_.resolver("CACHE MISS: " ++ key.toString))
+ keys.put( key, lockableKey )
+ lockableKey
+ } else {
+ val lockableKey = keys get key
+ //logger.foreach(_.resolver("CACHE HIT: " ++ lockableKey.toString ++ " -> " ++ key.toString))
+ lockableKey
+ }
+ }
+ import collection.JavaConversions._
+ //logger.resolver("CACHE: \n" ++ keys.mkString("\n"))
+ // synchronizing on key only, so asking for a particular key does
+ // not block the whole cache, but just that cache entry
+ lockableKey.synchronized{
+ if( ! (values containsKey lockableKey) ){
+ values.put( lockableKey, value )
+ }
+ values get lockableKey
+ }
+ }
+ def update( key: Key, value: Value ): Value = {
+ val lockableKey = keys get key
+ lockableKey.synchronized{
+ values.put( lockableKey, value )
+ value
+ }
+ }
+ def remove( key: Key ) = keys.synchronized{
+ assert(keys containsKey key)
+ val lockableKey = keys get key
+ lockableKey.synchronized{
+ if(values containsKey lockableKey){
+ // this is so values in the process of being replaced (which mean they have a key but no value)
+ // are not being removed
+ keys.remove( key )
+ values.remove( lockableKey )
+ }
+ }
+ }
+ def containsKey( key: Key ) = keys.synchronized{
+ keys containsKey key
+ }
+}
diff --git a/stage1/MavenRepository.scala b/stage1/MavenRepository.scala
new file mode 100644
index 0000000..4184d2d
--- /dev/null
+++ b/stage1/MavenRepository.scala
@@ -0,0 +1,9 @@
+package cbt
+import java.io._
+import java.net._
+case class MavenResolver( cbtHasChanged: Boolean, mavenCache: File, urls: URL* ){
+ def bind( dependencies: MavenDependency* )(implicit logger: Logger): Seq[BoundMavenDependency]
+ = dependencies.map( BoundMavenDependency(cbtHasChanged,mavenCache,_,urls.to) ).to
+ def bindOne( dependency: MavenDependency )(implicit logger: Logger): BoundMavenDependency
+ = BoundMavenDependency( cbtHasChanged, mavenCache, dependency, urls.to )
+}
diff --git a/stage1/MultiClassLoader.scala b/stage1/MultiClassLoader.scala
new file mode 100644
index 0000000..9546d47
--- /dev/null
+++ b/stage1/MultiClassLoader.scala
@@ -0,0 +1,42 @@
+package cbt
+import java.net._
+import scala.collection.JavaConverters._
+
+// do not make this a case class, required object identity equality
+class MultiClassLoader(final val parents: Seq[ClassLoader])(implicit val logger: Logger) extends ClassLoader(null) with CachingClassLoader{
+ override def findClass(name: String) = {
+ parents.find( parent =>
+ try{
+ null != parent.loadClass(name) // FIXME: is it correct to just ignore the resolve argument here?
+ } catch {
+ case _:ClassNotFoundException => false
+ }
+ ).map(
+ _.loadClass(name)
+ ).getOrElse( null )
+ }
+
+ // FIXME: is there more than findClass and findResource that needs to be dispatched?
+ override def findResource(name: String): URL = {
+ parents.foldLeft(null: URL)(
+ (acc, parent) => if( acc == null ) parent.getResource(name) else null
+ )
+ }
+ override def findResources(name: String): java.util.Enumeration[URL] = {
+ java.util.Collections.enumeration(
+ parents.flatMap( _.getResources(name).asScala ).asJava
+ )
+ }
+
+ override def toString = (
+ scala.Console.BLUE
+ ++ super.toString
+ ++ scala.Console.RESET
+ ++ "("
+ ++ (
+ if(parents.nonEmpty)(
+ "\n" ++ parents.map(_.toString).mkString(",\n").split("\n").map(" "++_).mkString("\n") ++ "\n"
+ ) else ""
+ ) ++")"
+ )
+}
diff --git a/stage1/PoorMansProfiler.scala b/stage1/PoorMansProfiler.scala
new file mode 100644
index 0000000..b7aa47d
--- /dev/null
+++ b/stage1/PoorMansProfiler.scala
@@ -0,0 +1,23 @@
+/*
+// temporary debugging tool
+package cbt
+import java.util.concurrent.ConcurrentHashMap
+import collection.JavaConversions._
+object PoorMansProfiler{
+ val entries = new ConcurrentHashMap[String, Long]
+ def profile[T](name: String)(code: => T): T = {
+ val before = System.currentTimeMillis
+ if(!(entries containsKey name)){
+ entries.put( name, 0 )
+ }
+ val res = code
+ entries.put( name, (entries get name) + (System.currentTimeMillis - before) )
+ res
+ }
+ def summary: String = {
+ "Profiling Summary:\n" + entries.toSeq.sortBy(_._2).map{
+ case (name, value) => name + ": " + (value / 1000.0)
+ }.mkString("\n")
+ }
+}
+*/ \ No newline at end of file
diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala
new file mode 100644
index 0000000..c94d1a4
--- /dev/null
+++ b/stage1/Stage1.scala
@@ -0,0 +1,195 @@
+package cbt
+
+import java.io._
+import java.util.concurrent.ConcurrentHashMap
+
+import scala.collection.JavaConverters._
+
+final case class Stage1ArgsParser(__args: Seq[String]) {
+ val _args = __args.drop(1)
+ /**
+ * Raw parameters including their `-D` flag.
+ **/
+ val propsRaw: Seq[String] = _args.toVector.filter(_.startsWith("-D"))
+
+ /**
+ * All arguments that weren't `-D` property declarations.
+ **/
+ val args: Seq[String] = _args.toVector diff propsRaw
+
+ /**
+ * Parsed properties, as a map of keys to values.
+ **/
+ val props = propsRaw
+ .map(_.drop(2).split("=")).map({
+ case Array(key, value) =>
+ key -> value
+ }).toMap ++ System.getProperties.asScala
+
+ val enabledLoggers = props.get("log")
+
+ val tools = _args contains "tools"
+}
+
+
+abstract class Stage2Base{
+ def run( context: Stage2Args ): Unit
+}
+
+case class Stage2Args(
+ cwd: File,
+ args: Seq[String],
+ cbtHasChanged: Boolean,
+ classLoaderCache: ClassLoaderCache,
+ cache: File,
+ cbtHome: File,
+ compatibilityTarget: File
+){
+ val ClassLoaderCache(
+ logger,
+ permanentKeys,
+ permanentClassLoaders
+ ) = classLoaderCache
+}
+object Stage1{
+ protected def newerThan( a: File, b: File ) ={
+ a.lastModified > b.lastModified
+ }
+
+ def getBuild( _context: java.lang.Object, _cbtChanged: java.lang.Boolean ) = {
+ val context = _context.asInstanceOf[Context]
+ val logger = new Logger( context.enabledLoggers, context.start )
+ val (changed, classLoader) = buildStage2(
+ context.compatibilityTarget,
+ ClassLoaderCache(
+ logger,
+ context.permanentKeys,
+ context.permanentClassLoaders
+ ),
+ _cbtChanged,
+ context.cbtHome,
+ context.cache
+ )
+
+ classLoader
+ .loadClass("cbt.Stage2")
+ .getMethod( "getBuild", classOf[java.lang.Object], classOf[java.lang.Boolean] )
+ .invoke(null, context, (_cbtChanged || changed): java.lang.Boolean)
+ }
+
+ def buildStage2(
+ compatibilityTarget: File, classLoaderCache: ClassLoaderCache, _cbtChanged: Boolean, cbtHome: File, cache: File
+ ): (Boolean, ClassLoader) = {
+ import classLoaderCache.logger
+
+ val lib = new Stage1Lib(logger)
+ import lib._
+ val paths = CbtPaths(cbtHome, cache)
+ import paths._
+
+ val stage2sourceFiles = (
+ stage2.listFiles ++ (stage2 ++ "/plugins").listFiles
+ ).toVector.filter(_.isFile).filter(_.toString.endsWith(".scala"))
+
+ val cbtHasChanged = _cbtChanged || lib.needsUpdate(stage2sourceFiles, stage2StatusFile)
+
+ val cls = this.getClass.getClassLoader.loadClass("cbt.NailgunLauncher")
+
+ val cbtDependency = CbtDependency(cbtHasChanged, mavenCache, nailgunTarget, stage1Target, stage2Target, compatibilityTarget)
+
+ logger.stage1("Compiling stage2 if necessary")
+ compile(
+ cbtHasChanged,
+ cbtHasChanged,
+ stage2sourceFiles, stage2Target, stage2StatusFile,
+ cbtDependency.dependencyClasspath,
+ mavenCache,
+ Seq("-deprecation","-feature","-unchecked"), classLoaderCache,
+ zincVersion = "0.3.9", scalaVersion = constants.scalaVersion
+ )
+
+ logger.stage1(s"calling CbtDependency.classLoader")
+ if( cbtHasChanged && classLoaderCache.persistent.containsKey( cbtDependency.classpath.string ) ) {
+ classLoaderCache.persistent.remove( cbtDependency.classpath.string )
+ }
+
+ val stage2ClassLoader = cbtDependency.classLoader(classLoaderCache)
+
+ {
+ // a few classloader sanity checks
+ val compatibilityClassLoader =
+ cbtDependency.stage1Dependency.compatibilityDependency.classLoader(classLoaderCache)
+ assert(
+ classOf[BuildInterface].getClassLoader == compatibilityClassLoader,
+ classOf[BuildInterface].getClassLoader.toString ++ "\n\nis not the same as\n\n" ++ compatibilityClassLoader.toString
+ )
+ //-------------
+ val stage1ClassLoader =
+ cbtDependency.stage1Dependency.classLoader(classLoaderCache)
+ assert(
+ classOf[Stage1Dependency].getClassLoader == stage1ClassLoader,
+ classOf[Stage1Dependency].getClassLoader.toString ++ "\n\nis not the same as\n\n" ++ stage1ClassLoader.toString
+ )
+ //-------------
+ assert(
+ Stage0Lib.get(stage2ClassLoader.getParent,"parents").asInstanceOf[Seq[ClassLoader]].contains(stage1ClassLoader),
+ stage1ClassLoader.toString ++ "\n\nis not contained in parents of\n\n" ++ stage2ClassLoader.toString
+ )
+ }
+
+ ( cbtHasChanged, stage2ClassLoader )
+ }
+
+ def run(
+ _args: Array[String],
+ cache: File,
+ cbtHome: File,
+ _cbtChanged: java.lang.Boolean,
+ compatibilityTarget: File,
+ start: java.lang.Long,
+ classLoaderCacheKeys: ConcurrentHashMap[String,AnyRef],
+ classLoaderCacheValues: ConcurrentHashMap[AnyRef,ClassLoader]
+ ): Int = {
+ val args = Stage1ArgsParser(_args.toVector)
+ val logger = new Logger(args.enabledLoggers, start)
+ logger.stage1(s"Stage1 start")
+
+ val classLoaderCache = ClassLoaderCache(
+ logger,
+ classLoaderCacheKeys,
+ classLoaderCacheValues
+ )
+
+
+ val (cbtHasChanged, classLoader) = buildStage2( compatibilityTarget, classLoaderCache, _cbtChanged, cbtHome, cache )
+
+ val stage2Args = Stage2Args(
+ new File( args.args(0) ),
+ args.args.drop(1).toVector,
+ // launcher changes cause entire nailgun restart, so no need for them here
+ cbtHasChanged = cbtHasChanged,
+ classLoaderCache = classLoaderCache,
+ cache,
+ cbtHome,
+ compatibilityTarget
+ )
+
+ logger.stage1(s"Run Stage2")
+ val exitCode = (
+ classLoader
+ .loadClass(
+ if(args.tools) "cbt.ToolsStage2" else "cbt.Stage2"
+ )
+ .getMethod( "run", classOf[Stage2Args] )
+ .invoke(
+ null,
+ stage2Args
+ ) match {
+ case code: ExitCode => code
+ case _ => ExitCode.Success
+ }
+ ).integer
+ logger.stage1(s"Stage1 end")
+ return exitCode;
+ }
+}
diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala
new file mode 100644
index 0000000..273b9af
--- /dev/null
+++ b/stage1/Stage1Lib.scala
@@ -0,0 +1,447 @@
+package cbt
+
+import java.io._
+import java.lang.reflect.InvocationTargetException
+import java.net._
+import java.nio.charset.StandardCharsets
+import java.nio.file._
+import java.nio.file.attribute.FileTime
+import javax.tools._
+import java.security._
+import java.util.{Set=>_,Map=>_,List=>_,_}
+import java.util.concurrent.ConcurrentHashMap
+import javax.xml.bind.annotation.adapters.HexBinaryAdapter
+
+// CLI interop
+case class ExitCode(integer: Int)
+object ExitCode{
+ val Success = ExitCode(0)
+ val Failure = ExitCode(1)
+}
+
+object CatchTrappedExitCode{
+ def unapply(e: Throwable): Option[ExitCode] = {
+ Option(e) flatMap {
+ case i: InvocationTargetException => unapply(i.getTargetException)
+ case e if TrapSecurityManager.isTrappedExit(e) => Some( ExitCode(TrapSecurityManager.exitCode(e)) )
+ case _ => None
+ }
+ }
+}
+
+class BaseLib{
+ def realpath(name: File) = new File(java.nio.file.Paths.get(name.getAbsolutePath).normalize.toString)
+}
+
+class Stage1Lib( val logger: Logger ) extends BaseLib{
+ lib =>
+ implicit val implicitLogger: Logger = logger
+
+ def libMajorVersion(libFullVersion: String) = libFullVersion.split("\\.").take(2).mkString(".")
+
+ // ========== file system / net ==========
+
+ def array2hex(padTo: Int, array: Array[Byte]): String = {
+ val hex = new java.math.BigInteger(1, array).toString(16)
+ ("0" * (padTo-hex.size)) ++ hex
+ }
+ def md5( bytes: Array[Byte] ): String = array2hex(32, MessageDigest.getInstance("MD5").digest(bytes)).toLowerCase
+ def sha1( bytes: Array[Byte] ): String = array2hex(40, MessageDigest.getInstance("SHA-1").digest(bytes)).toLowerCase
+
+ def red(string: String) = scala.Console.RED++string++scala.Console.RESET
+ def blue(string: String) = scala.Console.BLUE++string++scala.Console.RESET
+ def green(string: String) = scala.Console.GREEN++string++scala.Console.RESET
+
+ def write(file: File, content: String, options: OpenOption*): File = Stage0Lib.write(file, content, options:_*)
+
+ def download(url: URL, target: File, sha1: Option[String]): Boolean = {
+ if( target.exists ){
+ logger.resolver(green("found ") ++ url.string)
+ true
+ } else {
+ val incomplete = ( target ++ ".incomplete" ).toPath;
+ val connection = Stage0Lib.openConnectionConsideringProxy(url)
+ if(connection.getResponseCode != HttpURLConnection.HTTP_OK){
+ logger.resolver(blue("not found: ") ++ url.string)
+ false
+ } else {
+ System.err.println(blue("downloading ") ++ url.string)
+ logger.resolver(blue("to ") ++ target.string)
+ target.getParentFile.mkdirs
+ val stream = connection.getInputStream
+ try{
+ Files.copy(stream, incomplete, StandardCopyOption.REPLACE_EXISTING)
+ } finally {
+ stream.close()
+ }
+ sha1.foreach{
+ hash =>
+ val expected = hash.toLowerCase
+ val actual = this.sha1(Files.readAllBytes(incomplete))
+ assert( expected == actual, s"$expected == $actual" )
+ logger.resolver( green("verified") ++ " checksum for " ++ target.string)
+ }
+ Files.move(incomplete, target.toPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+ true
+ }
+ }
+ }
+
+ 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)
+ trapExitCode{
+ val c = classLoader.loadClass(cls)
+ val m = c.getMethod( "main", classOf[Array[String]] )
+ val instance =
+ if(!fakeInstance) null else c.newInstance
+ assert(
+ fakeInstance || (m.getModifiers & java.lang.reflect.Modifier.STATIC) > 0,
+ "Cannot run non-static method " ++ cls+".main"
+ )
+ m.invoke( instance, args.toArray.asInstanceOf[AnyRef] )
+ ExitCode.Success
+ }
+ }
+
+ /** shows an interactive dialogue in the shell asking the user to pick one of many choices */
+ def pickOne[T]( msg: String, choices: Seq[T] )( show: T => String ): Option[T] = {
+ if(choices.size == 0) None else if(choices.size == 1) Some(choices.head) else {
+ Option(System.console).map{
+ console =>
+ val indexedChoices: Map[Int, T] = choices.zipWithIndex.toMap.mapValues(_+1).map(_.swap)
+ System.err.println(
+ indexedChoices.map{ case (index,choice) => s"[${index}] "++show(choice)}.mkString("\n")
+ )
+ val range = s"1 - ${indexedChoices.size}"
+ System.err.println()
+ System.err.println( msg ++ " [" ++ range ++ "] " )
+ val answer = console.readLine()
+ val choice = try{
+ Some(Integer.parseInt(answer))
+ }catch{
+ case e:java.lang.NumberFormatException => None
+ }
+
+ choice.flatMap(indexedChoices.get).orElse{
+ System.err.println("Not in range "++range)
+ None
+ }
+ }.getOrElse{
+ System.err.println("System.console() == null. Use `cbt direct <task>` or see https://github.com/cvogt/cbt/issues/236")
+ None
+ }
+ }
+ }
+
+ /** interactively pick one main class */
+ def runClass( mainClasses: Seq[Class[_]] ): Option[Class[_]] = {
+ 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)
+ .filter(_.isFile)
+ .map(_.getPath)
+ .collect{
+ // no $ to avoid inner classes
+ case path if !path.contains("$") && path.endsWith(".class") =>
+ classLoader.loadClass(
+ path
+ .stripSuffix(".class")
+ .stripPrefix(targetDirectory.getPath)
+ .stripPrefix(File.separator) // 1 for the slash
+ .replace(File.separator, ".")
+ )
+ }.filter(
+ _.getDeclaredMethods().exists( m =>
+ m.getName == "main"
+ && m.getParameterTypes.toList == List(arrayClass)
+ && m.getReturnType == unitClass
+ )
+ )
+ }
+
+ implicit class ClassLoaderExtensions(classLoader: ClassLoader){
+ def canLoad(className: String) = {
+ try{
+ classLoader.loadClass(className)
+ true
+ } catch {
+ case e: ClassNotFoundException => false
+ }
+ }
+ }
+
+ def needsUpdate( sourceFiles: Seq[File], statusFile: File ) = {
+ val lastCompile = statusFile.lastModified
+ sourceFiles.filter(_.lastModified > lastCompile).nonEmpty
+ }
+
+ def compile(
+ cbtHasChanged: Boolean,
+ needsRecompile: Boolean,
+ files: Seq[File],
+ compileTarget: File,
+ statusFile: File,
+ classpath: ClassPath,
+ mavenCache: File,
+ scalacOptions: Seq[String] = Seq(),
+ classLoaderCache: ClassLoaderCache,
+ zincVersion: String,
+ scalaVersion: String
+ ): Option[File] = {
+
+ val cp = classpath.string
+ if(classpath.files.isEmpty)
+ throw new Exception("Trying to compile with empty classpath. Source files: " ++ files.toString)
+
+ if( files.isEmpty ){
+ None
+ }else{
+ if( needsRecompile ){
+ def Resolver(urls: URL*) = MavenResolver(cbtHasChanged, mavenCache, urls: _*)
+ val zinc = Resolver(mavenCentral).bindOne(MavenDependency("com.typesafe.zinc","zinc", zincVersion))
+ val zincDeps = zinc.transitiveDependencies
+
+ val sbtInterface =
+ zincDeps
+ .collect{ case d @
+ BoundMavenDependency(
+ _, _, MavenDependency( "com.typesafe.sbt", "sbt-interface", _, Classifier.none), _
+ ) => d
+ }
+ .headOption
+ .getOrElse( throw new Exception(s"cannot find sbt-interface in zinc $zincVersion dependencies: "++zincDeps.toString) )
+ .jar
+
+ val compilerInterface =
+ zincDeps
+ .collect{ case d @
+ BoundMavenDependency(
+ _, _, MavenDependency( "com.typesafe.sbt", "compiler-interface", _, Classifier.sources), _
+ ) => d
+ }
+ .headOption
+ .getOrElse( throw new Exception(s"cannot find compiler-interface in zinc $zincVersion dependencies: "++zincDeps.toString) )
+ .jar
+
+ val scalaLibrary = Resolver(mavenCentral).bindOne(MavenDependency("org.scala-lang","scala-library",scalaVersion)).jar
+ val scalaReflect = Resolver(mavenCentral).bindOne(MavenDependency("org.scala-lang","scala-reflect",scalaVersion)).jar
+ val scalaCompiler = Resolver(mavenCentral).bindOne(MavenDependency("org.scala-lang","scala-compiler",scalaVersion)).jar
+
+ val start = System.currentTimeMillis
+
+ val _class = "com.typesafe.zinc.Main"
+ val dualArgs =
+ Seq(
+ "-scala-compiler", scalaCompiler.toString,
+ "-scala-library", scalaLibrary.toString,
+ "-sbt-interface", sbtInterface.toString,
+ "-compiler-interface", compilerInterface.toString,
+ "-scala-extra", scalaReflect.toString,
+ "-d", compileTarget.toString
+ )
+ val singleArgs = scalacOptions.map( "-S" ++ _ )
+
+ val code =
+ redirectOutToErr{
+ System.err.println("Compiling to " ++ compileTarget.toString)
+ try{
+ lib.runMain(
+ _class,
+ dualArgs ++ singleArgs ++ Seq(
+ "-cp", cp // let's put cp last. It so long
+ ) ++ files.map(_.toString),
+ zinc.classLoader(classLoaderCache)
+ )
+ } catch {
+ case e: Exception =>
+ System.err.println(red("The Scala compiler crashed. Try running it by hand:"))
+ System.out.println(s"""
+ java -cp \\
+ ${zinc.classpath.strings.mkString(":\\\n")} \\
+ \\
+ ${_class} \\
+ \\
+ ${dualArgs.grouped(2).map(_.mkString(" ")).mkString(" \\\n")} \\
+ \\
+ ${singleArgs.mkString(" \\\n")} \\
+ \\
+ -cp \\
+ ${classpath.strings.mkString(":\\\n")} \\
+ \\
+ ${files.sorted.mkString(" \\\n")}
+ """
+ )
+ ExitCode.Failure
+ }
+ }
+
+ if(code == ExitCode.Success){
+ // write version and when last compilation started so we can trigger
+ // recompile if cbt version changed or newer source files are seen
+ write(statusFile, "")//cbtVersion.getBytes)
+ Files.setLastModifiedTime(statusFile.toPath, FileTime.fromMillis(start) )
+ } else {
+ System.exit(code.integer) // FIXME: let's find a better solution for error handling. Maybe a monad after all.
+ }
+ }
+ Some( compileTarget )
+ }
+ }
+ def redirectOutToErr[T](code: => T): T = {
+ val ( out, err ) = try{
+ // trying nailgun's System.our/err wrapper
+ val field = System.out.getClass.getDeclaredField("streams")
+ assert(System.out.getClass.getName == "com.martiansoftware.nailgun.ThreadLocalPrintStream")
+ assert(System.err.getClass.getName == "com.martiansoftware.nailgun.ThreadLocalPrintStream")
+ field.setAccessible(true)
+ val out = field.get(System.out).asInstanceOf[ThreadLocal[PrintStream]]
+ val err = field.get(System.err).asInstanceOf[ThreadLocal[PrintStream]]
+ ( out, err )
+ } catch {
+ case e: NoSuchFieldException =>
+ // trying cbt's System.our/err wrapper
+ val field = classOf[FilterOutputStream].getDeclaredField("out")
+ field.setAccessible(true)
+ val outStream = field.get(System.out)
+ val errStream = field.get(System.err)
+ assert(outStream.getClass.getName == "cbt.ThreadLocalOutputStream")
+ assert(errStream.getClass.getName == "cbt.ThreadLocalOutputStream")
+ val field2 = outStream.getClass.getDeclaredField("threadLocal")
+ field2.setAccessible(true)
+ val out = field2.get(outStream).asInstanceOf[ThreadLocal[PrintStream]]
+ val err = field2.get(errStream).asInstanceOf[ThreadLocal[PrintStream]]
+ ( out, err )
+ }
+
+ val oldOut: PrintStream = out.get
+ out.set( err.get: PrintStream )
+ val res = code
+ out.set( oldOut )
+ res
+ }
+
+ def trapExitCode( code: => ExitCode ): ExitCode = {
+ val trapExitCodeBefore = TrapSecurityManager.trapExitCode().get
+ try{
+ TrapSecurityManager.trapExitCode().set(true)
+ code
+ } catch {
+ case CatchTrappedExitCode(exitCode) =>
+ logger.stage1(s"caught exit code $exitCode")
+ exitCode
+ } finally {
+ TrapSecurityManager.trapExitCode().set(trapExitCodeBefore)
+ }
+ }
+
+ def ScalaDependency(
+ groupId: String, artifactId: String, version: String, classifier: Classifier = Classifier.none,
+ scalaMajorVersion: String
+ ) =
+ MavenDependency(
+ groupId, artifactId ++ "_" ++ scalaMajorVersion, version, classifier
+ )
+
+ def cacheOnDisk[T]
+ ( cbtHasChanged: Boolean, cacheFile: File )
+ ( deserialize: String => T )
+ ( serialize: T => String )
+ ( compute: => Seq[T] ) = {
+ if(!cbtHasChanged && cacheFile.exists){
+ import collection.JavaConversions._
+ Files
+ .readAllLines( cacheFile.toPath, StandardCharsets.UTF_8 )
+ .toStream
+ .map(deserialize)
+ } else {
+ val result = compute
+ val string = result.map(serialize).mkString("\n")
+ write(cacheFile, string)
+ result
+ }
+ }
+
+ def dependencyTreeRecursion(root: Dependency, indent: Int = 0): String = (
+ ( " " * indent )
+ ++ (if(root.needsUpdate) red(root.show) else root.show)
+ ++ root.dependencies.map( d =>
+ "\n" ++ dependencyTreeRecursion(d,indent + 1)
+ ).mkString
+ )
+
+ def transitiveDependencies(dependency: Dependency): Seq[Dependency] = {
+ def linearize(deps: Seq[Dependency]): Seq[Dependency] = {
+ // Order is important here in order to generate the correct lineraized dependency order for EarlyDependencies
+ // (and maybe this as well in case we want to get rid of MultiClassLoader)
+ try{
+ if(deps.isEmpty) deps else ( deps ++ linearize(deps.flatMap(_.dependencies)) )
+ } catch{
+ case e: Exception => throw new Exception(dependency.show, e)
+ }
+ }
+
+ // FIXME: this is probably wrong too eager.
+ // We should consider replacing versions during traversals already
+ // not just replace after traversals, because that could mean we
+ // pulled down dependencies current versions don't even rely
+ // on anymore.
+
+ val deps: Seq[Dependency] = linearize(dependency.dependencies).reverse.distinct.reverse
+ val hasInfo: Seq[Dependency with ArtifactInfo] = deps.collect{ case d:Dependency with ArtifactInfo => d }
+ val noInfo: Seq[Dependency] = deps.filter{
+ case _:Dependency with ArtifactInfo => false
+ case _ => true
+ }
+ noInfo ++ BoundMavenDependency.updateOutdated( hasInfo ).reverse.distinct
+ }
+
+
+ def actual(current: Dependency, latest: Map[(String,String),Dependency]) = current match {
+ case d: ArtifactInfo => latest((d.groupId,d.artifactId))
+ case d => d
+ }
+
+ def classLoaderRecursion( dependency: Dependency, latest: Map[(String,String),Dependency], cache: ClassLoaderCache ): ClassLoader = {
+ val d = dependency
+ val dependencies = dependency.dependencies
+ def dependencyClassLoader( latest: Map[(String,String),Dependency], cache: ClassLoaderCache ): ClassLoader = {
+ if( dependency.dependencies.isEmpty ){
+ // wrap for caching
+ new cbt.URLClassLoader( ClassPath(), ClassLoader.getSystemClassLoader().getParent() )
+ } else if( dependencies.size == 1 ){
+ classLoaderRecursion( dependencies.head, latest, cache )
+ } else{
+ val cp = d.dependencyClasspath.string
+ if( dependencies.exists(_.needsUpdate) && cache.persistent.containsKey(cp) ){
+ cache.persistent.remove(cp)
+ }
+ def cl = new MultiClassLoader( dependencies.map( classLoaderRecursion(_, latest, cache) ) )
+ if(d.isInstanceOf[BuildInterface])
+ cl // Don't cache builds right now. We need to fix invalidation first.
+ else
+ cache.persistent.get( cp, cl )
+ }
+ }
+
+ val a = actual( dependency, latest )
+ def cl = new cbt.URLClassLoader( a.exportedClasspath, dependencyClassLoader(latest, cache) )
+ if(d.isInstanceOf[BuildInterface])
+ cl
+ else
+ cache.persistent.get( a.classpath.string, cl )
+ }
+}
diff --git a/stage1/URLClassLoader.scala b/stage1/URLClassLoader.scala
new file mode 100644
index 0000000..ff8d2a1
--- /dev/null
+++ b/stage1/URLClassLoader.scala
@@ -0,0 +1,47 @@
+package cbt
+
+import java.net._
+import scala.util.Try
+
+case class URLClassLoader( classPath: ClassPath, parent: ClassLoader )( implicit val logger: Logger )
+ extends java.net.URLClassLoader(
+ classPath.strings.map( p => new URL("file:" ++ p) ).toArray,
+ parent
+ ) with CachingClassLoader{
+ val id = Math.abs( new java.util.Random().nextInt )
+ override def toString = (
+ scala.Console.BLUE
+ ++ getClass.getSimpleName ++ ":" ++ id.toString
+ ++ scala.Console.RESET
+ ++ "(\n"
+ ++ (
+ getURLs.map(_.toString).sorted.mkString(",\n")
+ ++ (
+ if(getParent() != ClassLoader.getSystemClassLoader().getParent())
+ ",\n" ++ Option(getParent()).map(_.toString).getOrElse("null")
+ else ""
+ )
+ ).split("\n").map(" "++_).mkString("\n")
+ ++ "\n)"
+ )
+}
+
+/*
+trait ClassLoaderLogging extends ClassLoader{
+ def logger: Logger
+ val prefix = s"[${getClass.getSimpleName}] "
+ val postfix = " in \name" ++ this.toString
+ override def loadClass(name: String, resolve: Boolean): Class[_] = {
+ //logger.resolver(prefix ++ s"loadClass($name, $resolve)" ++ postfix )
+ super.loadClass(name, resolve)
+ }
+ override def loadClass(name: String): Class[_] = {
+ //logger.resolver(prefix ++ s"loadClass($name)" ++ postfix )
+ super.loadClass(name)
+ }
+ override def findClass(name: String): Class[_] = {
+ //logger.resolver(prefix ++ s"findClass($name)" ++ postfix )
+ super.findClass(name)
+ }
+}
+*/
diff --git a/stage1/cbt.scala b/stage1/cbt.scala
new file mode 100644
index 0000000..7a239a1
--- /dev/null
+++ b/stage1/cbt.scala
@@ -0,0 +1,99 @@
+package cbt
+import java.io._
+import java.nio.file._
+import java.net._
+import java.util.concurrent.ConcurrentHashMap
+
+object `package`{
+ val mavenCentral = new URL("https://repo1.maven.org/maven2")
+ val jcenter = new URL("https://jcenter.bintray.com")
+ def bintray(owner: String) = new URL(s"https://dl.bintray.com/$owner/maven") // FIXME: url encode owner
+ private val sonatypeBase = new URL("https://oss.sonatype.org/content/repositories/")
+ val sonatypeReleases = sonatypeBase ++ "releases"
+ val sonatypeSnapshots = sonatypeBase ++ "snapshots"
+
+ private val lib = new BaseLib
+ implicit class FileExtensionMethods( 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( file.toString ++ s )
+ }
+ def /(s: String): File = new File(file.getAbsolutePath + File.separator + s)
+ def parent = lib.realpath(file ++ "/..")
+ def string = file.toString
+ }
+ implicit class URLExtensionMethods( url: URL ){
+ def ++( s: String ): URL = new URL( url.toString ++ s )
+ def string = url.toString
+ }
+ implicit class BuildInterfaceExtensions(build: BuildInterface){
+ import build._
+ def triggerLoopFiles: Seq[File] = triggerLoopFilesArray.to
+ def crossScalaVersions: Seq[String] = crossScalaVersionsArray.to
+ }
+ implicit class ArtifactInfoExtensions(subject: ArtifactInfo){
+ import subject._
+ def str = s"$groupId:$artifactId:$version"
+ def show = this.getClass.getSimpleName ++ s"($str)"
+ }
+ implicit class DependencyExtensions(subject: Dependency){
+ import subject._
+ def dependencyClasspath: ClassPath = ClassPath(dependencyClasspathArray.to)
+ def exportedClasspath: ClassPath = ClassPath(exportedClasspathArray.to)
+ def classpath = exportedClasspath ++ dependencyClasspath
+ def dependencies: Seq[Dependency] = dependenciesArray.to
+ def needsUpdate: Boolean = needsUpdateCompat
+ }
+ implicit class ContextExtensions(subject: Context){
+ import subject._
+ val paths = CbtPaths(cbtHome, cache)
+ implicit def logger: Logger = new Logger(enabledLoggers, start)
+ def classLoaderCache: ClassLoaderCache = new ClassLoaderCache(
+ logger,
+ permanentKeys,
+ permanentClassLoaders
+ )
+ def cbtDependency = {
+ import paths._
+ CbtDependency(cbtHasChanged, mavenCache, nailgunTarget, stage1Target, stage2Target, compatibilityTarget)
+ }
+ def args: Seq[String] = argsArray.to
+ def enabledLoggers: Set[String] = enabledLoggersArray.to
+ def scalaVersion = Option(scalaVersionOrNull)
+ def version = Option(versionOrNull)
+ def parentBuild = Option(parentBuildOrNull)
+ def start: scala.Long = startCompat
+ def cbtHasChanged: scala.Boolean = cbtHasChangedCompat
+
+ def copy(
+ projectDirectory: File = projectDirectory,
+ args: Seq[String] = args,
+ enabledLoggers: Set[String] = enabledLoggers,
+ cbtHasChanged: Boolean = cbtHasChanged,
+ version: Option[String] = version,
+ scalaVersion: Option[String] = scalaVersion,
+ cache: File = cache,
+ cbtHome: File = cbtHome,
+ parentBuild: Option[BuildInterface] = None
+ ): Context = ContextImplementation(
+ projectDirectory,
+ cwd,
+ args.to,
+ enabledLoggers.to,
+ startCompat,
+ cbtHasChangedCompat,
+ version.getOrElse(null),
+ scalaVersion.getOrElse(null),
+ permanentKeys,
+ permanentClassLoaders,
+ cache,
+ cbtHome,
+ cbtRootHome,
+ compatibilityTarget,
+ parentBuild.getOrElse(null)
+ )
+ }
+}
+
diff --git a/stage1/constants.scala b/stage1/constants.scala
new file mode 100644
index 0000000..437cf19
--- /dev/null
+++ b/stage1/constants.scala
@@ -0,0 +1,7 @@
+package cbt
+object constants{
+ val scalaXmlVersion = "1.0.5"
+ val scalaVersion = "2.11.8"
+ val zincVersion = "0.3.9"
+ val scalaMajorVersion = scalaVersion.split("\\.").take(2).mkString(".")
+}
diff --git a/stage1/logger.scala b/stage1/logger.scala
new file mode 100644
index 0000000..57f0cfa
--- /dev/null
+++ b/stage1/logger.scala
@@ -0,0 +1,61 @@
+package cbt
+
+/**
+ * This represents a logger with namespaces that can be enabled or disabled as needed. The
+ * namespaces are defined using {{enabledLoggers}}. Possible values are defined in the subobject
+ * "names".
+ *
+ * We can replace this with something more sophisticated eventually.
+ */
+case class Logger(enabledLoggers: Set[String], start: Long) {
+ def this(enabledLoggers: Option[String], start: Long) = {
+ this(
+ enabledLoggers.toVector.flatMap( _.split(",") ).toSet,
+ start
+ )
+ }
+
+ val disabledLoggers: Set[String] = enabledLoggers.filter(_.startsWith("-")).map(_.drop(1))
+
+ def log(name: String, msg: => String) = {
+ if(
+ (
+ (enabledLoggers contains name)
+ || (enabledLoggers contains "all")
+ ) && !(disabledLoggers contains name)
+ ){
+ logUnguarded(name, msg)
+ }
+ }
+
+ def showInvocation(method: String, args: Any) = method ++ "( " ++ args.toString ++ " )"
+
+ final def stage1(msg: => String) = log(names.stage1, msg)
+ final def stage2(msg: => String) = log(names.stage2, msg)
+ final def loop(msg: => String) = log(names.loop, msg)
+ final def task(msg: => String) = log(names.task, msg)
+ final def composition(msg: => String) = log(names.composition, msg)
+ final def resolver(msg: => String) = log(names.resolver, msg)
+ final def lib(msg: => String) = log(names.lib, msg)
+ final def test(msg: => String) = log(names.test, msg)
+ final def git(msg: => String) = log(names.git, msg)
+ final def pom(msg: => String) = log(names.pom, msg)
+
+ private object names{
+ val stage1 = "stage1"
+ val stage2 = "stage2"
+ val loop = "loop"
+ val task = "task"
+ val resolver = "resolver"
+ val composition = "composition"
+ val lib = "lib"
+ val test = "test"
+ val pom = "pom"
+ val git = "git"
+ }
+
+ private def logUnguarded(name: String, msg: => String) = {
+ val timeTaken = ((System.currentTimeMillis.toDouble - start) / 1000).toString
+ System.err.println( s"[$timeTaken][$name] $msg" )
+ }
+}
diff --git a/stage1/resolver.scala b/stage1/resolver.scala
new file mode 100644
index 0000000..13e8e52
--- /dev/null
+++ b/stage1/resolver.scala
@@ -0,0 +1,401 @@
+package cbt
+import java.nio.file._
+import java.nio.charset.StandardCharsets
+import java.net._
+import java.io._
+import scala.xml._
+import scala.concurrent._
+import scala.concurrent.duration._
+
+trait DependencyImplementation extends Dependency{
+ implicit protected def logger: Logger
+ protected def lib = new Stage1Lib(logger)
+
+ def needsUpdate: Boolean
+ //def cacheClassLoader: Boolean = false
+ private[cbt] def targetClasspath: ClassPath
+ def dependencyClasspathArray: Array[File] = dependencyClasspath.files.toArray
+ def exportedClasspathArray: Array[File] = exportedClasspath.files.toArray
+ def exportedClasspath: ClassPath
+ def dependenciesArray: Array[Dependency] = dependencies.to
+
+ def needsUpdateCompat: java.lang.Boolean = needsUpdate
+
+ /*
+ //private type BuildCache = KeyLockedLazyCache[Dependency, Future[ClassPath]]
+ def exportClasspathConcurrently: ClassPath = {
+ // FIXME: this should separate a blocking and a non-blocking EC
+ import scala.concurrent.ExecutionContext.Implicits.global
+ Await.result(
+ exportClasspathConcurrently(
+ transitiveDependencies
+ .collect{ case d: ArtifactInfo => d }
+ .groupBy( d => (d.groupId,d.artifactId) )
+ .mapValues( _.head )
+ //, new BuildCache
+ ), // FIXME
+ Duration.Inf
+ )
+ }
+
+ def concurrencyEnabled = false
+
+ /**
+ The implementation of this method is untested and likely buggy
+ at this stage.
+ */
+ def exportClasspathConcurrently(
+ latest: Map[(String, String),Dependency with ArtifactInfo]//, cache: BuildCache
+ )( implicit ec: ExecutionContext ): Future[AnyRef] = {
+ Future.sequence( // trigger compilation / download of all dependencies first
+ this.dependencies.map{
+ d =>
+ // find out latest version of the required dependency
+ val l = d match {
+ case m: BoundMavenDependency => latest( (m.groupId,m.artifactId) )
+ case _ => d
+ }
+ // // trigger compilation if not already triggered
+ // cache.get( l, l.exportClasspathConcurrently( latest, cache ) )
+ l.exportClasspathConcurrently( latest ) // FIXME
+ }
+ ).map(
+ // merge dependency classpaths into one
+ ClassPath.flatten(_)
+ ).map(
+ _ =>
+ // now that all dependencies are done, compile the code of this
+ exportedClasspath
+ )
+ }
+ */
+
+ def classLoader( cache: ClassLoaderCache ): ClassLoader = {
+ /*
+ if( concurrencyEnabled ){
+ // trigger concurrent building / downloading dependencies
+ exportClasspathConcurrently
+ }
+ */
+ lib.classLoaderRecursion(
+ this,
+ (this +: transitiveDependencies).collect{
+ case d: ArtifactInfo => d
+ }.groupBy(
+ d => (d.groupId,d.artifactId)
+ ).mapValues(_.head),
+ cache // FIXME
+ )
+ }
+ // FIXME: these probably need to update outdated as well
+ def classpath : ClassPath = exportedClasspath ++ dependencyClasspath
+ def dependencyClasspath : ClassPath = ClassPath(
+ transitiveDependencies
+ .flatMap(_.exportedClasspath.files)
+ .distinct // <- currently needed here to handle diamond dependencies on builds (duplicate in classpath)
+ )
+ def dependencies: Seq[Dependency]
+
+ private object transitiveDependenciesCache extends Cache[Seq[Dependency]]
+ /** return dependencies in order of linearized dependence. this is a bit tricky. */
+ def transitiveDependencies: Seq[Dependency] = transitiveDependenciesCache{
+ lib.transitiveDependencies(this)
+ }
+
+ override def show: String = this.getClass.getSimpleName
+ // ========== debug ==========
+ def dependencyTree: String = lib.dependencyTreeRecursion(this)
+}
+
+// TODO: all this hard codes the scala version, needs more flexibility
+class ScalaCompilerDependency(cbtHasChanged: Boolean, mavenCache: File, version: String)(implicit logger: Logger) extends BoundMavenDependency(cbtHasChanged, mavenCache, MavenDependency("org.scala-lang","scala-compiler",version, Classifier.none), Seq(mavenCentral))
+class ScalaLibraryDependency (cbtHasChanged: Boolean, mavenCache: File, version: String)(implicit logger: Logger) extends BoundMavenDependency(cbtHasChanged, mavenCache, MavenDependency("org.scala-lang","scala-library",version, Classifier.none), Seq(mavenCentral))
+class ScalaReflectDependency (cbtHasChanged: Boolean, mavenCache: File, version: String)(implicit logger: Logger) extends BoundMavenDependency(cbtHasChanged, mavenCache, MavenDependency("org.scala-lang","scala-reflect",version, Classifier.none), Seq(mavenCentral))
+
+case class ScalaDependencies(cbtHasChanged: Boolean, mavenCache: File, version: String)(implicit val logger: Logger) extends DependencyImplementation{ sd =>
+ override final val needsUpdate = false
+ def targetClasspath = ClassPath()
+ def exportedClasspath = ClassPath()
+ def dependencies = Seq(
+ new ScalaCompilerDependency(cbtHasChanged, mavenCache, version),
+ new ScalaLibraryDependency(cbtHasChanged, mavenCache, version),
+ new ScalaReflectDependency(cbtHasChanged, mavenCache, version)
+ )
+}
+
+case class BinaryDependency( path: File, dependencies: Seq[Dependency] )(implicit val logger: Logger) extends DependencyImplementation{
+ def exportedClasspath = ClassPath(Seq(path))
+ override def needsUpdate = false
+ def targetClasspath = exportedClasspath
+}
+
+/** Allows to easily assemble a bunch of dependencies */
+case class Dependencies( dependencies: Seq[Dependency] )(implicit val logger: Logger) extends DependencyImplementation{
+ override def needsUpdate = dependencies.exists(_.needsUpdate)
+ override def exportedClasspath = ClassPath()
+ override def targetClasspath = ClassPath()
+}
+
+case class Stage1Dependency(cbtHasChanged: Boolean, mavenCache: File, nailgunTarget: File, stage1Target: File, compatibilityTarget: File)(implicit val logger: Logger) extends DependencyImplementation{
+ override def needsUpdate = cbtHasChanged
+ override def targetClasspath = exportedClasspath
+ override def exportedClasspath = ClassPath( Seq(nailgunTarget, stage1Target) )
+ val compatibilityDependency = CompatibilityDependency(cbtHasChanged, compatibilityTarget)
+ override def dependencies = Seq(
+ compatibilityDependency
+ ) ++
+ MavenResolver(cbtHasChanged,mavenCache,mavenCentral).bind(
+ MavenDependency("org.scala-lang","scala-library",constants.scalaVersion),
+ MavenDependency("org.scala-lang.modules","scala-xml_"+constants.scalaMajorVersion,constants.scalaXmlVersion)
+ )
+}
+case class CompatibilityDependency(cbtHasChanged: Boolean, compatibilityTarget: File)(implicit val logger: Logger) extends DependencyImplementation{
+ override def needsUpdate = cbtHasChanged
+ override def targetClasspath = exportedClasspath
+ override def exportedClasspath = ClassPath( Seq(compatibilityTarget) )
+ override def dependencies = Seq()
+}
+case class CbtDependency(cbtHasChanged: Boolean, mavenCache: File, nailgunTarget: File, stage1Target: File, stage2Target: File, compatibilityTarget: File)(implicit val logger: Logger) extends DependencyImplementation{
+ override def needsUpdate = cbtHasChanged
+ override def targetClasspath = exportedClasspath
+ override def exportedClasspath = ClassPath( Seq( stage2Target ) )
+ val stage1Dependency = Stage1Dependency(cbtHasChanged, mavenCache, nailgunTarget, stage1Target, compatibilityTarget)
+ override def dependencies = Seq(
+ stage1Dependency
+ ) ++
+ MavenResolver(cbtHasChanged, mavenCache,mavenCentral).bind(
+ MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0"),
+ MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r")
+ )
+}
+
+case class Classifier(name: Option[String])
+object Classifier{
+ object none extends Classifier(None)
+ object javadoc extends Classifier(Some("javadoc"))
+ object sources extends Classifier(Some("sources"))
+}
+abstract class DependenciesProxy{
+
+}
+class BoundMavenDependencies(
+ cbtHasChanged: Boolean, mavenCache: File, urls: Seq[URL], mavenDependencies: Seq[MavenDependency]
+)(implicit logger: Logger) extends Dependencies(
+ mavenDependencies.map( BoundMavenDependency(cbtHasChanged,mavenCache,_,urls) )
+)
+case class MavenDependency(
+ groupId: String, artifactId: String, version: String, classifier: Classifier = Classifier.none
+){
+ private[cbt] def serialize = groupId ++ ":" ++ artifactId ++ ":"++ version ++ classifier.name.map(":" ++ _).getOrElse("")
+}
+object MavenDependency{
+ private[cbt] def deserialize = (_:String).split(":") match {
+ case col => MavenDependency( col(0), col(1), col(2), Classifier(col.lift(3)) )
+ }
+}
+// FIXME: take MavenResolver instead of mavenCache and repositories separately
+case class BoundMavenDependency(
+ cbtHasChanged: Boolean, mavenCache: File, mavenDependency: MavenDependency, repositories: Seq[URL]
+)(implicit val logger: Logger) extends ArtifactInfo with DependencyImplementation{
+ val MavenDependency( groupId, artifactId, version, classifier ) = mavenDependency
+ assert(
+ Option(groupId).collect{
+ case BoundMavenDependency.ValidIdentifier(_) =>
+ }.nonEmpty,
+ s"not a valid groupId: '$groupId'"
+ )
+ assert(
+ Option(artifactId).collect{
+ case BoundMavenDependency.ValidIdentifier(_) =>
+ }.nonEmpty,
+ s"not a valid artifactId: '$artifactId'"
+ )
+ assert(
+ version != "" && version != null && !version.startsWith(" ") && !version.endsWith(" "),
+ s"not a valid version: '$version'"
+ )
+ override def show: String = this.getClass.getSimpleName ++ "(" ++ mavenDependency.serialize ++ ")"
+
+ override def needsUpdate = false
+
+ private val groupPath = groupId.split("\\.").mkString("/")
+ protected[cbt] def basePath = s"/$groupPath/$artifactId/$version/$artifactId-$version" ++ classifier.name.map("-"++_).getOrElse("")
+
+ //private def coursierJarFile = userHome++"/.coursier/cache/v1/https/repo1.maven.org/maven2"++basePath++".jar"
+
+ def exportedJars = Seq( jar )
+ override def exportedClasspath = ClassPath( exportedJars )
+ override def targetClasspath = exportedClasspath
+ import scala.collection.JavaConversions._
+
+ private def resolve(suffix: String, hash: Option[String]): File = {
+ logger.resolver("Resolving "+this)
+ val file = mavenCache ++ basePath ++ "." ++ suffix
+ val urls = repositories.map(_ ++ basePath ++ "." ++ suffix)
+ urls.find(
+ lib.download(_, file, hash)
+ ).getOrElse(
+ throw new Exception(s"\nCannot resolve\n$this\nCan't find any of\n"++urls.mkString("\n"))
+ )
+ file
+ }
+
+ private def resolveHash(suffix: String) = {
+ Files.readAllLines(
+ resolve( suffix ++ ".sha1", None ).toPath,
+ StandardCharsets.UTF_8
+ ).mkString("\n").split(" ").head.trim
+ }
+
+ private object jarSha1Cache extends Cache[String]
+ def jarSha1: String = jarSha1Cache{ resolveHash("jar") }
+
+ private object pomSha1Cache extends Cache[String]
+ def pomSha1: String = pomSha1Cache{ resolveHash("pom") }
+
+ private object jarCache extends Cache[File]
+ def jar: File = jarCache{ resolve("jar", Some(jarSha1)) }
+
+ private object pomCache extends Cache[File]
+ def pom: File = pomCache{ resolve("pom", Some(pomSha1)) }
+
+ private def pomXml = XML.loadFile(pom.string)
+ // ========== pom traversal ==========
+
+ private lazy val transitivePom: Seq[BoundMavenDependency] = {
+ (pomXml \ "parent").collect{
+ case parent =>
+ BoundMavenDependency(
+ cbtHasChanged: Boolean,
+ mavenCache,
+ MavenDependency(
+ (parent \ "groupId").text,
+ (parent \ "artifactId").text,
+ (parent \ "version").text
+ ),
+ repositories
+ )(logger)
+ }.flatMap(_.transitivePom) :+ this
+ }
+
+ private lazy val properties: Map[String, String] = (
+ transitivePom.flatMap{ d =>
+ val props = (d.pomXml \ "properties").flatMap(_.child).map{
+ tag => tag.label -> tag.text
+ }
+ logger.pom(s"Found properties in $pom: $props")
+ props
+ }
+ ).toMap
+
+ private lazy val dependencyVersions: Map[String, (String,String)] =
+ transitivePom.flatMap(
+ p =>
+ (p.pomXml \ "dependencyManagement" \ "dependencies" \ "dependency").map{
+ xml =>
+ val groupId = p.lookup(xml,_ \ "groupId").get
+ val artifactId = p.lookup(xml,_ \ "artifactId").get
+ val version = p.lookup(xml,_ \ "version").get
+ artifactId -> (groupId, version)
+ }
+ ).toMap
+
+ def dependencies: Seq[BoundMavenDependency] = {
+ if(classifier == Classifier.sources) Seq()
+ else {
+ lib.cacheOnDisk(
+ cbtHasChanged, mavenCache ++ basePath ++ ".pom.dependencies"
+ )( MavenDependency.deserialize )( _.serialize ){
+ (pomXml \ "dependencies" \ "dependency").collect{
+ case xml if ( (xml \ "scope").text == "" || (xml \ "scope").text == "compile" ) && (xml \ "optional").text != "true" =>
+ val artifactId = lookup(xml,_ \ "artifactId").get
+ val groupId =
+ lookup(xml,_ \ "groupId").getOrElse(
+ dependencyVersions
+ .get(artifactId).map(_._1)
+ .getOrElse(
+ throw new Exception(s"$artifactId not found in \n$dependencyVersions")
+ )
+ )
+ val version =
+ lookup(xml,_ \ "version").getOrElse(
+ dependencyVersions
+ .get(artifactId).map(_._2)
+ .getOrElse(
+ throw new Exception(s"$artifactId not found in \n$dependencyVersions")
+ )
+ )
+ val classifier = Classifier( Some( (xml \ "classifier").text ).filterNot(_ == "").filterNot(_ == null) )
+ MavenDependency( groupId, artifactId, version, classifier )
+ }.toVector
+ }.map(
+ BoundMavenDependency( cbtHasChanged, mavenCache, _, repositories )
+ ).to
+ }
+ }
+ def lookup( xml: Node, accessor: Node => NodeSeq ): Option[String] = {
+ //println("lookup in "++pomUrl)
+ val Substitution = "\\$\\{([^\\}]+)\\}".r
+ accessor(xml).headOption.map{v =>
+ //println("found: "++v.text)
+ Substitution.replaceAllIn(
+ v.text,
+ matcher => {
+ val path = matcher.group(1)
+ properties.get(path).orElse(
+ transitivePom.reverse.flatMap{ d =>
+ Some(path.split("\\.").toList).collect{
+ case "project" :: path =>
+ path.foldLeft(d.pomXml:NodeSeq){ case (xml,tag) => xml \ tag }.text
+ }.filter(_ != "")
+ }.headOption
+ )
+ .getOrElse(
+ throw new Exception(s"Can't find $path in \n$properties.\n\npomParents: $transitivePom\n\n pomXml:\n$pomXml" )
+ )
+ //println("lookup "++path ++ ": "++(pomXml\path).text)
+ }
+ )
+ }
+ }
+}
+object BoundMavenDependency{
+ def ValidIdentifier = "^([A-Za-z0-9_\\-.]+)$".r // according to maven's DefaultModelValidator.java
+ def semanticVersionLessThan(left: Array[Either[Int,String]], right: Array[Either[Int,String]]) = {
+ // FIXME: this ignores ends when different size
+ val zipped = left zip right
+ val res = zipped.map {
+ case (Left(i),Left(j)) => i compare j
+ case (Right(i),Right(j)) => i compare j
+ case (Left(i),Right(j)) => i.toString compare j
+ case (Right(i),Left(j)) => i compare j.toString
+ }
+ res.find(_ != 0).map(_ < 0).getOrElse(false)
+ }
+ def toInt(str: String): Either[Int,String] = try {
+ Left(str.toInt)
+ } catch {
+ case e: NumberFormatException => Right(str)
+ }
+ /* this obviously should be overridable somehow */
+ def updateOutdated(
+ deps: Seq[Dependency with ArtifactInfo],
+ versionLessThan: (Array[Either[Int,String]], Array[Either[Int,String]]) => Boolean = semanticVersionLessThan
+ )(implicit logger: Logger): Seq[Dependency with ArtifactInfo] = {
+ val latest = deps
+ .groupBy( d => (d.groupId, d.artifactId) )
+ .mapValues(
+ _.groupBy(_.version) // remove duplicates
+ .map( _._2.head )
+ .toVector
+ .sortBy( _.version.split("\\.|\\-").map(toInt) )( Ordering.fromLessThan(versionLessThan) )
+ .last
+ )
+ deps.map{
+ d =>
+ val l = latest((d.groupId,d.artifactId))
+ if(d != l) logger.resolver("outdated: "++d.show)
+ l
+ }
+ }
+}
diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala
new file mode 100644
index 0000000..128d2f8
--- /dev/null
+++ b/stage2/BasicBuild.scala
@@ -0,0 +1,247 @@
+package cbt
+
+import java.io._
+import java.net._
+
+class BasicBuild(val context: Context) extends BaseBuild
+trait BaseBuild extends BuildInterface with DependencyImplementation with TriggerLoop with SbtDependencyDsl{
+ def context: Context
+
+ // library available to builds
+ implicit protected final val logger: Logger = context.logger
+ implicit protected final val classLoaderCache: ClassLoaderCache = context.classLoaderCache
+ implicit protected final val _context = context
+ override protected final val lib: Lib = new Lib(logger)
+
+ // ========== general stuff ==========
+
+ def enableConcurrency = false
+ final def projectDirectory: File = lib.realpath(context.projectDirectory)
+ assert( projectDirectory.exists, "projectDirectory does not exist: " ++ projectDirectory.string )
+ final def usage: String = lib.usage(this.getClass, show)
+
+ final def taskNames: String = lib.taskNames(this.getClass).sorted.mkString("\n")
+
+ // ========== meta data ==========
+
+ def defaultScalaVersion: String = constants.scalaVersion
+ final def scalaVersion = context.scalaVersion getOrElse defaultScalaVersion
+ final def scalaMajorVersion: String = lib.libMajorVersion(scalaVersion)
+ def crossScalaVersions: Seq[String] = Seq(scalaVersion, "2.10.6")
+ final def crossScalaVersionsArray: Array[String] = crossScalaVersions.to
+ def projectName = "default"
+
+ // TODO: this should probably provide a nice error message if class has constructor signature
+ def copy(context: Context): BuildInterface = lib.copy(this.getClass, context).asInstanceOf[BuildInterface]
+ def zincVersion = "0.3.9"
+
+ def dependencies: Seq[Dependency] =
+ // FIXME: this should probably be removed
+ Resolver( mavenCentral ).bind(
+ "org.scala-lang" % "scala-library" % scalaVersion
+ )
+
+ // ========== paths ==========
+ final private val defaultSourceDirectory = projectDirectory ++ "/src"
+
+ /** base directory where stuff should be generated */
+ def target: File = projectDirectory ++ "/target"
+ /** base directory where stuff should be generated for this scala version*/
+ def scalaTarget: File = target ++ s"/scala-$scalaMajorVersion"
+ /** directory where jars (and the pom file) should be put */
+ def jarTarget: File = scalaTarget
+ /** directory where the scaladoc should be put */
+ def docTarget: File = scalaTarget ++ "/api"
+ /** directory where the class files should be put (in package directories) */
+ def compileTarget: File = scalaTarget ++ "/classes"
+ /**
+ File which cbt uses to determine if it needs to trigger an incremental re-compile.
+ Last modified date is the time when the last successful compilation started.
+ Contents is the cbt version git hash.
+ */
+ def compileStatusFile: File = compileTarget ++ ".last-success"
+
+ /** 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(lib.sourceFileFilter)
+
+ /** Absolute path names for all individual files found in sources directly or contained in directories. */
+ final def sourceFiles: Seq[File] = lib.sourceFiles(sources)
+
+ protected def logEmptySourceDirectories(): Unit = {
+ val nonExisting =
+ sources
+ .filterNot( _.exists )
+ .diff( Seq(defaultSourceDirectory) )
+ if(!nonExisting.isEmpty) logger.stage2("Some sources do not exist: \n"++nonExisting.mkString("\n"))
+ }
+ logEmptySourceDirectories()
+
+ def Resolver( urls: URL* ) = MavenResolver( context.cbtHasChanged, context.paths.mavenCache, urls: _* )
+
+ def ScalaDependency(
+ groupId: String, artifactId: String, version: String, classifier: Classifier = Classifier.none,
+ scalaVersion: String = scalaMajorVersion
+ ) = lib.ScalaDependency( groupId, artifactId, version, classifier, scalaVersion )
+
+ final def DirectoryDependency(path: File) = cbt.DirectoryDependency(
+ context.copy( projectDirectory = path, args = Seq() )
+ )
+
+ def triggerLoopFiles: Seq[File] = sources ++ transitiveDependencies.collect{ case b: TriggerLoop => b.triggerLoopFiles }.flatten
+
+ def localJars : Seq[File] =
+ Seq(projectDirectory ++ "/lib")
+ .filter(_.exists)
+ .flatMap(_.listFiles)
+ .filter(_.toString.endsWith(".jar"))
+
+ override def dependencyClasspath : ClassPath = ClassPath(localJars) ++ super.dependencyClasspath
+
+ protected def compileDependencies: Seq[Dependency] = Nil
+ final def compileClasspath : ClassPath =
+ dependencyClasspath ++ ClassPath( compileDependencies.flatMap(_.exportedClasspath.files).distinct )
+
+ def resourceClasspath: ClassPath = {
+ val resourcesDirectory = projectDirectory ++ "/resources"
+ ClassPath( if(resourcesDirectory.exists) Seq(resourcesDirectory) else Nil )
+ }
+ def exportedClasspath : ClassPath = ClassPath(compile.toSeq) ++ resourceClasspath
+ def targetClasspath = ClassPath(Seq(compileTarget))
+ // ========== compile, run, test ==========
+
+ /** scalac options used for zinc and scaladoc */
+ def scalacOptions: Seq[String] = Seq(
+ "-feature",
+ "-deprecation",
+ "-unchecked"
+ )
+
+ private object needsUpdateCache extends Cache[Boolean]
+ def needsUpdate: Boolean = needsUpdateCache(
+ context.cbtHasChanged
+ || lib.needsUpdate( sourceFiles, compileStatusFile )
+ || transitiveDependencies.filterNot(_ == context.parentBuild).exists(_.needsUpdate)
+ )
+
+ private object compileCache extends Cache[Option[File]]
+ def compile: Option[File] = compileCache{
+ lib.compile(
+ context.cbtHasChanged,
+ needsUpdate || context.parentBuild.map(_.needsUpdate).getOrElse(false),
+ sourceFiles, compileTarget, compileStatusFile, compileClasspath,
+ context.paths.mavenCache, scalacOptions, context.classLoaderCache,
+ zincVersion = zincVersion, scalaVersion = scalaVersion
+ )
+ }
+
+
+ def mainClasses: Seq[Class[_]] = compile.toSeq.flatMap( lib.mainClasses( _, classLoader(classLoaderCache) ) )
+
+ def runClass: Option[String] = lib.runClass( mainClasses ).map( _.getName )
+
+ def run: ExitCode = runClass.map( lib.runMain( _, context.args, classLoader(context.classLoaderCache) ) ).getOrElse{
+ logger.task( "No main class found for " ++ projectDirectory.string )
+ ExitCode.Success
+ }
+
+ def clean: ExitCode = {
+ lib.clean(
+ target,
+ context.args.contains("force"),
+ context.args.contains("dry-run"),
+ context.args.contains("list"),
+ context.args.contains("help")
+ )
+ }
+
+ def repl: ExitCode = {
+ lib.consoleOrFail("Use `cbt direct repl` instead")
+
+ val colorized = "scala.color"
+ if(Option(System.getProperty(colorized)).isEmpty) {
+ // set colorized REPL, if user didn't pass own value
+ System.setProperty(colorized, "true")
+ }
+
+ val scalac = new ScalaCompilerDependency(context.cbtHasChanged, context.paths.mavenCache, scalaVersion)
+ lib.runMain(
+ "scala.tools.nsc.MainGenericRunner",
+ Seq(
+ "-bootclasspath",
+ scalac.classpath.string,
+ "-classpath",
+ classpath.string
+ ) ++ context.args,
+ scalac.classLoader(classLoaderCache)
+ )
+ }
+
+ def test: Option[ExitCode] =
+ Some(new lib.ReflectBuild(
+ DirectoryDependency(projectDirectory++"/test").build
+ ).callNullary(Some("run")))
+ def t = test
+ def rt = recursiveUnsafe(Some("test"))
+
+ def recursiveSafe(_run: BuildInterface => Any): ExitCode = {
+ val builds = (this +: transitiveDependencies).collect{
+ case b: BuildInterface => b
+ }
+ val results = builds.map(_run)
+ if(
+ results.forall{
+ case Some(_:ExitCode) => true
+ case None => true
+ case _:ExitCode => true
+ case other => false
+ }
+ ){
+ if(
+ results.collect{
+ case Some(c:ExitCode) => c
+ case c:ExitCode => c
+ }.filter(_ != 0)
+ .nonEmpty
+ ) ExitCode.Failure
+ else ExitCode.Success
+ } else ExitCode.Success
+ }
+
+ def recursive: ExitCode = {
+ recursiveUnsafe(context.args.lift(1))
+ }
+
+ def recursiveUnsafe(taskName: Option[String]): ExitCode = {
+ recursiveSafe{
+ b =>
+ System.err.println(b.show)
+ lib.trapExitCode{ // FIXME: trapExitCode does not seem to work here
+ try{
+ new lib.ReflectBuild(b).callNullary(taskName)
+ ExitCode.Success
+ } catch {
+ case e: Throwable => println(e.getClass); throw e
+ }
+ }
+ ExitCode.Success
+ }
+ }
+
+ def c = compile
+ def r = run
+
+ /*
+ context.logger.composition(">"*80)
+ context.logger.composition("class " ++ this.getClass.toString)
+ context.logger.composition("dir " ++ projectDirectory.string)
+ context.logger.composition("sources " ++ sources.toList.mkString(" "))
+ context.logger.composition("target " ++ target.string)
+ context.logger.composition("context " ++ context.toString)
+ context.logger.composition("dependencyTree\n" ++ dependencyTree)
+ context.logger.composition("<"*80)
+ */
+
+ // ========== cbt internals ==========
+ def finalBuild: BuildInterface = this
+ override def show = this.getClass.getSimpleName ++ "(" ++ projectDirectory.string ++ ")"
+}
diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala
new file mode 100644
index 0000000..b183745
--- /dev/null
+++ b/stage2/BuildBuild.scala
@@ -0,0 +1,81 @@
+package cbt
+import java.nio.file._
+
+trait BuildBuild extends BaseBuild{
+ private final val managedContext = context.copy(
+ projectDirectory = managedBuildDirectory,
+ parentBuild=Some(this)
+ )
+
+ object plugins{
+ final lazy val scalaTest = DirectoryDependency( managedContext.cbtHome ++ "/plugins/scalatest" )
+ final lazy val sbtLayout = DirectoryDependency( managedContext.cbtHome ++ "/plugins/sbt_layout" )
+ final lazy val scalaJs = DirectoryDependency( managedContext.cbtHome ++ "/plugins/scalajs" )
+ final lazy val scalariform = DirectoryDependency( managedContext.cbtHome ++ "/plugins/scalariform" )
+ final lazy val scalafmt = DirectoryDependency( managedContext.cbtHome ++ "/plugins/scalafmt" )
+ final lazy val wartremover = DirectoryDependency( managedContext.cbtHome ++ "/plugins/wartremover" )
+ final lazy val uberJar = DirectoryDependency( managedContext.cbtHome ++ "/plugins/uber-jar" )
+ final lazy val sonatypeRelease = DirectoryDependency( managedContext.cbtHome ++ "/plugins/sonatype-release" )
+ }
+
+ override def dependencies =
+ super.dependencies :+ context.cbtDependency
+ def managedBuildDirectory: java.io.File = lib.realpath( projectDirectory.parent )
+ private object managedBuildCache extends Cache[BuildInterface]
+ def managedBuild = managedBuildCache{
+ val managedBuildFile = projectDirectory++"/build.scala"
+ logger.composition("Loading build at "++managedContext.projectDirectory.toString)
+ val build = (
+ if(managedBuildFile.exists){
+ val contents = new String(Files.readAllBytes(managedBuildFile.toPath))
+ val cbtUrl = ("cbt:"++GitDependency.GitUrl.regex++"#[a-z0-9A-Z]+").r
+ cbtUrl
+ .findFirstIn(contents)
+ .flatMap{
+ url =>
+ val Array(base,hash) = url.drop(4).split("#")
+ if(context.cbtHome.string.contains(hash))
+ None
+ else Some{
+ // Note: cbt can't use an old version of itself for building,
+ // otherwise we'd have to recursively build all versions since
+ // the beginning. Instead CBT always needs to build the pure Java
+ // Launcher in the checkout with itself and then run it via reflection.
+ val dep = new GitDependency(base, hash, Some("nailgun_launcher"))
+ val ctx = managedContext.copy( cbtHome = dep.checkout )
+ dep.classLoader(classLoaderCache)
+ .loadClass( "cbt.NailgunLauncher" )
+ .getMethod( "getBuild", classOf[AnyRef] )
+ .invoke( null, ctx )
+ }
+ }.getOrElse{
+ try{
+ classLoader(context.classLoaderCache)
+ .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: "+context.projectDirectory)
+ }
+ }
+ } else if( projectDirectory.listFiles.exists( _.getName.endsWith(".scala") ) ){
+ throw new Exception(
+ "No file build.scala (lower case) found in " ++ projectDirectory.getPath
+ )
+ } else if( projectDirectory.getParentFile.getName == "build" ){
+ new BasicBuild( managedContext ) with BuildBuild
+ } else {
+ new BasicBuild( managedContext )
+ }
+ )
+ try{
+ build.asInstanceOf[BuildInterface]
+ } catch {
+ case e: ClassCastException if e.getMessage.contains("Build cannot be cast to cbt.BuildInterface") =>
+ throw new Exception("Your Build class needs to extend BaseBuild in: "+context.projectDirectory, e)
+ }
+ }
+ override def triggerLoopFiles = super.triggerLoopFiles ++ managedBuild.triggerLoopFiles
+ override def finalBuild: BuildInterface = if( context.projectDirectory == context.cwd ) this else managedBuild.finalBuild
+}
diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala
new file mode 100644
index 0000000..a834435
--- /dev/null
+++ b/stage2/BuildDependency.scala
@@ -0,0 +1,36 @@
+package cbt
+import java.io.File
+/*
+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 DependencyImplementation{
+ final def triggerLoopFilesArray = triggerLoopFiles.toArray
+ def triggerLoopFiles: Seq[File]
+}
+/** You likely want to use the factory method in the BasicBuild class instead of this. */
+final case class DirectoryDependency(context: Context) extends TriggerLoop{
+ override def show = this.getClass.getSimpleName ++ "(" ++ context.projectDirectory.string ++ ")"
+ lazy val logger = context.logger
+ override lazy val lib: Lib = new Lib(logger)
+ private lazy val root = lib.loadRoot( context.copy(args=Seq()) )
+ lazy val build = root.finalBuild
+ def exportedClasspath = ClassPath()
+ def dependencies = Seq(build)
+ def triggerLoopFiles = root.triggerLoopFiles
+ def needsUpdate = build.needsUpdate
+ def targetClasspath = ClassPath()
+}
+/*
+case class DependencyOr(first: DirectoryDependency, second: JavaDependency) extends ProjectProxy with DirectoryDependencyBase{
+ val isFirst = new File(first.context.projectDirectory).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/GitDependency.scala b/stage2/GitDependency.scala
new file mode 100644
index 0000000..650fd09
--- /dev/null
+++ b/stage2/GitDependency.scala
@@ -0,0 +1,82 @@
+package cbt
+import java.io._
+import java.nio.file.Files.readAllBytes
+import java.net._
+import org.eclipse.jgit.api._
+import org.eclipse.jgit.internal.storage.file._
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
+import org.eclipse.jgit.lib.Ref
+
+object GitDependency{
+ val GitUrl = "(git:|https:|file:/)//([^/]+)/(.+)".r
+}
+case class GitDependency(
+ url: String, ref: String, subDirectory: Option[String] = None // example: git://github.com/cvogt/cbt.git#<some-hash>
+)(implicit val logger: Logger, classLoaderCache: ClassLoaderCache, context: Context ) extends DependencyImplementation{
+ import GitDependency._
+ override def lib = new Lib(logger)
+
+ // TODO: add support for authentication via ssh and/or https
+ // See http://www.codeaffine.com/2014/12/09/jgit-authentication/
+ private val GitUrl( _, domain, path ) = url
+
+ private val credentialsFile = context.projectDirectory ++ "/git.login"
+
+ private object checkoutCache extends Cache[File]
+
+ private def authenticate(_git: CloneCommand) =
+ if(!credentialsFile.exists){
+ _git
+ } else {
+ val (user, password) = {
+ // TODO: implement safer method than reading credentials from plain text file
+ val c = new String(readAllBytes(credentialsFile.toPath)).split("\n").head.trim.split(":")
+ (c(0), c.drop(1).mkString(":"))
+ }
+ _git.setCredentialsProvider( new UsernamePasswordCredentialsProvider(user, password) )
+ }
+
+ def checkout: File = checkoutCache{
+ val checkoutDirectory = context.cache ++ s"/git/$domain/$path/$ref"
+ val _git = if(checkoutDirectory.exists){
+ logger.git(s"Found existing checkout of $url#$ref in $checkoutDirectory")
+ val _git = new Git(new FileRepository(checkoutDirectory ++ "/.git"))
+ val actualRef = _git.getRepository.getBranch
+ if(actualRef != ref){
+ logger.git(s"actual ref '$actualRef' does not match expected ref '$ref' - fetching and checking out")
+ _git.fetch().call()
+ _git.checkout().setName(ref).call
+ }
+ _git
+ } else {
+ logger.git(s"Cloning $url into $checkoutDirectory")
+ val _git = authenticate(
+ Git
+ .cloneRepository()
+ .setURI(url)
+ .setDirectory(checkoutDirectory)
+ ).call()
+
+ logger.git(s"Checking out ref $ref")
+ _git.checkout().setName(ref).call()
+ _git
+ }
+ val actualRef = _git.getRepository.getBranch
+ assert( actualRef == ref, s"actual ref '$actualRef' does not match expected ref '$ref'")
+ checkoutDirectory
+ }
+ private object dependencyCache extends Cache[DependencyImplementation]
+ def dependency = dependencyCache{
+ DirectoryDependency(
+ context.copy(
+ projectDirectory = checkout ++ subDirectory.map("/" ++ _).getOrElse("")
+ )
+ )
+ }
+
+ def dependencies = Seq(dependency)
+
+ def exportedClasspath = ClassPath()
+ private[cbt] def targetClasspath = exportedClasspath
+ def needsUpdate: Boolean = false
+}
diff --git a/stage2/Lib.scala b/stage2/Lib.scala
new file mode 100644
index 0000000..25183a3
--- /dev/null
+++ b/stage2/Lib.scala
@@ -0,0 +1,521 @@
+package cbt
+
+import java.io._
+import java.net._
+import java.lang.reflect.InvocationTargetException
+import java.nio.file.{Path =>_,_}
+import java.nio.file.Files.{readAllBytes, deleteIfExists, delete}
+import java.security.MessageDigest
+import java.util.jar._
+import java.lang.reflect.Method
+
+import scala.util._
+
+// pom model
+case class Developer(id: String, name: String, timezone: 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"
+
+ def copy(cls: Class[_], context: Context) =
+ cls
+ .getConstructor(classOf[Context])
+ .newInstance(context)
+
+ /** Loads Build for given Context */
+ def loadDynamic(context: Context, default: Context => BuildInterface = new BasicBuild(_)): BuildInterface = {
+ 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 => BuildInterface = new BasicBuild(_)): BuildInterface = {
+ context.logger.composition( context.logger.showInvocation("Build.loadRoot",context.projectDirectory) )
+ def findStartDir(projectDirectory: File): File = {
+ val buildDir = realpath( projectDirectory ++ "/build" )
+ if(buildDir.exists) findStartDir(buildDir) else projectDirectory
+ }
+
+ val start = findStartDir(context.projectDirectory)
+
+ val useBasicBuildBuild = context.projectDirectory == start
+
+ val rootBuildClassName = if( useBasicBuildBuild ) buildBuildClassName else buildClassName
+ try{
+ if(useBasicBuildBuild) default( context ) else new cbt.BasicBuild( context.copy( projectDirectory = start ) ) with BuildBuild
+ } catch {
+ case e:ClassNotFoundException if e.getMessage == rootBuildClassName =>
+ throw new Exception(s"no class $rootBuildClassName found in " ++ start.string)
+ }
+ }
+
+ def srcJar(sourceFiles: Seq[File], artifactId: String, scalaMajorVersion: String, version: String, jarTarget: File): Option[File] = {
+ lib.jarFile(
+ jarTarget ++ ("/"++artifactId++"_"++scalaMajorVersion++"-"++version++"-sources.jar"),
+ sourceFiles
+ )
+ }
+
+ def jar(artifactId: String, scalaMajorVersion: String, version: String, compileTarget: File, jarTarget: File): Option[File] = {
+ lib.jarFile(
+ jarTarget ++ ("/"++artifactId++"_"++scalaMajorVersion++"-"++version++".jar"),
+ Seq(compileTarget)
+ )
+ }
+
+ def docJar(
+ cbtHasChanged: Boolean,
+ scalaVersion: String,
+ sourceFiles: Seq[File],
+ dependencyClasspath: ClassPath,
+ docTarget: File,
+ jarTarget: File,
+ artifactId: String,
+ scalaMajorVersion: String,
+ version: String,
+ compileArgs: Seq[String],
+ classLoaderCache: ClassLoaderCache,
+ mavenCache: File
+ ): Option[File] = {
+ if(sourceFiles.isEmpty){
+ None
+ } else {
+ docTarget.mkdirs
+ val args = Seq(
+ // FIXME: can we use compiler dependency here?
+ "-cp", dependencyClasspath.string, // FIXME: does this break for builds that don't have scalac dependencies?
+ "-d", docTarget.toString
+ ) ++ compileArgs ++ sourceFiles.map(_.toString)
+ logger.lib("creating docs for source files "+args.mkString(", "))
+ redirectOutToErr{
+ runMain(
+ "scala.tools.nsc.ScalaDoc",
+ args,
+ ScalaDependencies(cbtHasChanged,mavenCache,scalaVersion)(logger).classLoader(classLoaderCache)
+ )
+ }
+ lib.jarFile(
+ jarTarget ++ ("/"++artifactId++"_"++scalaMajorVersion++"-"++version++"-javadoc.jar"),
+ Vector(docTarget)
+ )
+ }
+ }
+
+ // task reflection helpers
+ def tasks(cls:Class[_]): Map[String, Method] =
+ Stream
+ .iterate(cls.asInstanceOf[Class[Any]])(_.getSuperclass)
+ .takeWhile(_ != null)
+ .toVector
+ .dropRight(1) // drop Object
+ .reverse
+ .flatMap(
+ c =>
+ c
+ .getDeclaredMethods
+ .filterNot( _.getName contains "$" )
+ .filter{ m =>
+ java.lang.reflect.Modifier.isPublic(m.getModifiers)
+ }
+ .filter( _.getParameterTypes.length == 0 )
+ .map(m => NameTransformer.decode(m.getName) -> m)
+ ).toMap
+
+ def taskNames(cls: Class[_]): Seq[String] = tasks(cls).keys.toVector.sorted
+
+ def usage(buildClass: Class[_], show: String): String = {
+ val baseTasks = Seq(
+ classOf[BasicBuild],
+ classOf[PackageJars],
+ classOf[Publish]
+ ).flatMap(lib.taskNames).distinct.sorted
+ val thisTasks = lib.taskNames(buildClass) diff baseTasks
+ (
+ (
+ if( thisTasks.nonEmpty ){
+ s"""Methods provided by Build ${show}
+
+ ${thisTasks.mkString(" ")}
+
+"""
+ } else ""
+ ) ++ s"""Methods provided by CBT (but possibly overwritten)
+
+ ${baseTasks.mkString(" ")}"""
+ ) ++ "\n"
+ }
+
+ class ReflectBuild[T:scala.reflect.ClassTag](build: BuildInterface) extends ReflectObject(build){
+ def usage = lib.usage(build.getClass, build.show)
+ }
+ abstract class ReflectObject[T](obj: T){
+ def usage: String
+ def callNullary( taskName: Option[String] ): ExitCode = {
+ logger.lib("Calling task " ++ taskName.toString)
+ val ts = tasks(obj.getClass)
+ taskName.map( NameTransformer.encode ).flatMap(ts.get).map{ method =>
+ val result: Option[Any] = Option(method.invoke(obj)) // null in case of Unit
+ result.flatMap{
+ case v: Option[_] => v
+ case other => Some(other)
+ }.map{
+ value =>
+ // Try to render console representation. Probably not the best way to do this.
+ scala.util.Try( value.getClass.getDeclaredMethod("toConsole") ) match {
+ case scala.util.Success(toConsole) =>
+ println(toConsole.invoke(value))
+ ExitCode.Success
+
+ case scala.util.Failure(e) if Option(e.getMessage).getOrElse("") contains "toConsole" =>
+ value match {
+ case code if code.getClass.getSimpleName == "ExitCode" =>
+ // FIXME: ExitCode needs to be part of the compatibility interfaces
+ ExitCode(Stage0Lib.get(code,"integer").asInstanceOf[Int])
+ case other =>
+ println( other.toString ) // no method .toConsole, using to String
+ ExitCode.Success
+ }
+
+ case scala.util.Failure(e) =>
+ throw e
+ }
+ }.getOrElse(ExitCode.Success)
+ }.getOrElse{
+ taskName.foreach{ n =>
+ System.err.println(s"Method not found: $n")
+ System.err.println("")
+ }
+ System.err.println(usage)
+ taskName.map{ _ =>
+ ExitCode.Failure
+ }.getOrElse( ExitCode.Success )
+ }
+ }
+ }
+
+ def consoleOrFail(msg: String) = {
+ Option(System.console).getOrElse(
+ throw new Exception(msg + ". System.console() == null. See https://github.com/cvogt/cbt/issues/236")
+ )
+ }
+
+ def clean(target: File, force: Boolean, dryRun: Boolean, list: Boolean, help: Boolean): ExitCode = {
+ def depthFirstFileStream(file: File): Vector[File] = {
+ (
+ if (file.isDirectory) {
+ file.listFiles.toVector.flatMap(depthFirstFileStream(_))
+ } else Vector()
+ ) :+ file
+ }
+ lazy val files = depthFirstFileStream( target )
+
+ if( help ){
+ System.err.println( s"""
+ list lists files to be delete
+ force does not ask for confirmation
+ dry-run does not actually delete files
+""" )
+ ExitCode.Success
+ } else if (!target.exists){
+ System.err.println( "Nothing to clean. Does not exist: " ++ target.string )
+ ExitCode.Success
+ } else if( list ){
+ files.map(_.string).foreach( println )
+ ExitCode.Success
+ } else {
+ val performDelete = (
+ force || {
+ val console = consoleOrFail("Use `cbt direct clean` or `cbt clean help`")
+ System.err.println("Files to be deleted:\n\n")
+ files.foreach( System.err.println )
+ System.err.println("")
+ System.err.print("To delete the above files type 'delete': ")
+ console.readLine() == "delete"
+ }
+ )
+
+ if( !performDelete ) {
+ System.err.println( "Ok, not cleaning." )
+ ExitCode.Failure
+ } else {
+ // use same Vector[File] that was displayed earlier as a safety measure
+ files.foreach{ file =>
+ System.err.println( red("Deleting") ++ " " ++ file.string )
+ if(!dryRun){
+ delete( file.toPath )
+ }
+ }
+ System.err.println( "Done." )
+ ExitCode.Success
+ }
+ }
+ }
+
+ // file system helpers
+ def basename(path: File): String = path.toString.stripSuffix("/").split("/").last
+ def dirname(path: File): File = new File(realpath(path).string.stripSuffix("/").split("/").dropRight(1).mkString("/"))
+ def nameAndContents(file: File) = basename(file) -> readAllBytes(file.toPath)
+
+ /** 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)
+ } yield file
+ }
+
+ // FIXME: for some reason it includes full path in docs
+ def jarFile( jarFile: File, files: Seq[File], mainClass: Option[String] = None ): Option[File] = {
+ Files.deleteIfExists(jarFile.toPath)
+ if( files.isEmpty ){
+ None
+ } else {
+ jarFile.getParentFile.mkdirs
+ logger.lib("Start packaging "++jarFile.string)
+ val manifest = new Manifest()
+ manifest.getMainAttributes.put( Attributes.Name.MANIFEST_VERSION, "1.0" )
+ manifest.getMainAttributes.putValue( "Created-By", "Chris' Build Tool" )
+ mainClass foreach { className =>
+ manifest.getMainAttributes.put(Attributes.Name.MAIN_CLASS, className)
+ }
+ val jar = new JarOutputStream(new FileOutputStream(jarFile), manifest)
+ try{
+ 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 ++ File.separator)
+ } else file.toString
+ val entry = new JarEntry( name )
+ entry.setTime(file.lastModified)
+ jar.putNextEntry(entry)
+ jar.write( readAllBytes( file.toPath ) )
+ jar.closeEntry()
+ name
+ }
+
+ val duplicateFiles = (names diff names.distinct).distinct
+ assert(
+ duplicateFiles.isEmpty,
+ s"Conflicting file names when trying to create $jarFile: "++duplicateFiles.mkString(", ")
+ )
+ } finally {
+ jar.close()
+ }
+
+ logger.lib("Done packaging " ++ jarFile.toString)
+
+ Some(jarFile)
+ }
+ }
+
+ lazy val passphrase =
+ consoleOrFail( "Use `cbt direct <task>`" ).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.toString)
+
+ 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,
+ scalaMajorVersion: 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,
+ inceptionYear: Int,
+ organization: Option[Organization],
+ dependencies: Seq[Dependency],
+ 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 ++ "_" ++ scalaMajorVersion}</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>
+ {license.url.map(url => <url>url</url>).getOrElse( scala.xml.NodeSeq.Empty )}
+ <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>
+ <inceptionYear>{inceptionYear}</inceptionYear>
+ {organization.map{ org =>
+ <organization>
+ <name>{org.name}</name>
+ {org.url.map( url => <url>url</url> ).getOrElse( scala.xml.NodeSeq.Empty )}
+ </organization>
+ }.getOrElse(scala.xml.NodeSeq.Empty)}
+ <dependencies>
+ {dependencies.map{
+ case d:ArtifactInfo =>
+ <dependency>
+ <groupId>{d.groupId}</groupId>
+ <artifactId>{d.artifactId}</artifactId>
+ <version>{d.version}</version>
+ </dependency>
+ }}
+ </dependencies>
+</project>
+ // FIXME: do not build this file name including scalaMajorVersion in multiple places
+ val path = jarTarget.toString ++ ( "/" ++ artifactId++ "_" ++ scalaMajorVersion ++ "-" ++ version ++ ".pom" )
+ val file = new File(path)
+ write(file, "<?xml version='1.0' encoding='UTF-8'?>\n" ++ xml.toString)
+ }
+
+ 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 publishUnsigned( sourceFiles: Seq[File], artifacts: Seq[File], url: URL, credentials: Option[String] = None ): Unit = {
+ if(sourceFiles.nonEmpty){
+ publish( artifacts, url, credentials )
+ }
+ }
+
+ def publishLocal( sourceFiles: Seq[File], artifacts: Seq[File], mavenCache: File, releaseFolder: String ): Unit = {
+ if(sourceFiles.nonEmpty){
+ val targetDir = mavenCache ++ releaseFolder.stripSuffix("/")
+ targetDir.mkdirs
+ artifacts.foreach{ a =>
+ val target = targetDir ++ ("/" ++ a.getName)
+ System.err.println(blue("publishing ") ++ target.getPath)
+ Files.copy( a.toPath, target.toPath, StandardCopyOption.REPLACE_EXISTING )
+ }
+ }
+ }
+
+ def publishSigned( artifacts: Seq[File], url: URL, credentials: Option[String] = None ): Unit = {
+ // TODO: make concurrency configurable here
+ publish( artifacts ++ artifacts.map(sign), url, credentials )
+ }
+
+ private def publish(artifacts: Seq[File], url: URL, credentials: Option[String]): Unit = {
+ val files = artifacts.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, credentials)
+ }
+
+ def uploadAll(url: URL, nameAndContents: Seq[(String, Array[Byte])], credentials: Option[String] = None ): Unit =
+ nameAndContents.foreach { case (name, content) => upload(name, content, url, credentials ) }
+
+ def upload(fileName: String, fileContents: Array[Byte], baseUrl: URL, credentials: Option[String] = None): Unit = {
+ import java.net._
+ import java.io._
+ val url = baseUrl ++ "/" ++ fileName
+ System.err.println(blue("uploading ") ++ url.toString)
+ val httpCon = Stage0Lib.openConnectionConsideringProxy(url)
+ httpCon.setDoOutput(true)
+ httpCon.setRequestMethod("PUT")
+ credentials.foreach(
+ c => {
+ val encoding = new sun.misc.BASE64Encoder().encode(c.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
+
+ val realFiles = files.map(realpath)
+
+ realFiles.map{
+ // WatchService can only watch folders
+ case file if file.isFile => dirname(file)
+ case file => 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...")
+ logger.loop("Waiting for file changes...2")
+ Option(watcher.take).map{
+ key =>
+ val changedFiles = key
+ .pollEvents
+ .toVector
+ .filterNot(_.kind == StandardWatchEventKind.OVERFLOW)
+ .map(_.context.toString)
+ // make sure we don't react on other files changed
+ // in the same folder like the files we care about
+ .filter{ name => realFiles.exists(name startsWith _.toString) }
+ .map(new File(_))
+
+ changedFiles.foreach( f => logger.loop( "Changed: " ++ f.toString ) )
+ changedFiles.collect(action)
+ key.reset
+ }
+ }
+ }
+ }
+}
diff --git a/stage2/License.scala b/stage2/License.scala
new file mode 100644
index 0000000..a35a922
--- /dev/null
+++ b/stage2/License.scala
@@ -0,0 +1,57 @@
+package cbt
+import java.net.URL
+case class License(name: String, shortName: String, url: Option[String])
+object License{
+ val PublicDomain = License("Public Domain", "Public Domain", None)
+ val Scala = License ("Scala License", "Scala License", Some("http://www.scala-lang.org/license.html"))
+ val TypesafeSubscriptionAgreement = License(
+ "Typesafe Subscription Agreement", "Typesafe Subscription Agreement",
+ Some("http://downloads.typesafe.com/website/legal/TypesafeSubscriptionAgreement.pdf")
+ )
+
+ private def spdx(id: String, name: String) = License(name, id, Some(s"https://spdx.org/licenses/$id.html"))
+ val Academic = spdx("AFL-3.0", "Academic Free License")
+ val Affero = spdx("AGPL-3.0", "GNU Affero General Public License v3.0")
+ val Apache2 = spdx("Apache-2.0", "Apache License 2.0")
+ val Apple2_0 = spdx("APSL-2.0", "Apple Public Source License 2.0")
+ val Beerware = spdx("Beerware", "Beerware License")
+ val Bsd2Clause = spdx("BSD-2-Clause", """BSD 2-clause "Simplified" License""")
+ val Bsd3Clause = spdx("BSD-3-Clause", """BSD 3-clause "New" or "Revised" License""")
+ val BsdOriginal = spdx("BSD-4-Clause", """BSD 4-clause "Original" or "Old" License""")
+ val CreativeCommonsZeroUniversal = spdx("CC0-1.0", "Creative Commons Zero v1.0 Universal")
+ val CreativeCommonsAttributionNonCommercialShareAlike_2_0 = spdx("CC-BY-NC-SA-2.0", "Creative Commons Attribution Non Commercial Share Alike 2.0")
+ val CreativeCommonsAttributionNonCommercialShareAlike_2_5 = spdx("CC-BY-NC-SA-2.5", "Creative Commons Attribution Non Commercial Share Alike 2.5")
+ val CreativeCommonsAttributionNonCommercialShareAlike_3_0 = spdx("CC-BY-NC-SA-3.0", "Creative Commons Attribution Non Commercial Share Alike 3.0")
+ val CreativeCommonsAttributionNonCommercialShareAlike_4_0 = spdx("CC-BY-NC-SA-4.0", "Creative Commons Attribution Non Commercial Share Alike 4.0")
+ val CreativeCommonsAttributionShareAlike_2_5 = spdx("CC-BY-SA-2.5", "Creative Commons Attribution Share Alike 2.5")
+ val CreativeCommonsAttribution_3_0 = spdx("CC-BY-3.0", "Creative Commons Attribution 3.0")
+ val CreativeCommonsAttributionShareAlike_3_0 = spdx("CC-BY-SA-3.0", "Creative Commons Attribution Share Alike 3.0")
+ val CreativeCommonsAttribution_4_0 = spdx("CC-BY-4.0", "Creative Commons Attribution 4.0")
+ val CreativeCommonsAttributionShareAlike_4_0 = spdx("CC-BY-SA-4.0", "Creative Commons Attribution Share Alike 4.0")
+ val Eclipse = spdx("EPL-1.0", "Eclipse Public License 1.0")
+ val GPL1 = spdx("GPL-1.0", "GNU General Public License v1.0 only")
+ val GPL1Plus = spdx("GPL-1.0+", "GNU General Public License v1.0 or later")
+ val GPL2 = spdx("GPL-2.0", "GNU General Public License v2.0 only")
+ val GPL2Plus = spdx("GPL-2.0+", "GNU General Public License v2.0 or later")
+ val GPl3 = spdx("GPL-3.0", "GNU General Public License v3.0 only")
+ val GPL3Plus = spdx("GPL-3.0+", "GNU General Public License v3.0 or later")
+ val ISC = spdx("ISC", "ISC License")
+ val LGPL2 = spdx("LGPL-2.0", "GNU Library General Public License v2 only")
+ // @deprecated("-", "-")
+ val LGPL2_Plus = spdx("LGPL-2.0+", "GNU Library General Public License v2 or later")
+ val LGPL2_1 = spdx("LGPL-2.1", "GNU Library General Public License v2.1 only")
+ // @deprecated("-", "-")
+ val LGPL2_1_Plus = spdx("LGPL-2.1+", "GNU Library General Public License v2.1 or later")
+ val LGPL3 = spdx("LGPL-3.0", "GNU Lesser General Public License v3.0 only")
+ // @deprecated("use LGPL3", "2.0rc2")
+ val LGPL3_Plus = spdx("LGPL-3.0+", "GNU Lesser General Public License v3.0 or later")
+ /** Spdx.org does not (yet) differentiate between the X11 and Expat versions
+ for details see http://en.wikipedia.org/wiki/MIT_License#Various_versions */
+ val MIT = spdx("MIT", "MIT License")
+ val MPL_1_0 = spdx("MPL-1.0", "Mozilla Public License 1.0")
+ val MPL_1_1 = spdx("MPL-1.1", "Mozilla Public License 1.1")
+ val MPL2 = spdx("MPL-2.0", "Mozilla Public License 2.0")
+ val Unlicense = spdx("Unlicense", "The Unlicense")
+ val W3C = spdx("W3C", "W3C Software Notice and License")
+ val WTFPL = spdx("WTFPL", "Do What The F*ck You Want To Public License")
+}
diff --git a/stage2/NameTransformer.scala b/stage2/NameTransformer.scala
new file mode 100644
index 0000000..33489ca
--- /dev/null
+++ b/stage2/NameTransformer.scala
@@ -0,0 +1,161 @@
+// Adapted from https://github.com/scala/scala/blob/5cb3d4ec14488ce2fc5a1cc8ebdd12845859c57d/src/library/scala/reflect/NameTransformer.scala
+/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+package cbt
+
+/** Provides functions to encode and decode Scala symbolic names.
+ * Also provides some constants.
+ */
+object NameTransformer {
+ // XXX Short term: providing a way to alter these without having to recompile
+ // the compiler before recompiling the compiler.
+ val MODULE_SUFFIX_STRING = sys.props.getOrElse("SCALA_MODULE_SUFFIX_STRING", "$")
+ val NAME_JOIN_STRING = sys.props.getOrElse("SCALA_NAME_JOIN_STRING", "$")
+ val MODULE_INSTANCE_NAME = "MODULE$"
+ val LOCAL_SUFFIX_STRING = " "
+ val SETTER_SUFFIX_STRING = "_$eq"
+ val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$"
+
+ private val nops = 128
+ private val ncodes = 26 * 26
+
+ private class OpCodes(val op: Char, val code: String, val next: OpCodes)
+
+ private val op2code = new Array[String](nops)
+ private val code2op = new Array[OpCodes](ncodes)
+ private def enterOp(op: Char, code: String) = {
+ op2code(op.toInt) = code
+ val c = (code.charAt(1) - 'a') * 26 + code.charAt(2) - 'a'
+ code2op(c.toInt) = new OpCodes(op, code, code2op(c))
+ }
+
+ /* Note: decoding assumes opcodes are only ever lowercase. */
+ enterOp('~', "$tilde")
+ enterOp('=', "$eq")
+ enterOp('<', "$less")
+ enterOp('>', "$greater")
+ enterOp('!', "$bang")
+ enterOp('#', "$hash")
+ enterOp('%', "$percent")
+ enterOp('^', "$up")
+ enterOp('&', "$amp")
+ enterOp('|', "$bar")
+ enterOp('*', "$times")
+ enterOp('/', "$div")
+ enterOp('+', "$plus")
+ enterOp('-', "$minus")
+ enterOp(':', "$colon")
+ enterOp('\\', "$bslash")
+ enterOp('?', "$qmark")
+ enterOp('@', "$at")
+
+ /** Replace operator symbols by corresponding `\$opname`.
+ *
+ * @param name the string to encode
+ * @return the string with all recognized opchars replaced with their encoding
+ */
+ def encode(name: String): String = {
+ var buf: StringBuilder = null
+ val len = name.length()
+ var i = 0
+ while (i < len) {
+ val c = name charAt i
+ if (c < nops && (op2code(c.toInt) ne null)) {
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append(op2code(c.toInt))
+ /* Handle glyphs that are not valid Java/JVM identifiers */
+ }
+ else if (!Character.isJavaIdentifierPart(c)) {
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append("$u%04X".format(c.toInt))
+ }
+ else if (buf ne null) {
+ buf.append(c)
+ }
+ i += 1
+ }
+ if (buf eq null) name else buf.toString()
+ }
+
+ /** Replace `\$opname` by corresponding operator symbol.
+ *
+ * @param name0 the string to decode
+ * @return the string with all recognized operator symbol encodings replaced with their name
+ */
+ def decode(name0: String): String = {
+ //System.out.println("decode: " + name);//DEBUG
+ val name = if (name0.endsWith("<init>")) name0.stripSuffix("<init>") + "this"
+ else name0
+ var buf: StringBuilder = null
+ val len = name.length()
+ var i = 0
+ while (i < len) {
+ var ops: OpCodes = null
+ var unicode = false
+ val c = name charAt i
+ if (c == '$' && i + 2 < len) {
+ val ch1 = name.charAt(i+1)
+ if ('a' <= ch1 && ch1 <= 'z') {
+ val ch2 = name.charAt(i+2)
+ if ('a' <= ch2 && ch2 <= 'z') {
+ ops = code2op((ch1 - 'a') * 26 + ch2 - 'a')
+ while ((ops ne null) && !name.startsWith(ops.code, i)) ops = ops.next
+ if (ops ne null) {
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append(ops.op)
+ i += ops.code.length()
+ }
+ /* Handle the decoding of Unicode glyphs that are
+ * not valid Java/JVM identifiers */
+ } else if ((len - i) >= 6 && // Check that there are enough characters left
+ ch1 == 'u' &&
+ ((Character.isDigit(ch2)) ||
+ ('A' <= ch2 && ch2 <= 'F'))) {
+ /* Skip past "$u", next four should be hexadecimal */
+ val hex = name.substring(i+2, i+6)
+ try {
+ val str = Integer.parseInt(hex, 16).toChar
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append(str)
+ /* 2 for "$u", 4 for hexadecimal number */
+ i += 6
+ unicode = true
+ } catch {
+ case _:NumberFormatException =>
+ /* `hex` did not decode to a hexadecimal number, so
+ * do nothing. */
+ }
+ }
+ }
+ }
+ /* If we didn't see an opcode or encoded Unicode glyph, and the
+ buffer is non-empty, write the current character and advance
+ one */
+ if ((ops eq null) && !unicode) {
+ if (buf ne null)
+ buf.append(c)
+ i += 1
+ }
+ }
+ //System.out.println("= " + (if (buf == null) name else buf.toString()));//DEBUG
+ if (buf eq null) name else buf.toString()
+ }
+}
diff --git a/stage2/PackageJars.scala b/stage2/PackageJars.scala
new file mode 100644
index 0000000..a101993
--- /dev/null
+++ b/stage2/PackageJars.scala
@@ -0,0 +1,33 @@
+package cbt
+import java.io.File
+
+// would love to call this just `Package` but that conflicts with scala package objects.
+trait PackageJars extends BaseBuild with ArtifactInfo{
+ def name: String
+ def artifactId = name
+ def defaultVersion: String
+ final def version = context.version getOrElse defaultVersion
+ def `package`: Seq[File] = lib.concurrently( enableConcurrency )(
+ Seq(() => jar, () => docJar, () => srcJar)
+ )( _() ).flatten
+
+ private object cacheJarBasicBuild extends Cache[Option[File]]
+ def jar: Option[File] = cacheJarBasicBuild{
+ compile.flatMap( lib.jar( artifactId, scalaMajorVersion, version, _, jarTarget ) )
+ }
+
+ private object cacheSrcJarBasicBuild extends Cache[Option[File]]
+ def srcJar: Option[File] = cacheSrcJarBasicBuild{
+ lib.srcJar( sourceFiles, artifactId, scalaMajorVersion, version, scalaTarget )
+ }
+
+ private object cacheDocBasicBuild extends Cache[Option[File]]
+ def docJar: Option[File] = cacheDocBasicBuild{
+ lib.docJar(
+ context.cbtHasChanged,
+ scalaVersion, sourceFiles, compileClasspath, docTarget,
+ jarTarget, artifactId, scalaMajorVersion, version,
+ scalacOptions, context.classLoaderCache, context.paths.mavenCache
+ )
+ }
+}
diff --git a/stage2/Plugin.scala b/stage2/Plugin.scala
new file mode 100644
index 0000000..94a8749
--- /dev/null
+++ b/stage2/Plugin.scala
@@ -0,0 +1,4 @@
+package cbt
+trait Plugin extends BaseBuild{
+ override def dependencies = super.dependencies :+ context.cbtDependency
+}
diff --git a/stage2/Publish.scala b/stage2/Publish.scala
new file mode 100644
index 0000000..7e00620
--- /dev/null
+++ b/stage2/Publish.scala
@@ -0,0 +1,49 @@
+package cbt
+import java.io.File
+import java.net.URL
+import java.nio.file.Files.readAllBytes
+
+trait Publish extends PackageJars{
+ def description: String
+ def url: URL
+ def developers: Seq[Developer]
+ def licenses: Seq[License]
+ def scmUrl: String
+ def scmConnection: String
+ def inceptionYear: Int
+ def organization: Option[Organization]
+
+ // ========== package ==========
+
+ /** put additional xml that should go into the POM file in here */
+ def pom: File = lib.pom(
+ groupId = groupId,
+ artifactId = artifactId,
+ version = version,
+ scalaMajorVersion = scalaMajorVersion,
+ name = name,
+ description = description,
+ url = url,
+ developers = developers,
+ licenses = licenses,
+ scmUrl = scmUrl,
+ scmConnection = scmConnection,
+ inceptionYear,
+ organization,
+ dependencies = dependencies,
+ jarTarget = jarTarget
+ )
+
+ // ========== publish ==========
+ private val releaseFolder = s"/${groupId.replace(".","/")}/${artifactId}_$scalaMajorVersion/$version/"
+
+ def publishLocal: Unit =
+ lib.publishLocal( sourceFiles, `package` :+ pom, context.paths.mavenCache, releaseFolder )
+
+ def publishSnapshotLocal: Unit =
+ copy( context.copy(version = Some(version+"-SNAPSHOT")) ).publishLocal
+
+ def isSnapshot: Boolean = version.endsWith("-SNAPSHOT")
+
+ override def copy(context: Context) = super.copy(context).asInstanceOf[Publish]
+}
diff --git a/stage2/SbtDependencyDsl.scala b/stage2/SbtDependencyDsl.scala
new file mode 100644
index 0000000..05cb709
--- /dev/null
+++ b/stage2/SbtDependencyDsl.scala
@@ -0,0 +1,15 @@
+package cbt
+trait SbtDependencyDsl{ self: BaseBuild =>
+ /** SBT-like dependency builder DSL for syntax compatibility */
+ class DependencyBuilder2( groupId: String, artifactId: String, scalaVersion: Option[String] ){
+ def %(version: String) = scalaVersion.map(
+ v => ScalaDependency(groupId, artifactId, version, scalaVersion = v)
+ ).getOrElse(
+ MavenDependency(groupId, artifactId, version)
+ )
+ }
+ implicit class DependencyBuilder(groupId: String){
+ def %%(artifactId: String) = new DependencyBuilder2( groupId, artifactId, Some(scalaMajorVersion) )
+ def %(artifactId: String) = new DependencyBuilder2( groupId, artifactId, None )
+ }
+} \ No newline at end of file
diff --git a/stage2/Scaffold.scala b/stage2/Scaffold.scala
new file mode 100644
index 0000000..866e5da
--- /dev/null
+++ b/stage2/Scaffold.scala
@@ -0,0 +1,52 @@
+package cbt
+import java.io._
+import java.nio.file._
+import java.net._
+trait Scaffold{
+ def logger: Logger
+
+ private def createFile( projectDirectory: File, fileName: String, code: String ){
+ val outputFile = projectDirectory ++ ("/" ++ fileName)
+ Stage0Lib.write( outputFile, code, StandardOpenOption.CREATE_NEW )
+ import scala.Console._
+ println( GREEN ++ "Created " ++ fileName ++ RESET )
+ }
+
+ def createMain(
+ projectDirectory: File
+ ): Unit = {
+ createFile(projectDirectory, "Main.scala", s"""object Main{
+ def main( args: Array[String] ): Unit = {
+ println( Console.GREEN ++ "Hello World" ++ Console.RESET )
+ }
+}
+"""
+ )
+ }
+
+ def createBuild(
+ projectDirectory: File
+ ): Unit = {
+ createFile(projectDirectory, "build/build.scala", s"""import cbt._
+class Build(val context: Context) extends BaseBuild{
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here for scala-library, etc.
+ Seq(
+ // source dependency
+ // DirectoryDependency( projectDirectory ++ "/subProject" )
+ ) ++
+ // pick resolvers explicitly for individual dependencies (and their transitive dependencies)
+ Resolver( mavenCentral, sonatypeReleases ).bind(
+ // CBT-style Scala dependencies
+ // ScalaDependency( "com.lihaoyi", "ammonite-ops", "0.5.5" )
+ // MavenDependency( "com.lihaoyi", "ammonite-ops_2.11", "0.5.5" )
+
+ // SBT-style dependencies
+ // "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
+ // "com.lihaoyi" % "ammonite-ops_2.11" % "0.5.5"
+ )
+}
+"""
+ )
+ }
+}
diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala
new file mode 100644
index 0000000..3d5c244
--- /dev/null
+++ b/stage2/Stage2.scala
@@ -0,0 +1,90 @@
+package cbt
+import java.io._
+
+object Stage2 extends Stage2Base{
+ def getBuild(__context: java.lang.Object, _cbtChanged: java.lang.Boolean) = {
+ val _context = __context.asInstanceOf[Context]
+ val context = _context.copy(
+ cbtHasChanged = _context.cbtHasChanged || _cbtChanged
+ )
+ val first = new Lib(context.logger).loadRoot( context )
+ first.finalBuild
+ }
+
+ def run( args: Stage2Args ): Unit = {
+ import args.logger
+ val paths = CbtPaths(args.cbtHome,args.cache)
+ import paths._
+ val lib = new Lib(args.logger)
+
+ logger.stage2(s"Stage2 start")
+ val loop = args.args.lift(0) == Some("loop")
+ val direct = args.args.lift(0) == Some("direct")
+ val cross = args.args.lift(0) == Some("cross")
+
+ val taskIndex = if (loop || direct || cross) {
+ 1
+ } else {
+ 0
+ }
+ val task = args.args.lift( taskIndex )
+
+ val context: Context = ContextImplementation(
+ args.cwd,
+ args.cwd,
+ args.args.drop( taskIndex +1 ).toArray,
+ logger.enabledLoggers.toArray,
+ logger.start,
+ args.cbtHasChanged,
+ null,
+ null,
+ args.permanentKeys,
+ args.permanentClassLoaders,
+ args.cache,
+ args.cbtHome,
+ args.cbtHome,
+ args.compatibilityTarget,
+ null
+ )
+ val first = lib.loadRoot( context )
+ val build = first.finalBuild
+
+ def call(build: BuildInterface): ExitCode = {
+ if(cross){
+ build.crossScalaVersions.map{
+ v => new lib.ReflectBuild(
+ build.copy(context.copy(scalaVersion = Some(v)))
+ ).callNullary(task)
+ }.filter(_ != ExitCode.Success).headOption getOrElse ExitCode.Success
+ } else {
+ new lib.ReflectBuild(build).callNullary(task)
+ }
+ }
+
+ 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's own source code.")
+ logger.loop("Restarting CBT.")
+ scala.util.control.Breaks.break
+
+ case file if triggerFiles.exists(file.toString startsWith _.toString) =>
+ val build = lib.loadDynamic(context)
+ logger.loop(s"Re-running $task for " ++ build.show)
+ call(build)
+ }
+ } else {
+ val code = call(build)
+ logger.stage2(s"Stage2 end")
+ System.exit(code.integer)
+ }
+ }
+}
diff --git a/stage2/ToolsStage2.scala b/stage2/ToolsStage2.scala
new file mode 100644
index 0000000..df615fc
--- /dev/null
+++ b/stage2/ToolsStage2.scala
@@ -0,0 +1,12 @@
+package cbt
+import java.io._
+object ToolsStage2 extends Stage2Base{
+ def run( _args: Stage2Args ): Unit = {
+ val args = _args.args.dropWhile(Seq("tools","direct") contains _)
+ val lib = new Lib(_args.logger)
+ val toolsTasks = new ToolsTasks(lib, args, _args.cwd, _args.classLoaderCache, _args.cache, _args.cbtHome, _args.cbtHasChanged)
+ new lib.ReflectObject(toolsTasks){
+ def usage: String = "Available methods: " ++ lib.taskNames(toolsTasks.getClass).mkString(" ")
+ }.callNullary(args.lift(0))
+ }
+}
diff --git a/stage2/ToolsTasks.scala b/stage2/ToolsTasks.scala
new file mode 100644
index 0000000..324b7d8
--- /dev/null
+++ b/stage2/ToolsTasks.scala
@@ -0,0 +1,150 @@
+package cbt
+import java.net._
+import java.io.{Console=>_,_}
+import java.nio.file._
+class ToolsTasks(
+ lib: Lib,
+ args: Seq[String],
+ cwd: File,
+ classLoaderCache: ClassLoaderCache,
+ cache: File,
+ cbtHome: File,
+ cbtHasChanged: Boolean
+){
+ private val paths = CbtPaths(cbtHome, cache)
+ import paths._
+ private def Resolver( urls: URL* ) = MavenResolver(cbtHasChanged,mavenCache,urls: _*)
+ implicit val logger: Logger = lib.logger
+ def createMain: Unit = lib.createMain( cwd )
+ def createBuild: Unit = lib.createBuild( cwd )
+ def gui = NailgunLauncher.main(Array(
+ "0.0",
+ (cbtHome / "tools" / "gui").getAbsolutePath,
+ "run",
+ cwd.getAbsolutePath,
+ constants.scalaMajorVersion
+ ))
+ def resolve = {
+ ClassPath.flatten(
+ args(1).split(",").toVector.map{
+ d =>
+ val v = d.split(":")
+ Resolver(mavenCentral).bindOne(MavenDependency(v(0),v(1),v(2))).classpath
+ }
+ )
+ }
+ def dependencyTree = {
+ args(1).split(",").toVector.map{
+ d =>
+ val v = d.split(":")
+ Resolver(mavenCentral).bindOne(MavenDependency(v(0),v(1),v(2))).dependencyTree
+ }.mkString("\n\n")
+ }
+ def amm = ammonite
+ def ammonite = {
+ val version = args.lift(1).getOrElse(constants.scalaVersion)
+ val classLoader = Resolver(mavenCentral).bindOne(
+ MavenDependency(
+ "com.lihaoyi","ammonite-repl_2.11.8",args.lift(1).getOrElse("0.5.8")
+ )
+ ).classLoader(classLoaderCache)
+ // FIXME: this does not work quite yet, throws NoSuchFileException: /ammonite/repl/frontend/ReplBridge$.class
+ lib.runMain(
+ "ammonite.repl.Main", args.drop(2), classLoader
+ )
+ }
+ def scala = {
+ val version = args.lift(1).getOrElse(constants.scalaVersion)
+ val scalac = new ScalaCompilerDependency( cbtHasChanged, mavenCache, version )
+ val _args = Seq("-cp", scalac.classpath.string) ++ args.drop(2)
+ lib.runMain(
+ "scala.tools.nsc.MainGenericRunner", _args, scalac.classLoader(classLoaderCache)
+ )
+ }
+ def cbtEarlyDependencies = {
+ val scalaVersion = args.lift(1).getOrElse(constants.scalaVersion)
+ val scalaMajorVersion = scalaVersion.split("\\.").take(2).mkString(".")
+ val scalaXmlVersion = args.lift(2).getOrElse(constants.scalaXmlVersion)
+ val zincVersion = args.lift(3).getOrElse(constants.zincVersion)
+ val scalaDeps = Seq(
+ Resolver(mavenCentral).bindOne(MavenDependency("org.scala-lang","scala-reflect",scalaVersion)),
+ Resolver(mavenCentral).bindOne(MavenDependency("org.scala-lang","scala-compiler",scalaVersion))
+ )
+
+ val scalaXml = Dependencies(
+ Resolver(mavenCentral).bind(
+ MavenDependency("org.scala-lang.modules","scala-xml_"+scalaMajorVersion,scalaXmlVersion),
+ MavenDependency("org.scala-lang","scala-library",scalaVersion)
+ )
+ )
+
+ val zinc = Resolver(mavenCentral).bindOne(MavenDependency("com.typesafe.zinc","zinc",zincVersion))
+
+ def valName(dep: BoundMavenDependency) = {
+ val words = dep.artifactId.split("_").head.split("-")
+ words(0) ++ words.drop(1).map(s => s(0).toString.toUpperCase ++ s.drop(1)).mkString ++ "_" ++ dep.version.replace(".","_") ++ "_"
+ }
+
+ def jarVal(dep: BoundMavenDependency) = "_" + valName(dep) +"Jar"
+ def transitive(dep: Dependency) = (dep +: lib.transitiveDependencies(dep).reverse).collect{case d: BoundMavenDependency => d}
+ def codeEach(dep: Dependency) = {
+ transitive(dep).tails.map(_.reverse).toVector.reverse.drop(1).map{
+ deps =>
+ val d = deps.last
+ val parents = deps.dropRight(1)
+ val parentString = if(parents.isEmpty) "rootClassLoader" else ( valName(parents.last) )
+ val n = valName(d)
+ s"""
+ // ${d.groupId}:${d.artifactId}:${d.version}
+ download(new URL(mavenUrl + "${d.basePath}.jar"), Paths.get(${n}File), "${d.jarSha1}");
+
+ String[] ${n}ClasspathArray = new String[]{${deps.sortBy(_.jar).map(valName(_)+"File").mkString(", ")}};
+ String ${n}Classpath = classpath( ${n}ClasspathArray );
+ ClassLoader $n =
+ classLoaderCache.contains( ${n}Classpath )
+ ? classLoaderCache.get( ${n}Classpath )
+ : classLoaderCache.put( classLoader( ${n}File, $parentString ), ${n}Classpath );"""
+ }
+ }
+ val assignments = codeEach(zinc) ++ codeEach(scalaXml)
+ val files = scalaDeps ++ transitive(scalaXml) ++ transitive(zinc)
+ //{ case (name, dep) => s"$name =\n ${tree(dep, 4)};" }.mkString("\n\n ")
+ val code = s"""// This file was auto-generated using `cbt tools cbtEarlyDependencies`
+package cbt;
+import java.io.*;
+import java.nio.file.*;
+import java.net.*;
+import java.security.*;
+import static cbt.Stage0Lib.*;
+import static cbt.NailgunLauncher.*;
+
+class EarlyDependencies{
+
+ /** ClassLoader for stage1 */
+ ClassLoader classLoader;
+ String[] classpathArray;
+ /** ClassLoader for zinc */
+ ClassLoader zinc;
+
+${files.map(d => s""" String ${valName(d)}File;""").mkString("\n")}
+
+ public EarlyDependencies(
+ String mavenCache, String mavenUrl, ClassLoaderCache2<ClassLoader> classLoaderCache, ClassLoader rootClassLoader
+ ) throws Exception {
+${files.map(d => s""" ${valName(d)}File = mavenCache + "${d.basePath}.jar";""").mkString("\n")}
+
+${scalaDeps.map(d => s""" download(new URL(mavenUrl + "${d.basePath}.jar"), Paths.get(${valName(d)}File), "${d.jarSha1}");""").mkString("\n")}
+${assignments.mkString("\n")}
+
+ classLoader = scalaXml_${scalaXmlVersion.replace(".","_")}_;
+ classpathArray = scalaXml_${scalaXmlVersion.replace(".","_")}_ClasspathArray;
+
+ zinc = zinc_${zincVersion.replace(".","_")}_;
+ }
+}
+"""
+ val file = nailgun ++ ("/" ++ "EarlyDependencies.java")
+ lib.write( file, code )
+ println( Console.GREEN ++ "Wrote " ++ file.string ++ Console.RESET )
+ }
+}
diff --git a/stage2/plugins/AdvancedFlags.scala b/stage2/plugins/AdvancedFlags.scala
new file mode 100644
index 0000000..4ff701d
--- /dev/null
+++ b/stage2/plugins/AdvancedFlags.scala
@@ -0,0 +1,10 @@
+package cbt
+trait AdvancedScala extends BaseBuild{
+ override def scalacOptions = super.scalacOptions ++ Seq(
+ "-language:postfixOps",
+ "-language:implicitConversions",
+ "-language:higherKinds",
+ "-language:existentials",
+ "-language:experimental.macros"
+ )
+}
diff --git a/stage2/plugins/Dotty.scala b/stage2/plugins/Dotty.scala
new file mode 100644
index 0000000..8671fb6
--- /dev/null
+++ b/stage2/plugins/Dotty.scala
@@ -0,0 +1,172 @@
+package cbt
+import java.io.File
+import java.net.URL
+import java.nio.file.Files
+import java.nio.file.attribute.FileTime
+
+trait Dotty extends BaseBuild{
+ def dottyVersion: String = "0.1-20160926-ec28ea1-NIGHTLY"
+ def dottyOptions: Seq[String] = Seq()
+ override def scalaTarget: File = target ++ s"/dotty-$dottyVersion"
+
+ private lazy val dottyLib = new DottyLib(
+ logger, context.cbtHasChanged, context.paths.mavenCache,
+ context.classLoaderCache, dottyVersion = dottyVersion
+ )
+
+ private object compileCache extends Cache[Option[File]]
+ override def compile: Option[File] = compileCache{
+ dottyLib.compile(
+ needsUpdate || context.parentBuild.map(_.needsUpdate).getOrElse(false),
+ sourceFiles, compileTarget, compileStatusFile, compileClasspath,
+ dottyOptions
+ )
+ }
+
+ def doc: ExitCode =
+ dottyLib.doc(
+ sourceFiles, compileClasspath, docTarget, dottyOptions
+ )
+
+ override def repl = dottyLib.repl(context.args, classpath)
+
+ override def dependencies = Resolver(mavenCentral).bind(
+ ScalaDependency( "org.scala-lang.modules", "scala-java8-compat", "0.8.0-RC7" )
+ )
+}
+
+class DottyLib(
+ logger: Logger,
+ cbtHasChanged: Boolean,
+ mavenCache: File,
+ classLoaderCache: ClassLoaderCache,
+ dottyVersion: String
+){
+ val lib = new Lib(logger)
+ import lib._
+
+ private def Resolver(urls: URL*) = MavenResolver(cbtHasChanged, mavenCache, urls: _*)
+ private lazy val dottyDependency = Resolver(mavenCentral).bindOne(
+ MavenDependency("ch.epfl.lamp","dotty_2.11",dottyVersion)
+ )
+
+ def repl(args: Seq[String], classpath: ClassPath) = {
+ consoleOrFail("Use `cbt direct repl` instead")
+ lib.runMain(
+ "dotty.tools.dotc.repl.Main",
+ Seq(
+ "-bootclasspath",
+ dottyDependency.classpath.string,
+ "-classpath",
+ classpath.string
+ ) ++ args,
+ dottyDependency.classLoader(classLoaderCache)
+ )
+ }
+
+ def doc(
+ sourceFiles: Seq[File],
+ dependencyClasspath: ClassPath,
+ docTarget: File,
+ compileArgs: Seq[String]
+ ): ExitCode = {
+ if(sourceFiles.isEmpty){
+ ExitCode.Success
+ } else {
+ docTarget.mkdirs
+ val args = Seq(
+ // FIXME: can we use compiler dependency here?
+ "-bootclasspath", dottyDependency.classpath.string, // FIXME: does this break for builds that don't have scalac dependencies?
+ "-classpath", dependencyClasspath.string, // FIXME: does this break for builds that don't have scalac dependencies?
+ "-d", docTarget.toString
+ ) ++ compileArgs ++ sourceFiles.map(_.toString)
+ logger.lib("creating docs for source files "+args.mkString(", "))
+ val exitCode = redirectOutToErr{
+ runMain(
+ "dotty.tools.dottydoc.api.java.Dottydoc",
+ args,
+ dottyDependency.classLoader(classLoaderCache),
+ fakeInstance = true // this is a hack as Dottydoc's main method is not static
+ )
+ }
+ System.err.println("done")
+ exitCode
+ }
+ }
+
+ def compile(
+ needsRecompile: Boolean,
+ files: Seq[File],
+ compileTarget: File,
+ statusFile: File,
+ classpath: ClassPath,
+ dottyOptions: Seq[String]
+ ): Option[File] = {
+
+ if(classpath.files.isEmpty)
+ throw new Exception("Trying to compile with empty classpath. Source files: " ++ files.toString)
+
+ if( files.isEmpty ){
+ None
+ }else{
+ if( needsRecompile ){
+ val start = System.currentTimeMillis
+
+ val _class = "dotty.tools.dotc.Main"
+ val dualArgs =
+ Seq(
+ "-d", compileTarget.toString
+ )
+ val singleArgs = dottyOptions.map( "-S" ++ _ )
+
+ val code =
+ try{
+ System.err.println("Compiling with Dotty to " ++ compileTarget.toString)
+ compileTarget.mkdirs
+ redirectOutToErr{
+ lib.runMain(
+ _class,
+ dualArgs ++ singleArgs ++ Seq(
+ "-bootclasspath", dottyDependency.classpath.string, // let's put cp last. It so long
+ "-classpath", classpath.string // let's put cp last. It so long
+ ) ++ files.map(_.toString),
+ dottyDependency.classLoader(classLoaderCache)
+ )
+ }
+ } catch {
+ case e: Exception =>
+ System.err.println(red("Dotty crashed. See https://github.com/lampepfl/dotty/issues. To reproduce run:"))
+ System.out.println(s"""
+java -cp \\
+${dottyDependency.classpath.strings.mkString(":\\\n")} \\
+\\
+${_class} \\
+\\
+${dualArgs.grouped(2).map(_.mkString(" ")).mkString(" \\\n")} \\
+\\
+${singleArgs.mkString(" \\\n")} \\
+\\
+-bootclasspath \\
+${dottyDependency.classpath.strings.mkString(":\\\n")} \\
+-classpath \\
+${classpath.strings.mkString(":\\\n")} \\
+\\
+${files.sorted.mkString(" \\\n")}
+"""
+ )
+ ExitCode.Failure
+ }
+
+ if(code == ExitCode.Success){
+ // write version and when last compilation started so we can trigger
+ // recompile if cbt version changed or newer source files are seen
+ write(statusFile, "")//cbtVersion.getBytes)
+ Files.setLastModifiedTime(statusFile.toPath, FileTime.fromMillis(start) )
+ } else {
+ System.exit(code.integer) // FIXME: let's find a better solution for error handling. Maybe a monad after all.
+ }
+ }
+ Some( compileTarget )
+ }
+ }
+}
diff --git a/stage2/plugins/GithubPom.scala b/stage2/plugins/GithubPom.scala
new file mode 100644
index 0000000..8b11385
--- /dev/null
+++ b/stage2/plugins/GithubPom.scala
@@ -0,0 +1,11 @@
+package cbt
+import java.net.URL
+trait GithubPom extends Publish{
+ def user: String
+ def githubProject = name
+ def githubUser = user
+ final def githubUserProject = githubUser ++ "/" ++ githubProject
+ override def url = new URL(s"http://github.com/$githubUserProject")
+ override def scmUrl = s"git@github.com:$githubUserProject.git"
+ override def scmConnection = s"scm:git:$scmUrl"
+}
diff --git a/stage2/plugins/ScalaParadise.scala b/stage2/plugins/ScalaParadise.scala
new file mode 100644
index 0000000..28ee934
--- /dev/null
+++ b/stage2/plugins/ScalaParadise.scala
@@ -0,0 +1,29 @@
+package cbt
+trait ScalaParadise extends BaseBuild{
+ def scalaParadiseVersion = "2.1.0"
+
+ private def scalaParadiseDependency =
+ Resolver( mavenCentral ).bindOne(
+ "org.scalamacros" % ("paradise_" ++ scalaVersion) % scalaParadiseVersion
+ )
+
+ override def dependencies = (
+ super.dependencies // don't forget super.dependencies here
+ ++ (
+ if(scalaVersion.startsWith("2.10."))
+ Seq(scalaParadiseDependency)
+ else
+ Seq()
+ )
+ )
+
+ override def scalacOptions = (
+ super.scalacOptions
+ ++ (
+ if(scalaVersion.startsWith("2.10."))
+ Seq("-Xplugin:"++scalaParadiseDependency.jar.string)
+ else
+ Seq()
+ )
+ )
+}
diff --git a/stage2/plugins/readme.txt b/stage2/plugins/readme.txt
new file mode 100644
index 0000000..d959c72
--- /dev/null
+++ b/stage2/plugins/readme.txt
@@ -0,0 +1,4 @@
+This directory is for built-in plugins which are always available.
+This is nice for plugins, which themselves do not have any
+compile-time dependencies except for CBT itself.
+See <cbt home>/plugins/ for plugins, that require an explicit dependency in the BuildBuild.
diff --git a/stage2/pom.scala b/stage2/pom.scala
new file mode 100644
index 0000000..b521d51
--- /dev/null
+++ b/stage2/pom.scala
@@ -0,0 +1,6 @@
+package cbt
+import java.net.URL
+case class Organization(
+ name: String,
+ url: Option[URL]
+)
diff --git a/test/build/build.scala b/test/build/build.scala
new file mode 100644
index 0000000..5a138fb
--- /dev/null
+++ b/test/build/build.scala
@@ -0,0 +1,4 @@
+import cbt._
+class Build(val context: cbt.Context) extends BaseBuild{
+ override def dependencies = super.dependencies :+ context.cbtDependency
+}
diff --git a/test/empty-build-file/Main.scala b/test/empty-build-file/Main.scala
new file mode 100644
index 0000000..19d4beb
--- /dev/null
+++ b/test/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/empty-build-file/build/build.scala b/test/empty-build-file/build/build.scala
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/empty-build-file/build/build.scala
diff --git a/test/empty-build/Main.scala b/test/empty-build/Main.scala
new file mode 100644
index 0000000..19d4beb
--- /dev/null
+++ b/test/empty-build/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/empty-build/build/build/dummy b/test/empty-build/build/build/dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/empty-build/build/build/dummy
diff --git a/test/forgot-extend/build/build.scala b/test/forgot-extend/build/build.scala
new file mode 100644
index 0000000..9181a5d
--- /dev/null
+++ b/test/forgot-extend/build/build.scala
@@ -0,0 +1,2 @@
+import cbt._
+class Build(val context: Context)
diff --git a/test/library-test/Foo.scala b/test/library-test/Foo.scala
new file mode 100644
index 0000000..75c0780
--- /dev/null
+++ b/test/library-test/Foo.scala
@@ -0,0 +1,4 @@
+package lib_test
+object Foo{
+ def bar = "Hello, Foo Bar"
+}
diff --git a/test/library-test/build/build.scala b/test/library-test/build/build.scala
new file mode 100644
index 0000000..d07e58e
--- /dev/null
+++ b/test/library-test/build/build.scala
@@ -0,0 +1,25 @@
+import cbt._
+
+// cbt:https://github.com/cvogt/cbt.git#bf4ea112fe668fb7e2e95a2baca4989b16384783
+class Build(val context: Context) extends BaseBuild with PackageJars{
+ def groupId = "cbt.test"
+ def defaultVersion = "0.1"
+ def name = "library-test"
+
+ override def dependencies =
+ super.dependencies ++ // don't forget super.dependencies here for scala-library, etc.
+ Seq(
+ // source dependency
+ // DirectoryDependency( projectDirectory ++ "/subProject" )
+ ) ++
+ // pick resolvers explicitly for individual dependencies (and their transitive dependencies)
+ Resolver( mavenCentral, sonatypeReleases ).bind(
+ // CBT-style Scala dependencies
+ // ScalaDependency( "com.lihaoyi", "ammonite-ops", "0.5.5" )
+ // MavenDependency( "com.lihaoyi", "ammonite-ops_2.11", "0.5.5" )
+
+ // SBT-style dependencies
+ // "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
+ // "com.lihaoyi" % "ammonite-ops_2.11" % "0.5.5"
+ )
+}
diff --git a/test/multi-build/build/build.scala b/test/multi-build/build/build.scala
new file mode 100644
index 0000000..5576a3d
--- /dev/null
+++ b/test/multi-build/build/build.scala
@@ -0,0 +1,7 @@
+import cbt._
+class Build(val context: Context) extends BaseBuild{
+ override def dependencies = Seq(
+ DirectoryDependency(projectDirectory++"/sub1"),
+ DirectoryDependency(projectDirectory++"/sub2")
+ ) ++ super.dependencies
+}
diff --git a/test/multi-build/code.scala b/test/multi-build/code.scala
new file mode 100644
index 0000000..3fe85ad
--- /dev/null
+++ b/test/multi-build/code.scala
@@ -0,0 +1,5 @@
+object Main extends App{
+ println("root here")
+ println(Foo(5))
+ println(Bar("test"))
+}
diff --git a/test/multi-build/sub1/code.scala b/test/multi-build/sub1/code.scala
new file mode 100644
index 0000000..b2d5deb
--- /dev/null
+++ b/test/multi-build/sub1/code.scala
@@ -0,0 +1,4 @@
+case class Foo(i: Int)
+object Main extends App{
+ println("sub1 here")
+} \ No newline at end of file
diff --git a/test/multi-build/sub2/code.scala b/test/multi-build/sub2/code.scala
new file mode 100644
index 0000000..1ec6ebf
--- /dev/null
+++ b/test/multi-build/sub2/code.scala
@@ -0,0 +1,4 @@
+case class Bar(s: String)
+object Main extends App{
+ println("sub1 here")
+}
diff --git a/test/no-build-file/Main.scala b/test/no-build-file/Main.scala
new file mode 100644
index 0000000..19d4beb
--- /dev/null
+++ b/test/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/no-build-file/build/foo.scala b/test/no-build-file/build/foo.scala
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/no-build-file/build/foo.scala
diff --git a/test/nothing/placeholder b/test/nothing/placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/nothing/placeholder
diff --git a/test/simple-fixed-cbt/Main.scala b/test/simple-fixed-cbt/Main.scala
new file mode 100644
index 0000000..75f9349
--- /dev/null
+++ b/test/simple-fixed-cbt/Main.scala
@@ -0,0 +1,6 @@
+import lib_test.Foo
+import org.eclipse.jgit.lib.Ref
+import com.spotify.missinglink.ArtifactLoader
+object Main extends App{
+ println(Foo.bar)
+}
diff --git a/test/simple-fixed-cbt/build/build.scala b/test/simple-fixed-cbt/build/build.scala
new file mode 100644
index 0000000..1d0640d
--- /dev/null
+++ b/test/simple-fixed-cbt/build/build.scala
@@ -0,0 +1,14 @@
+import cbt._
+
+// cbt:https://github.com/cvogt/cbt.git#bf4ea112fe668fb7e2e95a2baca4989b16384783
+class Build(val context: cbt.Context) extends PackageJars{
+ override def dependencies = super.dependencies ++ Seq(
+ DirectoryDependency( context.cbtHome ++ "/test/library-test" )
+ ) ++ Resolver( mavenCentral ).bind(
+ MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"),
+ MavenDependency("com.spotify", "missinglink-core", "0.1.1")
+ )
+ def groupId: String = "cbt.test"
+ def defaultVersion: String = "0.1"
+ def name: String = "simple-fixed-cbt"
+} \ No newline at end of file
diff --git a/test/simple-fixed/Main.scala b/test/simple-fixed/Main.scala
new file mode 100644
index 0000000..75f9349
--- /dev/null
+++ b/test/simple-fixed/Main.scala
@@ -0,0 +1,6 @@
+import lib_test.Foo
+import org.eclipse.jgit.lib.Ref
+import com.spotify.missinglink.ArtifactLoader
+object Main extends App{
+ println(Foo.bar)
+}
diff --git a/test/simple-fixed/build/build.scala b/test/simple-fixed/build/build.scala
new file mode 100644
index 0000000..a2bd584
--- /dev/null
+++ b/test/simple-fixed/build/build.scala
@@ -0,0 +1,29 @@
+import cbt._
+
+class Build(context: cbt.Context) extends BasicBuild(context){
+ override def dependencies = (
+ super.dependencies
+ ++
+ Seq(
+ GitDependency("https://github.com/cvogt/cbt.git", "908e05e296974fe67d8aaf9f094d97ff986905af", Some("test/library-test"))
+ )
+ ++
+ Resolver(mavenCentral).bind(
+ ScalaDependency("com.typesafe.play", "play-json", "2.4.4"),
+ MavenDependency("joda-time", "joda-time", "2.9.2"),
+ // the below tests pom inheritance with dependencyManagement and variable substitution for pom properties
+ MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"),
+ // the below tests pom inheritance with variable substitution for pom xml tag contents
+ MavenDependency("com.spotify", "missinglink-core", "0.1.1")
+ )
+ ++
+ Resolver(
+ mavenCentral,
+ bintray("tpolecat"),
+ sonatypeSnapshots
+ ).bind(
+ "org.cvogt" %% "play-json-extensions" % "0.8.0",
+ "ai.x" %% "lens" % "1.0.0"
+ )
+ )
+}
diff --git a/test/simple/Main.scala b/test/simple/Main.scala
new file mode 100644
index 0000000..75f9349
--- /dev/null
+++ b/test/simple/Main.scala
@@ -0,0 +1,6 @@
+import lib_test.Foo
+import org.eclipse.jgit.lib.Ref
+import com.spotify.missinglink.ArtifactLoader
+object Main extends App{
+ println(Foo.bar)
+}
diff --git a/test/simple/build/build.scala b/test/simple/build/build.scala
new file mode 100644
index 0000000..586daca
--- /dev/null
+++ b/test/simple/build/build.scala
@@ -0,0 +1,43 @@
+import cbt._
+
+class Build(val context: cbt.Context) extends BaseBuild{
+ override def dependencies = (
+ super.dependencies
+ ++
+ Seq(
+ GitDependency("https://github.com/cvogt/cbt.git", "908e05e296974fe67d8aaf9f094d97ff986905af", Some("test/library-test"))
+ ) ++
+ // FIXME: make the below less verbose
+ Resolver( mavenCentral ).bind(
+ ScalaDependency("com.typesafe.play", "play-json", "2.4.4"),
+ MavenDependency("joda-time", "joda-time", "2.9.2"),
+ // the below tests pom inheritance with dependencyManagement and variable substitution for pom properties
+ MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"),
+ // the below tests pom inheritance with variable substitution for pom xml tag contents
+ MavenDependency("com.spotify", "missinglink-core", "0.1.1"),
+ // the below tests pom inheritance with variable substitution being parts of strings
+ MavenDependency("cc.factorie","factorie_2.11","1.2")
+ // the dependency below uses a maven version range. Currently not supported.
+ // TODO: put in a proper error message for version range not supported
+ //MavenDependency("com.github.nikita-volkov", "sext", "0.2.4")
+ // currently breaks with can't find https://repo1.maven.org/maven2/org/apache/avro/avro-mapred/1.7.7/avro-mapred-1.7.7-hadoop2.pom.sha1
+ // org.apache.spark:spark-sql_2.11:1.6.1
+ // currently fails, let's see if because of a bug
+ // io.spray:spray-http:1.3.3
+ ) ++
+ Resolver( new java.net.URL("http://maven.spikemark.net/roundeights") ).bind(
+ // Check that lower case checksums work
+ ScalaDependency("com.roundeights","hasher","1.2.0")
+ ) ++
+ Resolver(
+ mavenCentral,
+ bintray("tpolecat"),
+ sonatypeSnapshots
+ ).bind(
+ "org.cvogt" %% "play-json-extensions" % "0.8.0",
+ "ai.x" %% "lens" % "1.0.0"
+ )
+ )
+
+ def printArgs = context.args.mkString(" ")
+}
diff --git a/test/test.scala b/test/test.scala
new file mode 100644
index 0000000..5b4a4af
--- /dev/null
+++ b/test/test.scala
@@ -0,0 +1,262 @@
+package cbt
+package test
+import java.util.concurrent.ConcurrentHashMap
+import java.io.File
+import java.nio.file._
+import java.net.URL
+
+// micro framework
+object Main{
+ def main(_args: Array[String]): Unit = {
+ val start = System.currentTimeMillis
+ val args = new Stage1ArgsParser(_args.toVector)
+ implicit val logger: Logger = new Logger(args.enabledLoggers, System.currentTimeMillis)
+ val lib = new Lib(logger)
+ val cbtHome = new File(System.getenv("CBT_HOME"))
+
+ var successes = 0
+ var failures = 0
+ def assertException[T:scala.reflect.ClassTag](msg: String = "")(code: => Unit)(implicit logger: Logger) = {
+ try{
+ code
+ assert(false, msg)
+ }catch{ case _:AssertionError => }
+ }
+ def assert(condition: Boolean, msg: String = "")(implicit logger: Logger) = {
+ scala.util.Try{
+ Predef.assert(condition, "["++msg++"]")
+ }.map{ _ =>
+ print(".")
+ successes += 1
+ }.recover{
+ case e: AssertionError =>
+ println("FAILED")
+ e.printStackTrace
+ failures += 1
+ }.get
+ }
+
+ def runCbt(path: String, _args: Seq[String])(implicit logger: Logger): Result = {
+ import java.io._
+ val allArgs: Seq[String] = ((cbtHome.string ++ "/cbt") +: "direct" +: (_args ++ args.propsRaw))
+ logger.test(allArgs.toString)
+ val pb = new ProcessBuilder( allArgs :_* )
+ pb.directory(cbtHome ++ ("/test/" ++ path))
+ val p = pb.start
+ val berr = new BufferedReader(new InputStreamReader(p.getErrorStream));
+ val bout = new BufferedReader(new InputStreamReader(p.getInputStream));
+ import collection.JavaConversions._
+ val err = Stream.continually(berr.readLine()).takeWhile(_ != null).mkString("\n")
+ val out = Stream.continually(bout.readLine()).takeWhile(_ != null).mkString("\n")
+ p.waitFor
+ Result(p.exitValue == 0, out, err)
+ }
+ case class Result(exit0: Boolean, out: String, err: String)
+ def assertSuccess(res: Result, msg: => String)(implicit logger: Logger) = {
+ assert(res.exit0, msg ++ res.toString)
+ }
+
+ // tests
+ def usage(path: String)(implicit logger: Logger) = {
+ val usageString = "Methods provided by CBT"
+ val res = runCbt(path, Seq())
+ logger.test(res.toString)
+ val debugToken = "usage " ++ path ++ " "
+ assertSuccess(res,debugToken)
+ assert(res.out == "", debugToken ++ res.toString)
+ assert(res.err contains usageString, debugToken ++ res.toString)
+ }
+ def compile(path: String)(implicit logger: Logger) = task("compile", path)
+ def task(name: String, path: String)(implicit logger: Logger) = {
+ val res = runCbt(path, Seq(name))
+ val debugToken = name ++ " " ++ path ++ " "
+ assertSuccess(res,debugToken)
+ res
+ // assert(res.err == "", res.err) // FIXME: enable this
+ }
+
+ def clean(path: String)(implicit logger: Logger) = {
+ val res = runCbt(path, Seq("clean", "dry-run", "force"))
+ val debugToken = "\n"++lib.red("Deleting") ++ " " ++ (cbtHome ++("/test/"++path++"/target")).toPath.toAbsolutePath.toString++"\n"
+ val debugToken2 = "\n"++lib.red("Deleting") ++ " " ++ (cbtHome ++("/test/"++path)).toPath.toAbsolutePath.toString++"\n"
+ assertSuccess(res,debugToken)
+ assert(res.out == "", debugToken ++ " " ++ res.toString)
+ assert(res.err.contains(debugToken), debugToken ++ " " ++ res.toString)
+ assert(
+ !res.err.contains(debugToken2),
+ "Tried to delete too much: " ++ debugToken2 ++ " " ++ res.toString
+ )
+ res.err.split("\n").filter(_.startsWith(lib.red("Deleting"))).foreach{ line =>
+ assert(
+ line.size >= debugToken2.trim.size,
+ "Tried to delete too much: " ++ line ++" debugToken2: " ++ debugToken2
+ )
+ }
+ }
+
+ logger.test( "Running tests " ++ _args.toList.toString )
+
+ val cache = cbtHome ++ "/cache"
+ val mavenCache = cache ++ "/maven"
+ val cbtHasChanged = true
+ def Resolver(urls: URL*) = MavenResolver(cbtHasChanged, mavenCache, urls: _*)
+
+ {
+ val noContext = ContextImplementation(
+ cbtHome ++ "/test/nothing",
+ cbtHome,
+ Array(),
+ Array(),
+ start,
+ cbtHasChanged,
+ null,
+ null,
+ new ConcurrentHashMap[String,AnyRef],
+ new ConcurrentHashMap[AnyRef,ClassLoader],
+ cache,
+ cbtHome,
+ cbtHome,
+ cbtHome ++ "/compatibilityTarget",
+ null
+ )
+
+ val b = new BasicBuild(noContext){
+ override def dependencies =
+ Resolver(mavenCentral).bind(
+ MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0"),
+ MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0")
+ )
+ }
+ val cp = b.classpath
+ assert(cp.strings.distinct == cp.strings, "duplicates in classpath: " ++ cp.string)
+ }
+
+ // test that messed up artifacts crash with an assertion (which should tell the user what's up)
+ assertException[AssertionError](){
+ Resolver(mavenCentral).bindOne( MavenDependency("com.jcraft", "jsch", " 0.1.53") ).classpath
+ }
+ assertException[AssertionError](){
+ Resolver(mavenCentral).bindOne( MavenDependency("com.jcraft", null, "0.1.53") ).classpath
+ }
+ assertException[AssertionError](){
+ Resolver(mavenCentral).bindOne( MavenDependency("com.jcraft", "", " 0.1.53") ).classpath
+ }
+ assertException[AssertionError](){
+ Resolver(mavenCentral).bindOne( MavenDependency("com.jcraft%", "jsch", " 0.1.53") ).classpath
+ }
+ assertException[AssertionError](){
+ Resolver(mavenCentral).bindOne( MavenDependency("", "jsch", " 0.1.53") ).classpath
+ }
+
+ (
+ (
+ if(System.getenv("CIRCLECI") == null){
+ // tenporarily disable on circleci as it seems to have trouble reliably
+ // downloading from bintray
+ Dependencies(
+ Resolver( bintray("tpolecat") ).bind(
+ lib.ScalaDependency("org.tpolecat","tut-core","0.4.2", scalaMajorVersion="2.11")
+ )
+ ).classpath.strings
+ } else Nil
+ ) ++
+ Dependencies(
+ Resolver( sonatypeReleases ).bind(
+ MavenDependency("org.cvogt","scala-extensions_2.11","0.5.1")
+ )
+ ).classpath.strings
+ ++
+ Dependencies(
+ Resolver( mavenCentral ).bind(
+ MavenDependency("ai.x","lens_2.11","1.0.0")
+ )
+ ).classpath.strings
+ ).foreach{
+ path => assert(new File(path).exists, path)
+ }
+
+ usage("nothing")
+ compile("nothing")
+ //clean("nothing")
+ usage("multi-build")
+ compile("multi-build")
+ clean("multi-build")
+ usage("simple")
+ compile("simple")
+ clean("simple")
+ usage("simple-fixed")
+ compile("simple-fixed")
+
+ compile("../plugins/sbt_layout")
+ compile("../plugins/scalafmt")
+ compile("../plugins/scalajs")
+ compile("../plugins/scalariform")
+ compile("../plugins/scalatest")
+ compile("../plugins/wartremover")
+ compile("../plugins/uber-jar")
+ compile("../examples/scalafmt-example")
+ compile("../examples/scalariform-example")
+ compile("../examples/scalatest-example")
+ compile("../examples/scalajs-react-example/js")
+ compile("../examples/scalajs-react-example/jvm")
+ compile("../examples/multi-project-example")
+ if(sys.props("java.version").startsWith("1.7")){
+ System.err.println("\nskipping dotty tests on Java 7")
+ } else {
+ compile("../examples/dotty-example")
+ task("run","../examples/dotty-example")
+ task("doc","../examples/dotty-example")
+ }
+ task("fastOptJS","../examples/scalajs-react-example/js")
+ task("fullOptJS","../examples/scalajs-react-example/js")
+ compile("../examples/uber-jar-example")
+
+ {
+ val res = task("docJar","simple-fixed-cbt")
+ assert( res.out endsWith "simple-fixed-cbt_2.11-0.1-javadoc.jar", res.out )
+ assert( res.err contains "model contains", res.err )
+ assert( res.err endsWith "documentable templates", res.err )
+ }
+
+ {
+ val res = runCbt("simple", Seq("printArgs","1","2","3"))
+ assert(res.exit0)
+ assert(res.out == "1 2 3", res.out)
+ }
+
+ {
+ val res = runCbt("../examples/build-info-example", Seq("run"))
+ assert(res.exit0)
+ assert(res.out contains "version: 0.1", res.out)
+ }
+
+ {
+ val res = runCbt("forgot-extend", Seq("run"))
+ assert(!res.exit0)
+ assert(res.err contains "Build cannot be cast to cbt.BuildInterface", res.err)
+ }
+
+ {
+ val res = runCbt("no-build-file", Seq("run"))
+ assert(!res.exit0)
+ assert(res.err contains "No file build.scala (lower case) found in", res.err)
+ }
+
+ {
+ val res = runCbt("empty-build-file", Seq("run"))
+ assert(!res.exit0)
+ assert(res.err contains "You need to define a class Build in build.scala in", res.err)
+ }
+
+ {
+ val res = runCbt("../examples/wartremover-example", Seq("compile"))
+ assert(!res.exit0)
+ assert(res.err.contains("var is disabled"), res.out)
+ assert(res.err.contains("null is disabled"), res.out)
+ }
+
+ System.err.println(" DONE!")
+ System.err.println( successes.toString ++ " succeeded, "++ failures.toString ++ " failed" )
+ if(failures > 0) System.exit(1) else System.exit(0)
+ }
+}
diff --git a/tools/gui/build/build.scala b/tools/gui/build/build.scala
new file mode 100644
index 0000000..5312e55
--- /dev/null
+++ b/tools/gui/build/build.scala
@@ -0,0 +1,12 @@
+import cbt._
+
+class Build(val context: Context) extends BaseBuild {
+
+ override def dependencies = {
+ super.dependencies ++ Resolver(mavenCentral).bind(
+ MavenDependency("org.eclipse.jetty", "jetty-server", "9.3.12.v20160915"),
+ MavenDependency("org.scalaj", "scalaj-http_" + constants.scalaMajorVersion, "2.3.0")
+ )
+ }
+
+}
diff --git a/tools/gui/resources/template-project/build/build.scala b/tools/gui/resources/template-project/build/build.scala
new file mode 100644
index 0000000..3ec5db9
--- /dev/null
+++ b/tools/gui/resources/template-project/build/build.scala
@@ -0,0 +1,5 @@
+import cbt._
+
+class Build(val context: Context) extends BaseBuild##with## {
+
+##projectName####dependencies##}
diff --git a/tools/gui/resources/template-project/build/build/build.scala b/tools/gui/resources/template-project/build/build/build.scala
new file mode 100644
index 0000000..1a59657
--- /dev/null
+++ b/tools/gui/resources/template-project/build/build/build.scala
@@ -0,0 +1,7 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild {
+
+ override def dependencies = super.dependencies##plus##
+
+}
diff --git a/tools/gui/resources/template-project/src/main/scala/Main.scala b/tools/gui/resources/template-project/src/main/scala/Main.scala
new file mode 100644
index 0000000..48f8174
--- /dev/null
+++ b/tools/gui/resources/template-project/src/main/scala/Main.scala
@@ -0,0 +1,5 @@
+##package##object Main {
+ def main(args: Array[String]): Unit = {
+ println("Hello World")
+ }
+}
diff --git a/tools/gui/resources/web/favicon.ico b/tools/gui/resources/web/favicon.ico
new file mode 100644
index 0000000..2ec8115
--- /dev/null
+++ b/tools/gui/resources/web/favicon.ico
Binary files differ
diff --git a/tools/gui/resources/web/index.html b/tools/gui/resources/web/index.html
new file mode 100644
index 0000000..9ad45a5
--- /dev/null
+++ b/tools/gui/resources/web/index.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="author" content="tim-zh">
+ <title>CBT</title>
+ <link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<div id="popup">
+ <div onclick="hidePopup()">×</div>
+ <table id="popup-table"></table>
+</div>
+<div class="container">
+
+ <h1>CBT bootstrap</h1>
+
+ <div id="cwd" class="entry"><span>current dir:</span> </div>
+ <div class="entry"><input type="text" id="name" placeholder="project name"></div>
+ <div class="entry"><input type="text" id="package" placeholder="default package"></div>
+ <hr>
+
+ <div class="entry"><input type="text" id="query" placeholder="add maven dependency" onkeyup="handleSearchInput(event)"></div>
+ <button class="small-btn" onclick="search()">search</button>
+ <div id="dependencies"></div>
+ <hr>
+
+ <input type="checkbox" id="readme-flag"><label for="readme-flag">readme.md</label>
+ <input type="checkbox" id="dotty-flag"><label for="dotty-flag">dotty</label>
+ <input type="checkbox" id="uberJar-flag"><label for="uberJar-flag">uber jar</label>
+ <input type="checkbox" id="wartremover-flag"><label for="wartremover-flag">wartremover</label>
+ <hr>
+
+ <button id="create-project" onclick="createProject()">create project</button>
+
+</div>
+<script src="jquery-3.1.1.min.js"></script>
+<script src="main.js"></script>
+</body>
+</html>
diff --git a/tools/gui/resources/web/jquery-3.1.1.min.js b/tools/gui/resources/web/jquery-3.1.1.min.js
new file mode 100644
index 0000000..4c5be4c
--- /dev/null
+++ b/tools/gui/resources/web/jquery-3.1.1.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */
+!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=r.isArray(d)))?(e?(e=!1,f=c&&r.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d="";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&"parentNode"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e);return!1}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}return!1}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g="string"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError("Thenable self-resolution");j=a&&("object"==typeof a||"function"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,M,e),g(f,c,N,e)):(f++,j.call(a,g(f,c,M,e),g(f,c,N,e),g(f,c,M,c.notifyWith))):(d!==M&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),
+a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},T=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function U(){this.expando=r.expando+U.uid++}U.uid=1,U.prototype={cache:function(a){var b=a[this.expando];return b||(b={},T(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){r.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(K)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var V=new U,W=new U,X=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Y=/[A-Z]/g;function Z(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:X.test(a)?JSON.parse(a):a)}function $(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Y,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=Z(c)}catch(e){}W.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return W.hasData(a)||V.hasData(a)},data:function(a,b,c){return W.access(a,b,c)},removeData:function(a,b){W.remove(a,b)},_data:function(a,b,c){return V.access(a,b,c)},_removeData:function(a,b){V.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=W.get(f),1===f.nodeType&&!V.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),$(f,d,e[d])));V.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){W.set(this,a)}):S(this,function(b){var c;if(f&&void 0===b){if(c=W.get(f,a),void 0!==c)return c;if(c=$(f,a),void 0!==c)return c}else this.each(function(){W.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=V.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var _=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,aa=new RegExp("^(?:([+-])=|)("+_+")([a-z%]*)$","i"),ba=["Top","Right","Bottom","Left"],ca=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},da=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function ea(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&aa.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var fa={};function ga(a){var b,c=a.ownerDocument,d=a.nodeName,e=fa[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e="block"),fa[d]=e,e)}function ha(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?("none"===c&&(e[f]=V.get(d,"display")||null,e[f]||(d.style.display="")),""===d.style.display&&ca(d)&&(e[f]=ga(d))):"none"!==c&&(e[f]="none",V.set(d,"display",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ha(this,!0)},hide:function(){return ha(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){ca(this)?r(this).show():r(this).hide()})}});var ia=/^(?:checkbox|radio)$/i,ja=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c<d;c++)V.set(a[c],"globalEval",!b||V.get(b[c],"globalEval"))}var oa=/<|&#?\w+;/;function pa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if("object"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(oa.test(f)){g=g||l.appendChild(b.createElement("div")),h=(ja.exec(f)||["",""])[1].toLowerCase(),i=la[h]||la._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g,h=[],i=b.delegateCount,j=a.target;if(i&&j.nodeType&&!("click"===a.type&&a.button>=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c<i;c++)d=b[c],e=d.selector+" ",void 0===g[e]&&(g[e]=d.needsContext?r(e,this).index(j)>-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i<b.length&&h.push({elem:j,handlers:b.slice(i)}),h},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==wa()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===wa()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&r.nodeName(this,"input"))return this.click(),!1},_default:function(a){return r.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ua:va,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:va,isPropagationStopped:va,isImmediatePropagationStopped:va,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ua,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ua,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ua,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&ra.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&sa.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return xa(this,a,b,c,d)},one:function(a,b,c,d){return xa(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=va),this.each(function(){r.event.remove(this,a,c,b)})}});var ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/<script|<style|<link/i,Aa=/checked\s*(?:[^=]|=\s*.checked.)/i,Ba=/^true\/(.*)/,Ca=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}W.hasData(a)&&(h=W.access(a),i=r.extend({},h),W.set(b,i))}}function Ha(a,b){var c=b.nodeName.toLowerCase();"input"===c&&ia.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function Ia(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,ma(j,"script"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Fa),l=0;l<i;l++)j=h[l],ka.test(j.type||"")&&!V.access(j,"globalEval")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Ca,""),k))}return a}function Ja(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(ma(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&na(ma(d,"script")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(ya,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d<e;d++)Ha(f[d],g[d]);if(b)if(c)for(f=f||ma(a),g=g||ma(h),d=0,e=f.length;d<e;d++)Ga(f[d],g[d]);else Ga(a,h);return g=ma(h,"script"),g.length>0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(ma(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ia(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(ma(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var Ka=/^margin/,La=new RegExp("^("+_+")(?!px)[a-z%]+$","i"),Ma=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",i.innerHTML="",qa.appendChild(h);var b=a.getComputedStyle(i);c="1%"!==b.top,g="2px"===b.marginLeft,e="4px"===b.width,i.style.marginRight="50%",f="4px"===b.marginRight,qa.removeChild(h),i=null}}var c,e,f,g,h=d.createElement("div"),i=d.createElement("div");i.style&&(i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",o.clearCloneStyle="content-box"===i.style.backgroundClip,h.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Na(a,b,c){var d,e,f,g,h=a.style;return c=c||Ma(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&La.test(g)&&Ka.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Oa(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Pa=/^(none|table(?!-c[ea]).+)/,Qa={position:"absolute",visibility:"hidden",display:"block"},Ra={letterSpacing:"0",fontWeight:"400"},Sa=["Webkit","Moz","ms"],Ta=d.createElement("div").style;function Ua(a){if(a in Ta)return a;var b=a[0].toUpperCase()+a.slice(1),c=Sa.length;while(c--)if(a=Sa[c]+b,a in Ta)return a}function Va(a,b,c){var d=aa.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Wa(a,b,c,d,e){var f,g=0;for(f=c===(d?"border":"content")?4:"width"===b?1:0;f<4;f+=2)"margin"===c&&(g+=r.css(a,c+ba[f],!0,e)),d?("content"===c&&(g-=r.css(a,"padding"+ba[f],!0,e)),"margin"!==c&&(g-=r.css(a,"border"+ba[f]+"Width",!0,e))):(g+=r.css(a,"padding"+ba[f],!0,e),"padding"!==c&&(g+=r.css(a,"border"+ba[f]+"Width",!0,e)));return g}function Xa(a,b,c){var d,e=!0,f=Ma(a),g="border-box"===r.css(a,"boxSizing",!1,f);if(a.getClientRects().length&&(d=a.getBoundingClientRect()[b]),d<=0||null==d){if(d=Na(a,b,f),(d<0||null==d)&&(d=a.style[b]),La.test(d))return d;e=g&&(o.boxSizingReliable()||d===a.style[b]),d=parseFloat(d)||0}return d+Wa(a,b,c||(g?"border":"content"),e,f)+"px"}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Na(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=a.style;return b=r.cssProps[h]||(r.cssProps[h]=Ua(h)||h),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=aa.exec(c))&&e[1]&&(c=ea(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(r.cssNumber[h]?"":"px")),o.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b);return b=r.cssProps[h]||(r.cssProps[h]=Ua(h)||h),g=r.cssHooks[b]||r.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Na(a,b,d)),"normal"===e&&b in Ra&&(e=Ra[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each(["height","width"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Pa.test(r.css(a,"display"))||a.getClientRects().length&&a.getBoundingClientRect().width?Xa(a,b,d):da(a,Qa,function(){return Xa(a,b,d)})},set:function(a,c,d){var e,f=d&&Ma(a),g=d&&Wa(a,b,d,"border-box"===r.css(a,"boxSizing",!1,f),f);return g&&(e=aa.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=r.css(a,b)),Va(a,c,g)}}}),r.cssHooks.marginLeft=Oa(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Na(a,"marginLeft"))||a.getBoundingClientRect().left-da(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px"}),r.each({margin:"",padding:"",border:"Width"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];d<4;d++)e[a+ba[d]+b]=f[d]||f[d-2]||f[0];return e}},Ka.test(a)||(r.cssHooks[a+b].set=Va)}),r.fn.extend({css:function(a,b){return S(this,function(a,b,c){var d,e,f={},g=0;if(r.isArray(b)){for(d=Ma(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f<g;f++)if(d=e[f].call(c,b,a))return d}function fb(a,b,c){var d,e,f,g,h,i,j,k,l="width"in b||"height"in b,m=this,n={},o=a.style,p=a.nodeType&&ca(a),q=V.get(a,"fxshow");c.queue||(g=r._queueHooks(a,"fx"),null==g.unqueued&&(g.unqueued=0,h=g.empty.fire,g.empty.fire=function(){g.unqueued||h()}),g.unqueued++,m.always(function(){m.always(function(){g.unqueued--,r.queue(a,"fx").length||g.empty.fire()})}));for(d in b)if(e=b[d],_a.test(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}n[d]=q&&q[d]||r.style(a,d)}if(i=!r.isEmptyObject(b),i||!r.isEmptyObject(n)){l&&1===a.nodeType&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=q&&q.display,null==j&&(j=V.get(a,"display")),k=r.css(a,"display"),"none"===k&&(j?k=j:(ha([a],!0),j=a.style.display||j,k=r.css(a,"display"),ha([a]))),("inline"===k||"inline-block"===k&&null!=j)&&"none"===r.css(a,"float")&&(i||(m.done(function(){o.display=j}),null==j&&(k=o.display,j="none"===k?"":k)),o.display="inline-block")),c.overflow&&(o.overflow="hidden",m.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]})),i=!1;for(d in n)i||(q?"hidden"in q&&(p=q.hidden):q=V.access(a,"fxshow",{display:j}),f&&(q.hidden=!p),p&&ha([a],!0),m.done(function(){p||ha([a]),V.remove(a,"fxshow");for(d in n)r.style(a,d,n[d])})),i=eb(p?q[d]:0,d,m),d in q||(q[d]=i.start,p&&(i.end=i.start,i.start=0))}}function gb(a,b){var c,d,e,f,g;for(c in a)if(d=r.camelCase(c),e=b[d],f=a[c],r.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=r.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function hb(a,b,c){var d,e,f=0,g=hb.prefilters.length,h=r.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Za||cb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;g<i;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),f<1&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:r.extend({},b),opts:r.extend(!0,{specialEasing:{},easing:r.easing._default},c),originalProperties:b,originalOptions:c,startTime:Za||cb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=r.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;c<d;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(gb(k,j.opts.specialEasing);f<g;f++)if(d=hb.prefilters[f].call(j,a,k,j.opts))return r.isFunction(d.stop)&&(r._queueHooks(j.elem,j.opts.queue).stop=r.proxy(d.stop,d)),d;return r.map(k,eb,j),r.isFunction(j.opts.start)&&j.opts.start.call(a,j),r.fx.timer(r.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}r.Animation=r.extend(hb,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return ea(c.elem,a,aa.exec(b),c),c}]},tweener:function(a,b){r.isFunction(a)?(b=a,a=["*"]):a=a.match(K);for(var c,d=0,e=a.length;d<e;d++)c=a[d],hb.tweeners[c]=hb.tweeners[c]||[],hb.tweeners[c].unshift(b)},prefilters:[fb],prefilter:function(a,b){b?hb.prefilters.unshift(a):hb.prefilters.push(a)}}),r.speed=function(a,b,c){var e=a&&"object"==typeof a?r.extend({},a):{complete:c||!c&&b||r.isFunction(a)&&a,duration:a,easing:c&&b||b&&!r.isFunction(b)&&b};return r.fx.off||d.hidden?e.duration=0:"number"!=typeof e.duration&&(e.duration in r.fx.speeds?e.duration=r.fx.speeds[e.duration]:e.duration=r.fx.speeds._default),null!=e.queue&&e.queue!==!0||(e.queue="fx"),e.old=e.complete,e.complete=function(){r.isFunction(e.old)&&e.old.call(this),e.queue&&r.dequeue(this,e.queue)},e},r.fn.extend({fadeTo:function(a,b,c,d){return this.filter(ca).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=r.isEmptyObject(a),f=r.speed(b,c,d),g=function(){var b=hb(this,r.extend({},a),f);(e||V.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=r.timers,g=V.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&ab.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||r.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=V.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=r.timers,g=d?d.length:0;for(c.finish=!0,r.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;b<g;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),r.each(["toggle","show","hide"],function(a,b){var c=r.fn[b];r.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(db(b,!0),a,d,e)}}),r.each({slideDown:db("show"),slideUp:db("hide"),slideToggle:db("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){r.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),r.timers=[],r.fx.tick=function(){var a,b=0,c=r.timers;for(Za=r.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||r.fx.stop(),Za=void 0},r.fx.timer=function(a){r.timers.push(a),a()?r.fx.start():r.timers.pop()},r.fx.interval=13,r.fx.start=function(){$a||($a=a.requestAnimationFrame?a.requestAnimationFrame(bb):a.setInterval(r.fx.tick,r.fx.interval))},r.fx.stop=function(){a.cancelAnimationFrame?a.cancelAnimationFrame($a):a.clearInterval($a),$a=null},r.fx.speeds={slow:600,fast:200,_default:400},r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",o.checkOn=""!==a.value,o.optSelected=c.selected,a=d.createElement("input"),a.value="t",a.type="radio",o.radioValue="t"===a.value}();var ib,jb=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return S(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)),
+void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!r.nodeName(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Qb=[],Rb=/(=)\?(?=&|$)|\?\?/;r.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Qb.pop()||r.expando+"_"+rb++;return this[a]=!0,a}}),r.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Rb.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Rb.test(b.data)&&"data");if(h||"jsonp"===b.dataTypes[0])return e=b.jsonpCallback=r.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Rb,"$1"+e):b.jsonp!==!1&&(b.url+=(sb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||r.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?r(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Qb.push(e)),g&&r.isFunction(f)&&f(g[0]),g=f=void 0}),"script"}),o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if("string"!=typeof a)return[];"boolean"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(""),e=b.createElement("base"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=B.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=pa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.fn.load=function(a,b,c){var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=mb(a.slice(h)),a=a.slice(0,h)),r.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&r.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?r("<div>").append(r.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},r.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){r.fn[b]=function(a){return this.on(b,a)}}),r.expr.pseudos.animated=function(a){return r.grep(r.timers,function(b){return a===b.elem}).length};function Sb(a){return r.isWindow(a)?a:9===a.nodeType&&a.defaultView}r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,"position"),l=r(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=r.css(a,"top"),i=r.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),d.width||d.height?(e=f.ownerDocument,c=Sb(e),b=e.documentElement,{top:d.top+c.pageYOffset-b.clientTop,left:d.left+c.pageXOffset-b.clientLeft}):d):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===r.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),r.nodeName(a[0],"html")||(d=a.offset()),d={top:d.top+r.css(a[0],"borderTopWidth",!0),left:d.left+r.css(a[0],"borderLeftWidth",!0)}),{top:b.top-d.top-r.css(c,"marginTop",!0),left:b.left-d.left-r.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===r.css(a,"position"))a=a.offsetParent;return a||qa})}}),r.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;r.fn[a]=function(d){return S(this,function(a,d,e){var f=Sb(a);return void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each(["top","left"],function(a,b){r.cssHooks[b]=Oa(o.pixelPosition,function(a,c){if(c)return c=Na(a,b),La.test(c)?r(a).position()[b]+"px":c})}),r.each({Height:"height",Width:"width"},function(a,b){r.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||"boolean"!=typeof e),h=c||(e===!0||f===!0?"margin":"border");return S(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf("outer")?b["inner"+a]:b.document.documentElement["client"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body["scroll"+a],f["scroll"+a],b.body["offset"+a],f["offset"+a],f["client"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),r.parseJSON=JSON.parse,"function"==typeof define&&define.amd&&define("jquery",[],function(){return r});var Tb=a.jQuery,Ub=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=Ub),b&&a.jQuery===r&&(a.jQuery=Tb),r},b||(a.jQuery=a.$=r),r});
diff --git a/tools/gui/resources/web/main.js b/tools/gui/resources/web/main.js
new file mode 100644
index 0000000..b4fe38b
--- /dev/null
+++ b/tools/gui/resources/web/main.js
@@ -0,0 +1,129 @@
+Notification.requestPermission();
+
+$.get("/api/cwd").done(cwd => {
+ $("#cwd").append(cwd[0]);
+}).fail(e => notifyFail(e));
+
+document.body.onkeydown = function (event) {
+ if (event.keyCode == 27)
+ hidePopup();
+};
+
+$("#create-project")[0].disabled = false;
+
+let dependencies = [];
+
+function getFlags() {
+ return "readme/" + $("#readme-flag")[0].checked
+ + " dotty/" + $("#dotty-flag")[0].checked
+ + " uberJar/" + $("#uberJar-flag")[0].checked
+ + " wartremover/" + $("#wartremover-flag")[0].checked;
+}
+
+function createProject() {
+ let button = $("#create-project")[0];
+ let buttonText = button.innerHTML;
+ button.innerHTML = "...";
+ button.blur();
+ button.disabled = true;
+ $.post("/api/project", {
+ name: $("#name").val(),
+ pack: $("#package").val(),
+ dependencies: dependencies.length == 0 ? "" :
+ dependencies.map(l => l.group + "/" + l.artifact + "/" + l.version).reduce((a, b) => a + " " + b),
+ flags: getFlags()
+ }).done(() => {
+ notify("Done.");
+ }).fail(e =>
+ notifyFail(e)
+ ).always(() => {
+ button.innerHTML = buttonText;
+ button.disabled = false;
+ });
+}
+
+function handleSearchInput(event) {
+ if (event.keyCode == 13)
+ search();
+}
+function search() {
+ let query = $("#query").val();
+ if (query) {
+ $.get("/api/dependency", { query: query }).done(data => {
+ let entries = data.response.docs.map(x => ({
+ group: x.g,
+ artifact: x.a
+ }));
+ showPopup(makeRowsFrom(entries, selectDependency));
+ }).fail(e => notifyFail(e));
+ document.activeElement.blur();
+ }
+}
+function selectDependency(selected) {
+ $.get("/api/dependency/version", { group: selected.group, artifact: selected.artifact }).done(data => {
+ let versions = data.response.docs.map(x => ({ version: x.v }));
+ $("#popup-table").html(makeRowsFrom(versions, function (version) {
+ selectDependencyVersion(selected.group, selected.artifact, version.version);
+ }));
+ }).fail(e => notifyFail(e));
+}
+function selectDependencyVersion(group, artifact, version) {
+ let dependency = { group: group, artifact: artifact, version: version };
+ dependencies.push(dependency);
+ let scalaName = artifact.match(/^(.+)_\d+\.\d+$/);
+ let name = scalaName && scalaName[1] ? scalaName[1] : artifact;
+ var depDiv = $("<div class='entry removable'>" + name + " " + version + "</div>");
+ depDiv.click(function () {
+ removeDependency(dependency, depDiv);
+ });
+ $("#dependencies").append(depDiv);
+ hidePopup();
+}
+function removeDependency(d, div) {
+ dependencies.splice(dependencies.indexOf(d), 1);
+ div.remove();
+}
+
+function showPopup(contents) {
+ $("#popup").show();
+ $("#popup-table").html(contents);
+}
+function hidePopup() {
+ $("#popup").hide();
+ $("#popup-table").html("");
+}
+
+function makeRowsFrom(results, rowAction) {
+ if (results.length == 0) {
+ return [];
+ } else {
+ let rows = [];
+ let row = $("<tr></tr>");
+ rows.push(row);
+ let fields = [];
+ for (let field in results[0])
+ if (results[0].hasOwnProperty(field)) {
+ fields.push(field);
+ $("<td>" + field + "</td>").appendTo(row);
+ }
+ results.forEach(result => {
+ let row = $("<tr></tr>");
+ row.click(function () {
+ rowAction(result);
+ });
+ rows.push(row);
+ fields.forEach(field => $("<td>" + result[field] + "</td>").appendTo(row));
+ });
+ return rows;
+ }
+}
+
+function notify(text, title) {
+ new Notification(title || "", { body: text });
+}
+
+function notifyFail(e) {
+ let head = e.status == 0 ? "Error" : e.status + " " + e.statusText;
+ let body = e.status == 0 ? "No response from UI server." : e.responseText;
+ notify(body, head);
+}
diff --git a/tools/gui/resources/web/styles.css b/tools/gui/resources/web/styles.css
new file mode 100644
index 0000000..6aa45e9
--- /dev/null
+++ b/tools/gui/resources/web/styles.css
@@ -0,0 +1,196 @@
+* {
+ outline: none;
+}
+
+body {
+ font-family: 'proxima nova', sans-serif;
+ font-size: 2em;
+ background: #002B36;
+ color: #fff;
+}
+
+::-moz-selection {
+ background-color: #fff;
+ color: #073642;
+}
+
+::selection {
+ background-color: #fff;
+ color: #073642;
+}
+
+h1 {
+ font-weight: normal;
+ margin: 8%;
+}
+
+hr {
+ border: none;
+ margin: 0.8em 0;
+ border-bottom: transparent solid 1px;
+}
+
+button, .small-btn {
+ background: #dc322f;
+ color: #fff;
+ font-size: 1em;
+ padding: 0.2em;
+ border: transparent solid 0.1em;
+ border-radius: 1em;
+ text-transform: uppercase;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.small-btn {
+ font-size: 0.8em;
+ padding: 0 1em;
+}
+
+button:hover {
+ background: #073642;
+ border: #fff solid 0.1em;
+}
+
+button:active {
+ background: #dc322f;
+}
+
+button:disabled {
+ background: transparent;
+ cursor: default;
+}
+
+input[type="text"] {
+ background: #073642;
+ color: #fff;
+ border: none;
+ width: 33%;
+ font-size: 1em;
+}
+
+input[type="checkbox"] {
+ display: none;
+}
+
+input[type="checkbox"] + label {
+ position: relative;
+ padding-left: 1.6em;
+ cursor: pointer;
+ display: inline-block;
+}
+
+input[type="checkbox"] + label:before, input[type="checkbox"] + label:after {
+ content: '';
+ margin-top: 0.25em;
+ border-radius: 1em;
+ position: absolute;
+ left: 0;
+ height: 0.8em;
+ transition: all .2s;
+}
+
+input[type="checkbox"] + label:before {
+ width: 1.4em;
+ background: #073642;
+}
+
+input[type="checkbox"] + label:after {
+ width: 0.8em;
+ background: #fff;
+ border: #073642 solid 0.15em;
+ box-sizing: border-box;
+}
+
+input[type="checkbox"] + label:hover:before, input[type="checkbox"]:checked + label:before {
+ background-color: #dc322f;
+}
+
+input[type="checkbox"] + label:hover:after {
+ border-color: #dc322f;
+}
+
+input[type="checkbox"]:checked + label:after {
+ left: 0.6em;
+ border-color: #dc322f;
+}
+
+.container {
+ margin: 0 8%;
+ text-align: center;
+}
+
+button, .entry {
+ margin-bottom: 0.5em;
+}
+
+.entry span:first-child {
+ color: #27b3d9;
+}
+
+.removable:hover {
+ text-decoration: line-through;
+ cursor: pointer;
+}
+
+#popup {
+ position: fixed;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
+ background: #073642;
+ width: 84%;
+ max-height: 80%;
+ overflow-y: scroll;
+ text-align: center;
+ display: none;
+ z-index: 2;
+}
+
+#popup div:first-child {
+ position: fixed;
+ padding: 0 0.2em;
+ text-align: right;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+#popup div:first-child:hover {
+ color: #dc322f;
+}
+
+#popup table {
+ margin: 2%;
+ width: 96%;
+ border-collapse: collapse;
+}
+
+#popup td {
+ border-right: #fff solid 1px;
+}
+
+#popup td:last-child {
+ border-right: none;
+}
+
+#popup tr {
+ cursor: pointer;
+}
+
+#popup tr:hover {
+ color: #dc322f;
+}
+
+#popup tr:nth-child(even) {
+ background: #002B36;
+}
+
+#popup tr:first-child {
+ background: #fff;
+ color: #002B36;
+ cursor: default;
+}
+
+#popup tr:first-child:hover {
+ color: #002B36;
+}
diff --git a/tools/gui/src/JettyServer.scala b/tools/gui/src/JettyServer.scala
new file mode 100644
index 0000000..d6024c2
--- /dev/null
+++ b/tools/gui/src/JettyServer.scala
@@ -0,0 +1,67 @@
+import java.net.MalformedURLException
+import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
+
+import org.eclipse.jetty.server.{Request, Server}
+import org.eclipse.jetty.server.handler._
+
+import scala.util.{Failure, Success, Try}
+
+abstract class JettyServer(port: Int, staticFilesUrl: String) {
+
+ private val handlerOfStatic = {
+ val handler = new ContextHandler("/")
+ val resourceHandler = new ResourceHandler
+ resourceHandler.setDirectoriesListed(true)
+ resourceHandler.setResourceBase(staticFilesUrl)
+ handler.setHandler(resourceHandler)
+ handler
+ }
+
+ private val handlerOfApi = {
+ val handlerWrapper = new ContextHandler("/api/")
+ val handler = new AbstractHandler {
+ override def handle(target: String,
+ baseRequest: Request,
+ request: HttpServletRequest,
+ response: HttpServletResponse) = {
+ response.setContentType("application/json")
+ response.setCharacterEncoding("UTF-8")
+
+ route(request.getMethod, target, request.getParameter) match {
+ case Success(result) =>
+ response.getWriter.write(result)
+ case Failure(e: MalformedURLException) =>
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND)
+ response.getWriter.write(e.getMessage)
+ case Failure(e) =>
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
+ response.getWriter.write(s"${e.getClass.getName}: ${e.getMessage}")
+ }
+
+ baseRequest.setHandled(true)
+ }
+ }
+ handlerWrapper.setHandler(handler)
+ handlerWrapper
+ }
+
+ private val server = {
+ val s = new Server(port)
+ val handlers = new HandlerCollection
+ handlers.setHandlers(Array(handlerOfStatic, handlerOfApi, new DefaultHandler))
+ s.setHandler(handlers)
+ s
+ }
+
+ def start() = {
+ server.start()
+ println(s"UI server started at localhost:$port")
+ }
+
+ def stop() = {
+ server.stop()
+ println("UI server stopped.")
+ }
+
+ def route(method: String, path: String, param: String => String): Try[String]
+}
diff --git a/tools/gui/src/Main.scala b/tools/gui/src/Main.scala
new file mode 100644
index 0000000..d7a9f7d
--- /dev/null
+++ b/tools/gui/src/Main.scala
@@ -0,0 +1,85 @@
+import java.io.{File, IOException}
+import java.net.MalformedURLException
+
+import scala.io.Source
+import scala.util.{Failure, Success, Try}
+import scalaj.http.Http
+
+object Main {
+
+ private val maven_host = "search.maven.org"
+ private val cbt_home = System.getenv("CBT_HOME")
+
+ implicit class StringExtensionMethods(str: String) {
+ def /(s: String): String = str + File.separator + s
+ }
+
+ val uiPort = 9080
+
+ def main(args: Array[String]) = launchUi(new File(args(0)), args(1))
+
+ def launchUi(projectDirectory: File, scalaMajorVersion: String): Unit = {
+ val staticBase = new File(cbt_home / "tools" / "gui" / "resources" / "web").toURI.toURL.toExternalForm
+ val server = new JettyServer(uiPort, staticBase) {
+ override def route(method: String, path: String, param: String => String) = (method, path) match {
+ case ("GET", "/cwd") =>
+ Success(s"""["$projectDirectory"]""")
+ case ("POST", "/project") =>
+ val name = param("name")
+ val defaultPackage = param("pack")
+ val dependencies = param("dependencies")
+ val flags = param("flags")
+ handleIoException {
+ new ProjectBuilder(name, defaultPackage, dependencies, flags, projectDirectory, scalaMajorVersion).build()
+ Success("[]")
+ }
+ case ("GET", "/dependency") =>
+ val query = param("query")
+ handleIoException(handleMavenBadResponse(searchDependency(query)))
+ case ("GET", "/dependency/version") =>
+ val group = param("group")
+ val artifact = param("artifact")
+ handleIoException(handleMavenBadResponse(searchDependencyVersion(group, artifact)))
+ case _ =>
+ Failure(new MalformedURLException(s"Incorrect path: $path"))
+ }
+ }
+ server.start()
+ java.awt.Desktop.getDesktop.browse(new java.net.URI(s"http://localhost:$uiPort/"))
+
+ println("Press Enter to stop UI server.")
+ while (Source.stdin.getLines().next().nonEmpty) {}
+ server.stop()
+ }
+
+ private def searchDependency(query: String) = {
+ Http(s"http://$maven_host/solrsearch/select")
+ .param("q", query)
+ .param("rows", "30")
+ .param("wt", "json")
+ .asString.body
+ }
+
+ private def searchDependencyVersion(group: String, artifact: String) = {
+ val query = s"""q=g:"$group"+AND+a:"$artifact""""
+ Http(s"http://$maven_host/solrsearch/select?" + query)
+ .param("rows", "30")
+ .param("wt", "json")
+ .param("core", "gav")
+ .asString.body
+ }
+
+ private def handleIoException(f: => Try[String]) = try f catch {
+ case e: IOException =>
+ e.printStackTrace()
+ Failure(e)
+ }
+
+ private def handleMavenBadResponse(result: String) = {
+ if (result.startsWith("{"))
+ Success(result)
+ else
+ Failure(new Exception(s"Bad response from $maven_host: $result"))
+ }
+
+}
diff --git a/tools/gui/src/ProjectBuilder.scala b/tools/gui/src/ProjectBuilder.scala
new file mode 100644
index 0000000..ceba376
--- /dev/null
+++ b/tools/gui/src/ProjectBuilder.scala
@@ -0,0 +1,149 @@
+import java.io.File
+import java.nio.file._
+
+import Main.StringExtensionMethods
+
+import scala.io.Source
+
+class ProjectBuilder(
+ name: String,
+ defaultPackage: String,
+ dependencyString: String,
+ flagsString: String,
+ location: File,
+ scalaMajorVersion: String
+) {
+
+ private val newLine = System.lineSeparator()
+ private val blankLine = newLine + newLine
+ private val templateDir = System.getenv("CBT_HOME") / "tools" / "gui" / "resources" / "template-project"
+ private val projectPath = location.getPath / name
+ private val buildDir = projectPath / "build"
+ private val mainDir = projectPath / "src" / "main" / "scala" / defaultPackage.split("\\.").reduce(_ / _)
+ private val testDir = projectPath / "src" / "test" / "scala" / defaultPackage.split("\\.").reduce(_ / _)
+
+ private val dependencies =
+ if (dependencyString.isEmpty)
+ ""
+ else {
+ def parseDependencies(input: String): Seq[Dependency] = {
+ input.split(" ").map { x =>
+ val xs = x.split("/")
+ Dependency(xs(0), xs(1), xs(2))
+ }
+ }
+
+ val list = parseDependencies(dependencyString)
+ val str = list.map(" " ++ _.serialized).mkString(s",$newLine")
+ s""" override def dependencies = {
+ | super.dependencies ++ Resolver(mavenCentral).bind(
+ |$str
+ | )
+ | }""".stripMargin
+ }
+
+ private val flags = {
+ val map = flagsString.split(" ").map { x =>
+ val Array(a, b) = x.split("/")
+ (a, b)
+ }.toMap
+ Flags(map("readme") == "true", map("dotty") == "true", map("uberJar") == "true", map("wartremover") == "true")
+ }
+
+ private val plugins = {
+ var content = ""
+ if (flags.dotty)
+ content += " with Dotty"
+ if (flags.uberJar)
+ content += " with UberJar"
+ if (flags.wartremover)
+ content += " with WartRemover"
+ content
+ }
+
+ private val buildBuildDependencies = {
+ var content = ""
+ if (flags.uberJar)
+ content += " :+ plugins.uberJar"
+ if (flags.wartremover)
+ content += " :+ plugins.wartremover"
+ content
+ }
+
+ def build(): Unit = {
+ new File(buildDir).mkdirs()
+ new File(mainDir).mkdirs()
+ new File(testDir).mkdirs()
+ addBuild()
+ if (buildBuildDependencies.nonEmpty)
+ addBuildBuild()
+ addMain()
+ addReadme(flags)
+ }
+
+ private def writeTemplate(templatePath: String, targetPath: String, replacements: (String, String, String)*) = {
+ var content = Source.fromFile(templatePath).mkString
+ for (replacement <- replacements)
+ if (replacement._1.nonEmpty)
+ content = content.replace(replacement._3, replacement._2)
+ else
+ content = content.replace(replacement._3, "")
+ write(new File(targetPath), content, StandardOpenOption.CREATE_NEW)
+ }
+
+ private def addBuild() =
+ writeTemplate(
+ templateDir / "build" / "build.scala",
+ buildDir / "build.scala",
+ (name, s""" override def projectName = "$name"$blankLine""", "##projectName##"),
+ (dependencyString, dependencies + blankLine, "##dependencies##"),
+ (plugins, plugins, "##with##")
+ )
+
+ private def addBuildBuild() =
+ writeTemplate(
+ templateDir / "build" / "build" / "build.scala",
+ buildDir / "build" / "build.scala",
+ (buildBuildDependencies, buildBuildDependencies, "##plus##")
+ )
+
+ private def addMain() =
+ writeTemplate(
+ templateDir / "src" / "main" / "scala" / "Main.scala",
+ mainDir / "Main.scala",
+ (defaultPackage, s"package $defaultPackage$blankLine", "##package##")
+ )
+
+ private def addReadme(flags: Flags) = {
+ if (flags.readme) {
+ val content =
+ if (name.nonEmpty)
+ s"# $name$blankLine"
+ else
+ ""
+ write(new File(projectPath / "readme.md"), content, StandardOpenOption.CREATE_NEW)
+ }
+ }
+
+ private case class Dependency(group: String, artifact: String, version: String) {
+ val nameVersion = """^(.+)_(\d+\.\d+)$""".r
+ val (name, scalaVersion) = artifact match {
+ case nameVersion(n, v) => (n, Some(v))
+ case str => (str, None)
+ }
+
+ def serialized =
+ if (scalaVersion.contains(scalaMajorVersion))
+ s"""ScalaDependency("$group", "$name", "$version")"""
+ else
+ s"""MavenDependency("$group", "$artifact", "$version")"""
+ }
+
+ private def write(file: File, content: String, option: OpenOption) = {
+ file.getParentFile.mkdirs()
+ Files.write(file.toPath, content.getBytes, option)
+ }
+
+ private case class Flags(readme: Boolean, dotty: Boolean, uberJar: Boolean, wartremover: Boolean)
+
+}