From 3715c4f8bbf0440315d888ec4cfb2f6694268104 Mon Sep 17 00:00:00 2001 From: Niklaus Giger Date: Tue, 17 Apr 2012 21:34:51 +0200 Subject: [PATCH] Braid: Add mirror 'buildr' at '7a9c78a' --- .braids | 8 + buildr/.gitignore | 21 + buildr/.rvmrc | 27 + buildr/CHANGELOG | 1384 +++++++++++++++++ buildr/Gemfile | 4 + buildr/LICENSE | 176 +++ buildr/NOTICE | 26 + buildr/README.rdoc | 134 ++ buildr/Rakefile | 47 + buildr/_buildr | 35 + buildr/_jbuildr | 35 + buildr/addon/buildr/antlr.rb | 61 + buildr/addon/buildr/bnd.rb | 149 ++ buildr/addon/buildr/checkstyle.rb | 201 +++ buildr/addon/buildr/cobertura.rb | 21 + buildr/addon/buildr/drb.rb | 279 ++++ buildr/addon/buildr/emma.rb | 21 + buildr/addon/buildr/findbugs.rb | 227 +++ buildr/addon/buildr/hibernate.rb | 145 ++ buildr/addon/buildr/javacc.rb | 81 + buildr/addon/buildr/javancss.rb | 155 ++ buildr/addon/buildr/jaxb_xjc.rb | 72 + buildr/addon/buildr/jdepend.rb | 174 +++ buildr/addon/buildr/jetty.rb | 243 +++ buildr/addon/buildr/jibx.rb | 85 + buildr/addon/buildr/nailgun.rb | 221 +++ buildr/addon/buildr/openjpa.rb | 84 + .../org/apache/buildr/BuildrNail$Main.class | Bin 0 -> 561 bytes .../buildr/org/apache/buildr/BuildrNail.class | Bin 0 -> 256 bytes .../buildr/org/apache/buildr/BuildrNail.java | 41 + .../org/apache/buildr/JettyWrapper$1.class | Bin 0 -> 223 bytes .../buildr/JettyWrapper$BuildrHandler.class | Bin 0 -> 3786 bytes .../org/apache/buildr/JettyWrapper.class | Bin 0 -> 1435 bytes .../org/apache/buildr/JettyWrapper.java | 144 ++ buildr/addon/buildr/pmd.rake | 166 ++ buildr/addon/buildr/protobuf.rb | 87 ++ buildr/addon/buildr/xmlbeans.rb | 88 ++ buildr/all-in-one/_buildr | 19 + buildr/all-in-one/buildr | 368 +++++ buildr/all-in-one/buildr.cmd | 1 + buildr/bin/buildr | 19 + buildr/buildr.buildfile | 58 + buildr/buildr.gemspec | 89 ++ buildr/doc/_config.yml | 1 + buildr/doc/_layouts/default.html | 91 ++ buildr/doc/_layouts/preface.html | 22 + buildr/doc/artifacts.textile | 217 +++ buildr/doc/building.textile | 276 ++++ buildr/doc/contributing.textile | 277 ++++ buildr/doc/css/default.css | 236 +++ buildr/doc/css/print.css | 101 ++ buildr/doc/css/syntax.css | 23 + buildr/doc/download.textile | 163 ++ buildr/doc/extending.textile | 212 +++ buildr/doc/images/1442160941-frontcover.jpg | Bin 0 -> 4759 bytes buildr/doc/images/asf-logo.gif | Bin 0 -> 5866 bytes buildr/doc/images/asf-logo.png | Bin 0 -> 15220 bytes buildr/doc/images/buildr-hires.png | Bin 0 -> 199068 bytes buildr/doc/images/buildr.png | Bin 0 -> 17916 bytes buildr/doc/images/favicon.png | Bin 0 -> 6018 bytes buildr/doc/images/growl-icon.tiff | Bin 0 -> 223396 bytes buildr/doc/images/note.png | Bin 0 -> 3381 bytes buildr/doc/images/project-structure.png | Bin 0 -> 106140 bytes buildr/doc/images/tip.png | Bin 0 -> 4223 bytes buildr/doc/images/zbuildr.png | Bin 0 -> 155565 bytes buildr/doc/images/zbuildr.tif | Bin 0 -> 3207784 bytes buildr/doc/index.textile | 84 + buildr/doc/installing.textile | 294 ++++ buildr/doc/languages.textile | 561 +++++++ buildr/doc/mailing_lists.textile | 29 + buildr/doc/more_stuff.textile | 1001 ++++++++++++ buildr/doc/packaging.textile | 628 ++++++++ buildr/doc/preface.textile | 54 + buildr/doc/projects.textile | 276 ++++ buildr/doc/quick_start.textile | 210 +++ buildr/doc/releasing.textile | 117 ++ buildr/doc/scripts/buildr-git.rb | 512 ++++++ buildr/doc/scripts/gitflow.rb | 296 ++++ buildr/doc/scripts/install-jruby.sh | 44 + buildr/doc/scripts/install-linux.sh | 73 + buildr/doc/scripts/install-osx.sh | 52 + buildr/doc/settings_profiles.textile | 287 ++++ buildr/doc/testing.textile | 247 +++ buildr/etc/KEYS | 189 +++ buildr/lib/buildr.rb | 107 ++ buildr/lib/buildr/clojure.rb | 32 + buildr/lib/buildr/clojure/shell.rb | 52 + buildr/lib/buildr/core/application.rb | 691 ++++++++ buildr/lib/buildr/core/build.rb | 509 ++++++ buildr/lib/buildr/core/cc.rb | 161 ++ buildr/lib/buildr/core/checks.rb | 249 +++ buildr/lib/buildr/core/common.rb | 146 ++ buildr/lib/buildr/core/compile.rb | 617 ++++++++ buildr/lib/buildr/core/doc.rb | 276 ++++ buildr/lib/buildr/core/environment.rb | 128 ++ buildr/lib/buildr/core/filter.rb | 399 +++++ buildr/lib/buildr/core/generate.rb | 193 +++ buildr/lib/buildr/core/help.rb | 114 ++ buildr/lib/buildr/core/jrebel.rb | 42 + buildr/lib/buildr/core/linux.rb | 29 + buildr/lib/buildr/core/osx.rb | 45 + buildr/lib/buildr/core/progressbar.rb | 161 ++ buildr/lib/buildr/core/project.rb | 971 ++++++++++++ buildr/lib/buildr/core/run.rb | 39 + buildr/lib/buildr/core/shell.rb | 132 ++ buildr/lib/buildr/core/test.rb | 837 ++++++++++ buildr/lib/buildr/core/transports.rb | 563 +++++++ buildr/lib/buildr/core/util.rb | 474 ++++++ buildr/lib/buildr/groovy.rb | 20 + buildr/lib/buildr/groovy/bdd.rb | 106 ++ buildr/lib/buildr/groovy/compiler.rb | 153 ++ buildr/lib/buildr/groovy/doc.rb | 73 + buildr/lib/buildr/groovy/shell.rb | 55 + buildr/lib/buildr/ide/eclipse.rb | 424 +++++ buildr/lib/buildr/ide/eclipse/java.rb | 49 + buildr/lib/buildr/ide/eclipse/plugin.rb | 67 + buildr/lib/buildr/ide/eclipse/scala.rb | 64 + buildr/lib/buildr/ide/idea.rb | 812 ++++++++++ buildr/lib/buildr/java/ant.rb | 90 ++ buildr/lib/buildr/java/bdd.rb | 345 ++++ buildr/lib/buildr/java/cobertura.rb | 303 ++++ buildr/lib/buildr/java/commands.rb | 249 +++ buildr/lib/buildr/java/compiler.rb | 128 ++ buildr/lib/buildr/java/deprecated.rb | 137 ++ buildr/lib/buildr/java/doc.rb | 84 + buildr/lib/buildr/java/ecj.rb | 69 + buildr/lib/buildr/java/emma.rb | 240 +++ buildr/lib/buildr/java/external.rb | 73 + buildr/lib/buildr/java/jruby.rb | 120 ++ .../org/apache/buildr/JavaTestFilter.class | Bin 0 -> 3552 bytes .../org/apache/buildr/JavaTestFilter.java | 142 ++ buildr/lib/buildr/java/packaging.rb | 730 +++++++++ buildr/lib/buildr/java/pom.rb | 185 +++ buildr/lib/buildr/java/rjb.rb | 154 ++ buildr/lib/buildr/java/test_result.rb | 96 ++ buildr/lib/buildr/java/tests.rb | 424 +++++ buildr/lib/buildr/java/version_requirement.rb | 172 ++ buildr/lib/buildr/packaging/archive.rb | 534 +++++++ buildr/lib/buildr/packaging/artifact.rb | 908 +++++++++++ .../buildr/packaging/artifact_namespace.rb | 1011 ++++++++++++ .../lib/buildr/packaging/artifact_search.rb | 139 ++ buildr/lib/buildr/packaging/gems.rb | 93 ++ buildr/lib/buildr/packaging/package.rb | 243 +++ buildr/lib/buildr/packaging/tar.rb | 187 +++ .../buildr/packaging/version_requirement.rb | 192 +++ buildr/lib/buildr/packaging/zip.rb | 183 +++ buildr/lib/buildr/packaging/ziptask.rb | 352 +++++ buildr/lib/buildr/resources/buildr.icns | Bin 0 -> 72357 bytes buildr/lib/buildr/resources/completed.png | Bin 0 -> 12805 bytes buildr/lib/buildr/resources/failed.png | Bin 0 -> 11469 bytes buildr/lib/buildr/resources/icons-license.txt | 17 + buildr/lib/buildr/run.rb | 195 +++ buildr/lib/buildr/scala.rb | 26 + buildr/lib/buildr/scala/bdd.rb | 249 +++ buildr/lib/buildr/scala/compiler.rb | 295 ++++ buildr/lib/buildr/scala/doc.rb | 144 ++ .../scala/org/apache/buildr/Specs2Runner.java | 38 + .../apache/buildr/SpecsSingletonRunner.class | Bin 0 -> 1891 bytes .../apache/buildr/SpecsSingletonRunner.java | 57 + buildr/lib/buildr/scala/shell.rb | 48 + buildr/lib/buildr/scala/tests.rb | 209 +++ buildr/lib/buildr/shell.rb | 184 +++ buildr/lib/buildr/version.rb | 18 + buildr/rakelib/all-in-one.rake | 122 ++ buildr/rakelib/checks.rake | 28 + buildr/rakelib/doc.rake | 123 ++ buildr/rakelib/metrics.rake | 39 + buildr/rakelib/package.rake | 73 + buildr/rakelib/release.rake | 160 ++ buildr/rakelib/rspec.rake | 90 ++ buildr/rakelib/stage.rake | 217 +++ buildr/spec/addon/bnd_spec.rb | 330 ++++ buildr/spec/addon/drb_spec.rb | 328 ++++ buildr/spec/addon/jaxb_xjc_spec.rb | 130 ++ buildr/spec/core/application_spec.rb | 578 +++++++ buildr/spec/core/build_spec.rb | 837 ++++++++++ buildr/spec/core/cc_spec.rb | 223 +++ buildr/spec/core/checks_spec.rb | 519 +++++++ buildr/spec/core/common_spec.rb | 725 +++++++++ buildr/spec/core/compile_spec.rb | 669 ++++++++ buildr/spec/core/doc_spec.rb | 195 +++ buildr/spec/core/extension_spec.rb | 201 +++ buildr/spec/core/generate_spec.rb | 33 + buildr/spec/core/project_spec.rb | 772 +++++++++ buildr/spec/core/run_spec.rb | 106 ++ buildr/spec/core/shell_spec.rb | 146 ++ buildr/spec/core/test_spec.rb | 1320 ++++++++++++++++ buildr/spec/core/transport_spec.rb | 544 +++++++ buildr/spec/core/util_spec.rb | 141 ++ buildr/spec/groovy/bdd_spec.rb | 80 + buildr/spec/groovy/compiler_spec.rb | 251 +++ buildr/spec/groovy/doc_spec.rb | 65 + buildr/spec/ide/eclipse_spec.rb | 739 +++++++++ buildr/spec/ide/idea_spec.rb | 1196 ++++++++++++++ buildr/spec/java/ant_spec.rb | 37 + buildr/spec/java/bdd_spec.rb | 164 ++ buildr/spec/java/cobertura_spec.rb | 112 ++ buildr/spec/java/commands_spec.rb | 93 ++ buildr/spec/java/compiler_spec.rb | 255 +++ buildr/spec/java/doc_spec.rb | 56 + buildr/spec/java/ecj_spec.rb | 115 ++ buildr/spec/java/emma_spec.rb | 121 ++ buildr/spec/java/external_spec.rb | 56 + buildr/spec/java/java_spec.rb | 132 ++ buildr/spec/java/packaging_spec.rb | 1266 +++++++++++++++ buildr/spec/java/pom_spec.rb | 70 + buildr/spec/java/run_spec.rb | 78 + buildr/spec/java/test_coverage_helper.rb | 257 +++ buildr/spec/java/tests_spec.rb | 682 ++++++++ buildr/spec/packaging/archive_spec.rb | 775 +++++++++ .../spec/packaging/artifact_namespace_spec.rb | 758 +++++++++ buildr/spec/packaging/artifact_spec.rb | 1142 ++++++++++++++ buildr/spec/packaging/packaging_helper.rb | 63 + buildr/spec/packaging/packaging_spec.rb | 719 +++++++++ buildr/spec/sandbox.rb | 203 +++ buildr/spec/scala/bdd_spec.rb | 222 +++ buildr/spec/scala/compiler_spec.rb | 321 ++++ buildr/spec/scala/doc_spec.rb | 83 + buildr/spec/scala/scala.rb | 31 + buildr/spec/scala/tests_spec.rb | 295 ++++ buildr/spec/spec_helpers.rb | 369 +++++ buildr/spec/version_requirement_spec.rb | 145 ++ buildr/spec/xpath_matchers.rb | 121 ++ buildr/tests/BUILDR-320/Buildfile | 29 + buildr/tests/JavaSystemProperty/Buildfile | 18 + .../src/test/java/FooTest.java | 24 + buildr/tests/compile_with_parent/Buildfile | 18 + .../child/src/main/java/Foo.java | 23 + buildr/tests/helloWorld/Buildfile | 20 + .../helloWorld/src/main/java/HelloWorld.java | 23 + buildr/tests/include_as/Buildfile | 24 + buildr/tests/include_as/doc/index.html | 8 + buildr/tests/include_path/Buildfile | 24 + buildr/tests/include_path/doc/index.html | 8 + buildr/tests/integration_testing.rb | 81 + buildr/tests/junit3/Buildfile | 9 + buildr/tests/junit3/src/main/java/Foo.java | 23 + .../tests/junit3/src/test/java/FooTest.java | 28 + buildr/tests/package_war_as_jar/Buildfile | 15 + .../package_war_as_jar/src/main/java/Foo.java | 19 + 240 files changed, 49737 insertions(+) create mode 100644 .braids create mode 100644 buildr/.gitignore create mode 100644 buildr/.rvmrc create mode 100644 buildr/CHANGELOG create mode 100644 buildr/Gemfile create mode 100644 buildr/LICENSE create mode 100644 buildr/NOTICE create mode 100644 buildr/README.rdoc create mode 100644 buildr/Rakefile create mode 100755 buildr/_buildr create mode 100755 buildr/_jbuildr create mode 100644 buildr/addon/buildr/antlr.rb create mode 100644 buildr/addon/buildr/bnd.rb create mode 100644 buildr/addon/buildr/checkstyle.rb create mode 100644 buildr/addon/buildr/cobertura.rb create mode 100644 buildr/addon/buildr/drb.rb create mode 100644 buildr/addon/buildr/emma.rb create mode 100644 buildr/addon/buildr/findbugs.rb create mode 100644 buildr/addon/buildr/hibernate.rb create mode 100644 buildr/addon/buildr/javacc.rb create mode 100644 buildr/addon/buildr/javancss.rb create mode 100644 buildr/addon/buildr/jaxb_xjc.rb create mode 100644 buildr/addon/buildr/jdepend.rb create mode 100644 buildr/addon/buildr/jetty.rb create mode 100644 buildr/addon/buildr/jibx.rb create mode 100644 buildr/addon/buildr/nailgun.rb create mode 100644 buildr/addon/buildr/openjpa.rb create mode 100644 buildr/addon/buildr/org/apache/buildr/BuildrNail$Main.class create mode 100644 buildr/addon/buildr/org/apache/buildr/BuildrNail.class create mode 100644 buildr/addon/buildr/org/apache/buildr/BuildrNail.java create mode 100644 buildr/addon/buildr/org/apache/buildr/JettyWrapper$1.class create mode 100644 buildr/addon/buildr/org/apache/buildr/JettyWrapper$BuildrHandler.class create mode 100644 buildr/addon/buildr/org/apache/buildr/JettyWrapper.class create mode 100644 buildr/addon/buildr/org/apache/buildr/JettyWrapper.java create mode 100644 buildr/addon/buildr/pmd.rake create mode 100644 buildr/addon/buildr/protobuf.rb create mode 100644 buildr/addon/buildr/xmlbeans.rb create mode 100644 buildr/all-in-one/_buildr create mode 100755 buildr/all-in-one/buildr create mode 100755 buildr/all-in-one/buildr.cmd create mode 100755 buildr/bin/buildr create mode 100644 buildr/buildr.buildfile create mode 100644 buildr/buildr.gemspec create mode 100644 buildr/doc/_config.yml create mode 100644 buildr/doc/_layouts/default.html create mode 100644 buildr/doc/_layouts/preface.html create mode 100644 buildr/doc/artifacts.textile create mode 100644 buildr/doc/building.textile create mode 100644 buildr/doc/contributing.textile create mode 100644 buildr/doc/css/default.css create mode 100644 buildr/doc/css/print.css create mode 100644 buildr/doc/css/syntax.css create mode 100644 buildr/doc/download.textile create mode 100644 buildr/doc/extending.textile create mode 100644 buildr/doc/images/1442160941-frontcover.jpg create mode 100644 buildr/doc/images/asf-logo.gif create mode 100644 buildr/doc/images/asf-logo.png create mode 100644 buildr/doc/images/buildr-hires.png create mode 100644 buildr/doc/images/buildr.png create mode 100644 buildr/doc/images/favicon.png create mode 100644 buildr/doc/images/growl-icon.tiff create mode 100644 buildr/doc/images/note.png create mode 100644 buildr/doc/images/project-structure.png create mode 100644 buildr/doc/images/tip.png create mode 100644 buildr/doc/images/zbuildr.png create mode 100644 buildr/doc/images/zbuildr.tif create mode 100644 buildr/doc/index.textile create mode 100644 buildr/doc/installing.textile create mode 100644 buildr/doc/languages.textile create mode 100644 buildr/doc/mailing_lists.textile create mode 100644 buildr/doc/more_stuff.textile create mode 100644 buildr/doc/packaging.textile create mode 100644 buildr/doc/preface.textile create mode 100644 buildr/doc/projects.textile create mode 100644 buildr/doc/quick_start.textile create mode 100644 buildr/doc/releasing.textile create mode 100755 buildr/doc/scripts/buildr-git.rb create mode 100755 buildr/doc/scripts/gitflow.rb create mode 100755 buildr/doc/scripts/install-jruby.sh create mode 100755 buildr/doc/scripts/install-linux.sh create mode 100755 buildr/doc/scripts/install-osx.sh create mode 100644 buildr/doc/settings_profiles.textile create mode 100644 buildr/doc/testing.textile create mode 100644 buildr/etc/KEYS create mode 100644 buildr/lib/buildr.rb create mode 100644 buildr/lib/buildr/clojure.rb create mode 100644 buildr/lib/buildr/clojure/shell.rb create mode 100644 buildr/lib/buildr/core/application.rb create mode 100644 buildr/lib/buildr/core/build.rb create mode 100644 buildr/lib/buildr/core/cc.rb create mode 100644 buildr/lib/buildr/core/checks.rb create mode 100644 buildr/lib/buildr/core/common.rb create mode 100644 buildr/lib/buildr/core/compile.rb create mode 100644 buildr/lib/buildr/core/doc.rb create mode 100644 buildr/lib/buildr/core/environment.rb create mode 100644 buildr/lib/buildr/core/filter.rb create mode 100644 buildr/lib/buildr/core/generate.rb create mode 100644 buildr/lib/buildr/core/help.rb create mode 100644 buildr/lib/buildr/core/jrebel.rb create mode 100644 buildr/lib/buildr/core/linux.rb create mode 100644 buildr/lib/buildr/core/osx.rb create mode 100644 buildr/lib/buildr/core/progressbar.rb create mode 100644 buildr/lib/buildr/core/project.rb create mode 100644 buildr/lib/buildr/core/run.rb create mode 100644 buildr/lib/buildr/core/shell.rb create mode 100644 buildr/lib/buildr/core/test.rb create mode 100644 buildr/lib/buildr/core/transports.rb create mode 100644 buildr/lib/buildr/core/util.rb create mode 100644 buildr/lib/buildr/groovy.rb create mode 100644 buildr/lib/buildr/groovy/bdd.rb create mode 100644 buildr/lib/buildr/groovy/compiler.rb create mode 100644 buildr/lib/buildr/groovy/doc.rb create mode 100644 buildr/lib/buildr/groovy/shell.rb create mode 100644 buildr/lib/buildr/ide/eclipse.rb create mode 100644 buildr/lib/buildr/ide/eclipse/java.rb create mode 100644 buildr/lib/buildr/ide/eclipse/plugin.rb create mode 100644 buildr/lib/buildr/ide/eclipse/scala.rb create mode 100644 buildr/lib/buildr/ide/idea.rb create mode 100644 buildr/lib/buildr/java/ant.rb create mode 100644 buildr/lib/buildr/java/bdd.rb create mode 100644 buildr/lib/buildr/java/cobertura.rb create mode 100644 buildr/lib/buildr/java/commands.rb create mode 100644 buildr/lib/buildr/java/compiler.rb create mode 100644 buildr/lib/buildr/java/deprecated.rb create mode 100644 buildr/lib/buildr/java/doc.rb create mode 100644 buildr/lib/buildr/java/ecj.rb create mode 100644 buildr/lib/buildr/java/emma.rb create mode 100644 buildr/lib/buildr/java/external.rb create mode 100644 buildr/lib/buildr/java/jruby.rb create mode 100644 buildr/lib/buildr/java/org/apache/buildr/JavaTestFilter.class create mode 100644 buildr/lib/buildr/java/org/apache/buildr/JavaTestFilter.java create mode 100644 buildr/lib/buildr/java/packaging.rb create mode 100644 buildr/lib/buildr/java/pom.rb create mode 100644 buildr/lib/buildr/java/rjb.rb create mode 100644 buildr/lib/buildr/java/test_result.rb create mode 100644 buildr/lib/buildr/java/tests.rb create mode 100644 buildr/lib/buildr/java/version_requirement.rb create mode 100644 buildr/lib/buildr/packaging/archive.rb create mode 100644 buildr/lib/buildr/packaging/artifact.rb create mode 100644 buildr/lib/buildr/packaging/artifact_namespace.rb create mode 100644 buildr/lib/buildr/packaging/artifact_search.rb create mode 100644 buildr/lib/buildr/packaging/gems.rb create mode 100644 buildr/lib/buildr/packaging/package.rb create mode 100644 buildr/lib/buildr/packaging/tar.rb create mode 100644 buildr/lib/buildr/packaging/version_requirement.rb create mode 100644 buildr/lib/buildr/packaging/zip.rb create mode 100644 buildr/lib/buildr/packaging/ziptask.rb create mode 100644 buildr/lib/buildr/resources/buildr.icns create mode 100644 buildr/lib/buildr/resources/completed.png create mode 100644 buildr/lib/buildr/resources/failed.png create mode 100644 buildr/lib/buildr/resources/icons-license.txt create mode 100644 buildr/lib/buildr/run.rb create mode 100644 buildr/lib/buildr/scala.rb create mode 100644 buildr/lib/buildr/scala/bdd.rb create mode 100644 buildr/lib/buildr/scala/compiler.rb create mode 100644 buildr/lib/buildr/scala/doc.rb create mode 100644 buildr/lib/buildr/scala/org/apache/buildr/Specs2Runner.java create mode 100644 buildr/lib/buildr/scala/org/apache/buildr/SpecsSingletonRunner.class create mode 100644 buildr/lib/buildr/scala/org/apache/buildr/SpecsSingletonRunner.java create mode 100644 buildr/lib/buildr/scala/shell.rb create mode 100644 buildr/lib/buildr/scala/tests.rb create mode 100644 buildr/lib/buildr/shell.rb create mode 100644 buildr/lib/buildr/version.rb create mode 100644 buildr/rakelib/all-in-one.rake create mode 100644 buildr/rakelib/checks.rake create mode 100644 buildr/rakelib/doc.rake create mode 100644 buildr/rakelib/metrics.rake create mode 100644 buildr/rakelib/package.rake create mode 100644 buildr/rakelib/release.rake create mode 100644 buildr/rakelib/rspec.rake create mode 100644 buildr/rakelib/stage.rake create mode 100644 buildr/spec/addon/bnd_spec.rb create mode 100644 buildr/spec/addon/drb_spec.rb create mode 100644 buildr/spec/addon/jaxb_xjc_spec.rb create mode 100644 buildr/spec/core/application_spec.rb create mode 100644 buildr/spec/core/build_spec.rb create mode 100644 buildr/spec/core/cc_spec.rb create mode 100644 buildr/spec/core/checks_spec.rb create mode 100644 buildr/spec/core/common_spec.rb create mode 100644 buildr/spec/core/compile_spec.rb create mode 100644 buildr/spec/core/doc_spec.rb create mode 100755 buildr/spec/core/extension_spec.rb create mode 100644 buildr/spec/core/generate_spec.rb create mode 100644 buildr/spec/core/project_spec.rb create mode 100644 buildr/spec/core/run_spec.rb create mode 100644 buildr/spec/core/shell_spec.rb create mode 100644 buildr/spec/core/test_spec.rb create mode 100644 buildr/spec/core/transport_spec.rb create mode 100644 buildr/spec/core/util_spec.rb create mode 100644 buildr/spec/groovy/bdd_spec.rb create mode 100644 buildr/spec/groovy/compiler_spec.rb create mode 100644 buildr/spec/groovy/doc_spec.rb create mode 100644 buildr/spec/ide/eclipse_spec.rb create mode 100644 buildr/spec/ide/idea_spec.rb create mode 100644 buildr/spec/java/ant_spec.rb create mode 100644 buildr/spec/java/bdd_spec.rb create mode 100644 buildr/spec/java/cobertura_spec.rb create mode 100644 buildr/spec/java/commands_spec.rb create mode 100644 buildr/spec/java/compiler_spec.rb create mode 100644 buildr/spec/java/doc_spec.rb create mode 100644 buildr/spec/java/ecj_spec.rb create mode 100644 buildr/spec/java/emma_spec.rb create mode 100644 buildr/spec/java/external_spec.rb create mode 100644 buildr/spec/java/java_spec.rb create mode 100644 buildr/spec/java/packaging_spec.rb create mode 100644 buildr/spec/java/pom_spec.rb create mode 100644 buildr/spec/java/run_spec.rb create mode 100644 buildr/spec/java/test_coverage_helper.rb create mode 100644 buildr/spec/java/tests_spec.rb create mode 100644 buildr/spec/packaging/archive_spec.rb create mode 100644 buildr/spec/packaging/artifact_namespace_spec.rb create mode 100644 buildr/spec/packaging/artifact_spec.rb create mode 100644 buildr/spec/packaging/packaging_helper.rb create mode 100644 buildr/spec/packaging/packaging_spec.rb create mode 100644 buildr/spec/sandbox.rb create mode 100644 buildr/spec/scala/bdd_spec.rb create mode 100644 buildr/spec/scala/compiler_spec.rb create mode 100644 buildr/spec/scala/doc_spec.rb create mode 100644 buildr/spec/scala/scala.rb create mode 100644 buildr/spec/scala/tests_spec.rb create mode 100644 buildr/spec/spec_helpers.rb create mode 100644 buildr/spec/version_requirement_spec.rb create mode 100644 buildr/spec/xpath_matchers.rb create mode 100644 buildr/tests/BUILDR-320/Buildfile create mode 100644 buildr/tests/JavaSystemProperty/Buildfile create mode 100644 buildr/tests/JavaSystemProperty/src/test/java/FooTest.java create mode 100644 buildr/tests/compile_with_parent/Buildfile create mode 100644 buildr/tests/compile_with_parent/child/src/main/java/Foo.java create mode 100644 buildr/tests/helloWorld/Buildfile create mode 100644 buildr/tests/helloWorld/src/main/java/HelloWorld.java create mode 100644 buildr/tests/include_as/Buildfile create mode 100644 buildr/tests/include_as/doc/index.html create mode 100644 buildr/tests/include_path/Buildfile create mode 100644 buildr/tests/include_path/doc/index.html create mode 100644 buildr/tests/integration_testing.rb create mode 100644 buildr/tests/junit3/Buildfile create mode 100644 buildr/tests/junit3/src/main/java/Foo.java create mode 100644 buildr/tests/junit3/src/test/java/FooTest.java create mode 100644 buildr/tests/package_war_as_jar/Buildfile create mode 100644 buildr/tests/package_war_as_jar/src/main/java/Foo.java diff --git a/.braids b/.braids new file mode 100644 index 0000000..8396f15 --- /dev/null +++ b/.braids @@ -0,0 +1,8 @@ +--- +buildr: + branch: trunk + remote: trunk/braid/buildr + revision: 7a9c78ae6b1d952aa30e1a81106d60b5207a25e3 + squashed: true + type: git + url: git://git.apache.org/buildr.git diff --git a/buildr/.gitignore b/buildr/.gitignore new file mode 100644 index 0000000..8eac7ff --- /dev/null +++ b/buildr/.gitignore @@ -0,0 +1,21 @@ +pkg +failed +buildr.pdf +rdoc +_reports +_site +_snapshot +_staged +_release +tmp +*.swp +*.log +_all-in-one +nbproject +.rakeTasks +buildr.iml +buildr.ipr +buildr.iws +libs +Gemfile.lock +*.rbc \ No newline at end of file diff --git a/buildr/.rvmrc b/buildr/.rvmrc new file mode 100644 index 0000000..14d41ca --- /dev/null +++ b/buildr/.rvmrc @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +cat < /dev/null + if [ $? -gt 0 ]; then + echo "Installing bundler..." + gem install bundler + fi + + bundle install +else + echo "${ruby_string} was not found, please run 'rvm install ${ruby_string}' and then cd back into the project directory." +fi diff --git a/buildr/CHANGELOG b/buildr/CHANGELOG new file mode 100644 index 0000000..19aaf58 --- /dev/null +++ b/buildr/CHANGELOG @@ -0,0 +1,1384 @@ +1.4.8 (Pending) +* Added: Add several utility methods to IDEA extension for defining artifacts and configurations. +* Change: Upgraded to Apache Ant 1.8.3 +* Change: Default maven2 repository is now repo1.maven.org/maven2. +* Change: Make minimumTokenCount and encoding configurable for the PMD/CPD action + and default encoding to UTF-8 for compatibility with external tools (i.e. Jenkins) +* Change: BUILDR-615 VersionRequirement.version? now returns true for + versions following pattern "r9999", e.g. "r09" +* Change: BUILDR-630 Run task should not add test dependencies (Russell Teabeault) +* Change: BUILDR-629 JavaRunner should include target/resources in classpath (Russell Teabeault) +* Fixed: BUILDR-617 pom exclusion does not work (Kafka Liu) +1.4.7 (2011-11-17) +* Added: Add a Findbugs extension. +* Added: Add a Checkstyle extension. +* Added: Add a JavaNCSS extension. +* Added: Add a PMD extension. +* Added: MultiTest framework that allows combining multiple test frameworks + for a single project. +* Added: Scala Specs2 framework support. +* Added: Buildr.transitive() now accepts hash with :scopes, :optional and + :scopes_transitive parameters +* Added: Improved scala file change detection + (to avoid recompiling unnecessarily) +* Added: ScalaTest now automatically loads the Mockito library +* Added: Enhance the Intellij IDEA extension to support the addition of "artifacts" + and "configurations" to the generated project file. +* Added: BUILDR-598 TestNG support for :groups and :excludegroups (Christopher Coco) +* Added: BUILDR-616 Buildr development - If using rvm a default .rvmrc file would be helpful (Russell Teabeault) +* Change: Scala Specs upgraded to 1.6.9 if using Scala 2.9.1 +* Change: Scala 2.9.1 is now default +* Change: Make it possible to parameterize the JDepend extension and control the + projects that are included in the analysis and to enable support for + loading a per project jdepend.properties. +* Change: Parameterize the the directory where the top level cobertura tasks will generate + reports. Specify using Buildr::Cobertura.report_dir = '....' +* Change: Stop pretty printing the Intellij IDEA project files to avoid IDEA breaking + in the presence of non-normalized whitespace content. +* Change: Change the Intellij IDEA extension to always rebuild the project files. +* Change: Upgrade to require atoulme-Antwrap 0.7.2 +* Change: Changed the default output directory for Intellij IDEA extension to be + _(:target, :main, :idea, :classes) from _(:target, :main, :java) and the + default test output directory to be _(:target, :test, :idea, :classes) + from _(:target, :test, :java) +* Change: Upgrade to highline 1.6.2 +* Change: Upgrade to jekyll 0.11.0, jekylltask 1.1.0, RedCloth 4.2.7, rdoc 3.8 for + generating documentation +* Change: Upgrade to require rubygems > 1.8.6 +* Change: BUILDR-603 Remove install/uninstall actions from :gem packaging type +* Change: BUILDR-602 Fail the build when gem dependencies are missing rather than + attempting to install the dependencies +* Change: BUILDR-601 Remove Buildr::Util::Gems +* Change: BUILDR-600 Centralize the common ad internal requires into one location. +* Change: Upgrade to JRuby 1.6.2 +* Change: Move to Bundler to manage the project dependencies +* Change: BUILDR-548 Remove support for JTestR as it is no longer maintained (Antoine Toulme) +* Change: BUILDR-614 Buildr development - Using rvm, bundler and OSX installs the wrong rjb (Russell Teabeault) +* Change: Upgrade to RJB 1.3.7 +* Fixed: ArtifactNamespace fails when using artifacts with classfier. +* Fixed: Buildr.artifacts() should handle any object with :to_spec method + (i.e., any object that ActsAsArtifact) +* Fixed: Handle HTTP Unauthorized (501) result code when downloading artifacts. +* Fixed: BUILDR-611 Buildr should not unnecessarily recompile Java files + explicitly added to compile.from +* Fixed: scaladoc generation with scala 2.9.x +* Fixed: Bnd Plugin: Add each artifact individually as a prerequisite to + bundle / package task when passed to classpath_element method +* Fixed: BUILDR-439 "The command line is too long" when running TestNG tests (Tammo Van Lessen) +* Fixed: BUILDR-595 Add option to specifiy location of ca cert +* Fixed: BUILDR-596 Update installation notes to talk about the all-in-one bundle + +1.4.6 (2011-06-21) +* Added: BUILDR-592 Allow Users to Specify SSH Options for Deployment (Marc-André Laverdière) +* Fixed: BUILDR-591 Sort modules in iml files generated by idea task to ensure + main_dependencies are exported +* Added: Support for Scala 2.9.0+ (with help of Alexis Midon) +* Fixed: BUILDR-583 Update jruby install to use jruby version 1.6.1 (Alexis Midon) +* Fixed: BUILDR-582 Revert the name change for the task to generate Intellij + project files to 'idea' +* Change: BUILDR-579 Format generated IDEA project files to look more like what + IntelliJ generates (Peter Royal) +* Change: BUILDR-574 Enhance idea task to generate test resources with test scope + (Jean-Philippe Caruana) +* Change: BUILDR-576 Upgrade to JUnit 4.8.2 +* Change: Upgrade to JRuby 1.6.2 +* Change: Scala 2.9.0-1 is now default, along with ScalaCheck 1.9, ScalaTest 1.6.1 + and Specs 1.6.8. +* Change: ScalaCheck, ScalaTest and Specs now default to sane versions when using + older Scala versions. +* Fixed: BUILDR-571 Generated IDEA projects include resources multiple times (Peter Royal) +* Fixed: BUILDR-573 HTTP upload PUT request with incorrect Content-Type (Mathias Doenitz) +* Fixed: BUILDR-578 Tar task does not preserve uid/gid on folders (Jean-Philippe Caruana) +* Fixed: BUILDR-251 Classifier not handled when downloading snapshot artifacts (Ryan Fowler) +* Fixed: BUILDR-585 "TypeError : can't dup NilClass" when merging jars +* Fixed: BUILDR-586 ScalaTest uses deprecated ant task (Martin Partel) +* Fixed: BUILDR-584 eclipse plugin should use absolute path +* Fixed: BUILDR-587 ScalaTest uses deprecated reporter parameters + +1.4.5 (2011-02-20) +* Added: BUILDR-555 Add support for the jaxb binding compiler (Mark Petrovic) +* Added: BUILDR-554 Add support for OSGi bundle packages by importing the + buildr_bnd plugin +* Added: BUILDR-125 Add support for in application.xml of + EAR packaging (Mikael Amborn) +* Added: BUILDR-550 Add support for groovydoc +* Added: BUILDR-521: System tray notifications for Linux systems + (via libnotify/notify-send) +* Added: BUILDR-537 Shell tasks should use JAVA_OPTS by default +* Added: BUILDR-538 Shell tasks should support passing :java_args +* Added: BUILDR-544 Support ${groupId} in pom files (Chris Dean) +* Added: BUILDR-552 Projects may now be defined using project(:name) and a block +* Added: BUILDR-564 Add package(:scaladoc) +* Added: Automatically add "require buildr/{groovy,scala}" when generating + project if Groovy/Scala files are detected. +* Change: BUILDR-540 Upgrade to rspec 2.1.0 +* Change: BUILDR-546 Upgrade to Rubyzip 0.9.4 (Michael Guymon) +* Change: BUILDR-556 Merge buildr-iidea extension back into buildr. +* Change: Upgrade default Scala compiler version to 2.8.1-final +* Change: Upgrade to ScalaCheck 1.8 +* Change: Upgrade to ScalaTest 1.3 +* Change: Upgrade to Specs 1.6.6 +* Change: Upgrade to JRuby 1.5.6 +* Fixed: BUILDR-542 Release task: SVN tagging fails if parent tag directory + does not exist yet (Gerolf Seitz) +* Fixed: BUILDR-543 POMs are installed and uploaded twice when using artifacts + with classifier +* Fixed: BUILDR-522 Send notifications when continuous compilation + succeeds/fails. +* Fixed: BUILDR-551 Continuous compilation not working for project trees +* Fixed: BUILDR-557 MD5 + SHA1 checksums are not Maven compliant (Tammo van Lessen) +* Change: Upgrade to Groovy 1.7.5 +* Change: BUILDR-545 Add the ability to specify the description element in in + application.xml contained within an ear. +* Fixed: BUILDR-547 - Ensure ECJ compiler works when there is a space in the + path of dependencies. +* Fixed: BUILDR-558 Artifact uploads should show a progress bar (Tammo van Lessen) +* Fixed: BUILDR-560 show a meaning full error message when POM cannot be parsed + (Tammo van Lessen) +* Fixed: BUILDR-562 WAR package isn't updated if files under src/main/webapp + are updated +* Fixed: BUILDR-569 Buildr fails under JRuby 1.6.0.RC1 due to read-only $? variable +* Fixed: BUILDR-570 Buildr does not work with Rubygems 1.5.x +* Fixed: Scaladoc task would cause build to exit prematurely + +1.4.4 (2010-11-16) +* Change: BUILDR-549 Upgrade to RJB 1.3.3 to address "Cannot create JVM" issue with Java Update 3 + on Mac OS X. Win32 platform upgraded to RJB 1.3.2. +* Change: RSpec gem dependency ~> 1.3.1 +* Change: Upgrade to JtestR 0.6 + +1.4.3 (2010-10-15) +* Added: BUILDR-514 New 'run' local task. http://buildr.apache.org/more_stuff.html#run +* Added: BUILDR-518 Load _buildr.rb or .buildr.rb from same directory as Buildfile + if they exist (Peter Donald) +* Added: BUILDR-519 Load repositories.release_to from build settings (Peter Donald) +* Fixed: BUILDR-520 Scaladoc 2.8 no longer support -windowtitle, use -doc-title instead. +* Fixed: BUILDR-512 Buildr::Util.ruby invokes non existent method (Peter Donald) +* Fixed: BUILDR-513 --trace fails with NoMethodError : undefined method + `include?' for nil:NilClass +* Fixed: BUILDR-515 -update-snapshot doesn't work as expected +* Fixed: BUILDR-517 package(:jar).include(directory, :as=>"foo") produces a corrupted jar +* Fixed: BUILDR-524 Optimized and more robust reading of jar MANIFEST.MF (Hugues Malphettes) +* Fixed: BUILDR-525 Documentation refers to repositories.upload_to rather than + repositories.release_to (Peter Donald) +* Fixed: BUILDR-526 Gracefully handle h2 sections with no id in documentation (Peter Donald) +* Fixed: BUILDR-527 package(:war) if libs passed are files (instead of artifacts) +* Fixed: BUILDR-528 Stop using deprecated method Gem::Dependency.version_requirements correctly (Peter Donald) +* Fixed: BUILDR-529 Stop using gem name "foo" in tests as it is the name of an actual gem (Peter Donald) +* Fixed: BUILDR-531 Improve error message when build requires gem that can't be found in local/remote + gem repositories (Peter Donald) +* Fixed: BUILDR-532 package_as_source does not package resources (Tammo van Lessen) +* Fixed: BUILDR-534 package_with_sources does not package source artifacts if no sources but resources exist. + (Tammo Van Lessen) +* Fixed: BUILDR-535 Failing "checks" produce no meaningful errors on JRuby +* Fixed: JavaRebel was previously not correctly detected. + +1.4.2 (2010-09-18) +* Added: BUILDR-415 Ability to exclude tests from command line +* Added: BUILDR-495 Document twitter on Buildr's homepage +* Added: BUILDR-212 Update support for SNAPSHOT artifacts (Timo Rantalaiho and Izzet Mustafa) +* Added: BUILDR-465 Eclipse project names should be customizable +* Added: BUILDR-493 Eclipse task should generate javadocpath +* Added: BUILDR-509 Option to generate non-prefixed Eclipse project names +* Added: BUILDR-510 Add support for trace categories: --trace=foo,bar +* Added: Integration test to show how to change the war packaging spec. +* Added: Integration test to show how to use junit 3. +* Added: Integration test to show how to get ahold of parent project +* Change: BUILDR-473 Update jruby-openssl dependency version or support a range of versions +* Change: BUILDR-478 Upgrade to net-ssh 2.0.23 and net-sftp 2.0.4 (Shane Witbeck) +* Change: BUILDR-475 Support for long names on tar.gz (updated to minitar 0.5.3) +* Change: BUILDR-484 Upgrade to Scala 2.8.0 (final) and associated dependencies + (ScalaCheck 1.7, ScalaTest 1.2, Specs 1.6.5) +* Change: BUILDR-487 package :sources should default to using .jar extension (instead of .zip) +* Change: Upgrade to Jruby 1.5.2 +* Fixed: BUILDR-143 Upload to a file:// path needs ability to specify permissions (Joel Muzzerall) +* Fixed: BUILDR-144 Filter does not preserve file permissions +* Fixed: BUILDR-163 cobertura-check +* Fixed: BUILDR-203 Compiler guessing very inefficient +* Fixed: BUILDR-225 ArchiveTask#merge, not according to doc +* Fixed: BUILDR-256 Automatically installing gems aborts rspec test runner (Rhett Sutphin) +* Fixed: BUILDR-285 Cobertura failing when running build +* Fixed: BUILDR-302 Move out-of-date Nailgun documentation to wiki (Shane Witbeck) +* Fixed: BUILDR-317 ecj compiler +* Fixed: BUILDR-326 follow up: binary safe untarring on Windows (Sam Hendley) +* Fixed: BUILDR-335 follow up: excluding libraries from war is confusing +* Fixed: BUILDR-342 The jruby gem installer invokes the removed Gem.manage_gems function (Rhett Sutphin) +* Fixed: BUILDR-403 Buildr::Util::Gems.install does not find gems on remote sources +* Fixed: BUILDR-436 release task should only replace "-SNAPSHOT" (spec from Jean-Philippe Caruana) +* Fixed: BUILDR-438 Release Task: customizable version numbers (Alexis Midon) +* Fixed: BUILDR-464 Improve the versioning of Buildr (Rhett Sutphin) +* Fixed: BUILDR-466 Rendering issue with IE on the website (Shane Witbeck) +* Fixed: BUILDR-468 test:failed does not respect test.exclude +* Fixed: BUILDR-469 test:failed causes all transitive tests to run +* Fixed: BUILDR-472 ECJ dependency now required to build any java project +* Fixed: BUILDR-477 Error while parsing maven-metadata.xml +* Fixed: BUILDR-479 Enforce using a minimal version of jruby +* Fixed: BUILDR-481 Antwrap monkey-patching in core.rb +* Fixed: BUILDR-482 Javadoc : cannot load class java.com.sun.tools.javadoc.Main +* Fixed: BUILDR-488 artifact poms not reinstalled +* Fixed: BUILDR-491 sftp download goes into infinite loop +* Fixed: BUILDR-498 Artifact download fails with "negative argument" if + terminal capabilities are undefined +* Fixed: BUILDR-499 Java package caching through constants + e.g. (Java.java.lang.String cached as Java::Lang::String) + can shadow Ruby modules +* Fixed: BUILDR-501 Fix buildr label when listing tasks (Peter Donald) +* Fixed: BUILDR-503 Include with as includes directories as files when the directory has the same name as the path +* Fixed: BUILDR-506 Gem packaging does not work under windows (Peter Donald) +* Fixed: BUILDR-508 Remove unnecessary use of Java.classpath in OpenJPA + extension (Peter Donald) +* Fixed: BUILDR-507 Gem packaging should replace dashes with dots in + version number (Peter Donald) + +1.4.1 (2010-07-07) +* Added: BUILDR-420 Support external compiler +* Added: BUILDR-425 Specify dev dependencies in .gemspec +* Change: BUILDR-459 Update gemspec to accept json_pure ~> 1.4.3 +* Fixed: BUILDR-455 cc_spec.rb l 160 depends on time and thus fails intermittently +* Fixed: BUILDR-461 Packages with different ids collide +* Fixed: BUILDR-439 "The command line is too long" when running TestNG tests +* Fixed: BUILDR-463 Setting a system property in the buildfile causes a NoClassDefFoundError + +1.4.0 (2010-06-18) +* Added: BUILDR-405 Enhance the idea7x extension to supply a task to delete generated files + (Peter Donald) +* Added: Support for regexps in include and exclude patterns (BUILDR-406) +* Added: Support for Scala 2.8 compiler-level change detection and dependency + tracking +* Added: Continuous compilation +* Added: Generic documentation framework (using the `doc` task). Replaces + `javadoc` task +* Added: New "test:failed" task to execute only tests that failed during last + run (Antoine Toulme) +* Added: Project extensions (before/after_define) now support dependency ordering + similar to Rake (e.g. before_define(:my_setup => :compile) +* Added: BUILDR-328 Detect Eclipse plugin project with META-INF/MANIFEST.MF + and Bundle-SymbolicName: entry +* Added: Support for Eclipse classpath variables to avoid absolute pathnames in + generated .classpath using: + eclipse.classpath_variables { :VAR => '/path/to/libraries' } +* Added: Support for excluding libraries from Eclipse classpath using: + eclipse.exclude_libs += ['/path/to/some/library.jar'] +* Added: Environment variable IGNORE_BUILDFILE can be set to "yes" or + "true" to ignore changes in Buildfile when running tests. +* Added: "buildr test=only" will only run tests explicitly specified on the + command line (and ignore transitive test dependencies) +* Added: ArtifactNamespace.{keys,clear} methods +* Added: BUILDR-326 Support unzipping tar.gz files (Antoine Toulme) +* Added: BUILDR-368 Support protocol buffer code generation + (Pepijn Van Eeckhoudt) +* Added: BUILDR-375 Buildr now recognizes buildfile.rb and Buildfile.rb + (Kerry Wilson) +* Added: BUILDR-390 Buildr::group() should accept :classifier argument +* Added: BUILDR-407 Exclude and include patterns should support lambdas or procs +* Added: BUILDR-408 Filter include() and exclude() should accept Rake tasks +* Added: BUILDR-409 archive.include() should convert arguments to artifact + if applicable +* Added: BUILDR-453 Provide a ci task that uses the ci_reporter gem (Pepijn Van Eeckhoudt) +* Added: ScalaTest now generates JUnit XML reports in addition to text files. +* Change: Updated to Ant 1.8.0 +* Change: Updated to Cobertura 1.9.4.1 +* Change: Updated to Groovy 1.7.1 +* Change: Updated to JRuby 1.5.1 +* Change: Updated to JtestR 0.5 +* Change: Updated to JUnit 4.7 +* Change: Updated to JMock 2.5.1 (Antoine Toulme) +* Change: Updated to RJB 1.2.5 +* Change: Updated to Scala Specs 1.6.2.1 +* Change: Updated to ScalaCheck 1.6 +* Change: Updated to ScalaTest 1.0.1 +* Change: Updated to json_pure 1.4.0 +* Change: Load buildr.rb from $HOME/.buildr instead of $HOME + ($HOME/buildr.rb is still loaded with deprecation warning) +* Change: BUILDR-400 Don't forbid projects to use their own compiler after one has been guessed +* Change: BUILDR-401 Don't set compiler to output warnings if verbose +* Change: Buildr.settings.build['scala.version'] now overrides SCALA_HOME to + determine which Scala libraries used for compiling. If both are + are provided and reference the same Scala version, then local + jars from SCALA_HOME are used. +* Change: Tagline changed from "The build system that doesn't suck" to "Build like you code" +* Change: BUILDR-355 Use Rake for defining tasks to do the Buildr distro over JRuby (Izzet Mustafa oglu) +* Change: BUILDR-448 Don't use sudo by default for rake setup +* Change: BUILDR-450 Update .gitignore to exclude idea project files and files generated during spec tests (Peter Donald) +* Fixed: BUILDR-208 ansi control characters are printed on Windows (Pepijn Van Eeckhoudt) +* Fixed: BUILDR-348 Buildr fails on windows with jruby and ODE 1.X +* Fixed: BUILDR-183 Can't define root artifact namespace outside of project + (Ittay Dror) +* Fixed: BUILDR-223 Release Task: customizable commit message (Alexis Midon) +* Fixed: BUILDR-232 buildr should print the class of an exception, not just + its message (Antoine Toulme) +* Fixed: BUILDR-233 Can't specify version in artifact namespace +* Fixed: BUILDR-267 Skipping tests is only done after they are compiled + (Antoine Toulme) +* Fixed: BUILDR-281 Application#initialize fails if home dir isn't writable +* Fixed: BUILDR-327 Specifying :plugin eclipse nature explicitly fails +* Fixed: BUILDR-330 Install task should re-install artifact even if they + already exist (Alexis Midon) +* Fixed: BUILDR-334 Eclipse .classpath files use absolute paths for library + entries (Stefan Wasilewski) +* Fixed: BUILDR-336 Java::Commands.java Prints Command Without --trace + (Antoine Toulme) +* Fixed: BUILDR-341 jruby -S extract is no longer supported by jruby + (Antoine Toulme) +* Fixed: BUILDR-344 Buildr::TestFramework::TestResult::YamlFormatter uses + deprecated form of example_pending (Rhett Sutphin) +* Fixed: BUILDR-345 Improve project documentation (Peter Schröder) +* Fixed: BUILDR-346 Test classpath can not be set (Peter Schröder) +* Fixed: BUILDR-347 Compile.from does not work correctly with FileTask when + no compiler is set (Peter Schröder) +* Fixed: BUILDR-349 resources.filter should use defaults from profile.yaml + even if mapping is provided +* Fixed: BUILDR-360 Reintroduce tag_name instance method for Git release task for + backward compatibility (Antoine Toulme) +* Fixed: BUILDR-361 Generate Eclipse .project file even if project has no + nature. Also prevent generation of .project if project has + children. (Antoine Toulme) +* Fixed: BUILDR-364 Package spec should be set to a Symbol when :file is + used (Klaas Prause) +* Fixed: BUILDR-365 test task should use test compile dependencies +* Fixed: BUILDR-366 Scala dependencies should be lazily loaded into + Java.classpath +* Fixed: BUILDR-373 Package type specific implementations of install, + uninstall and upload are not invoked (Antoine Toulme) +* Fixed: BUILDR-374 upload tasks can attempt to upload artifacts multiple times (Pepijn Van Eeckhoudt) +* Fixed: BUILDR-379 Ant sql task abruptly terminates buildr +* Fixed: BUILDR-380 GitRelease: recursive search for root '/' does not work + under Windows (Antoine Toulme) +* Fixed: BUILDR-381 JUnit tests on Groovy project fail with + NoClassDefFoundError: junit/framework/TestCase +* Fixed: BUILDR-382 Packages with default spec are not always created correctly +* Fixed: BUILDR-383 artifact().from(task_dependency) should not trigger + task_dependency if artifact exists +* Fixed: BUILDR-384 Buildr fails with rubygems 1.3.6 +* Fixed: BUILDR-386 Display JRuby version in buildr -V (Antoine Toulme) +* Fixed: BUILDR-388 Continuous Compilation Support for Sub-Projects +* Fixed: BUILDR-391 resources task does not detect changes +* Fixed: BUILDR-392 Array values not flattened in (one version) of eclipse + task properties (Antoine Toulme, Peter Dettman) +* Fixed: BUILDR-306 Cobertura extension does not handle dependencies + correctly (Pepijn Van Eeckhoudt) +* Fixed: BUILDR-398 FileUtils#sh does not work correctly on Windows + (Pepijn Van Eeckhoudt) +* Fixed: BUILDR-399 invoke_with_call_chain does not restore call chain + correctly (Pepijn Van Eeckhoudt) +* Fixed: BUILDR-418 jruby exception: `ffi_libraries': no library specified +* Fixed: BUILDR-442 Errors while running the specs with jruby 1.5 +* Fixed: BUILDR-449 Fix failing specs on Windows (Pepijn Van Eeckhoudt) +* Fixed: buildr test=all didn't run all tests as expected +* Fixed: Fail-fast if package.with() or include() called with nil values +* Fixed: Failures not reported correctly for ScalaTest (Alex Eagle) +* Fixed: Test dependencies should include test compile dependencies +* Fixed: Classpath correctly passed to Scala shell +* Fixed: Removed redundant tracing of command arguments +* Fixed: filter.using(hash) now correctly substitutes mappings with boolean + "false" value +* Fixed: BUILDR-404 buildr -V causes exception on JRuby +* Fixed: BUILDR-411 fix for RDoc generation +* Fixed: BUILDR-417 package_as_javadoc calls deprecated method + (Pepijn Van Eeckhoudt) +* Fixed: BUILDR-412 Gemspec dependencies don't add up - to the point it's not possible to release +* Fixed: BUILDR-414 Provide tag_name method on GitRelease as part of API +* Fixed: BUILDR-419 Exclusion patterns only work if they contain a wildcard +* Fixed: BUILDR-421 The MANIFEST.MF file packaged by Buildr as permissions set to 600 +* Fixed: BUILDR-423 MANIFEST.MF files are not closed, leading to open files leak. +* Fixed: BUILDR-447 Path object do not include empty dirs in base directory (Peter Donald) +* Fixed: BUILDR-457 package(:jar) adds . entry to the jar + +1.3.5 (2009-10-05) +* Added: Interactive shell (REPL) support +* Added: BeanShell as default shell for java projects, bsh is small and it's + syntax provides the closest to an interpreted java. The BeanShell + console includes a graphical class browser. Shell is named :bsh +* Added: Mandriva (urpmi) installation support (with help from Franck Villaume). +* Added: BUILDR-56 Download Scala artifacts if not available locally +* Added: BUILDR-163 cobertura:check (Marko Sibakov, Daniel Spiewak). +* Added: BUILDR-295 Eclipse task: make 'M2_REPO' repository variable configurable +* Added: BUILDR-300 Make Eclipse task more configurable (Antoine Toulme, Alex Boisvert) +* Change: Upgraded to rubyforge-1.0.5 and net-ssh 2.0.15 +* Change: Monkey-Patched FileUtils::sh on JRuby to use POSIX `system` +* Change: Updated to Rake 0.8.7, RSpec 1.2.8 and JRuby-openssl 0.5.2. +* Change: Updated to easyb 0.9 (Joel Muzzerall) +* Change: Updated to TestNG 5.10 +* Change: Updated to JRuby 1.3.1 +* Fixed: BUILDR-23 Support for setting file mode when packaging (Ittay Dror). +* Fixed: BUILDR-278 tasks/*.rake files are loaded after the buildfile (Rhett Sutphin) +* Fixed: BUILDR-282 release goal should not strip leading '0' digits from version numbers. +* Fixed: BUILDR-289 Improved error message when JAVA_HOME points to an invalid JRE/JDK installation +* Fixed: BUILDR-290 Dependencies cannot be downloaded over SSL. +* Fixed: BUILDR-291 Local tasks do not support arguments (Ittay Dror). +* Fixed: BUILDR-292 Workaround for JRUBY-3381 on FileUtils.mv +* Fixed: BUILDR-301 TestNG doesn't report failure if more than one test fails +* Fixed: BUILDR-307 Failures are not reported correctly for ScalaTest (Jeremie Lenfant-Engelmann) +* Fixed: BUILDR-313 Prevent release with uncommitted_files on Git 1.4.3+ (Alexis Midon) +* Fixed: BUILDR-315 Fix Eclipse .classpath for local libraries (Mat Schaffer) +* Fixed: BUILDR-304 Referencing an existing package task using the package + method fails if the package has a custom filename (Rhett Sutphin) +* Fixed: BUILDR-322 When specifying files (instead of directories) as sources for compile task, + Buildr uses target directory timestamp only (not compiled output timestamp) +* Fixed: BUILDR-324: Regression - baseDir system property is not set when executing tests [Alexis Midon] +* Fixed: BUILDR-325: Overriding package spec with classifer doesn't work (Antoine Toulme) + +1.3.4 (2009-04-21) +* Added: BUILDR-93 Add specs for ScalaCheck integration +* Added: BUILDR-94 Add specs for Scala Specs integration +* Added: BUILDR-136 Support Scala/Java Joint Compiler (Daniel Spiewak). +* Added: BUILDR-159 Improved 'check' to accept both tar and tgz archives. +* Added: BUILDR-164 New 'artifacts:sources' task to download source code + for artifact jars. +* Added: BUILDR-222 Support Git as a version control system +* Added BUILDR-223 Release Task: customizable commit message +* Added: BUILDR-242 Include Scala-Tools Repository by Default. +* Added: BUILDR-268 Allow proxying for https connections (Joel Muzzerall). +* Added: Info message "Packaging filename.ext" now displayed for packaging tasks +* Added: Added Scala.version and Scala.version_str +* Change: require 'buildr/scala' is now officially required to use Scala features +* Change: Introduced new options from Rake 0.8.3: -I (libdir), -R (rakelib), + --rules, --no-search, --silent. +* Change: Upgraded to Rubyforge 1.0.1. +* Change: Upgraded to use Rake 0.8.4. +* Change: Upgraded to use Net-SSH 2.0.11. +* Change: Upgraded to use RSpec 1.2.2. +* Change: Upgraded to use JRuby 1.1.6 (when auto-installing). +* Change: Buildr, no longer in incubation (hurray!): new site, mailing list, SVN, Git. +* Change: BUILDR-171 Eclipse task generates meta-data files for projects with + test source code but no main source code. +* Change: BUILDR-177 Moved cobertura and emma extensions to lib directory. +* Change: BUILDR-187 Source code attachment for Eclipse .classpath. +* Change: BUILDR-188 Source code attachment for IDEA .iml file (Marko Sibakov). +* Change: BUILDR-209 Scala Specs Should Use src/specs/scala/ +* Change: BUILDR-237 Use MacPorts Scala on OS X. +* Change: BUILDR-260 Upgrade to Scala 2.7.3 compatible dependencies: + ScalaSpecs 1.4.3, ScalaCheck 1.5 and ScalaTest 0.9.5 +* Change: Buildr now uses Jekyll to generate Web site/documentation: +http://github.com/mojombo/jekyll/ This replaces Docter so less code to +maintain and the same Textile/Liquid mechanism as when using Github pages. +* Change: To access Release object (e.g. to set tag_name) use Release.find. +* Fixed: Removed double complete/fail messages showing up on console. +* Fixed: BUILDR-140 Get rid of const_defined? all across the board. +* Fixed: BUILDR-158 Nailgun is now a delegate for buildr/drb (a pure-ruby dRuby server) +* Fixed: BUILDR-170 ArtifactNamespace#method_missing has a condition that is never true. +* Fixed: BUILDR-172 Scala compiler not loaded by default. +* Fixed: BUILDR-175 Fail to find child project when calling project method inside project definition. +* Fixed: BUILDR-185 Exception if using artifact names with hyphen (Joel +Muzzerall). +* Fixed: BUILDR-192 TestNG report results are overwritten (Alexis Midon). +* Fixed: BUILDR-193 TestNG uses project name for suite name (not valid file + name on Windows). +* Fixed: BUILDR-194 Buildr always adds 'Manifest-Version' to generated manifest file. +* Fixed: BUILDR-198 Filter#run always calls mkpath with :verbose. +* Fixed: BUILDR-199 ArchiveTask#needed uses 'each' with no effect (Ittay Dror). +* Fixed: BUILDR-201 Sample project is not valid (Alexis Midon). +* Fixed: BUILDR-214 Buildr is stuck uploading to sftp repository (Heikki Hulkko). +* Fixed: BUILDR-216 Profiles documentation is wrong (Shane Witbeck). +* Fixed: BUILDR-218 Manifest.from_zip fails if the zip doesn't already have +META-INF/MANIFEST.MF (Joel Muzzerall). +* Fixed: BUILDR-226 Release task should use XML output of "svn info" instead +of human-readable output (Alexis Midon). +* Fixed: BUILDR-230 release task fails if there's a space in the path to the +Buildfile. +* Fixed: BUILDR-235 JRuby download link is broke (Alexis Midon). +* Fixed: BUILDR-239 HTTP redirects lose authentication information (Joel +Muzzerall). +* Fixed: BUILDR-240 Make TestNG print traces in the console (Alex Midon). +* Fixed: BUILDR-241 IDEA7X IPR generation does not pay attention to base_dir +for submodules (Rhett Sutphin). +* Fixed: BUILDR-247 OpenObject does not work with Hash#only (Rhett Sutphin). +* Fixed: BUILDR-253 ZipTask now uses Zlib::DEFAULT_COMPRESSION instead of NO_COMPRESSION +* Fixed: BUILDR-255 tasks/*.rake files are loaded more than once. +* Fixed: BUILDR-261 ScalaSpecs should be run with Scala dependencies +* Fixed: BUILDR-263 package(:war).merge not working correctly with exclude() +* Fixed: BUILDR-271 Using buildr --environment leads to "Don't know how to +build task XXX". +* Fixed: BUILDR-269 rspec bdd is broken (Jeff Hodges) +* Fixed: BUILDR-272 'rake gem' does not build gem under JRuby (Clinton R. +Nixon). +* Remove: BUILDR-215 buildr:freeze and unfreeze tasks don't work since we're +no longer running with the rake command. + +1.3.3 (2008-10-08) +* Added: JtestR support. Implemented pending jtestr specs. +* Added: Growl notifications (OS X only). +* Added: error, info and trace methods. +* Added: Release task support for alternative SVN repository layout + (e.g., http://my.repo.org/trunk/foo). +* Added: BUILDR-128 Emma support +* Added: BUILDR-135. Extracted reusable replacement logic into Filter::Mapper +* Added: BUILDR-148 It is now possible to set the version of various 3rd + party libraries from the build.yml file. Supported libraries + include Ant and the various test and BDD frameworks. +* Change: Error reporting now shows 'buildr aborted!' (used to say rake), + more of the stack trace without running --trace, and when running + with supported terminal, error message is red. +* Change: Eclipse task updated to documented Scala plugin requirements + (http://www.scala-lang.org/node/94) +* Change: Buildr.application.buildfile returns a task instead of a String. +* Change: BUILDR-104 Buildr::group has :under and :version, but not :type. + Now it has :type too (Lacton). +* Change: BUILDR-139 Incremental test run. +* Change: BUILDR-141 Removed NEXT_VERSION from release task. +* Change: BUILDR-148 ant-junit no longer included in root classpath, but + specified during taskdef. +* Change: BUILDR-153 To customize the SVN tag used by the release task, set + Release.tag_name to either the tag value or a proc that takes the + version number and return the desired tag. +* Fixed: Should not display "(in `pwd`, development)" when using --quiet. +* Fixed: Release task's regexp to find either THIS_VERSION and VERSION_NUMBER. +* Fixed: BUILDR-106 download(artifact(...)=>url) broken in certain cases + (Lacton). +* Fixed: BUILDR-108 Trace to explain why a compile is done (Lacton). +* Fixed: BUILDR-109 Failure of "Buildr::Filter should respond to :include and + use these inclusion patterns" (Lacton). +* Fixed: BUILDR-110 Error creating buildfile from POM when missing plugin + configuration (Geoffrey Ruscoe). +* Fixed: BUILDR-112 Using a user gem repository with 'rake setup' (Lacton). +* Fixed: BUILDR-114 Hash.from_java_properties does not behave + like java.util.Properties (Lacton). +* Fixed: BUILDR-116: TestTask should include the main compile target in its + dependencies, even when using non standard directories (Lacton). +* Fixed: BUILDR-117 Shared directory for both code and resources produces + duplicate Eclipse classpath entries (Nathan Hamblen) +* Fixed: BUILDR-119 Eclipse task does not accept test resource folders + (Lacton) +* Fixed: BUILDR-122: eclipse task should not check for directory existence +* Fixed: BUILDR-123: eclipse task should honor ResourceTask's target directory +* Fixed: BUILDR-124 unzip(...).from_path does not work correctly without + include (Rhett Sutphin). +* Fixed: BUILDR-126 Tests options are shared between unrelated projects when + using #options instead of #using (Lacton). +* Fixed: BUILDR-129. Modifying a project manifest should not alter it's + parent project manifest. +* Fixed: BUILDR-137 JRuby 1.1.3 and Buildr 1.3.2 don't appear to work + (on Windows). +* Fixed: BUILDR-138 ScalaTest premature use of Buildr::Repositories + inconsistent with customizing locations. +* Fixed: BUILDR-152 Project.task fails when task name starts with a colon. +* Fixed: BUILDR-157 Tasks library not loaded from a submodule. +* Docs: BUILDR-111 Troubleshoot tip when Buildr's bin directory shows up in + RUBYLIB (Geoffrey Ruscoe). + +1.3.2 (2008-07-18) +* Added: --prereqs command line argument to show all tasks and their +dependencies. You can also follow with regular expression to narrow down the +list of tasks. +* Changed: Upgraded to Rubyforge 1.0.0. +* Changed: BUILDR-86 Use newest versions of net-ssh and net-sftp gems. +* Changed: BUILDR-88 Test classes/resources should come before compile +classes/resources so they load up earlier in java classpath. +* Changed: BUILDR-102 Update JUnit Version to 4.4. +* Fixed: BUILDR-73 idea7x task incorrect adds target/resources to the sources +paths. +* Fixed: BUILDR-76 Added more specs and fixes to compile task. +* Fixed: BUILDR-77 Layout feature not working. +* Fixed: BUILDR-79 Remove :source option for Scala compiler +* Fixed: BUILDR-80 Fix reference to Util#timestamp method on nailgun addon. +* Fixed: BUILDR-82 Temporary work around for Net::SSH 2.0.2 attempting to +load Pageant DLLs when running on JRuby/Windows. +* Fixed: BUILDR-89 JUnit (and all other Java frameworks) no longer include +abstract classes. +* Fixed: BUILDR-90 Installing from source doesn't work with JRuby. +* Fixed: BUILDR-91 When doing a release, buildr should spawn the same version +of buildr +* Fixed: BUILDR-92 IDEA 7x: add resources directories to classpath. +* Fixed: BUILDR-95: Only download Scala test framework artifacts when required +* Fixed: BUILDR-100 Directory structure documentation needs updating. +* Fixed: Installation instructions updated for RubyGems 1.2.0. + +1.3.1.1 (2008-06-04) +* Fixed: BUILDR-78 Broken dependency on Rubyforge Gem. + +1.3.1 (2008-05-19) +* Added: Downloading files from SFTP server, uploading to HTTP. +* Added: jibx_bind method to use JiBX for Java<=>XML binding (by David +Peterson). +* Changed: Upgraded to Net::SSH 2.0 and Net::SFTP 2.0. +* Fixed: BUILDR-67 HTTP GET now works with query parameters (Tommy Knowlton). +* Fixed: BUILDR-68 Now accepting JAVA_HOME setting on OS X (Nathan Hamblen). +* Fixed: JUnit now accepts java_args and passes these arguments to the JVM +(only applicable when forking). +* Fixed: BUILDR-70 JUnit not passing environment variables from the +:environment option. +* Fixed: BUILDR-75 Filter now runs if there's a target directory, even if +there are no source files to copy over, since everyone else just checks +resources.target for existence before depending on it. +* Fixed: BUILDR-63 Possible fix. + +1.3.0 (2008-04-25) +* Added: Testing with EasyB (Nicolas Modrzyk). +* Added: Testing with JBehave (John Layton). +* Added: Testing with RSpec (Nick Sieger). +* Added: Nailgun integration for improved user experience when running on +JRuby. +* Added: Cobertura tasks can be invoked for a single project using project +name as prefix to cobetura tasks. +* Added: Cobertura can exclude specified classes from instrumentation. +* Added: ArchiveTask#clean can be used to remove content from a package. +* Added: Groovy compiler. +* Added: Mechanism to simplify creating extensions (see Extension module). +* Added: To run all test cases 'rake spec'. Test coverage reports will show +up in html/coverage. To run failing tests against, 'rake failing'. +* Added: Layout class for controlling the project layout. Also cleaned up +places where paths were used instead of path names. +* Added: HTTP Basic authentication support (Yuen-Chi Lian). +* Added: EAR packaging (Victor Hugo Borja). +* Added: Profiles(.yaml), based on the code provided by Yanko Ivanov. +* Added: Resources task picks the default mapping from the filter element of +the current profile (if specified). +* Added: Consolidated API for RJB and JRuby, replacing the now deprecated +JavaWrapper. +* Added: JRuby 1.1 support (Victor Hugo Borja, Nick Sieger). +* Added: IDEA 7 task: use buildr idea7x (Shane Witbeck). +* Added: Experimental support for installing/loading Gems as part of a build. +* Added: Experimental support for YAML configurtion files: +~/.buildr/settings.yaml, build.yaml and profiles.yaml. +* Added: Ability to create a package that is not an artifact and specify the +target file using the :file argument. +* Changed: JUnit/TestNG test cases are selected by superClass or annotations, +not by class-name pattern. +* Changed: Upgraded to Antwrap 0.7.0, thanks to Caleb Powell for relicensing +under Apache License. +* Changed: Upgraded to Rake 0.8, RSpec 1.1, RJB 1.1, OpenJPA 1.0.1. +* Changed: Resources are now copied to target/resources instead of +target/classes, and target/test/resources instead of target/test-resources. +* Changed: Test cases are now compiled into target/test/classes instead of +target/test-classes. +* Changed: Compile extension and CompileTask are now separate from the Java +module. Multiple compilers can be used, either guessed from the project +layout, or specified with compile.using(:name). +* Changed: Test extension and TestTask are now separate from the Java module. +JUnit and TestNG are Java specific extensions picked using test.with(:name). +* Changed: For compile and test, use dependencies instead of classpath (with +works are before). +* Changed: Test framework componentized along the same lines as the +compilers. +* Changed: The way packaging is handled: package_as_[type] is now called once +for a given package with the exact file name. If packaging requires a change +to the specifiction (e.g. a different file type than the package type), add a +package_as_[type]_spec method. +* Changed: The default packaging type is inferred from the compiler, and +without a compiler, defaults to :zip. +* Changed: JUnit test framework now runs on all classes that extend +junit.framework.TestCase. +* Changed: Scalac compiler now used by the regular compile task, the scalac +task is deprecated. +* Changed: RDoc are now generated using Allison +(http://blog.evanweaver.com/files/doc/fauna/allison). +* Changed: Resource tasks no longer generate target directory if there are no +resources to copy over. +* Changed: To prevent collissions with required files, the source layout now +places everything under lib/buildr, so require 'core/compile' is now require +'buildr/core/compile'. +* Changed: The various Java tasks (JavaCC, XMLBeans, JDepends, etc) are now +located in the extra directory, and may at some point relocate to an addon +Gem. +* Removed: Prepare tasks removed. +* Removed: All deprecated features since 1.1. If you've seen warnings before, +except the build to break. +* Removed: No longer using Facets or recommending you use it in buildfiles. +* Fixed: More typos/documentation fixes by Lacton +* Fixed: Artifact.pom resolves artifact without classifier, i.e +org.testng:testng:jar:jdk15:5.1 uses org.testng:testng:pom:5.1 (Tommy). +* Fixed: More patches towards JRuby support, courtesy of Vic Borja. +* Fixed: Error when downloading a file from a server which answers with a +response with no content length. +* Fixed: Improved the Eclipse task (BUILDR-17): removed resources target +directory from the source directories, made the main resource directories +relative to the project directory and reordered project elements (Thomas +Marek). +* Fixed: When compiling Scala only include scala-library and scala-compiler +JARs (John Layton). +* Fixed: POM generation now applies JAR as default packaging if unspecified +(Maarten Billemont). + +1.2.10 (2007-11-26) +* Changed: Resources sets permission on copied files to make them +read/write-able (Shane Witbeck). +* Changed: Artifact download no longer generates destination directory if not +downloaded (Antoine). +* Fixed: EOL in MANIFEST.MF. +* Fixed: Bunch of typos, courtesy of Merlyn Albery-Speyer and Soemirno +Kartosoewito. + +1.2.9 (2007-11-08) +* Changed: Upgraded to RJB 1.0.11. +* Fixed: Backward compatibility in Java.rjb/wrapper. + +1.2.8 (2007-11-01) +* Added: Resolving Maven snapshots from remote repository (Rhett Sutphin) +* Changed: scala options.target now takes number, e.g. "1.5" instead of +"jvm-1.5" (Nathan Hamblen) +* Changed: Eclipse task uses updated Scala plugin nature and builder (Alex +Boisvert) +* Fixed: Bringing Buildr back to 1.0.9, XMLBeans fix. + +1.2.7 (2007-10-29) +* Added: You can create an artifact from a given file using +artifact().from(). You can then install it into the local +repository or upload it to the release server using install() and +upload(). (Idea: Shane Witbeck and Tommy Mason). +* Added: ANTLR support. +* Changed: Speed boost to ZIP packaging. +* Changed: RjbWrapper is now JavaWrapper, and revised to nicely support JRuby. +A few other minor tweaks to make JRuby support possible in the future. (Travis +Tilley) +* Changed: JUnit now runs tests with clonevm false by default, you can change +with test.using :clonevm=>true (Karel) +* Changed: JUnit now switches over to project's base directory. +* Changed: package(:war).with(:libs, :classes) uses only these specified libs +and class directories, replacing any previous value. +* Fixed: Jetty task no longer sets "log4j.configuration" system property +* Fixed: release task didn't work + +1.2.6 (2007-09-26) +* Added: Option for setting environment name (-e) and attribute accessor +(Buildr.environment). Default taken from BUILDR_ENV environment variable. +* Added: AAR packaging for Axis2 service archives (Alex Boisvert) +* Added: Environment variable for JUnit tests (test.using :environment=>). +* Added: tar method similar to zip method. +* Added: Experimental transitive method. Looks like artifacts, quacks like +artifacts, but returns artifacts by the boat load. (Credit, Daniel Roop) +* Changed: Now accepting JAVA_OPTS in addition to JAVA_OPTIONS. +* Changed: TarTask is now based on ArchiveTask, same API as ZipTask. +* Changed: Javadoc array arguments now passed as multiple command line options +(e.g. :link=>['foo', 'bar'] becomes --link foo --link bar). (Daniel Roop) +* Changed: Jetty task now uses SLF4J instead of commons-logging + log4j for +better hot-swap capability and plugability (Alex Boisvert) +* Removed: Turns out --verbose command line option is useless. Removed. +* Fixed: Jetty task now uses WebAppContextClassLoader to support hot-swapping +webapps (Alex Boisvert) +* Fixed: "release" task now works with SVN URLs ending with /branches/*/ (Alex +Boisvert) +* Fixed: Resources not included in JAR/WAR unless there's a src/main/java +directory (Olexandr Zakordonskyy). +* Fixed: Files starting with dot (e.g. .config) not copied over as resource +files, and not included in ZIP (Olexandr Zakordonskyy). +* Fixed: Empty directories not copied over as resources (Olexandr +Zakordonskyy). +* Fixed: JAVA_OPTS and test.options[:java_args] not passed to JUnit task +(Staube). +* Fixed: archive.exclude doesn't work when including a directory using +:from/:as option. +* Fixed: JUnit/TestNG no longer run inner classes as test classes (Mark +Feeney). + +1.2.5 (2007-08-13) +* Fixed: Buildr not finding buildfile in parent directory, or switching to +parent directory. +* Fixed: checks.rb:103: warning: multiple values for a block parameter (2 for +1) +* Fixed: ZIPs include empty META-INF directory. + +1.2.4 (2007-08-03) +* Added: Forking option for JUnit test framework: :once to fork for each +project, :each to fork for each test case, and false to not fork. (Tammo van +Lessen) +* Added: Path traversal in Zip, so zip.path("foo/bar").path("..") returns +zip.path("foo"). +* Fixed: JUnit test framework output shows errors in console, more readable +when forking is on (Tammo van Lessen). +* Fixed: Cobertura reports not working (Anatol Pomozov). +* Fixed: Zip creates funky directory name when using :as (Tommy Mason). +* Fixed: package_as_tar incorrectly calling with(options) (Tommy Mason). +* Fixed: Loading of everything which should get rid of "already initialized +constant VERSION" warning. +* Fixed: --requires option now works properly when using buildr. +* Fixed: MANIFEST.MF lines must not be longer than 72 characters (Tommy +Mason). +* Fixed: Creating manifest from array does not place Name first. +* Fixed: Complain if no remote repositories defined, add at least one +repository when creating from POM, POM reader fails if dependencyManagement +missing (Jean-Baptiste Quenot). +* Fixed: Not looking for buildfile in parent directory. +* Fixed: Project's compile/test task looking for options in local task of same +name. +* Fixed: ZIP/JAR/WAR include directory entries in some cases and not others. +* Fixed: Computation of relative paths in Eclipse project generation (Cameron +Pope) + +1.2.3 (2007-07-26) +* Added: Get your buildfile created form existing POM, just run buildr on +existing Maven project (Anatol Pomozov). +* Added: package(:tar), package(:tgz), TarballTask dn TarTask (Tommy +Knowlton). +* Changed: The ArchiveTask needs no introduction: it's a base task that +provides common functionality for ZipTask, TarTask and friends. +* Fixed: Release runs buildr instead of buildr.cmd on Windows (Chris Power). +* Fixed: Cobertura reports broken (Anatol Pomozov). + +1.2.2 (2007-07-18) +* Added: resources.using and filter.using now accepts a format as the first +argument, default being :maven, but you can also use :ant, :ruby or pass a +regular expression +(http://groups.google.com/group/buildr-talk/browse_thread/thread/5216d5ae8bfff29b). +* Fixed: Sleek upload with changelog for each release courtesy of Anatol +Pomozov. +* Fixed: Zip.path.contains fails on paths with more than one directory +(http://groups.google.com/group/buildr-talk/browse_thread/thread/5d305bbeeb814d1). +* Fixed: Speed of sorting entries when creating new Zip file +(http://groups.google.com/group/buildr-talk/browse_thread/thread/8b4d1b0e983f32f). +* Fixed: Uploading using SFTP creates directory for uploaded file +(http://groups.google.com/group/buildr-talk/browse_thread/thread/80021d35cecfecdc). + +1.2.1 (2007-07-12) +* Added: Proxy exclusion, use environment variable NO_PROXY, or +options.proxy.exclude = || [] +(http://groups.google.com/group/buildr-talk/t/9f1e988e0dbeea9f). +* Added: You can now copy resources from multiple source directories, using +resources.from +(http://groups.google.com/group/buildr-talk/browse_thread/thread/4f2867a6dbbc19d4). +* Added: Hash.from_java_properties(string) and hash.to_java_properties. +* Changed: Buildr.options now wrap various environment variables instead of +duplicating them (HTTP_PROXY, NO_PROXY, TEST, DEBUG). +* Changed: No longer passing proxies to transports, instead they obtain them +from environment variables. +* Changed: Buildr now uses XJavaDoc 1.1 instead of 1.1-j5. If you need the +1.1-j5 fix, see here +http://groups.google.com/group/buildr-talk/browse_thread/thread/49f3226810466c94/1f0d25d002433fe2. +* Fixed: One RubyForge release for all packages, instead of one per package +(Anatol Pomozov). +* Fixed: buildr command does not recognize project tasks (foo:compile) or +default task (http://groups.google.com/group/buildr-talk/t/660061a0bc81989a). +* Fixed: Upload fails on SFTP permissions. +* Fixed: Hibernate.schema_export not passing Ant task when yielding. +* Fixed: IntelliJ Idea project files generation for projects more than two +degrees deep. + +1.2.0 (2007-06-06) +* Added: Artifact.list returns specs for all registered artifacts (those +created with artifact or package). +* Added: Buildr.option.java_args are used when creating the RJB JVM, when +running a Java process (unless you override directly), and when running JUnit +tests (again, unless override). +* Added: TestNG support (test.using :testng). +* Added: You can run multiple tests from the command line, e.g. rake +test:foo,bar. +* Added: If you want to distribute source code and JavaDoc alongside your JARs +(helpful when using IDE/debugging), you can now do so by calling +package_with_sources and package_with_javadoc on the project (or the parent +project to affect all its sub-projects). +* Added: junit:report task generates XML and HTML reports in the reports/junit +directory. +* Added: test=all option runs all test cases ignoring failure. +* Added: project generation for IntelliJ Idea. Imports dependencies properly +from your local repository (the M2_REPO path variable must be defined), +supports tests and resources. +* Added: A check task for each project that runs after packaging and can be +used to check the build itself, using RSpec matchers. +* Added: The help task can be used to get basic information about your build. +Right now it returns a list of described tasks, but you can extend it using +the help method. Try it out: rake help. +* Added: Integration tests that run after packaging (unless tests are +disabled). There's only one integration tests task (duh) that you can access +from anywhere. You can tell a project to run its tests during the integration +phase with test.using :integration. +* Added: package :sources and package :javadoc, used by package_with_sources +and package_with_javadoc. +* Added: Unzip paths now return root/target. (Nathan) +* Added: buildr command line, replacing rake. Differs from rake in two ways: +uses buildfile by default (but Rakefile also works) and offers to create +buildfile if you don't already have one. +* Added: options.proxy.http now set from the environment variable HTTP_PROXY +(Anatol Pomozov). +* Added: options.java_args now set from environment variable JAVA_OPTIONS. +* Changed: Filter now complains if source directory or target directory not +set, or if source directory does not exist. +* Changed: Filter.run returns true if filter run, false otherwise, and can be +run multiple times. +* Changed: repositories.proxy returns a URI or nil; you can still set a proxy +using a hash. +* Changed: Transports went the way of the Dodo, instead we now use +read/write/download/upload methods implemented on URI itself. +* Changed: We now have a way to configure multiple proxies through the +options.proxy method; use that instead of repositories.proxies. +* Changed: Upgraded to Ant 1.7.0, JUnit 4.3, JMock 1.2. +* Changed: TestTask now provides list of test classes and failed classes +through test_classes and failed_tests attributes. +* Changed: The jetty method is now available everywhere, so you can change the +URL using jetty.url = at the top of the Rakefile. Also upgraded to 6.1.3. +* Changed: Test classes are now identified as either starting with Test* or +ending with *Test, before attempting any include/exclude patterns. Anything +ending with *TestCase or *Suite ignored for now (but if you explain why, we +can add it back). +* Changed: What used to be the projects task is now help:projects task, +anticipating more help: tasks to come. +* Changed: We now have 3(!) JDepend tasks: jdepend:swing (with windows!), +jdepend:text (console) and jdepend:xml (enterprisy). +* Changed: Good news for packagers: package_as_ yield no longer required, just +make sure to create the task once and return it each time. +* Changed: JUnit tests now run using Ant, which makes them faster to run, and +gives you text/XML reports (check out the reports/junit directory). +* Changed: Cobertura now writes reports to reports/cobertura, in fact, if +you're looking for a report of any kind, the reports directory is the place to +find it. +* Changed: Upgraded to AntWrap 0.6. Note that with AntWrap 0.6 we yield to the +block instead of doing instance_eval, so any call to the ant project must be +prefixed with an AntProject object. Code that relies on the old functionality +(and that's pretty much any code with element-containing tasks) will break. +* Changed: artifacts now accepts a struct. +* Changed: The repositories.download method folded into Artifact, the +repositories.deploy method renamed upload and folded into ActsAsArtifact. +* Changed: The deploy task is now called upload, and repositories.deploy_to is +now repositories.release_to. +* Removed: The check task, which previously was a way to find some circular +dependencies (multitask) but not others (dynamically defined). +* Removed: JUnitTask, test.junit and Java.junit methods all deprecated; +anything you need to affect the unit tests is right there in TestTask. +* Removed: The package(:jar) and package(:war) options, such as :manifest, +:include, :libs are all deprecated. Instead, use the package method to define +the package, and the with method to enhance it, e.g. +package(:war).with(:libs=>...) instead of package(:war, :libs=>...). +* Removed: The []= method on ZipTask and anything derived from it is +deprecated in favor of using attribute accessors. +* Removed: Ant.executable and Ant.declarative are deprecated. Use Buildr.ant +instead of Ant.executable. Use AntWrap directly if you need the +Ant.declarative functionality. +* Fixed: Filter now properly handles multiple keys on the same line. +* Fixed: Tests teardown now properly executing. +* Fixed: Cobertura tasks now run tests, even if test=no. +* Fixed: XMLBeans compile task not detecting change to XSD file. +* Fixed: URI.download and download task do not create directory path for +downloaded file (Anders Bengtsson). +* Fixed: Gets JVM version number from system property java.version instead of +calling java -version. +* Fixed: Artifact downloads POM first, such that you can download/create/fake +it youself. + +1.1.3 (2007-06-12) +* Added: Long awaited idea project files generation. Very early code, the iml +seems to be generated okay but needs testing. The ipr is still missing but +will come in due time (and it's not always necessary anyway). +*Fixed: Doc bug: unzip doesn't have an into(dir) method. +*Fixed: File names don't always have a dot. +*Fixed: For Jetty servers, http://foo//bar is not http://foo/bar + +1.1.2 (2007-05-29) +* Added: Allow passing :java_args option to the junit task +* Added: Hibernate XDoclet and SchemaExport tasks. (Requires buildr/hibernate) +* Added: JDepend UI for seeing depenencies across all projects. (Requires +buildr/jdepend) +* Added: Cobertura test coverage tasks, reporting both html and xml. (Requires +buildr/cobertura) +* Changed: tools_jar now returns empty array on OS X, part of the ongoing +Write Once/Test Everywere effort. (Credit Paul Brown) +* Fixed: Work around keep_alive bug in Net::HTTP. +(http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/10818) + +1.1.1 (2007-05-16) +* Changed: Test case class names must end with Test, TestCase, Suite or +TestSuite. +* Changed: You can now run rake test:{foo,bar} to match against either foo or +bar (requires \{..\} on UNIX). +* Changed: JAVA_HOME now required on all platforms, along with more OS X +fixes. (Credit Paul Brown) +* Fixed: You can now run rake test: from any directory, and it will find +just the right test cases. + +1.1.0 (2007-05-13) +* Added: Proxy setting for downloading from remote repositories (use +repositories.proxy = ...). +* Added: projects task to list all the projects you can build. +* Added: Project attribute target to specify the target directory. +* Changed: The project and projects methods now accepts relative names when +called on a project. For example, project("foo").project("bar") finds the +sub-project "bar" in "foo". +* Changed: The project method now returns self if called on a method with no +name. +* Changed: The -warning flag (javac) is now set to true only when verbose. +* Changed: OpenJPA mapping now using Ant task instead of spawning another Java +instance. +* Changed: The test:name pattern translates to *name* so you can run tests by +package name, but only if you don't use * in the pattern. +* Changed: All projects are not evaluated when referenced (i.e. calling +project/projects) or before running any task. Project tasks do not exist until +a projet is evaluated. +* Removed: The projects method no longer accepts the :in argument, call +projects on a project instead. +* Fixed: Local directory tasks now work from any directory in the project. +* Fixed: Artifacts no longer created with timestamp from server. +* Fixed: Buildr no longer fails when run without tools.jar or JAVA_HOME +(OS X). (Credit Lyle Johnson) +* Fixed: Manifest gets EOL to keep EOF company. (Credit Tommy Knowlton) +* Fixed: Compile tasks clean after themselves when target directory changed. +(Credit Lyle Johnson) + +1.0.0 (2007-05-04) +* Added: buildr:freeze and buildr:unfreeze task. These set the Rakefile to use +a particular version of Buildr, freezing by setting to the current version of +Buildr, unfreeze to use the latest Gem. +* Added: Buildr.options, with three options to start with: test, debug and +parallel. +* Added: Buildr.option.debug or environment variable DEBUG to control the +compiler debug option. Defaults to yes, except when doing a release. +* Changed: Buildr now fails nicely if JAVA_HOME not set. +* Changed: Migrated test cases to RSpec 0.9. +* Changed: Extended circular dependency check to multitask. +* Changed: JavaCC using RJB. +* Changed: OpenJPA 0.9.7 no longer snapshoted. +* Fixed: For Windows users: user's home directory, fu_check_options is now +rake_check_options, java command works around funky system bbug. + +0.22 (2007-04-26) +* Added: Calling projects(:in=>foo) returns only the sub-projects defined in +foo. +* Added: _() as shortcut for path_to(). +* Added: You can pass properties to java by setting the :properties options. +* Added: JUnit task has a way of setting options (options accessor and using +method), which for now supports passing properties to java. +* Added: You can now use the struct method to create a Struct for structoring +your multiple artifacts. +* Changed: Use rake artifacts to download all artifacts not already in the +local repository, and also download modified artifacts +(*cough*snapshots*cough*) +* Changed: Transport.download now uses timestamp on the destination file and +If-Modified-Since header to skip downloads of unmodified files. +* Changed: Downloading artifact sets the time stamp from the repository. +* Changed: Use buildr.rake in the project's directory and your home directory, +instead of buildr.rb. +* Changed: filter method accepts one argument, the source directory. Use +filter(src).into(target). +* Changed: Running Javac/Apt/Javadoc in process. +* Changed: Using Ant for OpenJPA enhancer and XMLBeans schema compiler. +* Changed: Jetty, JavaCC, OpenJPA and XMLBeans are no longer included by +default. You need to require them explicitly, e.g. require "buildr/jetty". +* Removed: Tasks no longer use a base directory, always map paths directly +using file, path_to or _(). +* Fixed: The artifacts task no longer downloads POMs for artifacts created by +the Rakefile. + +0.21 (2007-04-20) +* Added: Methods to read and write a file (shortcut for +File.read/File.open.write). +* Changed: Filter task now takes a source directory and target directory, and +copies all included (sans excluded) files between the two. +* Changed: Artifact type is now symbol instead of string (so :jar instead of +"jar"). You can still specify a string, but the return value from #to_spec or +#type is a symbol. +* Changed: Eclipse task now adds "src/main/resources", "src/test/java", +"src/test/resources" to build path, and excludes ".svn" and "CVS" directories +from being copied into target directories. +* Changed: The test task will now run JUnit test cases from classes ending +with Test or Suite. And the inclusion pattern is always set. +* Fixed: Project property not inherited if false. + +0.20 (2007-04-18) +* Added: JavadocTask to generate Javadoc documentation for the project, +javadoc method on the project itself to return its javadoc task, and +Java.javadoc to do all the heavy lifting. +* Changed: Release code is now implemented as module instead of class. SVN +copy made from working copy instead of double commit. +* Removed: package :file_name options. Does not work with deployed artifacts +or POMs. +* Fixed: Packages not deployed in the right path (but POMs are). +* Fixed: JARs and WARs include redundant META-INF directory. +* Fixed: The local package task is now a dependency for install/deploy, and +build is dependency for package. + +0.19 (2007-04-13) +* Fixed: Eclipse task correctly handles FileTasks +* Fixed: Eclipse task output directory is "target/classes" +(Project.compile.target) instead of "/target" +* Added: Set specific file permissions when uploading with SFTP transport with +:permission option +* Fixed: Correctly use JAVA_HOME environment variable, if available, for +determining java version +* Added: ConcatTask and concat: a file task that creates or updates the target +file by concatenating all the file prerequisites. +* Added: Ant module (requires antwrap and rjb Gems), so also added RJB setup +module. +* Added: When zipping you can include the contents of a directory using +:as=>".". +* Added: Convenience apt method returns a file task that generates sources +using APT. +* Added: Convenience open_jpa_enhance method to enhance compiled files. +* Added: Convenience compile_xml_beans setups the compiler to include +XSD-generated XML Beans. +* Added: Convenience javacc/jjtraa methods return file tasks that generate +source files. +* Added: build is now the default task. +* Added: jetty:start and jetty:stop tasks to start/stop the server from the +console. +* Added: jetty:use to start Jetty inside the build or hook to an existing +server. +* Added: jetty:setup and jetty:teardown to perform tasks around jetty:use. +* Added: The local build task will now execute the local test task. So +building a project (or sub-project) will run the test cases on that project +(or sub-project) but not any of its dependencies. +* Added: ZipTask accepts nested path (i.e. calling path inside a path). +* Added: package(:war) by defaults picks libraries from the compiler +classpath. You can always override by passing the :libs option. +* Changed: Eclipse task now generates library path with M2_REPO variable or +project-relative paths where appropriate +* Changed: compile.target (CompileTask) and resources.target (Filter) are now +file tasks, not strings. So passing the target to someone else will hopefully +convience them to invoke or enhance it. +* Changed: Java related tasks like OpenJPA, XMLBeans, JavaCC all moved to the +Buildr::Java module. +* Changed: Handling of package_as arguments to support JBI packaging. +* Changed: meta_inf project property is an array accepting filenames (strings) +and file tasks. +* Changed: meta_info by default only includes the LICENSE file from the +top-level project. +* Changed: The WarTask :classes argument is now a directory name, and will +include all files in this directory. +* Changed: WarTask and JarTask accept meta_inf argument. +* Changed: Behavior of needed? and prerequsities in base Rake::Task. This will +probably not affect you, but don't be surprised if it disappears (see +lib/core/rake_ext.rb for details). +* Changed: Were previous the test task would link to test.run, it now executes +the entire test lifecycle, and is the major point for extending the test +lifecycle. +* Changed: test.run is now test.junit. +* Changed: Ant.define is now Ant.declarative, Ant.execute is now +Ant.executable. +* Changed: The filter method now returns a Filter class that can be used to +set a filter, but is not itself a task. Instead, it creates a task when +setting its target. +* Changed: Project.resources now returns a ResourceTask that includes, but is +not itself a filter, accessed using the accessor filter. +* Changed: UnzipTask eliminated and replaced with Unzip which you now have to +run directly by calling extract. However, unzip method creates a file task +and returns an Unzip object that can be used as a reference to that file +task. +* Changed: Attributes is now InheritedAttributes. +* Changed: The first call to package configures the package task from the +options, the second call only returns the package task. +* Removed: :cp argument, always use :classpath. +* Removed: src_dir, java_src_dir, target_dir, webapp_src_dir and all other +premature configuration attributes. +* Removed: Project tests method deprecated in favor of a single test method; +it now accepts an enhancement block, not an instance_eval block. +* Removed: FilterTask is dead. +* Removed: sub_projects method. Is anyone using this? +* Fixed: Local buildr.rb not loaded from running from inside a sub-project +directory. +* Fixed: Eclipse task now executed whenever a change is made in the Rakefile, +or any file it requires, include buildr.rb and task files. +* Fixed: Circular dependency in release task. + +0.18 (2007-03-26) +* Added: manifest attribute on project, used by default when packaging +JAR/WAR. +* Added: default manifest includes build-by, build-jdk and +implementation-title. +* Added: compile.from(sources) in the same vein as compile.with(classpath) +* Added: load all *.rake files form the tasks directory (if exists) for use +in +the main Rakefile. +* Added: Java.tools returns a reference to tools.jar on JDKs that include it. +* Added: brought back experimental test tasks. +* Added: artifacts task to download all artifacts referenced by project (using +either artifact or artifacts method). +* Changed: back to old behavior, compile task only executes if there are any +files to compile, and compile? method removed. +* Changed: repositories.remote is now an array instead of a hash, and +repositories are searched in the order in which they appear. +* Changed: release task is now a regular task, using the Release object +instead of being a ReleaseTask. +* Changed: eclipse task executes artifacts task. +* Fixed: inherited attributes now cache default value, useful when working +with arrays/hashes. +* Fixed: manifest file generated even if manifest attribute is false. +* Fixed: compile task now properly detects when not all files compiled. +* Fixed: bug that caused project file tasks to execute twice. + +0.17 (2007-03-14) +* Added: project.task acts like Rake's task but can also fetch a task from a +project using the project's namespace. +* Added: project.file acts like Rake's file but resolve relative paths based +on the project base directory. +* Added: Rake tasks execute in the directory in which they were defined. +* Added: enhanced Rake with circular dependency, and you can find all circular +dependencies by running rake check. +* Added: enhanced Rake in_namespace, if the namespace starts with colon, +creates a namespace relative to the root instead of the current namespace. +* Changed: a project definition is now a task definition. +* Changed: use enhance to extend the project definition instead of +after_define. +* Changed: LocalDirectoryTask replaced with Project.local_task. +* Changed: projects method accepts multiple names, returning only these +project definitions, returns all of them with no arguments. +* Changed: packge only defines the essentials once, so you can call package on +a project to retrieve a specific package by type/id. +* Changed: zip task (and jar/war) no longer resolve artifacts for you, must +call artifacts directly. +* Changed: cannot access a project before it's defined, but can do that with +sub-projects to establish dependencies. + +0.16 (2007-03-07) +* Added: zip.include :as=> to include file under specified name. +* Added: zip.merge to include the (expanded) contents of one zip file in +another. +* Added: experimental test task using JUnit and JMock. +* Changed: project.to_s returns name, projects returns sorted by name. +* Changed: project definition now executed using project's base directory as +the current directory. +* Fixed: artifact test cases and minor code cleanup. +* Fixed: attempts to download artifact even if created by task. +* Fixed: release task now deletes old tagged copy and reports SVN usage. +* Fixed: OpenJPA not including target directory in classpath. + +0.15 (2007-02-28) +* Fixed: tasks fail unless deployment server specified. +* Changed: deploy method executes deployment, instead of returning a task. + +0.14 (2007-02-28) +* Added: check task that looks for obvious errors in the Rakefile. +* Added: deploy task for managing deployment. +* Added: release task that updates version numbers, commits and tags SVN. +* Changed: the project name is now the fully qualified name, e.g. ode:axis2 +* Changed: you can now lookup a project before it's defined; you still can +only define a project once. +* Changed: you can lookup projects by full qualified name. +* Changed: release_to changed to deploy_to, which is now a getter/setter. +* Fixed: removed Java.home which conflicted with JRuby. +* Fixed: install task did not re-install modified files. +* Fixed: deploying only uploads one artifact. +* Fixed: timing issues. +* Fixed: Maven classifier now used properly. + +0.13 (2007-02-26) +* Added: global java method. +* Added: project build method. +* Added: OpenJPA mapping_tool method. +* Added: Rakefile to generate Gem. +* Changed: you can now lookup a sub-project from the top project method. +* Changed: the projects methods return all sub-projects. +* Fixed: bug in JarTask that resolved artifacts too early. +* Fixed: global tasks (clean, build, etc) now complain if executed from a +directory that does not map to any project. +* Fixed: to work with Rake 0.7.2. + +0.12 (2007-02-24) +* Added: call prepare with list of tasks to add them as prerequisites. +* Added: project.id returns the compound name, e.g. foo, foo-bar, +foo-bar-baz. +* Added: JavaCC, XMLBeans schema compiler, OpenJPA enhancer, APT tasks. +* Changed: the default package ID is take from the project ID instead of its +name. +* Changed: renamed buildr and moved here. +* Changed: moved all code into Buildr module. +* Fixed: download breaking when POM not found. +* Fixed: compile task fails if classpath is empty. +* Fixed: zip task fails if target directory does not exist. +* Fixed: packaging task does not require build. +* Fixed: compiler not showing command when trace is on. +* Fixed: zip dependencies were all fucked up. +* Fixed: package should not depend on build. + +0.11 (2007-02-16) +* Added: test cases for unzip task +* Added: prepare method to access prepare task +* Added: prepare, compile and resources accept a block you can use to enhance +the task +* Changed: ZipTask executes all includes files as prerequisites, and now +includes directories correctly +* Changed: Jar/WarTask are now extended using with(options) method +* Changed: JarTask now accepts array of sections (each being a hash) for the +manifest, and a proc/method to generate it +* Changed: added HighLine to hide password entry on the command line +* Changed: unzip now using UnzipTask with its own shorthand syntax. +* Changed: filter task gets a consistent syntax to unzip + +0.10 (2007-02-13) +* Added: modifier for artifacts +* Added: ZipTask, WarTask +* Added: get POM artifact directly from artifact +* Changed: JAR and WAR packaging based on new and improved Zip task +* Changed: options for packaging, but not affecting current Rakefile +* Remove: delete task + +0.9 (2007-02-09) +* Added: attributes for configuring compile (sources, classpath, target, +options) +* Added: shorthand notation for specifying compilation (to, with, using) +* Changed: copy task is dead (name conflict), instead we get the better filter +task with include/exclude patterns +* Changed: rewrite of compile task, now better than ever +* Changed: compile can be used inside and outside project +* Changed: compiler no longer infers anything from its prerequisites +* Changed: compiler accepts files, artifacts and tasks on the classpath +* Changed: resources task now working as expected +* Remove: global task artifacts was the root of all evil and got canned. + +0.8 (2007-02-05) +* Added: release task and release_to configuration for repositories +* Added: SFTP uploader for releases +* Added: convenience method group() for specifying multiple artifacts in same +group and version number +* Added: install target copies package to local repository and adds a POM, +uninstall package removes package (and POM) from local repository +* Changed: project lookup now happens through project() method +* Changed: locating file in the local repository now happens through +Repositories +* Changed: downloading file into the local repository now happens through +Repositories +* Changed: notation for specifying multiple artifacts in a string is now +foo,bar,baz +* Changed: artifact identifier is now specified with the key :id +* Changed: download POM alongside artifact and install in local repository +* Changed: no more scoping artifacts collection in project, use compile.with +instead +* Changed: moved HTTP download logic to transports.rb +* Removed: deprecated grouping with multiple artifacts under id key + +0.6 (2007-02-01) +* Added: Artifact resolution introduces the notion of a spec, which can be +supported using ActsAsArtifact +* Added: You can now use a project as an artifact, resulting in all its +packages being added, or use a task as artifact +* Changed: project.sub_projects renamed project.projects +* Changed: what used to be called dependencies is now called artifacts +* Changed: all artifacts are now created as tasks that know how to download +themselves unless some other behavior is specified +* Changed: local and remote repositories are now defined on the Rakefile +instead of individual projects +* Changed: attributes now stored directly as instance variables +* Changed: ANSI colors and progress bar now using Ruby Facets + +0.5 (2007-01-24) +* Added: Build number for each top-level project, build_number method for +accessing it and build:increment task for updating the build number file. +* Added: to_path method on project to resolve paths relative to base_dir. +* Added: recursive_task method on project to create task in +project/sub-project. +* Added: compiler property for passing any options to Javac. +* Changed: remove task renamed uninstall. +* Changed: and to confuse more remove task (RemoveTask) renamed delete. +* Changed: consolidated before_create/after_create to on_create. +* Changed: version, group, artifact added as accessors to project. +* Changed: project definition block takes project as argument. +* Changed: project enhanced only if new settings or block. +* Changed: local_repository is now separate attribute from repositories. +* Changed: Directory structure, now split into rbs, rbs-java and tasks. +* Removed: project.options. Using a different attributes mechanism. + +0.4 (2007-01-23) +* Added: CopyTask now deals with files and directories, can copy multiple +files, and applies filter to all of them. Filter can be a hash or a proc. +* Added: Project gets resources_filter attribute that can be used to set the +filter on all copied resources. +* Added: HTTP module for getting and downloading files, and a download task. +* Changed: Dependencies now check signatures for every file, if available, and +show download progress. + +0.3 (2007-01-22) +* Added: Dependencies loaded from Maven repositories if not existing or built +by project. Use rake dependencies to force update, or let compilation take +care of it. +* Added: Copy task for copying one file to another, and filtering support. + +0.2 (2007-01-21) +* Added: remove task to get rid of packages added to the local repository. +* Changed: recompile project if any of its dependencies is newer than the +source code. Will cause recompile if any of the dependencies was compiled and +packaged again. +* Changed: compile task depends on javac task and resource copy tasks. This +might change when adding filtering later on. + +0.1 (2007-01-19) +* Added: build and clean tasks +* Added: resources are now copied over during compilation +* Added: POM file generated in local repository (keep Maven happy) +* Added: compile scope for use by javac +* Added: WAR packaging. +* Changed: Root project operates on the current directory, sub-projects on sub +directories. See Rakefile for example. diff --git a/buildr/Gemfile b/buildr/Gemfile new file mode 100644 index 0000000..a01bb93 --- /dev/null +++ b/buildr/Gemfile @@ -0,0 +1,4 @@ +# Use `bundle install` in order to install these gems +# Use `bundle exec rake` in order to run the specs using the bundle +source "http://rubygems.org" +gemspec diff --git a/buildr/LICENSE b/buildr/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/buildr/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/buildr/NOTICE b/buildr/NOTICE new file mode 100644 index 0000000..5bb5a67 --- /dev/null +++ b/buildr/NOTICE @@ -0,0 +1,26 @@ +Apache Buildr +Copyright 2007-2009 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + + +COPYRIGHT NOTICES +----------------- +Copyright 2007 Intalio + +This product includes software developed by Intalio +http://www.intalio.com + + +MATERIALS IN DOCUMENTATION +-------------------------- +The following materials are incorporated into the documentation +of this software. + +* DejaVu - http://dejavu.sourceforge.net +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. +Bitstream Vera is a trademark of Bitstream, Inc. + +* ColorCons - http://www.mouserunner.com/ +Copyright (c) 2006 Ken Saunders diff --git a/buildr/README.rdoc b/buildr/README.rdoc new file mode 100644 index 0000000..58020bf --- /dev/null +++ b/buildr/README.rdoc @@ -0,0 +1,134 @@ += Buildr + +This is Buildr, the build system that lets you build like you code. + +http://buildr.apache.org/ + +== Get Started + +=== Install Buildr + +Buildr needs Ruby 1.8 or later and RubyGems 0.9 or later. + +Windows users can get the one-click Ruby installer, which includes the latest +version of Ruby and RubyGems: + +http://rubyinstaller.rubyforge.org + +Make sure to set JAVA_HOME environment variable first, then: + + gem install buildr + +(Use sudo for Linux and OS X) + +More installation and setup instructions available online +http://buildr.apache.org/ + + +=== RTFM + +* Buildr documentation: http://buildr.apache.org +* More about Rake: http://docs.rubyrake.org +* Antwrap documentation: http://antwrap.rubyforge.org + + +=== Mailing list + +Users: users-subscribe@buildr.apache.org http://buildr.markmail.org/search/list:users + +Developers: dev-subscribe@buildr.apache.org http://buildr.markmail.org/search/list:dev + +Create your own Buildfile and start living the life! + + +== Where's My Ruby? + +Buildr needs Ruby 1.8 or later and RubyGems 0.9 or later. All other +dependencies are installed when you run: + + gem install buildr + +=== Windows + +Windows users can get the one-click Ruby installer, which includes the latest +version of Ruby and RubyGems: + +http://rubyinstaller.rubyforge.org + +Before installing Buildr, please set the JAVA_HOME environment variable to +point to your JDK distribution. Next, use Ruby Gem to install Buildr: + + > gem install buildr + +When prompted for a platform, select mswin32. + +=== Linux, BSD, Cygwin + +On Linux/BSD/Cygwin, use your default package manager, for example, for Ubuntu: + +$ sudo apt-get install ruby +$ sudo apt-get install ruby1.8-dev +$ sudo apt-get install build-essential +$ sudo apt-get install libopenssl-ruby + +Before installing Buildr, please set the JAVA_HOME environment variable to +point to your JDK distribution. Next, use Ruby Gem to install Buildr: + + $ sudo env JAVA_HOME=$JAVA_HOME gem install buildr + +When prompted for a platform, select ruby. + +=== OS X + +Leopard includes the latest version of Ruby, if you are using Tiger or an older +release, we recommend re-installing the latest: + +http://hivelogic.com/narrative/articles/ruby-rails-mongrel-mysql-osx + +To install Buildr: + + $ sudo gem install buildr + +When prompted for a platform, select ruby. + + +== Living On the Edge + +You can check the latest sources from SVN: + + svn co http://svn.apache.org/repos/asf/buildr/trunk + +Or browse the SVN repository online: +http://svn.apache.org/repos/asf/buildr + +If you prefer Git, a Git fork is available from http://github.com/buildr/buildr/tree/master: + + git clone git://github.com/buildr/buildr.git + +To install Buildr locally from source: + + cd buildr + rake install + +If the cutting edge doesn't work, make sure to check the CHANGELOG, to see +which changes might have broken your build. To run all the test cases: + + rake spec + + +== License + +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with this +work for additional information regarding copyright ownership. The ASF +licenses this file to you 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/buildr/Rakefile b/buildr/Rakefile new file mode 100644 index 0000000..6e52c31 --- /dev/null +++ b/buildr/Rakefile @@ -0,0 +1,47 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +# To work-around a bug with gemcutter: http://stackoverflow.com/questions/4932881/gemcutter-rake-build-now-throws-undefined-method-write-for-syckemitter +require 'psych' unless RUBY_PLATFORM[/java/] + +# We need JAVA_HOME for most things (setup, spec, etc). +unless ENV['JAVA_HOME'] + if RUBY_PLATFORM[/java/] + ENV['JAVA_HOME'] = Java.java.lang.System.getProperty('java.home') + elsif RUBY_PLATFORM[/darwin/] + ENV['JAVA_HOME'] = '/System/Library/Frameworks/JavaVM.framework/Home' + else + fail "Please set JAVA_HOME first (set JAVA_HOME=... or env JAVA_HOME=... rake ...)" + end +end + + +# Load the Gem specification for the current platform (Ruby or JRuby). +def spec(platform = RUBY_PLATFORM[/java/] || 'ruby') + @specs ||= ['ruby', 'java', 'x86-mswin32'].inject({}) { |hash, spec_platform| + $platform = spec_platform + hash.update(spec_platform=>Gem::Specification.load('buildr.gemspec')) + } + @specs[platform] +end + +# Tell us if we need sudo for various commands. +def sudo_needed? + Config::CONFIG['host_os'] !~ /windows|cygwin|bccwin|cygwin|djgpp|mingw|mswin|wince/i && !ENV['GEM_HOME'] +end + + +desc 'Clean up all temporary directories used for running tests, creating documentation, packaging, etc.' +task :clobber diff --git a/buildr/_buildr b/buildr/_buildr new file mode 100755 index 0000000..4cdaf92 --- /dev/null +++ b/buildr/_buildr @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +# Run buildr from source, specifically for testing stuff without doing a rake install. + +require 'rubygems' +$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib') << File.join(File.dirname(__FILE__), 'addon') + +require 'buildr/version' +spec = Gem::Specification.load(File.join(File.dirname(__FILE__), 'buildr.gemspec')) +# To avoid a warning about the version_requirements deprecation, we use this method inline. +def version_required(gem_def) + return Gem::Dependency.instance_methods.map(&:to_sym).include?(:requirement) ? gem_def.requirement : gem_def.version_requirements +end +spec.dependencies.each do |dep| + gem dep.name, version_required(dep).to_s if dep.type == :runtime +end +Gem.loaded_specs['buildr'] = spec # Prevents RubyGem from loading files from installed Buildr gems + +require 'buildr' +Buildr.application.run diff --git a/buildr/_jbuildr b/buildr/_jbuildr new file mode 100755 index 0000000..fde0bd8 --- /dev/null +++ b/buildr/_jbuildr @@ -0,0 +1,35 @@ +#!/usr/bin/env jruby +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +# Run buildr from source, specifically for testing stuff without doing a rake install. + +require 'rubygems' +$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib') << File.join(File.dirname(__FILE__), 'addon') + +require 'buildr/version' +spec = Gem::Specification.load(File.join(File.dirname(__FILE__), 'buildr.gemspec')) +# To avoid a warning about the version_requirements deprecation, we use this method inline. +def version_required(gem_def) + return Gem::Dependency.instance_methods.map(&:to_sym).include?(:requirement) ? gem_def.requirement : gem_def.version_requirements +end +spec.dependencies.each do |dep| + gem dep.name, version_required(dep).to_s if dep.type == :runtime +end +Gem.loaded_specs['buildr'] = spec # Prevents RubyGem from loading files from installed Buildr gems + +require 'buildr' +Buildr.application.run diff --git a/buildr/addon/buildr/antlr.rb b/buildr/addon/buildr/antlr.rb new file mode 100644 index 0000000..b144503 --- /dev/null +++ b/buildr/addon/buildr/antlr.rb @@ -0,0 +1,61 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + # Provides ANTLR grammar generation tasks. Require explicitly using require "buildr/antlr". + module ANTLR + REQUIRES = [ "org.antlr:antlr:jar:3.0", "antlr:antlr:jar:2.7.7", "org.antlr:stringtemplate:jar:3.0" ] + + Java.classpath << REQUIRES + + class << self + def antlr(*args) + options = Hash === args.last ? args.pop : {} + rake_check_options options, :output, :token + + args = args.flatten.map(&:to_s).collect { |f| File.directory?(f) ? FileList[f + "/**/*.g"] : f }.flatten + args = ["-o", options[:output]] + args if options[:output] + if options[:token] + # antlr expects the token directory to exist when it starts + mkdir_p options[:token] + args = ["-lib", options[:token]] + args + end + Java.load + #Java.org.antlr.Tool.new_with_sig("[Ljava.lang.String;", args).process + Java.org.antlr.Tool.new(args.to_java(Java.java.lang.String)).process + end + end + + def antlr(*args) + if Hash === args.last + options = args.pop + in_package = options[:in_package].split(".") + token = options[:token].split(".") if options[:token] + else + in_package = []; token = nil + end + file(path_to(:target, :generated, :antlr)=>args.flatten) do |task| + args = {:output=>File.join(task.name, in_package)} + args.merge!({:token=>File.join(task.name, token)}) if token + ANTLR.antlr task.prerequisites, args + end + end + + end + + class Project + include ANTLR + end +end diff --git a/buildr/addon/buildr/bnd.rb b/buildr/addon/buildr/bnd.rb new file mode 100644 index 0000000..8a3153c --- /dev/null +++ b/buildr/addon/buildr/bnd.rb @@ -0,0 +1,149 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Bnd + class << self + # The specs for requirements + def dependencies + ["biz.aQute:bnd:jar:0.0.384"] + end + + # Repositories containing the requirements + def remote_repository + "http://www.aQute.biz/repo" + end + + def bnd_main(*args) + cp = Buildr.artifacts(self.dependencies).each(&:invoke).map(&:to_s) + Java::Commands.java 'aQute.bnd.main.bnd', *(args + [{ :classpath => cp }]) + end + end + + class BundleTask < Rake::FileTask + attr_reader :project + attr_accessor :classpath + + def [](key) + @params[key] + end + + def []=(key, value) + @params[key] = value + end + + def classpath_element(dependencies) + artifacts = Buildr.artifacts([dependencies]) + artifacts.each do |artifact| + self.prerequisites << artifact + end + artifacts.each do |dependency| + self.classpath << dependency.to_s + end + end + + def to_params + params = self.project.manifest.merge(@params).reject { |k, v| v.nil? } + params["-classpath"] ||= self.classpath.collect(&:to_s).join(", ") + params['Bundle-SymbolicName'] ||= [self.project.group, self.project.name.gsub(':', '.')].join('.') + params['Bundle-Name'] ||= self.project.comment || self.project.name + params['Bundle-Description'] ||= self.project.comment + params['Bundle-Version'] ||= self.project.version + if params["Include-Resource"].nil? && !project.resources.target.nil? + params["Include-Resource"] = "#{project.resources.target}/" + end + params['-removeheaders'] ||= "Include-Resource,Bnd-LastModified,Created-By,Implementation-Title,Tool" + + params + end + + def project=(project) + @project = project + end + + def classpath=(classpath) + @classpath = [] + Buildr.artifacts([classpath.flatten.compact]).each do |dependency| + self.prerequisites << dependency + @classpath << dependency.to_s + end + @classpath + end + + def classpath + @classpath ||= ([project.compile.target] + project.compile.dependencies).flatten.compact + end + + protected + + def initialize(*args) #:nodoc: + super + @params = {} + enhance do + filename = self.name + # Generate BND file with same name as target jar but different extension + bnd_filename = filename.sub /(\.jar)?$/, '.bnd' + + params = self.to_params + params["-output"] = filename + File.open(bnd_filename, 'w') do |f| + f.print params.collect { |k, v| "#{k}=#{v}" }.join("\n") + end + + Buildr::Bnd.bnd_main( "build", "-noeclipse", bnd_filename ) + begin + Buildr::Bnd.bnd_main( "print", "-verify", filename ) + rescue => e + rm filename + raise e + end + end + end + end + + module ProjectExtension + include Extension + + first_time do + desc "Does `bnd print` on the packaged bundle and stdouts the output for inspection" + Project.local_task("bnd:print") + end + + def package_as_bundle(filename) + project.task('bnd:print' => [filename]) do |task| + Buildr::Bnd.bnd_main("print", filename) + end + + dirname = File.dirname(filename) + directory(dirname) + + # Add Buildr.application.buildfile so it will rebuild if we change settings + task = BundleTask.define_task(filename => [Buildr.application.buildfile, dirname]) + task.project = self + # the last task is the task considered the packaging task + task + end + + # Change the bundle package to .jar extension + def package_as_bundle_spec(spec) + spec.merge(:type => :jar) + end + end + end +end + +class Buildr::Project + include Buildr::Bnd::ProjectExtension +end \ No newline at end of file diff --git a/buildr/addon/buildr/checkstyle.rb b/buildr/addon/buildr/checkstyle.rb new file mode 100644 index 0000000..fd10bfe --- /dev/null +++ b/buildr/addon/buildr/checkstyle.rb @@ -0,0 +1,201 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + # Provides the checkstyle:html and checkstyle:xml tasks. + # Require explicitly using require "buildr/checkstyle". + module Checkstyle + + class << self + + # The specs for requirements + def dependencies + [ + 'com.puppycrawl.tools:checkstyle:jar:5.4', + 'commons-cli:commons-cli:jar:1.2', + 'antlr:antlr:jar:2.7.7', + 'com.google.collections:google-collections:jar:1.0', + 'commons-beanutils:commons-beanutils-core:jar:1.8.3', + 'commons-logging:commons-logging:jar:1.1.1' + ] + end + + def checkstyle(configuration_file, format, output_file, source_paths, options = {}) + dependencies = (options[:dependencies] || []) + self.dependencies + cp = Buildr.artifacts(dependencies).each(&:invoke).map(&:to_s) + + args = [] + if options[:properties_file] + args << "-p" + args << options[:properties_file] + end + args << "-c" + args << configuration_file + args << "-f" + args << format + args << "-o" + args << output_file + source_paths.each do |source_path| + args << "-r" + args << source_path + end + + begin + Java::Commands.java 'com.puppycrawl.tools.checkstyle.Main', *(args + [{:classpath => cp, :properties => options[:properties], :java_args => options[:java_args]}]) + rescue => e + raise e if options[:fail_on_error] + end + end + end + + class Config + def enabled? + File.exist?(self.configuration_file) + end + + def html_enabled? + File.exist?(self.style_file) + end + + attr_writer :config_directory + + def config_directory + @config_directory || project._(:source, :main, :etc, :checkstyle) + end + + attr_writer :report_dir + + def report_dir + @report_dir || project._(:reports, :checkstyle) + end + + attr_writer :configuration_file + + def configuration_file + @configuration_file || "#{self.config_directory}/checks.xml" + end + + attr_writer :fail_on_error + + def fail_on_error? + @fail_on_error.nil? ? false : @fail_on_error + end + + attr_writer :format + + def format + @format || 'xml' + end + + attr_writer :xml_output_file + + def xml_output_file + @xml_output_file || "#{self.report_dir}/checkstyle.xml" + end + + attr_writer :html_output_file + + def html_output_file + @html_output_file || "#{self.report_dir}/checkstyle.html" + end + + attr_writer :style_file + + def style_file + @style_file || "#{self.config_directory}/checkstyle-report.xsl" + end + + attr_writer :suppressions_file + + def suppressions_file + @suppressions_file || "#{self.config_directory}/suppressions.xml" + end + + attr_writer :import_control_file + + def import_control_file + @import_control_file || "#{self.config_directory}/import-control.xml" + end + + def properties + properties = {:basedir => self.project.base_dir} + properties['checkstyle.suppressions.file'] = self.suppressions_file if File.exist?(self.suppressions_file) + properties['checkstyle.import-control.file'] = self.import_control_file if File.exist?(self.import_control_file) + properties + end + + def source_paths + @source_paths ||= [self.project.compile.sources, self.project.test.compile.sources] + end + + def extra_dependencies + @extra_dependencies ||= [self.project.compile.dependencies, self.project.test.compile.dependencies] + end + + protected + + def initialize(project) + @project = project + end + + attr_reader :project + + end + + module ProjectExtension + include Extension + + def checkstyle + @checkstyle ||= Buildr::Checkstyle::Config.new(project) + end + + after_define do |project| + if project.checkstyle.enabled? + desc "Generate checkstyle xml report." + project.task("checkstyle:xml") do + puts "Checkstyle: Analyzing source code..." + mkdir_p File.dirname(project.checkstyle.xml_output_file) + Buildr::Checkstyle.checkstyle(project.checkstyle.configuration_file, + project.checkstyle.format, + project.checkstyle.xml_output_file, + project.checkstyle.source_paths.flatten.compact, + :properties => project.checkstyle.properties, + :fail_on_error => project.checkstyle.fail_on_error?, + :dependencies => project.checkstyle.extra_dependencies) + end + + if project.checkstyle.html_enabled? + xml_task = project.task("checkstyle:xml") + desc "Generate checkstyle html report." + project.task("checkstyle:html" => xml_task) do + puts "Checkstyle: Generating report" + mkdir_p File.dirname(project.checkstyle.html_output_file) + Buildr.ant "checkstyle" do |ant| + ant.xslt :in => project.checkstyle.xml_output_file, + :out => project.checkstyle.html_output_file, + :style => project.checkstyle.style_file + end + end + + end + end + end + end + end +end + +class Buildr::Project + include Buildr::Checkstyle::ProjectExtension +end diff --git a/buildr/addon/buildr/cobertura.rb b/buildr/addon/buildr/cobertura.rb new file mode 100644 index 0000000..16ac5a3 --- /dev/null +++ b/buildr/addon/buildr/cobertura.rb @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +if Buildr::VERSION < '1.5' + Buildr.application.deprecated "'buildr/cobertura', use 'buildr/java/cobertura' instead" + require 'buildr/java/cobertura' +else + raise "#{__FILE__} should be removed since its use is deprecated since version 1.3.4" +end diff --git a/buildr/addon/buildr/drb.rb b/buildr/addon/buildr/drb.rb new file mode 100644 index 0000000..3192d49 --- /dev/null +++ b/buildr/addon/buildr/drb.rb @@ -0,0 +1,279 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require 'delegate' +require 'drb/drb' + +module Buildr + + # This addon allows you start a DRb server hosting a buildfile, so that + # you can later invoke tasks on it without having to load + # the complete buildr runtime again. + # + # Usage: + # + # buildr -r buildr/drb drb:start + # + # Once the server has been started you can invoke tasks using a simple script: + # + # #!/usr/bin/env ruby + # require 'rubygems' + # require 'buildr/drb' + # Buildr::DRbApplication.run + # + # Save this script as 'dbuildr', make it executable and use it to invoke tasks. + # + # dbuildr clean compile + # + # The dbuildr script will run as the server if there isn't one already running. + # Subsequent calls to dbuildr will act as the client and invoke the tasks you + # provide in the server. + # If the buildfile has been modified it will be reloaded on the BuildrServer. + # + # JRuby users can use a nailgun client to invoke tasks as fast as possible + # without having to incur JVM startup time. + # See the documentation for buildr/nailgun. + module DRbApplication + + port = ENV['DRB_PORT'] || 2111 + PORT = port.to_i + + class SavedTask #:nodoc: + + def initialize(original) + @original = original.clone + @prerequisites = original.prerequisites.clone if original.respond_to?(:prerequisites) + @actions = original.actions.clone if original.respond_to?(:actions) + end + + def name + @original.name + end + + def actions + @actions ||= [] + end + + def prerequisites + @prerequisites ||= [] + end + + def define! + @original.class.send(:define_task, @original.name => prerequisites).tap do |task| + task.comment = @original.comment + actions.each { |action| task.enhance &action } + end + end + end # SavedTask + + class Snapshot #:nodoc: + + attr_accessor :projects, :tasks, :rules, :layout, :options + + # save the tasks,rules,layout defined by buildr + def initialize + @rules = Buildr.application.instance_eval { @rules || [] }.clone + @options = Buildr.application.options.clone + @options.rakelib ||= ['tasks'] + @layout = Layout.default.clone + @projects = Project.instance_eval { @projects || {} }.clone + @tasks = Buildr.application.tasks.inject({}) do |hash, original| + unless projects.key? original.name # don't save project definitions + hash.update original.name => SavedTask.new(original) + end + hash + end + end + + end # Snapshot + + class << self + + attr_accessor :original, :snapshot + + def run + begin + client = connect + rescue DRb::DRbConnError => e + run_server! + else + run_client(client) + end + end + + def client_uri + "druby://:#{PORT + 1}" + end + + def remote_run(cfg) + with_config(cfg) { Buildr.application.remote_run(self) } + rescue => e + cfg[:err].puts e.message + e.backtrace.each { |b| cfg[:err].puts "\tfrom #{b}" } + raise e + end + + def save_snapshot(app) + if app.instance_eval { @rakefile } + @snapshot = self::Snapshot.new + app.buildfile_reloaded! + end + end + + private + + def server_uri + "druby://:#{PORT}" + end + + def connect + buildr = DRbObject.new(nil, server_uri) + uri = buildr.client_uri # obtain our uri from the server + DRb.start_service(uri) + buildr + end + + def run_client(client) + client.remote_run :dir => Dir.pwd, :argv => ARGV, + :in => $stdin, :out => $stdout, :err => $stderr + end + + def setup + unless original + # Create the stdio delegator that can be cached (eg by fileutils) + delegate_stdio + + # Lazily load buildr the first time it's needed + require 'buildr' + + # Save the tasks,rules,layout defined by buildr + # before loading any project + @original = self::Snapshot.new + + Buildr.application.extend self + save_snapshot(Buildr.application) + end + end + + def run_server + setup + DRb.start_service(server_uri, self) + puts "#{self} waiting on #{server_uri}" + end + + def run_server! + setup + if RUBY_PLATFORM[/java/] + require 'buildr/nailgun' + Buildr.application['nailgun:drb'].invoke + else + run_server + DRb.thread.join + end + end + + def delegate_stdio + $stdin = SimpleDelegator.new($stdin) + $stdout = SimpleDelegator.new($stdout) + $stderr = SimpleDelegator.new($stderr) + end + + def with_config(remote) + @invoked = true + set = lambda do |env| + ARGV.replace env[:argv] + $stdin.__setobj__(env[:in]) + $stdout.__setobj__(env[:out]) + $stderr.__setobj__(env[:err]) + Buildr.application.instance_variable_set :@original_dir, env[:dir] + end + original = { + :dir => Buildr.application.instance_variable_get(:@original_dir), + :argv => ARGV, + :in => $stdin.__getobj__, + :out => $stdout.__getobj__, + :err => $stderr.__getobj__ + } + begin + set[remote] + yield + ensure + set[original] + end + end + + end # class << DRbApplication + + def remote_run(server) + @options = server.original.options.clone + init 'Distributed Buildr' + if @rakefile + if buildfile_needs_reload? + reload_buildfile(server, server.original) + else + clear_invoked_tasks(server.snapshot || server.original) + end + else + reload_buildfile(server, server.original) + end + top_level + end + + def buildfile_reloaded! + @last_loaded = buildfile.timestamp if @rakefile + end + + private + + def buildfile_needs_reload? + !@last_loaded || @last_loaded < buildfile.timestamp + end + + def reload_buildfile(server, snapshot) + clear_for_reload(snapshot) + load_buildfile + server.save_snapshot(self) + end + + def clear_for_reload(snapshot) + Project.clear + @tasks = {} + @rules = snapshot.rules.clone + snapshot.tasks.each_pair { |name, saved| saved.define! } + Layout.default = snapshot.layout.clone + end + + def clear_invoked_tasks(snapshot) + @rules = snapshot.rules.clone + (@tasks.keys - snapshot.projects.keys).each do |name| + if saved = snapshot.tasks[name] + # reenable this task, restoring its actions/prereqs + task = @tasks[name] + task.reenable + task.prerequisites.replace saved.prerequisites.clone + task.actions.replace saved.actions.clone + else + # tasks generated at runtime, drop it + @tasks.delete(name) + end + end + end + + task('drb:start') { run_server! } if Buildr.respond_to?(:application) + + end # DRbApplication + +end + diff --git a/buildr/addon/buildr/emma.rb b/buildr/addon/buildr/emma.rb new file mode 100644 index 0000000..7089440 --- /dev/null +++ b/buildr/addon/buildr/emma.rb @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +if Buildr::VERSION < '1.5' + Buildr.application.deprecated "'buildr/emma', use 'buildr/java/emma' instead" + require 'buildr/java/emma' +else + raise "#{__FILE__} should be removed since its use is deprecated since version 1.3.4" +end diff --git a/buildr/addon/buildr/findbugs.rb b/buildr/addon/buildr/findbugs.rb new file mode 100644 index 0000000..a277b8c --- /dev/null +++ b/buildr/addon/buildr/findbugs.rb @@ -0,0 +1,227 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + # Provides the findbugs:html and findbugs:xml tasks. + # Require explicitly using require "buildr/findbugs". + module Findbugs + + class << self + + # The specs for requirements + def dependencies + [ + 'com.google.code.findbugs:findbugs-ant:jar:1.3.9', + 'com.google.code.findbugs:findbugs:jar:1.3.9', + 'com.google.code.findbugs:bcel:jar:1.3.9', + 'com.google.code.findbugs:jsr305:jar:1.3.9', + 'com.google.code.findbugs:jFormatString:jar:1.3.9', + 'com.google.code.findbugs:annotations:jar:1.3.9', + 'dom4j:dom4j:jar:1.6.1', + 'jaxen:jaxen:jar:1.1.1', + 'jdom:jdom:jar:1.0', + 'xom:xom:jar:1.0', + 'com.ibm.icu:icu4j:jar:2.6.1', + 'asm:asm:jar:3.1', + 'asm:asm-analysis:jar:3.1', + 'asm:asm-tree:jar:3.1', + 'asm:asm-commons:jar:3.1', + 'asm:asm-util:jar:3.1', + 'asm:asm-xml:jar:3.1', + 'commons-lang:commons-lang:jar:2.4' + ] + end + + def findbugs(output_file, source_paths, analyze_paths, options = { }) + dependencies = (options[:dependencies] || []) + self.dependencies + cp = Buildr.artifacts(dependencies).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR) + + args = { + :output => "xml:withMessages", + :outputFile => output_file, + :effort => 'max', + :pluginList => '', + :classpath => cp, + :timeout => "90000000", + :debug => "false" + } + args[:failOnError] = true if options[:fail_on_error] + args[:excludeFilter] = options[:exclude_filter] if options[:exclude_filter] + args[:jvmargs] = options[:java_args] if options[:java_args] + + Buildr.ant('findBugs') do |ant| + ant.taskdef :name =>'findBugs', + :classname =>'edu.umd.cs.findbugs.anttask.FindBugsTask', + :classpath => cp + ant.findBugs args do + source_paths.each do |source_path| + ant.sourcePath :path => source_path.to_s + end + Buildr.artifacts(analyze_paths).each(&:invoke).each do |analyze_path| + ant.auxAnalyzePath :path => analyze_path.to_s + end + if options[:properties] + options[:properties].each_pair do |k, v| + ant.systemProperty :name => k, :value => v + end + end + if options[:extra_dependencies] + ant.auxClasspath do |aux| + Buildr.artifacts(options[:extra_dependencies]).each(&:invoke).each do |dep| + aux.pathelement :location => dep.to_s + end + end + end + end + end + end + end + + class Config + + attr_accessor :enabled + + def enabled? + !!@enabled + end + + def html_enabled? + File.exist?(self.style_file) + end + + attr_writer :config_directory + + def config_directory + @config_directory || project._(:source, :main, :etc, :findbugs) + end + + attr_writer :report_dir + + def report_dir + @report_dir || project._(:reports, :findbugs) + end + + attr_writer :fail_on_error + + def fail_on_error? + @fail_on_error.nil? ? false : @fail_on_error + end + + attr_writer :xml_output_file + + def xml_output_file + @xml_output_file || "#{self.report_dir}/findbugs.xml" + end + + attr_writer :html_output_file + + def html_output_file + @html_output_file || "#{self.report_dir}/findbugs.html" + end + + attr_writer :style_file + + def style_file + @style_file || "#{self.config_directory}/findbugs-report.xsl" + end + + attr_writer :filter_file + + def filter_file + @filter_file || "#{self.config_directory}/filter.xml" + end + + def properties + @properties ||= { } + end + + attr_writer :java_args + + def java_args + @java_args || "-server -Xss1m -Xmx800m -Duser.language=en -Duser.region=EN " + end + + def source_paths + @source_paths ||= [self.project.compile.sources, self.project.test.compile.sources] + end + + def analyze_paths + @analyze_path ||= [self.project.compile.target] + end + + def extra_dependencies + @extra_dependencies ||= [self.project.compile.dependencies, self.project.test.compile.dependencies] + end + + protected + + def initialize(project) + @project = project + end + + attr_reader :project + + end + + module ProjectExtension + include Extension + + def findbugs + @findbugs ||= Buildr::Findbugs::Config.new(project) + end + + after_define do |project| + if project.findbugs.enabled? + desc "Generate findbugs xml report." + project.task("findbugs:xml") do + puts "Findbugs: Analyzing source code..." + mkdir_p File.dirname(project.findbugs.xml_output_file) + + options = + { + :properties => project.findbugs.properties, + :fail_on_error => project.findbugs.fail_on_error?, + :extra_dependencies => project.findbugs.extra_dependencies + } + options[:exclude_filter] = project.findbugs.filter_file if File.exist?(project.findbugs.filter_file) + + Buildr::Findbugs.findbugs(project.findbugs.xml_output_file, + project.findbugs.source_paths.flatten.compact, + project.findbugs.analyze_paths.flatten.compact, + options) + end + + if project.findbugs.html_enabled? + xml_task = project.task("findbugs:xml") + desc "Generate findbugs html report." + project.task("findbugs:html" => xml_task) do + puts "Findbugs: Generating report" + mkdir_p File.dirname(project.findbugs.html_output_file) + Buildr.ant "findbugs" do |ant| + ant.style :in => project.findbugs.xml_output_file, + :out => project.findbugs.html_output_file, + :style => project.findbugs.style_file + end + end + end + end + end + end + end +end + +class Buildr::Project + include Buildr::Findbugs::ProjectExtension +end diff --git a/buildr/addon/buildr/hibernate.rb b/buildr/addon/buildr/hibernate.rb new file mode 100644 index 0000000..380d4a4 --- /dev/null +++ b/buildr/addon/buildr/hibernate.rb @@ -0,0 +1,145 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Provides Hibernate Doclet and schema export tasks. Require explicitly using require "buildr/hibernate". + module Hibernate + + REQUIRES = Buildr.struct( + :collections => "commons-collections:commons-collections:jar:3.1", + :logging => "commons-logging:commons-logging:jar:1.0.3", + :dom4j => "dom4j:dom4j:jar:1.6.1", + :hibernate => "org.hibernate:hibernate:jar:3.1.2", + :xdoclet => Buildr.group("xdoclet", "xdoclet-xdoclet-module", "xdoclet-hibernate-module", + # :under=>"xdoclet", :version=>"1.2.3") + ["xdoclet:xjavadoc:jar:1.1-j5"] + :under=>"xdoclet", :version=>"1.2.3") + ["xdoclet:xjavadoc:jar:1.1"] + ) + + class << self + include Buildr::Ant + + # :call-seq: + # doclet(options) => AntProject + # + # Uses XDoclet to generate HBM files form annotated source files. + # Options include: + # * :sources -- Directory (or directories) containing source files. + # * :target -- The target directory. + # * :excludetags -- Tags to exclude (see HibernateDocletTask) + # + # For example: + # doclet :sources=>compile.sources, :target=>compile.target, :excludedtags=>"@version,@author,@todo" + def doclet(options) + options[:sources].each { |src| file(src).invoke } + ant "hibernatedoclet" do |ant| + ant.taskdef :name=>"hibernatedoclet", :classname=>"xdoclet.modules.hibernate.HibernateDocletTask", :classpath=>options[:classpath] + ant.hibernatedoclet :destdir=>options[:target].to_s, :excludedtags=>options[:excludedtags], :force=>"true" do + ant.hibernate :version=>"3.0" + options[:sources].map(&:to_s).each do |source| + ant.fileset :dir=>source.to_s, :includes=>"**/*.java" + end + end + end + end + + # :call-seq: + # schemaexport(properties) { ... } => AntProject + # + # Runs the Hibernate SchemaExportTask with the specified properties. For example: + # Buildr::Hibernate.schemaexport(:properties=>properties.to_s, :quiet=>"yes", :text=>"yes", :delimiter=>";", + # :drop=>"no", :create=>"yes", :output=>target) do + # fileset :dir=>source.to_s, :includes=>"**/*.hbm.xml" + # end + def schemaexport(classpath, options = nil) + ant "schemaexport" do |ant| + ant.taskdef :name=>"schemaexport", :classname=>"org.hibernate.tool.hbm2ddl.SchemaExportTask", :classpath=>classpath + ant.schemaexport(options) { yield ant if block_given? } if options + end + end + + end + + def hibernate_requires() + @requires ||= REQUIRES.dup + end + + # :call-seq: + # hibernate_doclet(options?) => task + # + # Runs the hibernate doclet on the source files and creates HBM files in the target directory. + # By default runs on all source files, but you can limit it to a given package using the :package + # options. You can also pass other options to the doclet task. + # + # For example: + # resources hibernate_doclet(:package=>"org.apache.ode.store.hib", :excludedtags=>"@version,@author,@todo") + def hibernate_doclet(options = {}) + if options[:package] + depends = compile.sources.map { |src| FileList[File.join(src.to_s, options[:package].gsub(".", "/"), "*.java")] }.flatten + else + depends = compile.sources.map { |src| FileList[File.join(src.to_s, "**/*.java")] }.flatten + end + file("target/hbm.timestamp"=>depends) do |task| + Hibernate.doclet({ :sources=>compile.sources, :target=>compile.target, :classpath => hib_resolve_classpath }.merge(options)) + write task.name + end + end + + # :call-seq: + # hibernate_schemaexport(path) => task + # hibernate_schemaexport(path) { |task, ant| .. } => task + # + # Returns an new file task with an accessor (ant) to an AntProject that defines the schemaexport task. + # If called with a block, the task will yield to the block passing both itself and the Ant project. + # + # See #schemaexport. + # + # For example: + # hibernate_schemaexport "derby.sql" do |task, ant| + # ant.schemaexport :properties=>"derby.properties", :output=>task.name, + # :delimiter=>";", :drop=>"no", :create=>"yes" do + # fileset(:dir=>compile.sources.first) { include :name=>"**/*.hbm.xml" } } + # end + # end + def hibernate_schemaexport(args, &block) + path, arg_names, deps = Rake.application.resolve_args([args]) + file(path).enhance { |task| + unless task.respond_to? :ant #this is a hack. A better way to do the job is to create a real task for all this. + class << task ; attr_accessor :ant ; end + task.ant = Hibernate.schemaexport(hib_resolve_classpath) + end + } + + if block + file(path).enhance(deps) { |task| block.call task, task.ant } + else + file(path).enhance deps + end + end + + protected + + # This will download all the required artifacts before returning a classpath, and we want to do this only once. + def hib_resolve_classpath + Buildr.artifacts(hibernate_requires.to_a).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR) + end + + end + + class Project + include Hibernate + end + +end diff --git a/buildr/addon/buildr/javacc.rb b/buildr/addon/buildr/javacc.rb new file mode 100644 index 0000000..90d0925 --- /dev/null +++ b/buildr/addon/buildr/javacc.rb @@ -0,0 +1,81 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + # Provides JavaCC compile tasks. Require explicitly using require "buildr/javacc". + module JavaCC + + REQUIRES = [ "net.java.dev.javacc:javacc:jar:4.0", "net.java.dev.javacc:javacc:jar:4.0" ] + + Java.classpath << REQUIRES + + class << self + + def javacc(*args) + options = Hash === args.last ? args.pop : {} + rake_check_options options, :output + + args = args.flatten.map(&:to_s).collect { |f| File.directory?(f) ? FileList[f + "/**/*.jj"] : f }.flatten + args.unshift "-OUTPUT_DIRECTORY=#{options[:output]}" if options[:output] + Java.load + Java.org.javacc.parser.Main.mainProgram(args.to_java(Java.java.lang.String)) == 0 or + fail "Failed to run JavaCC, see errors above." + end + + def jjtree(*args) + options = Hash === args.last ? args.pop : {} + rake_check_options options, :output, :build_node_files + + args = args.flatten.map(&:to_s).collect { |f| File.directory?(f) ? FileList[f + "**/*.jjt"] : f }.flatten + args.unshift "-OUTPUT_DIRECTORY=#{options[:output]}" if options[:output] + args.unshift "-BUILD_NODE_FILES=#{options[:build_node_files] || false}" + Java.load + Java.org.javacc.jjtree.JJTree.new.main(args.to_java(Java.java.lang.String)) == 0 or + fail "Failed to run JJTree, see errors above." + end + + end + + def javacc(*args) + if Hash === args.last + options = args.pop + in_package = options[:in_package].split(".") + else + in_package = [] + end + file(path_to(:target, :generated, :javacc)=>args.flatten) do |task| + JavaCC.javacc task.prerequisites, :output=>File.join(task.name, in_package) + end + end + + def jjtree(*args) + if Hash === args.last + options = args.pop + in_package = options[:in_package].split(".") + build_node_files = options[:build_node_files] + else + in_package = [] + end + file(path_to(:target, :generated, :jjtree)=>args.flatten) do |task| + JavaCC.jjtree task.prerequisites, :output=>File.join(task.name, in_package), :build_node_files=>build_node_files + end + end + + end + + class Project + include JavaCC + end +end diff --git a/buildr/addon/buildr/javancss.rb b/buildr/addon/buildr/javancss.rb new file mode 100644 index 0000000..9f94b80 --- /dev/null +++ b/buildr/addon/buildr/javancss.rb @@ -0,0 +1,155 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + # Provides the javancss:html and javancss:xml tasks. + # Require explicitly using require "buildr/javancss". + module JavaNCSS + + class << self + + # The specs for requirements + def dependencies + [ + 'org.codehaus.javancss:javancss:jar:32.53', + 'javancss:ccl:jar:29.50', + 'javancss:jhbasic:jar:29.50' + ] + end + + def javancss(output_file, source_paths, options = {}) + dependencies = (options[:dependencies] || []) + self.dependencies + cp = Buildr.artifacts(dependencies).each(&:invoke).map(&:to_s) + + args = [] + args << "-all" + args << "-xml" + args << "-out" + args << output_file + args << "-recursive" + source_paths.each do |source_path| + args << source_path + end + + begin + Java::Commands.java 'javancss.Main', *(args + [{:classpath => cp, :properties => options[:properties], :java_args => options[:java_args]}]) + rescue => e + raise e if options[:fail_on_error] + end + end + end + + class Config + def enabled? + !!@enabled + end + + attr_writer :enabled + + def html_enabled? + File.exist?(self.style_file) + end + + attr_writer :config_directory + + def config_directory + @config_directory || project._(:source, :main, :etc, :javancss) + end + + attr_writer :report_dir + + def report_dir + @report_dir || project._(:reports, :javancss) + end + + attr_writer :fail_on_error + + def fail_on_error? + @fail_on_error.nil? ? false : @fail_on_error + end + + attr_writer :xml_output_file + + def xml_output_file + @xml_output_file || "#{self.report_dir}/javancss.xml" + end + + attr_writer :html_output_file + + def html_output_file + @html_output_file || "#{self.report_dir}/javancss.html" + end + + attr_writer :style_file + + def style_file + @style_file || "#{self.config_directory}/javancss2html.xsl" + end + + def source_paths + @source_paths ||= [self.project.compile.sources, self.project.test.compile.sources] + end + + protected + + def initialize(project) + @project = project + end + + attr_reader :project + + end + + module ProjectExtension + include Extension + + def javancss + @javancss ||= Buildr::JavaNCSS::Config.new(project) + end + + after_define do |project| + if project.javancss.enabled? + desc "Generate JavaNCSS xml report." + project.task("javancss:xml") do + puts "JavaNCSS: Analyzing source code..." + mkdir_p File.dirname(project.javancss.xml_output_file) + Buildr::JavaNCSS.javancss(project.javancss.xml_output_file, + project.javancss.source_paths.flatten.compact, + :fail_on_error => project.javancss.fail_on_error?) + end + + if project.javancss.html_enabled? + xml_task = project.task("javancss:xml") + desc "Generate JavaNCSS html report." + project.task("javancss:html" => xml_task) do + puts "JavaNCSS: Generating report" + mkdir_p File.dirname(project.javancss.html_output_file) + Buildr.ant "javancss" do |ant| + ant.xslt :in => project.javancss.xml_output_file, + :out => project.javancss.html_output_file, + :style => project.javancss.style_file + end + end + + end + end + end + end + end +end + +class Buildr::Project + include Buildr::JavaNCSS::ProjectExtension +end diff --git a/buildr/addon/buildr/jaxb_xjc.rb b/buildr/addon/buildr/jaxb_xjc.rb new file mode 100644 index 0000000..033914a --- /dev/null +++ b/buildr/addon/buildr/jaxb_xjc.rb @@ -0,0 +1,72 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module JaxbXjc + class << self + + def jaxb_version + "2.2.1" + end + + # The specs for requirements + def dependencies + [ + "javax.xml.bind:jaxb-api:jar:#{jaxb_version}", + "com.sun.xml.bind:jaxb-impl:jar:#{jaxb_version}", + "com.sun.xml.bind:jaxb-xjc:jar:#{jaxb_version}" + ] + end + + # Repositories containing the requirements + def remote_repository + "http://download.java.net/maven/2" + end + + def xjc(*args) + cp = Buildr.artifacts(self.dependencies).each(&:invoke).map(&:to_s) + Java::Commands.java 'com.sun.tools.xjc.XJCFacade', *(args + [{ :classpath => cp }]) + end + end + + def compile_jaxb(files, *args) + options = Hash === args.last ? args.pop.dup : {} + rake_check_options options, :directory, :keep_content, :package, :id + args = args.dup + files = Array === files ? files.flatten : [files] + + target_dir = options[:directory] || path_to(:target, :generated, :jaxb) + timestamp_file = File.expand_path("#{target_dir}/jaxb-#{options[:id] || 1}.cache") + + file(target_dir => timestamp_file) + + file(timestamp_file => files.flatten) do |task| + rm_rf target_dir unless options[:keep_content] + mkdir_p target_dir + args << "-d" << target_dir + args << "-p" << options[:package] if options[:package] + args += files.collect{|f| f.to_s} + JaxbXjc.xjc args + touch timestamp_file + end + + target_dir + end + end +end + +class Buildr::Project + include Buildr::JaxbXjc +end \ No newline at end of file diff --git a/buildr/addon/buildr/jdepend.rb b/buildr/addon/buildr/jdepend.rb new file mode 100644 index 0000000..ad6d6a6 --- /dev/null +++ b/buildr/addon/buildr/jdepend.rb @@ -0,0 +1,174 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + # Addes the projectname:jdepend:swing, projectname:jdepend:text and + # projectname:jdepend:xml tasks. + # + # Require explicitly using require "buildr/jdepend". + module JDepend + + class << self + + # The specs for requirements + def dependencies + [ + 'jdepend:jdepend:jar:2.9.1' + ] + end + + def jdepend(output_file, target_paths, options = {}) + dependencies = (options[:dependencies] || []) + self.dependencies + cp = Buildr.artifacts(dependencies).each(&:invoke).map(&:to_s) + + args = [] + if output_file + args << "-file" + args << output_file + end + target_paths.each do |target_path| + file(target_path).invoke + args << target_path.to_s + end + + # If no output file then we must be trying to run the swing app + command = output_file ? 'jdepend.xmlui.JDepend' : 'jdepend.swingui.JDepend' + + begin + Java::Commands.java command, *(args + [{:classpath => cp, :properties => options[:properties], :java_args => options[:java_args]}]) + rescue => e + raise e if options[:fail_on_error] + end + end + end + + class Config + def enabled? + !!@enabled + end + + attr_writer :enabled + + def html_enabled? + File.exist?(self.style_file) + end + + attr_writer :config_directory + + def config_directory + @config_directory || project._(:source, :main, :etc, :jdepend) + end + + attr_writer :report_dir + + def report_dir + @report_dir || project._(:reports, :jdepend) + end + + attr_writer :fail_on_error + + def fail_on_error? + @fail_on_error.nil? ? false : @fail_on_error + end + + attr_writer :xml_output_file + + def xml_output_file + @xml_output_file || "#{self.report_dir}/jdepend.xml" + end + + attr_writer :html_output_file + + def html_output_file + @html_output_file || "#{self.report_dir}/jdepend.html" + end + + attr_writer :style_file + + def style_file + @style_file || "#{self.config_directory}/jdepend.xsl" + end + + def target_paths + @target_paths ||= [self.project.compile.target, self.project.test.compile.target] + end + + def to_options + { + :fail_on_error => project.jdepend.fail_on_error?, + # Set user home so that jdepend.properties will be loaded from there if present + :properties => { 'user.home' => project.jdepend.config_directory } + } + end + + protected + + def initialize(project) + @project = project + end + + attr_reader :project + + end + + module ProjectExtension + include Extension + + def jdepend + @jdepend ||= Buildr::JDepend::Config.new(project) + end + + after_define do |project| + if project.jdepend.enabled? + desc "Generate JDepend xml report." + project.task("jdepend:xml") do + puts "JDepend: Analyzing source code..." + mkdir_p File.dirname(project.jdepend.xml_output_file) + Buildr::JDepend.jdepend(project.jdepend.xml_output_file, + project.jdepend.target_paths.flatten.compact, + project.jdepend.to_options) + end + + desc "Run JDepend with Swing UI." + project.task("jdepend:swing") do + puts "JDepend: Analyzing source code..." + Buildr::JDepend.jdepend(nil, + project.jdepend.target_paths.flatten.compact, + project.jdepend.to_options) + end + + if project.jdepend.html_enabled? + xml_task = project.task("jdepend:xml") + desc "Generate JDepend html report." + project.task("jdepend:html" => xml_task) do + puts "JDepend: Generating report" + mkdir_p File.dirname(project.jdepend.html_output_file) + Buildr.ant "jdepend" do |ant| + ant.xslt :in => project.jdepend.xml_output_file, + :out => project.jdepend.html_output_file, + :style => project.jdepend.style_file + end + end + + end + end + end + end + end +end + +class Buildr::Project + include Buildr::JDepend::ProjectExtension +end diff --git a/buildr/addon/buildr/jetty.rb b/buildr/addon/buildr/jetty.rb new file mode 100644 index 0000000..609174d --- /dev/null +++ b/buildr/addon/buildr/jetty.rb @@ -0,0 +1,243 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require 'uri' +require 'net/http' +require 'thread' + +module Buildr + + # Provides a collection of tasks and methods for using Jetty, specifically as a server + # for testing your application. + # + # Build files should always start Jetty by invoking the #use task, typically as + # a prerequisite. This task will start Jetty once during the build, and shut it down + # when the build completes. + # + # If you want to keep Jetty running across builds, and look at error messages, you can + # start Jetty in a separate console with: + # buildr jetty:start + # To stop this instance of Jetty, simply kill the process (Ctrl-C) or run: + # buildr jetty:stop + # + # If you start Jetty separately from the build, the #use task will connect to that + # existing server. Since you are using Jetty across several builds, you will want to + # cleanup any mess created by each build. You can use the #setup and #teardown tasks, + # which are called when Jetty is first used in the build, and when the build ends. + class Jetty + + # Which version of Jetty we're using by default (change with options.jetty.version). + VERSION = "6.1.3" + SLF4J_VERSION = "1.4.3" + + # Libraries used by Jetty. + REQUIRES = [ "org.mortbay.jetty:jetty:jar:#{VERSION}", "org.mortbay.jetty:jetty-util:jar:#{VERSION}", + "org.mortbay.jetty:servlet-api-2.5:jar:#{VERSION}", "org.slf4j:slf4j-api:jar:#{SLF4J_VERSION}", + "org.slf4j:slf4j-simple:jar:#{SLF4J_VERSION}", "org.slf4j:jcl104-over-slf4j:jar:#{SLF4J_VERSION}" ] + + Java.classpath << REQUIRES + Java.classpath << File.dirname(__FILE__) + + # Default URL for Jetty (change with options.jetty.url). + URL = "http://localhost:8080" + + class << self + + # :call-seq: + # instance() => Jetty + # + # Returns an instance of Jetty. + def instance() + @instance ||= Jetty.new("jetty", URL) + end + + end + + def initialize(name, url) #:nodoc: + @url = url + namespace name do + @setup = task("setup") + @teardown = task("teardown") + @use = task("use") { fire } + end + end + + # The URL for the Jetty server. Leave as is if you want to use the default server + # (http://localhost:8080). + attr_accessor :url + + # :call-seq: + # start(pipe?) + # + # Starts Jetty. This method does not return, it keeps the thread running until + # Jetty is stopped. If you want to run Jetty parallel with other tasks in the build, + # invoke the #use task instead. + def start(sync = nil) + begin + puts "classpath #{Java.classpath.inspect}" + port = URI.parse(url).port + puts "Starting Jetty at http://localhost:#{port}" if verbose + Java.load + jetty = Java.org.apache.buildr.JettyWrapper.new(port) + sync << "Started" if sync + sleep # Forever + rescue Interrupt # Stopped from console + rescue Exception=>error + puts "#{error.class}: #{error.message}" + end + exit! # No at_exit + end + + # :call-seq: + # stop() + # + # Stops Jetty. Stops a server running in a separate process. + def stop() + uri = URI.parse(url) + begin + Net::HTTP.start(uri.host, uri.port) do |http| + http.request_post "/buildr/stop", "" + end + rescue Errno::ECONNREFUSED + # Expected if Jetty server not running. + rescue EOFError + # We get EOFError because Jetty is brutally killed. + end + puts "Jetty server stopped" + end + + # :call-seq: + # running?() => boolean + # + # Returns true if it finds a running Jetty server that supports the Buildr + # requests for deploying, stopping, etc. + def running?() + uri = URI.parse(url) + begin + Net::HTTP.start(uri.host, uri.port) do |http| + response = http.request_get("/buildr/") + response.is_a?(Net::HTTPSuccess) && response.body =~ /Alive/ + end + rescue Errno::ECONNREFUSED, Errno::EBADF + false + end + end + + # :call-seq: + # deploy(url, webapp) => path + # + # Deploy a WAR in the specified URL. + def deploy(url, webapp) + use.invoke + uri = URI.parse(url) + Net::HTTP.start(uri.host, uri.port) do |http| + response = http.request_post("/buildr/deploy", "webapp=#{webapp}&path=#{uri.path}") + if Net::HTTPOK === response && response.body =~ /Deployed/ + path = response.body.split[1] + puts "Deployed #{webapp}, context path #{uri.path}" if trace? + path + else + fail "Deployment failed: #{response}" + end + end + end + + # :call-seq: + # undeploy(url) => boolean + # + # Undeploys a WAR from the specified URL. + def undeploy(url) + use.invoke + uri = URI.parse(url) + Net::HTTP.start(uri.host, uri.port) do |http| + response = http.request_post("/buildr/undeploy", "path=#{uri.path}") + if Net::HTTPOK === response && response.body =~ /Undeployed/ + true + else + fail "Deployment failed: #{response}" + end + end + end + + # :call-seq: + # setup(*prereqs) => task + # setup(*prereqs) { |task| .. } => task + # + # This task executes when Jetty is first used in the build. You can use it to + # deploy artifacts into Jetty. + def setup(*prereqs, &block) + @setup.enhance prereqs, &block + end + + # :call-seq: + # teardown(*prereqs) => task + # teardown(*prereqs) { |task| .. } => task + # + # This task executes when the build is done. You can use it to undeploy artifacts + # previously deployed into Jetty. + def teardown(*prereqs, &block) + @teardown.enhance prereqs, &block + end + + # :call-seq: + # use(*prereqs) => task + # use(*prereqs) { |task| .. } => task + # + # If you intend to use Jetty, invoke this task. It will start a new instance of + # Jetty and close it when the build is done. However, if you already have a server + # running in the background (e.g. jetty:start), it will use that server and will + # not close it down. + def use(*prereqs, &block) + @use.enhance prereqs, &block + end + + protected + + # If you want to start Jetty inside the build, call this method instead of #start. + # It will spawn a separate process that will run Jetty, and will stop Jetty when + # the build ends. However, if you already started Jetty from the console (with + # take jetty:start), it will use the existing instance without shutting it down. + def fire() + unless running? + sync = Queue.new + Thread.new { start sync } + # Wait for Jetty to fire up before doing anything else. + sync.pop == "Started" or fail "Jetty not started" + puts "Jetty started" if verbose + at_exit { stop } + end + @setup.invoke + at_exit { @teardown.invoke } + end + + end + + namespace "jetty" do + desc "Start an instance of Jetty running in the background" + task("start") { Jetty.instance.start } + desc "Stop an instance of Jetty running in the background" + task("stop") { Jetty.instance.stop } + end + + # :call-seq: + # jetty() => Jetty + # + # Returns a Jetty object. You can use this to discover the Jetty#use task, + # configure the Jetty#setup and Jetty#teardown tasks, deploy and undeploy to Jetty. + def jetty() + @jetty ||= Jetty.instance + end + +end diff --git a/buildr/addon/buildr/jibx.rb b/buildr/addon/buildr/jibx.rb new file mode 100644 index 0000000..6b017c9 --- /dev/null +++ b/buildr/addon/buildr/jibx.rb @@ -0,0 +1,85 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require 'java/java' + +module Buildr + + # Provides JiBX bytecode enhancement. Require explicitly using require 'buildr/jibx'. + module JiBX + + JIBX_VERSION = '1.1.5' + BCEL_VERSION = '5.2' + STAX_VERSION = '1.0-2' + XPP3_VERSION = '1.1.4c' + + REQUIRES = ["org.jibx:jibx-bind:jar:#{JIBX_VERSION}", + "org.jibx:jibx-run:jar:#{JIBX_VERSION}", + "org.apache.bcel:bcel:jar:#{BCEL_VERSION}", + "javax.xml.stream:stax-api:jar:#{STAX_VERSION}", + "xpp3:xpp3:jar:#{XPP3_VERSION}"] + + Java.classpath << REQUIRES + + class << self + + def bind(options) + rake_check_options options, :classpath, :output, :binding, :target, :verbose, :load + artifacts = Buildr.artifacts(options[:classpath]).each { |a| a.invoke }.map(&:to_s) + [options[:output].to_s] + binding = file(options[:binding]).tap { |task| task.invoke }.to_s + + Buildr.ant 'jibx' do |ant| + ant.taskdef :name=>'bind', + :classname=>'org.jibx.binding.ant.CompileTask', + :classpath => requires.join(File::PATH_SEPARATOR) + ant.bind :verbose => options[:verbose].to_s, :load => options[:load].to_s, :binding=>options[:binding].to_s do + ant.classpath :path => artifacts.join(File::PATH_SEPARATOR) + end + end + end + + private + + def requires() + @requires ||= Buildr.artifacts(REQUIRES).each { |artifact| artifact.invoke }.map(&:to_s) + end + + end + + def jibx_bind(options = nil) + + # FIXME - add support for :bindingfileset and :classpathset + # Note: either :binding or :bindingfileset should be set, and either + # :classpath or :classpathset should be set, and options passed to + # ant.bind should be adjusted accordingly. At present, only :binding + # and :classpath are supported (which should be fine for most!) + jibx_options = {:output => compile.target, + :classpath => compile.classpath, + :binding => path_to(:source, :main, :resources, 'META-INF/binding.xml'), + :target => compile.target, + :load => false, + :verbose => false + } + + JiBX.bind jibx_options.merge(options || {}) + end + + end + + class Project + include JiBX + end +end + diff --git a/buildr/addon/buildr/nailgun.rb b/buildr/addon/buildr/nailgun.rb new file mode 100644 index 0000000..1b05961 --- /dev/null +++ b/buildr/addon/buildr/nailgun.rb @@ -0,0 +1,221 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require 'jruby' +require 'rbconfig' +require 'tmpdir' +require 'buildr/drb' + + +module Buildr + + # This addon is provided for fast interaction with a DRb BuildrServer (buildr/drb). + # + # This module delegates task invocation to the BuildrServer, it only implements + # nailgun required logic (server/client). + # + # Usage: + # + # buildr -r buildr/nailgun nailgun:start + # + # Once the server has been started you can invoke tasks using the nailgun client + # installed on $JRUBY_HOME/tool/nailgun. It's recommended to add this path to + # your PATH environment variable, so that the ng command is available at any dir. + # + # ng build # invoke the build task + # + module Nailgun + extend self + + VERSION = '0.7.1' + NAME = "nailgun-#{VERSION}" + URL = "http://downloads.sourceforge.net/nailgun/#{NAME}.zip" + ARTIFACT_SPEC = "com.martiansoftware:nailgun:jar:#{VERSION}" + PORT = DRbApplication::PORT + 2 + ADDON_BIN = File.dirname(__FILE__) + + # Returns the path to JRUBY_HOME. + def jruby_home + ENV['JRUBY_HOME'] || Config::CONFIG['prefix'] + end + + # Returns the path to NAILGUN_HOME. + def nailgun_home + ENV['NAILGUN_HOME'] || File.expand_path('tool/nailgun', jruby_home) + end + + def tmp_path(*paths) + File.join(Dir.tmpdir, 'nailgun', *paths) + end + + module Util + extend self + + def add_to_sysloader(path) + sysloader = java.lang.ClassLoader.getSystemClassLoader + add_url_method = java.lang.Class.forName('java.net.URLClassLoader'). + getDeclaredMethod('addURL', [java.net.URL.java_class].to_java(java.lang.Class)) + add_url_method.setAccessible(true) + add_url_method.invoke(sysloader, [java.io.File.new(path).toURI.toURL].to_java(java.net.URL)) + end + + # invoke a java constructor + def ctor(on_class, *args) + parameters = [] + classes = [] + args.each do |obj| + case obj + when nil + classes.push(nil) + parameters.push(nil) + when Hash + vclass = obj.keys.first + value = obj[vclass] + classes.push(vclass.java_class) + parameters.push(value) + else + parameters.push obj + classes.push obj.class.java_class + end + end + on_class = [on_class.java_class].to_java(java.lang.Class)[0] + ctor = on_class.getDeclaredConstructor(classes.to_java(java.lang.Class)) + ctor.setAccessible(true) + ctor.newInstance(parameters.to_java(java.lang.Object)) + end + + end # Util + + module Client + + def main(nail) + nail.out.println "Connected to #{nail.getNGServer}" + + runtime = JRuby.runtime + + stdout = Util.ctor(org.jruby.RubyIO, runtime, java.io.OutputStream => nail.out) + stderr = Util.ctor(org.jruby.RubyIO, runtime, java.io.OutputStream => nail.err) + stdin = Util.ctor(org.jruby.RubyIO, runtime, java.io.InputStream => nail.in) + + dir = nail.getWorkingDirectory + argv = [nail.command] + nail.args + + DRbApplication.remote_run :dir => dir, :argv => argv, + :in => stdin, :out => stdout, :err => stderr + rescue => e + nail.err.println e unless SystemExit === e + nail.exit 1 + end + + end # Client + + module Server + def initialize(host, port) + @host = host || "*" + @port = port + super(host, port) + end + + def start + self.allow_nails_by_class_name = false + + NGClient::Main.nail = NGClient.new + self.default_nail_class = NGClient::Main + + @thread = java.lang.Thread.new(self) + @thread.setName(to_s) + @thread.start + + sleep 1 while getPort == 0 + info "#{self} Started." + end + + def stop + @thread.kill + end + + def to_s + version = "Buildr #{Buildr::VERSION} #{RUBY_PLATFORM[/java/] && '(JRuby '+ (Buildr.settings.build['jruby'] || JRUBY_VERSION) +')'}" + self.class.name+'('+[version, @host, @port].join(', ')+')' + end + end # Server + + namespace(:nailgun) do + + dist_zip = Buildr.download(tmp_path(NAME + '.zip') => URL) + dist_dir = Buildr.unzip(tmp_path(NAME) => dist_zip) + + nailgun_jar = file(tmp_path(NAME, NAME, NAME + '.jar')) + nailgun_jar.enhance [dist_dir] unless File.exist?(nailgun_jar.to_s) + + attr_reader :artifact + @artifact = Buildr.artifact(ARTIFACT_SPEC).from(nailgun_jar) + + compiled_bin = file(tmp_path(NAME, NAME, 'ng' + Config::CONFIG['EXEEXT']) => dist_dir.target) do |task| + unless task.to_s.pathmap('%x') == '.exe' + Dir.chdir(task.to_s.pathmap('%d')) do + info "Compiling #{task.to_s}" + system('make', task.to_s.pathmap('%f')) or + fail "Nailgun binary compilation failed." + end + end + end + + attr_reader :installed_bin + @installed_bin = file(File.expand_path(compiled_bin.to_s.pathmap('%f'), nailgun_home) => compiled_bin) do |task| + mkpath task.to_s.pathmap('%d'), :verbose => false + cp compiled_bin.to_s, task.to_s, :verbose => false + end + + task('drb-notice') do + info '' + info 'Running in JRuby, a nailgun server will be started so that' + info 'you can use your nailgun client to invoke buildr tasks: ' + info '' + info ' '+Nailgun.installed_bin.to_s + info '' + end + + task('drb' => ['drb-notice', 'start']) + + desc 'Start the nailgun server' + task('start' => [installed_bin, 'setup']) do |task| + server = NGServer.new(nil, PORT) + server.start + end + + task('setup' => artifact) do + module Util + include Buildr::Util + end + + Util.add_to_sysloader artifact.to_s + Util.add_to_sysloader ADDON_BIN + + class NGClient + include org.apache.buildr.BuildrNail + include Client + end + + class NGServer < com.martiansoftware.nailgun.NGServer + include Server + end + end + + end # ng_tasks + + end # module Nailgun +end diff --git a/buildr/addon/buildr/openjpa.rb b/buildr/addon/buildr/openjpa.rb new file mode 100644 index 0000000..3666e1a --- /dev/null +++ b/buildr/addon/buildr/openjpa.rb @@ -0,0 +1,84 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Provides OpenJPA bytecode enhancement and Mapping tool task. Require explicitly using require "buildr/openjpa". + module OpenJPA + + VERSION = "1.0.1" + + REQUIRES = [ "org.apache.openjpa:openjpa:jar:#{VERSION}", + "commons-collections:commons-collections:jar:3.1", + "commons-dbcp:commons-dbcp:jar:1.2.1", + "commons-lang:commons-lang:jar:2.1", + "commons-pool:commons-pool:jar:1.2", + "javax.persistence:persistence-api:jar:1.0", + "org.apache.geronimo.specs:geronimo-j2ee-connector_1.5_spec:jar:1.0", + "org.apache.geronimo.specs:geronimo-jta_1.0.1B_spec:jar:1.0", + "net.sourceforge.serp:serp:jar:1.11.0" ] + + class << self + + def enhance(options) + rake_check_options options, :classpath, :properties, :output + artifacts = Buildr.artifacts(options[:classpath]).each { |a| a.invoke }.map(&:to_s) + [options[:output].to_s] + properties = file(options[:properties]).tap { |task| task.invoke }.to_s + + Buildr.ant "openjpa" do |ant| + ant.taskdef :name=>"enhancer", :classname=>"org.apache.openjpa.ant.PCEnhancerTask", + :classpath=>requires.join(File::PATH_SEPARATOR) + ant.enhancer :directory=>options[:output].to_s do + ant.config :propertiesFile=>properties + ant.classpath :path=>artifacts.join(File::PATH_SEPARATOR) + end + end + end + + def mapping_tool(options) + rake_check_options options, :classpath, :properties, :sql, :action + artifacts = Buildr.artifacts(options[:classpath]).each{ |a| a.invoke }.map(&:to_s) + properties = file(options[:properties].to_s).tap { |task| task.invoke }.to_s + + Buildr.ant("openjpa") do |ant| + ant.taskdef :name=>"mapping", :classname=>"org.apache.openjpa.jdbc.ant.MappingToolTask", + :classpath=>requires.join(File::PATH_SEPARATOR) + ant.mapping :schemaAction=>options[:action], :sqlFile=>options[:sql].to_s, :ignoreErrors=>"true" do + ant.config :propertiesFile=>properties + ant.classpath :path=>artifacts.join(File::PATH_SEPARATOR) + end + end + end + + private + + def requires() + @requires ||= Buildr.artifacts(REQUIRES).each { |artifact| artifact.invoke }.map(&:to_s) + end + + end + + def open_jpa_enhance(options = nil) + jpa_options = { :output=>compile.target, :classpath=>compile.dependencies, + :properties=>path_to(:source, :main, :resources, 'META-INF/persistence.xml') } + OpenJPA.enhance jpa_options.merge(options || {}) + end + + end + + class Project + include OpenJPA + end +end diff --git a/buildr/addon/buildr/org/apache/buildr/BuildrNail$Main.class b/buildr/addon/buildr/org/apache/buildr/BuildrNail$Main.class new file mode 100644 index 0000000000000000000000000000000000000000..d23651fd70f63ebbedb5efdc53c4feb143c6434d GIT binary patch literal 561 zcmaKqO-sW-5Qg7P)22$(vh$H^q8U0+P|T;ZZwn(ZqHY;pM{=`uZ0&-#Y$wVTNs+H*_v9W ztfyZHUoo^>y+IW6P{gS(R1)2$k0O>_1EWmw{#iFtseDR1y3f9fvUng*eLd2h8a)_` z2jQTMih~j?3v~yZaLLT9=j~1$n3Ld~D<$J@Ad*BT3^gsu1EEIz(i_V`%Fvi4M!N~= zL1@ydi?n(&tF2I0pq!lL6<~`ZqiV7J2KJnTRXR&~u}+wSxqs*@jK64u(0RDO=pKw* jz)*d~65vKr+E7O+myrXG`i#TqIb;@5JmrhgTXN(2cU^WK`2&%v-Wakx|${zd= zJ1Gn+o!3TemUC;oJo(wnCn4F|+GTQ~v?L7PYNhvX6Ub@7-%J=XUjy5HR)5L)3UfFeQ%ahO`@Hrm5O=t&^jp(6Bd#Na~d literal 0 HcmV?d00001 diff --git a/buildr/addon/buildr/org/apache/buildr/BuildrNail.java b/buildr/addon/buildr/org/apache/buildr/BuildrNail.java new file mode 100644 index 0000000..706db62 --- /dev/null +++ b/buildr/addon/buildr/org/apache/buildr/BuildrNail.java @@ -0,0 +1,41 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + + +package org.apache.buildr; + +import com.martiansoftware.nailgun.NGContext; + +public interface BuildrNail { + + public void main(NGContext ctx); + + public static class Main { + private static BuildrNail nail; + public static void setNail(BuildrNail _nail) { nail = _nail; } + public static void nailMain(NGContext ctx) { + nail.main(ctx); + } + } + +} + +/* + * Local Variables: + * indent-tabs-mode: nil + * c-basic-offset: 2 + * End: + */ diff --git a/buildr/addon/buildr/org/apache/buildr/JettyWrapper$1.class b/buildr/addon/buildr/org/apache/buildr/JettyWrapper$1.class new file mode 100644 index 0000000000000000000000000000000000000000..8a35b986ec9be7d857fcb8b98527780872459168 GIT binary patch literal 223 zcmaKmJqp4=5QX1pG#UfO3rGrUmz+Tq6vV>9dXo(KVcCS;MDS`B9>7D1o6gF4%nT3a zea!pwd;v%h`tW@Ogy7orCgVe;n6Nr=*FA1Vwk?}&E$@=B-0Ms=w$jC!yRylBEC_L9 z3L#sWl`PV}Qn?Yo(M>`CAv|i$W~ZdJYzff-6iVtsT+*7ej6F1!e literal 0 HcmV?d00001 diff --git a/buildr/addon/buildr/org/apache/buildr/JettyWrapper$BuildrHandler.class b/buildr/addon/buildr/org/apache/buildr/JettyWrapper$BuildrHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..3429bbef498bc914930ded1d006872579db99242 GIT binary patch literal 3786 zcmbVPYj6|S75=VlY1hkwjd(ap8^=kEBOBX`DQ#R#Nr)lE#Jpn1#^lwNbg|Y*+GTfT z?1Yd9ltA-t(+4FXueP*J+JppT95*Q~4QbP)uRrY!(@g*LM}KxEzdJp5SCZG*L7k3f zcJDoV?z!hX=X~d0efXbu-UHByzbLpF12y!xQ^745lrW^A8iPWzOUCd5?8aRSa5wIe zaj$|V+$SVolc6g}ASofGpcY)l9vLG-FD=8U!6^1h$S7EjK^a-`&dD&v+fuL(NwJ45 z!BNlvS3w?Q3ie}Mh$n>XehClAI3VLeF*_*XAq8uUT*zFlStB{A^l<+MD>u^lQa}vHS;kyjs`*h24 z7?$*n>SMZ=ca4m;S$EP~b*q~p;-(F!X%$0bpJ|V1x}_)6TubDQOv=`_aMzs}vS~fH zh3+~dXSnMbZirV>ZW&~#?lDuGp|;P+@oo8Rg4+XnB14i$TF<4#fX98GGJ0-A>vwG< zH`47%#x;lAV;SyhY1g&1&GhK^1$Xd$dG5HC)f~&rIlQ}9c%p0?PjbsONFw2T5{^sw zJ_8uk-dv8`JsI6`IE83aMO+ndH+6P<`}Lc7JIQY|1jxm~D0YY(sMv(tRP5d}A4i937Z@Pm__vZGH={?3s-quCp zE2!_vW@f3Ycn`m1XszVEsGL4ir+^4Q&#U+qey!qy@clP9q2hg9WRU$x@sx@W@LQ3f z-w_>W01c#VbHAX5iVwx2-{T`ny_V0FGPLB*;!p&eQq`!tjg)|rl&2{Fn&X<3!;kR? z34c`aCt>K%!cbtNG7T82w%<-BLI ztG;A|!nK3CV(|mD;BpiLV&bwTlrmK03W4)OPBl>--z{*co`1HU<(~28`QXmya*%Qp zj?1$YYBTQ=1Yf)495rsPXT|v-4vHLY-2OjpQS2nNP?<>|@1p?=dqON-XsqLbv*} zS@Kz{h-x;^nq$07p9bH%*_#)?dS_0=U%+rk6(bra9o? zm1?1mN@sv)t&N6F88K}-@ah8e5@$5!XkJzl-dc}Y{jQ$eJ3#G>xZr2FhGbmI-tk8TS6h32%p zsvcp;v`cM!RGNYuLfaH-7@|r63y!0{eF`eg78bCm?L2C$3#dC?)L86k{JSk0ox+k3 z)`r`nOZ`#gEi9YHa`K@*dQ}0jSePEstHmfnj{-hdz~@`fAR?yMoJYf2DJES+Lrf~* zT9Sv(p;3&lE9qQ6G>wMgSU47$#0^3@{4Sb?tD-CVhpVH_{RP|@lco_Lj!BbfDWElA zW2G?E<{J{j_MsxA4ua$lg;*3+fZxL}j&wisyDL zM<1@kR^hf6;?!jxgmu_J?K0*?ILkfQf z$G@-#pJIfCkY);Uw7bD-G0K)=FS`aAwgOq!43n+FKDHh<>x08~!ex7rXBNiT0qkc- zFu|V1{p>g%Ca)i0CvkwiiU-;2ILOZ6A$AszunRcEKE`48R~%uNaEwqr#{Pk$?4Ni% z6u}drMR<}>0TCieAGX$k__njiw+50`iEphdC&6Y27NPGTeufCQU~NBk&^t<_ZS+qL Mbu#fekMAJ#Uz438&;S4c literal 0 HcmV?d00001 diff --git a/buildr/addon/buildr/org/apache/buildr/JettyWrapper.class b/buildr/addon/buildr/org/apache/buildr/JettyWrapper.class new file mode 100644 index 0000000000000000000000000000000000000000..2f2b397a27ac65436195d6bae9dc8d94f0004d7f GIT binary patch literal 1435 zcmbVMdr#9)6#w153Z=k!MIHj86U!KM0|XRAWddRljWd|(AEeuEpro`*TL^p=KZPc; zB`*2_{7}YoOJyK0Lz>)k&b`0$xaa$ljp)FLSdub2F)AS?BZ4sr<1%6x zZGa|ZT0%yKj0s^(N|=(-EWi(ioE9E4VwsijNWxHg8ylx4;llS4TETWbG1XXk9ZbzP`6x;Ldx!VA77fbWr4F;pIx0 zCtEKTx$7n}=`_QJ>Sx!>dU2a;1^+(UD^c$UM=zJTGdFUx^8RL-5LxDLl;xyha?aj! zihS7+*J(Rl-T02auPf+5uYx|@R?v?p4BeMCUz`sxsOQfwZa6(@&=Um9xo|iW( zafT2g+cO2vvB)s-zbWf)%+Pb*DPV$vEM6#B!lHs@api%l3uB0jPHCoYZE0@`JH#S} zJ6HQ8G7KGcuM-wAgjGSf7MFVgKi}%!Hba-XR)^<2$FR2M1aRrv*T05?iHsjdri8)$ z>B7fB&;DY;sgG`|qv=;2R6kw77=m|qK`P{YeX}WVVVZS zCE6b(>ocM>EP#*6D#FPcBKf{5qDNpg#PU^0uT$*}6*Sf$=aa08ruEcV1quP1>9pkt zq2xD&B0tcY52tb!#K~l0tAaM6R?z;HqSK4fI*uk + */ +public class JettyWrapper { + + private Server _server; + private ContextHandlerCollection _handlerColl; + + public JettyWrapper(int port) throws Exception { + _server = new Server(port); + // Adding the buildr handler to control our server lifecycle + ContextHandler context = new ContextHandler(); + context.setContextPath("/buildr"); + Handler handler = new BuildrHandler(); + context.setHandler(handler); + + _handlerColl = new ContextHandlerCollection(); + _handlerColl.setHandlers(new Handler[] {context}); + + _server.addHandler(_handlerColl); + _server.start(); + } + +/* + public void join() { + try { + _server.join(); + } catch (Exception e) { + e.printStackTrace(); + } + } +*/ + + private class BuildrHandler extends AbstractHandler { + + private HashMap _apps = new HashMap(); + + public void handle(String string, HttpServletRequest request, + HttpServletResponse response, int i) throws IOException, ServletException { + response.setContentType("text/html"); + if (request.getPathInfo().equals("/")) { + response.getWriter().println("Alive"); + ((Request)request).setHandled(true); + return; + } else if (request.getPathInfo().equals("/deploy")) { + try { + String webapp = request.getParameter("webapp"); + String path = request.getParameter("path"); + System.out.println("Deploying " + webapp + " in " + path); + WebAppContext context; + + context = (WebAppContext) _apps.get(path); + if (context != null) { + context.stop(); + _handlerColl.removeHandler(context); + _apps.remove(path); + } + + context = new WebAppContext(webapp, path); + context.setConfigurationClasses(new String[] { + "org.mortbay.jetty.webapp.WebInfConfiguration", + "org.mortbay.jetty.webapp.WebXmlConfiguration"}); + context.setClassLoader(new WebAppClassLoader(context)); + + _handlerColl.addHandler(context); + context.start(); + _apps.put(path, context); + response.getWriter().println("Deployed"); + response.getWriter().println(context.getTempDirectory()); + ((Request)request).setHandled(true); + } catch (Throwable e) { + e.printStackTrace(); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + ((Request)request).setHandled(true); + return; + } + } else if (request.getPathInfo().equals("/undeploy")) { + try { + String path = request.getParameter("path"); + WebAppContext context = (WebAppContext) _apps.get(path); + if (context != null) { + System.out.println("Undeploying app at " + path); + context.stop(); + _handlerColl.removeHandler(context); + _apps.remove(path); + } + response.getWriter().println("Undeployed"); + ((Request)request).setHandled(true); + } catch (Throwable e) { + e.printStackTrace(); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + ((Request)request).setHandled(true); + return; + } + } else if (request.getPathInfo().equals("/stop")) { + try { + _server.stop(); + _server.destroy(); + // Brute force + System.exit(0); + } catch (Exception e) { + e.printStackTrace(); + } + } + response.getWriter().println("OK " + request.getPathInfo()); + ((Request)request).setHandled(true); + } + + } +} diff --git a/buildr/addon/buildr/pmd.rake b/buildr/addon/buildr/pmd.rake new file mode 100644 index 0000000..14372bc --- /dev/null +++ b/buildr/addon/buildr/pmd.rake @@ -0,0 +1,166 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + # Provides the pmd:rule:xml, pmd:rule:html, pmd:cpd:xml + # and pmd:cpd:html tasks. + # + # Require explicitly using require "buildr/pmd". + module Pmd + + class << self + + # The specs for requirements + def dependencies + [ + 'pmd:pmd:jar:4.2.6', + 'jaxen:jaxen:jar:1.1.1', + 'asm:asm:jar:3.2' + ] + end + + def pmd(rule_set_files, format, output_file_prefix, source_paths, options = {}) + dependencies = (options[:dependencies] || []) + self.dependencies + cp = Buildr.artifacts(dependencies).each(&:invoke).map(&:to_s) + (options[:rule_set_paths] || []).each {|p| cp << p} + + puts "PMD: Analyzing source code..." + mkdir_p File.dirname(output_file_prefix) + + Buildr.ant("pmd-report") do |ant| + ant.taskdef :name=> 'pmd', :classpath => cp.join(';'), :classname => 'net.sourceforge.pmd.ant.PMDTask' + ant.pmd :shortFilenames => true, :rulesetfiles => rule_set_files.join(',') do + ant.formatter :type => format, :toFile => "#{output_file_prefix}.#{format}" + source_paths.each do |src| + ant.fileset :dir=> src, :includes=>'**/*.java' + end + end + + end + end + + def cpd(format, output_file_prefix, source_paths, options = {}) + dependencies = (options[:dependencies] || []) + self.dependencies + cp = Buildr.artifacts(dependencies).each(&:invoke).map(&:to_s) + minimum_token_count = options[:minimum_token_count] || 100 + encoding = options[:encoding] || 'UTF-8' + + puts "PMD-CPD: Analyzing source code..." + mkdir_p File.dirname(output_file_prefix) + + Buildr.ant("cpd-report") do |ant| + ant.taskdef :name=> 'cpd', :classpath => cp.join(';'), :classname => 'net.sourceforge.pmd.cpd.CPDTask' + ant.cpd :format => format, :minimumTokenCount => minimum_token_count, :encoding => encoding, :outputFile => "#{output_file_prefix}.#{format}" do + source_paths.each do |src| + ant.fileset :dir=> src, :includes=>'**/*.java' + end + end + + end + end + end + + class Config + + attr_writer :enabled + + def enabled? + !!@enabled + end + + attr_writer :rule_set_files + + def rule_set_files + @rule_set_files || ['basic','imports','unusedcode'] + end + + attr_writer :rule_set_paths + + def rule_set_paths + @rule_set_paths ||= [] + end + + attr_writer :report_dir + + def report_dir + @report_dir || project._(:reports, :pmd) + end + + attr_writer :output_file_prefix + + def output_file_prefix + @output_file_prefix || "#{self.report_dir}/pmd" + end + + attr_writer :cpd_output_file_prefix + + def cpd_output_file_prefix + @cpd_output_file_prefix || "#{self.report_dir}/cpd" + end + + def source_paths + @source_paths ||= [self.project.compile.sources, self.project.test.compile.sources] + end + + def flat_source_paths + source_paths.flatten.compact + end + + protected + + def initialize(project) + @project = project + end + + attr_reader :project + end + + module ProjectExtension + include Extension + + def pmd + @pmd ||= Buildr::Pmd::Config.new(project) + end + + after_define do |project| + if project.pmd.enabled? + desc "Generate pmd xml report." + project.task("pmd:rule:xml") do + Buildr::Pmd.pmd(project.pmd.rule_set_files, 'xml', project.pmd.output_file_prefix, project.pmd.flat_source_paths, :rule_set_paths => project.pmd.rule_set_paths) + end + + desc "Generate pmd html report." + project.task("pmd:rule:html") do + Buildr::Pmd.pmd(project.pmd.rule_set_files, 'html', project.pmd.output_file_prefix, project.pmd.flat_source_paths, :rule_set_paths => project.pmd.rule_set_paths) + end + + desc "Generate pmd cpd xml report." + project.task("pmd:cpd:xml") do + Buildr::Pmd.cpd('xml', project.pmd.cpd_output_file_prefix, project.pmd.flat_source_paths) + end + + desc "Generate pmd cpd text report." + project.task("pmd:cpd:text") do + Buildr::Pmd.cpd('text', project.pmd.cpd_output_file_prefix, project.pmd.flat_source_paths) + end + end + end + end + end +end + +class Buildr::Project + include Buildr::Pmd::ProjectExtension +end diff --git a/buildr/addon/buildr/protobuf.rb b/buildr/addon/buildr/protobuf.rb new file mode 100644 index 0000000..502ed66 --- /dev/null +++ b/buildr/addon/buildr/protobuf.rb @@ -0,0 +1,87 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Provides Protocol buffer code generation tasks. + # + # Require explicitly using require "buildr/protobuf". + # + # Usage in your project: + # + # protoc _("path/to/proto/files") + # + # and also supports two options, + # + # :output => "target/generated/protoc" # this is the default + # :lang => "java" # defaults to compile.language + # + module Protobuf + class << self + def protoc(*args) + options = Hash === args.last ? args.pop : {} + rake_check_options options, :output, :lang, :include + + options[:lang] ||= :java + options[:output] ||= File.expand_path "target/generated/protoc" + options[:include] ||= [] + + command_line = [] + + command_line << "--#{options[:lang]}_out=#{options[:output]}" if options[:output] + + (paths_from_sources(*args) + options[:include]).each { |i| command_line << "-I#{i}" } + + command_line += files_from_sources(*args) + + mkdir_p( options[:output] ) + + system protoc_path, *command_line + end + + def protoc_path + ENV['PROTOC'] || "protoc" + end + + def files_from_sources(*args) + args.flatten.map(&:to_s).collect { |f| File.directory?(f) ? FileList[f + "/**/*.proto"] : f }.flatten + end + + def paths_from_sources(*args) + args.flatten.map(&:to_s).collect { |f| File.directory?(f) ? f : File.dirname(f) } + end + end + + def protoc(*args) + if Hash === args.last + options = args.pop + else + options = {} + end + + options[:output] ||= path_to(:target, :generated, :protoc) + options[:lang] ||= compile.language if compile.language + + file(options[:output]=>Protobuf.files_from_sources(*args)) do |task| + Protobuf.protoc task.prerequisites, options + end + end + + end + + class Project + include Protobuf + end +end diff --git a/buildr/addon/buildr/xmlbeans.rb b/buildr/addon/buildr/xmlbeans.rb new file mode 100644 index 0000000..3515646 --- /dev/null +++ b/buildr/addon/buildr/xmlbeans.rb @@ -0,0 +1,88 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Provides XMLBeans schema compiler. Require explicitly using require "buildr/xmlbeans". + # + # require 'buildr/xmlbeans' + # define 'some_proj' do + # compile_xml_beans _(:source, :main, :xsd) # the directory with *.xsd + # end + module XMLBeans + + # You can use ArtifactNamespace to customize the versions of + # :xmlbeans or :stax used by this module: + # + # require 'buildr/xmlbeans' + # Buildr::XMLBeans::REQUIRES.xmlbeans = '2.2.0' + REQUIRES = ArtifactNamespace.for(self) do |ns| + ns.xmlbeans! 'org.apache.xmlbeans:xmlbeans:jar:2.3.0', '>2' + ns.stax_api! 'stax:stax-api:jar:>=1.0.1' + end + + class << self + + def compile(*args) + options = Hash === args.last ? args.pop : {} + options[:verbose] ||= trace?(:xmlbeans) + rake_check_options options, :verbose, :noop, :javasource, :jar, :compile, :output, :xsb + puts "Running XMLBeans schema compiler" if verbose + Buildr.ant "xmlbeans" do |ant| + ant.taskdef :name=>"xmlbeans", :classname=>"org.apache.xmlbeans.impl.tool.XMLBean", + :classpath=>requires.join(File::PATH_SEPARATOR) + ant.xmlbeans :srconly=>"true", :srcgendir=>options[:output].to_s, :classgendir=>options[:output].to_s, + :javasource=>options[:javasource] do + args.flatten.each { |file| ant.fileset File.directory?(file) ? { :dir=>file } : { :file=>file } } + end + end + # Touch paths to let other tasks know there's an update. + touch options[:output].to_s, :verbose=>false + end + + def requires() + @requires ||= REQUIRES.artifacts + end + end + + def compile_xml_beans(*args) + # Run whenever XSD file changes, but typically we're given an directory of XSD files, or even file patterns + # (the last FileList is there to deal with things like *.xsdconfig). + files = args.flatten.map { |file| File.directory?(file) ? FileList["#{file}/*.xsd"] : FileList[file] }.flatten + # Generate sources and add them to the compile task. + generated = file(path_to(:target, :generated, :xmlbeans)=>files) do |task| + XMLBeans.compile args.flatten, :output=>task.name, + :javasource=>compile.options.source, :xsb=>compile.target + end + compile.using(:javac).from(generated).with(*XMLBeans.requires) + # Once compiled, we need to copy the generated XSB/XSD and one (magical?) class file + # into the target directory, or the rest is useless. + compile do |task| + verbose(false) do + base = generated.to_s + FileList["#{base}/**/*.{class,xsb,xsd}"].each do |file| + target = File.join(compile.target.to_s, Util.relative_path(file, base)) + mkpath File.dirname(target) ; cp file, target + end + end + end + end + + end + + class Project + include XMLBeans + end +end diff --git a/buildr/all-in-one/_buildr b/buildr/all-in-one/_buildr new file mode 100644 index 0000000..22dc64b --- /dev/null +++ b/buildr/all-in-one/_buildr @@ -0,0 +1,19 @@ +#!/usr/bin/env jruby +# +# This file was generated by RubyGems. +# +# The application 'buildr' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0" + +if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then + version = $1 + ARGV.shift +end + +gem 'buildr', version +load Gem.bin_path('buildr', 'buildr', version) diff --git a/buildr/all-in-one/buildr b/buildr/all-in-one/buildr new file mode 100755 index 0000000..efad8dd --- /dev/null +++ b/buildr/all-in-one/buildr @@ -0,0 +1,368 @@ +#!/bin/bash +# ----------------------------------------------------------------------------- +# jruby.sh - Start Script for the JRuby interpreter +# +# Environment Variable Prequisites +# +# JRUBY_OPTS (Optional) Default JRuby command line args +# JRUBY_SHELL Where/What is system shell +# +# JAVA_HOME Must point at your Java Development Kit installation. +# +# ----------------------------------------------------------------------------- + +cygwin=false + +# ----- Identify OS we are running under -------------------------------------- +case "`uname`" in + CYGWIN*) cygwin=true;; + Darwin) darwin=true;; +esac + +# ----- Verify and Set Required Environment Variables ------------------------- + +## resolve links - $0 may be a link to home +PRG=$0 +progname=`basename "$0"` + +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '.*/.*' > /dev/null; then + if expr "$link" : '/' > /dev/null; then + PRG="$link" + else + PRG="`dirname ${PRG}`/${link}" + fi + else + PRG="`dirname $PRG`/$link" + fi +done + +JRUBY_HOME_1=`dirname "$PRG"` # the ./bin dir +if [ "$JRUBY_HOME_1" = '.' ] ; then + cwd=`pwd` + JRUBY_HOME=`dirname $cwd` # JRUBY-2699 +else + JRUBY_HOME=`dirname "$JRUBY_HOME_1"` # the . dir +fi + +if [ -z "$JRUBY_OPTS" ] ; then + JRUBY_OPTS="" +fi + +JRUBY_OPTS_SPECIAL="--ng" # space-separated list of special flags +unset JRUBY_OPTS_TEMP +function process_special_opts { + case $1 in + --ng) nailgun_client=true;; + *) break;; + esac +} +for opt in ${JRUBY_OPTS[@]}; do + for special in ${JRUBY_OPTS_SPECIAL[@]}; do + if [ $opt != $special ]; then + JRUBY_OPTS_TEMP="${JRUBY_OPTS_TEMP} $opt" + else + # make sure flags listed in JRUBY_OPTS_SPECIAL are processed + case "$opt" in + --ng) + process_special_opts $opt;; + esac + fi + done +done +JRUBY_OPTS=${JRUBY_OPTS_TEMP} + +if [ -z "$JAVA_HOME" ] ; then + JAVA_CMD='java' +else + if $cygwin; then + JAVA_HOME=`cygpath -u "$JAVA_HOME"` + fi + JAVA_CMD="$JAVA_HOME/bin/java" +fi + +# If you're seeing odd exceptions, you may have a bad JVM install. +# Uncomment this and report the version to the JRuby team along with error. +#$JAVA_CMD -version + +JRUBY_SHELL=/bin/sh + +# ----- Set Up The Boot Classpath ------------------------------------------- + +CP_DELIMITER=":" + +# add main jruby jar to the bootclasspath +for j in "$JRUBY_HOME"/lib/jruby.jar "$JRUBY_HOME"/lib/jruby-complete.jar; do + if [ ! -e "$j" ]; then + continue + fi + if [ "$JRUBY_CP" ]; then + JRUBY_CP="$JRUBY_CP$CP_DELIMITER$j" + else + JRUBY_CP="$j" + fi + if [ $JRUBY_ALREADY_ADDED ]; then + echo "WARNING: more than one JRuby JAR found in lib directory" + fi + JRUBY_ALREADY_ADDED=true +done + +if $cygwin; then + JRUBY_CP=`cygpath -p -w "$JRUBY_CP"` +fi + +# ----- Set Up The System Classpath ------------------------------------------- + +if [ "$JRUBY_PARENT_CLASSPATH" != "" ]; then + # Use same classpath propagated from parent jruby + CP=$JRUBY_PARENT_CLASSPATH +else + # add other jars in lib to CP for command-line execution + for j in "$JRUBY_HOME"/lib/*.jar; do + if [ "$j" == "$JRUBY_HOME"/lib/jruby.jar ]; then + continue + fi + if [ "$j" == "$JRUBY_HOME"/lib/jruby-complete.jar ]; then + continue + fi + if [ "$CP" ]; then + CP="$CP$CP_DELIMITER$j" + else + CP="$j" + fi + done + + if $cygwin; then + CP=`cygpath -p -w "$CP"` + fi +fi + +if $cygwin; then + # switch delimiter only after building Unix style classpaths + CP_DELIMITER=";" +fi + +# ----- Execute The Requested Command ----------------------------------------- + +if [ -z "$JAVA_MEM" ] ; then + JAVA_MEM=-Xmx500m +fi + +if [ -z "$JAVA_STACK" ] ; then + JAVA_STACK=-Xss1024k +fi + +JAVA_VM=-client +JAVA_ENCODING="" + +declare -a java_args +declare -a ruby_args + +java_class=org.jruby.Main + +# Split out any -J argument for passing to the JVM. +# Scanning for args is aborted by '--'. +while [ $# -gt 0 ] +do + case "$1" in + # Stuff after '-J' in this argument goes to JVM + -J*) + val=${1:2} + if [ "${val:0:4}" = "-Xmx" ]; then + JAVA_MEM=$val + elif [ "${val:0:4}" = "-Xss" ]; then + JAVA_STACK=$val + elif [ "${val}" = "" ]; then + $JAVA_CMD -help + echo "(Prepend -J in front of these options when using 'jruby' command)" + exit + elif [ "${val}" = "-X" ]; then + $JAVA_CMD -X + echo "(Prepend -J in front of these options when using 'jruby' command)" + exit + elif [ "${val}" = "-classpath" ]; then + CP="$CP$CP_DELIMITER$2" + CLASSPATH="" + shift + elif [ "${val}" = "-cp" ]; then + CP="$CP$CP_DELIMITER$2" + CLASSPATH="" + shift + else + if [ "${val:0:3}" = "-ea" ]; then + VERIFY_JRUBY="yes" + elif [ "${val:0:16}" = "-Dfile.encoding=" ]; then + JAVA_ENCODING=$val + fi + java_args=("${java_args[@]}" "${1:2}") + fi + ;; + # Match switches that take an argument + -C|-e|-I|-S) ruby_args=("${ruby_args[@]}" "$1" "$2"); shift ;; + # Match same switches with argument stuck together + -e*|-I*|-S*) ruby_args=("${ruby_args[@]}" "$1" ) ;; + # Run with the instrumented profiler: http://jiprof.sourceforge.net/ + --profile) + PROFILE_ARGS="-javaagent:$JRUBY_HOME/lib/profile.jar -Dprofile.properties=$JRUBY_HOME/lib/profile-ruby.properties" + JRUBY_OPTS=("${JRUBY_OPTS[@]}" "-X+C") + VERIFY_JRUBY="yes" + ;; + # Run with the instrumented profiler: http://jiprof.sourceforge.net/ + --profile-all) + PROFILE_ARGS="-javaagent:$JRUBY_HOME/lib/profile.jar -Dprofile.properties=$JRUBY_HOME/lib/profile-all.properties" + JRUBY_OPTS=("${JRUBY_OPTS[@]}" "-X+C") + VERIFY_JRUBY="yes" + ;; + # Run with JMX management enabled + --manage) + java_args=("${java_args[@]}" "-Dcom.sun.management.jmxremote") + java_args=("${java_args[@]}" "-Djruby.management.enabled=true") ;; + # Don't launch a GUI window, no matter what + --headless) + java_args=("${java_args[@]}" "-Djava.awt.headless=true") ;; + # Run under JDB + --jdb) + if [ -z "$JAVA_HOME" ] ; then + JAVA_CMD='jdb' + else + if $cygwin; then + JAVA_HOME=`cygpath -u "$JAVA_HOME"` + fi + JAVA_CMD="$JAVA_HOME/bin/jdb" + fi + java_args=("${java_args[@]}" "-sourcepath" "$JRUBY_HOME/lib/ruby/1.8:.") + JRUBY_OPTS=("${JRUBY_OPTS[@]}" "-X+C") ;; + --client) + JAVA_VM=-client ;; + --server) + JAVA_VM=-server ;; + --noclient) # JRUBY-4296 + unset JAVA_VM ;; # For IBM JVM, neither '-client' nor '-server' is applicable + --sample) + java_args=("${java_args[@]}" "-Xprof") ;; + --ng-server) + # Start up as Nailgun server + java_class=com.martiansoftware.nailgun.NGServer + VERIFY_JRUBY=true ;; + --ng) + # Use native Nailgun client to toss commands to server + process_special_opts "--ng" ;; + # Abort processing on the double dash + --) break ;; + # Other opts go to ruby + -*) ruby_args=("${ruby_args[@]}" "$1") ;; + # Abort processing on first non-opt arg + *) break ;; + esac + shift +done + +# Force file.encoding to UTF-8 when on Mac, since Apple JDK defaults to MacRoman (JRUBY-3576) +if [[ $darwin && -z "$JAVA_ENCODING" ]]; then + java_args=("${java_args[@]}" "-Dfile.encoding=UTF-8") +fi + +# Add a property to report memory max +JAVA_OPTS="$JAVA_OPTS $JAVA_VM -Djruby.memory.max=${JAVA_MEM:4} -Djruby.stack.max=${JAVA_STACK:4}" + +# Append the rest of the arguments +ruby_args=("${ruby_args[@]}" "$@") + +# Put the ruby_args back into the position arguments $1, $2 etc +set -- "${ruby_args[@]}" + +JAVA_OPTS="$JAVA_OPTS $JAVA_MEM $JAVA_STACK" + +JFFI_BOOT="" +if [ -d $JRUBY_HOME/lib/native/ ]; then + for d in $JRUBY_HOME/lib/native/*`uname -s`; do + if [ -z "$JFFI_BOOT" ]; then + JFFI_BOOT="$d" + else + JFFI_BOOT="$JFFI_BOOT:$d" + fi + done +fi +JFFI_OPTS="-Djffi.boot.library.path=$JFFI_BOOT" + +if $cygwin; then + JRUBY_HOME=`cygpath --mixed "$JRUBY_HOME"` + JRUBY_SHELL=`cygpath --mixed "$JRUBY_SHELL"` + + if [[ ( "${1:0:1}" = "/" ) && ( ( -f "$1" ) || ( -d "$1" )) ]]; then + win_arg=`cygpath -w "$1"` + shift + win_args=("$win_arg" "$@") + set -- "${win_args[@]}" + fi + + # fix JLine to use UnixTerminal + stty -icanon min 1 -echo > /dev/null 2>&1 + if [ $? = 0 ]; then + JAVA_OPTS="$JAVA_OPTS -Djline.terminal=jline.UnixTerminal" + fi + +fi + +if [ "$nailgun_client" != "" ]; then + if [ -f $JRUBY_HOME/tool/nailgun/ng ]; then + exec $JRUBY_HOME/tool/nailgun/ng org.jruby.util.NailMain $JRUBY_OPTS "$@" + else + echo "error: ng executable not found; run 'make' in ${JRUBY_HOME}/tool/nailgun" + exit 1 + fi +else +if [ "$VERIFY_JRUBY" != "" ]; then + if [ "$PROFILE_ARGS" != "" ]; then + echo "Running with instrumented profiler" + fi + + "$JAVA_CMD" $PROFILE_ARGS $JAVA_OPTS "$JFFI_OPTS" "${java_args[@]}" -classpath "$JRUBY_CP$CP_DELIMITER$CP$CP_DELIMITER$CLASSPATH" \ + "-Djruby.home=$JRUBY_HOME" \ + "-Djruby.lib=$JRUBY_HOME/lib" -Djruby.script=jruby \ + "-Djruby.shell=$JRUBY_SHELL" \ + $java_class $JRUBY_OPTS -S _buildr "$@" + + # Record the exit status immediately, or it will be overridden. + JRUBY_STATUS=$? + + if [ "$PROFILE_ARGS" != "" ]; then + echo "Profiling results:" + cat profile.txt + rm profile.txt + fi + + if $cygwin; then + stty icanon echo > /dev/null 2>&1 + fi + + exit $JRUBY_STATUS +else + if $cygwin; then + # exec doed not work correctly with cygwin bash + "$JAVA_CMD" $JAVA_OPTS "$JFFI_OPTS" "${java_args[@]}" -Xbootclasspath/a:"$JRUBY_CP" -classpath "$CP$CP_DELIMITER$CLASSPATH" \ + "-Djruby.home=$JRUBY_HOME" \ + "-Djruby.lib=$JRUBY_HOME/lib" -Djruby.script=jruby \ + "-Djruby.shell=$JRUBY_SHELL" \ + $java_class $JRUBY_OPTS -S _buildr "$@" + + # Record the exit status immediately, or it will be overridden. + JRUBY_STATUS=$? + + stty icanon echo > /dev/null 2>&1 + + exit $JRUBY_STATUS + else + exec "$JAVA_CMD" $JAVA_OPTS "$JFFI_OPTS" "${java_args[@]}" -Xbootclasspath/a:"$JRUBY_CP" -classpath "$CP$CP_DELIMITER$CLASSPATH" \ + "-Djruby.home=$JRUBY_HOME" \ + "-Djruby.lib=$JRUBY_HOME/lib" -Djruby.script=jruby \ + "-Djruby.shell=$JRUBY_SHELL" \ + $java_class $JRUBY_OPTS -S _buildr "$@" + fi +fi +fi + +# Be careful adding code down here, you might override the exit +# status of the jruby invocation. diff --git a/buildr/all-in-one/buildr.cmd b/buildr/all-in-one/buildr.cmd new file mode 100755 index 0000000..39ad199 --- /dev/null +++ b/buildr/all-in-one/buildr.cmd @@ -0,0 +1 @@ +@%~dp0\_buildr.exe %* \ No newline at end of file diff --git a/buildr/bin/buildr b/buildr/bin/buildr new file mode 100755 index 0000000..9c53ee2 --- /dev/null +++ b/buildr/bin/buildr @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require 'rubygems' +require 'buildr' +Buildr.application.run diff --git a/buildr/buildr.buildfile b/buildr/buildr.buildfile new file mode 100644 index 0000000..7bc24cb --- /dev/null +++ b/buildr/buildr.buildfile @@ -0,0 +1,58 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +$LOADED_FEATURES << 'jruby' unless RUBY_PLATFORM =~ /java/ # Pretend to have JRuby, keeps Nailgun happy. +require 'buildr/jetty' +require 'buildr/nailgun' +require 'buildr/scala' +repositories.remote << 'http://repo1.maven.org/maven2' +repositories.remote << 'http://scala-tools.org/repo-releases/' + + +define 'buildr' do + compile.using :source=>'1.5', :target=>'1.5', :debug=>false + + define 'java' do + compile.using(:javac).from(FileList['lib/buildr/java/**/*.java']).into('lib/buildr/java') + end + + define 'scala' do + compile.using(:javac).from(FileList['lib/buildr/scala/**/*.java']).into('lib/buildr/scala') + end + + desc 'Buildr extra packages (Antlr, Cobertura, Hibernate, Javacc, JDepend, Jetty, OpenJPA, XmlBeans)' + define 'extra', :version=>'1.0' do + compile.using(:javac).from(FileList['addon/buildr/**/*.java']).into('addon/buildr').with(Buildr::Jetty::REQUIRES, Buildr::Nailgun::ARTIFACT_SPEC) + # Legals included in source code and show in RDoc. + legal = 'LICENSE', 'NOTICE' + package(:gem).include(legal).path('lib').include('addon/buildr') + package(:gem).spec do |spec| + spec.author = 'Apache Buildr' + spec.email = 'users@buildr.apache.org' + spec.homepage = "http://buildr.apache.org" + spec.rubyforge_project = 'buildr' + spec.extra_rdoc_files = legal + spec.rdoc_options << '--webcvs' << 'http://svn.apache.org/repos/asf/buildr/trunk/' + spec.add_dependency 'buildr', '~> 1.3' + end + + install do + addon package(:gem) + end + + upload do + end + end +end diff --git a/buildr/buildr.gemspec b/buildr/buildr.gemspec new file mode 100644 index 0000000..15bc772 --- /dev/null +++ b/buildr/buildr.gemspec @@ -0,0 +1,89 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +unless defined?(Buildr::VERSION) + require File.join(File.dirname(__FILE__), 'lib', 'buildr', 'version.rb') + $LOADED_FEATURES << 'buildr/version.rb' +end + +Gem::Specification.new do |spec| + spec.name = 'buildr' + spec.version = Buildr::VERSION.dup + spec.author = 'Apache Buildr' + spec.email = "users@buildr.apache.org" + spec.homepage = "http://buildr.apache.org/" + spec.summary = "Build like you code" + spec.description = <<-TEXT +Apache Buildr is a build system for Java-based applications, including support +for Scala, Groovy and a growing number of JVM languages and tools. We wanted +something that's simple and intuitive to use, so we only need to tell it what +to do, and it takes care of the rest. But also something we can easily extend +for those one-off tasks, with a language that's a joy to use. + TEXT + spec.rubyforge_project = 'buildr' + + # Rakefile needs to create spec for both platforms (ruby and java), using the + # $platform global variable. In all other cases, we figure it out from RUBY_PLATFORM. + spec.platform = $platform || RUBY_PLATFORM[/java/] || 'ruby' + + spec.files = Dir['{addon,bin,doc,etc,lib,rakelib,spec}/**/*', '*.{gemspec,buildfile}'] + + ['LICENSE', 'NOTICE', 'CHANGELOG', 'README.rdoc', 'Rakefile', '_buildr', '_jbuildr'] + spec.require_paths = 'lib', 'addon' + spec.bindir = 'bin' # Use these for applications. + spec.executable = 'buildr' + + spec.extra_rdoc_files = 'README.rdoc', 'CHANGELOG', 'LICENSE', 'NOTICE' + spec.rdoc_options = '--title', 'Buildr', '--main', 'README.rdoc', + '--webcvs', 'http://svn.apache.org/repos/asf/buildr/trunk/' + spec.post_install_message = "To get started run buildr --help" + + spec.required_rubygems_version = ">= 1.8.6" + + # Tested against these dependencies. + spec.add_dependency 'rake', '0.8.7' + spec.add_dependency 'builder', '2.1.2' + spec.add_dependency 'net-ssh', '2.0.23' + spec.add_dependency 'net-sftp', '2.0.4' + spec.add_dependency 'rubyzip', '0.9.4' + spec.add_dependency 'highline', '1.6.2' + spec.add_dependency 'json_pure', '1.4.3' + spec.add_dependency 'rubyforge', '2.0.3' + spec.add_dependency 'hoe', '2.3.3' + spec.add_dependency 'rjb', '1.3.7' if spec.platform.to_s == 'x86-mswin32' || spec.platform.to_s == 'ruby' + spec.add_dependency 'atoulme-Antwrap', '~> 0.7.2' + spec.add_dependency 'diff-lcs', '1.1.2' + spec.add_dependency 'rspec-expectations', '2.1.0' + spec.add_dependency 'rspec-mocks', '2.1.0' + spec.add_dependency 'rspec-core', '2.1.0' + spec.add_dependency 'rspec', '2.1.0' + spec.add_dependency 'xml-simple', '1.0.12' + spec.add_dependency 'minitar', '0.5.3' + spec.add_dependency 'jruby-openssl', '>= 0.7' if spec.platform.to_s == 'java' + + # The documentation is currently not generated whe building via jruby + unless spec.platform.to_s == 'java' + spec.add_development_dependency 'jekyll', '0.11.0' + spec.add_development_dependency 'RedCloth', '4.2.9' + spec.add_development_dependency 'jekylltask', '1.1.0' + spec.add_development_dependency 'rdoc', '3.8' + spec.add_development_dependency 'rcov', '0.9.9' + end + + spec.add_development_dependency 'ci_reporter', '1.6.3' + + spec.add_development_dependency 'bundler' + spec.add_development_dependency 'win32console' if spec.platform.to_s == 'x86-mswin32' + spec.add_development_dependency 'rubyforge' +end diff --git a/buildr/doc/_config.yml b/buildr/doc/_config.yml new file mode 100644 index 0000000..b136b57 --- /dev/null +++ b/buildr/doc/_config.yml @@ -0,0 +1 @@ +pygments: true diff --git a/buildr/doc/_layouts/default.html b/buildr/doc/_layouts/default.html new file mode 100644 index 0000000..16d53ac --- /dev/null +++ b/buildr/doc/_layouts/default.html @@ -0,0 +1,91 @@ + + + + buildr — {{ page.title }} + + + + + + +
+ +
+
    +
  1. Start Here +
      +
    1. Welcome
    2. +
    3. Quick Start
    4. +
    5. Installing & Running
    6. +
    7. Community Wiki
    8. +
    +
  2. +
  3. Using Buildr +
      +
    1. This Guide (PDF)
    2. +
    3. Projects
    4. +
    5. Building
    6. +
    7. Artifacts
    8. +
    9. Packaging
    10. +
    11. Testing
    12. +
    13. Releasing
    14. +
    15. Settings/Profiles
    16. +
    17. Languages
    18. +
    19. More Stuff
    20. +
    21. Extending Buildr
    22. +
    23. How-Tos
    24. +
    +
  4. +
  5. Reference +
      +
    1. API
    2. +
    3. Rake
    4. +
    5. Antwrap
    6. +
    7. Troubleshooting
    8. +
    +
  6. +
  7. Get Involved +
      +
    1. Download
    2. +
    3. Mailing Lists
    4. +
    5. Twitter
    6. +
    7. Issues/Bugs
    8. +
    9. CI Jobs
    10. +
    11. Contributing
    12. +
    13. Specs
    14. +
    15. Coverage
    16. +
    +
  8. +
  9. +
    + + + + + Google Custom Search +
    +
  10. +
  11. The Buildr Book +

    +

    Based on the Buildr documentation, available from Amazon and + CreateSpace

    +
  12. +
+
+
+

{{ page.title }}

+ {{ content | toc }} + {{ content }} +
+ +
+ + diff --git a/buildr/doc/_layouts/preface.html b/buildr/doc/_layouts/preface.html new file mode 100644 index 0000000..c152c83 --- /dev/null +++ b/buildr/doc/_layouts/preface.html @@ -0,0 +1,22 @@ + + + + buildr + + + + + + +
+
+ {{ content }} +
+
+ + diff --git a/buildr/doc/artifacts.textile b/buildr/doc/artifacts.textile new file mode 100644 index 0000000..4d569bf --- /dev/null +++ b/buildr/doc/artifacts.textile @@ -0,0 +1,217 @@ +--- +layout: default +title: Artifacts +--- + + +In Buildr, almost everything is a file or a file task. You compile source files that come from the file system using dependencies found on the file system, generating even more files. But how do you get these dependencies to start with, and how do you share them with others? + +Artifacts. We designed Buildr to work as a drop-in replacement for Maven 2.0, and share artifacts through the same local and remote repositories. Artifact tasks know how to download a file from one of the remote repositories, and install it in the local repository, where Buildr can find it. Packages know how to create files and upload them to remote repositories. + +We'll get into all of that in a second, but first, let's introduce the artifact specification. It's a simple string that takes one of two forms: + +{% highlight text %} +group:id:type:version +group:id:type:classifier:version +{% endhighlight %} + +For example, @'org.apache.axis2:axis2:jar:1.2'@ refers to an artifact with group identifier org.apache.axis2, artifact identifier axis2, a JAR file with version 1.2. Classifiers are typically used to distinguish between similar file types, for example, a source distribution and a binary distribution that otherwise have the same identifier and are both ZIP files. + + +h2(#specifying). Specifying Artifacts + +If your Buildfile spells out @'org.apache.axis2:axis2:jar:1.2'@ more than once, you're doing something wrong. Repeating the same string over and over will make your code harder to maintain. You'll know that when you upgrade to a new version in one place, forget to do it in another, and end up with a mismatch. + +You can use Ruby's syntax to do simple string substitution, for example: + +{% highlight ruby %} +AXIS_VERSION = '1.2' + +compile.with "org.apache.axis2:axis2:jar:#{AXIS_VERSION}" +{% endhighlight %} + +Better yet, you can define all your artifacts at the top of the Buildfile and use constants to reference them in your project definition. For example: + +{% highlight ruby %} +AXIS2 = 'org.apache.axis2:axis2:jar:1.2' + +compile.with AXIS2 +{% endhighlight %} + +Note that we're not using a separate constant for the version number. In our experience, it's unnecessary. The version number intentionally appears at the end of the string, where it stands out easily. + +If you have a set of artifacts that belong to the same group and version, and that's quite common, you can use the @group@ shortcut: + +{% highlight ruby %} +AXIOM = group('axiom-api', 'axiom-impl', 'axiom-dom', + :under=>'org.apache.ws.commons.axiom', :version=>'1.2.4') +{% endhighlight %} + +p(note). Buildr projects also define a @group@ attribute which can lead to some confusion. If you want to define an artifact group within a project definition, you should use the explicit qualifier @Buildr::group@. + +If you have several artifacts you always use together, consider placing them in an array. Methods that accept lists of artifacts also accept arrays. For example: + +{% highlight ruby %} +OPENJPA = ['org.apache.openjpa:openjpa:jar:1.2.1', + 'net.sourceforge.serp:serp:jar:1.12.0'] +AXIS_OF_WS = [AXIS2, AXIOM] + +compile.with OPENJPA, AXIS_OF_WS +{% endhighlight %} + +Another way to group related artifacts together and access them individually is using the @struct@ shortcut. For example: + +{% highlight ruby %} +JAVAX = struct( + :activation =>'javax.activation:activation:jar:1.1', + :persistence =>'javax.persistence:persistence-api:jar:1.0', + :stream =>'stax:stax-api:jar:1.0.1', +) + +compile.with JAVAX.persistence, OPENJPA +{% endhighlight %} + +In our experience, using constants in this manner makes your Buildfile much easier to write and maintain. + +And, of course, you can always place your artifact specifications in a separate file and require it into your Buildfile. For example, if you're working on several different projects that all share the same artifacts: + +{% highlight ruby %} +require '../shared/artifacts' +{% endhighlight %} + +When you use @require@, Ruby always looks for a filename with the @.rb@ extension, so in this case it expects to find @artifacts.rb@ in the @shared@ directory. + +One last thing. You can also treat artifact specifications as hashes. For example: + +{% highlight ruby %} +AXIS = { :group=>'org.apache.axis2', :id=>'axis2', :version=>'1.2' } +compile.with AXIS +puts compile.dependencies.first.to_hash +=> { :group=>'org.apache.axis2', :id=>'axis2', + :version=>'1.2', :type=>:jar } +{% endhighlight %} + + +h2(#repositories). Specifying Repositories + +Buildr can download artifacts for you, but only if you tell it where to find them. You need to specify at least one remote repository, from which to download these artifacts. + +When you call @repositories.remote@, you get an array of URLs for the various remote repositories. Initially, it's an empty array, to which you can add new repositories. For example: + +{% highlight ruby %} +repositories.remote << 'http://www.ibiblio.org/maven2/' +{% endhighlight %} + +If your repository requires HTTP authentication, you can write, + +{% highlight ruby %} +repositories.remote << URI.parse("http://user:password@repository.example.com") +{% endhighlight %} + +If you need to use a proxy server to access remote repositories, you can set the environment variable @HTTP_PROXY@ to the proxy server URL (use @HTTPS_PROXY@ for proxying HTTPS connections). You can also work without a proxy for certain hosts by specifying the @NO_PROXY@ environment variable. For example: + +{% highlight sh %} +$ export HTTP_PROXY = 'http://myproxy:8080' +$ export NO_PROXY = '*.mycompany.com,localhost,special:800' +{% endhighlight %} + +Alternatively you can use the Buildr options @proxy.http@ and @proxy.exclude@: + +{% highlight ruby %} +options.proxy.http = 'http://myproxy:8080' +options.proxy.exclude << '*.mycompany.com' +options.proxy.exclude << 'localhost' +{% endhighlight %} + +All the artifacts download into the local repository. Since all your projects share the same local repository, you only need to download each artifact once. Buildr was designed to be used alongside Maven 2.0, for example, when migrating projects from Maven 2.0 over to Buildr. By default it will share the same local repository, expecting the repository to be the @.m2/repository@ directory inside your home directory. + +You can choose to relocate the local repository by giving it a different path, for example: + +{% highlight ruby %} +repositories.local = '/usr/local/maven/repository' +{% endhighlight %} + +That's one change you don't want to commit into the Buildfile, so the best place to do it is in the @buildr.rb@ file in the @.buildr@ directory under your home directory. + +Buildr downloads artifacts when it needs to use them, for example, to compile a project. You don't need to download artifacts directly. Except when you do, for example, if you want to download all the latest artifacts and then go off-line. It's as simple as: + +{% highlight sh %} +$ buildr artifacts +{% endhighlight %} + + +h2(#downloading). Downloading Artifacts + +Within your buildfile you can download artifacts directly by invoking them, for example: + +{% highlight ruby %} +artifact('org.apache.openjpa:openjpa:jar:1.2.1').invoke +artifacts(OPENJPA).each(&:invoke) +{% endhighlight %} + +When you let Buildr download artifacts for you, or by invoking the artifact task yourself, it scans through the remote repositories assuming each repository follows the Maven 2 structure. Starting from the root repository URL, it will look for each artifact using the path @group/id/version/id-version.type@ (or ...@/id-version-classifier.type@). The group identifier becomes a path by turning periods (@.@) into slashes (@/@). So to find @org.apache.axis2:axis2:jar:1.2@, we're going to look for @org/apache/axis2/axis2/1.2/axis2-1.2.jar@. + +You'll find a lot of open source Java libraries in public repositories that support this structure (for example, the "Ibiblio Maven":http://www.ibiblio.org/maven2/ repository). And, of course, every remote repository you setup for your projects. + +But there are exceptions to the rule. Say we want to download the Dojo widget library and use it in our project. It's available from the Dojo Web site, but that site doesn't follow the Maven repository conventions, so our feeble attempt to use existing remote repositories will fail. + +We can still treat Dojo as an artifact, by telling Buildr where to download it from: + +{% highlight ruby %} +DOJO = '0.2.2' + +url = "http://download.dojotoolkit.org/release-#{DOJO}/dojo-#{DOJO}-widget.zip" +download(artifact("dojo:dojo:zip:widget:#{DOJO}")=>url) +{% endhighlight %} + +Explaining how it works is tricky, skip if you don't care for the details. On the other hand, it will give you a better understanding of Buildr/Rake, so if not now, come back and read it later. + +We use the @artifact@ method to create an @Artifact@ task that references the Dojo widget in our local repository. The @Artifact@ task is a file task with some additional behavior added by Buildr. When you call @compile.with@, that's exactly what it does internally, turning each of your artifact specifications into an @Artifact@ task. + +But the @Artifact@ task doesn't know how to download the Dojo widget, only how to handle conventional repositories. So we're going to create a download task as well. We use the @download@ method to create a file task that downloads the file from a remote URL. (Of course, it will only download the file if it doesn't already exist.) + +But which task gets used when? We could have defined these tasks separately and used some glue code to make one use the other. Instead, we call @download@ with the results of @artifact@. Essentially, we're telling @download@ to use the same file path as @artifact@. So now we have two file tasks that point to the very same file. We wired them together. + +You can't have more than one task pointing to the same file. Rake's rule of the road. What Rake does is merge the tasks together, creating a single file task for @artifact@, and then enhancing it with another action from @download@. One task, two actions. Statistically, we've doubled the odds that at least one of these actions will manage to download the Dojo widget and install it in the local repository. + +Since we ordered the calls to @artifact@ first and @download@ second, we know the actions will execute in that order. But @artifact@ is slightly devilish: when its action runs, it adds another action to the end of the list. So the @artifact@ action runs first, adds an action at the end, the @download@ action runs second, and downloads the Dojo widget for us. The second @artifact@ action runs last, but checks that the file already exist and doesn't try to download it again. + +Magic. + + +h2(#install_upload). Install and Upload + +Generally you use artifacts that download from remote repositories into the local repository, or artifacts packaged by the project itself (see "Packaging":packaging.html), which are then installed into the local repository and uploaded to the release server. + +Some artifacts do not fall into either category. In this example we're going to download a ZIP file, extract a JAR file from it, and use that JAR file as an artifact. We would then expect to install this JAR in the local repository and upload it to the release server, where it can be shared with other projects. + +So let's start by creating a task that downloads the ZIP, and another one to extract it and create the JAR file: + +{% highlight ruby %} +app_zip = download('target/app.zip'=>url) +bean_jar = file('target/app/bean.jar'=>unzip('target/app'=>app_zip)) +{% endhighlight %} + +When you call @artifact@, it returns an @Artifact@ task that points to the artifact file in the local repository, downloading the file if it doesn't already exist. You can override this behavior by enhancing the task and creating the file yourself (you may also want to create a POM file). Or much simpler, call the @from@ method on the artifact and tell it where to find the source file. + +So the next step is to specify the artifact and tell it to use the extracted JAR file: + +{% highlight ruby %} +bean = artifact('example.com:beans:jar:1.0').from(bean_jar) +{% endhighlight %} + +The artifact still points to the local repository, but when we invoke the task it copies the source file over to the local repository, instead of attempting a download. + +Use the @install@ method if you want the artifact and its POM installed in the local repository when you run the @install@ task. Likewise, use the @upload@ method if you want the artifact uploaded to the release server when you run the @upload@ task. You do not need to do this on artifacts downloaded from a remote server, or created with the @package@ method, the later are automatically added to the list of installed/uploaded artifacts. + +Our example ends by including the artifact in the @install@ and @upload@ tasks: + +{% highlight ruby %} +install bean +upload bean +{% endhighlight %} + +p(tip). Calling the @install@ (and likewise @upload@) method on an artifact run @buildr install@. If you need to download and install an artifact, invoke the task directly with @install().invoke@. + + +We'll talk more about installing and uploading in the next chapter, but right now we're going to "package some artifacts":packaging.html. diff --git a/buildr/doc/building.textile b/buildr/doc/building.textile new file mode 100644 index 0000000..36af8ac --- /dev/null +++ b/buildr/doc/building.textile @@ -0,0 +1,276 @@ +--- +layout: default +title: Building +--- + + +To remove any confusion, Buildr's build task is actually called @build@. It's also the default task that executes when you run @buildr@ without any task name. + +The @build@ task runs two other tasks: @compile@ and its associated tasks (that would be, @resources@) and @test@ and its associated tasks (@test:compile@, @test:setup@ and friends). We'll talk about @compile@ more in this section, and @test@ later on. We'll also show you how to run @build@ without testing, not something we recommend, but a necessary feature. + +Why @build@ and not @compile@? Some projects do more than just compiling. Other projects don't compile at all, but perform other build tasks, for example, creating a database schema or command line scripts. So we want you to get in the practice of running the @build@ task, and help you by making it the default task. + + +h2(#compiling). Compiling + +Each project has its own @compile@ task you can invoke directly, by running @buildr compile@ or as part of another build task. (Yes, that @build@). + +The @compile@ task looks for source files in well known directories, determines which compiler to use, and sets the target directory accordingly. For example, if it finds any Java source files in the @src/main/java@ directory, it selects the Javac compiler and generates bytecode in the @target/classes@ directories. If it finds Scala source files in the @src/main/scala@ directory it selects the Scalac compiler, and so forth. + +A single project cannot use multiple compilers at the same time, hence you may prefer creating subprojects by programming language. Some compilers like Groovy's are joint-compilers, this means they can handle several languages. When the Groovy compiler is selected for a project, .groovy and .java files are compiled by groovyc. + +Most often, that's just good enough and the only change you need to make is adding compile dependencies. You can use @compile.dependencies@ to get the array of dependency file tasks. For Java, each of these tasks points to a JAR or a directory containing Java classes, and the entire set of dependencies is passed to Javac as the classpath. + +Buildr uses file tasks to handle dependencies, but here we're talking about the Rake dependency mechanism. It's a double entendre. It invokes these tasks before running the compiler. Some of these tasks will download JARs from remote repositories, others will create them by compiling and packaging from a different project. Using file task ensures all the dependencies exist before the compiler can use them. + +An easier way to specify dependencies is by calling the @compile.with@ method. It takes a list of arguments and adds them to the dependency list. The @compile.with@ method is easier to use, it accepts several type of dependencies. You can use file names, file tasks, projects, artifacts specifications and even pass arrays of dependencies. + +Most dependencies fall into the last three categories. When you pass a project to @compile.with@, it picks up all the packages created by that project. In doing so, it establishes an order of dependency between the two projects (see "Defining the Project":projects.html#defining). For example, if you make a change in project _teh-api_ and build _teh-impl_, Buildr will detect that change, recompile and package _teh-api_ before compiling _teh-impl_. You can also select a specific package using the project's @package@ or @packages@ methods (see "Packaging":packaging.html). + +When you pass an artifact specification to @compile.with@, it creates an @Artifact@ task that will download that artifact from one of the remote repositories, install it in the local repository, and use it in your project. Rake's dependency mechanism is used here to make sure the artifact is downloaded once, when needed. Check the "Artifacts":artifacts.html section for more information about artifact specification and repositories. + +For now let's just show a simple example: + +{% highlight ruby %} +compile.with 'org.apache.axis2:axis2:jar:1.2', + 'org.apache.derby:derby:jar:10.1.2.1', projects('teh-api', 'teh-impl') +{% endhighlight %} + +Passing arrays to @compile.with@ is just a convenient for handling multiple dependencies, we'll show more examples of that when we talk about "Artifacts":artifacts.html. + +Likewise, the @compile@ task has an array of file tasks that point at the source directories you want to compile from. You can access that array by calling @compile.sources@. You can use @compile.from@ to add new source directories by passing a file name or a file task. + +For example, let's run the APT tool on our annotated source code before compiling it: + +{% highlight ruby %} +compile.from apt +{% endhighlight %} + +When you call @apt@ on a project, it returns a file task that points to the @target/generated/apt@ directory. This file task executes by running APT, using the same list of source directories, dependencies and compiler options. It then generates new source files in the target directory. Calling @compile.from@ with that file task includes those additional source files in the list of compiled sources. + +Here's another example: + +{% highlight ruby %} +jjtree = jjtree(_('src/main/jjtree'), :in_package=>'com.acme') +compile.from javacc(jjtree, :in_package=>'com.acme'), jjtree +{% endhighlight %} + +This time, the variable @jjtree@ is a file task that reads a JJTree source file from the @src/main/jjtree@ directory, and generates additional source files in the @target/generated/jjtree@ directory. The second line creates another file task that takes those source files, runs JavaCC on them, and generates yet more source files in @target/generated/javacc@. Finally, we include both sets of source files in addition to those already in @src/main/java@, and compile the lot. + +The interesting thing about these two examples is how you're wiring file tasks together to create more complicated tasks, piping the output of one task into the inputs of another. Wiring tasks this way is the most common way to handle complex builds, and uses Rake's dependency mechanism to only run tasks when it detects a change to one of the source files. + +You can also control the target directory. Use @compile.target@ to get the target directory file task. If you need to change the target directory, call the @compile.into@ method with the new path. + +We use method pairs to give you finer control over the compiler, but also a way to easily configure it. Methods like @dependencies@ and @sources@ give you a live array you can manipulate, or iterate over. On the other hand, methods like @with@ and @from@ accept a wider set of arguments and clean them up for you. They also all return the same task you're calling, so you can chain methods together. + +For example: + +{% highlight ruby %} +compile.from('srcs').with('org.apache.axis2:axis2:jar:1.2'). + into('classes').using(:target=>'1.4') +{% endhighlight %} + +Buildr uses the method pair and method chaining idiom in many places to make your life easier without sacrificing flexibility. + +Occasionally, you'll need to post-process the generated bytecode. Since you only want to do that after compiling, and let the compiler decide when to do that – only when changes require re-compiling – you'll want to extend the @compile@ task. You can do that by calling @compile@ with a block. + +For example, to run the OpenJPA bytecode enhancer after compiling the source files: + +{% highlight ruby %} +compile { open_jpa_enhance } +{% endhighlight %} + +You can change various compile options by calling, you guessed, @compile.options@. For example, to set the compiler to VM compatibility with Java 1.5 and turn on all Lint messages: + +{% highlight ruby %} +compile.options.target = '1.5' +compile.options.lint = 'all' +{% endhighlight %} + +Or, if you want to chain methods together: + +{% highlight ruby %} +compile.using :target=>'1.5', :lint=>'all' +{% endhighlight %} + + +Sub-projects inherit compile options from their parent project, so you only need to change these settings once in the top project. You can do so, even if the top project itself doesn't compile anything. + +The options available to you depend on which compiler you are using for this particular project, obviously the options are not the same for Java and Flash. Two options are designed to work consistently across compilers. + +Buildr turns the @warning@ option on by default, but turns it off when you run @buildr --silent@. It also sets the @debug@ option on, but turns it off when making a release. You can also control the @debug@ option from the command line, for example: + +{% highlight ruby %} +# When calling buildr +$ buildr compile debug=off + +# Once until we change the variable +$ export DEBUG=off +$ buildr compile +{% endhighlight %} + +The default source and target directories, compiler settings and other options you can use depend on the specific language. You can find more information in the "Languages":languages.html section. + + +h2(#resources). Resources + + +The @compile@ task comes bundled with a @resources@ task. It copies files from the @src/main/resources@ directory into @target/resources@. Best used for copying files that you want to include in the generated code, like configuration files, i18n messages, images, etc. + +The @resources@ task uses a filter that can change files as it copies them from source to destination. The most common use is by mapping values using a hash. For example, to substitute "${version}" for the project's version number and "${copyright}" for "Acme Inc (C) 2007" : + +{% highlight ruby %} +resources.filter.using 'version'=>version, + 'copyright'=>'Acme Inc (C) 2007' +{% endhighlight %} + +You can also use "profiles":settings_profiles.html#profiles to supply a name/value map that all @resources@ task should default to, by adding a @filter@ element to each of the profiles. The following examples shows a @profiles.yaml@ file that applies the same filter in development and test environments: + +{% highlight yaml %} +filter: &alpha1 + version: experimental + copyright: Acme Inc (C) 2007 + +development: + filter: *alpha1 +test: + filter: *alpha1 +{% endhighlight %} + +You can specify a different format by passing it as the first argument. Supported formats include: + +|_. Format |_. Usage | +| @:ant@ | Map from @key@ to value. | +| @:maven@ | Map from @${key}@ to value (default). | +| @:ruby@ | Map from @#{key}@ to value. | +| @:erb@ | Map from @<%=key%>@ to value. | +| @Regexp@ | Map using the matched value of the regular expression (e.g. @/=(.*?)=/@). | + +For example, using the @:ruby@ format instead of the default @:maven@ format: + +{% highlight ruby %} +resources.filter.using :ruby, 'version'=>version, + 'copyright'=>'Acme Inc (C) 2007' +{% endhighlight %} + +For more complicated mapping you can also pass a method or a proc. The filter will call it once for each file with the file name and content. + +If you need to copy resource files from other directories, add these source directories by calling the @from@ method, for example: + +{% highlight ruby %} +resources.from _('src/etc') +{% endhighlight %} + +You can select to copy only specific files using common file matching patterns. For example, to include only HTML files: + +{% highlight ruby %} +resources.include '*.html' +{% endhighlight %} + +To include all files, except for files in the @scratch@ directory: + +{% highlight ruby %} +resources.exclude 'scratch/*' +{% endhighlight %} + +The filter always excludes the @CVS@ and @.svn@ directories, and all files ending with @.bak@ or @~@, so no need to worry about these. + +A file pattern can match any file name or part of a file name using an asterisk (@*@). Double asterisk (@**@) matches directories recursively, for example, @'src/main/java/**/*.java'@. You can match any character using a question mark (@?@), or a set of characters using square brackets (@[]@), similar to regular expressions, for example, @'[Rr]eadme'@. You can also match from a set of names using curly braces (@{}@), for example, @'*.{html,css}'@. + +You can use filters elsewhere. The @filter@ method creates a filter, the @into@ method sets the target directory, and @using@ specifies the mapping. Last, you call @run@ on the filter to activate it. + +For example: + +{% highlight ruby %} +filter('src/specs').into('target/specs'). + using('version'=>version, 'created'=>Time.now).run +{% endhighlight %} + +The @resources@ task is, in fact, just a wrapper around such a filter that automatically adds the @src/main/resources@ directory as one of the source directories. + + +h2(#more). More On Building + +The @build@ task runs the @compile@ (and @resources@) tasks as prerequisites, followed by any actions you add to it, and completes by running the @test@ task. The @build@ task itself is a prerequisite to other tasks, for example, @package@ and @upload@. + +You can extend the @build@ task in two ways. You can add more prerequisites that will execute before the task itself, or you can add actions that will execute as part of the task. Which one you choose is up to you, we'll show you how they differ in a second. If you call @build@ with a list of tasks, it adds these tasks as prerequisites. Call @build@ with a block, and it adds that block as an action. Again, a common idiom you'll see elsewhere in Buildr and Rake. + +Let's look at a simple example. Say we want to generate a Derby database from an SQL file and include it in the ZIP package: + +{% highlight ruby %} +db = Derby.create(_('target/derby/db')=>_('src/main/sql/derby.sql')) +package(:zip).include db +{% endhighlight %} + +There's nothing fundamentally wrong with this code, if that's what you intend to do. But in practice, you don't always run the @package@ task during development, so you won't notice if something is wrong with this task when you build. For example, if it fails to generate the SQL file. In addition, the @package@ task runs after @build@, so you can't use the database in your test cases. + +So let's refactor it. We're going to use the variable @db@ to reference the file task that creates the database, and make it a prerequisite of the @build@ task. And use that same variable again to include the database in the ZIP package: + +{% highlight ruby %} +db = Derby.create(_('target/derby/db')=>_('src/main/sql/derby.sql')) +build db +package(:zip).include db +{% endhighlight %} + +Much better. We're using the same task twice, but since we're using Rake here, it will only execute once. In fact, it will only execute if we don't already have a Derby database, or if it detects a change to the SQL file and needs to recreate the database. + +p(tip). @Derby.create@ is not part of Buildr, you can get "derby.rake":http://svn.apache.org/repos/asf/ode/trunk/tasks/derby.rake here. + +Here's another example. We want to copy some files over as part of the build, and apply a filter to them. This time, we're going to extend the @build@ task: + +{% highlight ruby %} +build do + filter('src/specs').into('target/specs'). + using('version'=>version, 'created'=>Time.now).run +end +{% endhighlight %} + +The @build@ task is recursive, so running @buildr build@ picks the current project and runs its @build@ task, which in turn runs the @build@ task on each of its sub-projects. One @build@ task to rule them all. + + +h2(#cleaning). Cleaning + +The @build@ task has an evil twin, the @clean@ task. It's the task you use to remove all the files created during the build, especially when you mess things up and want to start all over. + +It basically erases the target directories, the one called @target@, and if you get creative and change the target directory for tasks like @compile@, it will also erase those. If you decide to generate files outside the target directory and want to cleanup after yourself, just extend the @clean@ task. + +For example: + +{% highlight ruby %} +clean { rm_rf _('staged') } +{% endhighlight %} + +The @rm_rf@ method deletes the directory and all files in it. It's named after UNIX's infamous @rm -rf@. Use it wisely. This is also a good time to introduce you to @FileUtils@, a standard Ruby library that contains convenient methods for creating and deleting directories, copying and moving files, even comparing two files. They're all free of charge when you use Buildr. + + +h2(#continuous-compilation). Continuous Compilation + +And if all that weren't enough, Buildr also offers a time-saving feature called continuous compilation. This feature, implemented by the @cc@ task, instructs Buildr to loop eternally, polling your project's source directories for changes. Whenever a change is detected, Buildr immediately triggers the appropriate compilation step and goes right back to polling. This allows you to reap many of the benefits of an incrementally compiling IDE like Eclipse without sacrificing your favorite build tool. + +To get started, simply invoke the @cc@ task at the command prompt: + +{% highlight sh %} +$ buildr cc +{% endhighlight %} + +This task will immediately invoke the @compile@ and @test:compile@ tasks on your project if necessary. This ensures that your build is completely up to the minute before polling is initiated. After this initial build (if required), Buildr will print a notification indicating which directories are being monitored. By default, these directories will include any source folders (e.g. @src/main/java/@), any test directories (e.g. @src/spec/scala/@) as well as any resources (e.g. @src/main/resources/). The Buildr process will remain running during this time, meaning that in order to test this functionality, we will need to open a new shell: + +{% highlight sh %} +$ touch src/main/java/Test.java +{% endhighlight %} + +The moment we run this command, Buildr will detect the change and invoke the @compile@ task. It will *not* invoke the @test:compile@ task, since none of the test files were actually changed. This ensures that potentially time-consuming tasks are avoided if possible. Note that, unlike the @build@ task, the continuous compilation also does not actually run any of your tests. Continuous compilation is designed to be a simple daemon which runs forever, quickly recompiling your project as soon as you save or delete a file. We can terminate the continuous compilation task by pressing Ctrl-C. Left to its own devices, the @cc@ task really will loop forever. + +There are several advantages to continuous compilation. Number one is convenience. Once you invoke the @cc@ task, you can focus exclusively on the code, editing and saving your files in an unbroken workflow. There is no need to break your concentration to invoke Buildr manually unless you need to run the test suite, deploy the application or anything beyond compilation. The second advantage is speed. By using the continuous compilation process, you avoid repeatedly incurring Buildr's startup overhead. While this startup time is kept to a minimum, it is still perceptable, particularly when running on JRuby. Since the @cc@ task runs within a Buildr instance which has already been started, there is no need for repeated, inefficient load times. Again, this allows you to focus more completely on what's really important: the code. + +By default, the @cc@ task will poll your sources once every 200 milliseconds. We have found that this frequency strikes a nice balance between CPU load (which is insignificant) and nearly-instant detection. However, you may wish to tune this value based on your own needs. To do so, simply use the @cc.frequency@ property in your project definition: + +{% highlight ruby %} +project 'foo' do + cc.frequency 1.5 # poll every one-and-a-half seconds +end +{% endhighlight %} + +If you find that the 200 ms default imposes too much overhead, try changing @cc.frequency@ to a higher value. On the flip side, if you find that you're waiting too long for changes to be caught by the poll, tune the frequency lower. + +Now let's "talk about the artifacts":artifacts.html we mentioned before. diff --git a/buildr/doc/contributing.textile b/buildr/doc/contributing.textile new file mode 100644 index 0000000..73e1753 --- /dev/null +++ b/buildr/doc/contributing.textile @@ -0,0 +1,277 @@ +--- +layout: default +title: Contributing +--- + +Buildr is a community effort, and we welcome all contributors. Here's your chance to get involved and help your fellow developers. + +h2(#involved). Getting involved + +All our discussions are done in the open, over "email":mailing_lists.html, and that would be the first place to look for answers, raise ideas, etc. For bug reports, issues and patches, "see below":#bugs. + + +h2(#mailing_lists). Mailing Lists + +We run two mailing lists, the "users":http://buildr.markmail.org/search/list:users mailing list for developers working with Buildr, that would be you if you're using Buildr or interested in using it. There's the "dev":http://buildr.markmail.org/search/list:dev mailing list for talking about development of Buildr itself. There's also "commits":http://buildr.markmail.org/search/list:commits mailing list for following SVN commits and JIRA issues. + +Check the "mailing lists":mailing_lists.html page for more information on subscribing, searching and posting to the mailing list. + + +h2(#irc). Internet Relay Chat + +We are live on IRC under the buildr channel on irc.freenode.net, with a broad coverage of the US timezone. We tend to idle there, so feel free to ping the channel owner (toulmean) to make noise. + +Our "conversations": are logged by the "echelog":http://echelog.matzon.dk/logs/browse/buildr/1279663200 and "irclogger":http://irclogger.com/buildr/ bots. If you're really curious, we also have "activity statistics":http://echelog.matzon.dk/stats/buildr.html + +Sincere thanks to Matzon and Christopher Schneider for setting these up! + + +h2(#bugs). Bugs (aka Issues) + +We really do try to keep bugs to a minimum, and anticipate everything you'll ever want to do with Buildr. We're also, not perfect. So you may have found a bug, or have an enhancement in mind, or better yet, a patch to contribute. Here's what you can do. + +If it's a bug, enhancement or patch, add it to "JIRA":http://issues.apache.org/jira/browse/Buildr. For trivial stuff, that's good enough. + +If it needs more attention, start a discussion over on the mailing list. We will still use JIRA to log the progress, but the mailing list is a better place for talking things through. + +When reporting a bug, please tell us which version of Ruby, Buildr and Java you are using, and also which operating system you are on: + +{% highlight sh %} +$ ruby --version +$ buildr --version +$ java --version +{% endhighlight %} + + +h2(#wiki). Community Wiki + +"Our community Wiki":http://cwiki.apache.org/confluence/display/BUILDR/Index. + + +h2(#code). Contributing Code + +Yes, please. + +If you have a patch to submit, do it through "JIRA":http://issues.apache.org/jira/browse/Buildr. We want to make sure Apache gets the right to use your contribution, and the JIRA upload form includes a simple contribution agreement. Lawyer not included. + +h3. The Perfect Patch + +If you want to get your patch accepted quickly: + +# Provide a good summary of the bug/fix. We use that to decide which issue we can do quickly, and also copy and paste it into the changelog. +# Provide short explanation of what failed, under what conditions, why, and what else could be affected by the change (when relevant). The helps us understand the problem and move on to the next step. +# Provide a patch with relevant specs, or a fix to incomplete/broken specs. First thing we have to do is replicate the problem, before applying the change, and then make sure the change fixes that problem. And we need to have those specs in there, they make sure we don't accidentally break it again in the future. +# Provide a patch with the fix/change itself. Keep it separate from the specs, so it's easy to apply them individually. + +If you don't know how to fix it, but can at least write a spec for the correct behavior (which, obviously would fail), do just that. A spec is preferred to a fix. + +h3. Working on a new feature? + +If you want to work on a cool new feature, but not quite ready to submit a patch, there's still a way you can get the Buildr community involved. We're experimenting with using Git for that. You can use Git to maintain a fork of Buildr that can keep up with changes in the main branch (tip: use @git rebase@), while developing your own changes/features on it. + +That way you can get other people involved, checking out the code, and eventually merge it back with the main branch. Check out the "Git section":#git below and the post "Git forking for fun and profit":http://blog.labnotes.org/2008/04/30/git-forking-for-fun-and-profit/. + + +h2(#edge). Living on the edge + +Did we mention Buildr is an open source project? In fact, when you install Buildr you get all the source code, documentation, test case and everything you need to use it, extend it and patch it. Have a look in your Gem directory. + +h3(#svn). SVN + +But if you want to work with the latest and greatest, you'll want to check out "Buildr from SVN":http://svn.apache.org/repos/asf/buildr: + +{% highlight sh %} +$ svn co http://svn.apache.org/repos/asf/buildr/trunk buildr +{% endhighlight %} + +You can also browse the "Buildr repository":http://svn.apache.org/repos/asf/buildr. + +h3(#git). Git + +Not a fan of SVN? We understand. You can also use the "official Apache Git clone.":http://git.apache.org This clone is maintained by the ASF and kept in sync with the SVN repository (though, in practice there may be some delay in cloning recent commits). Apache's Git hosting supports both git:// and http:// protocols (you should use git:// if at all possible as it is faster than http://): + +{% highlight sh %} +$ git clone git://git.apache.org/buildr.git +# or... +$ git clone http://git.apache.org/buildr.git +{% endhighlight %} + +If you want to learn more about Git, you can start by watching Scott Chacon’s "Git presentation":http://en.oreilly.com/rails2008/public/asset/attachment/2816 (PDF), or any of the "Git screencasts":http://www.gitcasts.com/. For more, there's also the "Git Internals book":http://peepcode.com/products/git-internals-pdf. + +And keep this "Git cheat sheet":http://ktown.kde.org/~zrusin/git/git-cheat-sheet-medium.png close at hand. Very useful. + +h4. GitHub + +You are also welcome to fork or clone the "Buildr repository on GitHub":http://github.com/apache/buildr. This repository is just an exact mirror of the official Apache Git clone referenced above (updated every 30 minutes). Some of the core committers also maintain their own forks of Buildr on GitHub, often containing experimental and in-progress development slated for eventual inclusion into the SVN. For reference, they are listed below: + +* "Assaf Arkin":http://github.com/assaf/buildr +* "Victor Hugo Borja":http://github.com/vic/buildr +* "Daniel Spiewak":http://github.com/djspiewak/buildr + +h3. Working with Source Code + +To install Buildr from the source directory: + +{% highlight sh %} +$ cd buildr +$ rake setup install +{% endhighlight %} + +When using Buildr for JRuby: + +{% highlight sh %} +$ cd buildr +$ jruby -S rake setup install +{% endhighlight %} + +The _setup_ task takes care of installing all the necessary dependencies used for building, testing and running Buildr. Once in a while we upgrade or add new dependencies, if you're experiencing a missing dependency, simply run @rake setup@ again. + +The _install_ task creates a Gem in your working directory (_pkg/_) and install it in your local repository. Since Ruby Gems uses version numbers to detect new releases, if you installed Buildr this way and want to upgrade to the latest official release, you need to use @gem install buildr@ rather than @gem upgrade@. + +Both _setup_ and _install_ tasks use the @sudo@ command on platforms that require it (i.e. not Windows), so there's no need to run @sudo rake@ when working with the Buildr source code. + + +h3. Using development build + +Occasionally we'll make development builds from the current code in trunk/head. We appreciate if you can take the time to test those out and report any bugs. To install development builds, use the Gem repository at @people.apache.org/~assaf/buildr/snapshot@: + +{% highlight sh %} +gem source --add http://people.apache.org/~assaf/buildr/snapshot/ +{% endhighlight %} + +Since Ruby Gems uses version numbers to detect new releases, if you installed Buildr from a snapshot and want to upgrade to a newer snapshot or the latest official release, you need to use @gem install buildr@ rather than @gem upgrade@. + +If you want to go back to using the RubyForge releases: + +{% highlight sh %} +gem source --remove http://people.apache.org/~assaf/buildr/snapshot/ +gem install buildr +{% endhighlight %} + + +h2(#testing). Tested and Documented + +Two things we definitely encourage! + +h3. Testing/Specs + +Obviously we won't turn down patches, but we'll love you even more if you include a test case. One that will fail without the patch, and run successfully with it. If not for our love, then think of the benefit to you: once we add that test case, we won't accidentally break that feature in the next release. + +We test using "RSpec":http://rspec.info/, a Behavior-Driven Development test framework. The main difference between RSpec and xUnit is that RSpec helps you formulate test cases in terms of specifications: you describe how the code should behave, and run RSpec to make sure it matches that specification. + +You can run an individual specifications using the @spec@ command, for example: + +{% highlight sh %} +$ spec spec/compiler_spec.rb +$ spec spec/compiler_spec.rb -l 409 +{% endhighlight %} + +The first command will run all the specifications in @compiler_spec@, the second command will run only the specification identified by line 409 of that file. You can use line numbers to point at a particular specification (lines starting with @it@), or set of specifications (lines starting with @describe@). You can also use the @-e@ command line option to name a particular specification. + +To make sure your change did not break anything else, you can run all the specifications (be patient, we have a lot of these): + +{% highlight sh %} +$ rake spec +{% endhighlight %} + +If you get any failures, you can use @rake failed@ to run only the failed specs, and repeat until there are no more failed specs to run. The list of failed specs is stored in the file _failed_. + +We always @rake spec@ before making a release. + +For full test coverage: + +{% highlight sh %} +$ rake coverage +{% endhighlight %} + +Specification and coverage reports are HTML files you can view with a Web browser, look for them in the _reports_ directory. You can also check out the "RSpec report":specs.html and "test coverage":coverage/index.html we publish with each release. + + +h2(#docs). Documentation + +Yes, we do make typos, spelling errors and sometimes we write things that don't make sense, so if you find a documentation bug, or want to help make the documentation even better, here's the way to do it. + +For simple typos and quick fixes, just send a message to the mailing list or log an issue in JIRA. + +If you end up rewriting a significant piece of text, or add new documentation (you rock!), send a patch. Making documentation patches is fairly easy. All the documentation is generated from text files in the @doc/pages@ directory, so all you need to do is check it out from Git/SVN, edit, and @svn diff@ to create a patch. + +We use "Textile":http://www.textism.com/tools/textile/ as the markup language, it takes all of a few minutes to learn, it's intuitive to use, and produces clean HTML. You can learn it all in a few minutes from the "Textile Reference Manual":http://redcloth.org/textile. Also check out the "Textile Quick Reference":http://hobix.com/textile/quick.html. + +Syntax highlighting handled by "Pygments":http://pygments.org. Use the special @highlight@ tag to separate code sample from the rest of the text and to tell Pygments which language to use. For example: + +
+{% highlight ruby %}
+define 'project' do
+  # Just a sample
+end
+{% endhighlight %}
+
+ +Have a look at existing documentation to see writing conventions, specifically: + +* Separate paragraphs with two newlines. +* Use one newline only if you need a <br> tag, otherwise, no newlines inside the paragraph. +* When creating a new page, don't forget the YAML premable at the top (Jekyll needs the page title and layout). +* The layout uses H1 to render the page title; only use H2 through H4 for the page content. +* Use H2 headers for the major page sections. Give each H2 header a unique ID so the table of contents can link to it. +* Separating sentences with two spaces, just a convenience when editing in a text editor using monospaced fonts. +* If in doubt, ask. + +To go from Textile to HTML we use "Jekyll":http://github.com/mojombo/jekyll. You can use the @jekyll@ rake task to transform the files under @doc@ and create a copy of the Web site in the directory @_site@. For example: + +{% highlight sh %} +$ rake jekyll +$ open _site/index.html +{% endhighlight %} + +There is no live editing, but you can run @rake jekyll auto=true@, and when you update and save a Textile page it will regenerate the corresponding HTML page. + +To go from HTML to PDF we use "PrinceXML":http://www.princexml.com/. The target file @buildr.pdf@ is generated by first running Jekyll and then merging the generated HTML pages into a single PDF document. For example: + +{% highlight sh %} +$ rake buildr.pdf +$ open buildr.pdf +{% endhighlight %} + + +h2(#ci). Continuous Integration + +Buildr uses the Jenkins continuous integration tool to perform builds, run tests and report back on problems when changes are made to the source code repository. + +The care and feeding of the "CI Jobs":https://builds.apache.org/view/A-F/view/Buildr is the responsibility of the committers. To get access to configure the CI Jobs a committer needs to follow the directions on the "jenkins":http://wiki.apache.org/general/Hudson documentation site. + +You may also need to coordinate with the Apache infrastructure team to get accounts on the actual slave hosts that run the CI jobs. This access may be required to install tools and gems required to run the CI jobs. The main slave host to get access to is vesta.apache.org at the time of writing. You can also log on to the slave host, impersonate hudson and manually run tasks when you are attempting to track down build problems. Of course to impersonate hudson you will need to learn how to use "OPIE.":http://apache.org/dev/freebsd-jails + +h2(#contributors). Contributors + +Here is the list of people who are actively working and committing on Buildr: + +*"Assaf Arkin":http://labnotes.org* (assaf at apache.org) + +Started working on Buildr because Maven was too much pain and Rake wasn't enough. Assaf has been hanging around Apache since 1999, as founding contributor to XML Apache, Ode and Buildr. Assaf is also co-author of "Ruby In Practice":http://manning.com/mcanally/. + +*Alex Boisvert* + +Came to Buildr as a refuge from the Maven Uncertainty Principle. Alex has been working mostly on the Scala integration and believes Ruby scripting is a great complement to statically typed languages. + +*"Matthieu Riou":http://offthelip.org* + +*Victor Hugo Borja* (vborja at apache.org) + +Currently a Java Developer at "http://jwmsolutions.com":http://jwmsolutions.com, Victor has been enjoying and using Apache's software since 1999 when he started with Java, now he prefers programming Ruby and is happy to help on Apache's first ruby project. + +*Lacton* (lacton at apache.org) + +A test-infected developer since 2001, Lacton yearns for a development infrastructure that would shorten feedback loops so much that testing, building, refactoring and committing would feel as easy and natural as breathing air. + +*"Daniel Spiewak":http://www.codecommit.com/blog* (djspiewak at apache.org) + +Daniel originally came to Buildr in search of a Scala build tool which was better than Ant. He got more than he bargained for. Now, he works to advance Buildr as the absolute best tool for supporting Scala development. + +*"Antoine Toulme":http://www.lunar-ocean.com/* (toulmean at apache.org) + +Antoine used Buildr first as an excuse to evade in Ruby land, creating plugins for Debian packaging, GWT compilation, or the NSIS installer. His main area of interest is the resolving of dependencies in the OSGi world. He works on making Buildr a standalone rock solid tool. + +*Peter Donald* + +Peter already used rake to automate jobs in his ruby and java projects. When it came time to upgrade that home grown ant/java/rake build system Buildr seemed the perfect match. diff --git a/buildr/doc/css/default.css b/buildr/doc/css/default.css new file mode 100644 index 0000000..783b7f1 --- /dev/null +++ b/buildr/doc/css/default.css @@ -0,0 +1,236 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + + +body { + background-color: #fff; + color: #000; + font-family: "Helvetica Neue", "DejaVu Sans", "Verdana", sans-serif; + text-align: center; + line-height: 150%; +} + +a:link, a:visited{ + color: #0044b3; + text-decoration: none; +} + +a:hover{ + text-decoration: underline; +} + +img { + border: none; +} + +pre, code { + font-family: "Monaco", "DejaVu Sans Mono", "Courier New", "Courier"; + font-size: 11pt; +} +pre { + margin: 0.3em 0 0.3em 0.9em; + padding: 0; + line-height: 1.8em; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ +} +pre br { display: none; } + +h1, h2, h3 { + font-family: "Gill Sans"; + padding: 0; + margin: 1.6em 0 0.6em 0; + line-height: 1.25em; + letter-spacing: 0.03em; +} + +h1 { + font-size: 2em; + margin-top: 0; + padding-bottom: 0.3em; + border-bottom: 1px solid #808080; +} + +h2 { + font-size: 1.3em; + padding-bottom: 0.3em; + border-bottom: 1px solid #a0a0a0; +} + +h3 { + font-size: 1.1em; +} + +blockquote { + padding-left: 2em; + padding-right: 2em; + margin-left: 0.3em; + margin-right: 0; + font-style: italic; +} + +ul { + list-style-type: disc; +} + +ul ul { + list-style-type: disc; + padding-left: 1em; +} + +table { + border-spacing: 0; + width: 100%; + margin: 0.3em 0 0.3em 0; +} + +th, td { + padding: 0.3em 0.5em 0.3em 0.5em; + border-bottom: 1px solid #D8D8D8; + vertical-align: top; + background-color: #FFFFFF; +} + +th, thead td { + border-bottom: none; + background-color: #669966; + color: #ffffff; + text-align: left; +} + + +#wrap{ + margin: 1em auto 2em auto; + text-align: left; + width: 65em; +} + + +#header { + margin: 0 0 3em 18em; + position: relative; +} + +#header .tagline { + float: right; + font-size: 1.2em; + font-weight: bold; + position: absolute; + top: 0.6em; + right: 0; +} + + +#pages { /* Parent Wrapper for inside boxes */ +/* display: inline; */ /* IE Hack */ + width: 12em; + float: left; + margin-right: 2em; + font-family: "Gill Sans",sans-serif; + font-size: 11pt; + text-align: right; + border-right: 1px solid #ccc; + padding-right: 2em; +} +#pages ol { + list-style: none; + padding: 0; + margin: 0; +} +#pages ol li { + padding: 0.3em 0 0.6em 0em; + color: #669966; + font-weight: bold; +} +#pages ol li ol { + margin-top: 0.3em; + padding: 0.3em 0 0.9em 0.9em; +} +#pages ol li ol li { + padding: 0.3em 0 0.3em 0em; +} +#pages ol li a { + font-weight: normal; +} +#pages form { + margin-top: 2em; + padding: 0.3em 0 0.5em 0.5em; +} +#pages ol li p { + font-weight: normal; + color: #000; + vertical-align: middle; +} + + +#content { /* Parent Wrapper for inside boxes */ +/* display: inline; *//* IE Hack */ + float: left; + width: 44em; + margin-left: 0.3em; + margin-bottom: 5em; + word-wrap: break-word; /* prevents container divs from wrapping */ +} + +ol.toc { + list-style: none; + padding: 0; + margin: 0 0 2em 0; +} +ol.toc li { + padding: 0.3em 0 0.3em 0; + margin: 0; +} +ol.toc li ol.toc { + margin: 0; + padding-left: 0.9em; +} + + +#content p.tip, #content p.note { + margin: 0.9em 0 0 0; + padding: 0 0 2em 4em; +} +#content p.tip { background: url("../images/tip.png") 0 0 no-repeat; } +#content p.note { background: url("../images/note.png") 0 0 no-repeat; } + +#content .footnote { + margin-top: 2.5em; +} +#content .footnote { + padding-top: 1.5em; + border-top: 1px solid #ccc; +} +#content .footnote + .footnote { + margin-top: 0.5em; + padding-top: 0; + border: none; +} +#content .footnote sup { font-weight: bold; } + +#content .footnote-links dt { font-weight: bold; } +#content .footnote-links dt:after { content: ": " } +#content .footnote-links dd { } + + +#footer { + color: #888; + border-top: 1px solid #ccc; + font-size: 0.9em; + padding: 0.3em; + margin: 2em 0 3em 0em; + clear: both; +} diff --git a/buildr/doc/css/print.css b/buildr/doc/css/print.css new file mode 100644 index 0000000..cefbfc2 --- /dev/null +++ b/buildr/doc/css/print.css @@ -0,0 +1,101 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + + +@media print { + @page { size: letter; } + @page:first { + @top-left { content: normal } + @top-right { content: normal } + } + @page:right { + margin: 1.25in 1in 1.5in 1.5in; + font: normal 10pt "Gill Sans" !important; + @top-left { content: string(pagetitle); } + @top-right { content: counter(page); } + } + @page:left { + margin: 1.25in 1.5in 1.5in 1in; + font: normal 10pt "Gill Sans" !important; + @top-left { content: counter(page); } + @top-right { content: string(pagetitle); } + } + + title { string-set: doctitle content(); } + + body { + font-family: "Palatino"; + margin: 0; + color: black; + background: white; + font-size: 11pt; + } + + h1 { + string-set: pagetitle content(); + page-break-before: always; + } + h1:first-child { page-break-before: avoid; } + h1, h2, h3 { + font-family: "Gill Sans"; + } + pre, p, blockquote { page-break-inside: avoid; } + pre, code { + font-family: "Monaco", "DejaVu Sans Mono", "Courier New", "Courier"; + font-size: 9pt; + } + pre br { + display: none; + } + a:link, a:visited { + background: transparent; + text-decoration: none; + } + + + #header, #pages, #footer { display: none } + #wrap, #content { + float: none !important; + color: black; + background: transparent; + width: auto !important; + margin: 0; + padding: 0; + border: 0; + } + + ol.toc a:link, ol.toc a:visited { text-decoration: none; } + ol.toc a:after { content: leader('.') target-counter(attr(href), page); } + + .title { + page-break-before: always; + border: none; + } + .title img { + display: block; + width: 80%; + margin: 2em auto 4em auto; + } + p.preface { + page-break-before: always; + padding-top: 1.5in; + } + div.preface.quotes { + page-break-before: always; + padding-top: 1in; + } + +} diff --git a/buildr/doc/css/syntax.css b/buildr/doc/css/syntax.css new file mode 100644 index 0000000..fc8ee66 --- /dev/null +++ b/buildr/doc/css/syntax.css @@ -0,0 +1,23 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + +.highlight .k, .highlight .kp { color: blue } /* keyword */ +.highlight .no { color: darkblue } /* constant */ +.highlight .s1 { color: green } /* string */ +.highlight .n { color: black } /* identifier */ +.highlight .o, .highlight .p { color: darkblue } /* = + */ +.highlight .ss { color: darkblue } /* symbol */ +.highlight .c1 { color: gray } /* comment */ diff --git a/buildr/doc/download.textile b/buildr/doc/download.textile new file mode 100644 index 0000000..01ad19b --- /dev/null +++ b/buildr/doc/download.textile @@ -0,0 +1,163 @@ +--- +layout: default +title: Download +--- + + +h2(#install). Installing Buildr + +*The easy way:* Follow the "quick installation instructions":installing.html to get Buildr up and running in a matter of minutes. + + +h2(#dists). Official & Unofficial Distributions + +The official Apache distribution consists of the digitally signed binaries (gems) and source packages "available below":#dist. To install these binaries, you must first download them to disk and then install them using the @gem install@ command (or @rake install@ for a source distribution). + +In addition, contributors to this project maintain a separate distribution over on "RubyForge":http://rubyforge.org/projects/buildr. Using this distribution, you're able to install Buildr directly from the remote gem repository and to automatically upgrade when a new release comes out. The RubyForge distribution is *not* an official Apache distribution. + +The source code is included in both source and binary distribution, the Gem distribution expands the source code into your local Gem repository. That's in addition to getting the source code directly from "SVN":http://svn.apache.org/repos/asf/buildr or "GitHub":http://github.com/buildr/buildr/tree/master. Learn more about working with source code and "living on the edge":contributing.html#edge. + + +h2(#dist). Binaries and Source Code + +h3. buildr 1.4.6 (2011-06-22) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.4.6-java.gem":http://www.apache.org/dyn/closer.cgi/buildr/1.4.6/buildr-1.4.6-java.gem | "4b5952d99abd59bd1d296bf21b4854e2":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6-java.gem.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6-java.gem.asc | +| "buildr-1.4.6-x86-mswin32.gem":http://www.apache.org/dyn/closer.cgi/buildr/1.4.6/buildr-1.4.6-x86-mswin32.gem | "2c89b87a44f7f957169434969d2cdaed":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6-x86-mswin32.gem.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6-x86-mswin32.gem.asc | +| "buildr-1.4.6.gem":http://www.apache.org/dyn/closer.cgi/buildr/1.4.6/buildr-1.4.6.gem | "4d6848a6370925e54135c36f5085be94":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6.gem.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6.gem.asc | +| "buildr-1.4.6.tgz":http://www.apache.org/dyn/closer.cgi/buildr/1.4.6/buildr-1.4.6.tgz | "3b76c4037d3ad95c79c3dd8d07805fb0":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6.tgz.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6.tgz.asc | +| "buildr-1.4.6.zip":http://www.apache.org/dyn/closer.cgi/buildr/1.4.6/buildr-1.4.6.zip | "e9b7a313d7ac726c0a74c62afa7bfdc5":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6.zip.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.6/buildr-1.4.6.zip.asc | + +p>. ("Release signing keys":http://www.apache.org/dist/buildr/1.4.6/KEYS) + + +h3. buildr 1.4.5 (2011-02-21) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.4.5.gem":http://archive.apache.org/dist/buildr/1.4.5/buildr-1.4.5.gem | "57cccec175e0b1b682a36f75e54d15cf":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5.gem.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5.gem.asc | +| "buildr-1.4.5-x86-mswin32.gem":http://archive.apache.org/dist/buildr/1.4.5/buildr-1.4.5-x86-mswin32.gem | "d24b11446eaa01e24b9ec1be4b6d93d1":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5-x86-mswin32.gem.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5-x86-mswin32.gem.asc | +| "buildr-1.4.5-java.gem":http://archive.apache.org/dist/buildr/1.4.5/buildr-1.4.5-java.gem | "c167e67e33e7c3b4bf2316d7405eb11e":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5-java.gem.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5-java.gem.asc | +| "buildr-1.4.5.tgz":http://archive.apache.org/dist/buildr/1.4.5/buildr-1.4.5.tgz | "9b335479ce006caf109c4786d83aa00c":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5.tgz.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5.tgz.asc | +| "buildr-1.4.5.zip":http://archive.apache.org/dist/buildr/1.4.5/buildr-1.4.5.zip | "0d3b3b474a3c90c717bdc953aab8e626":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5.zip.md5 | "Sig":http://www.apache.org/dist/buildr/1.4.5/buildr-1.4.5.zip.asc | + +p>. ("Release signing keys":http://www.apache.org/dist/buildr/1.4.5/KEYS) + + +h3. buildr 1.4.4 (2010-11-16) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.4.4-x86-mswin32.gem":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4-x86-mswin32.gem | "7d9cfb91a15dfcebc2abaa7f0cc18512":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4-x86-mswin32.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4-x86-mswin32.gem.asc | +| "buildr-1.4.4.gem":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4.gem | "581e7951c07607f0437a924ec131a706":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4.gem.asc | +| "buildr-1.4.4-java.gem":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4-java.gem | "0076cdb62e248b525d71916713f5b290":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4-java.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4-java.gem.asc | +| "buildr-1.4.4.tgz":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4.tgz | "246208815e2efc29fad0df1cee031830":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4.tgz.asc | +| "buildr-1.4.4.zip":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4.zip | "a932b66401102bf9814e77a3c5541d73":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.4/buildr-1.4.4.zip.asc | + +p>. ("Release signing keys":http://archive.apache.org/dist/buildr/1.4.4/KEYS) + + +h3. buildr 1.4.3 (2010-10-15) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.4.3-x86-mswin32.gem":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3-x86-mswin32.gem | "7d21269d4dc6d04c81637eec3a97dd89":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3-x86-mswin32.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3-x86-mswin32.gem.asc | +| "buildr-1.4.3-java.gem":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3-java.gem | "5e12a84fbb8a5ffdd9f995995fa78d77":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3-java.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3-java.gem.asc | +| "buildr-1.4.3.gem":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3.gem | "b8ff992f07f8d400902e74d5d0e488b8":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3.gem.asc | +| "buildr-1.4.3.tgz":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3.tgz | "01514479fad89e6a44f4f4d9fc5d0198":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3.tgz.asc | +| "buildr-1.4.3.zip":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3.zip | "2b2e996775e6152c618308901b6815be":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.3/buildr-1.4.3.zip.asc | + +p>. ("Release signing keys":http://archive.apache.org/dist/buildr/1.4.3/KEYS) + + +h3. buildr 1.4.2 (2010-09-18) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.4.2-java.gem":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2-java.gem | "481b5dffac34ef368352f9dea095ad42":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2-java.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2-java.gem.asc | +| "buildr-1.4.2-x86-mswin32.gem":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2-x86-mswin32.gem | "ddd6d1bee497b514bc17507f411b57c9":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2-x86-mswin32.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2-x86-mswin32.gem.asc | +| "buildr-1.4.2.gem":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2.gem | "5f3a83491b1e7ea372fd2f7d5331195b":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2.gem.asc | +| "buildr-1.4.2.tgz":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2.tgz | "15fbb3b1fa46d6e0c99f08f169f380dc":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2.tgz.asc | +| "buildr-1.4.2.zip":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2.zip | "33f1c15537bb5b28cb05244d8696e70b":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.2/buildr-1.4.2.zip.asc | + +p>. ("Release signing keys":http://archive.apache.org/dist/buildr/1.4.2/KEYS) + + +h3. buildr 1.4.0 (2010-06-18) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.4.0-java.gem":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0-java.gem | "6304234b3e4b49ffb95dd5f155442935":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0-java.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0-java.gem.asc | +| "buildr-1.4.0-x86-mswin32.gem":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0-x86-mswin32.gem | "6a4b0ab7cc651128c25a4b7cc4867479":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0-x86-mswin32.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0-x86-mswin32.gem.asc | +| "buildr-1.4.0.gem":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0.gem | "96c4c4d56ac4baffbaa1980af39c2220":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0.gem.asc | +| "buildr-1.4.0.tgz":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0.tgz | "f025d57e1a71e40bb02def291eb02363":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0.tgz.asc | +| "buildr-1.4.0.zip":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0.zip | "a7c39d799e0e7f31827d803d788927f0":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.4.0/buildr-1.4.0.zip.asc | + +p>. ("Release signing keys":http://archive.apache.org/dist/buildr/1.4.0/KEYS) + + +h3. buildr 1.3.5 (2009-10-05) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.3.5.gem":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5.gem | "5a04d8593f98606f15dd589988afe24d":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5.gem.asc | +| "buildr-1.3.5-java.gem":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5-java.gem | "d930c851196cd8f9ea66b7190d324dd4":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5-java.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5-java.gem.asc | +| "buildr-1.3.5-x86-mswin32.gem":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5-x86-mswin32.gem | "a6dfc07b4c22e1902de6269e5776a32c":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5-x86-mswin32.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5-x86-mswin32.gem.asc | +| "buildr-1.3.5.tgz":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5.tgz | "d246b84d70a5934536a3f0cd8de1abf7":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5.tgz.asc | +| "buildr-1.3.5.zip":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5.zip | "d05f9a24f488318d22964bd2f443a76c":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.5/buildr-1.3.5.zip.asc | + +p>. ("Release signing keys":http://archive.apache.org/dist/buildr/1.3.5/KEYS) + +h3. buildr 1.3.4 (2009-04-21) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.3.4.gem":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4.gem | "34247286f910be1724f63b9e795e75ed":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4.gem.asc | +| "buildr-1.3.4-java.gem":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4-java.gem | "44ed67bf835cf2abdc2b6723b5eceadb":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4-java.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4-java.gem.asc | +| "buildr-1.3.4.tgz":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4.tgz | "7d918b88a3bb8139f28f6ff0b39d003c":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4.tgz.asc | +| "buildr-1.3.4.zip":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4.zip | "8f4cf84dc6e293aac5fba7e2a9cc0776":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.4/buildr-1.3.4.zip.asc | + +p>. ("Release signing keys":http://www.apache.org/dist/buildr/KEYS) + + +h3. buildr 1.3.3-incubating (2008-10-08) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.3.3-incubating.gem":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-incubating.gem | "7192dad45441630cbf07b85af5f9069a":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-incubating.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-incubating.gem.asc | +| "buildr-1.3.3-java-incubating.gem":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-java-incubating.gem | "71ad4f0f8bfa951fa129db67a06b608a":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-java-incubating.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-java-incubating.gem.asc | +| "buildr-1.3.3-incubating.tgz":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-incubating.tgz | "e5ee6fe5b86386520c91a9633d02814b":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-incubating.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-incubating.tgz.asc | +| "buildr-1.3.3-incubating.zip":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-incubating.zip | "baab601fd46a877ee8e408891d68c842":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-incubating.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.3-incubating/buildr-1.3.3-incubating.zip.asc | + +p>. ("Release signing keys":http://archive.apache.org/dist/buildr/1.3.3-incubating/KEYS) + + +h3. buildr 1.3.2-incubating (2008-07-18) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.3.2-incubating.gem":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-incubating.gem | "225504bc195334c4eb9d6dec814d9db1":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-incubating.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-incubating.gem.asc | +| "buildr-1.3.2-java-incubating.gem":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-java-incubating.gem | "d7d8394c7aed887987be0e813e1e4cee":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-java-incubating.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-java-incubating.gem.asc | +| "buildr-1.3.2-incubating.tgz":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-incubating.tgz | "611e97df1bc76382ecbe6b60e9340f2b":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-incubating.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-incubating.tgz.asc | +| "buildr-1.3.2-incubating.zip":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-incubating.zip | "d65d20005f603338c0aedd4f17d0bc90":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-incubating.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.2-incubating/buildr-1.3.2-incubating.zip.asc | + +p>. ("Release signing keys":http://archive.apache.org/dist/buildr/1.3.2-incubating/KEYS) + + + +h3. buildr 1.3.1-incubating (2008-05-19) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.3.1-incubating.gem":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-incubating.gem | "476436429b9a6c4ed178009ba17dd724":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-incubating.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-incubating.gem.asc | +| "buildr-1.3.1-java-incubating.gem":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-java-incubating.gem | "7af37acc10621b18d4b870119c36d998":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-java-incubating.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-java-incubating.gem.asc | +| "buildr-1.3.1-incubating.tgz":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-incubating.tgz | "ad6694416fc2e6eb22ab1042dcc41411":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-incubating.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-incubating.tgz.asc | +| "buildr-1.3.1-incubating.zip":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-incubating.zip | "0470349978b93e645ca2e9607e304ed1":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-incubating.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.1-incubating/buildr-1.3.1-incubating.zip.asc | + +p>. ("Release signing keys":http://archive.apache.org/dist/buildr/1.3.1-incubating/KEYS) + + +h3. buildr 1.3.0-incubating (2008-05-01) + +|_. Package |_. MD5 Checksum |_. PGP | +| "buildr-1.3.0-incubating.gem":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-incubating.gem | "37758d0a8dabc570799b0a58d23d19f0":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-incubating.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-incubating.gem.asc | +| "buildr-1.3.0-java-incubating.gem":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-java-incubating.gem | "6a3a86740077f739c111514e0e2b9e06":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-java-incubating.gem.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-java-incubating.gem.asc | +| "buildr-1.3.0-incubating.tgz":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-incubating.tgz | "acd84ad8c5031962e65b0036f3bc2e76":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-incubating.tgz.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-incubating.tgz.asc | +| "buildr-1.3.0-incubating.zip":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-incubating.zip | "d85945f05efd0e512e6d769e3dd1cc98":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-incubating.zip.md5 | "Sig":http://archive.apache.org/dist/buildr/1.3.0-incubating/buildr-1.3.0-incubating.zip.asc | + +p>. ("Release signing keys":http://archive.apache.org/dist/buildr/1.3.0-incubating/KEYS) + + +p(note). When downloading from files please check the "md5sum":http://www.apache.org/dev/release-signing#md5 and verify the "OpenPGP":http://www.apache.org/dev/release-signing#openpgp compatible signature from the main Apache site. This "KEYS":http://www.apache.org/dist/buildr/KEYS file contains the public keys used for signing releases. It is recommended that (when possible) a web of trust is used to confirm the identity of these keys. For more information, please see the "Apache Release FAQ":http://www.apache.org/dev/release.html. + diff --git a/buildr/doc/extending.textile b/buildr/doc/extending.textile new file mode 100644 index 0000000..5a02fc3 --- /dev/null +++ b/buildr/doc/extending.textile @@ -0,0 +1,212 @@ +--- +layout: default +title: Extending Buildr +--- + +h2(#tasks). Organizing Tasks + +A couple of things we learned while working on Buildr. Being able to write your own Rake tasks is a very powerful feature. But if you find yourself doing the same thing over and over, you might also want to consider functions. They give you a lot more power and easy abstractions. + +For example, we use OpenJPA in several projects. It's a very short task, but each time I have to go back to the OpenJPA documentation to figure out how to set the Ant MappingTool task, tell Ant how to define it. After the second time, you're recognizing a pattern and it's just easier to write a function that does all that for you. + +Compare this: + +{% highlight ruby %} +file('derby.sql') do + REQUIRES = [ + 'org.apache.openjpa:openjpa-all:jar:0.9.7-incubating', + 'commons-collections:commons-collections:jar:3.1', + . . . + 'net.sourceforge.serp:serp:jar:1.11.0' ] + ant('openjpa') do |ant| + ant.taskdef :name=>'mapping', + :classname=>'org.apache.openjpa.jdbc.ant.MappingToolTask', + :classpath=>REQUIRES.join(File::PATH_SEPARATOR) + ant.mapping :schemaAction=>'build', :sqlFile=>task.name, + :ignoreErrors=>true do + ant.config :propertiesFile=>_('src/main/sql/derby.xml') + ant.classpath :path=>projects('store', 'utils' ). + flatten.map(&:to_s).join(File::PATH_SEPARATOR) + end + end +end +{% endhighlight %} + +To this: + +{% highlight ruby %} +file('derby.sql') do + mapping_tool :action=>'build', :sql=>task.name, + :properties=>_('src/main/sql/derby.xml'), + :classpath=>projects('store', 'utils') +end +{% endhighlight %} + +I prefer the second. It's easier to look at the Buildfile and understand what it does. It's easier to maintain when you only have to look at the important information. + +But just using functions is not always enough. You end up with a Buildfile containing a lot of code that clearly doesn't belong there. For starters, I recommend putting it in the @tasks@ directory. Write it into a file with a @.rake@ extension and place that in the @tasks@ directory next to the Buildfile. Buildr will automatically pick it up and load it for you. + +If you want to share these pre-canned definitions between projects, you have a few more options. You can share the @tasks@ directory using SVN externals, Git modules, or whichever cross-repository feature your source control system supports. Another mechanism with better version control is to package all these tasks, functions and modules into a "Gem":http://rubygems.org/ and require it from your Buildfile. You can run your own internal Gem server for that. + +To summarize, there are several common ways to distribute extensions: +* Put them in the same place (e.g. @~/.buildr@) and require them from your +@buildfile@ +* Put them directly in the project, typically under the @tasks@ directory. +* Put them in a shared code repository, and link to them from your project's @tasks@ directory +* As Ruby gems and specify which gems are used in the settings file + +You can also get creative and devise your own way to distribute extensions. +"Sake":http://errtheblog.com/post/6069 is a good example of such initiative that lets you deploy Rake tasks on a system-wide basis. + +h2(#extensions). Creating Extensions + +The basic mechanism for extending projects in Buildr are Ruby modules. In fact, base features like compiling and testing are all developed in the form of modules, and then added to the core Project class. + +A module defines instance methods that are then mixed into the project and become instance methods of the project. There are two general ways for extending projects. You can extend all projects by including the module in Project: + +{% highlight ruby %} +class Project + include MyExtension +end +{% endhighlight %} + +You can also extend a given project instance and only that instance by extending it with the module: + +{% highlight ruby %} +define 'foo' do + extend MyExtension +end +{% endhighlight %} + +Some extensions require tighter integration with the project, specifically for setting up tasks and properties, or for configuring tasks based on the project definition. You can do that by adding callbacks to the process. + +The easiest way to add callbacks is by incorporating the Extension module in your own extension, and using the various class methods to define callback behavior. + +|_. Method |_. Usage | +| @first_time@ | This block will be called once for any particular extension. You can use this to setup top-level and local tasks. | +| @before_define@ | This block is called once for the project with the project instance, right before running the project definition. You can use this to add tasks and set properties that will be used in the project definition. | +| @after_define@ | This block is called once for the project with the project instance, right after running the project definition. You can use this to do any post-processing that depends on the project definition. | + +This example illustrates how to write a simple extension: + +{% highlight ruby %} +module LinesOfCode + + include Extension + + first_time do + # Define task not specific to any projet. + desc 'Count lines of code in current project' + Project.local_task('loc') + end + + before_define do |project| + # Define the loc task for this particular project. + project.recursive_task 'loc' do |task| + lines = task.prerequisites.map { |path| + Dir["#{path}/**/*"] + }.flatten.uniq.inject(0) { |total, file| + total = 0 if total.nil? + if File.file? file then + total + File.readlines(file).count + end + } + puts "Project #{project.name} has #{lines} lines of code" + end + end + + after_define do |project| + # Now that we know all the source directories, add them. + task('loc' => project.compile.sources + project.test.sources) + end + + # To use this method in your project: + # loc path_1, path_2 + def loc(*paths) + task('loc' => paths) + end + +end + +class Buildr::Project + include LinesOfCode +end +{% endhighlight %} + +You may find interesting that this Extension API is used pervasively inside Buildr itself. Many of the standard tasks such as @compile@, @test@, @package@ are extensions to a very small core. + +Starting with Buildr 1.4, it's possible to define ordering between @before_define@ and @after_define@ code blocks in a way similar to Rake's dependencies. For example, if you wanted to override @project.test.compile.from@ in @after_define@, you could do so by in + +{% highlight ruby %} +after_define(:functional_tests) do |project| + # Change project.test.compile.from if it's not already pointing + # to a location with Java sources + if Dir["#{project.test.compile.from}/**/*.java"].size == 0 && + Dir["#{project._(:src, 'test-functional', :java)}/**/*.java"].size > 0 + project.test.compile.from project._(:src, 'test-functional', :java) + end +end + +# make sure project.test.compile.from is updated before the +# compile extension picks up its value +after_define(:compile => :functional_test) +{% endhighlight %} + +Core extensions provide the following named callbacks: @compile@, @test@, @build@, @package@ and @check@. + +h2(#layouts). Using Alternative Layouts + +Buildr follows a common convention for project layouts: Java source files appear in @src/main/java@ and compile to @target/classes@, resources are copied over from @src/main/resources@ and so forth. Not all projects follow this convention, so it's now possible to specify an alternative project layout. + +The default layout is available in @Layout.default@, and all projects inherit it. You can set @Layout.default@ to your own layout, or define a project with a given layout (recommended) by setting the @:layout@ property. Projects inherit the layout from their parent projects. For example: + +{% highlight ruby %} +define 'foo', :layout=>my_layout do + ... +end +{% endhighlight %} + +A layout is an object that implements the @expand@ method. The easiest way to define a custom layout is to create a new @Layout@ object and specify mapping between names used by Buildr and actual paths within the project. For example: + +{% highlight ruby %} +my_layout = Layout.new +my_layout[:source, :main, :java] = 'java' +my_layout[:source, :main, :resources] = 'resources' +{% endhighlight %} + +Partial expansion also works, so you can specify the above layout using: + +{% highlight ruby %} +my_layout = Layout.new +my_layout[:source, :main] = '' +{% endhighlight %} + +If you need anything more complex, you can always subclass @Layout@ and add special handling in the @expand@ method, you'll find one such example in the API documentation. + +The built-in tasks expand lists of symbols into relative paths, using the following convention: + +|_. Path |_. Expands to | +| @:source, :main, @ | Directory containing source files for a given language or usage, for example, @:java@, @:resources@, @:webapp@. | +| @:source, :test, @ | Directory containing test files for a given language or usage, for example, @:java@, @:resources@. | +| @:target, :generated@ | Target directory for generated code (typically source code). | +| @:target, :main, @ | Target directory for compiled code, for example, @:classes@, @:resources@. | +| @:target, :test, @ | Target directory for compile test cases, for example, @:classes@, @:resources@. | +| @:reports, @ | Target directory for generated reports, for example, @:junit@, @:coverage@. | + +All tasks are encouraged to use the same convention, and whenever possible, we recommend using the project's @path_to@ method to expand a list of symbols into a path, or use the appropriate path when available. For example: + +{% highlight ruby %} +define 'bad' do + # This may not be the real target. + puts 'Compiling to ' + path_to('target/classes') + # This will break with different layouts. + package(:jar).include 'src/main/etc/*' +end + +define 'good' do + # This is always the compiler's target. + puts 'Compiling to ' + compile.target.to_s + # This will work with different layouts. + package(:jar).include path_to(:source, :main, :etc, '*') +end +{% endhighlight %} diff --git a/buildr/doc/images/1442160941-frontcover.jpg b/buildr/doc/images/1442160941-frontcover.jpg new file mode 100644 index 0000000000000000000000000000000000000000..569adf1105fbadb8dc53392f1995849b97bd9455 GIT binary patch literal 4759 zcmd^Cc|26@+rP)m7-Y$oHDt+>C0j`{mXMGwSxd5|iOH6!WQq_aBukVcYZD?%4<2L9 zlCnoC%TQz;Lc@$X@925o=Y4v9eLlbU{r&g*{I1Wr&V60iIoEx@-|Idz=enUGXbfyW zYHVr@U<^@&DFOhEgL_6eA1?qbEPw(4fCV67!T`yzU<^G96ZzA&fyn{*k2n+KqAFmV z0Utx1qJ043M;rh@#<*oNX5PyGW7K!Y2KY%_^b82}!TL$4Dr+jM1Hkl{>CY_OWB8wT z`Bpyk0T}+kxP|kFFfqZIwgdr(Gr^GvB=UzcvoIr>QOrmriWP-oVPRoqK{B(lv9ho+ zY?iG_wsNaB85MmqCiZU9z2ubCwEOAVPjYhe@(T)|mc1yisH}SVTXkbobIYsNw)T$R zzW#xCgG29!$4L{DpTA5^f1O!c{NaHlGXXtpJES$nWtO(6{b2TgBNqNI zG5eF)|MD6J>~I)k@!;G54d|F)SBgp(jX!qOw%?buS%W@@jFC;`!#5gY>e=-mkb1NW z0?`nFK!YQ3no2y!MU(TzuX7T2R*bH5oEu$TRiQ^=6t1DYiQ`*$kxyVZbQgysAiyT# zN;iB$7vG8^y&&*pVY2{D&Z>aGq&Ceh`#(9uH;9X(LZtDBFS_(9d2d!jz-N8e-5J48 zmN%6ybht2~C;Ga3T;s$9Adr}(4}p>o_)WobE2M7--O#CO^QsvHveG+>PX6RRW{Q>j zZ+)IpWg~~1FP1j^WR+-fcLb-WuCJlD@-fFN|Ng00#gRs0-U3(yVYys8uU*G?xOa1| z8XH*KZl+JRxotQ1gv%H_m>A^e-6O>x?0NcvzMw>}g@8hUQ~AEwd-?~Bd!MP58xoLO z;&Da>XYL7q^26d>>Bbf)I?)#bD`$F4A{i^}(T$&78-;+e5?}is3~^a_fH*tjy2<{| z?%eEOJ-}ye1o)m5$;i#>y_=fBHdr#=H7?h+prlsq)J7sFWwE*Pbzx^hJErc+1m9a8 ziF)2{q##Pv-x0qfY-gx^e;DXm+JTMg4&Aln*R41gE|{C{t~?O=q<%z{qe(%?MCbHi z9=+@C%nN!a-i=&YkvC0ON}@VytWQ0((!by*Jv1dT29N08c)`8Tbx4XcapkT zaIJPJ2Oml6^}J|Lq~7d~wD`s>#9r`nqR*dhPe?x_|0P*g_^zMUt$;$a`>z&_#3Ne= z``qc#YKvTDF5b~Ebq=}glxr+D&zb3O8XsV>U-S1bwB^o|)U1*FbhTYM8;G*hkJ z*kW`hA0@TG<*eoSyn8#wE_QrmOfjMM_3vy*+X~JLtq-^^ueedwD1!~@i%r`e25>RY z?i(J-$0C0(Af|duFnQgv{Tv$GCS+>Wni*A>UrP>-e+7Z}A>&@RvT;>S)r!?F`@EJh z@AsyRm~$a!>O~21UZ=TGzyqH6Rh+*#sBM+_K3n9FO~=|h6>bB9TF1+v9k=0`I8+2> z@ZI1NN@(}(z)8JyF^WD zyo1GjdE=4?*5ifAi;a&^=y_)n7so7-lP2yxz8hCLpjX|U-WVwvft@f%X zn7X^}$2aNXGDsmU_*h|;Yb!-6W<5b~&df+DE}X8Yu-tp&w(kk)F;Rs(5z@DBZ?+@n zAJO)CejGhtzHE8jVDX#1Us!jox7Hgsn%EL^1mPUIJh};2Yl6wf6@PEO{IKO&o?dZJ zTezyED6K{45?cVwc2sl1O)FJADsM?613T0^ntpWu#ej;1xw@-eeJ>;Gk8STNEicn) zRJ|dS?uAB`<=T|U$Y=*0?>~Mf_t4#=@+XaU_i25j9-6KLn-6uRO((cGaq9L7r>o5wZ1v?JF?v+UGEOpg2q6*SZ5R>#?z7XS0d2Pe&nXHoY!JFG+JeOJZdYhGi zzw`!E(+m}hKsS!Qw?f$-a=)AotYar)Ea17A>ZG zYEC-P$OE;!g|tJIhg>%*V#;UZj9YE1!o4ce+PSaxlnysDg;8c;D5(nmw_mG^=oJ$f z4tv|`^kw>#?H$r3kMrea8Xb-?3#Qq`>g>&GebS{7VdoV0? z+@P!QPKTf5E88NBTwJ^Oo4Za(itU{JvhtbW6oDqD{PTq(Zc(GDUl1N*5iRN}<$LCJ z)fIbvk{-{lU71#9i?@-wC~>iUL2p3;*&EDk`e^R6PVK^p(?#{|^*RqYk7;urLuqav z#u4ajyi`_tZy3iwtqRqnsu=>es$IBo3RLq9GxEpkV}#t1Vcfp6tENi3`$$!mYEyP8 zCtv&5p?J%)?D*NkFyq8nBKZg&5$d@4vX=?8`l4qxr8 zvgaOpquz2Q-~4mda^{TwIcXkNA(Ehk_*-@-yOkz+9+WIwoFt(w@U}wIZ+CF@s00&t zi&V3^n+d1Y;L$yev5OgRQn)QGds z*DK^JB;{oOxm07)V%yTGtV^32-Xtv^)|A;UONBM6SyOVX6c*F_RLEwH{O){8To-sOLtP|kb|Y< z9Cr0-WtBR$YwG^_Rl7>Fh_i7*RvnIfHM_?4qQ2~TlzLJ1fpTEW;nV%!owG_4x0N1g zeP930L6l!Kv{{rr@a@6Dl}K{Uge;8*ZM7M%yFFBpk~}6~KFT+<&&ey7ltFOiz}+z; zpm~SCUQW*0i1_H_l09PNFP+lxxGGWdtdotwQAErXT5L6*B2_^7ovNWvPHNDVjvuqj zj}6s#pCFuF!E-kdX0&}f6^1by-_;3kFuu~acw1o3U6S$!FPLpwCFjH)3%4lL`}kI9 zdBs@N+N6#nj^34mKuwi`<@tDngTkIgdAS;Cu|vrTsDs^z4_$A%Ivxjh0rq8P%*Inn z#*#Xn#d8Te@rb7AdQ(?yX(7wI0JCFlaBWy1y4K=}(GGv97{BQk&&L?JlS z8W3Xo2(ds5vy;Y?+S`I+tOMpc5*67cjDe2sjU=mp7nCV(M|ESYKXInwKQjuSyWiCyA2~KB; zBb&6=i7b^|3vw01tO+09=)jP^HHpbq_g5%sQ;+m3k)n_6PZf8sxX-?nQ*xCzN>W}@ zHSPVi$2IWg{>ul@wRCqKjd+2W6|Kn=raLx3hcVkDO`L{(CC%*xCD5H}K*|0vuGA9O z+GMwMvYxzNRasMI*Guo`c)A`EG0&ZRBfG5gl*RO%M-nc;PB4?B7y@mW6O10P5Wk}C zXh-0Fy(A0RN@l6~!;_n%um=->38G_^42lMqOFR|El98^%+- z$_Vy<3lxCtSh-&*{L0_zsiOh`sojhnlWQdeHdN0(W*c6lMO9!oHc}yAsG00k{gbPQ zeo~WwAM7V?2t9i`@RJARvSWX#BR>~2{%@s*Z#opC$NGpA z8JYe*mktP2=%<)?pAR)iV^2zgfa32KjP1+zbbNyxnn*nkf$^pc2*fdJExyE>PveQ8 zGBPabq6dhSfq#tfS;2ml{Hxf%!-USI5QwnEkKL;bx%fT7lzN<%l;~kjGf0c8pTxgb MVfL0JFR#@H3%e=6ffsU0kGc(23+r6Ed?$p@B0x19h z0RR90A^8LV00000EC2ui02u-^000L6z@KnPEE41ejE#S;| zTugKrIB*ZYRccVpNJd~rhYeRw2ls47QDBNtlGK!+oR zh>9o_xNFy=2LpHvVDuQk0YrrVN5}}|kR%{kf*%04kW{ttPRCO<`t|ET~IIGB)kf?frI+YuIwQ-%r-92zuu5n)FNK97l5B^qe$ zDw5%nnExg~kJvJ!{|sbZItph2`mkz!O(Zt%Meo^X8y;zS!&VK`P_RM`Eu=7mgJhtP zK?ck@P(faM{gu;S4^YSdlL|U9z<~-NgHW1S&Nk*+G6tn9x}f07&G~5Ety2%nT>w_hU)K z>^7MyGY}JvG(<$B&jo7w2&9li9-$31Yz_gXlvE~hhl#G&(5<>{23H10D1TN6`jY$-wCEN@xF!+On z6jG?1g&6K7lRG;N7T92f!C;+FHrRn;WRiHwB0mgGv?US-`XInkGm7U=2Q@nS17}hR za3cT-kd|#n-1bxdn##itqZE707T_(f23{dRR@?&LWI3Oc(zm~;IefiNM{Dj zu2*e2@h1}XGW@P=;NshwtC<-wUO&@f{LN8A_$P#Hh14hs0l;lrqr66rB(V!F)F7O2 zw_@-C2DxTX!G#!BpjQPpNf+W^ei6&T0}D{nM0GO zst1jD{cS2v%Z&NZ*97)VjcP)8U-9G@GR*OU`L_n{Omb>(NJNqlN}j! zNFFcvP8x*QtP4VfG7d>V+mt{)A@r*TTg%uG0G2)^^lK|I5}9V#Ra2eOHm-u!ym4W zb+5x2>~^Jq3O&bXwe!%1gav{ZI5Pqf0Noa-7)s*72$T&=!cw+km5l`Gja@53rRKCv zRZ25J<-A~Kijgo+9uAaVA;bU5m_QV&(g^f%%_y@)64rq62mQ*=ID?eVPQHsXCMkv@ zmoUjf>@9neu;=9>`NvL%L~}#vSJe_YEkpe%IATjw<5=m%KweT6mO-aUpO6+*m`oM_ zANW83W~#amsLpl3Bw_}Z6T4j@Q!g+W%!Y2VI~DlOFe)IC6!CDV76Gb%E;MO?CaTFX zycCX8U0VqAIm5?oW(HU7+EgoA(IIeEq(r%34*my#O8VfRnG36DNR~j#F~NaTJIYua z$2bgHbO;qCS%5|t8?70ck7xX)DpKv2&@Im+E-2rc2d1zPe=hf(%I5=v0GjLX%p%t zf)<=ZW;wXlO9dcPAJ)`|Hgz2aFson9?z93h_|;Dp5L9^7WH7vIfCMDa02%O(J0(a~ zBEtF&Yy`-yJ|F}*Gez7$b}bpAKnh(km&h=y=E{LeFyO^$!rowgq$*T%|NIH{_ z;_h}Er%~*7vPeuWuXG2!Q4Cik#4c`v3q&A-6gWs`GmzN{T4jKkUR9=*1v6!Vx!=wX z(?UUWhhB0A55o{k1|xXEf_Y~h?KtxV3sBk(0H8D%tN~z+%*biFx+TP^vC{xG=~8L{ z95zPT)9zdf%Cf5&5J&VOLoi|g3YtdVQd*5fz_28;rEqt`d!e(1}6wiLd<5)b7uFK3OqnB3^)vFWkA$w`-O>f_MLVdob3|q zK*C_K3Y9Pn#=5)n2lHL&b0ydIn14T)Ua2GGN#mLEXDvM}{+c^A=Ywr%j-KKFv;`y0 zHTC~Z^)Hq~=|O))VR=KAa_*;ZLqLGmQeJlyapC0^)5B5!ZYOg{;1Cy33kk3SD?l7| zu^Y$dGq;xktg|{mM>=0-U#p{E6W~K)wo|fWX6=Ax7*aHZ5n421gE#>LZYBdyH)qWR zVXV~!UxWmp1OT!$8e2pJ5eN>IW&$R>+#7-;y{8vIHi;cxl0g_8?GhFW04-tnW9vVT2qn-C%;zgHm;qvj0Y4WTDX4-rr8+uQ0hl!bKUE{ z6tEjDpaYrm3g-9%@W6aC@iWRnf;d%EC+KA8Io~80VGM1BtQc0XaYK586ZFp_c((~rvPMT zSpo2m06AGWWnV`Ze8>j_Ft{8>#B_hr18Md%HnD@(;X4yxObYM-9$*0$PyjCQ3uWMu zB6*cqnU!TA0vA95I6?_R5RudokwaBXz-L+iKQvPxz%}&sWyO|sE{Fj|sZ*wiA91FX|i6lsf2lzcGo0%2g4TA7%NxtLOL0hK@jGvJj!Pyz;akAoo??N9+R z5Cit5f+|>SHMy2t^JV>5Q@g@nIrEc6DTp;VA3S1F~A001tq01{A}4UkkEAdA?+7rvqwI)$P{RG`h-qPt}+ z(&?5(2Sm-L96-gOp)~+;sWUqPeH;K_Ml%9fiU59!00#h-8c+ZluxMD23f54JRC)%% zmOL2-rCmUUAE`XS$Ovtv0a$904S*dn&;aiMS_#=%B6xd6S%SVKqsEq|>;a=SDMY*S znFfG^0xFlyS8X_WkF{Bm3l^3C2XFvLY5^LsG+%(IO+c)Umxy1WM~D<@^aQC`AgTPJ zMWpapH;}2?)&MA=9Yi&sMud-hu`7P@phCogLR6q?nX0M^Q!y2wU={#(SxiJX0kH@V z!iRiP_oKDA02a`zyvnapSpdSy1;)Ar18Ww@x|_{f1cvCWSpcoCv9P0HrFV7%3$}Fy z_H+a0pg*Otr|AGXbBQF_L)1A_p=p!p+LlYnuAs<@fAMU20W@-k9V1`?fZDHty05+3 zs~XS+so-(evQb1J5v!04_+%}~u&e}NDHBmXNzj{0@CQm@C4CY<(-1!?0$2Y68I58n z1i&&>>pxo(98$n9M3IR9$l$Z3_aqziR+S13_R>hlnl4OBEtmn8Q;;qKV7Et*v=^`t z!RWJZRRcg<1XBA9j|BlN0F?`X03q6?)KL?~mx{+ExkvP^%3-4AdbuiCrX*XkCabFJ zx-&3vGeV@N!~z0~>#I1cy1j}3P>G}fYp73<3V)ai%W69IQ(dVrBmj3CBVQ+i8CrG(HDFIJVoz4`x5@iS)eBI^+XOT=4CMlVwEGaLFu32+DBTlZ zPohKIBdyl;Rwe+v@DecHqeD~Bz40q(T`?3w0TvK|zCThFN+2_@(N->S10}EkAKC!D z^QEFiEUb8xviK|iOZQXAl%TG8iLdjjzUMkNb*^g)ooR})#&H#}@sP%VA*~=`FgseVLHOk|?%Nl9+izU~41Zk^Bc{?(ymkNRKTdlw?NbtkfB6)~!XB7Ic1wgZY zS^$9Bx)!hiCwu@4-~kv~6{|$FbhkN$|d_!4n#}t&M=&`z@91QxChulL%^qDualpS1W z9v~DXvCogJuat}ci|e{j>8BQO1p^Dp8Y9YGLBpP*(AD_DNdUF}W=MdLyhxnHnNoiV zfI<+PJr=D;0&&eskQG|SEx)`<%4`HsSQK+n%pudn(o9~tQ8qHIEp}we&is84ok-%# zAG>iw8UUy-uoFEH&=L@l1F&^TjhuPRW>1HY?l1rvBEiG=95WgKz2X4Q`KtQ$rbwq> z1C7c5M`{6%+{g<+0BF6*0}YiH(8*)_$tg|B$b2@@%mg)X%;L0iPZBlaB4cxL*9#%G zE>hTiy#aqMxQ!YG-ry)b4blO(#h1i6&QeENd{@Z~(<|b_`taC7tW6u`*+1YSPs$bV zxEm}ms0+}NDp1J?AfEwU)W?~}vn`7T#eiz(kIyczC<9!qg>g}OVTk-)5M#@MIa1);vz+&M*!%|HQ;iHW6I&pn4;5CJFpo4 zNV)(K2?CYel9fr@IZ+)V+I&w()#2)jy)CXn#M{9=A)(a+b*YNU$1}>Y0KS^ZuFJ1E zOS6Gmvow3qRS@4t;0;t^Xx9=5hW1vi00HrZ#iooD_>IcyaoLsRjgn9a<)YW~t>nk` zzI9|JRDp!Dr7bB1tvQebt#BrF6bWEywbqr{b(9)z(YJM!3hI{{5Fz9)SKmh<8f~LR z{_Dg1jg=+I#wW(c0DuA@fNhj~055PITzWLYLLCf{bI1cW6trQ;tmjJvUs_Q zp0V9|e0q`GFAn2@+T4=Ns}^8xH!jyrn{ok15~|mLM6t_R&PT{95whzI;ZWfJOQ3jM z;sbeZ>tl4yY<_{N(9tQ4NpnsTZKK(7WlH&N-$rmg92d&!2LSZ(*T<>^ecKSGgT}P3 zp>6f;C)QG_jj10B0$TcJeK8myuwWp7n{=Ff1{P-pMwDk}>Bw{#y3MlIMi0FG0QnfaM)DGdMLZh1d%|E4Z$D&O|VY`Cx+_X4&Mn{&{VRUJQ$w^)NDsk z?(N@xxc2}}C19aD5CKl)U^}4#n4Hu^)dSANU~-J%T?zx&QO7jVxYIX-JI?_!z@1c` zVAB_89`M{a8_6+lXBxl)Y0>d_;LK03*nQpbB3ZBu+l??%F1bOe40{CsUU|4JRrV(q z0)TGrnCt;gv=a~T0&gq_k1@2nm)=QkbpL~z=CZuV^z0?>r=B~Sn(5G*|qm`v@1HSzZP zY5^lK4-DV|Obtyg&{A!61GT>a5#UTEFu=I)`J}L@nlA-SI{e=<48{)zB}tM&P4;CU z0#5Yy05I;kh>K4j0@lap_fP_?a%UP)`z2rU;qK?Q&-lrI{^+0nTfq5!-u|{9_1Z7~ zop1h0P?G9@|M;K(S78640|XSxk61JwkxAu}*>pakQR$RgwO+9YS?!kF^*Vz9JN~~f AkN^Mx literal 0 HcmV?d00001 diff --git a/buildr/doc/images/asf-logo.png b/buildr/doc/images/asf-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..be54b76af2a1f6a80894241f63f577c56033d65b GIT binary patch literal 15220 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 literal 0 HcmV?d00001 diff --git a/buildr/doc/images/buildr-hires.png b/buildr/doc/images/buildr-hires.png new file mode 100644 index 0000000000000000000000000000000000000000..ed7e3768763524ade70bbb41f3288268f0f245e1 GIT binary patch literal 199068 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%0D|PT*4ET2D6K|3@4{5xJ@-Htj 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 z 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 literal 0 HcmV?d00001 diff --git a/buildr/doc/images/buildr.png b/buildr/doc/images/buildr.png new file mode 100644 index 0000000000000000000000000000000000000000..7099b8c0907aa37bc202e0f80bc70f374f0bb9f1 GIT binary patch literal 17916 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 literal 0 HcmV?d00001 diff --git a/buildr/doc/images/note.png b/buildr/doc/images/note.png new file mode 100644 index 0000000000000000000000000000000000000000..9653a3cec13ab3b9be273a4edf6da7ef369f2c4c GIT binary patch literal 3381 zcmV-54a)L~P)e5S#6A5RT+Noy>oYFc4oWN-Kkw@-CgMx7gAb6i);&_ zjjaMgtPn+mjsEbD3C16!CVu>BjQ&7P{N)dgM1rCXqEWOdO-pD^V~Vtt1}GKUvdAuM z?X=8xXLs+++z+4Uz4y${>_=$}iTvQSXU^Aq-uHRl^PY3=y({uj`u}>s`k>86BGyyI zV!m1`Wft{GDzI`fW=;dVz(I`5?0 zT#2H{_k)^Motp5g)lwLRr$Wy@R?GRX+;^(--}g)CiU~X+lI`ftZICE+r?aU2hT`(x zbber!WCsQ$)w5jEg@RbQoZ^cnC#700i$69Z)59la{KU&LKK#~1G+91k``O)_$Icyw zV0a~DE}H;D)?0D;R&mqp--YrB_qdbJ1Jmwaq}&!#)2u;ot4 zEL#Sw1nVniW1VnK!f0z!E-7$w#2X!z^8Tmg?Y+ChO8NY*HnAT0%;|T>TPG~sx?}=- z`nopQuJcgOnwz?dU)d)46$2tZJRATxJZk{Im%?OiW)Oiyl(J;<)JZAr{GFV6>BuQ} z%6fR?$l2Ftgch#7mpz@iZ{^~994GVbWn1rai+6uPQf?aHeGLc-z?VsCRcA=eSpkL- zW|)2rv{Xv`dR5Nt`iqP__2=@1a{b3!-zjl#Tr?RQ3kp32@Y(i75A|*PhSRZyvMA)tt>Ne(v$QDD;ucP0R_Q6$>=_+QX zUw>u8eXez4=Y%)5AUF(Ml)%1$uJsu=`@Oy|f88mp-yqg>RUDrN0T!qUkZ4GPS=%Ue zPq1vxN;ZTz$;Gk}osp1?>d*%wQN1qdzFx_8E^eEA?fA`K%+;U&!?<5wh{S>fxM4Gq z^TVs}x+h<}eX~flCaJ`j!!n4J1nca#L4@F@Lt6qIpFYCOciS4dUayIR{X%(N5r8)< zq~Jk{^Vt{HeQ}rfN}B}P%Il{;x%%Dd{d+~~^O2Yd-=@(gz9dfP^ZtWN*R1O<+_pu; zo78wW5zZ2OPb}+m)?HU3K-ob_YRYHPFOx!k7q&cgb_mMz$zKnagH$ShOXybf}z z8kd5Q;)d2^ly!6V&sMB)&hct2&p80;Ko50M2X$Mt<;9t{yMZB_TYT?<<%?F%R>}4p z0z3NqGfrFkSG(5@Nv7BX?|WiZ<7`0Fkb*{`$0+Xr^aTi!NRQRIrtmEosk5eGQ@}2pR_FgwUtbr48Bq(of`9-KYwDhDoK)t9O#gs-p9U=gLRK;akhEt?f+B)gZXp5zZ*84B zpZv^R4IhRrZ5nyBbHJ3zWrp|=31f}avI0vBi`R4(yV87;RBu7A1Gk$nqK5=4p_rGEDGj_K{kO+l_Rt24GUX`Rl$Y?+1 zxGG8FJc5`nv76&&0gQ~1H{i*pSw%u7fE{9UEV~C&$&ZUPW8H46rtK)5S73VjKnp=a z0lhF6FbyT(4F_GhX6u}oBJe}YoyG>@>}1R63k8yWq{u#vIE zZaWPE=+?+;5D2DoQ7Ti7ypa*f;JsGYhx1j!R8V^8AQ2rf{j`Flej1qvQx3pxN} zlrcF`!Rv!pBk_16!xGkN(eeKNNQQ@1qCp^CFVv$ zVm3Na4YA$SAJMx6FkfQ#><}`ZZz&Qo${$XE+cOQIAkOot(JPn4%3w?2Saop*2bqjG$a9kNNoVvd5B^~;*75k zB*WEVPB6?~ei|?-;_;~@Oixl8Vq86Ztq^!S0&uP-pM$8H;S(g05J@=Ntf4^w=Tfvg zvQOiHT1D`j$j!^mPc?o8o67?~C|B?#pd?t5NT%VrW&@^9uQC*L53!Rgp~o)E=8QuG z_K1O?JxVk41M5d53dO==KeM-W{}5M1O^eL)C(BUBb8pGQI>kCkN~P^ z^R=P;&{^E;b;lq}$82dRj{)zRdt{6CnG~O2jvQ%uH*O(7DYsVH<+<67*tV*Ot3V-!4i)nb=S9|nZD_MhRb=G5{&z=0q2UO4JI@)A*#DizTad`62E$Mx7SIu!t7AA2Hmrz{bAnDxrcFK#SL@ckt);d( zg|;3)d(^8%ySNJKc__iD9It@ZXGjFJ1grSiDE{F?GXC5ls+f~Xe|r*7@h`{ESR{ct zoXfM`fTx34r!B3c9RNcZ)E>QicyerxQgmoI&NuM6^OaZJB^^U;j!( z1kZpvK0)a}8y~YQv|Ya@Xa4bwRI62)u8ixK=ZP1dlhPx<5bw9YmL34Ztsy}zlsY~! z)-bzt`ynYapO1vrmn82_x(>s}a^v85l| z{OY;Cq8rD=rKQUvU{vnu?-&f++yidPUZ1yVK29d?O=kvy9B};F zvzuQ#H#*<6OX+)G1dIWG?rXm;zryy~2JN(atrJL3kaBXC_-%LzkL9q!T@qv7v1!u+OY@4Z+`DgsIskzQixNDTxC5SldUBGMs%^xlhf0wD;} zdnfcNp?A3Wd+&GdJ8P26oXH=V%-(0Mz1HW1zE@WwBVi!9ckdqAySHyX+`EUjeed3V zSE7e^SI*rhDegXqo!;uZ+`IP}_V0x^Z^;V3ckk)FcW+*OgyZd`5vIOSs5!9}wtKq% zeCInv-}!s$D*^&izqgMy>tB%4RsEbO-nf3#?)Ux8KKHY*$3{O_c*}Pl$3I_hA{O&m z2MZ54)z&VIIt~a_X&t8^3ng9qwer+;Pd7>C_WTHFUz1PW|No+S{64-{iKTvpo&WzA z*8V%iE=t|B|BH=P+V1{_g!GJJliHX63%BuK6y|{c#gQ|>+-j~@KroPNIn)VgR4h4)4esYA;Y!Hp{%d~P2A zqZ0GvPCKYgrW144{-ootSw^P4IAQBTQ(N*HGx|{5Oea#e==LbrR@uIEiD~=Fk9a=g zRRC(SJ)q;U3NK|aIrFqDkP@5)_rt5XLLSeayL6~&XpoA_c{?UvEwN*NZw>_6xL5{z z0M^6{feEK<08$14fd~d6=?IozA10m71tE>v;>0s$lKiABlk>tX#t!|d5<{=d$c>+9 z8&x0wG5lEse86$eeMB8!_Oc$S*zi?+)#gXp{f-GdeB)-6a(k>6`-g zzM^>@v*@WpJDHpLh;Ro?NvMWXq;S-99x6ggALW+4=f_x8F zv0n&FNz3|@uruFAt~>9mkkM1lSKY8+2gy!}vx3PTe~m@_gpS@kBgh^Pnqe02C=>{1 z0I#BXuL=b$mJ4y&tWcUnE=5O4jmorX?zyiD;l!7DLaV)<1$;f9sDHx<9mW#1mmwj4 z+loot3v%MavUYzSuG*+cZ4*b&KlVY)cwpnEMw$`or&<7BVVL>ZpYf28=JDxTE`U?T zV)o20>u-Dwl(a+1A9@nQj<_1|#KQ^GF?LGg;5^LHk=t<1&J=mBd87P7VMWoXrrz!e zbB$nRQ2OvppKcIOYT-0cdYVIZc(AA;w@nLo96GMLvpPK#Ua+s3rN}orUVRhX?};qo zLORdvp}=`Yu{~>u+Ls`wG~*7dx|>f=K3Z{ykn<^}nZS1w%cX#`M<|s;i{leIcYRXe z1QoowWcRa6qbD(DCRU_A&|tNq{I~C?kD=>D$CHCagLP6)h)eJ3x*X1u6Rs0!?V6fK z!FG+~vK*=HNflr-OxJh6A*OV@7&;36_4mlBFV;QVSt6Zp(8AwX$&tRLW`tOjxt7T_ z!SDHJSG|D|WdnH0Ea?`O&iIqIX_13KPNVg1m-e!UcLRRo=Rbw}*1j}}sJ8dEOP2DS3lPbUqbJoy zXP-j`@|sH4iaI~cX`7J@U zBV%yMr?En*oV;@b*SC2F6$!SqBfXrv`wHwn*T)mfSR3UD*m$w#dVymS7bcSTB{*~7 ztPL&h{?|xjOL=0exO?L#jYh(Is?ekRALh-@tDmCt5Sm8ON){-c7NWeb@jW@`&yp0Y z4J1DX855|Nh!HjFcoe~~lnG=Cl_tQ2Ps6Z}vn(LGIF znl0pr7&l&m%kHw+BJJZ6Wh-pH26yXw3w!gzPJERUJNcZ~Lxo32pt&3#GTbp|`Y;LO zG*{7tXIQ@%ji-1PKpLshz{x)>7w4bH;!CMW*|)9vah`;Xw@izDr&U`s{>{!?VqL+HW$rx-%JXK=bafV}Ir{N!-d2htat+!Z->n4{x@CLLar zxIB*y7DGfy*Jp7()QEg>o`;dS({Mpnd{kJo>J%*OIdglgj}eAx#ddRTJ0aCHj;%<( zT4(tqc%>ju^=$=W-mH*0tUzTKCsY|@?wzC|p0!=9U$rDPY2rX>)kmlSk8X&26esM( zm*~jsJb&fX9qbiS#M;dFIzrzPb~XT@2oxuEZ)+Q8awFt#OMSGslRj3lQ=WtJ>@A^x z(JUhB6On^oclZL2l`eR*qK}&~gwCp&A-CzmyiLtdP=u5urL6jup55<^S_=FAjo0t&0pLZrJAhBv~xuNX`yT8uY zQkKsrKNppl{=ug?4v!WyD+*!t0X6N~JBDYO_2*1f=wDk7Aen>77;`Oy6jDXOFL(~y zV>j~G`vKehHJ)cjZXqGQvtDZO%ZF0I1G@+qd?8((B)ShiPF?%l*;Y@OO^irhUyeoA z+IrGEvZN!hKs-d>#e^cIv_bvsMwTNu`g*w$Tf6=uNMJIeeakEGuhZzE)c_!G=Qv3K z($k0Cp168OTT!Y@**^X1xU&c~~st4QVPd)3jXgI=V|J)Py!5aIoQFdDv>R zYF70T8e-%&Wr4l>E*!obDUNE`E@iyCkgx%_tzp(rrM+UkoWv~QW0I_p^vux-F+RgN z5yLWQ$24ze5Y7$Syp<&&?Bl zAcjKTUZm=+E4jfP^}ILZI@W<#n)Pd$GpLFAb1-&r-;u&rizAI-Lw=^Dx*|&?;mGaS=G~<_pFz^3Swj2K!KlXkGG|%6HnSEYdX7XVEOxDT zNtqDqpCw|^x0EZiqCjO?r8mqK*-jOVp#@W_S;&r4=P6=ln?fj9S<>oSDZP?JB3Wh! zp$I}&gwyPC_~BZv51K|dkUd&j$gXL>3p(1vBW-4H&Nru3?CDX%wYm?B2F$I;uxl_T zMO}~Awa_SiO~2l;>Yz2W8xm!IDm|rm{9Cb`B*eHsLO>$9BB5NX`J)m~MR}7dPd(0g zev~E02C#6U;};dMn)=vUSj?REe#A4G|5Q@r*|$4VL|s3ekGpUCQp6`vo%wrn>?+bA>AZ+Rv{!Xn2Zr{q$-~8jRXwwSK2INC-Ha@k0fKsX)GL0AqkgD{#jIfibO03V9qnp~j3;@r z#|5~B+v^2L0GZfE^0c<`Cn7Msr&m9cRZh0J`D7Zh_9qE)akPm=di@PuGV7CYtm+ek zmV75ulqP+YZ+Jn;qekz#@wDtfB=-*?aCzi=$Nr#6?l!CU(NOt<&t;JJ$rJ}~)gK{a zc5mDunF&*`wyrUa+l+mk9$6)bznlvL-5;W`d--0b6X(Y+^YKMqY17#^j@`|ll=8ZM z8h8y`k0K`zV^5{CEdbL2NTzBHn*n(#F7~!)?VryE~;vBN|diao?CO*|LBmQUx$b&T3*1WB|r)s;}C#C_VzkE)Ka6LHc{ z@0#kbeRz9Gc_{!8iA$o^S{b!%eQH|lRvGDzhNy(ZWW|$GG)X^S>IxxpnU&wgaKy7# zsMdEfi4G8ewx=KtSCrqadAIKKEPZg~_={jV{Zig=ok$=lBe8tWDdkraHW|$M{Z;~y zPfqg*Y-;5Li(+PZ$bw9!s^%C)2jj z<^)1|S<;^ki)?;q?6LOcn*fUB9r?L#B6)ibTAcstiarUgQ7Cv^Q(*IqI67!v4*B;SQ=) z&luSCQBB0ffJ0msE_ZQd6gSUy1U-3N7RO)x3Eh)(TX&kHY$e-{Vwe-ucF3A0OE3{O za;$cbm|{~`+p8TP$F5QJ17?cFN;@*$B(;m`ZF{`N*m{E(S`N3dtgKi1XUl{>PfBI> zX9di^$%JQ2Y(TNKK$Ax`=ZiD*%zw|P7dl!Pcd5nnU7MXS9Jj!o`f*yovO&rd{eg60 z@nR`wSp8zN9I3Cw$`2=OrX2Y?>(QKm=f(kZ14AtnLPgB*PtvA*1IW5iqiA#Rip^6B zswuO($hPhJv4J=D=n(WX4*erlAl!7nnn9(Gx=NerEQn1XQlxq$1f_ISwk6PQ*i`IQ zOQ6JY+~tdn{PtuKn-B1d&Acr9WDwMp$$!c2Z{uD|&@iwG_Zp_tz4+3p`=aK#Ngw1W zBI{{>gtIlMe$RW)3o1{SbvEa|AcI(!S|A3tUO6*i56jyw#QPo7T*rwS?W;9cu9xUq zpEy;55=J52`q=kvkT9=knG+&LhkD2lg4;M>s?-Cr*K>Rv=S|yi0YEHEveC|zgue^5%j{S;|VY; zxztX1`D(mj*D^lq*~?EOi3yO<`zf;?>uC@SvpebK-$qB!$)s5pLL~TFo@2USLh^W2 zYx$rpGlYfVWWo0qhmpGxtjq+4T>La8iPHgx-~ZiOZAWP}cQIh(2I5t$nJKHWsb8(| zLLuJMySfGM7nQO49PY^lxj0-EammRcy!JE#YZ8nk{-jup5Y*mC!!u+cZt8Iu*9~=p za+{<2c{@Srh>T{J|N2Ic2R#B%HiQU?;yP3^cF!G5FY7Zejw&maZ8fX5@ABM8QtFuC zzgo!e8;f-$2cPvuqvkZ`S(zo?{HDLSeYP~PNg9G=B4pbO3DTw8! z*M->kN9kIML1EKH_VL;QP@HgXFeTvbi2b>URCnv3}RYd!ex99tb8S>WW*1F{k`y1qGW zr2y@wxz9^n{dI2*KT30(=E`mE=!6KHng{mz2gFyMu)j;CP~W9%JyTeb+sFv`d3ZTB z;6v*~67hk)ubogCl1AWgA2i()V0b;?{`uev&&H@9JYl%yF{KgaiscO zY*Mlk5?tSvNgi$f*q8KbtnfwuioUxvVGJAJ0M2wnWR3>qp$4p7S<$kRQRQ`asReWp z7(5binkRQ{1G^EQgK3@J{a#jP=8M}6B{>qSbZ@uR&$m6gEst3locz|)eSJpXoyhwt zR`Jqg%sAey;(QMEv#p}CD6za!M#m;WS)=_yXurgIr<#fMA0s$6Xh>7|0z+ISpO$vM z=e2OTB>x07NuZ>06$C`EN$#qN&sA?TW#Z$dEnfCH`cNH%UB|RLlw=QL+8S;W1$OVaP8fGxh_3Cb6P&6vL8uxosS}1R?l$` zB-r>?BWYVj`@KoB?x3$nCHhAzvowTQ-lHs~M_LVWZ2G%h-rzviSfj-x2}8?zhAh4K zIDgln!O~gM^R8$~Ta##iqJ_M-qD=G5bTRWeZCqdm%MbHAC`N&@X>|11uC~FqB)z~^ zL~4U&)nsKaKcmt>HWbM~|0VB)a@$Y+DFsmdP`C9$8+K!-*r7@FWDc9rFSXaSF*|=b zf2tVrioQQ-(=2tF zTpvh`RG?%w`TmYoo=F>We9VG=-gO>zQinU!}pJeW`RV-M)huBvM^)y%C~$> zTC@fUP}NtWxax`lx}1;-6jYDR(Bv0P8GoXQ`Q*Vu+K|e9GnTp)%o7pmY2>ru3R!$_ z`(1khi<1Hpx#Z)Az*L`=_e(nwm3cPXB-YE4m43lKYn?x^4gTeJpfSA%dTRDWs6LC? zEMA{)78hYpD#$n)Ri&5LdFiFf`V-7KNbGHLZSJEWthBiX`Ex zITdF5%v#rhNy9V0Rm7v`wtkgq|7&r*=bihJ=P>kE9y5g{YB|aQqEiI53X95GJYhYm zW`~lo_t`=BottWxCoH|0mrezbmx`gJ5wZob1;$JOPy`Md5dlhS@V-lF^l4O2esm}? z&p0&Nf68`0opVNG!TnO?r zC!+`B*i0;}z_(apm`f|SGv+veLiWiFnzwCT_Vic2oX{U8>C;)43euErx58*LM`;qeF3ZBM3wdw)iyEk|Nxc8Z?aJ@ij(>NB zw8I`E2ugcvgniM+D|&Vm!Gjmx{}^sUs{c+y=G2m%B%a#jwM>?E#lX( z{c!?cqA4Lo3VaP>H)w>jP}yP6tGBqRRKVEycATVg3%3!rAj<8SItoLhHo4KSmY5M%I?N-Tr<$1C=?6aqvH zzqCT^9+JOu3s9GU9d+&>ETZz`#g;^D7^6~7Q=u)aSBr#M5sF=t6f$3FepqHlOZ&U@ zk@|~WzEZLxxV`7kztO4Lt3UFPJPn8)4cD4ikRG>YWSbz-d=f&RR;{L4HEzrPeu;T} zTG~kNxF1v)6*Wk$^|kATBc|326GEfN&-g+{7#&6!xapWUQdQpXe3JRtuxRax_A zc+sPUjhO%;L#qfp#NieD2-e}C7wW5_*O>=yrqOvYZ8!?A&hDFfSntx?{_XFk7ef2u zj9$DsG~(f!og5KA>%X>AmJlgbD^^DKw6{w*E#}u}0_$d-q15g@TzP?a)s>v2L~5GD zXs5G|avr(*1sDHtz$NdVq449TwPah`2KB^g^#1!+3KYAg@&%d>9Kr5W4$8P}dm(^J zdj9k+vcL> z&jb7APsHtqMY7hT7-|VuVHLmT#NSU9X&Dg3s~sv{ixvz1b%f-JHXd+H1pUaE$=2(r z!`^_0HZt{Z-H(<}^Uf<7#a?yQk+uBDMf7l}vgS!2gc(aS^2@zYMQmbAubvUBw?t;w z)kd2j5GnGg)3S5>#IuzE$o3c}=(cftm|tmwdFoPZL45Xf=STU}23f~V$_{OAvn+cm zv?JY~TDn^{LkO){cGz)QI|-DQyUr2#le)Zqay6JTHtn(dA=#xEpFFiZ(izxf^%`V) z=^k_|_A4&IfKB>(`VdRzj)+X*xz8UsTD?4jH8qQ0(wh@iej{5 zzL8lyDIXs04Y{`CIQMV|Kjuez8vhu$rLDc^oj*3{TB`bQJ2H$LKTc zq0K>`Y1_7Z*mCb%@qiT5vnCBdBfaWA?eDy59bBLxwQeMj^pRGwr{S1xe^`pBjVV)1 zDxDyq>nQ5vIM04UjYZO?{Oh?VeN>XDm15XkK`kEvCFLn=a-K!3rC}84jHxHAramb! zaC8{(l0R0x@97sgJEX+g{75#vE1N~k%HOw}E#!^^-jMfJG1S$>SYy$pN(zqepzj#*%BRjRNTq_rk2?0zK{6!W`q;5>gp{n zM2@s^x7H9*Kb(&&fV$O;)VenMS`0k`7ySgKG_wbkb3NPaxq3gVY2BluR7Bld&yzjWEO9L9uLC=jwuyC z2`>H2fpW85xH-y>;&RGOvu8N3&_$AKv>e!h5HtAC9;%=BV_pO~f=|M6OQ{q0Q<~t8 z=i7AY4C>RD9oTC578VxY%}rd0AwPNg%(|XbZ`#Nhlx((g@q)` z_kyWq>;`fk!_0`u1_|Ns#%2pHE|O^|hR`Zacd>KyS`H4kMRZAD4J9CTJPk|zkcr3-sU@G_!_ zMsKkczVOV8j9$xXy!Ai*a9QqvSgW4oHcv6eeK?VyqRisIK9S}c@62HE$P3&jzY_Rr zc6^$wyq3gB^ZsVg2`$)^e#UM~aQbvL&bReiy zkCXe-^$#(PQ>aMB*|r+9!7u*%Y>Nb!kZ*YMeMvyb_wk1;v`a*Cf}VD7a0AORh?NP~QNb{vgtBN60fq#;d^-z~@mT8flnJ_i*Ze765$& z*WO4N2VVV{cbH!`#s88QbK(K-Oc*Ci2WpudMD**oS||xheNXaS%N=M}Lki^bNq4cEa@bNe{1!acf9l+fECllAa<~U2RsH) zTsxhCc&Ghbeli*B9}>j63CpNvFoVR#ePiUU;6ojCG^ATgbB=i>&q&9gm9vjSfU>ga0?x{uAm8rtY}_(sVf@W;R@sBc-7YWkC}i z)%s-^k*p8!rK(VwhXOg$y%AEjCn}J(@61{PQ1RMG7?+_yr)16V@{{Y~D|!fy$ndh< zbIs7|(}UUlA7-{sb0gnjgv8R43~MXZLk5B%Pw=5vwO-P)A2XVkziMmF6jQn|Ln5MWrtvL@>1AZ>@QT{i&sOQ>z z7{rB88Y3$*rpig&Pc>-XeHaahKiJ(!+lpUH8PjhN>0KaA#~f{1nuo(bM2IXzF&6;Q zvE*|>Jcz9l?K(Jh9>Ru2HkCr$RTf)N;Ce87xRUDha<-Zt4@JSuaz9FmJR(I>YIM~) zX4#KCdW(>eq4o=o~e-4JD-9y zRkWYX2tF982cn$3(Ns6Cj&t6TM+Uq!9yC|lnAG)PDpbV&&=e;;Zc}PTWim;a9tDQH zY~FY!l~191^vdL78}A;y0ODn-W(GMcEp(ikr2#98MG=;9&(k=NblI47V40UoA8r1? z4n>A(Rg0*@zDd0KxcxLGLSo=6d@6RnO)TU|RN z?5}&_k56@+MWJN%mFxHZw?Tc5BYq>elm#vB(WidtzXtA!8%z}i%uP%vA$fa)o(u9! zq}s4hT7a7>rz*MvJmpbv-_X;;Zpa|?IWS2CtTUkq`cW6gr;4uRXs8RzIpLp-${{{etfIbEWtprD;oJRw%|T=+%>s}% z&#p>%PWFP`#XKQ7Y;WL{xdz9AxN1OD^NyuZ!t6*{8+(p@z@ zbB{CsP`pypz@!KtP?w~a81`K2d96Tb5vqg)v#n_0#u*OufDjWl4BMpjt;y7yPq*(l zNH189jfJ&*k70kR^PXN1mKuNwR!7Q5k?N-cv))Cn)t@Ymv5~HZuD<{HFlr;4cq79Z zIHNhO$xmN{?_(V(61=wGO=S&$#l1Q_CRT!(9N?MpR$yrRb-M0L8T^9yE@f)_zw_PU->GiVIULq-*51;89zRjmxLa_kM1QU2_0yf6 zsb&JBjm=JMX;u~v8)4vX4LcrI0iq|6iR$YwqUBK)qkw`Ih1vp+1yy|mC1D|Cp0P)C zTrNEUmH9|@QyQYtv+762Q1e7qX%*6n?4rjtFT@sE4Q}dTGt3AkaAR(^I2%{znEX$9 zpX{~Php%-rF*&cbL8w>Y4$Ro$v8=bSlVeQ(I4-0rctWcE3N8Jmo|$!uy~*D4W@Ux)-9=ssrNKdTV;&164USa7v|LC>$xyjXLuCf-}LH1ws? zG2Lsv7)9oWcq2+7gyOf=X3MiG7)+n+O2;AJyI7bxK zbIIAx?})!Qwvjl`O%weyVmUy%uF)cGglmfn+Jkh&b}ZTB&1bD0^SXIo_L=w9CX@dK z`aSzq-P9BQT2I^9ZdkisVV6s#h(s7&P`F#&Yo_ZDK(<0WuDEwNcetH$G~6MgW*TE9 zZtwC$g3IWeK9*{l9E&g6<~Awwg28e1mF#CMb<8uLwK67tao)i#+Iy_(=i}n7G>W^e zCjbA;Ozhg$81);vS9`A&iH>&0l{m_>^(6eR1-lv{q8pg|HdnSbA{~TAV-L6&qR35Z zKTDTYmF109PpG(kSsN)a_4J9}+uY49>e5ar>dHx*^VbiY>F=9OkLEtwRXHjC**x!K z(_oC4`cRLuT|-pK_Q?h)rr-q`pKZmH*rE|fUvT;<5Bu)csy1LS#@kAVkdE9!R-`BI zz@#>*8skW(a-5U6Vu(I@qEOGG)l@P%6rn0CJ*-w?-tFa`aoKdnh(>9VDu%_aF+vKB z5_=McG%97)p*WN)Bc%qt)K7MK9(!2U?8_51*kx zzBy>m4oBO&Dp@{Rpdrw>M}GqUkHyseK7*HO+R!RzE^y-kmr1|X|B=EYF#vU|?Z`~^ zpNszqLvz59**{|#t>NR~`9FlXQ2x?w+-qV7^V4Jf&-%3420JJV@i3Tj3(zDmp=rpr|K@;F)X34A*EvKfmXiG87TBuEB)Rgm zFGbaAZzN7<{PSO*kJIO2Y6X4Tsb*=c0`1EIoENSlvJpSs!pk&gc~Xy!8Z{RV&z7-B z4kVFQy05JC<+LqiZD$J7A}SPu#@xp*=^(FDZ#ej%5>yipmbGizDugVL=>X)5si=;o zWQ+wY3{7B^A6@r1QMQC-@pduulNt10Z*>HnW#>pA3MsTNbMi@jZKXj#-p1F{cmO9j zOBxK&2~20%&-X@b^x2IOzc3;Eh=GyMl4v`+IwB3pxUmVdFW5sAKUE&+t_(GeugP;d zixVOD8UNAd7=7rH*s!Nyn&#_RP=$mjgFOdiAgInzNU+2@6RjfvhZCLaKZkieO)}W9%rw+9Fe$ z(8hZ0ZE&_OWeI(AI84Q+56J;e$KV>yMXqe-%|?~I?BG2cx`A_4^%kybu39a;NXORl zyQwQxF*nqzFPR1uU)c^=AabOh7qapm`N4%v-7bq3-&0gxJ3vp|2ko^kc)QnM_!FWa zGj>$9^ipDC<6vrKm-ZNSVAC^$&w`MzrHC*Ek7l9Y6Jc7$!w9}#o=4mQZMu!r6O~pn z#-HPewN@3}wDn6sXuqu~;HHwg>|9r-QIlzFfwDeV>(rGpUN48cnNQ!aCFJmkq!@8IMe_ddOt1! zr6hJ^1r0wOMNcG^YmKi1m(9jTm?uk2mFbQSZ8m3kO=5}#KI~WuHY36^g|?32HYSM3 zCB+Nz?`7J^C1u^AxU%QKhO_X4AHJ4dTzvR5>G)8fa;*-uJg)EXy=%v!O2R=?JQLf+Gn zFkYwQ>(dZJR*!58r4*BwJ=;qe{OLM#OIj_QrI;)s2oE?=>y66lFd{~k-GGm|5LJ3V zPlqwkxTs0H0r#~x_?iA)NA?Jwy;}T6m*uVqE+9-gvQ}uJb)>YUzy5%T&XoLs)Hy&s zDn0o0b({pb64-Ih6~4T!Ciuiy7?2js0v+GEuoTvJe?S_~GN-B0wc`P}!p0je@gN#N zg@bM2yF74LE?j(=5?SCzp2?8gq|<2#|J$A=^u<4vK*MU#9$jGiUEj*aLLhug1^WN{ zp@n$|echpXZ@aeFM!K^-?i#JyAxoZ3CGo^so^ii`0C`uQGQA@=(@nx)Cz%p`JM)!t znW*8uCF)H#hn}>JK1Jk!olSN(qNV;0q8J@lV+FX4)qm;ouC$+aFTkgB z69oC;Z5ZPq`~TY5In_rh%2(vdiZG5e>Y(phCKVfZ^w}w&l@~3`9BO|Piwu)7qU=k! ztcHgY%a&EI15;>QRz{jO6e{2};UNfRE&Za3a_Ty6aA(=&vn=^$L)GN0Ij`)aBWMhH zr1h|T0}A|*D#;+zM4yj-$RkHZGHiUeKbZoFV{sj0W0-mg9qygH{I$HZ%+T?WJmMVb zG%5LepRL^l#_>1Qv#8e05o5L)DEpD;3{bqW#QAd!_1O5XN%MDPiH8cHRB=ZP9dPv) z<^QF3-MwYG-2yDjoMe#nDyRI9x}~M8eT}9VUs$Y6=~pU*Dsg# zu8A1_=A&V!!#?p4370jP+HN5^a75hz9<~k1OkrUfj0quLwgv|zMK9YawgB>v1CO$OSRT0< z_+ATis@u6umdsSfJP;P5N=H;u8tVRP<%Ctx3^hDReoI6+E|RvE;%E33-J5p?5ESe7 zTdo+c9uW8A=A~_k2GAQm-I9Dp>i}4C)o)~^Kz!NKY$^R9N-~4~)^wyPisUz7*kH=qA^j+@m1;n&okZ4e1DxJ<7xlMf-@fsPKl6QM8+hp-ObE40I?G8f@4ba8IFk zA^}YZ*!+)olbv^iOU>iAU#5KZ&$}~@OmyC+iWT>skClD;BYSF7{64?W`2#9$nU(#q zWMoz{1LPJ$xpxAK7F<<vxILJA;^H}GGeO^pCp($i0!M5L?CJ)C5Z%|)p{t?@GyU?{Yqsu*Ma#uj(LVBpr+KCC(&;(1ibeNQ z@zfAvEy=3uXvFQ>1U94)qnOlRV@~a*nxj3=SsE0YT29>&&VF@Isz-4J0M?k-jG|OW+1erDc~_>A{9(YNa#U zPaN0^TXs6805`6>TnXiGz;Oh*Ex6Z#!xAgAl-)ec#LsVNAk;$)uT;d*OBxl!O=nwZ zF3U!H_3g(#`bNJeP(kxVQt0<4(6BkuT=Tn*2nB1kq9jR~4lgM-56=DX?!FSop>D%f zyRJ4;z`NS*a@*-4)YCL(5_i=LzIohoIky)KsV)Ahn&qei&=pK%T5P`i`>`khkhEy} z`pV-FZ#~sT9sX~Ec!me?`Ey_P_dD=!FC`9Hn16cHkuLq!gC1A7EST0ic)!EFi{2{m zj-T*P-;;XD-p01HL2`yK>=+Qw)cPdRz|^=K)4_dPRoID+Vj`w{ zIv1={!5ZqvA!#D42fNVW&pIP~z;w;IovLEM@p{7Ah~+VFzKIl-pyNHtq9Q6r@7hhD z#^!jj!7c0xY~Ste$}NrFqK8z1?1uE~Lz@e&q(#C1fCe;)yQc3r?#gpyeFTzAJsB7f z%FjhELJ${+rX`0`(pFbaD!^M>EQe9wdv|{DzprgH&?QF*K*w+ImfFt_*|Wuf4tWY0>e4$$=oH>OS8+F* zRS4lb*VE7AJ%uaAcE>){K!D(X%T}~0E;iohDOq}mH@Mb`BhButVYme< z;Ngpr)4-Yu6De7}_jOwea#0w2k5dB1HxV@h?1%P_E2Y2Il5rK$-n_Fum%ICh3zOdn zS*M61em=DW>%7@^v+66e8Y6`k_ zzL>JgoywS-diqU5v66!bnPzk>1QbYS7ds^$1$xFS>};M084+l`nrK~WY#7>{`7IRg z4wGKanSUN}vVVO7Fw|=fC!h=b{UTldm^ObV#K={Fd6p(Ynb!DI>Z#Bxy|DHIWh)5n=2%qA z@X&I6yUOw5?uiXpqsj)N0$h!uE79jYYm!BA2e21cJ3zurXc;|#+fW(fW6s=&%NJVh z6Ei$DcP`3kQ9CmM>?5IC-Pe|4SNRU!V=2ysfe1#b{TeP-`X|SS&i$Wz`G+o>ll&XI zm|baeDJC&aQXDT9``$r~AZE8oHpxm*dCcpy-8SN9jr(1U7*WTtggP%d^8G}!DB^$c z3WgYU`8OG|I{$~NWvv%M3zk016RkI{ovq7JQmEUTz1!e-u`kdDZC8J+WT|Q=O~dnn z{jPh>r&0D7V`V-xX}dBftrmeb=3KrSgDgcd?t@VY{LWoboF`xIv!AG@t8eqAI4XfR zm$y$u90`YH`g(J0_T_aO^rxtM&CZ0JwEjFxPT)d)_dyOUiv?_2cpFTz3?xjbh=13X zc*4ROBJO>0w9$5?4rJe#Cs0Hr$qc7V`h_9M7{w{+iqG+l@H z$;H|~6DYeMsu4(~4q}TxeARz^8M{P2j^+^^_%M0 zkng9>d*0ZOjdgpLSzcRy3Gx)&O03?n(YPT&?0rT*&Aq!lTnBpX)Ai6yY4Xz}Y1lH} zJwpK(++BB00TXQ;CApP-gf ze%b9Z5M;Hpxr@JM$1#pf~~!A65T4jsgmKBg;9&o$7>?uK8?Fz3OK8ucqCoKD#h#e1^tnn3%cf z+W-UoQL#Xd6Gc1!=Hm0%P!RhhSCeO`FDCp=7;Q1q@ z*PRC+q%LgZMP>9qYuGBG$!p*SKMa@54srVEC=4%9O{R-Cy+aZaS5KfzTgLD&TJSMp8e+*R6CHAW#(P@N z`y9Js{yO>`rfi642J7RyU29SzQm``&Y^0+VDFn#Q;rzgnD^LWv5znc%??U9eedB z?jOSxQ8kJaIKgmMjYJ7lVjcXOj4L*u*vw8O{FU72GD|Mf3QZwMwOY)qsm4bV$HexB zGjC__b+E5x5c4Frb4-sOLr(&0x=47wib|dXc8R|a9df$#JI*+Dlpmh$S0KK*@~4)k z0yPv+7AvEo}84XQZ3?~b%pZ)?9kMCOSH0DVl! zbKR0EEtEvmKL<8nG{iU0-S-wX{IJrZ^t9xuWRy;p!q#~H#XGOQgTkWHupK)c75u30 zltNi1B?Kx!_^%0`ML&A~Dy~Q_-0ju69F)P6k+Ds4-Ko(r){;9JpJ5vfG8U`akB#kq zJeZ&W19|;f002&o`}nsJFKe#-#1rI^Mr#k9H#wza9CVH zz&(o9&T37!ups-ZyF4p18=-x4TnYcO;bi%gJXO*kHfv-_W@Rqbn=7?Ozc65@Y+AdD z)<|7?CO)Yy@TwpAS8O14E6iv9CS@CHMJ+ceA}yjKz2IMC{{is$?-ml z;l|(X^+Q)AteWZb@#4!t4Z3#`Y6DD#b@#mAwu2{S)qeE{!BaxDNX_5y1gvjUM_KPK zdCaxZA~lLig({9{9Oy~QvQ~;vG{`M$T@5VUXsEF5q+WaGCA=j{XBb4wM4yGZqhYdl zBuI{kYKM*z>I7nF734;v&$Ig(LeTkVSBC@9{Q#?9cjoJjQV`#-yh`GoFrHE(-;0s- zC0i|!rPC;_qs!JyLUP4Y2B`2gUr7H3jkm-H*=dDK_&qki5|Mr;?@Z{o-~`D#Q9b$f zjx<&|kW&ygEM-(sx-Lcx5i`edymb^9lU)HJ7};IYe7PL!q_*Sin@v4SChAl@?nfq? zaYPBAkc!mrPh_Rqy(VEjo??8?1tmcOTT$e55#3)wl8)FXYU&IP6dW_75vjH!sd0|x ziL5m?alqS?I;>bq%cT8?S{Birp=+-A#umXU{G#~Nh(KKH)ntDtL)&F~HRa&ee z$75hdd3yP&Ztk4Iu_!9X$TcQjxN`{Wq!(au^cG3U9`&&0qk8Ro`i8}?P;<~LlL>>N z6FlsZll9^wr`jJ?$i6k<`1hg*4~jU?B;ntl7Mop0bZQi97N_n8i^+!tn9n8Gn863> z34PC36O@#sy8dEi+X9$U7FjBC#@%OpIGOm}*vg~G*YsvKx;Bu;3;%8-X7rVlk4?;p zB1S!)lQw4K`4FU0td2|`ez-f5dlskFbJe7ZG9(^=blkiLs*j_X?b%WIwN%gunK&5- z=i6FlPr}`Lv$M%pghBRV)10%GT912Es%Ft*Tt?=Nl^m0$auSvd>m!u&cTZk2MMP!! zJ9W7gk4p@jOiR*W;%jwY>GdNMb;?6X&BD&TcOY2unf}&-v|?|2@rWFp0nvBbgW|Jyp|bW!2yJU)*8aaK;ii8L3sy5T89te_0P2SWK}Dn!*L~Hu!O< zZefoL?&K;+@~U+@7wcs|?&9mkZhfb@jGKn(t|i(`ss9Ar+k0PScE!90SAvZ;DR*5+ zFpdtY;E_)Eyb#UZh%sYlZXDNNZ@D{q;cpNBp)L=On$JshR-P>nCY8brrI%Hgn5)zV zj_mT~^6jcUn>XetsXaewS7MogH^|*?tjgbQhW*+i$1j(H55XRG50sbR_f6(aJM4Ab zRp;;A9)oK|{e7;(O+BNpeIdCNP8A~O{<_64~v(I?#a#{O{zKx8u>3H@ns$vy7 zyu+WsZZ-NR`G~CcVMQ7J@&CiyTSrB?wqf520xG23g` zgh73lJ{;qroz|-U-_b|>c<^F~%Ok|NcdLBCL7j=bM!rG16M?e4MOe~jP;NzIbwB>6 zm2?^yhE(sHHLwSs{uLpd5R%vUcHUC2mai>=$xfoxk^8Qi{&G0YZRVMbNwY*UlZ|9dokrg>vyl0T%^3L3+)K0p36*>K z5DL)Y`wYa+hQj8*()5aN(e|+zfi*v{)7YA11lW#)AAkTWP04oIW(Z9phu4s(NQdpo zYATcEx}=|f)J^^aedCnZJT{6Eh^^f$l^*ZsCuwOx2xyZoL~%OC*#97=?rs!n)J8+K z5(Z-TZ8&F+UUES~wR09i-cN|lI+`p(NfzCUtAg{Fo;P_u}x~}m1Awk<#=k~St3N&J@+Tj zCyX=0*nK%?vgW*T`$GKRQ_L~!dV(W_Bl(Y`EBQhvl-u1VcPc724YmT-#VZx`5ks-r zDNs3DR#_mTFWzxhG1})UM2uTPuZGv13daxTO}O&WO2s*1op7LLUb1!A<9%$_ILAd@ zJjQk;SThJID6|QA-IZ5hswtk&FtN~-;V$b%hM(K6h@q|0n)>pbU^nX6A$!KPWD$@I zDYW@z@q<)N507fHxJ3spsik^MB;!nq%?chLGEWe+|Xj!r@RrWz#YHR5{qF zH+I#XN!-bBe5k*09|+h8Rr=O6eGmzUnk5R`%tbB)KgUl36W?WJ`H+QJtKXu^ylf$= z!>`?z9Dd&YvuhiwI7L3~Vw}o5a4e(HF+CRDeow9{qA5=!IMmjCC^g}!ntienBm7%b zo2ezwlYaGtCS2+U$${2g`mhmyv1m#$xj0*aB$7$Z1!^N2f)3Fjxv8A6;&I(}w{gJ* zy`#TK1rimU7;!O*0+?1gIU1o)`d$#rwe(H}>}~58dz!=Kn->+-;KHIT8Vk;P03)Eh z@~O&bX?@Q{CZ&*3v52+p?ZFHY#>Olv(|LY_T@zjPC`%Y|YE%-q_)%SCi!6HnQ;|{e z&yM-X3b`PImQ(Z(8+F~gblQn5T<)!=pJTjL%E#eEVnkus9=kT*c0c&ryolhDq)TM% z|9&gX*~O+Yb^cclHszWR!IDgtv|r^n<%Dc%&2@6(64#bU3?j7ok370Bva1h9=s zx@A0aTV?!{bB&t_b5j5pJ(emk%HO3aGH4=6f-~aU3A#Aj?%tvO%WoeBWmKYv3E35s4Zm}E({)iqgCET8wqro- z+IOd)>s3krJzJ$FAQl^0XE@@nrbUhZFvDEQN!pXVI>Q`2nWq}tT@}O#LM!_Mf=_cE z*6hMNYdQhuF{)hdLY(*0GIW@xaTUbtU> z=Xc`V2Mg6c?`U+t5)0pU8TC3a%wSDYBDV|fb-_z`c!;RXp|A)^4nV(>JNxVl=5@C1 zo`1;B^$MCXgs-Mw?EXRf?38wKVfD{09Pirbd9#!s`>UeqonW$wIKKO#Z2(B_oC<&ztXu>v_HE+Y(L3v*f4 zw_#shu_MK&$OM0DGf`T>vt(9CE(|K^xO`>oA@U@pkSaOhY9VKDu|sZ_L2lNoX?&Cw;DrUdHB6igGKmewwb zAt9WOJX(UnFm`uFri`?znX*ygy;I|9LU{+nKKIur zVR!Tz@yvuFMEmX!hN@dK9gD@Qk{nkw8x z4nK8W8lS9U6^l##&zXFgR>oOXxybR=3QQ~)`fER(y+ZnMP+AiHv$n^T>`3O4siyDR z+Ov+kKH`b`d0X>Liea3jZoS;j!P>vKGIYa6Y}&zkao*eU>{Vb>xz|GhtF|=c3->7%7P2pK z)PMa}UrC(^N~1Lj)WR`RfJ1E*ryFsk4!L7>-zLL=C-Hll> z=;6cU?no6EmXeMSR4$xdRo;Z5oF6T^)$eI*+ey?kWrgz*sk;abJp&B|?tY31L$4sQ zkdo7jN_eId#6OK};WQP)zozeoQQk2kG4rK$7<=LMDzwBYTA)P)!Gx1w+d_>#0-wOsM(Lskk<|zezXXLUYmk&Jnb$;XpJLV|Wb((Y^Tr0uKo7Wo(D(SUVgI()Qo1%N>oiIiOX z#NX%U5*0P7X4l-r@t|{|Jd%MU`7P^7mK|r5pRB`MAMW+1?XOtA0r-vXw{9|FS=@TO zByO9LDR_554v=I(M*jF6^Hwz0E(e}!@#T_Ay5P9DZ8pM|H#X^oh+@g4EAHmIda{lh z=P*}}^PHdXO#=aoH-DYrzd2PMoNqX+3rFFsj1)B9S1f^%TCu1y6>&-w0X7-as;yxT zAkaL|5;!`>8(o+ND3uC+W6Xsoa4n_+W|}69s_D$e;0vt`0} z{BK>>(F)P~EF9pyuQPY$FXqLG@}Idx1yn_;njeH10#bd5{_n)uGRzoy`iQi|dSW2H zz)}oXbW}UgbIilf0#U!Cyo0Kt;>!=*PmB17W98X4*P(u*K!hY8Fg6h=@#mu*A3n}D zpU{GN?p2sc>pjVWkvcbj7X@ySmX79Qfgq!eZx(^5{JI$F5hngqM;mi=1>m?J#vPg* zQmT9qz2C}YRd$x8YAV`>aTt@vmvVhk$T%VsXVa#kEVk&INue&{+(`n~##`#UE|_;? z9qtT5)U8|i^iA?o|33V-k5F#QsrscR{d_v#rJlwA%HXzxQJdLMVb8MDPug~U?MS3! z$tUB$H4ei)-av?-Go;TTa3=7ouamche)_SrUmXgsW+13Rffd}Qo1tooe2D1w&2gu^ z{A*+3aZG8c@K_65dJ#-J z%ccf+W}l@0KB zH!N@Jvg&4#z-;^qYTE7}G}`J0+3G>RD^k`a#j+s?KDyWiztl9mWGTNzzdce|$1UT! zo?{a!v}NQufp|^I{plau5|nIpgu2(}SC(AtHyQ`%wB8=}{L@g;*E(HK|M><*>ObT~ z%q4%_*eY}?;A6G^tS{+Oix&U2Dygsy8Yl6Y|8iNcEpn_)MkOF$r~EmT12|OJ+!3A< zA@v?!(oml%JYu{mBJ_`m>W~lr@SSnrhML8b8`mN!{^0-pe?m8C9tN4janxoaX6rfG z55VI5=QoumTea_x=ZM&2w*&xkP}Y~Tslt>|4C~-=Vg>wlc~o&D(r)xcW5j*{ zqWYmE`;G|N98dY5Pk)Yo_%kd`refed|9veYGN}-HN!`ZJ&g0jiU|u0ReHNWtVOVxa zOA1DFjSr+lj6biCc!r(_Sb#9D`U#?pOtGSae6Jy55!63Gp1l`9~o+whow78PuuaWU{`kg z4t!m{^`lrJXCJIbft{I~U&NDba7A*AXCq(5{7r~Btr0z1p3dgwxM4xX{9Tu{0>xx_ zA0N6aMu&*Hpi=#$+iiyAYIgRDY>W;fTxY*g&_cZ&?71K2=_8UQN0`S^YawWRec2Mv zQb^&33vH+@(w)}bS=*PEcQKr<;)r#HXIodXnx_CCTwJaGLq%wVX8>H5nxx{f{c{(V zMWW^N(|$ev;i0I8#l!Z5^sApjhBF1z4rP7K@6#oHrBdMhy*&P6or$O02^Nq1I7;4B z&(&-+dn_00xUXdv`v@&qgIrB0tQuxuN%hM-U1I`HzQ&=mg4K>jpskB19yluu#?&9((VL3bo;=uV63d^e*$T(xATUBSyF8pm|S!MvV?HSzkI+iE}mz88(XL|5`vw9RtP7`s4B>9gU> zpW`+~7gDYXQm#jfn!77aH@7q_M@y*dNN%ry$0d8cg^3gQ=m9|yRpL~N*#wcvy=iLm(XT>md8sB zkeQ=%j~)+=XzN%uUDd-A&x96QGy1K}Er+5Q3n?LUl(@x~us_oB_F2F4hWP)EFMJzK z%eu!@0)>bWTVT)Bwhp`RCsLn|mr{4H7~LYm{~Zn6y6ZU7uFw%;5FBay+k%;ACbeC} zqV;QWBd2yr657LC4fWVnmeB5%H4xeJ7mXDEXVBIueVIb7X7Ky6J&YJwbe=qGFr8k5 zsd>?U=HXv4x1Zm0=X4#Lyp1 z@BXyEvlVCdbJEvP1SfD2qAYhz2&{3aan^YLoH_~o!>;fO0(fLdWM%1&CklTn^vhIK zQI-b#1||NW)GEuL7b@W&ni}_&A2J?IvT&N+%p#pwhlFQ$;m`kEZ8I-X5khuvdlFHj zm869M?PU8)3_!aiF$g`6!Woxor*cR>+g8bf2=Hs@mQ=hB5zIY*|$##?wIMw2SN`#TgI+S7?T1M^ZsCu0IB50>;oh?5tOv&oAN5;Y=#5%)o} z{!+ffYlkv+*LrENQ2&2frz~WhwN_`{aow&)|I$qlHEw*z*79&G=RdwdaUh0$EDJ2auJL2w<{MFEtpml)>npu) zRPAe0hDa{Yp(H0imR{QxI@~qDCL_S7@gwG@{9E+ttzz0K+d|bA#)E~Jx+bnHZcF|K zViC~T!(WPfRV4@VR!1u{-=>n;1`o(Ose2RqZ2u{0D$3r;v0L1xonNIti~{|q(40^c zk>*K5UJgmoJZCJ!A*;9r_d$OD_W_f?`A9kGGRdSYq+9M3=F)YQ2>0Fmj07>msKR#Lt&l2OboHV_7ZoAs>xTxRywv)v(PHDM6zc zEq$tZ;_nCLiQDl1DT!cNIA@nq3W!PL=NrDOFZ%){1p;|wY1TjArkM{#>drL|zT5g{ zKaznDy2eVs-c1W?21}00_}Wv%uSaStv@hbqXwz1zom77)(5=&`8?pU8ivSbJW#`!h zN;}WxYLVAA?O>&!@L;+3p1tK{gmaWadNWpfIJxB^;3U^u{=j7X3q(BJUe(Ied+^J%l#vPnRDqg!WUBc|7y#!tv3s~HEseoSpFQOlHZm0Hl&rzCs?rj?wD!brv1~nWf*cd12rxn{ z$MS8C09~rXwYuPyCoBctoRpP9Yn0H72e^MOIuf^N0G>oUKr2I(S8D1&1GeUPLrQtaJbi=yXhtNz@^Dx$P}cVPY*@ObFM`3>h5t*#2~ZP`0XRZQjw;PW z;SQk=?;(-?NqtU+i4|7)(}rt&>`nA7#)fmF1m6l3b*6m+5Ll3)c*ucP1qHGE$@%wK zrdv|lsK1e)0Fe$hG!F@$H{!rquTD{<6U0y+BaEtDi3N6`N0hJTBIG5Jnf}CK z{l|{fN{W@pkX)x3!wAk+L%al%cA*YkLEVx<8k(wy#&HHxe%N?dyCd@ce0^$V2sZzj zr-|3cxcL8FbI9Qzx&b2e*!R;%N_~TC8A|<~j)UKxa)~q?Fc4Z6qu*rByjwVu^@xJT z5O(ZT=}_p6EPI}y8k(otLxMu-IyO*A=@!OXgE`*xR)QOeUyPY&US_@zbqfG8*#Ayw ze?Ng%yHS>SPKvKW;~o|X4GHd7JmmJ89l9m! z#O-XCqmmaWG3VOuKw|AK{qpnC@@j%{yvJCqKo$BikMdoUI1W7z6(>1z2PSuh&@(r-&&M z%)R7U-;9Mh`%v_OlO_D-%dfC;HX@Xy*}f!O)D{Tk-d9@S%Oj_57q#8~=?fHcJml#H zqORPtV->c!?%#cw%&_Prnkq_Ph2+>`6}b?WUw|T7it~Ah`G30j@%eL07wL+$=_GQ| z&lB?Jf7OGX;HiP6MwaVr=WH#hS<~@)h$|I@Mz$iCcE>byefnY(U0Wa8j?8Hk$&TG1 z#dU2fUN+3?w)bz=Ta2J2oMzOVLl?4D`X@l!y7u3Lo$np6wWl6jRU|S(2Zc4Ogv_M} zs=SM02n|T$yXwv4JUYKiOJ;#+%7o^hcK%#ppuPLTfVUt7YD+V9k|y)($+cS^+y5?V zlgL(&`>7VT1w7oV=aJ`R)1>_-V^*!fdU4V1aos1yP(CN<_%j#yLAX~1;rFZ(xmFTz>g+Ebi zekaJg{BzKu)g!6gI(>O@2HeDA`_^y<|Ge?9%|A3v_4OpFh-SJP6IwKYiHZQu_;0zj zT?rH-Nn|q~Ne;~!S;_ZRWRvT@_3?d=J~;I48?KXygDF6uHl;ESWaID_LTot0zsDsP zztpy{R~GG-e6KMP(++&KY~$PiQ^qBi0`J>DoGj065;fj9c@%&=OWVjPf%~A&k%m?zDx$J5tV>brM1#)Yws~I zrs3~o{S#Wfve(ffadbY6%|8nUn>SSQMG$9Zh}3Q9E~U*_-xgJE`4g^dnLObe>)cT| zzEX_g>m^g&WCJQKlC0P;Z414GKz8n#B06VXl1FjR|E<@6icJ`Qk&VKC2LIG&7GCQ&HJd|a@7q6h-2~ROK2VC)w&hdu{_nd1Up|SA=i{HMvGQe= z>XRMZott+vVr}FI*gHr)*gUQYSm20=)2$Wz^b7=h5S_)BuJMqVLJ9DAO6R8hOTm96 zaRM|8_@_s}{}&?ulk=$drX&17Y1WcA6lO(VbEoIc9~&D_cfOwfAc_K!>{!kow36tp z9}KJIL`DnORr@1k<3Nna;Yxn%gew zBBpCAT!ALYI^~+TRguc=xU?-mCS^5p*?etnjh5P#@)Ddp4Dr%XMtu@k>Y2~!Yi)j1 zaKAVAPkJnjlWWrIacP;AVQOHvbv*ZTPxHEG8b?qH+VLyBl;Zj^(DBnFhq{7}VAFAs z6NC$OKvpLD*0f~8LKup&;49!PF*giz6?|;2&zs>2<9X;sCfqN4qC;?Y;m=r znEe*b+MtKIzDsMv^`!9U^q{4tUDK#4D*Ton7mHOO50}E~{3E~f}dCH%G7vEUJA_`ReIe};r;6;$fuTraf zrl$AceE;9PmkmTao?It=d;|5hDr*0+N=;y)JH5(_PcLA&eR6{@O#hr80=2Niou?8c_CHEVi5QA1}~&<-3R+$;9K zD`*B4DY?>vyL)^dR|Ti%D>=QFKd3m2lB>|?K(QiX6ebsfBzx~Bq`v?jcf+XrCO35a zo%;k6@=qxAW{r3_Yi|FliPhlpFa8*pTb$SU&OuRo{3d@LR&DX(JK$xL36oOqe-B$~ zA}fe;{q`XZ#e8t+YC4)q;U7VZS=@-a7g25Tz;7{1h1FH0 z1SMacJjTk;Z8uIUwPs{H7eT;vgA?XDx-Km{8q0nGY%GuT>7nkgwY?Jcx46xpq{U_W zG4&~oEAN9#TUty=UURBP@#AyYUzMlwuFOX^Ku!`!K|NcO>0j%mF>gx^E7+E~^39VZhf#O^V#aIn=LSg0fhz|E87`SD5aJ27 zD*Yw~=YN)V=63AGc5<(Sw{Tqa=~?ac&9D}~v`u2w1@)azK)tO5x4QAAY}LSvFmV!P z1x>P9gL~o_CLR7l-)Ux#z=Z>7vAbK{P1Y){JQ0%!>@D9AbZh`Rc$>;s-pk4R$`3u> zV{<@0CYPRWx3c9$EPkBs6rdirSr&I3o1r4jIOU!QZteUMA~(q`xccx2nn#1u;2>Cq z{TAE=E>UcF+{rIRHtN>`IQPyHUaK=CE9>i>RFj@Qn{kG~M6>T#eJvs=sNHL1%%)wM z8mf5cx9KZBdRtcbE#2DOTH2|q&dAvflQUFs{_Cy}T;bu$7ELAWQS?4-mc^vO5x?dd zJ&mcsdqw2v5x!zc%rP%~O=f9At>+PGoBQi>tGQ)E`gN^M7G=iG3Kfdl=KD1CmL$V% zZ-1MaNL7y3aC;uB&mpr?wrg(3)Ia@^+OP{3&5W@O=}?HTo|uFFT9JrA_UVt#ADlT` z?2!+B8hddx9^#z9MvgfAn7=`dC*rjIdl#T^I?5XKIOd9T11YH_3Q8gK-McdN^Z80K z`X}&RK~qZi@AOuDyvZbzhV~rI4O08R$O8LZ?a=aB; znAYTPB|(v_V!FpP_JU69Bdy79Kp~jxwky>eMHwNAJ7^$KTflu7ZWS2r%TBw0CyHhJ z2m4vde%iNW|IPfM{9m|RYUeP``XAdoJ?z0#TbM$xBWYIbE&VuanjogV(P%AxurYW0;s~{K$Ptl)69^Y3G+9f_*MO4u4TSuazwMqCDfFwgPGyt)>jlznRlH z&)R^|F`w0uo^`iJUnMnx)6oA@j50=?R_ZeebkPlf-}@PaLHIW-r>wZ-A>tPAWaZvq zGF`uM*sTiQF}(Mysp*hq?w!k+qd8l&7zEW}NgEHSoc=ZWx-|(cto|Y(fVa}VF2*6OgRreV2IgRW2tI}29 zFs3z?Px_sZBo1OXe*GD@9hxbzY1%|n`Z7v@+-e^=VLt%D?szlxRtDnUx(MvI1PBAW z+M#;p=kB{t{XvgzszvURt`~3dlorjO#+>!Y3>dTqutctc5`NMCypLs8DGhjY|-!JRQq5XezC_nCK_5&C$!zCtSwY$LBEM_Rl{`AVinm3?2Krdk#8B+ zK3GYF9~C1n%}0{PutC<>%dmVc3T;Ql1$Y~K>FkV)F-XbWM=Mv6eN}~Q)lk4$LHy8$ zmDyZMnvP=EE#7lHXxp@4w{3?jubT|*3XT$&<_a>IS4q^*bU6WAk;>?0l@vpgSDJFr zyhH#ZdFI%fQf~)&zCC0LI6h~CxN2*{fN@{&3<>-Y3kIU0mk9Z&6f<8=3!1*i2rNeX zbNOH1TDh_RdUEgGL_k;#29k8H&roP-owJW1+Xh`}mvqUH`_$ueUxrq1!aOwK(&H(X z{7&v}@OpN-t5kDtf@8hO00-iLGRLC_mqUKS->W^Ck|gPgyNb38RB*GnW!{7oFMwvX zd=vN_G=MxMx{07|-kp0)IkW(7Qc6C0;%&*a;!Qt(S4fz99LLyg(lpE0zF&xufSRAx zq8@N}Jl6;L-a$(pU3~pA*@Ao)8hu@exrHiYN-!@_sZPRh`j3A-v#T|sG`*f*&uf|P zob+{BvaIqQ4Y9VZebq_KjU&wqOYq>>oG}^R6di+hV zMccd)gM_`$2-aEC@@7f}N0m+)RDV5%_@uiwwd8=z2kwB#)89y12RqwJ-b9~*Z9jn1 zZA1n04MK$>JfTxCn62z)a~Xc+;;$${(MIUO}=%kXG^#7#Pj-y&oO|Tx1Y?_1qU# zfpq1>r!@B^y98xH&q`rrjq!a~kD_z*M!iE<2fSE`62h6}QoxuYKO8dEC%~;bF{GEi z0VG+ej)1;20HFQi$@)D)nceJ|qc87hg^#{SCik~_@hkpJh6i~%QXlH_2otO%Iccl9 z@3pn80Xv?Uj7^c*Q@O}g0{@Vtn97i*@sP~hMzAaE+eNDQE;J zx=GzW9w`LNdy5!2&n`{T zG$jnkX;LZVS}=vgKw}@E!HWlXzG0b*AakNyc)nBe3|aopymOBE_2n@!O2-pk=aC&` z>TxCdoyk7Vnc7dJf$N-6StEKQPo>%oC7y)gSF~`zs|MmL? z!_(UL-FM^T1V?c7fmS}w;MM#Gf7MgtAUKI4s6;%FKHAFhF|Tpmp!=tO{aHjc(q1}-? zk5!yor>*RDbhBa?6UJ{D2Pw7(%&~@vY*!1W(T5%AJ$^xbF8pqR^BW`kLO8+FF2vkA zdyr`k!-+_sq`}Ac?~P}t{{SMPAHB2)BRosR+cCRggeJa^=I41a)c8jA7Y?m?PjIg1 zQCW(F8fUD&5wCQk=@*Wbd$g8}jov&A+2TJG$}MEIO%aQ>t)SJOyV}rlo&I=LIXY1o z)PT-z{o;qn$Z~5CYw24-pg90`Plk%7t;BDQm+?^S-KOOaS_)`>F(zwTwJI*v>3O{| z?0nMdZv~7eeD=S}Cf(!+EJy&tNq;})-*A1o(*c(A02bZ5J_uyig`fEV&jieW5lJ9)qd=PF1v#(@1{QB0Ta6S#I41@@aga>^*)8v zXD4XQuDWbhpd_{X{hb3;RP}rX_l>HAF(uw0#@@(k&TJ=9YF-+f#@n{owAB5oQ&K8x z-{vcYcjhtR2{ku!*i%T_=yK^ZcXo!2Q#K<3G{|ue{uLj^?&p#(aQd`1TT~w+AUUu@ zF*{o6<+bZJrKzuC3maP`=Lm=PO%+B~(}R#8;34*#I$eCK@I1rawev|{d03uqAY&VQ z^JD9*5>!d{OwCENv_oMKy$i=miG+1-S@C0hc`g{}Ug#r#pO$te&hG{p0Sl?Y*c}KM zRSY_hRv*MH=P#?Bf52@Fb+3PYYtFDp?Eu!9o)VB;{Ft zo1F(VLgCcY_!k%IEGWjR;JzFNN?=W;45KAgvZE z3W~!hoOR+GI#}=TVL^pEt-fY%dev2G>3Wu{U}={o=zqr;3KTn}Y;8qh$)PPAAC}eCV1jOjjiq0*U2R$~v@Uv`HoptrhVWs)3VafJY)~M!5;9+<9 z71YaeOIWp)_S0J56d{}MM8U=@!NA&7dWvSn-;c>%=swn!|30Xc#?K=OE)RVsGvtih z`LqZ9O~pk`h9>g#Ap*oB7yIz}4l&PCh__+6Gtkbgbxm`TB6I%pm8qMdBQW!4V<(gp z)~x4N-?n&4{e$x(4jp4Bf(7wqNTpQJl|KWmIz*`r7LWFHE^0f}uDe8Kr z?4Ahktv?o-a@h)$!xtGDis5xAHR|8Cm;8+yfAo}ISD4J=qX7zh^jc?q3Me}bs8dt% z(sd*U#usmT8#$nT;pt7_?A+x%)EYX)?NLQeCWNU{^I_X<7mj1sRZ|y$#=Xk z7*$n-d=G>c+MVStdobM|K}%bu_}^%Ya>=64tNe$|rt7p&r9; zyx z8v73)OU#BZr-nK!2b zooDk|$q@RB@EV!I`&xqY1$haJ!>ZNO&DeA@Xb0@sh0L=o3~3bkP(}<#M1Qg*m2iKE z!e2W~WyqfRU9vh^{eh=MpkQZTaT>7yqVpSn;toWtU1D}37-qP1(Kz=~TP}uz(j*d{ z@#CpfJa$Kxa3-F^y&&HH%4}_C_gfk#gW-zxf?Dh0{ksa1D=k&{R6yvb!%CYh=@U^h zad)09GPqp$UUG6$MU`aCaGgg*Og)8XZb6d_>E^Q!b7{guh$Ntak=gv}XBzp%rN5?9 z6$!r#hg)3;TGYNrfO0=m2z8OH3>L8!*VaqTu2&S~c5l3zJC$H@6Lg+QD_>-yX9K0M zf6se;Fg{O?+;^BqwjWq3YdPG6Q8vnp?iU?CDnb!=d`Z_Uh-cE}|3CxKCx)KR|0Ve1 zn0xMXjyHG-N!U5}qprm#>=xaUwsz^PHccfQ$Fu|G_8_zkn>+f;|E`rGdSF3VPp*Z?{)0Do)6td zf2C01v1@%A-Z8{WC@A-u1v2!?H9NVyUX&QI`%!3VW0jS4{ir6JR_}dPZb?T0^TKI% z%-UI6rJ?Z_+vw8a41bNs^jJp6c-{l9@c>;*VL3Gt>Tp!){BjXHDhZ7nilbbnb?r8u zEljwE?d_gLj6E8z@4aCvyB=PrA2xAEzUC3WBJaKfmtyd<-!-w?bO0)89uPd>OxDAW z=x0InjZ6n&>bBy!OO&z;`L@bX%>8sTO_hOi?UasWARMct^-&ak7nQ;hmC3EN4=tr_ zASGJy{ddoueW;p=sM7Ph5DF2dP_b^2CQ_vS z?iAY8Pn_E{@$ZYj^7G`l#PgT>n4?Itxo2q;g6FErHFwb7UPhuPj&kzv97&PsjyH38 zZCqPSyX)Y~W7GZV#ykFVCAnaC#M{erft%<^TimjQ{KB)kNdG!iZFXJoGG^Do;mgX# ze(Yev9D>4h1|=20^6<83V#V;H!yA3#uwm1D*Z6hTpK>-C5gi}mxksM@0iYO0LO&B) zd8iU+8=wZkd*cP99~L+R+xTP~ExhLAER%T0MJ43NQd2Rm43jKaLcQ)}bC7k}P3r^x zDy>=fo@xI>NenUGfC{75;RmKPr6w874FZluMlZ8ulC=gnRqK(XZKMrF^qCIQQZ^9f z_+#O6Y4WufV4+P%3R9QBxM);Ymg;Z;kz}`q&%LlJQ|1x&9j?sutasYbZd8EnFjR4t zmQlpqIz&{iOxoS}{82yA1u73txax7Pdr5MHb>)0c(fF7BjQb_4KV=8jg&SFIEqSGr zvj3Dwe$N!%U&_Vlqi(;T1t{zIEgN?P+TJ(~2@h5D z;tneQ&OMVZ;@*6$;=ZMieox%CwKy4tui`F?5f^RyGIo!4-{;wHrstSF2$|_2iLA@$ zgq#zI_xw(-V{&QPm|8+87x9LYg*{$JF9^oP63qtNW0_@_TbkOdV%}khUcWsu+nH7s z6tp>KN~?G%e5Mkf(;QJL&OKN!ItUkyV(Gz`w+)Agv?))^9!BJcWtzX=cr4at`Gt|- zhN_zC#5itN#Th^UUQQTM-7OH(bS%uQMfzW=Dl23zLoJ0YCB`zUzrQBR!A>nj{Uv$c zJR-`0dA59pH3qP9d}HIRXC1tN)~sdf6*B%uqt<1p0(5?!Q=D%JH$L>g-Tc=#I{CGj zlv>Ll#XIgd;JzDJB!6k;@kh$OzB%sl8U!W$V}LZe=>QLj(7E;V^p7llIg52VT5#nV zYNSf&a{bMk6%m6g2NXT1`Wr9_1nYi z!S9s0>O{40(g|j77^!a&L`crRq&1~xdMc#z3TsZjW2cla!iCw)vw}E|PX>K=T-Fha zy>hleyBUg#a0mm1D90v)ZQuyu<;Pyc(PJX-N1;_)*H7Ph-78vEIek@Vd? zoto;RO!g|kvnVXurh!{0Gy0iqEcg|UBThRij#adN?q?2Lr zVQw|h7JDm5)l&JlDb4g)!`q@~>ggRygMYjLq<_;5a8{g)tsBQ;ob3h;F!;xPiscLYtYZ83Opom;$uu>Z9FYR>g<(-v7X4g?u%;jnY z4wX%f56@1$EO#t9+;^8KXBelAfK5BPK_ZnmOM(Ce9ze>jNhajfa@aIQdNivb&DoSahB%vy01i@YP+%X zIYCc|L}l9{C?rY;OksvM`O zShJqPlCmDL+;p&|ELyO_=BADASq2*D4f7M37|Slm(L#iSUDk8Tw}8&@57-*-Qg_rh zTxL1PwaCe4Qln;_ul&r9f4tjs*B(#HbQ#KP*Ouyn<)+xU=ewR^)v_aCuf$TKkxF}k zhQ8tYDs6{=Hrt(zTF7+Ss|cD}15R;1xGm}?V8`(PhCEfGN@TDrxIvz>!}T)P+;Pf>R;%h6VKy40TFT3NZ+p}LMQuMVN{@6X{9V{H zWKH?AUWT(wj4Q!t`2Mdhe4rn^_fvHmaMwNmgOSG_s04k^8ly2mLhNC+vW909TJ(CrNNr@zjw5x&17z{P#i*K~^|z@<2?$_Qpq`p*3BaG(wA220*(t zWNT6|-*8Bk^w)}1xwXa6uJI?6ZJnRDxZG=!>Nw<=7-ZuYgA80>3Y%*Wbk`!Ygks_b z+p+Ss!aU}@OslIE?goy)SS?N#tY3%ekpBxcm0hqxe-Kj*gbP0sxd{J%LQSpX{}*Zs z#tVJjA9d`+D4z_PUBL9`WP_-{qA#(%9TaJH)H}%3A2!6Wy_c41lK{(_G#}UoModar zzMZ=5M_j-ONs-T@Q0f8eD8#GO4BPY0@yG5pMGSdfyD6M=vuXS&z;Pyb+lW$TzOV`O zalmF?M(mmH@dfzOtA!D=)xT;};Un`<1a)O$_}Z|gVgnT|_GR~iI8*uB4r*L1&;f|L z6%J`>$t6=H6PX=IaI|5HXAcl4R8)epKOr=_!%$3hMnI|Fl;AzR>Pk(NV6>cX^mTymcV4sT=X@b^s1K(wtI-XnkG=H`SEZ!Drd};4@C7HO@NrSBp4yBT6 zrrWQAJA!*>0l#g2-QxGC(L+>dgO>zK2m@7W@_l;6K+##*tPpqjNy|ZB&pA$Rw{dU3 z`IuKnWg4b~en-4Jq=b1{lveBfm>!_qf|E)eU!|iJ<%FJV0$ovu&>ouxbV1I7%H0#i zhX4VOR}(1D(f(o=dgJCr_4BB*U$}{o&?`W$8U8ei-gN?yT{p5T=Pa;;JMmk}520@n ztB~+qyK3t@OCU4`Xlp0P+HkRtPPFe22rV)*7O!EF6qu0aUAIgti;?I0gv+iY3`7(g zWxZ#uA+3&Hejd8@c@z7^)@#C_D?oYOp9j&wH(Ue0yhsL?epaOFvm39~u5+ooe( zH-xd>8~mWKJNi9in?fc}V>G;Q<6%UkY)ruUov-+mDpcEPj_)_kVbM3$G~qw&7YuMWjR; z22fFu?qL{8ML?xQy1R!SMmnUVML@b`M!LHhx;v%2hi-UJuKT{8=lcV`_g$=|Ybm&J zoEgvackE+tY>gu3AP2Mo|HwXYn77z&P@-zx*O82T8{6-ID|9v}RU5O8|L!eOu1dwn6-${mfZvibX8X7j)mmL0Ie{Il^$Ez139;awJ~0YQQWez~Z}ht@J`{?6IgpIV!>^>xxr@8|rs zo=)fFsG)IbP?BieX~}rz{sNc16nm~`9ZUL4-{u2<%Rv;*&`_jRAjkBVEe1y>zk-1QREB+`>iD3P z6X*NvAVvW_CLL^S?NWRv&>i{>4v(|0fdSw&iOc--vXJTSERJ?Jgl!PN#Lo;t91xehJkkS&_P*Sgh{HpCTO zJ3U4T0O-UAZ+}mG*mvMxJ*{wYH>|^CvUDVcIk%Z zQmAP>(OXVQ8@Fo1_ihasi{-WDzUNR9-$;~>fW@f7q_19|C|xUCNk1=)!c&B<*IAx= zM8urY1;4tXE$(^S^&*|#4}lNPD%J5kwI_RR^loPvzaGa=_Rgu~pb!B9()ZGJuAOZS zH1a8-ZJ_xtQ!*79T5}u|A*HET@k3+f)8M~*bKdiDg1dO}V&&SS`FYjsHOCv-^9O#l zwQm0BRMnKNq}hH-k&xA@al&}0C}c~m@pc!x;YTNA*MMkuRF@c zM7kL}cgJwq8unGbE_!h20((<30qoL5$U>RQ63oGEQ9T6^~w8lM$rQ3gr&q4SZr0@ zAbZS6Pb!DV&E>$ycvAYr$9|K(S2CM8z2@ZWzM~qhW6o~LI@YzA$SbbqcAYC`Sqo9) zA!f<#+OLii;6PVhS$CA22Q;`a!m+7^T$0ARFlcEJ3~lq8h4IhRZM{(K)b?#q(=pHg z*lkRIFku;D`rY9%QrL~?#m!;(gRH-x+$>q^io?c`98_u3m)N$$^Rp61U|gzEbba?; zZeCsc?}{eaHzglznN;c+aSEC3_%f}t5`uk@P{~Ay+ghr>L>u}wxxXLU;3|%Y8Mt=v z3O=^M&`X8})9=IMRKQGu^AB*b!xr_@DR>iBn#x!!+Fu0u05}%|0;Jl84EBD8l5YFf z@ck)m?FMOpuxNXVAPt^G6hu^;CLJHbj*PTLY&9Fd^qBL)_DclTyR>cEfu+o&2)n1KCPEpu;OeU4XUw(0Mq4`++Ff7sOK}hdC zRcV7Ei7n|X-65uDIe^J}fKu${R*yV|22OUpz_+m50PR|%DG1a{m+k>KUia+1Q~U@X z`e!UG$Kcfi(JJ@HfiE(31U9)}2`8>EoNPC7;6j9{goCCj?k~IrFZaAAGjR(O*i~cI zMijO*og;_{xquaUM^!;T74q%eWBwq>MkB*T-9WfouS6pqf8_-04RVj8sNr5Af$Zdq z6-?}mc9X;vquV02!baJwQx9d|U5P&4Y9{h7igjml0j zv(sd%sdFDobS`p5Nz5XlWleHjaVr_|hYuJ_Z5hXZS_U%JlHf|7S*>jrHY~oy$MLP0Z2SB!bFjhZj z(K!oLbv6LK8Y&lIwJ7$2fI)(?Pf^8l-*6vFbesL4m6nV!Htar#pp-^brStTHP(6R# z0fa4#3l47%KY&v5Jf5>CaR!|3?}awJ!!Q*+etD&%>b6U6Nc?e|!R{|}mktE<2K(OH z<4y$BPYMabhG;}w9h=eeL#b~iq14hS;va8sRxSJ6ifJe}D9pXH`CxM`-FH`guxcUJ zzzvFWeh&QFzh~`dD?_{Y)JF=NUgtlQHEWwj15YS?sL_BT^*Pj{8e$Xro4l<4nP7vn zRB!@E5$z~+bn``X=}t`C{z00|HXb+$vcV^SY|AfO&nnGn`MX5U=pnm*3tIm5+_M&5#$fs$YFXscyoY$bO149tBg98R~j+B z`MR{Uzn7iFd5vW6Ie#&UwAevh?BU7rkI?b3szR<_awzx>b^#7Njp9K%;Ux%zi=Q*g``b;vr19 zZnM^x{WcQi*yFxkrsq#IIL;zud~F7E3cgAr49n;eHg&5Z6jyefo$kE}`*fDVaZG#u z+sO&;h6>L_OZiAUlgr~5N#mQ;x)N@RM_H~=+pk?)K~Qeju7m-hnB6bQ51_b(+8brA zXPR#f`$V-$8k!JjC7i0p@1(BkCBW2V^S~gwhlA}UH-RGO(_+7XMZF=ip;ntbB(vS%x#+?=IOu*&6MLG<$AY_1;s#C03+m>xa6nJkB; z7%6?pqVdzvS^Zp!WBm_>i`_Fq!n?_yVi~nLZt0>lcQ?|vH=gxN%-f0dKUkIPUU?(Y zH>g%LsMag0Y%1CmP5|)xOnsizYGDOtD{bnv7M6_Ph_>TLz2gk2Sx5O+&%Phk{VH;Z zGm*C9s(z$X9uzY@Cq+m{yHYqqL*4z0_peD$bycqFt!MxRRSd!q01VCa5q>(?@_)| zaB=J1BCep&J40O%Z4mC!0xb5rC4lEa&%+YSvI(Th*Mw5&6U@T!gt%Lj@Ik}mD0sYc zKDC};G@jBdw1f|V+@o|Glt@@3uRm>Bi?Kb0z>LYhPx!O~<5(JPu-8C`u8(^%n^+OU zRhH%7mP~IigR9w_t69iB<47sWszLV2kZnUzhZhB;&_Q+r#|~ms>`S=5`(BWaPrlo> zg>48GW6vSPVz2wnGZdn8U5vgb4qQFgCvve>@%%?wDmf2cPFMO@wL1~#8HBEb$-EB# z+yw@%Tn1hTdr63UKb;C`{BUY0xK};FP`tZUWdUzD887MgHEzxy&#Tvo6RYiE|85Ju zr`?EjJ?5X}Dr==jx6F_bP|qa=c@7*H9A|dAZ7*xVRz&Kg+uEIia8=5Dl!%I(49 z!m`#=kZ_swm2{ku{cD7fmq=t4on-fv8y7F1$jbI(!@nV#deqjYr0*sVTv3!h_$7x# zUE&Y^wwi`(mwjL9%KlyL6+TKYt^baHa%XdmL{4>3?hIMM6uRF;pI@?(MTMLFShQx3 zb~k|UX{17`raxw5?UlAZB3jgv$D;=9%hQ}XkC%yy@+)pNvk1fL;ri z^@STA49}E>BYq3$gLMk$gPkuMEu{HpUF{Q;O%%KTM2}@r=oKM*dabo9n?9ob>Gjh` z`C%S}oBI%1cr#`4ZS4AYm#pn6cwE=}yT7}1qJpgIpa^BKkR6m*@O{eq(62lNvjE#PxqNYz%&Pq|XC?!D+PV0PIG0&x zC%0~Mwj}QlC?~wa4*izc2BAmVsFAn5HSPSx8>-wJCv?{x5G2}RJ`zB&c?G}%@0Y?` z^tc>)BKfL|=TWrC@CmbC)F-u**1HSOY~QSXKe$Kk|JsBhF<@S+VW#YE5On_48%DW{ z#yip8cBNR9`~Ax=JF;abLuw3vWA##*NPA)n&pr1P|H0nm%RrOC!;SHhinE(|>i#au zf|SM;xWJ-XE-w+k+T51e-tS83@K?01uV8j!iE+Tqy-hf zDn7e`Pv5hj!Y{A1)HkC-R=S(t`>3sNR(DcGBGyttL$K^n=$lF5l$`fR{dv5NqN)mi zM@FGdwJ)|vOw;DWx;CjJxlI;ke?O7nV~Xe8;yu~&TY)QRMu->z2%pd;A@D2fFbCr+ z)beJkCU?K>7tDMN4zn0G!5Uq;7apBLWk;PPR}*sR>_cE3nFK8$!IiLHQY5VM?BMyy zEdU*16zRn2_q~h1HT?X=;?xHb5pT9%+uwy{Ebmjh>;2JQyI-1YH#iP37lKA5qgn6Y&o4@RI1?KV1 zC+Q*AKc?|+jOYX>+M$nU3;75{_&Oh5I9Gq*C_cF_AYj)#(B8D$m}qB?)Rk!4>ZGsV z%V1>Oi1ny4_7a9^vF$YNJ}F!iikgNyWZf&*fSA<|Rx;HsH6A&i!_`ejYTABDD+q`0 zTHH2;l_YgKE>ySks1{3pv@vn}R(CMOH|HmE9R(UJy|yE1Z~rK-O)@wC>^q$a@{Wuw zKB~RmZf}Q2XG`BFj9}~MN;ZtzvP2&KhbsKBx9WHnZf!$4yMW|g0JdP)X#XIVdubRq z#?_MnHuxArs+X-u*7RiY3TPh|#oQ4G+-C0{QR-mC(D5)ysd?{H+UF8)G7-O@tKMFS~ZgIuWeT;tL z7C-*ZxwOeL%Or4`1Qlh)y!f`)i&(QW&obeq6Ns^wsx5qjUaduZ*#Rz*V+eukY-%&J;H$ZzOZb`ZION zJ6%~zTnKAMEn7ZE@1QZ#V#|6N*gJpUHG?MeNQ6-RpiT|_ymA$$>Ur{qzm#S+ernSV z_WrPOR##-(X)?oshiMe6X|{cvvlDQc?px_0A&F&L=NS+n>?vM28P__f1YZu~jO*W6 z2%H^@8m!8oub^e=aiIaqkUgj%ILvp|sB4gk!L>hd&4?lm#l7S<)uO`4-t@kXSTl@? zMp))NQ~C^AIC?bI(aqT#=eKV&GjWZu!oGlxUmc%BrEhVJ8y6Cmgq5q{FiaR1DBp~Z z^nR|axl!{Jm713mAS-hyB0An#d3MwF+cV%+{r26x2kjqHy&? zt}w;no4-)%qpzPny19mlQ(e7R&~_*rN<1^ zeX&}RtoDYySU(dLq;rO_1%l9`Y$rz!fv>hm0Fr3IjNAop&pED*q&DiMmoIdb}-B z32Mh(C{SsJJrm)WVipF78fE-mqpP-iRjQ=?BRL9Z z$Rp!_^oKC7btA814MS8kLuJyxq|oip?{1wProW`S|JSJ|dMp08NeIgM_%Amq3L(i11?dfX@f*Pb3psfO$J z!dA|dT6-sByx9Cf#);^GwBv#=`TnXD%Q);=QFCAPB=!+`oXbwZ@I73gWil;ZR%?$Z zSXyuSwHf*CV__3L+1TIVuLECYQ_v>oeUtZzUwEW|_#sX6PnkKiCfXtzz*F~LVT+h` zT)@>wWyy7R_EK?Y4~At>F^LOg^B+;jb7yHzfGLq#AVE{>ChEtt zBjRA-0dHp(E(tImt~)jOL8i2lc9Zg8rdW33zThe-to~HLVsG9b%LA`yU{K0m2H350 z_y3!XitiuF5_uVDC@fn(G>q~$P@>UT@3*|lNX{r$d$s8Uye)?b5NY0Xh?3XF<$bxr(-91XkDWT03N5vsqAfWUp3LMyR+$t0~>+1Qr z!CjfG?pd!{J9yo(y&_4AlMIM_3tBPrY<$bl=jrgw>N0FT>hW`Wj$%o3IU}ewWH8_- zxmv8XLOMjbIAv`wwaTZp2{TAtwqC?>x@+YuDKNrl~UI+-3z9_QS{?q2m@I{|89!eiIu7p?xZpFY_kjLYt% zZx>rtvk5`nT~EIWY%AYHM$FiV%pRuBaa}r@{@+B5a?57|FTVbVF)}vU&hbkZaXMV% zT**X%_yA?zwWU`)of~hB760h4+jeN<$Y1Yph+n*dy@G^Cg*B@ z7}o|#7NFXwilJ>o`D&2ne^91quB%~2{(}YNwMiW;00aB0$-Pk~Iopivwa#)hN&Ts7 zQi+X_7O&xZ6;_bWLl61NwmrMt!8zV=3*U7IQXMtn;5fKO69IKi%e{mK_#|PO$!@-(0C4+f%{w-pp@&6lQ1eg?6efc(o zjygDz3LVs;!x{?a)k#yYBd$GJ635RO$wck3tCaa|`Y&(PwHy%^8Gv!wh zC{uE%K5jbVjUsI^t8RC}1E6+}^bETQ044I{6SUyLxm9JBfBepWuXob<9kkp2Si|@P8uk6&T<1BAtXA7%v|~K3~>B~$Pmxn$KRK6aq1sPz zP{kX3{=ZLlDp!-n^*kjoqCPb@8rrWDJe+s5cvLY~^< zl`cbK;&ex>Qt2U$l8B?a#F7khZHVjFi*2;zO0sB;H9t07Fo>(1|AO zLn5_2o1764z6FpkrwQKegYAOAPT$?d7j;lxT05wZhGZS7wa%iK+-Kp=^1;kM(uB?v zsnh8CM`F;=enL9!fM(8vw(hir!_VZ__-8H9{o;%}u1%>*Nze?7AQ=CP&RM5Q6L9u- zBd)c^O7b8~&4(qvT|%5fyaZ7p&dnrq{s@=G6x(tNcsS5ZsD!7pA?y|!fGed*QB(1u zj1E-62{C$%GXQ;F>)E~t==OsmF-b?}wlh@?%{t2dr-7YQQKd~*uQU`@kbLD0Sc|PP zuePiYGaoY>!--k)UW4&9nDc8E%IAW%va46{9IHaJ}xD@A&Dh(1w z2+pM0P1g%yZ0+)EkJYVLAGoh8m(pSy^xr?E^;%bym@Kk>$soFoVT#Uc*5=@licKh$ z6N_;Da-^s>|Bgg75s#+d^@-dk^03Y*^13lKqg?%Y1Ig`SWx)ZNWwXDJi~q2WNUeUZ z;kj}db=|Di`p($M9_g6*WHYUhLNdfYG~Enq!8m|dVl)`x+#vdFo7S+2yWq`)Vom#J z$+r`cX-V8&p1XypCf^AIM(F1Ru~jn1{fubmzFg6^XA!%#+K=~@%;mU*wMowqLQU3z zEN3OOa@PL;qYt6^S09q)*d$}1_eiuwLBK9@FJ;v$n7E<~%%bhQo-q$t8a`e|U^}Y2 zIbu7FVC8>H;N!a6CKE@$%*YYf=Iv+oIMVj@=TNJk^nb2F&29bwT+FYlKpP6FEl+{K|^bi+OI zHJHpA#vYiof3`)xw;MwcvGpQd+aPAp*3^E!-w(8*Huu0~4W`DF zcngSz@B^E1ByF3SMac2(f(GlrY5ZxQ=YqehKGP=C9}E59xj0C^0o4H`a(`eM@^t|& z1?6?rM?w!^)nd5OE#FR`K!BjrmK!9U`RxjLk<+^_(bM7B_wcE}Q*>X5;r+t&%T%m+ z2^C4Pl`2L?Vg15$Ao>x`>ddKu`Q!G%R8NBe1kC#=U@p$Cf;B>y(1wb2WPFns;LRSZ zJ41jSUgU%KV|#&;<_p)ubh?n_jo;Ge-eT3sg|S2A+k!V3SAeqG$xQ~!W4k`y`22*p z8EaPII5h|Gr0eK4J|N#>!%F6cdCP`PY}S{ARP~EH%BQr1^?I2wrS%;4PS!gwhK$6Q zMKpXwpRpU$X^I*G%6LgK_19$emW-S5sb<@BOE4$$!{YD8>J>&&N`k-U*_h|m>oX-0 zPSuw0p}W5{nx-(6eGj`>i4huKr#b7Vc{wJ~*UnSn?y*OC;j=%Ajz;6Fa#raNVvb0Gtrr5Y>g}e-2nDYr&)g26(iMtMwIQPFnPsir z?ha@rGj+i~de*fQC?G1%Z$vEaqxMotSmqmCsxW>ogQ7`-BiSm8(QdG7?3Fr=W`0;f z+}vl>($IOM!EXd$E|CPZ{%%0g4}A+s6)Xg@_^ra|?#p^&-#|2s_1>d+59M4JhzBS! zurD;tpRz?d=j%7JvYX^3X-IATCf^uzv6bW9hD6h`X53Du(PMSTr8YnZUQ!w~e#=|f zv;fr8CP1+Kwd|wisE!gI3l&(2S*uu6>?a$MQYUyKd0yG)#x8Voc_5$2H>Syx;JV*2 zV0QbiUziJfuVEN2{^Fsmn}tiARo&T&-jroIzCXzBL&?A=mRyo$HE+>#U|V#%{^p_{ zeFo_3mu>p7Y2cqvSNQ8GkNjitb#K$C@RTLn<6920j^(ZmfQp45b4Asr_qb z3-7`opk~Ftp#===|B;ulH!SzqTA7poWm)0mD2pfduZou07BUkqjbN{4b?s)4B$Wt$ z6tTS$Vl;}D?NY^M+^QN?*Au(|AMve>w~hM?01S}#WRHlOKrd4N#&DKr_Eswys65Mr zNZ-3Y^<&8gk(J4|7LTGsJdRAs+ z#*vA_GqO98s-wB?VH(JiUBFz-O)TB5qdIHdO9svTMR&^cl(ZzJ12{0lW~0rg0z5HDqB^=f)Y% zTz`F(j|K)>#F9N6mcr<|aQ-%V*VazR^5qq;vTKfmeV}KpiofPtyLNx>5YU&jd0s;hTh(>K)B|@f<{j4$ z`#(1I5(;77oc_koy4{SniFZ4>8CH-BafRPAF87#n88K#SOeF>em+^jbQ_lhj+I`8j zDX?wk8qj_`*8)KVSa&Z#z)t>rCrf@XJmlDjz9qbJ6*VG2GK;sJBN+$ml1Cli+bf=gZhwvhW%=Q8qT3;bp2(IY4t_lLAt+M$46Lq(Uw(C!3I?I1hw zz%tfU$e^Xc*ynlEf4pPuisH9-SNeCs{_YbrUz^v>wXv94i=7`*-^c=fro=-WB=$U+ zi}1qY%2s)~Hj6XYQEz|;+BuYes>iKbYpXbR#yi*KqsxNcvH*aK!Fe`Pl}Z^p8h5R= zlk)`@x|( zKfSG_-*Rl<4}3XXpNIa9J)iNoxk?vnTN!_nh?C%t%Uu(@FdOM7o} zX68v6Al;_%#dS9<|CaV4ZnD!zlZTVW|4c+S=h>U(`-MX;KDm|6>oCC%@7yuIarQyA z?U!@M$SO0|uz}q|+HqC{54#2V%sE1jpb(devD^6|9&tG-yW|dDwnu7xt}mdaNEUom zV?;j0Ycxv#K1{Fd8o&2xnU&oje;v_Trib?CRc|9aa!=E!w)-fD(dqXQXsn#6R3Ue$ z|Duw7s4Fta?hNHZrrVk}9WweG9G1yDMrG#F!`CPw5hJr4wDdc^ZO1P!vrdL9BU*p{ zwVZGoX=(oB9)o~cgD<^a5e6lv>2z1GWp}v)Y4mttLVsg_jbJ5}H0d9+VM`RdzsLR6 zM$S>Vp&uD+uh&V&_-*KgoQWsVVu2nyExl=ii`%fd) zB#y4** zaepyVoW2<0W;xM|ks)>)L7D;HhJ_c^4P#u`<|?oy?8UEOwu62^mTf&w(j7h;RNamY zaJS$EUG8q_$7Xed)iqr=b87{Oc8h}c>@-lJvQ4Mkj|1%5>&(eF8;?Vmlfah65n6`9 zRbhombJ+;4FCGNb<#)zD%YU%$Ey6Yrhf&JJG&dcrb5}CM^>E7rrHR696p^?55d97! z{^Fis%S#UGIY*mk!A+xu8QF`Wg6vTFeF%9%KtgHTbNp$+%@Uy=1C~*vb&|PZijRvw z9z~{N({~Tai3-a)@r2aOx?hMxjUW=ncYYWJ#+jK+dJ!*t?srlSPO=+?unjaezvbRH z){18OiuqM+BrcIxLbIBAj3x~69D3Q`G4MyJ0;3SN(KK%$1^YxLZH&!J|0WhB06*== z1zh7>-6Z8u+$>z0wY3y#SJ)FnHLrtvgz(vpZ-5Xw%(eQ*{b49x=6R|`zVah z(WKwxgW{x+pBfMTK9^{f?=xn+e&XVEEjKE**v#Cm2Z5YpcdO9>!2P^ceA7e=IKIb> zoP^$xJtng=M-n>RnN;jxHd;Hz-FB0+ZF7lQPA)u(G_f@QPFU?J5PkG=eJs(4euKUj zLLb^%UcyAb&B?#~TOzdD(93s=A@?a9&7AjQC=eUuO9BL#QRZ8Pk;EGoj#BGT@ z1l5Uv-nHRW6s~{FkCmy8=qPV`Mo+A{yY&tCaGQ_xz^>u`q4qx`UMN#RLnUQXKZp+W zdzldRCDGlSwhi3fD~v3)WWuL;BxKQGEJhw06wXtPR7>}j6^1w^s4!;}(K|S{rJV-| zD3a&vg&Pm&fr}8`liWjl(*8pto$wU;UWLDz1LDdj+{@feFN5o`OC zBpDT#Wka30D0iKRUSx}z8_!j{vri%rZ>zPKwlCDOiie6A$nH{)Vrh*bMRtnJERgB3 z??h+W-_cP7a9Hur-LGyR$FDoY%sNXL6EB{WUyRzVgZ|>uWwdPJ`FaLqqS# z2GXoHLrPGV4W9OTXbQpR2}i%hQB#t$7iyr_Lp3?Y7l zAt45Ep%|K!#YQOK&@RN-nCCU>V4pEQ(~y`dKgKJ}J@_x>(LrfZG)&h=1lrfU)`WDNU#3`ZqjEyw+NXYD?BrNp*;(Yh z%9|}i?W&Ys^6M|F3wBG6Ho_)a{iAaE%SVCCmP$oZ3DJo#LZ01>5H=d<6k<`6nDcSP zk-va1dD~L>d)*lfp|4io8Zc|P3C>o_w-l`Y04|;mCdR)|Iet8@09J2(o^M|L48>f1 zJc%jMVhv89D+?mf5;1qqvCenQ(W_Mi*A2uvc7w&bO+r0b(0@C@TCCQgHymIicdx{3 zwY89Vq0_2Z{l$H`HnQv97(y16g0f&PtaiQs&11GvbY~A3&*4I4WkhRFD&ZXehXHeSZXo( z5UVWVM|5NDaWS>#Ga+1CG%XpahXsF62PF<}r+fv7ha~tUR@R!&`FhD*!I55lP(hk! z34)s)uE`*^$xhR+TJep)c{~-wJr1$FRhCt&>CAS1IFFd6V0oq2U!QL-mX9_zrE;gO zlJY>`!0OV)#7yRuHS!F#lFB$08eAqyBv*NatxJZmmjlAlvX6^ZTS8XE%}4;GsC+ML zqh)8)+tOQX;G~fYKQwL(Io@&=I(>g@XT|&F!Q@Bh38d9HX((1nHZ;$|+yCv^8cub& zQ?5VtcZH&qZBy3?=ilM81t$Cn0$V;cu-drqOGrB3yU}0QlyUhdsQVlZUXAHpdm}cL9j(F=S za9^MCuDJbL+yihSQ!^qou5&@4+&=rqinuj-n?(_gLXTb8`VgO<^Mpb$Zy(>x#}9ix zBmbp}kWP95d8+Uh@x#bgdp|737B4J)#SpZfFo@9?YiT|#y-5~SL+eZ5_CK zYx}#1h}7<`&dQHuZUaHie+XSW|0Lg`!6UG-wFw@!U8$jJeMBl?_($Qc0El)xG8~nQ zS;!fP_N>z-Cbp>=zMFUWlSf6s3HFZB6Pi&CPpNInV0XkRB0cg7VTYK*VnNML z=NCW!vCGY3`xy(INBfNrA|vw4nRc20u4v8_#slr$!4kx=Jv1jb5W0)_dG+y!-eSjE zCDT?XsQ^^vREZW>5&N`#yrq3WLxLv3hJG&6c61@YA-<+Fld1D~SOE93{kWZc9h#AL zsWArdL_OD(Q;q6;zsuwfl&t2`swU?@Ym;ll1cslizYqo-BlOo*RUg?ye5criFpVVI z{1{>FEDv=Zc_&JI8cOkfcd!n~;DldiY48J4+dI#`a!J`w&hirK1Q8S{uf_1pDiXqne5d|p0s3Bm=&*Oid0x!))>S;pPk1RboEhWnOO$M+%+E_O z@8V|ao$zXTDO`;4&@(E&G@-fw~*NiQZ=KO4)T)Ydt`|w192NnPFZ`- z=OqfM%7X?{1(`|hCwxiP#T!}a`Acp&2>uXT&ACG5kRjtmVjV%W+9J$G*6vLmfg$`2 zie?F?xqZ$i1!bvN=@_t_@I6z3JIh-rluB4FY-MUTIJCByEI(hv;g9tB@6$-)l#6gC zCyXK1VY=pi6H1e!zkRV`{-B1L-f!k{*J9(Ym^u<#AV~$xnG4$|%Mb@CmfRHs2vX#8 zs>q;8Qr+R<{=5piB2rnk*s=7SH0}xRYw3IwuMo?KuDCLy&(CYg3Opj_YzOVfNrpC` zUjoh^tg^wuas^lz)3opU<`=an3W4?2Gv7@!rNP1U$JRt)OQbHe1A&LW93vB}9Ft7U=CD256ro*^&kR`rP#@^(~i-sqqqYeX&!Hj6vXeV@5sj5jtm^^z^ohhsX0W%@pU zl)UhC0|}V}{M)MS?|h6RvZiyhnbw<^Aa>nK-zIgQq{BWpu-=>ogDc4`b11VZPA75J zznqe4`@1G9y5f&?34=3^21TG-{QpRN!0-Q&_~;X{Z@F#pBz8@1608CSioft*?Mqw1 z)_1`X^S{SHTrWf*lhck$tZCRjjsQgiN`6=l>@%G!Ih}rxwatp>Byp)@b|#<4LANOt z2-Od2bKEjIO$^6?R&(y#bC7;Q(zi}Uc4xJO{R}4#R1p*^^$^u;Po_2-{<$(A^-0}s zjwwWwW?C^UnsJ*;IH~ic%B$mXCQ%xSUu^1oh9-&Xxr}{O$AQO21r}S$*%DK$?}BE~ z>_k0FlI&JJ-YQy?^nAeblJzLg>d&42VOqI7t&Tzz_qDeMnYQ<=tm@GD50n!=_`7)3?B~NV1K<~9AhpVO|3V$bRn4k{947}F`tRnq9a>uZD=6+1 z3LzLPXd_CtVn*|eBDG(IMX<-8AMf89BVU>!0(N1^LsN|+>>l-Q>#Ob;KYNIewdWi! z)O%lEaDJSqcX-7qAozy0fi4|`0^v?o$D+5VyGXYdpRlO<=K?ODs#wzO%tWDQKJy0i z7yjZS(3kNGN>L|vX3!RfSsW`$ZN>^F2FoY8#9BtUdQ5T&3(6y+EuRjU^74z7)NiRA zmRM$^CQ@sqt1kjeL8eN3iXQTsJ}h-rr?f%5`)N#sWpdehysdAG;RAW;uPV24i~`91 z@%k)2lq8rg_Ov2tc{kdU?^+FsvTmErs?kub!`D@=bW*4l^6LcdeFL>6ZHprLZ5SY9GZY1{8x0V~|uO^0-YEa17zG9?N z&|HphopYgy$_r$?5~??C`n3Zw`o&~3$ok-WUsu^DpxM$1t;~^QO9@i49qNQeS*u(B z`cH-@Mw`sEwx6rEr2XhWo!|fb7^w~f^}Y-(efwWH=UNY_)LgVVwX6T{C;q?qG|K(- z*Ad$u3J0riFWMfAMyq$5>(DJ6q@Y(~8G}_#Q%~7WNTW52mVw{?k{q1)9}kfVSxjoe zQFHlhuSd;*ipnR^=H;oaOO{FMVE`Aob;-? zezv1ug^pFPrMf8R1GftBF9j$6xa{DJ#V^iSOgmuRB>nnNJFwwRd4uQI(cP)Ylgi`z zA%2m={x_oJYB%iaoH1QO10W7oUHib$1CwlsD zEZ)M`;@>orm#Np?^_+<#H*H!b{N~MV(JY=Zys>|Kp;$fl0IP|P{ms>0RHB#hR?e=+ z=sg&8aq_<%P~8+Y-39>*5l8NGU{A;t(R+SOjBB%Pga6-HGv^OhV85SFhhb4`l9I_) zm+qvWi@r2lw9MDS-{Rk7tCyjd7hq0sLWa8qC8U}LQs3o&^2LpA{1FFjuNn)7s-yi{ z-h&-$M|=mBpx|Hs{BwPtzK6Aw{#tI_J+#jVg*3SL4Z(BbopK<#d$g`Eo z0fRRk5Ht9wVdKYT;oEcyFnm`D;WT*Dkz$Te@O6sqmr-N6p?5aQYyQK4_n zkr0FflYN85R^JOc4In}2xqo9JHQ85=4L#0YYVyy^yV*sQ)kF}AWPF=5C`q;L=ICXP(z$w z#r*d+yp;KX9B+2gs=A>4@<5X3=AeZfZJRD3jta3C-rdw86zjHqs_nEGg8AFU;h((~ zKXf8;1&=B9mldW5{NAoszZ-_#urVr1-xv)vB?Aqm>wzmJMQZx)z8?CBwA1EttV6OXaVdYyOJ%6)7{ zGao@LElg;&BzZ=z%^dJY9(DnV!rPCb#(P(tYB&4Q^{}a>e>T@AW}lNiwAY1fomjp+ zPZC&p3w}K92)H}?1y&wK&mc6$2SLYDEATT8qyb`@uLf+Z4`~7MdX!cS0rjTcn8nML z2^0U5ZXEiHV42EU^ZavG-dR^Y0sFYwX<$gw*u(EHZ_wJjp+d*?!qo+E6=)oP>X$j0 zCSNIW9#un3U~Xc?T8U;Ybb1HZ+(Us5IDgp$!{5WPdcTZl)fWg5S)6?@=DWZRU$izf z81OW39@E`?r{DZIDll%gU5fssyvj5>F*iMjuc9pw+MWz}Oa8<=ZM-8k)OW$fSZn<^ z6o~f{_mJ8|7*Mf%LQHLT^JpB*=k!c0ap0vkN&$7lV5Xvj>*#_SgDWr zKfIk~TvYA;uT>sETBM}}L^=e7p#}vB1qSKvmK;iA0FjiG1}W+8?q z=h^$e_xYUj_MA7oQbE?NweI_Of4|oiwjNfyh@EM(+U$EXHb!5=e^WK|7k7A)#kKLS zWBfiIk1jdIoQca`>s#bcD2mEYOhb2@qgDqrDz&+Nw`+$SFD%7xURvyUZ!S%}JHGnA zJ*NP|OW=>p6Xa(ssF@N2Wx)UwlIN<@!UZLuO~E>>P0lQ#ub97;z=Fe zg3R(~cS%j%&#byw{xI28&ct-EBtTcK!|8iAOoV>9Juh1*pNlEz21a_+Qw9y; zQ>lY~UA1E82Gg5kY515k0p+i)C8if@>a8AXE9))sXNFxKrFFjczL4cR-IpZAqU3Ca zCI4RRr`pG^N#8kByN$AWz>s_e7YpFBYjy?!-jGf_{skI=s^TgojbCMutDC({s?0xn z(zedLUwhJ5GTygZGOWGlf2KdRTm4i~7hL22NY47n$=qd-Va@n~%HTAKL18??s*No zhf+G9Y%u+H&(4d;w%qO`yxNkITA7Rb@#a%uB2lO0i@q8LPl0GLSiCOE%E(;TQGTAH zrLx$=u5nVU{Et}T*zL-6xgn{*qxe+867z>8>HoUNFrv`S$}Jb9aDD>R0$i#w;e94t z%rtK=8EEUZQb|wN*=<)iZ%YdC|&tT&8g4oE=@tp#I9&S+vEyY>}R0(7zeP^XQb0axbgT8Pue5g%wMO_^D7@( zb4x6CT)jNwY?yu~;Eaf%i3>-hj2Sr`QF4|hG@GB4FN${Mz(;&2zAn!A2E}<7Xc#~$ z7y9(CjmYO6OA32U{uS=Nt3t<^@BIsM(5oduVv4 z38o8oE+(y$>&t{ODia1ZixDIx2ZqTYON$YH!U!MasWcAwt#G$nsRup&%(=P1Ix7uT z0X)QMtX&~wWt<@n38`|2_JXX0D0*AWrYp(AUc0OcX7ENRUF4Ra*SS<=mRMB}-h(Rl z(`tY*`XsrvtoCs8Vot}HE)tq$iLaaqNoK8bO+f2PlnTTn2%n>(#g81Zi+OD=)#olv zN#kM*KT8@i{5mBZYtux_-Ox>!6g}}5dbo;+K~JP}gkvtdkk)b88!hT@1cR2DI6@!d z6C#qOYTVtSk-P01EC@w(pzFB)-Rj@=O&_!SrP_K`;2*u3RF5q@0XZwX)f-H9gim*QeebAOjK*7QmDVfn)1K$)q zd%-46>0B;k-=RpbISS>g3_DOWhlH^h3GUP?5lB+fifNU{GN4|mwZRsGc=C$xQOvj(>ESxoG&5{JlKwHt zfV-)shEH4P@F!{j>WO0w0BR0?Z_nu&Mykd zRhR*e7hi~4->dAM7Y2nJ8{P&&ZC_F_;w4=l?-}1E zRPPvt_#fRK=UVcod@BVG^1aLZ2BfCsJcv-l&a`1a3O8cherSFfGAUB5)cKc8{9ns@ zp@$;(V)@snw#OD%&->na*ww#pGhF$pUHn#{eTZia4s447Y2D5xBB?JYV#tx>etqL< z=R?t>Y0OH45T5f^+bi2DK;C-a@`hI2!Be|i)<^(*l3dbFZ?{{~ z5V5so?IoCa3}QC;wE66s#`#WIT{H9kKZIcCmJqZ`{2F+|T9a=PLl`~-m5y}0mjnN6 zzw(^pem8+96JnnIpO(X?``z0QjDekhR$YIuyyk!|go0hj`sF_t=>F^fk3Yt{3Mk5J z2^~F$*xQ(Nmul_GThA6fUj3P5()0IKyL_6G`+Ce}tCel{uTr8Jo`v?T;*Qfzp2Pwt z!%nX2f(l94LK|}jNipSl@$5X_oQs5XwK}x+RbyCSs$>E{a}BXKjTb7v2fC_qB`o8i zXsLJi;?@(4!?}|@-4u68^&3IL+b*OPhkd_4p2Ie8hDC@{U6KA$XtBIj;@<#+umA3GLW*+;n{ft3^_Uoj<-@d) z%f$b)cX78^{olQdqW>4Yi{IsU{03DEoIo4LC|#JwqiCgdp4|tr`q0EjU=XTrdAI6v zk9Zv}{Ri zGyn5WM9%LTie@!;Gjk5vv#z4_RI9s?dq^lFqo6Ra_Lq@U&DsT08fq~xtUa0kE>n+m zloGL?1SS><00#9m4mOM+l_;BIRjSvQ7_$N4c!`?N;6AM{4zzz!NrF_ zQ?V%-9-t?!kBmVUG<`}&eUAsT9$gj`;r^pFHFc2REXXZ^6)60`9In;As7kF}bZ7fB zmk7WXW#^CfET$Q$Z$p+roealzgl%N|@ z7+L_UpW0h+X$~s-Vdie^(o}TkDd%;_xNY4J07-3C|0OsrNYDd!f263R;Ti%!#XBcf zaP8cG;I2#f%GanPx6zIRr;+c3y5;sTifhR=8u&o<2j8)GB_ughsxZ?N?uJxuL-Ax% zveJlwK+IIDm+yHq5aI4G1!t)Fj%enrT@MyM!vp^k&>QK@AZaprk*mQ2)B_93_mS!i z%5oDNB5RF=pDC4H!GBZ~${quK&VuPE5(w-1frwqddR%-cil?RF*u^zQ0E@+zNLo|v z-f?Pr`60JrdO)@NnCF8BY$S-n(2>}Gr^utI<9=|#+3=31xc5fY*wXtLbtV&msM&s34&Xu-7MMa%W;--9zVqP&%r9-ew6{}eiOUyLzxO(a9pmvO*3^1zw^V>5 z07+|N$A{(LHy8wbUA@d~ULsG}U2Z1nAu=DxYgM;c=haU757*fDOE}cSp7FliY54u) zHTtNRk`uxTyYVYAle~JjDoKYG38si4%U{v1!sXeu@Z*#GCN*Zg*j&p)(VFuDzvXj+ zyYGGC#AfGv8uyDKNhtDbrLy~2AueYg01AOjWN?#&A1J&8Aa^j@eBwy z-6r4N19La?Axs4+(OcI~g@|Jnwuq8vfkPuu#mIHM8J3Sc9q&V#Du(J93x}<3D%v`02D=*+EL)zi=&**?WsZ1`Z&6&z*Uba0{E1& z>|Tt4M5qRHmRwiD>~o73`z$-RU$u3=UC9G(((N`(-k?ITAz8M+L?Q}h$A5fYY;@5e zT5>q&pJr4`RI6H~A~35*8jWY}yDLnPp}DAeWLkk`%c2}W*Odn{r8?C)b*rn6(-WFE zZS?8egAdJchO-hw@j6RT+|!)B5nQ!8b!VHoo$qAlNQ9|PM*332>6pw4&^00{L~V66 zjK49W!m4-tnX|`^WE(Vb%eIhtMcB3+qJ4JgT(Ne6;iIu-x~`ux3EF=@S2)SqjpW>y z`@zQ|VtN+F1w-QWmrw#wp1V+iP4`kv_XnkD7o)0&A3PFypvX8&bBd-I>GS^5g4|V- z8Z>osY*SyC4Q9E`?N_Q&H2OjV2radW?+QVajJ&fdSMTP{PDu)zcb!w>mtef1)e7NK zA2=u7LAQR>6I~=E((~Kwjn*J1z#-}CA$73adPi+Pp-l0Y{hNK4d8!=K)umK3pAoKB zs+8(u)Ylaln~K-WN3x>x+->20cETKxWdmzT0cq-N!F5Gllr)6AU%7!jZVMu@?}se! zR*wNnxS-=?w+=t7gH95MuTdo4ip-&m*4&=~$sC-LoA+h-eX_yu)m7Fm5NBTF?gcvz zge7umLZ36i#79$M_5F`Y0?C^F-_F9jr4vvg3L?GkN`h|HuA@9$E9J2HBVuHZTPhF( zIG}GKW{U;*!bS=*mZ**#g>zW{4@SfH;cruOHNDJ+YFn(HlF7i)_s;@p@$dK-y_=7f zHq2>__w2|%CZl(*SX{=CI2kxqJSVH%?Wle;OAN>ki1>|- zF=)*XmJ_Vq#f71cCOMkR^;7zLB3zpnbh}(AK3%Cz;<1(3NV_hE>w>x{h3$ijurg6D z&<8fT^7jHAF>=9?IY@@#6~gu3gLsapu5U>2oSf@@PW?W*dZ0jFCpchPNF%-~0*2y) zUCM!=F(yBL7_At1`8=IYBEat+Dme&b)n6eF1-uX290Z+}F%JuHV4DF=Tl54J`?Rjh zK4tfm1XCxD><|KqJ|gj|Xpo4IOree&wrbAD3qN|N_X888!%Q@*qjl>H5W%`G=PxWb zEM)mQ>H8!(O;JFug%-Ni(L(?R_W8h#l^HhT!$aMnw16F(77#;5Xmi3hAiS-v z%sIyNH{nmU3mqCs#iEykyoO)!a51Ly=G-MW%&*l9MV%dI;A>y6Ye zL?Tqtg}dJaYq1fQ?gRINBG!9HZK08@fNd`oNS6O{9*F-#u{^qh2oI$2!1yFI`f01C zXaMXmmywEz*TVKs1EF%bZ(L;n*}Q-a`ktW2YkNVDcLAA9w0fH-QS#3ce7G#UL1>Y~ z50>v;riH~dS{hcx)fwS~llesAGz)@$oo`kpwRK?tr?{h{reC0SA#+)x^=^9AZz|~5 z!-bKz2*SBCxvS8H%s73o3yr`jLLG4C#ZKk0lnyxieLZ&`+F`>-CilodNRbytjeCH*X$R}!)X?LevnUdXc6K1J>9n7k`}7=Mz2gddpdxR9}I{zGs31c zjKk`lWwsBrcD#-*m-QR(NAm-1Rq{DRRYAuqh>s_pkbRH>el@Z-qkByvo;1b z-PcqN3ogiWAeS6k9X>=WOl3m(HoSGGV!z3T#wSkKB7J|Gb-L=`S%8#b*vn+cn-gRl zu%?M~8KJ5g;zwpA+2J=9w|Z%`3GFnx9NcZ~c@vx+g_oqjQq^gj0kLNzU=Neh&INrs zTR@XtvBIMyJ)O454D$FTxb8ot~)~rrU2t8c!*V2$DC{zebT|M)&xDKiP%bN7I-W>cuz` ztz(2Kt0!NRsAa2U7|_oxWmbBqs^;T2rxI+}A=nyUFyaF6>|a-GRF=jZotV z!8T>}u>tm{f}Hm}*mJ<)GD?<{BBd8d^pf6_MfZVvfDlUT_?Lv?h$Pu=QGCOig&V(D z7ooIYIltx?`&*#zV?=J~8wHd;?m}#syh$3TsJ+gShPf#&!5|Rh@zth+_cOg()$DYM zz<~RxKhjG7O3Rvn_T+Cl^nHALbJiXJ0HaN9wgnlRQ(XMJ4l;!1>2itnS@L;Wj;w6yD0Cg{Ed3^1*Q?gG?f<4)?+8N+ZufF@w{OZq26 zgb2_50)o&S@T}r6rNJCDJWnQod%Ja&ikSx8>N-orb5=9WY4rW;!F|Qk7HW(;W~Oh6 zhAZLrIDih9g&+09I>o5KKZ{9QI>`w09dqh8mjflKXVRXx&Qt#1Ci73{9R}=I5yFAK z2oc^UobWYC3LLW6=C?qv2%xE+`_|B{(akyWw@K~OlIx2#{#xOvUHt~pij3B^cz9b} zh`ni~vcYA9+NIwwM7pyeT;|vQ)gLM!5r!2DOMu7qm#ue5-+)kMSkeO^d zz+)2n{+e>>T*@gSLgY{e1ek>%nMR17YMftAwbHCuGiTD6sbfQ%o-f;RHkXs_ z9RN+G@p3?f^6fokoEl|YgK`PSn@`+aX-afPt=^i_Zj9iPn-3Ma`{MBj0W0cpFY#G_ zWn(jlb^?oS&5Ri|M~3oO(jn9C!Tf!<1e1{cPMH=Bjfzfq4q5<`OTIihb^Cp_8{&n_ zmxspm8R<{f^AF8ujgn8 zQLY!CSZnW7t2z~Q8=|UPkEO0L5rFy7hApt@~TgWjE*?vvve{(8Zrx+jw>`I zwCwxab(h^=(B{~^ZYWjEjLE4g>}j{ed1%?q9FkF~b!R=;Gk7gdvoPJ@??x1WmT^u@063&Y7T(fwNiPJunfzv2V=Wtc83N zMnnz0S^d!9P>W?C9$kOzQZvgG80ssXsws*el&xVlkot@8#`~mDzuR={-n46NT<8s= zPUbzXS}61aRN6gfFiV`>zv5wP4`h7b)`bd|-dEF*ZD<6ch#wE}!$bJ*3tWT*xl)w9 zcL0^1Vte#ChVY0>+4DHWFlZ&TuK&Tya1vc;>eA(8ZO46q%%->ElE7=gBxyD4^#0^e0w$a$MST*Ki@b__8MhQ6Z2XSTE3(- zpPbBpo_tCy-0KUQFZpHZMv8)UANe@%+`2p!qyU*%5K=ONKV6`Zeo{O%L5zDoE)Vgj zOor=(Ow6ewBqy(VqdD68WioM!gM(?5p_f<(h+8G}Iti%+jyHqPSrvOnRIIkpq`=DJKNE{n{|H4td6B<5^V}zB4=T2{83EUkRJnSb&%wIGG@x9Hp zC%Q&Dr5C0HOiuyZ4=X-%0Pv^Q6R1*2IlF1!+yR&_eg`QILnAj*!d?bx1>6iUw6_N_kybKnZ6He`s}{2KASo{==U;Js%4FFc{8X(aZo?FCtJw301bE4QkEo(8EY3l{sLUmsO5~_z?bJ zy)#MxB2DhlAmaU7lY@8RzFKFv(f7_aahva=ESZa1r$4E2X?|hS zSW9SIpz+#J6F$Rg-RKMCxPS;wmq5ZZKej?*b6t3F(PuEuCzGaeY~Bt@#^vBB`f zSHSEbnT$$oZ=6WqZ7tTA^2O5N>+gq%LmC4M_y&_!(@unQ#+gu@Xk3N7Df>4^+zJE_ zH3jWErmZEUtCj83Of*XUYwqB_G9Auf--p{(iS9Y*q%}AVxSL;9HKcNA2tXNscPgwm z%8AuANdN9s7!rDCHqle9y^bnY$Hx#bw!NM&(-fk9W%Mpt*YT)F(=qyYPw_|Ss6&xt z19Jp-c6OW=D>(4?1Y4ZbjDK^+u0ArL6(CA?HQ2udJ*8q84!i%!H#DFX8|aq#$)Jn* zJ>-L^+3l0MuWzIqh=We%y%YN&JAnL7v7sJ-^rxoKvm3pbNN1;E$G(a4w*+-v< zYz)xIZ454ckUS8y+i$`no_ZsJMt>eSNA@W3y_I?Wep}CCZ%`Z33+L6CziSPmU9cH( zir+^Q^XftUBL82?!$8?d$h7@o68Ptl{4+S-#lr_Duec4C+diNrZG*V(Me6k6lRlT_ zt)Mr~UduL*opOfT)uwxTFsJc6b`+q+QrB=-ZPM8ES)>-HxQn|oOQ&G6yW3C zKG$;zWEWxG6QQOyZKCIIkJIL_Nm*5xiMrs{R`clI-z!%qEvoI-Y%x()$2TuQ511Kh z!hS6j(a^SSm`e`cUFl%zKsbl>z<^}s z>|G-K)Y%=}tqOTenlP^bzPlKPo7Y&UqBIXXT)d5ZiG;Ieo_E`tBtOTGn&GVaE>E79 zG{cPWlQ*;eVnjqy3N6nMaYtpPXX=gvCGe@PH79<#a^k^5VVKtBdOgRmljetj={oe6 zjZ1ENgF9(HXtMKKS~(md0%oVfUyOjxSJ-8F7XiL6TD9oc*$5|wFp<+Zw4S#{fnv7@ z5zw6B0B~w#CEIOIl5Mcf%2CmiQs;wt3d*vrwg9oX<{4Lv^0{R5gPqgg7vS(`Nu!+n znIjlZBkh8boYV0jdkfPCOPrmB`T#w9!q3Ut*QvJ3P+$@QOi9_O~;uiYWQ{j6KU)|WaO}-f7@~o z7jE(+Ue!aOv%tPPYgHPS)Zb>l=>f>jBit(zgV$7DBvyK-A9PDGakm+FlCN1O0MmDL zl$AONci|jqT+_>*s-7_IS)v|kFV3!8nfXI^aInO!`kUsfzWap92SFR$fz-j5vuyLBA zDu`UCP1+;U_`Acex0ueH`71@5EcI>t4ZuZx^~>-<7w>1pLr))fIw!i4Be|623P5IU zOn||*!?k?DPdR)G)H|hVQk##cv?~b9mYF!1!tnjZ_xf5Y4H-!`vZ5i_RLv>p3=y3} zy^>3q>yJ#$&+c}r9|7uLKZ-l6(_0j8&Ajg`7URwt*koU#u0*muTPWo9vv~-={>#o+ z64oNy1SQnUp%DayxK)rRcIc!ILawhv7gn3%Z^Cu!e0NaKEK>XkvbucUNSgE%AJvE9-(zpX&N-MNP?$(zXjW2_&lWmWWcV^;=Uw=cdsb^Ja&@fcrhEsq$*Nk z&Bl}xm#+Wd4ZL4TSO?dk?otI%o zpRM5+Z}oihAFCWU6JSCc8k$alqb~Ddj5l8_h&17|j@*vTuS_;nk>9tjkq{l1QvGddU_r zDt+e9(mD!yZaWXcv^{)+kVD9>&7aqgFi2Bint~v<%UTtTh_`~Ke!VD*AmsqGlapvn zpFjyTSy~7oSIu8&aww4Y@!A6%rh!m0K@)qKf{;^t8G>^bB7zg~RiZ$$j+X}Qvfw}` zMq2PzcYD8HP;r)v7=_1{(= zJYNm+pc2G1RQf#-8zFMhK_AADo-E-IrOFImwKrg)S<5i%SE1#piu`!UdPL`(w>z37 zvYG##=iCpA;02sx{e$4ZB*+SbJqis|)Q?V(Tgg*N6edWXg&Z-EB=`GNcsR~)@<~za zCGN2Ti|{snh_!|AWzV$Z&!k;h;S>|8o`llbqnOfJ^Y?BggIA-#;Z`WC%GJ2Mgq4b3 zHiCUj569Xt$#$}K>g~H~PNCR%JkfAl{HO{4BB$7oz@!}ulbSfbrP#B+ERlg2gDyQQfgOqpsz80YWE@rbuDT|9|b&8d(L+;1~CrzKEG?mIU?6EE=xYyXD zF(_Vyim!4ZvWA5W6K+kjF5gmHUMhhgrAp(P*P?4eolU2<$|Z?bv|q+td`{{zU6&!z7N6g|Mmi;svWI_8pu{1N8-Cq6x@aWcjs2O`(=%p-!6md8##NJ3O zgNuYp@;dDB6Oy?#5?1W6Iwwz4sn$g8Tr=xZ>RZ;-($Zu-zrxHp!&iK5CLWOvqRT1+ z&Ii=VSzP+3n0MDm>|Q9V=)D-Y(c-l-8>ez_!Skkgng%~R;2}44RZ$Stn-!=0q?oG7 z6qk-h)#edUGjpz;uafkZTJVPa)m^f5hFLe!bx6im z&Oy$#Xv#MG8miXcw_f7<7cj}QiQ$FpE76SV?PVNlx!u!6+3xA0@4WzMK7;(#<+E*dUDd;LThv3nbn@@E!i+3?TjQT=7C3!Pl2d}`Ob}Oeu`ODS z`<~)HhvzJ|nm5^G z|LCiZ)5Xg^Vn6P%?ogowA zT$4ntxJdP);D4y0e=6QA;w|dNA^xkZpi%|cgh}w?yZ;kxX#n!ZnxEsd9Yy~WfFZdz z6H9iQmj9>G0Q_q%lMAMsItu?Gi%Q-DNJs27+5bZkZDa@j=4VZzsp$WS-FzGYzJWTQ z4(UH7iu;)41}DyHM26G-H|5p%!gaOJ^yiuaHj-99{QM(Nr?SbPw( z1$rsT3`NXV?|0jdoQ2@2D3&BH>+?CE8T?T9hFY{%y`(wAIhT8&=t9D7PvM03EHtb} zRvlUO4^osypKGku?|_6CK#GQS&>)MLX7Gf)JsL;yw{F&MiaQT?chipsiQ@F^+sn3q z1eEIT(h!CwQ^0YUjxM*VwvH2F)i!F(e+pZVV}`!D{4Irtj2fK-SPug&k^T@AJb;g~ z!_X&>Vzic89C#gESYmdM6ovL(xoSv<2+9$-=!z}JrA0GflIE0}2x+j;}v6X)UB$wlctK>C$%5AGnaf!@RM`{9?>G*mQqtd-v6KY%ZI74 zY_&wVuXI8efF13#dqLNuP(`DvQ+L3y-vFcS(!hz^FBG=aWNmN?86y)zhn(~ z5@sG%v^CPgo_Kj)?IQcs_3=D_gBj%LccF@o0;Mwd>SxcUOn z2;@Kk%1tgp*H79_v1N)O%tJ2rHDEf{G9+pKWOxDFTnufAA`BljIIrtZ!t+P2iSS=76&PQ5_fhbGgxWuoY=Js2-^*R37GoEuV=j*=CQqgUhaHTc7~asCcHw z)_IcI@iI>l>9d}u;5@Wx4K!4=Z34npz$TSL*wi%w(VV#6=`5uqLv1TrP$X7}gLiv1 znznNRXLb@^b9j`Esu=S8#&A zLmxRqLZ$*2F1JW|w|4THFC3eC9}(PMEg?s*{;;tYB*M{`xgKQ`K3`E(U|6$J zQD=?s&)t-AwXEOoQ*Tn0o1hcPo+GTePsmuSDwI8s$Zx9nuY?R62Zew2`Hvwx;MYy^ z0a^&^*K*FiuxyBH8O>MQSnD@(r|1@KIeeT5L}V0e1+7vQ*R%IBl)oPZv)d0uD+FAn zdX!j7C+WTUu#Ni3NvpnqQf^=_f@ks=vg!IM$Hez%Z_+19xm7z^khuukf2w{vUEQBF zL`Gw%$_kK5NdQu*4&uYb2jAi*v#qx;MSnz=c5C}-O12U&8Rvz^X)V$czrxbv7B0E3N zbdT}5t0vXCQaunNw8J>Cg-$l5c9$&C2U8Q8+c02JoB1H{wO)+rx+$j76li~*F)*9# ztlXxQiuj|_{@J2i7)Lwk3p$J3N_iP7;#ft4Fz3i9u4$i`we{09C)F0FYFU$W*SUh>dKerPJHM*1MZMB!Hg^^2J;zP3rzi zy8u*-A(3*tbyaU&dP;BuVjws2!aIpulejM_5hBj@e`8LgJ_wU=cP3_c5cAFkhp<0w zDc>$J%)eMCP#*lUxJHz%UX-um2I19l?ZSE7S8R>SN9si-0uNm}& zy`K$lnvToMyBw=FPtGk&JOb11hwY2fL-&R((6k}%c72DIVr zQrE%;)SGF8<9o?36>#i|1bm!fmP2JB`<~k0LSQY2pZrJDo~-+4)&XFPzxi&dRW}S9 zl8NwAe^q|{OBQyvsUQVcY|++P_?Hd9a*jhFc5QDyCqi5C9WHYe6PH|@ei$rn$a*XS zR=9_sP6=<5>8t4a8}g+Jw)IU?{OmJ(mn?Ac9rp>5?s=v=aZiC9=5eHLbP?KTp>6z| zhL72}3x@Y;nR!!zZ>&|eKd`;{c=kB?8D!@^D~b=fs9xP#W5A(-n9SEf4Oe?3BX92Aw96Na zh-inIKI447*ojZkhggzP=-QYz8DK#i41$g9OVRK$zs!&4lIh2C14=rExiHC1t%UuY zy8Y3bQrH?B5}@b(abhDI)lc7w;`C!>@QaD_G?9+Q$?U|UNcwjcKrv6K3+}c*zP6_+ zW|bCOB+d62liYl)hdiE9Mtl7g8>Vw%e%Gc6Hnjy`Fw%mClcM~)eoXEmxdUBLgnMYl zNaFjshmvrclp1|+%9UvcQJu)jH@VE;y=RSl%p>HtBG58o}n15R@Rc$F(FSc2J2y*@KJ1K>(MZ7#%QA~%ky3~Urd5CblWUY z+0_n7CtFD!{q=sJ81AJb#%dna&N{w4NqXv)p*)6 zH-#QNJ?iso>S2jkmIR@`N|}W87PXic42aKQb})etzClDSXkWnP7h3RwacCR!yjFuG zSk;q1!u)Q<9xkH?e!ND}9ba_y`O?ytbRJ#sxMr`rt*4NYY8d!T>sfzc&vF1_b;^NT zp^r`_z~r^_4zkm*qoI{F7Xz-@mu?3+>09zg?MLHlh)dP07%kn72g%9!zANOJM>Qeo zX=@y21E^Apo_8T=4DZaIj}n_G{}$*mPnN`RW0|I(D0MgQ{AB#(WkAO&w`FyKWG3Tik)OF z`~KWUT|uC$Y^p$cW>H{^-yiQwuU`j}wuerFBJU#wuw5&C95P)e#pzE#rfj+0SD2bo z$ewrojcrl$v&Nl@fdC-L)aC8+6ugFtj4qi$@O=lxlv=hrdc9)@9A?l>R)Jc?PqpZ;T^$jCFITz zFmYvS`~&NbyUq45+P}_n4zBM?Xm7+Fe#B1?NoI1Rsv^mt*_4(Me!la*nM%m6Ds~0k zqv=XN>D@22mff&<;F$FpKEQYcZ6LLmrrXkZxr1e_F7io`hnw!7><&}6 zG3Ade}NddWJh>P-M4K46}>u8m+xcLTVO(50uleagsVkudk0fi8-|f z^gmCR*&lN@`YD$-@rano9)b+{gHsI2W;Cv<*3Ry(*SW3J{X3?1+%7WsxxeU@Mej3c z2e^0hf3{beNNlNFa4{4e!;$c^pxzp^D#g1ii`;u4%yP5y=;y$M5FH`tPNm|_3|M8n zolSMaFBp+~>1)hJLJ}u#Kbu|29YSm=slT}aEnLpNyl}ayE6ugmnI4t3T#Z#H#Q_6o z);8p_SKH1R#DnAnz*rhNjoP(bn7NKGx@sDSy+%FdUz^|1uYn7I^t=1wB@{*d3>+%q z?-!iLEbb725+cCiaEFV_UH4|jlsRC^rr;KswaByw z7_dEJ-EY>@fQw=XD0|!94Q57wg^B11uRN8m1fkEWfu16pYOJ{=_msz@65-kC_aF>y9e zgh4s{&Bd@u)KDBW2DEBd&imvA@Zir7ku`D|Gq15rs#c?rhd|iNcSz9AZmASJit!Mx z5lCt7p01p{tDTVFOqyW5p2wa|os5Hac+t-ml6dGd!5T32zTqOIj{1DGk;F4QsjO+S zt2q^Q@_!BLB+|2+fb59}-zA=G=}!MUt22`CwL-b{_-xwKKj{+iIg>F1i3WJb6o6?Z z_wN5=-f`%@%xr7iBc_~T)?X9md}P5VE|5Jo_1E6f$- zNp%BoPpN>!Q-nR7`m-M|K-a_Pw~w=?(YCE$%cwNVsc*-idOD$+HY9jUz<)OP+jWx}Zv? zq{dZyIqgZF+xycorxNerJRfx3`-++#vjy_@3j9veD`P1Eoy zZ~3|c)fVoSd`0y8^XZ&jtA>Cto3%{Z0piC|YSbQ%W#TA@pp}$xn1w#&CZEK-mZ00Z zj-!0tFoachSX~=adpHh6TARqVIXyG9DGoBhH=C@TL$8QP2}+sNWSY0mZ0@#UvBYzn z-<$5!m=apOo=n$NS{eyGj|zLYR}(qD=)$vJ5$)b2`1{pq!B?L^fw1+Yv^yBE{iV3+ zY*FR7{Na4KUhIg9*(<}@=i3I2pRjr45XX0RCv?_m|CBT5EU23Fe`x1U#1&N<8;^2l zkG*Kp0)h1%Rte*qB0O%*P&C>O%U7_8+(Vx9_(1&x)w1sj2fakq0O{3NDCAJJRNNJ0 zk3Gn_R+f146#ftf&{Uj#<$_Zf5?wC ze%!_sLn>kb7*c;#_b@OM_ciLO*U0o2>pr*gns?~H(;@NAKx;RO z+`8bQ0)cA6wdcb1XdFYhKdyZN=R3P0Lc^(4@RZG(WxZ5Hy23Pd&4ypBLn=5q)GjH~ z`%~##YxB(Zz}*={lv)q_4~mPjbP0yxbpEd-iey!F9PQjsHePHO`JOK+j@gBZes&v! z{80+i$2z&?iBuP=QIGEEH;AK+?*;kx}6npY!8L@M{N zr#!*@W;`>bhL^}u*@PI>^|NK)cKC>C`FK6ZSJG~xo!2Pt1_~K`HY~WFjBpvD30mJ6 zawl>?Z~r4C zXy~R$=&Snmrh~a?SE%atw&{rz&8N&L2^-?qxhZ9`HYSykHeO#)QMDB2bd)FTmI#$& zuI&eANc&>vHqqK?df23tc&Cjf3O~t4%xHl4Y&OqaSh##(4xO((b!^`L?ndz#jdBE1 z6AcUCShybMe1P%jfV=46N4GXr2OR;Q=TDZ&&Gc_MvdIgS0Vh8b0PwZd!^`iz`qN;D zTX9m6Nk|7V8qJz&`;qfwX}4mO+d>;@YD8fPYx*%Te84TuT%Gf06PXw(NbNYN>o4>l z!lPj7C<@VBi2z6vb=paM0(Q2)=95E$VAMk9ASNp2f##{{PxJ^c?WCZMmkNTIkbpH) zHQs!QOJt}$?voRVcDuZGm{mwI>m???esSI^TNa;TWMmT6oUg{xo$xlcrQ)`V$(03R9K_ z6I$)-`M8C81McYP3i?GNY~;ArX14KeVFUNQ9rpxy6$GPXa7;;ysU1i;`8apX-O|3T zt*6i7yOL+lD?tVQoe2HyzKjr-HfKglv!=yap5aF%Fmqt|Cae;_=@N!`W{ zc$AL^?WorvXM%%}7Ang^^eWj_!aPp*;dmEU{YmCO2YzEa=M|?zQ4Ue@G zT8b#-Tt(wh9Zy zuq&(>Fk`AiQZFTYW$o<8fQ_Q#oc4}azx8c&+OcdphcSdXgSW%_^NE__sV4T|6Rc zsDmqKWR3~7Yv`K&HjmUjIJAANQY3h`5DOlyd%fd(vYQ0oHT*YxdSz}vxtbe_K z0otq}-*^82g;5;v)UgC8T-B8g7zEeKfQNu-T%Dw0Bh)|8D_Gf%#FJ5>H!P8|#r7A7 z_7F-sU-mkh^%H|z2Yoo_{IA5=g9g&Zdg%E4Mmsu=`4Dcx`cK%<&fx%8L}Ptq;Id+h z1Lr7oulX#zalm`um$oZqai^_^?x26>qsKKo2SkNCtd_{4)boz7v$r!HH^<+0~wH|m^IM@68`G9bDoG%JjBTRY`|MjsL|I^DrQQ7+8 zREa?|CSgQF@z~u&DzeCwp;BO|?{FtkNE72OZa3q)B1~?ta$b4vr(7Y>@AaKZ0bD|wDh3B!?W6Xf)PQs(Dk^2_GMo}VRM})-M zjDYj2-Y;qLCe|V=stj@QGlOb5_^AU&mMVG!;#@A$%5bw&euClX5qW+(eV5?i*@&%= zS}ul1S! zobxz-2RkdmIOp72OPu2P+QdfKu0L(rtNT*T@S_qs{Z)VR1L&LGE~5+k0H*>UN@lT| zJ{7~8rp^+iCjt@If@vsGmcKSzJR(AQ6T<^Y*w8!nr0JeKG{{H=38P(i*kPf3i(*NDfuY zLBmh>5%H>QO$9j(mR+lyqfOLs_owsG0*XhO%>p>St0m&zZAK zM)vRc!^3leGb0MWDus{vjgq686+C;Edmg-bD7W*`^8bf=%7$$sNQ)Ino=jWy!BK~Z2vzBB`LP-bG5Yp2S!)}^)aftao5%9Q>#1QjN^0pUwk*&BF&tETIY=T(V55!;{d==>(l z%FB9;*{1C6VWLnGP+rnQtkW9>W59ko0Y z3*OIcLf+k2_q#f5VK+*}3t9oe9gm_J*%yE7G7(8G{4ms{g)M1fpUmBsiab{)Rbi$` z^tG3~qL6W*`<~lfL~SAKSIYzvdCj6xvt$0mO}9Y>p*UcUP(g5WYlY)XPh6xE+{}SA zYMYj@LFC-4xV%Q1TFP|aXGZdn9@T-lINw&CCw`y>hwxo-mz{XTfXWtU)7UpkyZ}%; zxNhNJ+R1>E{nz35Anic={)9*SEvzEg`nQ=a_v>+2BRK*|DW4}2fnrmCF>=U#L4mbg zI*2l3c^}4~zri*<@JWy&A{%;o{*exn9q5tIxIYVl$3~32A|fJT8oVloco42MPLWLc zY^_OU_HA8TOxWWnkhK=_htPp~MtU}|H#tSu(Kd4J1%Ap`iWDk5oN}*;*c8xN#>x$3G4`W`VFbD+*?eRS( zIO1!S<5qgr7yiH@UV(Ch=MzpC;@%sNP%zWCS-bS7s%-dgi_FzO+a0aGX+ab7vMt#_ zoyZp2jAvt1Q^=OL?lia5S2ef&*e0FuK4ckN<39Y5XGy+EbmC55kg~_w!EaP34SB1Z zxXHKGTc6f0z@y(=#_~V}7N6gEHE)H%*l}@$G5sV0FGK|r?IP;A8CHF27mhu~VuW7C zH{(sA9kYHBZjXYK)*mQ!q>=3pFhe`DysjvGd+(8j#hU29DPVDHm) zY8Ec=5Py6}rhjtJQyN9;YLq9GM0{j#v!%wa)ZhGw+oOZ(F$I70&V%44HWowwUr7bg z`FE7&R(xvju;yF^f0THO`;B;DWWJC_wgWa_RCsgR;FHF0j&W;iGqDS4UxD0UWLW4^ zeAk7_Ce`UsjD54%s20DZ7%P@J$cs&UEnX68V7i_^V^bDtg!)l5OrOQHiJd=oTWb}B zGLgh|L=YHhn9v?INSLkHaq?s2ens@ zcH=9-4qKWD(|dt_zuaO_n&U@WZm$gQ^~}BGYf%FXy*uDAT|b+xuUZ}&2wG+^Gh2(6 zw7J~xZcIpi(=uHP@SF+=FaCZC*W^*2e*%x#Utie9)I>y5EC|2H*9#Vpg}_z^0mUK3 zxLOWxEk#R>bc{llq^IE+*iGmqSAW_>k|?l@18l&qPt1#}PY(`ws}78Hbs zx=E*(*)D#*lP5s4HfPD<#I4=mMVPpSvb&*fRcZjySi76?EL?|JwTgOGk*UYzbv|G( zGP%Xwk{85=`5~5rem$X%d1ur)ky`Bd&eL45kCY=QYU$jzAN+9#1Nax>8V=4@uG3a@ zk1kRJi_{0vBjxFwhQW25bs>3!J~p5idwplMStUKL^lNsf8IOxIfKB!nwBKz##%iA| z^Y=cK;rsFf*qygR@fA^*qdo2#1uYI-3bHVmx%ML$wvZqH|4Xv?zojbut;U-EO4~uZ zNMp=R>9xsypISg*w)fMM!z3u&--!Qu@m;~{%&F2WJ=id(&r^zm@XCCX>NCEg!@+)K zSSfYSvFF4!D|S<{np#hWJenOkfB9m-eXZls!A455nyp=H6V7dAWGJ=?_rMs$_cxVS z2Hk!;<(K+q|EI-brtw|FPPuV{!8=24905=GNQ@g<6zs_Vj0AR9jEvG;9wReqFrC?J zJQ8>pg|7pjIS23rmHEai>Ts8Lfk#|v%+Ko3D!TT!B3U;t0keJv?0W!UmWTv|kVe@! z55N7k9O)sjD0vE+<~)VP|2QmYK{nM|UY$;x9+%On$n^{f2>lXK_ar)uSoz{7(~Qx3 znQ3yg0L^PHZ&q>NWmo@S7$*QWoBL1xv#!UB)bYfCevRYTbjI&GENQjQYV$<$4J1Dk z3j0e&Qq?do+cdYb_*<-g%GK;p2bX}#tRi*>o6>j|wRBzP)V$ar+rg9)1wjiM?9=~| zZwU`1iN3aTxQO;_bPzuknMmWu++i+NEtj_BZ>jsp!sLpZcD7uasq#p($G~(ydB2}m zl{hklq@)^5kyw(C_8(>i^O5NO^zhymop@_p!ibyyJQWo7!unoqolbPFToH2|rf3In zD`t-7&J8zO5{tL|yxmE-#rP>@E_TSs&F5t|TLXBO05{c-pC;OVb9;E{=UYi5@Al?X&-)BKd2Q zuD>~@X&QDF0rmgrBk4Wn_?W-4C^bJ{gJ&qg|9Vn45J1QPBS1 zSU2`rXEIx6IH^(31m2=Wrf#S&HomJ0^7^)K9HgkD!9>7o_Gwjd?H#;hMwET#P4-u# z(X#IsY|*Itc%ybiZM(>&AGgb`I@-^a30Nsq64Sl@)^q_HB?4D`*Gb;rqAqKToE;59 zkNrRDtSNIJn<+rm725f~R9$TbcZoiT`|AFWo)2<9)-5LW~mg;8Lk39>l z@9*$ipUIxMoGzCYI)&Vqmh84*>6vjW9cnyi3Ut08hD|)~e)4Yqa6{k20cgy%k-Zl;Oky%D(j($7zU;1h5HTQMe ziLa;@u+c&%VN;Ik^ujj!qqroeu|E+T&tile|7l_ivb|roq2RRvh0xBhs=d{eeR_uG zK7|$Tgny)UZo$MS(Zx{t^ZH%fA?*ZA8upENg>5ee=pHYBYFe&&w(9JLNb-9g#@dMQ z?*Gj1g$e!hUG0&O?cId+XGPig$l%fW?t6V0LUrb033C<; z61yVxpPN<@y8&UUz8q=H-k8h@3BrgR?~|ESO5ieo%kDa8SN5?deRPI5Fp5Ynz3)N9 zD^h!@|9&Ju|5r1k0|Zv9A0&S?s{HtT>!(Mj`sfX|xI<<#$haBHnlx`$;w6K1kA`gT zB>V>i*w?mto=iatBoWGgH>z_P{aqsBYQNy8DMQ12*PdxnEyMfI9mUYhlw~S!3inXr z&b?H_fq3+X%N7J4(EJpgx)ZDvLbdyyJ2OLgM^w|oSE{B-UlRX@CH=3e2_jrdg=fE* z%omi4lofVwGlNJNvRzDSdE>CAMT$LAwme7^%&hz}KZlW4l>VqXZ*Lam{=1rD!^`$9 z4kLCu?>?!K#N~*lwX#x{C2Xlxuiw)&a$5zVVu0Z3Fk zhe24P+|tx|4-lZpBXl^4KL$i;Q9XD{0a$IzT23HqF^)N}Vm_>(Mq+(r#)Ylh279ys z#4eH-dfQet8SXNe;>2YO*RqYXdI}h>?=cEI*fWJD$2GzPylp#XXR7}5L;>H~2bgF5 zRZ@|XsoG_fUP_xyGgxEzG~sG^iU8~6N0mdYplRa+hx*A34y15k!066lBrNIf2_=Z0 zuW~U~pl{(|!oEx35ai&;V&J4xaf}Z%9UB7Buq1kh-ch*d{}Frk4grrC4y_Yl^PDHp z$duxMcp~Irzvkc;$9j#kV?D+oXL;=X_Ib z;wBr%TW*f72{MCej1e)H^3;{x%QwVr1+qIR`)cWB!a3HQBEDsFH?NW%bVx7?Of9pp z3Pg+~moZ|9t}a7*q1{B16iM3-z%EmYsjUgFSa@HVOk`covv{mh2)U~F%2464if9a`>r$WlZg?@wt^0<#0F3! zhKFuVPSLyTZV}Se*uiR`zhbcQR1(5luJYT6wQpR{@`JUtfTyp`k-@GD?Dc7yzov0E~(*YaZo znOo8S-Q#&dL*eBtGLXp+iug0xZO1{pqwBRPN|+UPWVo4)tQaPZWcN+qSs8_IPGY1Q zz1C04_TEK>=esrpqM6{w7+>+vODTrtK9yDv@C)i6AL zd;se}HYM?q3R_#V2eYb-L1&Z0?kmjT3*b9jY;FDG;Mo@dM-!9amQ1`NyqEl+E_v#j zrwKGPQe5Ar@@V~di31gnUFwURd(&YaFe&JJpj4{A;aJ3U?Zpl=cbj`4aalp==e>mg z3^5ItC-chvh6$%5Hcn^uvxao+a(4C|yUeFdub$lSeDrT4+ZH}au^;A9WU1XB_R&!X zMw*9Yz2g7ZU;GEoU=t$!Y?%in>-Ma5VH z*8fV^1N8t9B(vA&BDa5r=)ZsKEX(`{9~r^h+jOamr<8rFQ}f@8ucIAU{z3o#J;dT^ z+p$%FDc{kC|8py#>Omo@Rd;`KsW<*zCVOtToJGbsF_x3!7sJte-=iWmI%wltE-y~S z^!~Ey!NrLg=TBoh={8#@Bv@>XY8WlsfVW!i!o$K=Hb>`=mCv*VkT9T#DtB)UyL}w~ z^!5E5%Hr+N<48(rY8#QZKRNfBIS3!bUx?jt>_kiJjCbhy_* zYNY3|vbewCd~3^}R;+iW1b6cJ|0!<5LZSN)N#J{TPhS69h2&kD z0T4W0At!)RIln0~xQO`K`hSZX19RY(mfEqfLD$vl0J!}DIqmoVmN$<6K+E$LJib)R z#sWnM16eQZe=3-9Yv5Ee9=EBq@K!CSTR~MKmf1Cr7sB~+d4SDUfWM#i+w7lW=0iDf z85N$+e>iF1CQ3V2zUNM3d)s<^U~AH}-+%v4&sZ`57!Wvy7rM+aiW9j%8TJ=Ci~+<< zlRX(Pb%riz*;;lg`*j{#l&sfzMeg59YW%W!sVsMH_CbGrSgqm&HW+|9M3B^TD^@Rn z2*?<1?b!WGA|-BK8h1_OA%mjS zsLo=J&->$_i&Qq}#3ViT5;os~N!F^m&?EW28909M&)?ZbuOgaSqidE2c4aNzp z(pyo)*h71^AJ}*VV@0)AY{ALqV5q|uuI@nB1z-0kH0=IG2Ze)^!^ve$Wj-e)A>?GX zrbC-G66hA2p@Y19w5LscAjK-!LHB)>7E-uHLTGmiI;7F$#U}4(WZ-T+>ag3>bZyCF zrhKYC(-8h5-fXdLvrr-47Aw*8r+e&TZY`(j94Ac&Yb5YjTOIW~P54Vdk-R&X1VenI zp6zWGC-CkijNRjWxfy`xpQH1NDJ3~W+yMZ8C(<^+dRUj+1aj0ZjE9ZrQ?Vmu&PmC? z6uU#m65Ila*A7xqsV2h@FPl3~=Y6dG@G?MnZW?HkTz_QdGBj? zprPf@DmH(9rm5#Y-~FoY^x_oXNXQQlL)f~Lc(wxo>p-lG)EUm~VB9QfOVy!_QfB|1 zw)JXylcpv5?zG3@lwCqiTGx|Lb>?Q3z6|1sNJG=nf~_?|ejt_7AB2{^;JPt=ilO)U z)@k+T$p^Nx_k84=p1ixgFHO z#UyUALvmlmagE7!vi23fz!{KMO<8#4qP=nVT;hJeG96 zJF+CYK3+<^ga^ANTy{Syqe5i-Neh3yT7JT1Jls{pp`sI(fH?vsK!StgrTJ@TKN~wa zY_5|SwV||drnr*j0-b|4m)1ypmxs-RbPzyi$_h)gPM4xlSDMOmDO-} z&35%Gdtj<77Sg-G&N%$MlczRfgWB7((eI0RZ^6TW#U|PMMle!rIg}_ISJqH;_I}kz zj2l*&T%;sCbIoBqsACnNJc-}NdRP~cTn@FKE!SLqb?n*45^74VPwpT0%Dw6+F-zL- zlt4ByeNg$vz%SDB3?%v6sLS=O{Itl3HFkVXeSMZ-?mOlOwYNn^v52&R`nVL@tioLG z*xP_wK7A5`4vcHlrkFAsMSs1&kdk11^@QU8d0 zdhr16ED_*XU!-Bg?$`#qIWiMe==z?@zo|EHC6evKin zU>6P|+l&ODFeCTm__r{tzI;(`2isIk+8cLQ!10&$UznV*g@me+wivGK-B-FHbz+XL z65KUCG{*ir?%$DdrSwD(m-2@Kv;QpE|1S8nbbv}nGIp_Y53gs-6fX)K zz*%&~(^uw))^U)F{lAq<4sCna>}{TQ<)cr4(DCnS^21XYaMg#_pJIYf!-z+fT7}mz zdsEI>3!7JEPk>t8Y4{P2Lfl-&2@Z_k0cr;cy*KwS4y*oxLJ6LiR#QWq$%yl`((ozP zXc(;bz5Eir&5i|$FRSL38;)87AuE^lk^J}{#X~*I+^y{MiD7pS&PP-Dsq0SA4OiM( zU(quz(wUPbsvqRT0sNI_Ug1^C$=PT%U_GH6xI2xCAWMz*gcwbz$QYILOwF}tzyk4q ziZf1IA4~%plPd)KZ)K)b2%II{vR>K$--2%EvV!}6d)V}C8TD=ycgwXi2YPQh$+3$R zq`(DL!eaSJ+h2{T6voGpot!)L8}*ife7`RGl}A^8xpYog>5jGoIz1-({qbIkGdF9S zKB_jfZLrWAVrX; zT?^QGwUp&zd)h~5M2@>ymOZZ7uqin1&iedEf<@@V8|@F9l}-I*Ja;s8tO^SfnHJJf< zCnOqJD5fdDp^DHP8ZrHhA85?GmjT}ByCwg7s5ZQ%AjZ=)iL$_v2Hw=$ zQ@9q|O~_!3z*#=xJBP@tq4@J@`1^T#a!iQA@rYfx{tTngI{$4j$ zH+C~40#-~n&hq)^pQtJ2q2-TmS;XTo4-bQUyO#dy1Mzb%6HWnAR32e79;5lyxgy&$!-q0IjO`~Q)Y8!v~=j;?22g&K4 zWOWmAigb^@9kOB?vma@R8tsd4r>sR~H0*9@O6D&6Qp$Weby@;$v=z8*Gaf zw1Azx!mbOjP~Yb~@jz{@rR0-#!tjt6I$MYk@-t$JCq)BTuiK$(4<5 z6`lUhpS)*7Qso2K7|nYrp5jY`<`*YIxgabjC)v#npCK85q5G94_*YWfE{4k}x5SKk zc+OI+?k=Fe<7~;D<0gU=$!FMLHh2xqF~4wfkc@4S3bGJUTZ&+UAz^A@GaOysZBmt~y!3`*O# z6sox1xh4p-JZW9lXyC~f2RN@UghF@TO2_cAaiAVD`31>$ z+@>hY7ssL1xl1MfC1@kn80!)G(drw-XEA{kw}U4rc`-R|#U7s_kxJ zoZsID!1Or7TT$%FYmU8Oj(!ML&1My$5pZwN7%@DPrwF=(YTG~&w<2w88Dtk&J=XrB z7;Xj@xs`zcD`tRs$SH?f_SK3m({ane#0a$H`TN_H#DwaXkTC*r@Q?7s6KtQA+cT9% zH%dZV;Ma-e?^aDZmFTrbBX(p%j)M|8Y9jh#ky-)GWvVUBgw>;+Z>0aCV@4Fzms8G+$ z`Vz$EuB3RKtQPD7j8lHE?O;5RL~)bRxJRu(9+V9nsulPt zYWrh6f>MR-_S1!jB9lvb`d+Z6+}Zf}7WVdow6Ibk8IRaeixTqEXIAa zlH;sP)6r=PF`~l3caA{KzEJJApY-e3Iop4>eM^10F>rsOE4IVymA!)GFSn zp=Cf*c1e5PZlMd*0%n8b3c|YMZL|Ik*8=vUrw941{LK-M=M)7CGBelFHKJ7yumF!+ z4YE3XH-5XH^~QNBSp}!+S#qiY1paHnSZT>@qQwtVyw0Vsw53y}9#&x%CA6&;W(E0D zEqQ;3nwWE>%yDZla+;7W)mjF zQ$mFrYKJ0(i|J^A@MR}j5hn>L-26Eik~GkUi&lQv6HW<Zm&HWy`$@|0QUhWlGt1LfL`GgJr z8sql#`{Xz(|6&LNC*AZ1Et~}4NCPgM?lx8<4W6eQH*++;0#+=J;&{XPN@qXgEyaQi z9WRbeMfpcro}X=XayNAiC)9t6H8hLd$^qi}g2!d0pN2yY^^aBone}0=aF4`<72b~V zX@1RmQK1zYQ6f9ma{7JyN%iRNru2rT?wu+Ol<8d1ox*!j_Tp}g>6(W0KkC1uWTw9U*o7;g;MYK#I#cIQf8LsCc}-Vy zyXN`*yP7nht-tuKQaF@sj=DdGgXg|ftlmTcgzJY}s~yDaiJoJ5yZ&^BU`Z-5Io^`q zXMz_$q)phopH`E$zuKh>0sOaN6dwq=ppC~l=zC0Liu3K>XY(wSioB)oD#ka7 zyiW#F8i9_VB^hD?x{Gs6RbY1g3*rO;}O=5OKwwn&e8I87)!d4aqv8a}k`uSYe!&#%2)AySTbFm^@1#q?=tN;C%! zZAqVPb#T0#!m`FBCnageib@s%t;roUM8{b-mpAEJ`hN|f*wDuE)tg&$9Ci+?#gzod zVmg*U<)tGos8Ser_*fXOf!%v9Q;$TgT&T@u64)r?YVBHQ&>iPX)Itj< zThpyYIThv$T4{q_a&jLoB?7GS*dnqEdqVp%kS7;EB0sN?xXWxtc<5dw(g#s$;0z*f^OoJ#*%p&1RY< zvmv>@rEfvxNpi!g%0yg*v8VSg#~0zo$YLd&y|l36m#f!(NkI6Z14?@^`qE~%(=TRl z`jK1MnS{;iGtr+qu?FzTO4@u#^i#58vrPt`rsAY)B5McuGl=)f-s@Pgn}vb zbcy+)Z6cdNm*i4SnLg3f1D91$G>1@F1l?rRsYPYAdPdXE=zgr-p6Rmib=*hC?`iUj|ZA>3X-)FzKJpZM+;CX=nn0O)e5r9fLNMqiK~1&B~`gU zC)uQB3>-90-fuVaFL zV6o3cxqYl?(Y%_FEZ5yRhHgQloIC*FHTk7YU;iE~jq$ih5K(m96YzDm&|HblOQqb3&$dL_Xx zJ|76JjJBXMpnPkWb4R=k{~M`J%_ozKpu_t2l8MEHpGJ~@>-~H-T?OsPqj;s#&wpvr;oWHs?;TVkL?&!)S z#YfT%0qWP{>3!<18i)h!#w`Dv{9&=_mLO)~>Is`_UOr)*ZFrd-d$;BbZgj!tA8=@h z!xLuEkRmhC$I}$6JIT&xjcK=pBs{XRtF(b@XQp_$>EDCO-y)%iutQHVR%GBxZw3~K z?wYMKE>oz{v21-e9IyN;EoOS0G$!2DJebyS9o)Uda9scid+^!tj_|vrf=!ND!IdWg z>H96~hK<5DptQX!T0o%ktnpUEgDc2R9p4uq1J*Hpw@8JWN!1t4@L^S%W3x;qPC1q# zoYV#wMj}m)D%hWLnvuc#QAz%ZAo2>+1b~K`yrWONz97PMjriyH;DWQyWlO>u-|jA3 zEjR;8lhx`x?>Br0sa<|iUK)v+aON;i62%ngXY2#7&5OXh(@G2n@-OiWDg_8Px7CpfqD zRIrB|k7+exrsdG)9Q2v%yO1*~#rer&GkdMz}lwGX5yN5ZXj>L|x- z;r;pNc23}BDpiOeRzIOKvd!wgg1Vw=p7|!tV~)QlmCVLIvRNQYqGOg<-ws* zkcrErAW-u1xR~H&#*F4^vKQj=-*4do!G!#>Ld$Q6q8_W8XU52yBAt-iM&T1Hp_o593N@`_P7T4`A0|4!4(K<;I?j4_ zpxhlWR>*p^Gr~}$>Lt4Txki})56ZXbdhHK`_`uPZCmaX*1o4CJlbEnatdh5|4}Mlr52c+@ zXFyz`yj_lJ6(Q0b40`hh1Q(%|55vZG^}k2&9r7Nm*5+Ds;581o>Yx6pt8w-@waz~H zLnoH3|Ev=6_JHrXO(k~>;IV)7&Yo)|$}YNpKYCsATEYYV(ENRCH+FE)dq0Mud3%~ah+RRib6AbH;u>teZI0m~UEC>xF_yGxRG z5`|C~D&2U~+SDpK9yfm9=2q9JzNt=$p5c4kKbEwvN*3VqF=6k^-wDHIi>h$ge9m@>PlXq z8(anq&1lf^_9qRwG-BuK315Tip8C6y`D>+Uhff3einkI7A)4+sje4_p9h?bM_z@h2!g^dy`nH-$`PFg_wHaUsioz_cV$4 zW%HIPQd=QtPW1V?;2u^|*YsY$?t<$=&$17-jGG~_aDt%vUi zGQciD<-%Yb;S$gZXmnFM06AWZCz0K0XCsr(^*Ueu!WQxv8ukO(cU`u@2mfOnu37SS&7{9f#f z+IP7IS>J-jxuYkXo>T6zJ}I_w3Tp06%Jg)EYLmj)KjE^AzJ}UnH#K1a`1j3j?SjEi zwSzs9V#$B4F;MT=@7SauO3$u6j(2AscqBKMAPl1gMbWS`dKc|s;m2&@zWchZ-+%i$ z3~VLRNMTs|3Zvbc4m<7&kFys%wR~kDa@g5dq}J^y^R|)OpuJCA|F!s(r+pZi(!l*V zKK6{lu`BG9s7~;Z_cz}cUa9SDXB@!?R|u1HI&Z~6>YJ3ePDORQrnRJZJWGfi!#;#y zS-$1PD*R?DT`DPelHKG+krGid_OECQP`$r6oBQeMnN?OlS2Uw`KV6BDaNagCR2EY5 z@#osTt~OF)k>#96U5XExLD)D4@l2;s*zJd#S!Kjs^s`bd1iP9ruHeO3XV7s{UXW{s zK(=WbO+LeR&|MXV*iezh7P!WH7I%+?kcyW3$vV`DVggq_?{7KOh>TPP5_;Zyc*{At@w9zy(pPYN|WBtZ3E(vd8JCa;5`ptWyh{_(Q_8wqu#E!TR*q-+12Ti`eC)B*9qhPDB_&udRD8 z71b~4W)!4l)R&=xEt-{@LsrWQB;--y6u*?2Bv69vWFtq)bNX!%qX1%6gIZ3bVH-O; z2$-iIOpX$CdYRW2HkbxBq3HY?>ch6YFZh8uL7l$>EZS+3+`EyLS}n8JaO|Tf0hsv; zmaxR;*Vl|cGGyK`bkr~L|8#)~@Tb^1`a}mBQ6#q={AzDE`vP?(1@R*{kF-Xjx6kPc zrH~8VgO!Q5^2cfNijhfU&w@PRe~rhh zfK?3oxag9ZIas_vMdH3sgCY9Vd_)go!VP{7UgZm;XCCoU8ehCEgW#TZTzw*-q3St^ zS50~S^(|`9;td1lS02z~43^g4E86c8Q3mrg2VTJ(`+nL<(xe+0ZExEXIDWd=DyXVH z#S_=ox^aurLm+g7*sHl~A4lNk=1j{P`g32e%u z=YS@{zt3lJvV9_r4gaEWUoz}2M_`h*V)qAHeZpr`^XN4-p;5>C-3p#d4XJ6F$t4rS zlnn@~H10ymwX%y|G?m33>nyk-Fx-9xlt(J{Muow?%(pAGWfnsSGXL2i6yD_qvsP3x z!NimQkW`f6^wn>7 zy@T59T#32;1>y)3oO2&E{E$=WR*1_NgXdiRK+_3JUce&4tX4LVRb@l{I$*i|BWR<_B(e$o)n; z1S`GuqO$g?&?GP{6XdR%vnGoeX+I)&%A*SUOI=5-HHi;h=;#HH?y&FU%?ggZyGL0{ z7HzP6XkictNm~@jrZt@F3q2#+Si)!vlS+g$onKFOZ+t_Bd563v-VaT5sr6u^q+zaq zk5MZOk<(DgBq)u{XN}$+P!@!uyUYTCus6))XcIB%qW@C)q7O)~>$blm(PW4%^BZw7y4%D{KilmJIuiS*okw#?F@N;2t&_a)scG0IjL_fXST z9ja;ym+ysgjLcnI7$Mc^ydsRNh45;bHLfreIRL6H|RN?+jSmIWUY2lx^I_Y4^%E1cO_}QC&&Su2EnUa3~~xL7{=9 zjb1{`T=`jaxqgDp7QAOZ3lhX0F3i4EppPYzT{K5jVzWnu!mMSF42)u|s3eb?wm#&D zSH&0~Vyn6zN4=c?F|PNUJ3r5vUNw3kZ_-iht0cMafj=^%Iz3uz87O{IO`n{KE0m_|{HS z!pEvz--5(w(H2o}G+QPDAe-Oi#wjobq<1-j0J6D#x3MJoV_acUxMU(N{QK>B!MepX zWdludF_N=9@&1r`p=D(AF-g_qv1uJG9Vm0}*rVEF8KNrRS^>wH3#x<1o}0hrJwN}Z zs40jJ;iUuLiI4d+<(q@<9S8T>Pt2D>1xP|Fyc@s4Jljjp{wXO6C zc$fIe0{i*I{9Lbg#xCH6z_b(1d>8mqeyi>lzeyw@qFRm&U46`xu{-vr&FX0~@XShT zCYmub4--DB-)*aVyA?V&YJJ%9`>X&Q$I>dBg;C@gA%Z3&N6x~!*6&!{9oS<(8HxPW zWa`PJ7vM2S>eh|)*0zGQnIC{?CL~Mxa7BV;c@oy|&Oqh2QGy@^_r!qdwD;9htsjCd zB?$5Jk~3l?vTG}j1`q<>v9fR(qK!+qB|Hl1Iqsq6^rp&r*cUwzr<_gfQ7CDnHZkhWu*<*H4;DT7 zjm{iCUMc-whf?tX|Lslo)tfcM|2g#iM;+i~b%Mv<_~kABz3NTcmE`^FQG0%um;d+X zr98cLRidTXwz_CBjCZ@N$1#iZS=fA5z6k@V>3#10@%s!Rd|Oq$k8FJr!eVZc$-81b zjEt3h0@d~Og8_V7q@|T)6Hck7Hokd+)vY;IUK#z;i(=03Fi)@g>*3C5*SbW?)UCYI z3F}(p?K;0=uXhy2*`T2)NHiJ5uz>SprfC>ECa4%0tP0o z&4D&&?E@moreQ&P0kGupHAcaPFRrHLUmcS8_PPWo#s|RUufZ2T>KNI#Qie3V0~6?r zQaDPAz23+tWY0`vv2rOJ_1C=BGuJNySybA7Z&u}i|A^Od9UX}MF)LmCn3JyR`S!NB zDiS#+o~+Tc+e?^>?YQqOWKur1orQ4B^-eGZ-bJ^#)!2bGRHn93^Ovd?kuz5rlXvNC zVi4;+?j{sng*BoF;ZVzKXjNCJM7TzU=7Y2J&pdJflB`u8kmLwysF^9}sXJ>vVrX**^21T*38);4xlT_Hm zneMIMpEyxqj2Gi6GmOj+877f3`DQb|nH4w?#^%Jp8#|)B3^pi9sNx`^QzF1TmI-Cz zJ1;Q$FuUCyPQ92iLyg6>70YfhH`Y_wIbE~SzR?}U6~NQGdhF*gjP_AE{u6vKwlVJt zy(&LxRI&17u1ac?(sW{~jGgbejV`N14sX8Kk~@?7%p9U;jx z4OWxHOs;^%>^U(v4dWM82-_d|<{|SCdclpIj6%U5`pp5;&yD8a;j1topf8Gi(hvv)uvRf(G(F__gQ{w{TB zQakLvP==J43Xg9c8Z!61@~MU0c>C?fkgCM|Y-ah9!x?YyL|Sp%kd!z1HiADknGlc~ z&#LZg#om8T=N7v7`MK|BE>eDS@r}fW!yE2@t-6ISd(gp0 z;#-5av)n$v_!6Hn)KZ3q+gE;}b9JcA#+ZY&wnft^o6<1K?eGb3XLgl?8!EqlL{g(g zEsqZx4VRol%6H#Drtv_Gll~v7-ZQL;E^OOXK~QNTN)4d$C>DARp@}F(A&7|5LJP%6 z4ZR~GO+paq5SmCpdXo}*NkR=J2uOzjp-PPcg6#2qk8gi_|7LO|%&b|nX5Hs~Ua}4& z)YGFeTc-`gzbWP|`>!SWX&GplJB4KdY5ZLmY_9+KW^(V|HEX)=(Yy9*ml~KbX7bdS z&EtZKoyf^>DPu}a8_MU)X0eX9fFn6=BvdCzCa!Ejw8Jsi0xc+Mrb#0I=fX^Zu!SRZ zw?*thBt27%kqf$QCCzw2N$JKvv$)t~|JR9! zwrNFkODgNABvIYmm;0jdcTe6MRV~v*yiR`Q)IQ)~D)&-ePjmS2O8N9q8^uQmk&$00 zxMBV7JEt`8?M}#P6-vwpy37hts5b7Y<2t`2JW#CcL;w1&5}UGhdKm&!t2odgu(o@a zMP>aFiX#v6+XJ@j_RHKz4&Bv9m9i#(tWcu;&<@=?6U)|H?`Q<7n9utu_7H6v&Of>C zSJDDsbSqA&os!7N1tO!2VHmTdS2!O=#f5?2q%^k7K7d(oED3C`eIUxTE%vJy$O8`q zX43Tb*QrSsZp=QEUj(g&*` zcvv)?g!Ij<%?$wJh@+Xr>aI7r1Hdm|ei>}%`~9_dRQCRJCqSJ>)VXnouyD9$DF$#4 zQ4vym+aFJdVuN0Y09Io>1czO==+WURTffmx4?_sHG0%yF6(LIT!o6K-dyA0s>N4UJDbvfU0=Frd9zH? z9$mBjG{Gd1(x}A z@Xb6sHtK!XW%{!q7kzJLH>vjOzXaupR-60{zf-_Oebs;S+pCJL=0BW(*Vdu}hy%U0 zUe*${gZ6IBJpjy~FX}QOn_qh=?u&J$X8bA>nt#|-4(1l%f`5!ES*meuRN1v8GNvpV zp#leXionCidD6)Prz8_)g4GS>s|3F+3R`4_QYA%ZTB75fZ^RDXt@pCxe-mdi`o@Uy zE+G!iElv8jcETTi3}7ZHqHR5g3EpO!Sd?(G_Y5*shr^^3nO5fAiq?L>8p{ zhvwflCLnPIkM(TfOU;9D=4+rLF2`P+$amd(=Stbkzx!8(GbNiQGPf@>tXxzV zbtO-G58&fxW!$g5v=O0OoCwaHclK5G0)q>koS*L^vdOJrV>H zYB0VSVXsI8IUGNgJ4NwI_~)ucj$1|aFI|}5Ih#pF=hQpD{)dL8&HM#!DrF#`fLI@N z0yN|{`3yyyzxWr#jW@Q>NL)#1+XK#zSWJQZY`|XiOm~L>E4>!x$BF4@jXkbqmDYI- z8Gb9t_qGpFT{FBmD-q-hnXTcZTvxsKvNUwZ8nZXjZ93CPQj}{#C}{;fGv=bbot2Xr z>Sqm!kVbFZ4CHsIo%Xu_6F4!8Hay)sg;cZxG?% z>|Md!6%-j`O*>wUtUU+pv`Vcm95kKj@Yo5V3Q} zC2HI+GbR0 z)?RT%uMqwaCTB^;C76c)!vYvIf!C?3;?QA@%Ll#Cai8;npQV|Yap65AwSYS^`t3&O zwj0x&;-gM|TJAW}X3yS=$+;zNf(9|AhNNo^Hx;M0NtE2pTtY21BD*N`J_S`OeJ0|p zA++hDeXpL0iR!L>=_T;^r(h`qt8c$jv^o$vi07MpZhI63NqfoJz%Q0Q zOiE+&GBWy0QjDHVhIyUiW5{8PtO~+ud>dO|bSa4Tt6^3&RlLXbDRgmpLYK7vL|0JF z0-l%nLDVz;L|aj)fioV)$dHr`x6e) zRRG*&?XZH&X$~MM-~-dJ6OYHshVdRX&glCd22PbnYxvU7)1@=N@i^logEE>oF7D;4 zCgQ4{nHJaNmSK_y^)orADr0{o`1Zg#kI!lJs>Nwt2|Gu29v~O%VOW^{{x@g+WUXkw zGy#OZm|nUdj_hgcUL48Z+uASL`&u8%+Z_!Jhr{z1snCWg)@?eEV-*|o>A z+~WXuNKV@a*v70L2zI`{Ezi@yLrJ-3Ms8l6IL@oksi-CR_h_~*`dOBw)v`jz0IOz>~j zu`{m$iuXzS?n=Qk!S&rO;8i02TZ8KyoV-+`f+M@L5IDFwPOa|s>a{}H$0BozoKDo4W_FT!==B~v$f@7#*IAebfQ=b${?w2ep>qg7z zpB5s&oX{)GRcPP(7>e6BQ}UgA+Rz;#IZ8GiYg`%d1`MogzG`9>cI^Ye5i73tLF8;f z^oq8S8}u3p+YgAVoo|^pAIU?mfm^s3tKzDc4z?qG{f;|5q6Ro*%trqTG(`GQ6Tcs;AvS3KLt zlV)d89PMY7@gA{YwaIO6(aH7I8 z!mXg!>A^cau?D`ND(;Y8>paSZk^3c5)WQU8!`3*H#=dg}v4Vujq>U7yAeh%XA_`6Z zYEF?d@v`g;2SxNo`DP0dFK|b^g)bS3X!Oy8zz0glNGVilDv)kIVh|6v-><9AR0Lna zM9Y7XOgoJ|Jz#sKDnPlD+8kr$+5dNb+;s!2jUh?(iWo>(ljtCD)tTS7`@2%8x1^Jb zD{>i0pnTo*lxx?+(dxvwU?8wSOUQOx+2jO>hE{dvI=CRe;yyf1PmQRug-q}raW`EV z3=v}39Y?WVuEC3u^Y!%7cgk<{URcqxrvQo8_E#P=oNSF;kF{5js*X=(!A_?tY#+i6 z1gV=q{>DOGu_w0b5pVkStaVOWaeU@h2JJKCtgVIk)9|(jQvWeRGd^OziE(0tR+nTF zD_i{ZP#C?@$J^&CmRRb{c)P z4o4U4b?eM>f01P#hpA_)U?k9)q0Pm5*P#<=-ifJXvv+I3x4X}osjINPn6DOGbSjcX zFKObVaUo8PDPAxq>ve}IIc|N;Uhgmmr7_9EOhQy0_IulMNX(wqfA~nCbRH`*j?m3y zgs|Qe)aT)X>~_|$Nuv8${Y^LXtfaZ2AK7o9uSTdTG@|YyuK@g}ncJOWPa(b59};Up z;hEZC?XfUQ>tT6wOuT&$AFPBcU_?@#JXC7z70L~gJa=FSoDMiq*PPtwd@>5QC!bf`zXb%Q`Wo^gjs%l>4C)O3e> zb`nPV>)Q_>5*bz=@Y6%gJm?FUf>;k?&a&~}*k3pXiohzw4dZfB*PyxM)|r?NFS)>H zn7zMnElI66my$(QM~m(m>Jx5Qe$n)ZyTVCkdDMCt<@|drtnc+viHYiA1y%zbbq_Qf zmytpXb_dQMX13=eqT@X zW7uoEa)df+`*`|84=>Q}4jOCGp4aY9ml)kX_-p_6d=i<(h%+FRU9qzo;Yo>__WJm8vkH&G+lIqV%P0Q;S7Mf08()#Y%P$<8>cYKWDoYr`?|k%o;a=SzWS}sxUVYc{ zR>|bl&HCW6$A|W?5un@Mkn7lBWD*|wLpsdA2(w<-R@drrBVRoQJdx;8wedBEkLXai z#D2(_?C*;-6S36Z% zXU;XlfRj+oiw4tvzYqoorkDYAlCFXX%7W1vg;?Ab*p?7Urh>7GrJ?EY&Hx?xHW`=H z0BBKe?hXSUM&V^U!C?Q&9clPVbfptIz-Z2#y|>iXom94ZrA6DtAvR-y{o-p?K|Md+ z>WA)dj|j!jD6ap z3~={6lq$dEI*NK9I}}!4B$wK>{;f=x^>8okHbk4NJ@_?b^{HC{*ZR47n9!W8?D_rM zL#(f2`@?mb?t|p6b+>XS4(+k{3AzcwHp;I3EUW&jZ0f*GMZ{>m!~d>+Syj&59E1J% zYcNc!&VXz7D^4pO+A}bGzT$(Qx9RG_WU99d1=4ORG|f7djC>C`x*GauvTdC? z67M?Z=d-yhq}+%(`Vsu+Gw4-5wrinjj=Nn87XZ6-neIF@vbpOs2UNhGMUdPKcJR^~ z7?d8s8OR$6e693-qvwRlxT~o{4S~|jMKd;xt;$G*^%tbTzBh^yF2vt;^a=_Fn%;D9 zZ9rAv+8jrLjuTnTW09PlbylyCe#&Kr|3y8^mCr5NwwJzYSRCB(n$z6kV znzqZw_D&Gkt3(;x*opFfU%s`~G)|@wbtQ$&OWh5MrGhBQi7^kO>vSC*6o{iCj7@a5 zHAM(RN!G+~h8l$+@D(}z`3K0$a}u9ASEau97a4%PdM*r(=k#?5;N*>R*arSnSvV`E z^ktrMhK-THD}#dq%lGI%(O^<(p@N()c1NLVm)o(wp~A-(h4~lRceB34+5HHNDcr=s`hi7w*J5e<8mj zC+OLJZkvqgu5INs(Thf>@hZehg0oanLIdS8-1;g5Z1LXpp2+5Cur zRfs4}u$kv=xmTlt?mb{gTsq!fF!CAaAd6YEg4mY8P|QW>TW$#A9pa1cI7MWxw(7Rt z88)Kbw5!kdjgGU^oC)TPd48!2*vClUZlz-pIA=t^7HVhq zdW3NrVgMB|8%{7_i%`#SxW;dtvXvnl^W$aoG4;*8M#oomA_*G#h~KrtvE7k~d~b=F zx_OUt2Hf|b5)9KMGQ%|)x15D)8{`Tya#w%;8HrBMF`s*rb`$rQuSXyrgyywp7WCv5 z%1{!!IX_|S|JyfBfV#N$;J1oS=J}*SmO9!v@#@;hLb}tj-&dilJ|Zs9Gq*0u6LT^# zcKn6b>&x7SDW#u^)aB8>>1%fNN^;Jxe1P>&qu4euyj#{oiL9-pb{T6yFZ!LSw`Vhk zuAj{ZdbTPPI3^Ld2o-`WNg?m!-?0&OTzNd*9#|ODaj9V4LYh^9W!EZd|Kyv*ZdWLs zoS(5p`CwSeujd*9n$)AuDqnIakLGW&sYdWycjjPlQ84KA=#Aki4GJ%xz&Ve1lX?mW z?<<1}3pW{WD{Z@V&M6%woOkV~>>dX3PO;R#*qRSCSs$>y!Sg;k`XLHV_n0ot?o5w$ z{YpwYy99YOpw~YXxa*S(b6a`kvv_Ua2PS$+WfqEWC}n3_9c_OQ`(ookiJd1H*q=QA zeoyZhU7{!G?HnTSFySmzQs&8C=#0aT!{%RnpS;$Ltb3VJT3E%>P43i{wzmbRlb35h(Q0s>Ptf}{|La?KQxqU({hUN()@ipj( zQ(oW+t#(~RiCUUhm!R`*Xg#_u7P4O?jUz7^4`ZV76)5(dZmhMhHZTjv`2Ml+q#hNr0$DcJR?7}dY zA9P&@PH;-?_W>GjL?e0B`^84&>Jf@(-OV_ITLFL+V)ziA!=t&ZE*SFMo`*oijUZ{rSF2sX3g6?!FXD03?)cTbRs$8e_z0DC_Q8JW1q6Cm1gf? z0~>PN(J?hwo(a0<#S?t3{|Gp3yyy9Jl0c53M!$iUYI1?{w}y{nxd7+X)qROOUe=B| zLI~>6&+8U`Jimnj?WRcEy~u#RhvGbXK?rk?ag9s?=b?>3zBP#B-I| zm8LA#jV};_ed=8NBXLWl)vj`S>5IH`g$DnCki1z2Ul}x4NbI=7dHG&nBnKI0g_|j` z;Nx4w@pM$Ickf}TNFH;Ny2={trTOD%zfK$gYl#<{&UlIvmpAzz!?REO3 zximxnI@9eRmn-deF;4sL$wajYy`8V#`w@f?WO=x2EQ3gOpXt3 zS8$O3B;#SjqPx@&Vdx;<0e>${KMUtH4>Uzx`1-VP3_27^DiI zPpV{$cSeVYePEhwu-)yo_W;aNbJoYGtVs7UC@=-WxsBf#Pdi-j4l=^@vQY+l;eol< zJc>k=e5G$Vp1q?nj#Emqz%H z)_?rdkwv>Po74k~UjtQd#a|MgWO)pmotm1={+D!GPCUOB(?U&@3sn9;T+;v0PV`Ls zB)@mseh>fmuz$o-ipf8QX#^tJB=>(0wS0FTu*J6hrwml>|IeX1qW?bpCL`87{r^Kd zwVi9K%C!8H`TxPJuu1>s!K|ZNzybuY&xKMN29IilPI~NR9R%5yFNt#kNqF2 zidt~7SyJTb)FYK1mZuG+*n=dxtm-8=M4S)%R3!FbFhgkL=wpHZTnoy3H-= zvLS~=lf!}(>`eW6BU&ym9Ykk_ycbqI?8aM1i^|bjSb|nrb|D zyqkhOcS^k1NX!V$IKX(Z$9llDU8YQsZs|qm0tbd{wEiw!>w4RyWbThLZP{X}QG#o-R#x6Q@VE%uQm97W>3!=^3JH!cU>-ajXu;-?L65&4mQY$?Tetr$5%>`OB=L+?&&)XCf;xfG~=Xs8eeN(f-{#@ z6fRB-!}JUe&9ao(%pGg@e3jYOQzL@^DP8VQg7v&ZrT-C4L1{+;&LKa5;7sJOUT|8x z@M0hw93JA)r+2wf?ZJ$E%8qq2!=qbIOHDJ&t`^@Z50WgkEP0FuLxs4>-`+k+LB$bo z71L=aMRc!nvt4Bk)4z@Ff3D$fJk5<#TC7&e+IzJ$*<(1L%Id7-hTH3~hNpb${NJ7UKMXt1ZDkQe ziMf`?(cP>+4fhrx6{f>)_Nd;eXFdc?dM-AeKDev5GM!SJ`~JwCc#;0p(aOXw9ZLFNC2UD zpPa!sS$&&!Jzh|EvB;-aVsa&!qRBiKwg9>pBXbImro$z0c2AVp z_BxHT0>2xsCT2A5s?3^Nemrl~prTpkeYnisX<92X!);qv(I|#Jbo-&?(u_rR-r(QU zE$^}EeXT3e?*Lj~9$)fY0MVstIfNR&bOD~ay*A-PZps;DLiU+bJnMGEe)TEF>J+)y zogVA4`8rho#TZE7qk1V5k+LULKZUKjC#4tpMHnd>X}#h2u0c{HJGo;K?kgs5Pr?{s zhU0%0AI^0b7nNl`LjsfVGvYas3|AKRh4Y&p{3t6WOLh=-^~Lxu(GTK$QF_L!mv5kA z)rnH-S|BJwl+1_}`fH>|WG_W=_QEF`JKv{xLw0L*`DS;0XZ9<>y9-x+G=H=A%OJ~} zyZ_)oi-LjsLr5Yl-0-oxDgUdT#G%cPCx0a%2iY9+yHY=DWY&2o_e93Fz*IV+a7apA zmIs=>mof2ibEQm~M~*jhd@~%27eVf(XY>LNPoyDa^* zsB2SjfaP2QwfcGX0~suQ5e_MrgYpvJvGbW2z+x!l2Tn*47bT+8&PRDH98Rbc4dS_7 zUE`ylAL*n-#>5^QB6{nT2G}}2gCcxp1AkxpmwIXj^=&)PrQ@PoTpD|+e^CBXS4Y;^ zJT3yS@BGm`VgBRLI#9qA526)WTyrS-_OfDs_$gPi!{G;U52x}r27EPw{0X{}7@>Bc zu$3J!!qYsie_hu%h`HUJM=EzMWu3tbrUS9j8`jJP-JUg`b&FnqbG1ROq5{ivCn;5q zXUC@r_u!&C%{w~=#{AVWcmAu(nKS&&+K<#;yyv?W@>`Wd#Fge$YY17#)NiD9At+R7 zuF@_|-{B;A<>idhw`mXL!kKP1BRm(>z+w)Crp062adMXg!1^->ah&UK(lSMq*3YWo zZ~`m(-ouh?7K^g1$S;~advmTnuBTZHDgafL-0hB!5DfC!!OAxwN+TMOy%N+brCpDF z^Q}B5i^m=xskEB{K-R*)81tQXT#ea2fTK1;WgiI`d-7@$72des#3G{_F06FjImNh> z?!Gj#3LA;brhFVTHcUPQg=*qTMB&N=8BuE-GjG-Hy%FQtf`KQjm$dcoYG}2<5yrO^ z+K4vUJJ)%Squ$Neo&AilWe39TmGW|85(f-peeVb|xhFE}kXMCDclYzTT9P=79{_JI zSH7~D^GAsn%?h`9?jr-4rT{Z<>?9ZyeKgFWK&g+E?|DRWdy)_#lyRD!zK8DBFZ`+y z#K&LqsyOey8Jt&qI^Jvxo>SVKq}c~hjTT(Jo>WO6Lge`J{ak<|bo_xC5K^?(V-m-$ zd|riobu%NBHv_9N9Z!G$y~Bavpb9`zmG7b}-niWkuYDntmP#yVemr$esr2EFJleM_G008J|?ls6mzoM`EiKSF%GdZFuf>3k$>M zas_($!@oPIto^o1vsKQu@=G5#O2XGj>Kt_ep3)3k%kr}XXnC=|5_VteD2<6sWm~_Q z$KI{=SzH)058X^LbA63kWJJ>sp32mmp{C76 z^C&8AdEBf%*!gztaqsJjMiHlJP`WU&XAgk6s42NM3T3^(?I&sx#uUiyg`RJufErk_ zwr(gTHoxLPd!s5pT{w*KP9kamG%(&@tNNX3ZYHzRO&sV*`H)+<( zlf$5ZLa8A5#PKLOrNY^#%fJqIIF7HtIn5XpRzx%Cpc&Fb}1_4_D(o;IZ zD8~uPH5g#ds2f8R+J&zPO%)8722nme{gBSxwE8~8GiBfBgY!EfJ>_o)bIuLd^k4xy z3r|+JRr4{=K{qvDH^HZml|oT+c@a!FBB^kYadESx~!GP zx}}ui6V118)iv1^wYQaU-#D-+Iu*2O^^5wE=TGx!b{cD=QINaDi_%Y;fh70n#QgwM zvjKI9uf5<3C1t^0mi%~hq;wloP z;N$RUVv1hCOo9DT6-|UXIW^*x|5SlivEiD0%&Zh(jM{!1z(jQi)M9r=&HXo#leyAv z#k1~EqXtc`4efH4=ePeL{e|jLa>(zhPdq*@XC>VW)w?GQBN%IG%$SiCbR@Gr$6Spq z-0|*Rt?UdsaIi>6?c7yZ8R*Ip7g29~ir&+wE&lECh_-PVO8C=F&3I*&!}dm`Bv{&a z3$4gmHgdHq7v~EeCEw0F)g%3`KQbjetyVvI5?EjxBRP@wxot0!6?PZj(%t3qBk0e< zJB#Db2j6w&^UxiY?Tt__W~&;pDGCWvbWl0^XnBR+S*U4jH0&6$>NQf^0#mxv#2x3} z_I51d7v}+=TroSt^kv!3r^c;)vCj#-kJjy#9_bX_Q2B^YvE6ylb~bEOlnA>1+}MU$ znmau}r=d~XEIfmpwvgH)hUUMDj%0*5)Pdi$m(esj{-L7PY8!8qy?-|Dkrw;Pntn|r z5RhiMudbdJP`TIw;Kq#BuUS3lgMe&!&K7_!y#hde*Fb$uX=jfCDA&xf0?&z%(K}WJ z27S~|HfKz}{bE%nt$5*|jcsSgW&YFaAEZfOA5{x*h@Ifn>yFqR{X1i)=1z^Dw?Nvj>cM=jR1Bdnl@@$Hm*DH3;3hRya5}r*Zfzd zHL#S`!&wVDdINCcEzLjo4%t9I|B0=3{J#DH>YsONCf*b++&Ws^f%4WlRd(M-@SX@9 zex_puXIsYnbn~pB#{a3CA0)RNFX0(g zwnM53mpF4PmFiTyOx-)rkQ~hS(yV_^d+VWtzkPrC6dgWIA+Gs;fd2tl!Zg=aow+X8 z(PC-JqH6+50x5=)8aivQ>+fm@b_Xqp)7X6TSL(a8jtAA*biw+uY6yzHFs-=2UY<1$ z&A^YA2gWoWcs^r5H#9H-#o4^m zI}=Y?D1QH-C8_XP z<*F9uM9+Xp!r?j9TCAf4Cpky!DARG~7A3|QiA7gi`6QT<5WT>#V1@GNb$ThJ#LZyQ zci2iLCoOMeQPkX5&Q&poLV8z9Yww|PCIS(i>Vm|!3rVe7T+~KX(r!>e8@)q;4y?)` zl?L>_h@7+yx%rW9Y+c^b6(X>^>4Oo|(~iT|)xBE$jNTb`;z+EscY^ws)bu>Q z;)Z7Hp`?^XFla6KWGwhv*6633=mgT4M%3KU=3`!uA@hS2<%7Wpm4toepLLW^C#|8) zzi0gOp#y*8In62@l^QC(NozE#PIR3Kn7H4g*Ykw*W1VWA4uHf zo$-|5p3ijiazmV>m8oHt^=EhlZRNC|piY?YyKEN2Z-ov=7gjXhH;%a>Z3}-7^0=0enP7j7b-niPd*yc>(|Rs|sUtkk`024j z$i?@<-78()IJ`34p`R&QJO*v`QJ_#ZY|8l5*eM`d?0DK!a;re#M~{iOs+sbbf2!nV zbHNGIw+@kWe7E)T+c{O)2NrB)l36}POur7bDeAe2B~KkVdG@VK+Cc?xxlG@bgLt#! zT4loF>&r@hX`?UfC45|~=9!40@WZ%YC36Wz9X2tk5sh>Pt*l>s2Zh+6BSc>^_j#$N zXzA7=>9>~jeXsc<>@-;g_Wub75m#g41M_};muUZQ?hI06u3U|!DUL_*qZZq+cp?XU zvByJ&pIHJ4?Ofs8wB?@`BVs$EIlLZNjC!tK&HkPE%G}s2FSVP6Hv4PaS{ih3^F0?Y zc|^xvS~{fa3k$u6Rl*5O^zr9)6p!>3)$!`oJUS1%n7hc#Bi8MIyA5|!+k zp}}khcJ6C>OhPX)R9O?@nTJg|h8{y%CskRuB2ZO}4^4E02gAE4q9c5Zi!<4@_YpR) zM_X&jQKzM1Z{ASY&k^Ks5-AJK+QK40_6|0#3Ywrf(04}Zo=UqlTsTUGCRWJt+a7k= zVkGkDdB%3GOkP!qhl;U*&`SARR>}66h;YtrzB7@BASz;aydDwhlO$ULwAV()z$XfP z*f|_2`0DjH9HtgP&sikIr1`!Xr5>$MlYw>uhskYr?rgs$Dh!NlP&8F`;})4WjeNT% z1uD#6D@7OflyFHZf$w5Vkepyu&=ny~5R;6@11`)}1V(~90KV^Y@w7A$vT{D@aK4w; zU?_OY`h!3|;e{ep@Xz?_#LIfK%zWtHCu|egl6Bur_?_3_&<#MWx|j{yPGRHS%*$0f zGl{?`qzr;vbDikzE*g~)R7T@cezj_*NtK&1%FzjXF&8VQ3=GbpjL#;8uZnEs-ZC2Q zdv~4jHsdPp1~tROURizpyF9b|D4az*X7J*oVZr`H{DscsuER}WYT#rf_X zvSk7rnp_U#j@30hrX)H-0;Wvrm*MY}`R(P_>QHF!8Q!z?vh1U4J6q7RjUohUzyY?X zq+6TM;xu7djw)BlmE~LH_MFUnUaNRMB}FD!L{gKBj4h=4mBf{Ofw+7pgo@H=0@%Ue z&v&<1;fnoR!__*xU-KvaWYut{l&*F_Leg)r9iGohX6w;ZqR2GO-wf&ub?)mT|Htns zStAKI=FssGN+J^^n)h@UK=*rU+BSK`*ElMMLLhG)Zua~L>YgP}t* z8sFHy%6q1aUStix-~T}S_Qx48BtiS~OdRUgu<4&bs*acZi^QIfR;5Bt35QfJ;ocR* z6BrGH)fYQG^NXS6@kS}jh4-pGIYArgvm!xXGW-!vX?z?o0`wyph0><<#SWqOVYg19 zG$~)*jpzv-DN_RxMTe4wYY_dB6S%^r;0mnqyhjerN`|R8C%xFIOWbGnI#yAYdG>f4 zFLQ|F-X?scbV?0-0>wH&dj%i+UyjV-ojm}dIhav$vdANo8nbuU!uQyjcn8FQb0H(i z8WW*}4o}I|jXbanWmG|<*#FxxcQey55JYyeBZ#|9vaMEBDZVdBV`tvctQ3WTdkZAT zm{RY&0u_Gy3bVp3VX{>9xf5J8yQ;{6G!bM*b35KAcTGBXRn?$`{*t_#~Pw_Hw)aeMP>X zwvi69h9@|o*nogHCdXdmh(Kem1?9D&{*l`k#lrGrPLP5Q z-{D*uZUvt$u+TP5J;+z1m+5jp^%W|?me3lK$plj6X%kkk!2Vl;pCQKb?~f?fv#k6< zC|LuGhO_=^Lz~5MdR*17RfoF%-cA$xv=BFin0V2CAO0JGTXk8mZ%TjF_feP8 zK9?;8PyYQW z5YcyC8_;vYgHrE?BmPvq6BKzJyrBx~;EjW81lOB}AqR*m<;4buV?`%a9W3 zJ%9Ih`Fs?t$I()qb^3zW+tB@o5rN;dV_+lxU!~8Cl4);5%WkQ`*T(#KCVdv2VjE#| zmA=gRk?QKfpw*y1o5>nw&e9c=+B&Awl2#v%dMCnS$hSp`jLp0vTONi&4q9)$RTBst zKV;3&Km6Ch@&c-9H=z7m2QufH%97*ccx5qLx{Zr6$iwW_W+0wEm4da;S9RE!%P4el zALQKVgvfcaUGi1%UAuuT<(9F0(fwb9K{Y7=XP(tVXIRDB_hM((_JK7h%iCjJCx3bl z{JjNH-Lnoun4}U`I`hJ{|FSDg8SMX@y0u0Ne9(gj(bLPzwG3a8|KWZ$l77AHt+X~?S0a1cc>PeA`g-$$YmqYAz zi-p9+dqX?+V&g=FOh#jTBdbi)5N05{6rGME_pI$t1fGS+6dWytbz3TfCC1YQ!%pgR z(qLP&aT-tAsKyE`BE1&#^|CTO)HEfAqTLo-;U?pdh1aQPEpvJOB-iXIxR9BiPK^qo$;S z%_*OCM*DYyf>VCbM6ZMScrq@K_2AAWYB)PMhmW;X& zd3GU;}L zi6DGhQC@k>LFFQ$LbFAN$@c;&L=74aZ=rZU87IP>v z%1bfi_Zl1MvrvBOMHZz+gfxj)^Aam$B6Ec5=6Y8%3F4$`-D@g5!5q~29~lsQ-h1(s_1tb`KxaZ z>RKgOBcLX{L+|clu13jQtt^1p67m%pj5zl)%9a!!a|j zY~wAJFjp(V0*Vd?cVcOi8U)w+9U*?DbG8D3S8^K;+tl^;@Hp45#d%F}ABES9<15{5 z8eomCV^3j^gE+iOLTGFzC?Qn)E#sdb(7ET!Zmw>o*X@<3yx=%iA5b>?$Gc1`uLY6^ zIP7(8(_gb^damx#{+cpt=SbqI&1M2r-dEhY8ftl|SL|PqUjKej^8NTR1Jq!)$KOUV zIa1OlfDZvul~|_Lb>Utnb}%~gU?q?0GSAD6p$B4UYUwp?)Yb}Qz)Xe<5b|T`5jJ@Z zIwQ6|#W}k>_el<_L-ic(*L3XVo;|$1V?)8qHI&{yGEpIfPB03xhIY^%?1dDQy^NNl zZ7W!lO~sOo8AN(uWww0|KeQs28+)VpTPw*Osq{I}sNj3yBwgP=)Fh6i@vlFYcb<@o-l{Z?qV8mqkfq za<8oJ&@Nu%LwuXZL&8{Pm|XMUzA$Vq+g{drxfGQq@Uw=^`f>gMcE#Dp%(vxxZLDGo zb7|7hWIwN{YqWGn|D*X)jnk>jm1zdzW(@KbSOGhkPKGz%gN>T+LBpE~UYN1A*NJgr z>#g!D>mR}Y$$8*CoqDfOcPm7&{ShVG7r_o?lm1SbkODSgk~?0CJzs>y;)lW=O4v)T z+9v|IWJR}3pm6;a1kcRo_d2|w_^KE>vp#m0v$Qx3Bm?E8lb3YMIP%Nvu012PCSM>A z;`cNzflXaiD0S%*t7zzaI9s~oNLLFKh@AN}`??4H@4=zr&O86lY<$@Mr6e0&+Iko` zzEksU%a*wrImtfJ+kg0&>wbC|zkBU0q0AX0ozEqL*N?jU&nbe-s{XN?S5*a^+mO>E_*$9%O!nE2xk9!Rgz`LS;6g{cA2r_zp!gWzh-96arsjD#z{B% z=d6M{ksN~qubH&3C$CC&`|GyVaZ56AbxyIh=~?%^w@)pad|0F6m`3;RBX5OH?{f3G zt$gW>xyCl;t#>s1tk$#p#9J?kWv{ugwph}mYpn)}dhUcSzT1$h!OM)!X`Gu~2jH_uo0 z@}XNMy`APApJPogna{|76R%ryl`%w(Csuk`4h9Qu`K7ypT1*T^1AmL+m3n~|FzZe;Y_#n zIl1poXDXv!#eIj5X=}4m&%Le@tq-ocUUB6qkA{YrOe%z z6Quiq`}Rid&BjHL_f)67X54)4Z1z93jbFE(os-`jsAkNiR}naWT~0vSdA*IN z-&_#Yy|tM8jNXSuwzc~%uGP-j;C6ItUxMJS3vUaT)F+ezucmpKIqUkl!b@xX-DgKk zcPxCrhCOn*{%hDlOu)rN2blg>Of2QnxV#!^Em9S?&lcbTPwKapEJr%FNi!9+7-{9@ zi`tiwPKSyDoeq_IDHwL-6YzAXDd5wguDpCP75Q{1UEt|Zon|jJk=9Fjflr5eRr2K& ek~N)TKk7rz?mEKv$Ap;y2s~Z=T-G@yGywn~58n9z literal 0 HcmV?d00001 diff --git a/buildr/doc/images/tip.png b/buildr/doc/images/tip.png new file mode 100644 index 0000000000000000000000000000000000000000..0fa6569089a0d94486700466abb682504e4406e5 GIT binary patch literal 4223 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

BlrBUT02a+?Qz&wPppIV9K5 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 literal 0 HcmV?d00001 diff --git a/buildr/doc/images/zbuildr.tif b/buildr/doc/images/zbuildr.tif new file mode 100644 index 0000000000000000000000000000000000000000..478b4b1b26f9eca679c1f44c46dc15980e6a266e GIT binary patch literal 3207784 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

c|)13!-jg%j13FKcB9KB z(?>VUPa8b~Zp%+w|AYM0wQm%ruHh6vWIT)81pSr$I2T~W+J@o3*PsSu6#jQJ{O4BO z1K%N_**g`;Yo`u*?lL9!T~oD^5`TM;_w}29=!gFN zjmROlV;u_Go``mbK-=>iYCtX#SzSX0a1%8UpCEtuf+58_LQ;4nCdnfu5k{B z|F{qBM?_NCb^dE2hT~x(EDgj&e=7hLyw(DSz#L2ivw%Oqhay)1&>tKQK5h-z1~BOF z;B+Pur(=S*fY((uB~HeqNK^dq6t)L6^gJobPSjYS=*~I)Q+0ML??4#F`F8%Jij5(Hvq^Vbf%+UT(Lg^45u2ya4P8x@*q&n z{H@%?{Hff*yjJRA{!kiZepeb|ep6b_yj0r2{HnB-`9*0D^OMq1=0~Lq%yXsBnD3Pz zGY^$tGWS(D<~tRRy{q~kzQy)E+yvwg6XE|&kUvboeoVxenAl*S9I-z>#OX#be`zN( ze`w`0ue8dUUp414KWnx!KWTO`FEkf1KWHvxo@uURo@%aRzSrEsJl5RBJkmVOJkUJH z+|&Go`9|w8^Og2X_A_mc{X~c3-3A})aQs^zz?nne0{-6|`NL%750fz_lPBS4d-}`N zgI-MtW_~e_V_q0#FwYE&nC}g%nMa0=%mc$V=DuM!^PRx}bJt*q`Nm*1bH`vK^OfNa z<_p7v>}Q5&*iQ^UW^WojVy_wf%DZgL@h=*4f(yo+@ch5vCPUi_`GXC7|1{(e)6g$d zC(vsf8~WMGiJndNrAHQ#%sq=_=3Dbz=4@vfSU@h+R~;axI2&c9%Gm4D9sJN_w)pM=LP{+2pw!AT!}#~}+&<~^JZ^rs?! zutNqg1N$*kmHx2TqZhVj^k|wbeLK~izML9NpG=8kKAMujUb87=uh>-bF4@fGU9fK9 zpR-=TKWn{6aN2sQ;FR@<@PzdS;W6tS!lTxQqz+qOkUD7dwbVYFA7pmhypi2$!-?<> zOK;hR?R&Uss6DU~BL6`KFbm&v=Zx<>a8jlFGxh1qnU-|R!HKRp_%at~Mlfe*Cb6ex zWb;ng7xRzVR|$^T)e8^VwMreZ>yp}U+b_M>c1UKA?HZX~wp(O(*zOf=w>>4=YI|F> z+3tzl2D{(Iqjorh>^aHWf5F*HA%|j76T_pQkpq11E<+DoB=p%VHM;I(k8+wYRf-#aT;xXZazYKL=;^meC4nXOK3vRfRxMVlQ5WXgFY_Ip4oovPVWf*R)dNaYux6^t#<1WkGS^954$c? z7;;^sxXg8{(o)xhN`tN!l^46-RbJ%wi%Opxr_u|0+&SeB8IP9#@I<{k{P$@Cuf3{WUT4)7czvbT;r*jpoA+OUQ=31VQ~eMuccf=% z|JT9rz2T_!j>J4W9BcluK4njsCT$Bhr%mB@%(^glc2%f9e>fyUxI8#PdPz{a>_A|i z+@gR|Nw0s6e7Aq2VwYdLQm0>^O1tln>U`f(^%mb<8qK~ZHRkz!s@drGT%*DNPr#|y z{|7Z}--41ydKiGU?$Ey!1N~Ucvt#hQy(m0$AX<$!#7v+yF;kh9(X-fPQ9k^^$WUQ_ zM2vKAShDQGuq?R+p@ou;kP3zQ!F5V4!L7>kg1S{3gO;k-2d&Yl3*4?*8+1&oCg`SC zbfqO!Rrr#4paIn1^Sd`{Ktk}`g=l!r1bF`>cny5yZjZw=2jZ1!b%GJCNU)}* z$bE~1lhKzbh(zuJjuL>QiXJ~mrY7gMBA6H}>F6dJ>P_y^KF=hJO{Qm*Mnb`<1Z}F4wEj)j+HIUN)hL0X36Jd z6e(t9RHsticWI=gE!9d&Td$Llwp%wo?HsrZe$|Oj90$j; z;r*e#xdfiC7@iNW7ZyrWTahv~7aLN2u_ZI7cm`Wp=Ju9*#xmZzzZF1-*FPUMiq@Wr{SnT#stXEvTa0mMJNB;T4w67UY!%NoAKt%4C!z zh|-GFCCSBk3W-H!N^ymARbvX<)T0U(Yep2T)(*?xp%a>a3Va2A!q0zeh2?SMXujt* z#^DT(gZ-HPZbc5Wwt74Rg6FD)@5EZE+G=^Ks?nyhIi^%RXDX9lD5q7KGUppm zTH9nwZnI+&+FaP!`QE(f`9Xq+`H@m#tqC&0E$O1bmVB{)bA`N5bEBg7yoJi1^M+L2 zn>MStH62!SZMvoA*7#h_t>KNDTRrzboJJq)L%(l<{;Fo23oYBCW;=EqlhJH6t={OLY7)m;8HvCU+Tj6E}hNJUK+&nS`x|kSdu7kTaqc9HCQa| zG*~O+FxW1dF*qn^H!v!;9oR3KK5$h$eew6=>HWV;ruT93AF>a}z<3Td0`0}!$RB#p z2hc8rcIr~(4=dpRM}*|NN`Ypt(kAcK6UlS64SB3_Ah$K{jLRBd#(7OB>$oPCow+8J zXTLh1Z@aomFl}{Lv|5wBRuP5@_h#G{=_%4hsD&(+5pJr|`rx{~Y$$rd{Y{xuk`c{9M zx;31cvNeIR-kQZ)ZY^aeZEfUPZ0+HhZ5`p8Zr#bBxb+-=;@ExOgv~Gc6E<;z2^;^* zM(D5TMGXe5aqDu_V2r^3u0;*T2GoFT!993bL+71D)A#VmdY^n3eFijXzd2d# zpGxKj9LVf|2bmrSpos?~$>d-%86V6iqk}b!!NCqj|KM^)_s|%lbLb?abMPBRXaCQP z_P#$^?Y-PTtb+ak+TMXauY(;ef_B;%GAQW!?LiI5e$-$b!a9tjSc7pwNcyM6qn*){oTAMUv{dR8l!#Ov>jQN$LC|QoOL96fPVh`3tv6 z;rtJzaP~DRoZv6qgB@50v=sW#iEnBGLz25`0j87a)+~~}j}h;alf-=T z6?hJQ`-h&D$bc~B;~7xaE@V*94mgJPpG5zkK@G?SWB`{@gK?cm3Gs#aQ-)-|6p-*M zG4a1rB;MB=1cQY_R5RR=nu0};PM93e#>6NB4`fRJ&qJB+-Qg{zB(IZ6^ zqN?GBlrFNC3CLKih@Q{<&qV#laH5|w051MNFTjm6boehgze1-hwTO#Ru$UYuI`w95r9WVGqKgvQwg7;R!dukwWF+jdz zLG;>|=r0fCBbXe#tp&k;ysb$Y!WIV|hXd{%oEq{6Rrp**AV&Tm1HUUo{=h^2K%pQJ z^k?B=h%J- zr)fZ(hA#F4C?JcFAb*fU{vhi{oKzrjf>`4C8K4OJRSlXzJLsmr*v0gQT~2?nBlL=0 zPcPXm^b50-eqs*N3+6OEXKv6_<{mv^UeIIq4L#y>^iTkV{|l!BpRJ8&E~vxzkMD;v z@&`ra4+_rMA7AWGBypk?kVk)s%IS@0F8v|fLa$^O&`a4q`c-xb{UW;p8P6JeA+wR5 z%WS8oGW+SV%t?AEbB*rHd`I8O{z%`5-q0O6j=mEAhcCq(^Bztg`GX$v2W?~kI@lL& zBlOF79yH@^9`v_T2>qcLPcId+=of`z`ca{peo$znX9{ifL}4L)uh37A6_(LM`4M^` zKT7xHx6)ntz4Wd8ak?XamA+88OP?tINVk;U&?!0iC;tUE5o;eNAb&7J z24If-!Ay<*o@hY7noOc+hBN4)fhXP7523I1qef5ILq@;z_8W1$ea0MbFEW`u zAHbPI-$H;a1~m}Y*bhs5_roN0dTeGy-%hlq&nG(5EmJ?bW*SMC&64SYSq^i~te81t zTFsm?ZD3BAwlc>}yV#?q{p=CbBUr8dTgaYUm=^iX=O%NtnKN%jVGP538v#VvCI*h zH0Gd99=qSVl-*}t!|t(e6euAMA|i#EdC)c!mq@ zpW(;snGwP4v`=KW*=Mk0b_Kl6cIEs{c60a}Y?}n@ZQBLwYcEIj0*~RvpZ2!L)k9B{8_I($4K6m5~?hIX;h583a=Mjum=(jWb57>1an9ziaw-(8bgB>zJJtz@9Gj(qFibCM5v?TC*bPk2tm z+ZTc`&kx4E&H&8yePw9OSCvNnjcK+26dLw-W|sNQW(R$PdHuf8{63!~LC@?=Vb|<@ zsZQ@QnGWwcvTa_?qE@d3;%2V_Nt5R)`3BE1g}Gjb6zjaMDAsyCRGj1eO0mY9Q+S6w zwr}|neeez1ehu2E@G*A~AGy2mF*6nk;8 zfpLPifK*|te~xssf00a+UzMoAuR(6EZ@Z+{caeOJ@32CZ?oB`r>B{gNney-wQE6C>xHxQ{d|_CZLVoB{rQFbU z%Gsg2RI)6McOtMsQbe2qEbfG9es#2UA)gYf8)v1^nIjEE# zxke=|a))Ya7WqJC0MiTYb5C5lr{jpCHv^KBr!KiYgO5yxRFJSV1KE3qBK z&)rE_caWq^Ey)Jdm^_*4lI@x5Bv-Z~$%j{(7|btDh!PYeBuM4Or_1ET=Zmu9%EcLR zbLG?G+7(k`7b_*ju2xBm-L4uRdqOQf_A~GTum4g>h~<=l(tEzbI9x!R52O7%Fzs5O z3BQGHAAasg6;gAWA~mGzQf;~gRi#g7%F~>g;xuozAT^Mmml`3+PKlGsOi7haPtKK1 zO)iy7PO6n8Ce2rfPgeBplekqiI`NoVRN^P#2fY4MB|4E)21@T4&jiQwxC3ba z)*Re#1%uhBeZbExnJm?3$x}_17FA@MQfc;7D#~_b^0PeHoGgD{W@eZmJu_CAnvpD> zl#wl)kX|eomtG@@NpDezO6ygMNE=oTPurpzmUcunH0_pZXzFv-u+%pyVX2&Qcq*s# zo|_nl@jPx%7BZN8qF*tijDJXlhN?`UOm4NIUDgjwfRRXi# zCRjEknRl1Z?ZAMAe z(A7YyM4SH;KMX`>3$?VL&9G-n|Ip4Oo zNieOqM>wT-M9QXThm>{C8E{w1df~6q)?J*m^@6v^U@Nl|eLf1^Wvw_LLH&I60kkur z9oGZz)sN>;4&pfs%j9U*G8J-Ou1}83&B$R0&!HG{Ap0SAvK#Uv+o3RK+E5%bWhjHO z9x7%nhw9kLLkoBoLqk0Cp{;zgp_6>m<#%|dOMm8@F8NDfI>`OQX0&|-y8WHVAD~;c z5Zz)JYvHK+j?MGe9x)Ih+On{AgOlN}0VyhEJ~cNmhvPIJ=VIfe9g zI*{%zchcDvK-#;aNNZOrY3?p2jol5Tw!4p1cdsRt-TO&-_jOX<^^}x%{6WgwIp#e> z-8c@=->qnKe9J42d<8CBDU5ey*G>MOAk{lLdicYkV?D6F!eS9lP9X}1e!RsGM`q*pm=UWz| z-R;Y9J%Dxr+MKcl8PE=_1KC65v>*D=HHB5zKaLFG6zUMpG9-6VnxrpDNa~Ub2`}l8 z;F1aPFHa`kWjkUoyW&GI0N_LD3QoE!IN`3Mqpv{V3f}a}Mg067cmaNT%L250F8aK9 zBQl_EI1lh^KG1c9t_^f2LRa@Jkt+UF{tA)ob<{xIVlnBE1@d^{kqQ<9YvE>;5z+ry zU_vw;LfC_bI`v8{cFg7Zldp@D}pX> zT=y<`03L(y!4vRQ2n`Vo3i{+nW#ldBlV6Qdb7F;?mX1Vk{D}UHM;=lLnvjVs259)- z==8sLyyZWc13bShV30+S8L$P&A1DaKflPq>fsj8?BmGTn^d~Ju=FyMLV=4WP9OM

9C;x&|hR;@n=NAJZUh|MY&=fEW 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%0AyZZNsN2aNCU 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%0A6B%e31J?Xc{f+sb@zb*Sp}*)a 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 z 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 JD==_6Tt>r^ox7H19;Wlf* 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%0D?KfN$! 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 z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1Vn7Ut0Wly3#DEwO17bi7hygJm2E>3E5CdXB z42S_SAO^&M7!U(uKn#chF(3xSfEW-1VxY1Z@OV7cFU;~(Bc0XP=kdHy<*8xb + +*In details:* The @gem install@ and @gem update@ commands install Buildr from a binary distribution provided through "RubyForge":http://rubyforge.org/projects/buildr. This distribution is maintained by contributors to this project, but is *not* an official Apache distribution. You can obtain the official Apache distribution files from the "download page":download.html. + +Older versions of RubyGems are all kind of fail. You want to avoid these unless you have the patience to install each Buildr dependency manually. Get RubyGems 1.3.1 or later, and when using Debian packages (e.g. Ubuntu), make sure to get the unmolested RubyGems straight form the source. + +The Ruby interpreter and JVM must use compatible architectures. For example, OS X comes with 32-bit version of Ruby, Java 1.5 in both 32-bit and 64-bit flavors, and 64-bit Java 6. As a result you can run Ruby with Java 1.5 (32-bit), but to use Java 6 you either need to build Ruby from source for 64-bit, or use "Buildr for JRuby":#jruby. + +h2(#linux). Installing on Linux + +*The easy way:* Use this bash script to "install Buildr on Linux":scripts/install-linux.sh. This script will install the most recent version of Buildr, or if already installed, upgrade to the most recent version. It will also install Ruby 1.8.6 if not already installed (requires @apt-get@, @yum@ or @urpmi@) and upgrade to RubyGems 1.3.1 or later. + +
+ +*In details:* To get started you will need a recent version of Ruby, Ruby Gems and build tools for compiling native libraries (@make@, @gcc@ and standard headers). + +On *RedHat/Fedora* you can use yum to install Ruby and RubyGems, and then upgrade to the most recent version of RubyGems: + +{% highlight sh %} +$ sudo yum install ruby rubygems ruby-devel gcc +$ sudo gem update --system +{% endhighlight %} + +On *Ubuntu* you have to install several packages: + +{% highlight sh %} +$ sudo apt-get install ruby-full ruby1.8-dev libopenssl-ruby build-essential +{% endhighlight %} + +If using Ubuntu 9.10 or earlier, the Debian package for @rubygems@ will not allow you to install Buildr, so you need to install RubyGems from source: + +{% highlight sh %} +$ wget http://rubyforge.org/frs/download.php/45905/rubygems-1.3.1.tgz +$ tar xzf rubygems-1.3.1.tgz +$ cd rubygems-1.3.1 +$ sudo ruby setup.rb +$ sudo ln -s /usr/bin/gem1.8 /usr/bin/gem +{% endhighlight %} + +Before installing Buildr, please set the @JAVA_HOME@ environment variable to point to your JDK distribution. Next, use Ruby Gem to install Buildr: + +{% highlight sh %} +$ sudo env JAVA_HOME=$JAVA_HOME gem install buildr +{% endhighlight %} + +To upgrade to a new version or install a specific version: + +{% highlight sh %} +$ sudo env JAVA_HOME=$JAVA_HOME gem update buildr +$ sudo env JAVA_HOME=$JAVA_HOME gem install buildr -v 1.4.3 +{% endhighlight %} + + +h2(#osx). Installing on OS X + +*The easy way:* Use this script to "install Buildr on OS X":scripts/install-osx.sh. This script will install the most recent version of Buildr, or if already installed, upgrade to the most recent version. It will also install Ruby 1.8.6 if not already installed (using MacPorts/Fink) and upgrage RubyGems to 1.3.1 or later. + +You need to have the Apple Development Tools installed. They are available on the Mac OSX installation CD. + +p(note). Java Update 3 for Snow Leopard removes header files necessary to compile the native Ruby-Java Bridge (RJB) gem, so installing rjb gem may fail on OS X. The solution is to install Java for Mac OS X 10.6 Update 3 Developer Package from http://connect.apple.com before @gem install@. + +*Using RVM?* If you're not using the built-in ruby on OS X (e.g., if you're using RVM), you'll need to force-install the platform-independent RJB: + +{% highlight sh %} +$ gem install rjb -v 1.3.3 --platform ruby +{% endhighlight %} + +The darwin pre-built binary seems to only work with the built-in ruby. + +
+ +*In details:* OS X 10.5 (Leopard) comes with a recent version of Ruby 1.8.6. You do not need to install a different version of Ruby when running OS X 10.5. + +OS X 10.4 (Tiger) includes an older version of Ruby that is not compatible with Buildr. You can install Ruby 1.8.6 using MacPorts (@sudo port install ruby rb-rubygems@), Fink or the "Ruby One-Click Installer for OS X":http://rubyosx.rubyforge.org/. + +We recommend you first upgrade to the latest version of Ruby gems: + +{% highlight sh %} +$ sudo gem update --system +{% endhighlight %} + +Before installing Buildr, please set the @JAVA_HOME@ environment variable to point to your JDK distribution: + +{% highlight sh %} +$ export JAVA_HOME=/Library/Java/Home +{% endhighlight %} + +To install Buildr: + +{% highlight sh %} +$ sudo env JAVA_HOME=$JAVA_HOME gem install buildr +{% endhighlight %} + +To upgrade to a new version or install a specific version: + +{% highlight sh %} +$ sudo env JAVA_HOME=$JAVA_HOME gem update buildr +$ sudo env JAVA_HOME=$JAVA_HOME gem install buildr -v 1.3.4 +{% endhighlight %} + +h2(#windows). Installing on Windows + +*The easy way:* The easiest way to install Ruby is using the "one-click installer":http://rubyinstaller.rubyforge.org/. Be sure to install Ruby 1.8.6; support for Ruby 1.9.x is still a work in progress. Once installed, set the @JAVA_HOME@ environment variable and run @gem install buildr --platform mswin32@. + +
+ +*In details:* Before installing Buildr, please set the @JAVA_HOME@ environment variable to point to your JDK distribution. Next, use Ruby Gem to install Buildr: + +{% highlight sh %} +> gem install buildr --platform mswin32 +{% endhighlight %} + +To upgrade to a new version or install a specific version: + +{% highlight sh %} +> gem update buildr +> gem install buildr -v 1.3.4 --platform mswin32 +{% endhighlight %} + +h2(#jruby). Installing for JRuby + +*The easy way:* Use this bash script to "install Buildr on JRuby":scripts/install-jruby.sh. This script will install the most recent version of Buildr, or if already installed, upgrade to the most recent version. If necessary, it will also install JRuby 1.6.1 in @/opt/jruby@ and update the @PATH@ variable in @~/.bash_profile@ or @~/.profile@. + +
+ +*In details:* If you don't already have JRuby 1.5.1 or later installed, you can download it from the "JRuby site":http://www.jruby.org/download. + +After uncompressing JRuby, update your @PATH@ to include both @java@ and @jruby@ executables. + +For Linux and OS X: + +{% highlight sh %} +$ export PATH=$PATH:[path to JRuby]/bin:$JAVA_HOME/bin +$ jruby -S gem install buildr +{% endhighlight %} + +For Windows: + +{% highlight sh %} +> set PATH=%PATH%;[path to JRuby]/bin;%JAVA_HOME%/bin +> jruby -S gem install buildr +{% endhighlight %} + +To upgrade to a new version or install a specific version: + +{% highlight sh %} +$ jruby -S gem update buildr +$ jruby -S gem install buildr -v 1.3.4 +{% endhighlight %} + + +*Important: Running JRuby and Ruby side by side* + +Ruby and JRuby maintain separate Gem repositories, and in fact install slightly different versions of the Buildr Gem (same functionality, different dependencies). Installing Buildr for Ruby does not install it for JRuby and vice versa. + +If you have JRuby installed but not Ruby, the @gem@ and @buildr@ commands will use JRuby. If you have both JRuby and Ruby installed, follow the instructions below. To find out if you have Ruby installed (some operating systems include it by default), run @ruby --version@ from the command line. + +To work exclusively with JRuby, make sure it shows first on the path, for example, by setting @PATH=/opt/jruby/bin:$PATH@. + +You can use JRuby and Ruby side by side, by running scripts with the @-S@ command line argument. For example: + +{% highlight sh %} +$ # with Ruby +$ ruby -S gem install buildr +$ ruby -S buildr +$ # with JRuby +$ jruby -S gem install buildr +$ jruby -S buildr +{% endhighlight %} + +Run @buildr --version@ from the command line to find which version of Buildr you are using by default. If you see @(JRuby ...)@, Buildr is running on that version of JRuby. + +h2. Using multiple versions of Buildr + +Rubygems makes it possible to install several versions of Buildr side-by-side on the same system. If you want to run a specific version, you can do so by adding the version number between underscores ('_') as the first command-line parameter. For example, + +{% highlight sh %} +$ buildr _1.3.4_ clean # runs Buildr v1.3.4 + +$ buildr _1.4.4_ clean # runs Buildr v1.4.4 +{% endhighlight %} + +p(note). There are two `buildr` executables installed by Rubygems. One script serves to select the specified (or default) version of Buildr and is typically found under `/usr/bin/buildr` or `/var/lib/gems/1.8/bin/buildr`. The exact location will vary depending on your system. The other script is the Buildr bootstrap per se and can be found under the specific version of Buildr, e.g, `/var/lib/gems/1.8/gems/buildr-1.4.0/bin/buildr`. The first script should be on your `PATH`. The second script should not be called directly and should not be on your `PATH`. + +h2(#running). Running Buildr + +You need a *Buildfile*, a build script that tells Buildr all about the projects it's building, what they contain, what to produce, and so on. The Buildfile resides in the root directory of your project. We'll talk more about it in "the next chapter":projects.html. If you don't already have one, ask Buildr to create it by running @buildr@. + +p(tip). You'll notice that Buildr creates a file called @buildfile@. It's case sensitive, but Buildr will look for either @buildfile@ or @Buildfile@. + +You use Buildr by running the @buildr@ command: + +{% highlight sh %} +$ buildr [options] [tasks] [name=value] +{% endhighlight %} + +There are several options you can use, for a full list of options type @buildr --help@: + +|_. Option |_. Usage | +| @-f/--buildfile [file]@ | Specify the buildfile. | +| @-e/--environment [name]@ | Environment name (e.g. development, test, production). | +| @-h/--help@ | Display this help message. | +| @-n/--nosearch@ | Do not search parent directories for the buildfile. | +| @-q/--quiet@ | Do not log messages to standard output. | +| @-r/--require [file]@ | Require MODULE before executing buildfile. | +| @-t/--trace@ | Turn on invoke/execute tracing, enable full backtrace. | +| @-v/--verbose@ | Log message to standard output | +| @-V/--version@ | Display the program version. | +| @-P/--prereqs@ | Display tasks and dependencies, then exit. | + +You can tell Buildr to run specific tasks and the order to run them. For example: + +{% highlight sh %} +# Clean and rebuild +buildr clean build +# Package and install +buildr install +{% endhighlight %} + +If you don't specify a task, Buildr will run the "@build@ task":building.html, compiling source code and running test cases. Running a task may run other tasks as well, for example, running the @install@ task will also run @package@. + +There are several "environment variables":settings_profiles.html#env_vars that let you control how Buildr works, for example, to skip test cases during a build, or specify options for the JVM. Depending on the variable, you may want to set it once in your environment, or set a different value each time you run Buildr. + +For example: + +{% highlight sh %} +$ export JAVA_OPTS='-Xms1g -Xmx1g' +$ buildr TEST=no +{% endhighlight %} + + +h2(#help). Help Tasks + +Buildr includes a number of informative tasks. Currently that number stands at two, but we'll be adding more tasks in future releases. These tasks report information from the Buildfile, so you need one to run them. For more general help (version number, command line arguments, etc) use @buildr --help@. + +To start with, type: + +{% highlight sh %} +$ buildr help +{% endhighlight %} + +You can list the name and description of all your projects using the @help:projects@ task. For example: + +{% highlight sh %} +$ buildr help:projects +killer-app # Code. Build. ??? Profit! +killer-app:teh-api # Abstract classes and interfaces +killer-app:teh-impl # All those implementation details +killer-app:la-web # What our users see +{% endhighlight %} + +You are, of course, describing your projects for the sake of those who will maintain your code, right? To describe a project, or a task, call the @desc@ method before the project or task definition. + +So next let's talk about "projects":projects.html. + + +h2(#more). Learning More + +*Ruby* It pays to pick up Ruby as a second (or first) programming language. It's fun, powerful and slightly addictive. If you're interested in learning Ruby the language, a good place to start is "Programming Ruby: The Pragmatic Programmer's Guide":http://www.pragprog.com/titles/ruby/programming-ruby, fondly known as the _Pickaxe book_. + +For a quicker read (and much more humor), "Why’s (Poignant) Guide to Ruby":http://poignantguide.net/ruby/ is available online. More resources are listed on the "ruby-lang web site":http://www.ruby-lang.org/en/documentation/. + +*Rake* Buildr is based on Rake, a Ruby build system that handles tasks and dependencies. Check out the "Rake documentation":http://docs.rubyrake.org/ for more information. + +*AntWrap* Buildr uses AntWrap, for configuring and running Ant tasks. You can learn more from the "Antwrap documentation":http://antwrap.rubyforge.org/. + +*YAML* Buildr uses YAML for its profiles. You can "learn more about YAML here":http://www.yaml.org, and use this handy "YAML quick reference":http://www.yaml.org/refcard.html. diff --git a/buildr/doc/languages.textile b/buildr/doc/languages.textile new file mode 100644 index 0000000..8d25fe6 --- /dev/null +++ b/buildr/doc/languages.textile @@ -0,0 +1,561 @@ +--- +layout: default +title: Languages +--- + + +h2(#java). Java + + +h3. Compiling Java + +The Java compiler looks for source files in the project's @src/main/java@ directory, and defaults to compiling them into the @target/classes@ directory. It looks for test cases in the project's @src/test/java@ and defaults to compile them into the @target/test/classes@ directory. + +If you point the @compile@ task at any other source directory, it will use the Java compiler if any of these directories contains files with the extension @.java@. + +When using the Java compiler, if you don't specify the packaging type, it defaults to JAR. If you don't specify the test framework, it defaults to JUnit. + +The Java compiler supports the following options: + +|_. Option |_. Usage | +| @:debug@ | Generates bytecode with debugging information. You can also override this by setting the environment variable @debug@ to @off@. | +| @:deprecation@ | If true, shows deprecation messages. False by default. | +| @:lint@ | Defaults to false. Set this option to true to use all lint options, or specify a specific lint option (e.g. @:lint=>'cast'@). | +| @:other@ | Array of options passed to the compiler (e.g. @:other=>'-implicit:none'@). | +| @:source@ | Source code compatibility (e.g. '1.5'). | +| @:target@ | Bytecode compatibility (e.g. '1.4'). | +| @:warnings@ | Issue warnings when compiling. True when running in verbose mode. | + + +h3. Testing with Java + +h4. JUnit + +The default test framework for Java projects is "JUnit 4":http://www.junit.org. + +When you use JUnit, the dependencies includes JUnit and "JMock":http://www.jmock.org, and Buildr picks up all test classes from the project by looking for classes that either subclass @junit.framework.TestCase@, include methods annotated with @org.junit.Test@, or test suites annotated with @org.org.junit.runner.RunWith@. + +The JUnit test framework supports the following options: + +|_. Option |_. Value | +| @:fork@ | VM forking, defaults to true. | +| @:clonevm@ | If true clone the VM each time it is forked. | +| @:properties@ | Hash of system properties available to the test case. | +| @:environment@ | Hash of environment variables available to the test case. | +| @:java_args@ | Arguments passed as is to the JVM. | + +For example, to pass properties to the test case: + +{% highlight ruby %} +test.using :properties=>{ :currency=>'USD' } +{% endhighlight %} + +There are benefits to running test cases in separate VMs. The default forking mode is @:once@, and you can change it by setting the @:fork@ option. + +|_. :fork=> |_. Behavior | +| @:once@ | Create one VM to run all test classes in the project, separate VMs for each project. | +| @:each@ | Create one VM for each test case class. Slow but provides the best isolation between test classes. | +| @false@ | Without forking, Buildr runs all test cases in a single VM. This option runs fastest, but at the risk of running out of memory and causing test cases to interfere with each other. | + +You can see your tests running in the console, and if any tests fail, Buildr will show a list of the failed test classes. In addition, JUnit produces text and XML report files in the project's @reports/junit@ directory. You can use that to get around too-much-stuff-in-my-console, or when using an automated test system. + +In addition, you can get a consolidated XML or HTML report by running the @junit:report@ task. For example: + +{% highlight sh %} +$ buildr test junit:report test=all +$ firefox report/junit/html/index.html +{% endhighlight %} + +The @junit:report@ task generates a report from all tests run so far. If you run tests in a couple of projects, it will generate a report only for these two projects. The example above runs tests in all the projects before generating the reports. + +You can use the @build.yaml@ settings file to specify a particular version of JUnit or JMock. For example, to force your build to use JUnit version 4.4 and JMock 2.0: + +{% highlight yaml %} +junit: 4.4 +jmock: 2.0 +{% endhighlight %} + + +h4. TestNG + +You can use "TestNG":http://testng.org instead of JUnit. To select TestNG as the test framework, add this to your project: + +{% highlight ruby %} +test.using :testng +{% endhighlight %} + +Like all other options you can set with @test.using@, it affects the projects and all its sub-projects, so you only need to do this once at the top-most project to use TestNG throughout. You can also mix TestNG and JUnit by setting different projects to use different frameworks, but you can't mix both frameworks in the same project. (And yes, @test.using :junit@ will switch a project back to using JUnit) + +TestNG works much like JUnit, it gets included in the dependency list along with JMock, Buildr picks test classes that contain methods annotated with @org.testng.annotations.Test@, and generates test reports in the @reports/testng@ directory. At the moment we don't have consolidated HTML reports for TestNG. + +The TestNG test framework supports the following options: + +|_. Option |_. Value | +| @:properties@ | Hash of system properties available to the test case. | +| @:java_args@ | Arguments passed as is to the JVM. | + +You can use the @build.yaml@ settings file to specify a particular version of TestNG, for example, to force your build to use TestNG 5.7: + +{% highlight yaml %} +testng: 5.7 +{% endhighlight %} + + +h4. JBehave + +"JBehave":http://jbehave.org/ is a pure Java BDD framework, stories and behaviour specifications are written in the Java language. + +To use JBehave in your project you can select it with @test.using :jbehave@. + +This framework will search for the following patterns under your project: + +{% highlight text %} +src/spec/java/**/*Behaviour.java +{% endhighlight %} + +Supports the following options: + +|_. Option |_. Value | +| @:properties@ | Hash of system properties available to the test case. | +| @:java_args@ | Arguments passed as is to the JVM. | + +You can use the @build.yaml@ settings file to specify a particular version of JBehave, for example, to force your build to use JBehave 1.0.1: + +{% highlight yaml %} +jbehave: 1.0.1 +{% endhighlight %} + + +h3. Documentation + +Buildr offers support for using JavaDoc to generate documentation from any Java sources in a project. This is done using the @doc@ task: + +{% highlight sh %} +$ buildr doc +{% endhighlight %} + +This will use the same @.java@ sources used by the @compile@ task to produce JavaDoc results in the @target/doc/@ directory. By default, these sources are chosen only from the current project. However, it is possible to override this and generate documentation from the sources in a sub-project (potentially more than one): + +{% highlight ruby %} +define 'foo' do + # ... + + doc.from projects('foo:bar', 'foo') + + define 'bar' do + # ... + end +end +{% endhighlight %} + +With this configuration, the @doc@ task will use sources from both @foo:bar@ and +@foo@. + +The @doc@ task supports any option that the @javadoc@ command does (e.g. @-windowtitle@). To pass an option to the JavaDoc generator, simply specify it using the @doc@ method: + +{% highlight ruby %} +define 'foo' do + # ... + + doc :windowtitle => 'Abandon All Hope, Ye Who Enter Here', :private => true +end +{% endhighlight %} + + +h2(#scala). Scala + +Before using Scala, you must first @require@ the Scala compiler: + +{% highlight ruby %} +require 'buildr/scala' +{% endhighlight %} + +By default, Buildr will attempt to use the latest stable release of Scala, which is currently Scala 2.9.0 as of May 2011. Of course you can configure a specific version of Scala for your project by adding the following entry in @build.yaml@: + +{% highlight yaml %} +scala.version: 2.8.0.Beta1 # Pick your version +{% endhighlight %} + +Or, you can do the same programmatically: + +{% highlight yaml %} +# Must be placed before require 'buildr/scala' +Buildr.settings.build['scala.version'] = "2.8.0.Beta1" +{% endhighlight %} + +You may also determine the version in use by querying the @Scala.version@ attribute: + +{% highlight ruby %} +Scala.version # => '2.8.0' +{% endhighlight %} + +Regardless of how the Scala version is determined, if you have the same Scala version installed on your system and the SCALA_HOME environment variable points to it, then your local installation will be used. Otherwise, Buildr will download it from the "Scala Tools repository":http://scala-tools.org/ which is automatically enlisted when you @require@ Scala. The only drawback if you don't have a local installation is the FSC compiler won't be available. + +p(tip). For Mac users, if you have installed Scala via "MacPorts":http://www.macports.org/ Buildr will look in the +@/opt/local/share/scala/@ directory if you have not set @SCALA_HOME@. + + +h3. Compiling Scala + +The Scala compiler looks for source files in the project's @src/main/scala@ directory, and defaults to compiling them into the @target/classes@ directory. It looks for test cases in the project's @src/test/scala@ and defaults to compile them into the @target/test/classes@ directory. + +Any Java source files found in the @src/main/java@ directory will be compiled using the Scala/Java joint compiler into the @target/classes@ directory. Both the Java and the Scala sources are compiled with an inclusive classpath, meaning that you may have a Java class which depends upon a Scala class which depends upon a Java class, all within the same project. The Java sources will be compiled with the same dependencies as the Scala sources with the addition of the @scala-library.jar@ file as required for Scala interop. + +Note that you cannot use the Groovy *and* the Scala joint compilers in the same project. If both are required, the Groovy joint compiler will take precedence. ++ +If you point the @compile@ task at any other source directory, it will use the Scala compiler if any of these directories contains files with the extension @.scala@. The joint compilation of Java sources may only be pointed at an alternative directory using the feature to redefine the @_(:src, :main, :java)@ path. + +When using the Scala compiler, if you don't specify the packaging type, it defaults to JAR. + +The Scala compiler supports the following options: + +|_. Option |_. Usage | +| @:debug@ | If true, generates bytecode with debugging information. Scala 2.9 also accepts: none,source,line,vars,notc. | +| @:deprecation@ | If true, shows deprecation messages. False by default. | +| @:make@ | Make strategy to be used by the compiler (e.g. @:make=>'transitive'@). *Scala 2.8 only* | +| @:optimise@ | Generates faster bytecode by applying optimisations to the program. | +| @:other@ | Array of options passed to the compiler (e.g. @:other=>'-Xprint-types'@). | +| @:target@ | Bytecode compatibility (e.g. '1.4'). | +| @:warnings@ | Issue warnings when compiling. True when running in verbose mode. | +| @:javac@ | A hash of options passed to the @javac@ compiler verbatim. | + +h4. Fast Scala Compiler + +You may use @fsc@, the Fast Scala Compiler, which submits compilation jobs to a compilation daemon, by setting the environment variable @USE_FSC@ to @yes@. Note that @fsc@ _may_ cache class libraries -- don't forget to run @fsc -reset@ if you upgrade a library. + +h4. Rebuild detection + +The Scala 2.7 compiler task assumes that each @.scala@ source file generates a corresponding @.class@ file under @target/classes@ (or @target/test/classses@ for tests). The source may generate more @.class@ files if it contains more than one class, object, trait or for anonymous functions and closures. + +For example, @src/main/scala/com/example/MyClass.scala@ should generate at least @target/classes/com/example/MyClass.class@. If that it not the case, Buildr will always recompile your sources because it will assume this is a new source file that has never been compiled before. + +Fortunately, Scala 2.8 provides a substantially better interface for implementing change detection. Whenever you use Scala 2.8 (see below), Buildr will auto-detect the version and enable this feature dynamically. After the @compile@ task runs, the relevant target directory will contain a @.scala-deps@ file, generated by the Scala compiler. The manner in which this file is used can be configured using the @:make@ compiler option. The following values are available: + +* @:all@ - Disables compiler-level change detection +* @:changed@ - Only build changed files without considering file dependencies +* @:immediate@ - *unknown* +* @:transitive@ - Build changed files as well as their transitive file dependencies +* @:transitivenocp@ - Build changed files as well as their transitive file dependencies (*default*) + +Please note that there are limits to compiler-level change detection. Most notably, dependencies cannot be tracked across separate compilation targets. This would cause problems in the case where an API has been changed in a main source file. The test suite for the project will *not* be detected as requiring recompilation, potentially resulting in unexpected runtime exceptions. When in doubt, run @clean@ to remove all dependency information. In extreme cases, it is possible to completely disable compiler-level change detection by adding the following statement to your project definition: + +{% highlight ruby %} +compile.using :make => :all +{% endhighlight %} + +Effectively, this is telling the Scala compiler to ignore the information it has built up regarding source file dependencies. When in this mode, only Buildr's change detection semantics remain in play (as described above). + +To avoid unusual behavior, compiler-level change detection is disabled whenever the joint Scala-Java compiler is used. Thus, any @.java@ files in a project handled by the Scala compiler will cause the @:make@ option to be ignored and revert to the exclusive use of Buildr's change detection mechanism (as described above). + +h4. Scala 2.8 + +As of version 1.4, Buildr has *non-default* support for Scala 2.8. If your @SCALA_HOME@ environment variable is pointing to an installation of Scala 2.8, then Buildr will use that compiler and enable 2.8-specific features. + +As Scala 2.8 is currently pre-release, it is often desirable to maintain an installation of Scala 2.7 concurrently with Scala 2.8, defaulting to the former with the option to select the latter on a project-by-project basis. This is most easily accomplished by setting @SCALA_HOME@ to point to the Scala 2.7 installation and @SCALA28_HOME@ to point to the Scala 2.8 installation. With this configuration in place, Scala 2.8 can be selected for a specific project by dynamically reassigning @SCALA_HOME@ at the top of the buildfile (*before* @require 'buildr/scala'@): + +{% highlight ruby %} +ENV['SCALA_HOME'] = ENV['SCALA28_HOME'] + +require 'buildr/scala' +... +{% endhighlight %} + +h3. Testing with Scala + +Buildr supports two main Scala testing frameworks: "ScalaTest":http://www.artima.com/scalatest and "Specs":http://code.google.com/p/specs/. "ScalaCheck":http://code.google.com/p/scalacheck/ is also supported within the confines of either of these two frameworks. Thus, your Specs may use ScalaCheck properties, as may your ScalaTest suites. + +{% highlight ruby %} +test.using(:scalatest) +{% endhighlight %} + +h4. ScalaTest + +ScalaTest support is activated automatically when there are any @.scala@ source files contained in the @src/test/scala@ directory. If you are not using this directory convention, you may force the test framework by using the @test.using :scalatest@ directive. + +Buildr automatically detects and runs tests that extend the @org.scalatest.Suite@ interface. + +A very simplistic test class might look like, + +{% highlight scala %} +class MySuite extends org.scalatest.FunSuite { + test("addition") { + val sum = 1 + 1 + assert(sum === 2) + } +} +{% endhighlight %} + +You can also pass properties to your tests by doing @test.using :properties => { 'name'=>'value' }@, and by overriding the @Suite.runTests@ method in a manner similar to: + +{% highlight scala %} +import org.scalatest._ + +class PropertyTestSuite extends FunSuite { + var properties = Map[String, Any]() + + test("testProperty") { + assert(properties("name") === "value") + } + + protected override def runTests(testName: Option[String], + reporter: Reporter, stopper: Stopper, includes: Set[String], + excludes: Set[String], properties: Map[String, Any]) + { + this.properties = properties; + super.runTests(testName, reporter, stopper, + includes, excludes, properties) + } +} +{% endhighlight %} + +h4. Specs + +Specs is automatically selected whenever there are @.scala@ source files under the @src/spec/scala@ directory. It is also possible to force selection of the test framework by using the @test.using :specs@ directive. This can sometimes be useful when Scala sources may be found in *both* @src/test/scala@ and @src/spec/scala@. Normally in such cases, ScalaTest will have selection precedence, meaning that in case of a conflict between it and Specs, ScalaTest will be chosen. + +Any objects which extend the @org.specs.Specification@ superclass will be automatically detected and run. Note that any *classes* which extend @Specification@ will also be invoked. As such classes will not have a @main@ method, such an invocation will raise an error. + +A simple specification might look like this: + +{% highlight scala %} +import org.specs._ +import org.specs.runner._ + +object StringSpecs extends Specification { + "empty string" should { + "have a zero length" in { + "".length mustBe 0 + } + } +} +{% endhighlight %} + +ScalaCheck is automatically added to the classpath when Specs is used. However, JMock, Mockito, CGlib and similar are _not_. This is to avoid downloading extraneous artifacts which are only used by a small percentage of specifications. To use Specs with Mockito (or any other library) in a Buildr project, simply add the appropriate dependencies to @test.with@: + +{% highlight ruby %} +MOCKITO = 'org.mockito:mockito-all:jar:1.7' +CGLIB = 'cglib:cglib:jar:2.1_3' +ASM = 'asm:asm:jar:1.5.3' +OBJENESIS = 'org.objenesis:objenesis:jar:1.1' + +define 'killer-app' do + ... + + test.with MOCKITO, CGLIB, ASM, OBJENESIS +end +{% endhighlight %} + +The dependencies for Specs's optional features are defined "here":http://code.google.com/p/specs/wiki/RunningSpecs#Dependencies. + +h4. ScalaCheck + +You may use ScalaCheck inside ScalaTest- and Specs-inherited classes. Here is an example illustrating checks inside a ScalaTest suite, + +{% highlight scala %} +import org.scalatest.prop.PropSuite +import org.scalacheck.Arbitrary._ +import org.scalacheck.Prop._ + +class MySuite extends PropSuite { + + test("list concatenation") { + val x = List(1, 2, 3) + val y = List(4, 5, 6) + assert(x ::: y === List(1, 2, 3, 4, 5, 6)) + check((a: List[Int], b: List[Int]) => a.size + b.size == (a ::: b).size) + } + + test( + "list concatenation using a test method", + (a: List[Int], b: List[Int]) => a.size + b.size == (a ::: b).size + ) +} +{% endhighlight %} + + +h3. Documentation + +Buildr offers support for using ScalaDoc or VScalaDoc to generate documentation from any Scala sources in a project. This is done using the @doc@ task: + +{% highlight sh %} +$ buildr doc +{% endhighlight %} + +This will use the same @.scala@ sources used by the @compile@ task to produce ScalaDoc results in the @target/doc/@ directory. By default, these sources are chosen only from the current project. However, it is possible to override this and generate documentation from the sources in a sub-project (potentially more than one): + +{% highlight ruby %} +define 'foo' do + # ... + + doc.from projects('foo:bar', 'foo') + + define 'bar' do + # ... + end +end +{% endhighlight %} + +With this configuration, the @doc@ task will use sources from both @foo:bar@ and +@foo@. + +The @doc@ task supports any option that the @scaladoc@ command does (e.g. @-windowtitle@). To pass an option to the ScalaDoc (or VScalaDoc) generator, simply specify it using the @doc@ method: + +{% highlight ruby %} +define 'foo' do + # ... + + doc :windowtitle => 'Abandon All Hope, Ye Who Enter Here', :private => true +end +{% endhighlight %} + +By default, the @doc@ task will use the ScalaDoc generator on Scala projects. To select the VScalaDoc generator, you must use the @doc.using@ invocation: + +{% highlight ruby %} +define 'foo' do + doc.using :vscaladoc +end +{% endhighlight %} + +The @doc@ task is *not* joint-compilation aware. Thus, it will only generate ScalaDoc for mixed-source projects, it will not attempt to generate both JavaDoc and ScalaDoc. + + +h2(#groovy). Groovy + +h3. Compiling Groovy + +Before using the Groovy compiler, you must first require it on your buildfile: + +{% highlight ruby %} +require 'buildr/java/groovyc' +{% endhighlight %} + +Once loaded, the groovyc compiler will be automatically selected if any .groovy source files are found under @src/main/groovy@ directory, compiling them by default into the @target/classes@ directory. + +If the project has java sources in @src/main/java@ they will get compiled using the groovyc joint compiler. + +Sources found in @src/test/groovy@ are compiled into the @target/test/classes@. + +If you don't specify the packaging type, it defaults to JAR. + +The Groovy compiler supports the following options: + +|_. Option |_. Usage | +| @encoding@ | Encoding of source files. | +| @verbose@ | Asks the compiler for verbose output, true when running in verbose mode. | +| @fork@ | Whether to execute groovyc using a spawned instance of the JVM. Defaults to no. | +| @memoryInitialSize@ | The initial size of the memory for the underlying VM, if using fork mode, ignored otherwise. Defaults to the standard VM memory setting. (Examples: @83886080@, @81920k@, or @80m@) | +| @memoryMaximumSize@ | The maximum size of the memory for the underlying VM, if using fork mode, ignored otherwise. Defaults to the standard VM memory setting. (Examples: @83886080@, @81920k@, or @80m@) | +| @listfiles@ | Indicates whether the source files to be compiled will be listed. Defaults to no. | +| @stacktrace@ | If true each compile error message will contain a stacktrace. | +| @warnings@ | Issue warnings when compiling. True when running in verbose mode. | +| @debug@ | Generates bytecode with debugging information. Set from the debug environment variable/global option. | +| @deprecation@ | If true, shows deprecation messages. False by default. | +| @optimise@ | Generates faster bytecode by applying optimisations to the program. | +| @source@ | Source code compatibility. | +| @target@ | Bytecode compatibility. | +| @javac@ | Hash of options passed to the ant javac task. | + + +h3. Testing with Groovy + +h4. EasyB + +"EasyB":http://www.easyb.org/ is a BDD framework using "Groovy":http://groovy.codehaus.org/. + +Specifications are written in the Groovy language, of course you get seamless Java integration as with all things groovy. + +To use this framework in your project you can select it with @test.using :easyb@. + +This framework will search for the following patterns under your project: + +{% highlight text %} +src/spec/groovy/**/*Behavior.groovy +src/spec/groovy/**/*Story.groovy +{% endhighlight %} + +Supports the following options: + +|_. Option |_. Value | +| @:properties@ | Hash of system properties available to the test case. | +| @:java_args@ | Arguments passed as is to the JVM. | +| @:format@ | Report format, either @:txt@ or @:xml@ | + + +h3. Documentation + +Buildr offers support for using GroovyDoc to generate documentation from any Groovy sources in a project. This is done using the @doc@ task: + +{% highlight sh %} +$ buildr doc +{% endhighlight %} + +This will use the same @.groovy@ sources used by the @compile@ task to produce GroovyDoc results in the @target/doc/@ directory. By default, these sources are chosen only from the current project. However, it is possible to override this and generate documentation from the sources in a sub-project (potentially more than one): + +{% highlight ruby %} +define 'foo' do + # ... + + doc.from projects('foo:bar', 'foo') + + define 'bar' do + # ... + end +end +{% endhighlight %} + +With this configuration, the @doc@ task will use sources from both @foo:bar@ and +@foo@. + +The @doc@ task supports any option that the @groovydoc@ command does (e.g. @-windowtitle@). To pass an option to the GroovyDoc generator, simply specify it using the @doc@ method: + +{% highlight ruby %} +define 'foo' do + # ... + + doc :windowtitle => 'Abandon All Hope, Ye Who Enter Here', :private => true +end +{% endhighlight %} + +The @doc@ task is *not* joint-compilation aware. Thus, it will only generate GroovyDoc for mixed-source projects, it will not attempt to generate both JavaDoc and GroovyDoc. + + +h2(#ruby). Ruby + +h3. Testing with Ruby + +Buildr provides integration with some ruby testing frameworks, allowing you to test your Java code with state of the art tools. + +Testing code is written in "Ruby":http://www.ruby-lang.org/en/ language, and is run by using "JRuby":http://jruby.codehaus.org/. That means you have access to all your Java classes and any Java or Ruby tool out there. + +Because of the use of JRuby, you will notice that running ruby tests is faster when running Buildr on JRuby, as in this case there's no need to run another JVM. + +p(tip). When not running on JRuby, Buildr will use the @JRUBY_HOME@ environment variable to find the JRuby installation directory. If no @JRUBY_HOME@ is set or it points to an empty directory, Buildr will prompt you to either install JRuby manually or let it extract it for you. + +You can use the @build.yaml@ settings file to specify a particular version of JRuby (defaults to @1.4.0@ as of Buildr 1.3.5). For example: + +{% highlight yaml %} +jruby: 1.3.1 +{% endhighlight %} + +h4. RSpec + +"RSpec":http://rspec.info/ is the de-facto BDD framework for ruby. It's the framework used to test Buildr itself. + +To use this framework in your project you can select it with @test.using :rspec@. + +This framework will search for the following patterns under your project: + +{% highlight text %} +src/spec/ruby/**/*_spec.rb +{% endhighlight %} + +Supports the following options: + +|_. Option |_. Value | +| @:gems@ | Hash of gems needed before running the tests. Keys are gem names, values are the required gem version. An example use of this option would be to require the ci_reporter gem to generate xml reports | +| @:requires@ | Array of ruby files to require before running the specs | +| @:format@ | Array of valid RSpec @--format@ option values. Defaults to html report on the @reports@ directory and text progress | +| @:output@ | File path to output dump. @false@ to supress output | +| @:fork@ | Run the tests on a new java vm. (enabled unless running on JRuby) | +| @:properties@ | Hash of system properties available to the test case. | +| @:java_args@ | Arguments passed as is to the JVM. (only when fork is enabled) | + diff --git a/buildr/doc/mailing_lists.textile b/buildr/doc/mailing_lists.textile new file mode 100644 index 0000000..bd1c6e9 --- /dev/null +++ b/buildr/doc/mailing_lists.textile @@ -0,0 +1,29 @@ +--- +layout: default +title: Mailing Lists +--- + +

Search all Buildr archives:
+ +|_. users |_. For developers working with Buildr | +| Post | "users@buildr.apache.org":mailto:users@buildr.apache.org | +| Search | "http://buildr.markmail.org/search/list:users":http://buildr.markmail.org/search/list:users | +| Subscribe | "users-subscribe@buildr.apache.org":mailto:users-subscribe@buildr.apache.org | +| Unsubscribe | "users-unsubscribe@buildr.apache.org":mailto:users-unsubscribe@buildr.apache.org | +|_. dev |_. For development of Buildr itself | +| Post | "dev@buildr.apache.org":mailto:dev@buildr.apache.org | +| Search | "http://buildr.markmail.org/search/list:dev":http://buildr.markmail.org/search/list:dev | +| Subscribe | "dev-subscribe@buildr.apache.org":mailto:dev-subscribe@buildr.apache.org | +| Unsubscribe | "dev-unsubscribe@buildr.apache.org":mailto:dev-unsubscribe@buildr.apache.org | +|_. commits |_. Commit messages and JIRA status | +| Search | "http://buildr.markmail.org/search/list:commits":http://buildr.markmail.org/search/list:commits | +| Subscribe | "commits-subscribe@buildr.apache.org":mailto:commits-subscribe@buildr.apache.org | +| Unsubscribe | "commits-unsubscribe@buildr.apache.org":mailto:commits-unsubscribe@buildr.apache.org | +|_. ci |_. Continuous integration status | +| Search | "http://buildr.markmail.org/search/list:ci":http://buildr.markmail.org/search/list:ci | +| Subscribe | "ci-subscribe@buildr.apache.org":mailto:ci-subscribe@buildr.apache.org | +| Unsubscribe | "ci-unsubscribe@buildr.apache.org":mailto:ci-unsubscribe@buildr.apache.org | + + + + diff --git a/buildr/doc/more_stuff.textile b/buildr/doc/more_stuff.textile new file mode 100644 index 0000000..8a22f60 --- /dev/null +++ b/buildr/doc/more_stuff.textile @@ -0,0 +1,1001 @@ +--- +layout: default +title: More Stuff +--- + +h2(#shell). Interactive Shells (REPLs) + +Many languages (including Scala and Groovy) provide an interactive shell tool which allows developers to run arbitrary expressions and see the results immediately: + +
$ scala
+Welcome to Scala version 2.7.4.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_03-p3).
+Type in expressions to have them evaluated.
+Type :help for more information.
+
+scala> 6 * 7
+res0: Int = 42
+
+scala> 
+ +These tools are alternatively known as "REPLs" (Read, Eval, Print Loop), a term which originally comes from Lisp. This sort of interactive shell can be an invaluable asset when developing frameworks or other non-UI-oriented modules. A common use-case is running a quick, manual test to make sure that the framework is functioning properly. It is faster and easier to do this in a shell, and also averts the need for extra test cases to be developed just to check simple things during development. + +Unfortunately, for such a tool to be useful in Java, Scala or Groovy development, it must have access to the @CLASSPATH@, not only the compiled project binaries, but also its full list of dependencies. While it is usually possible with such tools to specify the classpath upon startup (e.g. the @-cp@ switch for the Scala shell), this can get extremely tedious for project's with more dependencies, especially when some of these dependencies are defined using @transitive@ artifacts. + +Buildr is capable of easing this workflow by providing full support for the configuration and launch of interactive shells, the relevant shell may be launched simply by invoking the @shell@ task: + +{% highlight sh %} +$ buildr shell +{% endhighlight %} + +The @shell@ task depends upon @compile@, meaning that any changed source files will be recompiled prior to the shell's launch. Tests will not be run, nor will test files be recompiled. From within the shell itself, you should have access to your project's compilation classpath (anything specified using @compile.with@) as well as the compiled sources for your project. + +The project classpath is determined by checking the current working directory of your system shell (the path from which the @buildr shell@ command was invoked) and recursively finding the relevant project for that directory. Thus, if you have a parent project @foo@ which has sub-projects @bar@ and @baz@, you may invoke an interactive shell for the @baz@ project simply by @cd@-ing into its project directory (or any of its sub-directories) and invoking the @shell@ task. You may also invoke a shell against a specific project by giving its fully-qualified descriptor as a prefix to the @shell@ task: + +{% highlight sh %} +$ buildr foo:bar:shell +{% endhighlight %} + +h3. Supported Shells + +By default, Buildr will open the interactive shell which corresponds to your project's language. Thus, if your project is using Groovy, it will invoke the @groovysh@ command, configured with the appropriate classpath. If your project is using Scala, then the @shell@ task will invoke the @scala@ command. Unfortunately, the Java language does not define an interactive shell of any kind, however for those projects using exclusively the Java language, Buildr will use the "BeanShell":http://www.beanshell.org/manual/quickstart.html#The_BeanShell_GUI console. + +However, you may still wish to exploit the advantages of an interactive shell from some other JVM language even while working in Java. Alternatively, you may want to use some shell other than the default even when working in a language which has its own. There are two ways to acheive this aim. The quickest way is to explicitly specify the relevant shell at the call-site: + +{% highlight sh %} +$ buildr foo:shell:jirb +{% endhighlight %} + +This will open the JIRB shell (the JRuby REPL) for the @foo@ project. Whenever you are specifying a shell explicitly in this fashion, you *must* fully-qualify the project name: + +{% highlight sh %} +$ buildr shell:jirb # wrong!! +{% endhighlight %} + +The above will fail because of the way that Rake tasks are dispatched. + +Obviously, this explicit specification is a little bit clunky. It would be much easier if we could simply say that we *always* want to use the JIRB shell for this particular project. This can be done by using the @shell.using@ directive within your project definition: + +{% highlight ruby %} +define 'foo' do + shell.using :jirb +end +{% endhighlight %} + +With this project definition, we can now invoke the @shell@ task and JIRB will be launched, regardless of the default for our project's language: + +{% highlight sh %} +$ buildr shell +{% endhighlight %} + +Buildr supports several different shell providers, and the framework is flexible enough that additional support can be added almost trivially. The following table gives the complete list of supported shells, their corresponding @shell.using@ descriptor and the language for which they are the default (if applicable): + +|_. Shell Name |_. Descriptor |_. Language | +| BeanShell Console | @:bsh@ | Java | +| Clojure REPL | @:clj@ | *N/A* | +| GroovySH | @:groovysh@ | Groovy | +| JRuby IRB | @:jirb@ | *N/A* | +| Scala Interpreter | @:scala@ | Scala | + +Note that some of these shells impose certain requirements to enable use. The Groovy shell requires the @GROOVY_HOME@ environment variable to point to the Groovy install path. The Clojure REPL makes a similar requirement of @CLOJURE_HOME@. The JRuby and Scala shells will use @JRUBY_HOME@ and @SCALA_HOME@ respectively if they are defined. However, if these environment variables are not defined, the relevant JAR files will be automatically downloaded from the appropriate Maven2 repository. + +h3. Verbosity and Tracing + +By default, Buildr is moderately verbose, meaning that it attempts to give you enough context into what's happening during the build. + +It's possible to silence Buildr if you're inconvenienced by its default verbosity by issuing, + +{% highlight sh %} +$ buildr --silent +{% endhighlight %} + +On the other hand, if you want Buildr to give you more context in order to trace what's happening, you can use the @-t@ options: + +{% highlight sh %} +$ buildr -t +{% endhighlight %} + +Using @-t@ will also display backtraces if and when they occur. + +Many components can be individually configured to display more output if you're debugging a specific area of your build. For instance, you could use @--trace=javac,groovyc@ to enable tracing of the Java and Groovy compilers: + +{% highlight sh %} +$ buildr --trace=javac,groovyc +{% endhighlight %} + +If you don't know which tracing category you need to enable or if you want a full firehose worth of traces, you can enable all traces: + +{% highlight sh %} +$ buildr --trace=all +{% endhighlight %} + +h3(#javarebel). JavaRebel Integration + +"JavaRebel":http://www.zeroturnaround.com/javarebel is a live bytecode reloading solution by Zero Turnaround. It's a lot like the hot code reload feature found in many Java IDE debuggers (like Eclipse and IntelliJ), but capable of handling things like member addition or removal and new class definition. The tool itself is commercial and works with any JVM language, but they do offer a free license for use with Scala classes only. + +If available, Buildr will use JavaRebel when configuring the launch for each interactive shell. Shells launched with JavaRebel integration will automatically receive recompiled changes on the fly without any need to restart the shell. There are some limitations to this which are specific to the shell in question, but for the most part, things Just Work. + +JavaRebel auto-magical integration is done by searching for a valid JavaRebel install path in the following list of environment variables (in order): + +* @REBEL_HOME@ +* @JAVA_REBEL@ +* @JAVAREBEL@ +* @JAVAREBEL_HOME@ + +These environment variables may point to either the JavaRebel install directory (e.g. @~/javarebel-2.0.1@), or the JAR file itself (e.g. @~/javarebel-2.0.1/javarebel.jar@). If the path is valid, Buildr will automatically append the appropriate JVM switches to the launch configurations of each shell: + +
$ buildr shell
+(in ~/foo, development)
+Compiling foo into ~/foo/target/classes
+Running java scala.tools.nsc.MainGenericRunner
+
+#############################################################
+
+ ZeroTurnaround JavaRebel 2.0.1 (200905251513)
+ (c) Copyright Webmedia, Ltd, 2007-2009. All rights reserved.
+
+ This product is licensed to Daniel Spiewak
+ for personal use only.
+
+#############################################################
+
+Welcome to Scala version 2.7.4.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_03-p3).
+Type in expressions to have them evaluated.
+Type :help for more information.
+
+scala> 
+ +Note that Buildr does *not* check to make sure that you have a valid JavaRebel license, so you may end up launching with JavaRebel configured but without the ability to use it (in which case, JavaRebel will print a notification). + +h2(#run). Running Your Application + +The @run@ task lets you easily run programs from your buildfile, such as launching your own application. + +In its simplest form, you simply define the main class of your Java application, + +{% highlight ruby %} +define 'my-project' do + compile.with COMMONS_IO, HTTPCLIENT + run.using :main => "org.example.Main" +end +{% endhighlight %} + +And then run, + +{% highlight ruby %} +~/my-project$ buildr run +{% endhighlight %} + +which would launch your application using the project's compile classpath. + +It's also possible to pass arguments to the JVM using the @:java_args@ option: + +{% highlight ruby %} + run.using :main => "org.example.Main", + :java_args => ["-server"] +{% endhighlight %} + +If your application requires arguments, you can pass in an array of values for the @:main@ option, or provide a set of system properties using @:properties@. + +{% highlight ruby %} + run.using :main => ["org.example.Main", "-t", "input.txt"], + :properties => { :debug => "true" } +{% endhighlight %} + +The @run@ task is a local task, which means that Buildr will automatically pick the @run@ task matching the project in the current directory. Executing the following command: + +{% highlight ruby %} +~/my-project/subproject$ buildr run +{% endhighlight %} + +will run the @my-project:subproject:run@ task, assuming @my-project@ is your top-level project. + +Here is a summary of @run.using@ options, + +|_. Option |_. Description ... | +| @:main@ | The java main class, e.g. "com.example.Main". Can also be an array if the main class requires arguments. | +| @:properties@ | A hash of system properties to be passed to java. | +| @:java_args@ | An array of additional parameters to pass to java | +| @:classpath@ | An array of additional classpath elements (i.e. artifacts, files, etc.). By default, the @run@ task automatically uses the @compile.dependencies@, @test.dependencies@ and @test.compile.target@ of your project. | + +p(note). The @run@ task also detects and uses JavaRebel if it's available. See the "JavaRebel":#javarebel section for details. + +h2(#gems). Using Gems + +The purpose of the buildfile is to define your projects, and the various tasks and functions used for building them. Some of these are specific to your projects, others are more general in nature, and you may want to share them across projects. + +There are several mechanisms for developing extensions and build features across projects which we cover in more details in the section "Extending Buildr":extending.html. Here we will talk about using extensions that are distributed in the form of RubyGems. + +"RubyGems":http://rubygems.rubyforge.org provides the @gem@ command line tool that you can use to search, install, upgrade, package and distribute gems. It installs all gems into a local repository that is shared across your builds and all other Ruby applications you may have running. You can install a gem from a local file, or download and install it from any number of remote repositories. + +RubyGems is preconfigured to use the "RubyForge":http://rubyforge.org repository. You'll find a large number of open source Ruby libraries there, including Buildr itself and all its dependencies. RubyForge provides a free account that you can use to host your projects and distribute your gems (you can use RubyForge strictly for distribution, as we do with Buildr). + +You can also set up your own private repository and use it instead or in addition to RubyForge. Use the @gem sources@ command to add repositories, and the @gem server@ command to run a remote repository. You can see all available options by running @gem help@. + +If your build depends on other gems, you will want to specify these dependencies as part of your build and check that configuration into source control. That way you can have a specific environment that will guarantee repeatable builds, whether you're building a particular version, moving between branches, or joining an existing project. Buildr will take care of installing all the necessary dependencies, which you can then manage with the @gem@ command. + +Use the @build.yaml@ file to specify these dependencies (see "Build Settings":settings_profiles.html#build for more information), for example: + +{% highlight yaml %} +# This project requires the following gems +gems: + # Suppose we want to notify developers when testcases fail. + - buildr-twitter-notifier-addon >=1 + # we test with ruby mock objects + - mocha + - ci_reporter +{% endhighlight %} + +Gems contain executable code, and for that reason Buildr will not install gems without your permission. When you run a build that includes any dependencies that are not already installed on your machine, Buildr will ask for permission before installing them. On Unix-based operating systems, you will also need sudo privileges and will be asked for your password before proceeding. + +Since this step requires your input, it will only happen when running Buildr interactively from the command line. In all other cases, Buildr will fail and report the missing dependencies. If you have an automated build environment, make sure to run the build once manually to install all the necessary dependencies. + +When installing a gem for the first time, Buildr will automatically look for the latest available version. You can specify a particular version number, or a set of version numbers known to work with that build. You can use equality operations to specify a range of versions, for example, @1.2.3@ to install only version 1.2.3, and @=> 1.2.3@ to install version 1.2.3 or later. + +You can also specify a range up to one version bump, for example, @~> 1.2.3@ is the same as @>= 1.2.3 < 1.3.0@, and @~> 1.2@ is the same as @>= 1.2.0 < 2.0.0@. If necessary, you can exclude a particular version number, for example, @~> 1.2.3 != 1.2.7@. + +Buildr will install the latest version that matches the version requirement. To keep up with newer versions, execute the @gem update@ command periodically. You can also use @gem outdated@ to determine which new versions are available. + +Most gems include documentation that you can access in several forms. You can use the @ri@ command line tool to find out more about a class, module or specific method. For example: + +{% highlight sh %} +$ ri Buildr::Jetty +$ ri Buildr::Jetty.start +{% endhighlight %} + +You can also access documentation from a Web browser by running @gem server@ and pointing your browser to "http://localhost:8808":http://localhost:8808. Note that after installing a new gem, you will need to restart the gem server to see its documentation. + + +h2(#java). Using Java Libraries + +Buildr runs along side a JVM, using either RJB or JRuby. The Java module allows you to access Java classes and create Java objects. + +Java classes are accessed as static methods on the @Java@ module, for example: + +{% highlight ruby %} +str = Java.java.lang.String.new('hai!') +str.toUpperCase +=> 'HAI!' +Java.java.lang.String.isInstance(str) +=> true +Java.com.sun.tools.javac.Main.compile(args) +{% endhighlight %} + +The @classpath@ attribute allows Buildr to add JARs and directories to the classpath, for example, we use it to load Ant and various Ant tasks, code generators, test frameworks, and so forth. + +When using an artifact specification, Buildr will automatically download and install the artifact before adding it to the classpath. + +For example, Ant is loaded as follows: + +{% highlight ruby %} +Java.classpath << 'org.apache.ant:ant:jar:1.7.0' +{% endhighlight %} + +Artifacts can only be downloaded after the Buildfile has loaded, giving it a chance to specify which remote repositories to use, so adding to classpath does not by itself load any libraries. You must call @Java.load@ before accessing any Java classes to give Buildr a chance to load the libraries specified in the classpath. + +When building an extension, make sure to follow these rules: + +# Add to the @classpath@ when the extension is loaded (i.e. in module or class definition), so the first call to @Java.load@ anywhere in the code will include the libraries you specify. +# Call @Java.load@ once before accessing any Java classes, allowing Buildr to set up the classpath. +# Only call @Java.load@ when invoked, otherwise you may end up loading the JVM with a partial classpath, or before all remote repositories are listed. +# Check on a clean build with empty local repository. + + +h2(#buildr-server). BuildrServer + +Buildr provides an addon that allows you start a "dRuby":http://www.ruby-doc.org/stdlib/libdoc/drb/rdoc/index.html server hosting a buildfile, so that you can later invoke tasks on it without having to load the complete buildr runtime again. + +Usage: + +{% highlight sh %} +buildr -r buildr/drb drb:start +{% endhighlight %} + +To stop the BuildrServer simply use Ctrl+C or kill the process. + +Once the server has been started you can invoke tasks using a simple script: + +{% highlight ruby %} +#!/usr/bin/env ruby +require 'rubygems' +require 'buildr/drb' +Buildr::DRbApplication.run +{% endhighlight %} + +Save this script as @dbuildr@, make it executable and use it to invoke tasks. + + +{% highlight sh %} +$ dbuildr clean compile +{% endhighlight %} + + +The @dbuildr@ command will start the BuildrServer if there isn't one already running. Subsequent calls to dbuildr will act as the client and invoke the tasks you provide to the server. If the buildfile has been modified it will be reloaded on the BuildrServer. + +h2(#notifications). Notifications: Growl, Libnotify, Qube + +Buildr support sending notifications when the build completes or fails, such as displaying the outcome message in an overlaid window on top of other applications. + +For OS X users, Buildr supports "Growl":http://growl.info/ out of the box by using the Ruby Cocoa bindings. + +For Debian-based Linux users, Buildr supports notifications via the "notify-send":http://manpages.ubuntu.com/manpages/gutsy/man1/notify-send.1.html command which is part of the "libnotify-bin":"http://packages.debian.org/search?keywords=libnotify-bin" package. Just make sure `notify-send` is installed and on your path is on your `PATH`. + +For other platforms or if you want to notify the user differently, Buildr offers two extension points: + +* @Buildr.application.on_completion@ +* @Buildr.application.on_failure@ + +Here is an example using these extension points to send notifications using "Qube":http://launchpad.net/qube: + +{% highlight ruby %} +# Send notifications using Qube +notify = lambda do |type, title, message| + param = case type + when 'completed'; '-i' + when 'failed'; '-e' + else '-i' + end + system "qube #{param} #{title.inspect} #{message.inspect}" +end + +Buildr.application.on_completion do |title, message| + notify['completed', title, message] +end +Buildr.application.on_failure do |title, message, ex| + notify['failed', title, message] +end +{% endhighlight %} + +You can place this code inside @buildr.rb@ in the @.buildr@ directory under your home directory. + +h2(#eclipse). Eclipse + +If you're using Eclipse, you can generate @.classpath@ and @.project@ from your Buildfile and use them to create a project in your workspace: + +{% highlight sh %} +$ buildr eclipse +{% endhighlight %} + +The @eclipse@ task will generate a @.classpath@ and @.project@ file for each of projects (and sub-project) that compiles source code. It will not generate files for other projects, for examples, projects you use strictly for packaging a distribution, or creating command line scripts, etc. + +If you add a new project, change the dependencies, or make any other change to your Buildfile, just run the @eclipse@ task again to re-generate the Eclipse project files. To have your libraries' source code available in Eclipse, run the @artifacts:sources@ task. + +You may explicitly specify the nature of your project, for example if you are developing an Eclipse plugin: + +{% highlight ruby %} +define 'my-plugin' do + eclipse.natures :plugin +end +{% endhighlight %} + +The currently supported natures are @:java@, @:scala@ and @:plugin@. Buildr will attempts to auto-detect your project type and apply the most relevant settings by default. If it doesn't or you need something special, you may also explicitly set the nature, container and builders of your project by doing: + +{% highlight ruby %} +define 'custom-plugin' do + eclipse.natures 'org.eclipse.pde.PluginNature' + eclipse.classpath_containers 'org.eclipse.pde.core.requiredPlugins' + eclipse.builders ['org.eclipse.pde.ManifestBuilder', 'org.eclipse.pde.SchemaBuilder'] +end +{% endhighlight %} + +One more thing; these settings are inherited hierarchically so you may set them on a parent project if you want to share them across different projects. + +h2(#idea). IntelliJ IDEA + +If you use IntelliJ IDEA, you can generate project files by issuing: + +{% highlight sh %} +$ buildr idea +{% endhighlight %} + +This task will generate a @.iml@ file for every project (or subproject) and a @.ipr@ that you can directly open for the root project. + +The generated project files can be removed by issuing: + +{% highlight sh %} +$ buildr idea:clean +{% endhighlight %} + +The idea task generates the project files based on the settings of each project and idea extension specific settings. The main and test source trees are added to the @.iml@ file for each project as are the respective resource directories. The target and report directories are excluded from the project. If the project files exist on the file system the extension will replace specific component sections in the xml with the generated component configurations. + +Dependencies come in two forms. Dependencies on other projects and dependencies on external jars. Dependencies on other projects are added as module dependencies in the @.iml@ while jars are added as regular file dependencies. Dependencies are exported from the @.iml@ file if they are compile dependencies. If a artifact that matches dependency but has a classifier of 'sources' is present then it is configured as the source for the dependency. Note: Use "buildr artifacts:sources" to download the source for dependencies. + +h3. Idea Specific Directives + +The extension specific settings of sub-projects inherit the parent projects settings unless overwritten. + +h4. Project file naming + +The extension will use the last element of the projects name when generating the @.ipr@ and @.iml@ files. i.e. A project named "foo" will generate "foo.iml" and "foo.ipr" while a project named "foo:bar" will generate "bar/bar.iml" and no ipr. (The @.ipr@ project files are only generated for the base project). The name can be modified by setting the "ipr.suffix" or "iml.suffix" settings which specifies the suffix appended to the file names. The user can also override the name completely by setting "ipr.id" or "iml.id". + +h5. Example: Setting id + +{% highlight ruby %} +define "foo" do + ipr.id = "beep" + define "bar" do + iml.id = "baz" + end +end +{% endhighlight %} + +Will generate: + +
+beep.ipr
+foo.iml
+bar/baz.iml
+
+ +h5. Example: Setting suffix + +{% highlight ruby %} +define "foo" do + ipr.suffix = "-suffix1" + iml.suffix = "-suffix2" + define "bar" +end +{% endhighlight %} + +Will generate: + +
+foo-suffix1.ipr
+foo-suffix2.iml
+bar/bar-suffix2.iml
+
+ +h4. Disabling project file generation + +The extension will not generate an iml file for a project if the "project.no_iml" method is invoked. Generation of ipr files can be disabled by invoking the method "project.no_ipr". + +h5. Example + +{% highlight ruby %} +define "foo" do + project.no_ipr + define "bar" do + project.no_iml + end +end +{% endhighlight %} + +Will generate: + +
+foo.iml
+
+ +h4. Disabling generation of content section in .iml file + +The extension will not generate a content section in an iml file if the "iml.skip_content!" method is invoked. This can be useful if a project is just exporting dependencies and has no associated source code. This may also be of use in scenarios where the build is repackaging an existing jar with more meta-data or the project is just a container for other projects. + +h5. Example + +{% highlight ruby %} +define "foo" do + iml.skip_content! +end +{% endhighlight %} + +h4. VCS Integration + +The extension will attempt to guess the VCS type of the project by looking for a @.svn@ or @.git@ directory in the base projects directory. If either of these are set it will configure the component as appropriate. Otherwise the user will need to manually specify the project to one of either 'Git' or 'svn' using the ipr.vcs setting. + +h5. Example + +{% highlight ruby %} +define "foo" do + ipr.vcs = 'Git' +end +{% endhighlight %} + +h4. Adding main, test or exclude paths to the .iml file + +The extension allows you to add source paths, test source paths or add paths to the excluded set by modifying the "iml.main_source_directories", "iml.test_source_directories" or "iml.excluded_directories" settings respectively. This is only needed when the defaults inherited from project.compile or project.test are not sufficient. + +h5. Example + +{% highlight ruby %} +define "foo" do + # Add path for generated resources to .iml file + iml.main_source_directories << _("generated/main/resources") + + # Add path for generated test resources to .iml file + iml.test_source_directories << _("generated/test/resources") + + # Exclude the temp directory created during testing + iml.excluded_directories << _("tmp") + + ... +end +{% endhighlight %} + +h4. Adding main or test dependencies to the .iml file + +The extension allows you to add main or test dependencies by modifying the "iml.main_dependencies" or "iml.test_dependencies" settings respectively. This is only needed when the defaults inherited from project.compile or project.test are not sufficient. Note: These dependencies are not included on compile path when running buildr. + +h5. Example + +{% highlight ruby %} +define "foo" do + # Add idea specific jar dependency to .iml file + iml.main_dependencies << 'group:id:jar:1.0' + + # Add idea specific test jar dependency to .iml file + iml.test_dependencies << 'group:id:jar:1.0' + ... +end +{% endhighlight %} + +h4. Dependency generation + +A file dependency that exists in the local maven 2 repository is stored in the IML file relative to the @$MAVEN_REPOSITORY$@ environment variable (that defaults to @~/.m2/repository@). The user can override the environment variable by setting the "iml.local_repository_env_override" setting. If the dependency does not exist in to maven repository or the "iml.local_repository_env_override" setting is set to nil, then the path stored in the IML is relative to the IML file. + +h5. Example: Setting local_repository_env_override + +{% highlight ruby %} +define "foo" do + iml.local_repository_env_override = nil + compile.with 'group:id:jar:1.0' +end +{% endhighlight %} + +Will generate a dependency with a path like: + +
+jar:///home/peter/.m2/repository/group/id/1.0/id-1.0.jar!/
+
+ +rather than the default + +
+jar://$MAVEN_REPOSITORY$/group/id/1.0/id-1.0.jar!/
+
+ +h5. Example: A dependency outside the maven repository + +{% highlight ruby %} +define "foo" do + compile.with _("foos-dep.jar") +end +{% endhighlight %} + +Will generate a dependency with a path like: + +
+jar://$MODULE_DIR$/foo-dep.jar!/
+
+ +h4. Module Facets + +Facets are IDEAs mechanism for adding support for languages, tools and frameworks other than core java. A facet can be added to a project so that it can be deployed as a web application or a hibernate application. A facet can also be used t provide support for other languages such as ruby and scala. The extension makes it possible to generate @.iml@ with the appropriate facets via the "iml.add_facet" method. It should be noted that facets are NOT inherited by sub-projects. + +h5. Example + +This example adds the web facet to a project. + +{% highlight ruby %} +define "foo" do + iml.add_facet("Web","web") do |facet| + facet.configuration do |conf| + conf.descriptors do |desc| + desc.deploymentDescriptor :name => 'web.xml', + :url => "file://$MODULE_DIR$/src/main/webapp/WEB-INF/web.xml", + :optional => "false", :version => "2.4" + end + conf.webroots do |webroots| + webroots.root :url => "file://$MODULE_DIR$/src/main/webapp", :relative => "/" + end + end + end +end +{% endhighlight %} + +h4. Project Configurations + +Configurations are IDEAs mechanism for running or debugging the project. Shared configurations are stored in the project file. The extension makes it possible to generate an @.ipr@ with specific configurations via the "ipr.add_configuration" method. + +h5. Example + +This example adds a configuration to invoke a GWT application. + +{% highlight ruby %} +define "foo" do + ... + ipr.add_configuration("Run Contacts.html", "GWT.ConfigurationType", "GWT Configuration") do |xml| + xml.module(:name => project.iml.id) + xml.option(:name => "RUN_PAGE", :value => "Contacts.html") + xml.option(:name => "compilerParameters", :value => "-draftCompile -localWorkers 2") + xml.option(:name => "compilerMaxHeapSize", :value => "512") + + xml.RunnerSettings(:RunnerId => "Run") + xml.ConfigurationWrapper(:RunnerId => "Run") + xml.method() + end +end +{% endhighlight %} + +h4. Project Artifacts + +IDEA can build artifacts such as jars and wars. The artifact configuration is stored in the project file. The extension makes it possible to generate an @.ipr@ with specific artifacts via the "ipr.add_artifact" method. + +h5. Example + +This example adds a jar artifact to the project. + +{% highlight ruby %} +define "foo" do + ... + ipr.add_artifact("MyFancy.jar", "jar") do |xml| + xml.tag!('output-path', project._(:artifacts, "MyFancy.jar")) + xml.element :id => "module-output", :name => "foo" + end +end +{% endhighlight %} + +h4. Custom Component Sections + +If the extension does not provide capability to generate configuration for a particular IDEA plugin the user can provide their own configuration data via the "ipr.add_component" or "iml.add_component" methods. + +h5. Example: Adding .ipr specific component + +This example changes the compiler configuration for project. + +{% highlight ruby %} +define "foo" do + ipr.add_component("CompilerConfiguration") do |component| + component.option :name => 'DEFAULT_COMPILER', :value => 'Javac' + component.option :name => 'DEPLOY_AFTER_MAKE', :value => '0' + component.resourceExtensions do |xml| + xml.entry :name => '.+\.nonexistent' + end + component.wildcardResourceExtensions do |xml| + xml.entry :name => '?*.nonexistent' + end + end +end +{% endhighlight %} + +h5. Example: Adding .iml specific component + +This example adds the web facet to a project. Note: This overrides the facets defined by the "iml.add_facet" method. + +{% highlight ruby %} +define "foo" do + iml.add_component("FacetManager") do |component| + component.facet :type => 'web', :name => 'Web' do |facet| + facet.configuration do |conf| + conf.descriptors do |desc| + desc.deploymentDescriptor :name => 'web.xml', + :url => "file://$MODULE_DIR$/src/main/webapp/WEB-INF/web.xml", + :optional => "false", :version => "2.4" + end + conf.webroots do |webroots| + webroots.root :url => "file://$MODULE_DIR$/src/main/webapp", :relative => "/" + end + end + end + end +end +{% endhighlight %} + +h4. Templates + +The underlying project files are xml the contain elements for a number of "components". The extension will load any existing project files and replace or add any component elements that are generated by the extension. The extension also allows the user to specify a template with either "ipr.template" or "iml.template" settings. If a template is specified it will be loaded and any component elements in these documents will be merged into the base document prior to merging in generated sections. Templates are useful if you want to enforce certain configuration options (i.e. project specific code style). + +h5. Example + +{% highlight ruby %} +define "foo" do + ipr.template = 'project.ipr.template' + iml.template = 'module.iml.template' +end +{% endhighlight %} + +h4. Groups + +IDEA provides the facility to organise modules into groups. By default the extension does not do this but it can be enabled by "iml.group" setting. If that setting is set to true then the @.iml@ file will be placed in a group based on the parent projects name. If the setting is a string then that is used as the name of the group. + +h5. Example + +{% highlight ruby %} +define "foo" do + iml.group = true + define 'bar' do + define 'baz' + end + define 'rab' do + iml.group = "MyGroup" + end +end +{% endhighlight %} + +Will place the generated .imls in the following groups: + +
+foo.iml                => ''
+bar/bar.iml            => 'foo'
+bar/baz/baz.iml        => 'foo/bar'
+rab/rab.iml            => 'MyGroup'
+
+ +h4. Add Extra .iml files to .ipr + +The 'ipr.extra_modules' setting makes it possible to add extra modules to the generated iml file. The setting is an array of file names relative to the base project directory. + +h5. Example + +{% highlight ruby %} +define "foo" do + ipr.extra_modules << 'other.iml' + ipr.extra_modules << 'other_other.iml' +end +{% endhighlight %} + +Will add the 'other.iml' and 'other_other.iml' files to the @.ipr@ project files. + +h4. Buildr plugin for IDEA + +Also, check out the "Buildr plugin for IDEA":http://www.digitalsanctum.com/buildr-plug-in/ (IDEA 7 and later). Once installed, open your project with IDEA. If IDEA finds that you have Buildr installed and finds a buildfile in the project's directory, it will show all the tasks available for that project. To run a task, double-click it. When the task completes, IDEA will show the results in the Buildr Output window. + +h2(#cobertura_emma). Cobertura, Emma + +You can use "Cobertura":http://cobertura.sourceforge.net/ or "Emma":http://emma.sourceforge.net/ to instrument your code, run the tests and create a test coverage report in either HTML or XML format. + +There are two main tasks for each tool, both of which generate a test coverage report in the @reports/cobertura@ (respectively @reports/emma@) directory. For example: + +{% highlight sh %} +$ buildr test cobertura:html +{% endhighlight %} + +As you can guess, the other tasks are @cobertura:xml@, @emma:html@ and @emma:xml@. + +If you want to generate a test coverage report only for a specific project, you can do so by using the project name as prefix to the tasks. + +{% highlight sh %} +$ buildr subModule:cobertura:html +{% endhighlight %} + +Each project can specify which classes to include or exclude from cobertura instrumentation by giving a class-name regexp to the @cobertura.include@ or @cobertura.exclude@ methods: + +{% highlight ruby %} +define 'someModule' do + cobertura.include 'some.package.==*==' + cobertura.include /some.(foo|bar).==*==/ + cobertura.exclude 'some.foo.util.SimpleUtil' + cobertura.exclude /==*==.Const(ants)?/i +end +{% endhighlight %} + +Emma has @include@ and @exclude@ methods too, but they take glob patterns instead of regexps. + +Cobertura also provides a @cobertura:check@ task. This task is intended to be used as a dependency for other tasks (such as @deploy@) which might wish to fail if coverage is unacceptable. The respective thresholds for task failure may be defined using the @cobertura.check@ configuration namespace. For example: + +{% highlight ruby %} +define 'someModule' do + cobertura.check.branch_rate = 75 + cobertura.check.line_rate = 100 + cobertura.check.total_line_rate = 98 + + task(:deploy).enhance 'cobertura:check' +end +{% endhighlight %} + +The @cobertura:check@ task supports all of the configuration parameters allowed by the @cobertura-check@ Ant task (as "documented here":http://cobertura.sourceforge.net/anttaskreference.html). Configuration parameters are "Ruby-ized" (as demonstrated in the example above). + +We want Buildr to load fast, and not everyone cares for these tasks, so we don't include them by default. If you want to use one of them, you need to require it explicitly. The proper way to do it in Ruby: + +{% highlight ruby %} +require 'buildr/java/cobertura' +require 'buildr/java/emma' +{% endhighlight %} + +You may want to add those to the Buildfile. Alternatively, you can use these tasks for all your projects without modifying the Buildfile. One convenient method is to add these lines to the @buildr.rb@ file in the @.buildr@ directory under your home directory. + +Another option is to require it from the command line (@--require@ or @-r@), for example: + +{% highlight sh %} +$ buildr -rbuildr/java/cobertura cobertura:html +{% endhighlight %} + +h2(#checkstyle). Checkstyle + +Checkstyle is integrated into Buildr through an extension. The extension adds the "checkstyle:xml" task that generates an xml report listing checkstyle violations and may add a "checkstyle:html" task if an appropriate xsl is present. A typical project that uses the extension may look something like; + +{% highlight ruby %} +require 'buildr/checkstyle' + +define "foo" do + project.version = "1.0.0" + + define "bar" do ... end + + checkstyle.config_directory = _('etc/checkstyle') + checkstyle.source_paths << project('bar')._(:source, :main, :java) + checkstyle.extra_dependencies << :javax_servlet + +end +{% endhighlight %} + +By default checkstyle will look for all configuration files in the src/main/etc/checkstyle directory but this can be overriden by the setting the "checkstyle.config_directory" parameter. The "checkstyle:xml" task will be defined if the checkstyle rules file is found. The rules file is typically named "checks.xml" but can be overridden by setting the "checkstyle.configuration_file" parameter. If a suppressions file or import control file is included in the directory, these will also be used by the extension. These names of these files will default to "suppressions.xml" and "import-control.xml" but these can be overriden by the parameters "checkstyle.suppressions_file" and "checkstyle.import_control_file". + +The extension will include the source and test directories of the project aswell as the compile and test dependencies when invoking the checkstyle tool. These can be added to by the parameters "checkstyle.source_paths" and "checkstyle.extra_dependencies" as appropriate. + +If the xsl file named "checkstyle-report.xsl" is present in the configuration directory then a "checkstyle:html" task will be defined. The name of the xsl file can be overridden by the parameter "checkstyle.style_file". + +h2(#findbugs). FindBugs + +FindBugs is integrated into Buildr through an extension. The extension adds the "findbugs:xml" task that generates an xml report listing findbugs violations and may add a "findbugs:html" task if an appropriate xsl is present. A typical project that uses the extension may look something like; + +{% highlight ruby %} +require 'buildr/findbugs' + +define "foo" do + project.version = "1.0.0" + + define "bar" do ... end + + findbugs.config_directory = _('etc/findbugs') + findbugs.source_paths << project('bar')._(:source, :main, :java) + findbugs.analyze_paths << project('bar').compile.target + findbugs.extra_dependencies << project('bar').compile.dependencies + +end +{% endhighlight %} + +By default findbugs will look for all configuration files in the src/main/etc/findbugs directory but this can be overriden by the setting the "findbugs.config_directory" parameter. The "findbugs:xml" task will past FindBugs a filter xml if a file named "filter.xml" is present in the configuration directory. This can be overridden by setting the "findbugs.filter_file" parameter. + +The extension will include the source and test directories of the project aswell as the compile and test dependencies when invoking the findbugs tool. These can be added to by the parameters "findbugs.source_paths" and "findbugs.extra_dependencies" as appropriate. The actual analysis is run across compiled artifacts ad this will default to the target directory of the project but this can be overriden by the "findbugs.analyze_paths" parameter. + +If the xsl file named "findbugs-report.xsl" is present in the configuration directory then a "findbugs:html" task will be defined. The name of the xsl file can be overridden by the parameter "findbugs.style_file". + +h2(#javancss). JavaNCSS + +JavaNCSS is integrated into Buildr through an extension. The extension adds the "javancss:xml" task that generates an xml report and may add a "javancss:html" task if an appropriate xsl is present. A typical project that uses the extension may look something like; + +{% highlight ruby %} +require 'buildr/javancss' + +define "foo" do + project.version = "1.0.0" + + define "bar" do ... end + + javancss.enabled = true + javancss.config_directory = _('etc/javancss') + javancss.source_paths << project('bar')._(:source, :main, :java) + +end +{% endhighlight %} + +The extension will include the source and test directories of the project when invoking the javancss tool. These can be added to by the parameters "javancss.source_paths". + +By default javancss will look for all configuration files in the src/main/etc/javancss directory but this can be overriden by the setting the "javancss.config_directory" parameter. The "javancss:xml" task will be defined if the "javancss.enabled" property is set to true. If the xsl file named "javancss2html.xsl" is present in the configuration directory then a "javancss:html" task will be defined. The name of the xsl file can be overridden by the parameter "javancss.style_file". + +h2(#jdepend). JDepend + +"JDepend":http://clarkware.com/software/JDepend.html is integrated into Buildr through an extension. The extension adds the "jdepend:xml" task that generates an xml report, "jdepend:swing" that shows a Swing UI, and may add a "jdepend:html" task if an appropriate xsl is present. A typical project that uses the extension may look something like; + +{% highlight ruby %} +require 'buildr/jdepend' + +define "foo" do + project.version = "1.0.0" + + define "bar" do ... end + + jdepend.enabled = true + jdepend.config_directory = _('etc/jdepend') + jdepend.target_paths << project('bar').compile.target + +end +{% endhighlight %} + +The extension will include the compiled source and test directories of the project when invoking the JDepend tool. These can be added to by the parameters "jdepend.target_paths". + +By default JDepend will look for all configuration files in the src/main/etc/jdepend directory but this can be overriden by the setting the "jdepend.config_directory" parameter. The "jdepend:xml" and "jdepend:swing" task will be defined if the "jdepend.enabled" property is set to true. If a "jdepend.properties" is included in the configuration directory then jdepend will load it during the analysis. If the xsl file named "jdepend.xsl" is present in the configuration directory then a "jdepend:html" task will be defined. The name of the xsl file can be overridden by the parameter "jdepend.style_file". + +h2(#pmd). PMD + +PMD is integrated into Buildr through an extension. The extension adds the "pmd:rule:xml" and "pmd:rule:html" tasks. A typical project that uses the extension may look something like; + + +{% highlight ruby %} +require 'buildr/javancss' + +define "foo" do + project.version = "1.0.0" + + define "bar" do ... end + + pmd.enabled = true + pmd.rule_set_paths = _('etc/pmd') + "/" + pmd.source_paths << project('bar')._(:source, :main, :java) + pmd.rule_set_files = ['basic','imports','unusedcode','logging-java','finalizers'] + +end +{% endhighlight %} + +The "pmd:rule:xml" task will be defined if the "pmd.enabled" property is set to true. + +The extension will include the source and test directories of the project when invoking the pmd tool. These can be added to by the parameters "pmd.source_paths". + +By default the pmd rule files 'basic','imports' and 'unusedcode' will be evaluated but this can be overriden by the "pmd.rule_set_files" parameter. The rule sets will be loaded from the classpath and this can be added to by modifying the "pmd.rule_set_paths" parameter. + +h2(#jaxb_xjc). JAXB Xjc Compiler + +Buildr includes an extension that provides the ability to invoke jaxb xjc binding compiler. A typical project that uses the extension may look something like; + +{% highlight ruby %} +require 'buildr/jaxb_xjc' + +define "foo" do + project.version = "1.0.0" + compile.from compile_jaxb(_('src/schemas/wildfire-1.3.xsd'), + "-quiet", + :package => "org.foo.api") + package :jar +end +{% endhighlight %} + +The method compile_jaxb accepts either an array of files or a single file as the first parameter. It then accepts 0 or more arguments that are passed to the underlying XJC compiler. The arguments are documented on the "jaxb site":https://jaxb.dev.java.net/nonav/2.2.1/docs/xjc.html. If the last argument is an options hash then the extension handles the options hash specially. The supported options include: + +* :directory: The directory to which source is generated. Defaults to _(:target, :generated, :jaxb) +* :keep_content: By default the generated directory will be deleted. If true is specified for this parameter the directory will not be deleted. +* :package: The package in which the source is generated. + +h2(#anything_ruby). Anything Ruby Can Do + +Buildr is Ruby code. That's an implementation detail for some, but a useful features for others. You can use Ruby to keep your build scripts simple and DRY, tackle ad hoc tasks and write reusable features without the complexity of "plugins". + +We already showed you one example where Ruby could help. You can use Ruby to manage dependency by setting constants and reusing them, grouping related dependencies into arrays and structures. + +You can use Ruby to perform ad hoc tasks. For example, Buildr doesn't have any pre-canned task for setting file permissions. But Ruby has a method for that, so it's just a matter of writing a task: + +{% highlight ruby %} +bins = file('target/bin'=>FileList[_('src/main/dist/bin/==*==')]) do |task| + mkpath task.name + cp task.prerequisites, task.name + chmod 0755, FileList[task.name + '/==*==.sh'], :verbose=>false +end +{% endhighlight %} + +You can use functions to keep your code simple. For example, in the ODE project we create two binary distributions, both of which contain a common set of files, and one additional file unique to each distribution. We use a method to define the common distribution: + +{% highlight ruby %} +def distro(project, id) + project.package(:zip, :id=>id).path("#{id}-#{version}").tap do |zip| + zip.include meta_inf + ['RELEASE_NOTES', 'README'].map { |f| path_to(f) } + zip.path('examples').include project.path_to('src/examples'), :as=>'.' + zip.merge project('ode:tools-bin').package(:zip) + zip.path('lib').include artifacts(COMMONS.logging, COMMONS.codec, + COMMONS.httpclient, COMMONS.pool, COMMONS.collections, JAXEN, SAXON, + LOG4J, WSDL4J, XALAN, XERCES) + project('ode').projects('utils', 'tools', 'bpel-compiler', 'bpel-api', + 'bpel-obj', 'bpel-schemas').map(&:packages).flatten.each do |pkg| + zip.include(pkg.to_s, :as=>"#{pkg.id}.#{pkg.type}", :path=>'lib') + end + yield zip + end +end +{% endhighlight %} + +And then use it in the project definition: + +{% highlight ruby %} +define 'distro-axis2' do + parent.distro(self, "#{parent.id}-war") { |zip| + zip.include project('ode:axis2-war').package(:war), :as=>'ode.war' } +end +{% endhighlight %} + +Ruby's functional style and blocks make some task extremely easy. For example, let's say we wanted to count how many source files we have, and total number of lines: + +{% highlight ruby %} +sources = projects.map { |prj| prj.compile.sources. + map { |src| FileList["#{src}/**/*.java"] } }.flatten +puts "There are #{source.size} source files" +lines = sources.inject(0) { |lines, src| lines += File.readlines(src).size } +puts "That contain #{lines} lines" +{% endhighlight %} + diff --git a/buildr/doc/packaging.textile b/buildr/doc/packaging.textile new file mode 100644 index 0000000..189da1e --- /dev/null +++ b/buildr/doc/packaging.textile @@ -0,0 +1,628 @@ +--- +layout: default +title: Packaging +--- + +For our next trick, we're going to try and create an artifact ourselves. We're going to start with: + +{% highlight ruby %} +package :jar +{% endhighlight %} + +We just told the project to create a JAR file in the @target@ directory, including all the classes (and resources) that we previously compiled into @target/classes@. Or we can create a WAR file: + +{% highlight ruby %} +package :war +{% endhighlight %} + +The easy case is always easy, but sometimes we have more complicated use cases which we'll address through the rest of this section. + +Now let's run the build, test cases and create these packages: + +{% highlight sh %} +$ buildr package +{% endhighlight %} + +The @package@ task runs the @build@ task (remember: @compile@ and @test@) and then runs each of the packaging tasks, creating packages in the projects' target directories. + +p(tip). The @package@ task and @package@ methods are related, but that relation is different from other task/method pairs. The @package@ method creates a file task that points to the package in the @target@ directory and knows how to create it. It then adds itself as a prerequisite to the @package@ task. Translation: you can create multiple packages from the same project. + + +h2(#referencing). Specifying And Referencing Packages + +Buildr supports several packaging types, and so when dealing with packages, you have to indicate the desired package type. The packaging type can be the first argument, or the value of the @:type@ argument. The following two are equivalent: + +{% highlight ruby %} +package :jar +package :type=>:jar +{% endhighlight %} + +If you do not specify a package type, Buildr will attempt to infer one. + +In the documentation you will find a number of tasks dealing with specific packaging types (@ZipTask@, @JarTask@, etc). The @package@ method is a convenience mechanism that sets up the package for you associates it with various project life cycle tasks. + +To package a particular file, use the @:file@ argument, for example: + +{% highlight ruby %} +package :zip, :file=>_('target/interesting.zip') +{% endhighlight %} + +This returns a file task that will run as part of the project's @package@ task (generating all packages). It will invoke the @build@ task to generate any necessary prerequisites, before creating the specified file. + +The package type does not have to be the same as the file name extension, but if you don't specify the package type, it will be inferred from the extension. + +Most often you will want to use the second form to generate packages that are also artifacts. These packages have an artifact specification, which you can use to reference them from other projects (and buildfiles). They are also easier to share across projects: artifacts install themselves in the local repository when running the @install@ task, and upload to the remote repository when running the @upload@ task (see "Installing and Uploading":#install_upload). + +The artifact specification is based on the project name (using dashes instead of colons), group identifier and version number, all three obtained from the project definition. You can specify different values using the @:id@, @:group@, @:version@ and @:classifier@ arguments. For example: + +{% highlight ruby %} +define 'killer-app', :version=>'1.0' do + # Generates silly-1.0.jar + package :jar, :id=>'silly' + + # Generates killer-app-la-web-1.x.war + project 'la-web' do + package :war, :version=>'1.x' + end + + # Generates killer-app-the-api-1.0-sources.zip + project 'teh-api' do + package :zip, :classifier=>'sources' + end +end +{% endhighlight %} + +The file name is determined from the identifier, version number, classifier and extension associated with that packaging type. + +If you do not specify the packaging type, Buildr attempt to infer it from the project definition. In the general case it will use the default packaging type, ZIP. A project that compiles Java classes will default to JAR packaging; for other languages, consult the specific documentation. + +A single project can create multiple packages. For example, a Java project may generate a JAR package for the runtime library and another JAR containing just the API; a ZIP file for the source code and another ZIP for the documentation. Make sure to always call @package@ with enough information to identify the specific package you are referencing. Even if the project only defines a single package, calling the @package@ method with no arguments does not necessarily refer to that one. + +You can use the @packages@ method to obtain a list of all packages defined in the project, for example: + +{% highlight ruby %} +project('killer-app:teh-impl').packages.first +project('killer-app:teh-impl').packages.select { |pkg| pkg.type == :zip } +{% endhighlight %} + + +h2(#zip). Packaging ZIPs + +ZIP is the most common form of packaging, used by default when no other packaging type applies. It also forms the basis for many other packaging types (e.g. JAR and WAR). Most of what you'll find here applies to other packaging types. + +Let's start by including additional files in the ZIP package. We're going to include the @target/docs@ directory and @README@ file: + +{% highlight ruby %} +package(:zip).include _('target/docs'), 'README' +{% endhighlight %} + +The @include@ method accepts files, directories and file tasks. You can also use file pattern to match multiple files and directories. File patterns include asterisk (@*@) to match any file name or part of a file name, double asterisk (@**@) to match directories recursively, question mark (@?@) to match any character, square braces (@[]@) to match a set of characters, and curly braces (@{}@) to match one of several names. + +And the same way you @include@, you can also @exclude@ specific files you don't want showing up in the ZIP. For example, to exclude @.draft@ and @.raw@ files: + +{% highlight ruby %} +package(:zip).include(_('target/docs')).exclude('*.draft', '*.raw') +{% endhighlight %} + +So far we've included files under the root of the ZIP. Let's include some files under a given path using the @:path@ option: + +{% highlight ruby %} +package(:zip).include _('target/docs'), :path=>"#{id}-#{version}" +{% endhighlight %} + +If you need to use the @:path@ option repeatedly, consider using the @tap@ method instead. For example: + +{% highlight ruby %} +package(:zip).path("#{id}-#{version}").tap do |path| + path.include _('target/docs') + path.include _('README') +end +{% endhighlight %} + +p(tip). The @tap@ method is not part of the core library, but a very useful extension. It takes an object, yields to the block with that object, and then returns that object. + +p(note). To allow you to spread files across different paths, the include/exclude patterns are specific to a path. So in the above example, if you want to exclude some files from the "target/docs" directory, make sure to call @exclude@ on the path, not on the ZIP task itself. + +If you need to include a file or directory under a different name, use the @:as@ option. For example: + +{% highlight ruby %} +package(:zip).include(_('corporate-logo-350x240.png'), :as=>'logo.png') +{% endhighlight %} + +You can also use @:as=>'.'@ to include all files from the given directory. For example: + +{% highlight ruby %} +package(:zip).include _('target/docs/*') +package(:zip).include _('target/docs'), :as=>'.' +{% endhighlight %} + +These two perform identically. They both include all the files from the @target/docs@ directory, but not the directory itself, and they are both lazy, meaning that the files can be created later and they will still get packaged into the zip package. + +For example, when you use @package :jar@, under the hood it specifies to include all the files from @target/classes@ with @:as=>'.'@. Even though this happens during project definition and nothing has been compiled yet (and in fact @target/classes@ may not even exist yet), the .class files generated during compilation are still packaged in the .jar file, as expected. + +If you need to get rid of all the included files, call the @clean@ method. Some packaging types default to adding various files and directories, for example, JAR packaging will include all the compiled classes and resources. + +You can also merge two ZIP files together, expanding the content of one ZIP into the other. For example: + +{% highlight ruby %} +package(:zip).merge _('part1.zip'), _('part2.zip') +{% endhighlight %} + +If you need to be more selective, you can apply the include/exclude pattern to the expanded ZIP. For example: + +{% highlight ruby %} +# Everything but the libs +package(:zip).merge(_('bigbad.war')).exclude('libs/**/*') +{% endhighlight %} + + +h2(#jar). Packaging JARs + +JAR packages extend ZIP packages with support for Manifest files and the META-INF directory. They also default to include the class files found in the @target/classes@ directory. + +You can tell the JAR package to include a particular Manifest file: + +{% highlight ruby %} +package(:jar).with :manifest=>_('src/main/MANIFEST.MF') +{% endhighlight %} + +Or generate a manifest from a hash: + +{% highlight ruby %} +package(:jar).with :manifest=>{ 'Copyright'=>'Acme Inc (C) 2007' } +{% endhighlight %} + +You can also generate a JAR with no manifest with the value @false@, create a manifest with several sections using an array of hashes, or create it from a proc. + +In large projects, where all the packages use the same manifest, it's easier to set it once on the top project using the @manifest@ project property. Sub-projects inherit the property from their parents, and the @package@ method uses that property if you don't override it, as we do above. + +For example, we can get the same result by specifying this at the top project: + +{% highlight ruby %} +manifest['Copyright'] = 'Acme Inc (C) 2007' +{% endhighlight %} + +If you need to mix-in the project's manifest with values that only one package uses, you can do so easily: + +{% highlight ruby %} +package(:jar).with :manifest=>manifest.merge('Main-Class'=>'com.acme.Main') +{% endhighlight %} + +If you need to include more files in the @META-INF@ directory, you can use the @:meta_inf@ option. You can give it a file, or array of files. And yes, there is a @meta_inf@ project property you can set once to include the same set of file in all the JARs. It works like this: + +{% highlight ruby %} +meta_inf << file('DISCLAIMER') << file('NOTICE') +{% endhighlight %} + +If you have a @LICENSE@ file, it's already included in the @meta_inf@ list of files. + +Other than that, @package :jar@ includes the contents of the compiler's target directory and resources, which most often is exactly what you intend it to do. If you want to include other files in the JAR, instead or in addition, you can do so using the @include@ and @exclude@ methods. If you do not want the target directory included in your JAR, simply call the @clean@ method on it: + +{% highlight ruby %} +package(:jar).clean.include( only_these_files ) +{% endhighlight %} + + +h2(#war). Packaging WARs + +Pretty much everything you know about JARs works the same way for WARs, so let's just look at the differences. + +Without much prompting, @package :war@ picks the contents of the @src/main/webapp@ directory and places it at the root of the WAR, copies the compiler target directory into the @WEB-INF/classes@ path, and copies any compiled dependencies into the @WEB-INF/libs@ paths. + +Again, you can use the @include@ and @exclude@ methods to change the contents of the WAR. There are two convenience options you can use to make the more common changes. If you need to include a classes directory other than the default: + +{% highlight ruby %} +package(:war).with :classes=>_('target/additional') +{% endhighlight %} + +If you want to include a different set of libraries other than the default: + +{% highlight ruby %} +package(:war).with :libs=>MYSQL_JDBC +{% endhighlight %} + +Both options accept a single value or an array. The @:classes@ option accepts the name of a directory containing class files, initially set to @compile.target@ and @resources.target@. The @:libs@ option accepts artifact specifications, file names and tasks, initially set to include everything in @compile.dependencies@. + +As you can guess, the package task has two attributes called @classes@ and @libs@; the @with@ method merely sets their value. If you need more precise control over these arrays, you can always work with them directly, for example: + +{% highlight ruby %} +# Add an artifact to the existing set: +package(:war).libs += artifacts(MYSQL_JDBC) +# Remove an artifact from the existing set: +package(:war).libs -= artifacts(LOG4J) +# List all the artifacts: +puts 'Artifacts included in WAR package:' +puts package(:war).libs.map(&:to_spec) +{% endhighlight %} + + +h2(#aar). Packaging AARs + +Axis2 service archives are similar to JAR's (compiled classes go into the root of the archive) but they can embed additional libraries under /lib and include @services.xml@ and WSDL files. + +{% highlight ruby %} +package(:aar).with(:libs=>'log4j:log4j:jar:1.1') +package(:aar).with(:services_xml=>_('target/services.xml'), + :wsdls=>_('target/*.wsdl')) +{% endhighlight %} + +The @libs@ attribute is a list of .jar artifacts to be included in the archive under /lib. The default is no artifacts; compile dependencies are not included by default. + +The @services_xml@ attribute points to an Axis2 services configuration file called @services.xml@ that will be placed in the @META-INF@ directory inside the archive. The default behavior is to point to the @services.xml@ file in the project's @src/main/axis2@ directory. In the second example above we set it explicitly. + +The @wsdls@ attribute is a collection of file names or glob patterns for WSDL files that get included in the @META-INF@ directory. In the second example we include WSDL files from the @target@ directory, presumably created by an earlier build task. In addition, AAR packaging will include all files ending with @.wsdl@ from the @src/main/axis2@ directory. + +If you already have WSDL files in the @src/main/axis2@ directory but would like to perform some filtering, for example, to set the HTTP port number, consider ignoring the originals and including only the filtered files, for example: + +{% highlight ruby %} +# Host name depends on environment. +host = ENV['ENV'] == 'test' ? 'test.host' : 'ws.example.com' +filter.from(_('src/main/axis2')).into(_(:target)). + include('services.xml', '==*==.wsdl').using('http_port'=>'8080', + 'http_host'=>host) +package(:aar).wsdls.clear +package(:aar).with(:services_xml=>_('target/services.xml'), + :wsdls=>_('target/==*==.wsdl')) +{% endhighlight %} + + +h2(#ear). Packaging EARs + +EAR packaging is slightly different from JAR/WAR packaging. It's main purpose is to package components together, and so it includes special methods for handling component inclusion that take care to update application.xml and the component's classpath. + +EAR packages support four component types: + +|_. Argument |_. Component | +| @:war@ | J2EE Web Application (WAR). | +| @:ejb@ | Enterprise Java Bean (JAR). | +| @:jar@ | J2EE Application Client (JAR). | +| @:lib@ | Shared library (JAR). | + +This example shows two ways for adding components built by other projects: + +{% highlight ruby %} +package(:ear) << project('coolWebService').package(:war) +package(:ear).add project('commonLib') # By default, the JAR package +{% endhighlight %} + +Adding a WAR package assumes it's a WAR component and treats it as such, but JAR packages can be any of three component types, so by default they are all treated as shared libraries. If you want to add an EJB or Application Client component, you need to say so explicitly, either passing @:type=>package@, or by passing the component type in the @:type@ option. + +Here are three examples: + +{% highlight ruby %} +# Assumed to be a shared library. +package(:ear).add 'org.springframework:spring:jar:2.6' +# Component type mapped to package. +package(:ear).add :ejb=>project('beanery') +# Adding component with specific package type. +package(:ear).add project('client'), :type=>:jar +{% endhighlight %} + +By default, WAR components are all added under the @/war@ path, and likewise, EJB components are added under the @/ejb@ path, shared libraries under @/lib@ and Application Client components under @/jar@. + +If you want to place components in different locations you can do so using the @:path@ option, or by specifying a different mapping between component types and their destination directory. The following two examples are equivalent: + +{% highlight ruby %} +# Specify once per component. +package(:ear).add project('coolWebService').package(:war), :path=>'coolServices' +# Configure once and apply to all added components. +package(:ear).dirs[:war] = 'coolServices' +package(:ear) << project('coolWebService').package(:war) +{% endhighlight %} + +EAR packages include an @application.xml@ file in the @META-INF@ directory that describes the application and its components. This file is created for you during packaging, by referencing all the components added to the EAR. There are a couple of things you will typically want to change. + +* *display-name* -- The application's display name defaults to the project's identifier. You can change that by setting the @display_name@ attribute. + +* *description* -- The application's description defaults to the project's comment. You can change that by setting the @description@ attribute. + +* *context-root* -- WAR components specify a context root, based on the package identifier, for example, "cool-web-1.0.war" will have the context root "cool-web". To specify a different context root, add the WAR package with the @context_root@ option. + +Again, by example: + +{% highlight ruby %} +package(:ear).display_name = 'MyCoolWebService' +package(:ear).description = 'MyCoolWebService: Making coolness kool again' +package(:ear).add project('coolWebService').package(:war), :context_root=>'coolness' +{% endhighlight %} + +If you need to disable the context root (e.g. for Portlets), set @context_root@ to @false@. + +It is also possible to add @security-role@ tags to the @application.xml@ file by appending a hash with @:id@, @:description@ and @:name@ to the @security_role@ array, like so: + +{% highlight ruby %} +package(:ear).security_roles << {:id=>'SecurityRole_123', + :description=>'Read only user', :name=>'coolUser'} +package(:ear).security_roles << {:id=>'SecurityRole_456', + :description=>'Super user', :name=>'superCoolUser'} +{% endhighlight %} + +h2(#bundle). Packaging OSGi Bundles + +OSGi bundles are jar files with additional metadata stored in the manifest. Buildr uses an external tool "Bnd":http://www.aqute.biz/Code/Bnd to create the package. Directives and properties can be explicitly passed to the build tool and buildr will provide reasonable defaults for properties that can be derived from the project model. Please see the bnd tool for documentation on the available properties. + +The bundle packaging format is included as an addon so the build file must explicitly require the addon using using require "buildr/bnd" and must add a remote repository from which the bnd can be downloaded. A typical project that uses the bundle packaging addon may look something like; + +{% highlight ruby %} +require "buildr/bnd" + +repositories.remote << Buildr::Bnd.remote_repository + +define 'myProject' do + ... + package(:bundle).tap do |bnd| + bnd['Import-Package'] = "*;resolution:=optional" + bnd['Export-Package'] = "*;version=#{version}" + end + ... +end +{% endhighlight %} + +The @[]@ method on the bundle package is used to provide directives to the bnd tool that are not inherited by sub-projects while the standard 'manifest' setting is used to define properties that inherited by sub-projects. + +h3. Defaults + +The addon sets the following bnd parameters; + +* "Bundle-Version" defaults to the project version. +* "Bundle-SymbolicName" defaults to the concatenation of the project group and project id, replacing ':' characters with '.'. +* "Bundle-Name" defaults to the project description if present else the project name +* "Bundle-Description" defaults to the project description. +* "-classpath" is set to the compile target directory and any compile time dependencies. +* "Include-Resource" defaults to the dir project.resources.target if it exists. + +h3. Parameters + +h4. classpath_element + +The user can also specify additional elements that are added to the classpath using the 'classpath_element' method. If the parameter to this element is a task, artifact, artifact namespace etc. then it will be resolved prior to invoking bnd. + +{% highlight ruby %} +... +define 'foo' do + ... + package(:bundle).tap do |bnd| + # This dependency will be added to classpath + bnd.classpath_element 'someOtherExistingFile.zip' + # All of these dependencies will be invoked and added to classpath + bnd.classpath_element artifact('com.sun.messaging.mq:imq:jar:4.4') + bnd.classpath_element project('bar') # Adds all the packages + bnd.classpath_element 'org.apache.ant:ant:jar:1.8.0' + bnd.classpath_element file('myLocalFile.jar') + ... + end + + project 'bar' do + ... + end +end +{% endhighlight %} + +h4. classpath + +The user can specify the complete classpath using the 'classpath' method. The classpath should be an array of elements. If the element is a task, artifact, artifact namespace etc. then it will be resolved prior to invoking bnd. + +{% highlight ruby %} +... +define 'foo' do + ... + package(:bundle).tap do |bnd| + bnd.classpath [ project.compile.target, + 'someOtherExistingFile.zip', + artifact('com.sun.messaging.mq:imq:jar:4.4'), + project('bar'), + 'org.apache.ant:ant:jar:1.8.0', + file('myLocalFile.jar') ] + ... + end + + project 'bar' do + ... + end +end +{% endhighlight %} + +h3. Examples + +h4. Including non-class resources in a bundle + +Bnd can be used to include non-class resources in a bundle. The following example includes all resources in 'src/etc' into the bundle. + +{% highlight ruby %} +define 'myproject' do + ... + package(:bundle).tap do |bnd| + bnd['Include-Resource'] = project._('src/etc') + '/' + ... + end +end +{% endhighlight %} + +h4. Using bnd to wrap an existing jar + +Bnd can be used to wrap an existing jar as an OSGi bundle. The following example wraps the OpenMQ JMS provider as an OSGi bundle. + +{% highlight ruby %} +... +# Add repository for OpenMQ +repositories.remote << 'http://download.java.net/maven/2' + +desc 'OSGi bundle for OpenMQ JMS provider client library' +define 'com.sun.messaging.mq.imq' do + project.version = '4.4' + project.group = 'iris' + package(:bundle).tap do |bnd| + bnd['Import-Package'] = "*;resolution:=optional" + bnd['Export-Package'] = "com.sun.messaging.*;version=#{version}" + bnd.classpath_element 'com.sun.messaging.mq:imq:jar:4.4' + end +end +{% endhighlight %} + +h4. Create an OSGi bundle with an Activator + +The following example presents a basic buildfile for building an OSGi bundle with an activator. + +{% highlight ruby %} +... +# repository for OSGi core bundle +repositories.remote << 'https://repository.apache.org/content/repositories/releases' + +desc 'Hello World bundle' +define 'helloworld' do + project.version = '1.0' + project.group = 'org.example' + compile.with 'org.apache.felix:org.osgi.core:jar:1.4.0' + package(:bundle).tap do |bnd| + bnd['Export-Package'] = "org.example.helloworld.api.*;version=#{version}" + bnd['Bundle-Activator'] = "org.example.helloworld.Activator" + end +end +{% endhighlight %} + +h4. Inheriting parameters for bnd tool + +The following example shows how you can use 'manifest' to define a bnd parameter that is inherited by all child sub-projects. The "Bundle-License" defined in the top level project is passed to the bnd tool when generating both the 'fe' and 'fi' sub-projects but the 'fo' sub-project overrides this parameter with a local value. + +{% highlight ruby %} +... +define 'myproject' do + manifest['Bundle-License'] = "http://www.apache.org/licenses/LICENSE-2.0" + ... + define 'fe' do + ... + package(:bundle).tap do |bnd| + ... + end + end + + define 'fi' do + ... + package(:bundle).tap do |bnd| + ... + end + end + + define 'fo' do + ... + package(:bundle).tap do |bnd| + bnd['Bundle-License'] = "http://www.apache.org/licenses/LICENSE-1.1" + end + end +end +{% endhighlight %} + +h2(#tar). Packaging Tars and GZipped Tars + +Everything you know about working with ZIP files translates to Tar files, the two tasks are identical in more respect, so here we'll just go over the differences. + +{% highlight ruby %} +package(:tar).include _('target/docs'), 'README' +package(:tgz).include _('target/docs'), 'README' +{% endhighlight %} + +The first line creates a Tar archive with the extension @.tar@, the second creates a GZipped Tar archive with the extension @.tgz@. + +In addition to packaging that includes the archive in the list of installed/released files, you can use the method @tar@ to create a @TarTask@. This task is similar to @ZipTask@, and introduces the @gzip@ attribute, which you can use to tell it whether to create a regular file, or GZip it. By default the attribute it set to true (GZip) if the file name ends with either @.gz@ or @.tgz@. + + +h2(#install_upload). Installing and Uploading + +You can bring in the artifacts you need from remote repositories and install them in the local repositories. Other projects have the same expectation, that your packages be their artifacts. + +So let's create these packages and install them in the local repository where other projects can access them: + +{% highlight sh %} +$ buildr install +{% endhighlight %} + +If you changes your mind you can always: + +{% highlight sh %} +$ buildr uninstall +{% endhighlight %} + +That works between projects you build on the same machine. Now let's share these artifacts with other developers through a remote repository: + +{% highlight sh %} +$ buildr upload +{% endhighlight %} + +Of course, you'll need to tell Buildr about the release server: + +{% highlight ruby %} +repositories.release_to = 'sftp://john:secret@release/usr/share/repo' +{% endhighlight %} + +This example uses the SFTP protocol. In addition, you can use the HTTP protocol -- Buildr supports HTTP and HTTPS, Basic Authentication and uploads using PUT -- or point to a directory on your file system. + +The URL in this example contains the release server ("release"), path to repository ("user/share/repo") and username/password for access. The way SFTP works, you specify the path on the release server, and give the user permissions to create directories and files inside the repository. The file system path is different from the path you use to download these artifacts through an HTTP server, and starts at the root, not the user's home directory. + +Of course, you'll want to specify the release server URL in the Buildfile, but leave the username/password settings private in your local @buildr.rb@ file. Let's break up the release server settings: + +{% highlight ruby %} +# build.rb, loaded first +repositories.release_to[:username] = 'john' +repositories.release_to[:password] = 'secret' + +# Buildfile, loaded next +repositories.release_to[:url] = 'sftp://release/usr/share/repo' +{% endhighlight %} + +The @upload@ task takes care of uploading all the packages created by your project, along with their associated POM files and MD5/SHA1 signatures (Buildr creates these for you). + +If you need to upload other files, you can always extend the @upload@ task and use @repositories.release_to@ in combination with @URI.upload@. You can also extend it to upload to different servers, for example, to publish the documentation and test coverage reports to your site: + +{% highlight ruby %} +# We'll let some other task decide how to create 'docs' +task 'upload'=>'docs' do + uri = URI("sftp://#{username}:#{password}@var/www/docs") + uri.upload file('docs') +end +{% endhighlight %} + +h3(#uploading-options). Uploading Options + +For convenience, you can also pass "any option of Net::SSH":http://net-ssh.github.com/ssh/v2/api/classes/Net/SSH.html#M000002 when configuring the remote repository. + +If you need to enforce to use password-only authentication for example, you can set this option: + +{% highlight ruby %} +# Set password authentication only +repositories.release_to[:options] = {:ssh_options=>{:auth_methods=> 'password'}} +{% endhighlight %} + +h2(#source_javadoc). Packaging Sources and JavaDocs + +IDEs can take advantage of source packages to help you debug and trace through compiled code. We'll start with a simple example: + +{% highlight ruby %} +package :sources +{% endhighlight %} + +This one creates a ZIP package with the classifier "sources" that will contain all the source directories in that project, typically @src/main/java@, but also other sources generated from Apt, JavaCC, XMLBeans and friends. + +You can also generate a ZIP package with the classifier "javadoc" that contains the JavaDoc documentation for the project. It uses the same set of documentation files generated by the project's @doc@ task, so you can use it in combination with the @doc@ method. For example: + +{% highlight ruby %} +package :javadoc +doc :windowtitle=>'Buggy but Works' +{% endhighlight %} + +By default Buildr picks the project's description for the window title. + +You can also tell Buildr to automatically create sources and JavaDoc packages in all the sub-projects that have any source files to package or document. Just add either or both of these methods in the top-level project: + +{% highlight ruby %} +package_with_sources +package_with_javadoc +{% endhighlight %} + +You can also tell it to be selective using the @:only@ and @:except@ options. +For example: + +{% highlight ruby %} +package_with_javadoc :except=>'la-web' +{% endhighlight %} + +We packaged the code, but will it actually work? Let's see "what the tests say":testing.html. diff --git a/buildr/doc/preface.textile b/buildr/doc/preface.textile new file mode 100644 index 0000000..f4bc86f --- /dev/null +++ b/buildr/doc/preface.textile @@ -0,0 +1,54 @@ +--- +layout: preface +--- + +p(title). !images/zbuildr.png! + +
    +
  1. "Quick Start":quick_start.html
  2. +
  3. "Installing and Running":installing.html
  4. +
  5. "Projects":projects.html
  6. +
  7. "Building":building.html
  8. +
  9. "Artifacts":artifacts.html
  10. +
  11. "Packaging":packaging.html
  12. +
  13. "Testing":testing.html
  14. +
  15. "Settings & Profiles":settings_profiles.html
  16. +
  17. "Languages":languages.html
  18. +
  19. "More Stuff":more_stuff.html
  20. +
  21. "Extending Buildr":extending.html
  22. +
  23. "Contributing":contributing.html
  24. +
+ + +p(preface). !images/asf-logo.png! + +Copyright 2007-2009 Apache Buildr + +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. + + + +
+"Daniel Spiewak":http://www.codecommit.com/blog: + +bq. If you think about it, the question isn’t “Why use Buildr?”, it’s really “Why use anything else?” The advantages afforded by Buildr are so substantial, I really can’t see myself going with any other tool, at least not when I have a choice. + +"Tristan Juricek":http://tristanhunt.com/: + +bq. That’s still the strongest sell: it builds everything I need, and as I’ve needed more, I just got things working without a lot of fuss. + +"Matthieu Riou":http://offthelip.org/: + +bq. We used to rely on Ant, with a fairly extensive set of scripts. It worked but was expensive to maintain. The biggest mistake afterward was to migrate to Maven2. I could write pages of rants explaining all the problems we ran into and we still ended up with thousands of lines of XML. + +"Martin Grotzke":http://www.javakaffee.de/blog/: + +bq. The positive side effect for me as a java user is that I learn a little ruby, and that’s easy but lots of fun… :-) +
+ + +p(preface). diff --git a/buildr/doc/projects.textile b/buildr/doc/projects.textile new file mode 100644 index 0000000..99f2094 --- /dev/null +++ b/buildr/doc/projects.textile @@ -0,0 +1,276 @@ +--- +layout: default +title: Projects +--- + + +h2(#starting). Starting Out + +In Java, most projects are built the same way: compile source code, run test cases, package the code, release it. Rinse, repeat. + +Feed it a project definition, and Buildr will set up all these tasks for you. The only thing you need to do is specify the parts that are specific to your project, like the classpath dependencies, whether you're packaging a JAR or a WAR, etc. + +The remainder of this guide deals with what it takes to build a project. But first, let's pick up a sample project to work with. We'll call it _killer-app_: + +{% highlight ruby %} +require "buildr/openjpa" + +include Buildr::OpenJPA + +VERSION_NUMBER = '1.0' + +AXIS2 = 'org.apache.axis2:axis2:jar:1.2' +AXIOM = group('axiom-api', 'axiom-impl', 'axiom-dom', + :under=>'org.apache.ws.commons.axiom', :version=>'1.2.4') +AXIS_OF_WS = [AXIOM, AXIS2] +OPENJPA = ['org.apache.openjpa:openjpa:jar:1.2.0', + 'net.sourceforge.serp:serp:jar:1.12.0'] + +repositories.remote << 'http://www.ibiblio.org/maven2/' + +desc 'Code. Build. ??? Profit!' +define 'killer-app' do + + project.version = VERSION_NUMBER + project.group = 'acme' + manifest['Copyright'] = 'Acme Inc (C) 2007' + compile.options.target = '1.5' + + desc 'Abstract classes and interfaces' + define 'teh-api' do + package :jar + end + + desc 'All those implementation details' + define 'teh-impl' do + compile.with AXIS_OF_WS, OPENJPA + compile { open_jpa_enhance } + package :jar + end + + desc 'What our users see' + define 'la-web' do + test.with AXIS_OF_WS + package(:war).with :libs=>projects('teh-api', 'teh-impl') + end + + javadoc projects + package :javadoc + +end +{% endhighlight %} + +A project definition requires four pieces of information: the project name, group identifier, version number and base directory. The project name ... do we need to explain why its necessary? The group identifier and version number are used for packaging and deployment, we'll talk more about that in the "Packaging":packaging.html section. The base directory lets you find files inside the project. + +Everything else depends on what that particular project is building. And it all goes inside the project definition block, the piece of code that comes between @define .. do@ and @end@. + + +h2(#dir_structure). The Directory Structure + +Buildr follows a convention we picked from years of working with Apache projects. + +Java projects are laid out so the source files are in the @src/main/java@ directory and compile into the @target/classes@ directory. Resource files go in the @src/main/resources@ directory, and copied over to @target/resources@. Likewise, tests come from @src/test/java@ and @src/test/resources@, and end life in @target/test/classes@ and @target/test/resources@, respectively. + +WAR packages pick up additional files from the aptly named @src/main/webapp@. And most stuff, including generated source files are parked under the @target@ directory. Test cases and such may generate reports in the, you guessed it, @reports@ directory. + +Other languages will use different directories, but following the same general conventions. For example, Scala code compiles from the @src/main/scala@ directory, RSpec tests are found in the @src/test/rspec@ directory, and Flash will compile to @target/flash@. Throughout this document we will show examples using mostly Java, but you can imagine how this pattern applies to other languages. + +When projects grow big, you split them into smaller projects by nesting projects inside each other. Each sub-project has a sub-directory under the parent project and follows the same internal directory structure. You can, of course, change all of that to suite your needs, but if you follow these conventions, Buildr will figure all the paths for you. + +Going back to the example above, the directory structure will look something like this: + +p=. !images/project-structure.png! + +Notice the @buildfile@ at the top. That's your project build script, the one Buildr runs. + +When you run the @buildr@ command, it picks up the @buildfile@ (which here we'll just call _Buildfile_) from the current directory, or if not there, from the closest parent directory. So you can run @buildr@ from any directory inside your project, and it will always pick up the same Buildfile. That also happens to be the base directory for the top project. If you have any sub-projects, Buildr assumes they reflect sub-directories under their parent. + +And yes, you can have two top projects in the same Buildfile. For example, you can use that to have one project that groups all the application modules (JARs, WARs, etc) and another project that groups all the distribution packages (binary, sources, javadocs). + +When you start with a new project you won't see the @target@ or @reports@ directories. Buildr creates these when it needs to. Just know that they're there. + + +h2(#naming). Naming And Finding Projects + +Each project has a given name, the first argument you pass when calling @define@. The project name is just a string, but we advise to stay clear of colon (@:@) and slashes (@/@ and @\@), which could conflict with other task and file names. Also, avoid using common Buildr task names, don't pick @compile@, @build@ or any existing task name for your project name. + +Since each project knows its parent project, child projects and siblings, you can reference them from within the project using just the given name. In other cases, you'll need to use the full name. The full name is just @parent:child@. So if you wanted to refer to _teh-impl_, you could do so with either @project('killer-app:teh-impl')@ or @project('killer-app').project('teh-impl')@. + +The @project@ method is convenient when you have a dependency from one project to another, e.g. using the other project in the classpath, or accessing one of its source files. Call it with a project name and it will return that object or raise an error. You can also call it with no arguments and it will return the project itself. It's syntactic sugar that's useful when accessing project properties. + +The @projects@ method takes a list of names and returns a list of projects. If you call it with no arguments on a project, it returns all its sub-projects. If you call it with no argument in any other context, it returns all the projects defined so far. + +Let's illustrate this with a few examples: + +{% highlight ruby %} +puts projects.inspect +=> [project("killer-app"), project("killer-app:teh-api") ... ] + +puts project('killer-app').projects.inspect +=> [project("killer-app:teh-api"), project("killer-app:teh-impl") ... ] + +puts project('teh-api') +=> No such project teh-api + +puts project('killer-app:teh-api').inspect +=> project("killer-app:teh-api") + +puts project('killer-app').project('teh-api').inspect +=> project("killer-app:teh-api") +{% endhighlight %} + +To see a list of all projects defined in your Buildfile run @buildr help:projects@. + + +h2(#tasks). Running Project Tasks + +Most times, you run tasks like @build@ or @package@ that operate on the current project and recursively on its sub-projects. The "current project" is the one that uses the current working directory. So if you're in the @la-web/src@ directory looking at source files, _la-web_ is the current project. For example: + +{% highlight sh %} +# build killer-app and all its sub-projects +$ buildr build + +# switch to and test only teh-impl +$ cd teh-impl +$ buildr test + +# switch to and package only la-web +$ cd ../la-web +$ buildr package +{% endhighlight %} + +You can use the project's full name to invoke one of its tasks directly, and it doesn't matter which directory you're in. For example: + +{% highlight sh %} +# build killer-app and all its sub-projects +$ buildr killer-app:build + +# test only teh-impl +$ buildr killer-app:teh-impl:test + +# package only la-web +$ buildr killer-app:la-web:package +{% endhighlight %} + +Buildr provides the following tasks that you can run on the current project, or on a specific project by prefixing them with the project's full name: + +{% highlight text %} +clean # Clean files generated during a build +compile # Compile all projects +build # Build the project +upload # Upload packages created by the project +install # Install packages created by the project +javadoc # Create the Javadocs for this project +package # Create packages +test # Run all test cases +uninstall # Remove previously installed packages +{% endhighlight %} + +To see a list of all the tasks provided by Buildr run @buildr help:tasks@. + + +h2(#properties). Setting Project Properties + +We mentioned the group identifier, version number and base directory. These are project properties. There are a few more properties we'll cover later on. + +There are two ways to set project properties. You can pass them as a hash when you call @define@, or use accessors to set them on the project directly. For example: + +{% highlight ruby %} +define 'silly', :version=>'1.0' do + project.group = 'acme' +end + +puts project('silly').version +=> 1.0 +puts project('silly').group +=> acme +{% endhighlight %} + +Project properties are inherited. You can specify them once in the parent project, and they'll have the same value in all its sub-projects. In the example, we only specify the version number once, for use in all sub-projects. + + +h2(#paths). Resolving Paths + +You can run @buildr@ from any directory in your project. To keep tasks consistent and happy, it switches over to the Buildfile directory and executes all the tasks from there, before returning back to your working directory. Your tasks can all rely on relative paths that start from the same directory as the Buildfile. + +But in practice, you'll want to use the @path_to@ method. This method calculates a path relative to the project, a better way if you ever need to refactor your code, say turn a ad hoc task into a function you reuse. + +The @path_to@ method takes an array of strings and concatenates them into a path. Absolute paths are returned as they are, relative paths are expanded relative to the project's base directory. Slashes, if you don't already know, work very well on both Windows, Linux and OS X. And as a shortcut, you can use @_@. + +For example: + +{% highlight ruby %} +# Relative to the current project +path_to('src', 'main', 'java') + +# the same using symbols +path_to(:src, :main, :java) + +# Exactly the same thing +_('src/main/java') + +# Relative to the teh-impl project +project('teh-impl')._('src/main/java') +{% endhighlight %} + + +h2(#defining). Defining The Project + +The project definition itself gives you a lot of pre-canned tasks to start with, but that's not enough to build a project. You need to specify what gets built and how, which dependencies to use, the packages you want to create and so forth. You can configure existing tasks, extend them to do additional work, and create new tasks. All that magic happens inside the project definition block. + +Since the project definition executes each time you run Buildr, you don't want to perform any work directly inside the project definition block. Rather, you want to use it to specify how different build task work when you invoke them. Here's an example to illustrate the point: + +{% highlight ruby %} +define 'silly' do + puts 'Running buildr' + + build do + puts 'Building silly' + end +end +{% endhighlight %} + +Each time you run Buildr, it will execute the project definition and print out "Running buildr". We also extend the @build@ task, and whenever we run it, it will print "Building silly". Incidentally, @build@ is the default task, so if you run Buildr with no arguments, it will print both messages while executing the build. If you run Buildr with a different task, say @clean@, it will only print the first message. + +The @define@ method gathers the project definition, but does not execute it immediately. It executes the project definition the first time you reference that project, directly or indirectly, for example, by calling @project@ with that project's name, or calling @projects@ to return a list of all projects. Executing a project definition will also execute all its sub-projects' definitions. And, of course, all project definitions are executed once the Buildfile loads, so Buildr can determine how to execute each of the build tasks. + +If this sounds a bit complex, don't worry. In reality, it does the right thing. A simple rule to remember is that each project definition is executed before you need it, lazy evaluation of sort. The reason we do that? So you can write projects that depend on each other without worrying about their order. + +In our example, the _la-web_ project depends on packages created by the _teh-api_ and _teh-impl_ projects, the later requiring _teh-api_ to compile. That example is simple enough that we ended up specifying the projects in order of dependency. But you don't always want to do that. For large projects, you may want to group sub-projects by logical units, or sort them alphabetically for easier editing. + +One project can reference another ahead of its definition. If Buildr detects a cyclic dependency, it will let you know. + +In this example we define one project in terms of another, using the same dependencies, so we only need to specify them once: + +{% highlight ruby %} +define 'bar' do + compile.with project('foo').compile.dependencies +end + +define 'foo' do + compile.with ..lots of stuff.. +end +{% endhighlight %} + +One last thing to remember. Actually three, but they all go hand in hand. + +*Self is project* Each of these project definition blocks executes in the context of that project, just as if it was a method defined on the project. So when you call the @compile@ method, you're essentially calling that method on the current project: @compile@, @self.compile@ and @project.compile@ are all the same. + +*Blocks are closures* The project definition is also a closure, which can reference variables from enclosing scopes. You can use that, for example, to define constants, variables and even functions in your Buildfile, and reference them from your project definition. As you'll see later on, in the "Artifacts":artifacts.html section, it will save you a lot of work. + +*Projects are namespaces* While executing the project definition, Buildr switches the namespace to the project name. If you define the task "do-this" inside the _teh-impl_ project, the actual task name is "killer-app:teh-impl:do-this". Likewise, the @compile@ task is actually "killer-app:teh-impl:compile". + +From outside the project you can reference a task by its full name, either @task('foo:do')@ or @project('foo').task('do')@. If you need to reference a task defined outside the project from within the project, prefix it with "rake:", for example, @task('rake:globally-defined')@. + + +h2(#your_own_tasks). Writing Your Own Tasks + +Of all the features Buildr provide, it doesn't have a task for making coffee. Yet. If you need to write your own tasks, you get all the power of Rake: you can use regular tasks, file tasks, task rules and even write your own custom task classes. Check out the "Rake documentation":http://docs.rubyrake.org/ for more information. + +We mentioned projects as namespaces before. When you call @task@ on a project, it finds or defines the task using the project namespace. So given a project object, @task('do-this')@ will return it's "do-this" task. If you lookup the source code for the @compile@ method, you'll find that it's little more than a shortcut for @task('compile')@. + +Another shortcut is the @file@ method. When you call @file@ on a project, Buildr uses the @path_to@ method to expand relative paths using the project's base directory. If you call @file('src')@ on _teh-impl_, it will return you a file task that points at the @teh-impl/src@ directory. + +In the current implementation projects are also created as tasks, although you don't invoke these tasks directly. That's the reason for not using a project name that conflicts with an existing task name. If you do that, you'll find quick enough, as the task will execute each time you run Buildr. + +So now that you know everything about projects and tasks, let's go and "build some code":building.html. diff --git a/buildr/doc/quick_start.textile b/buildr/doc/quick_start.textile new file mode 100644 index 0000000..e0831ce --- /dev/null +++ b/buildr/doc/quick_start.textile @@ -0,0 +1,210 @@ +--- +layout: default +title: Quick Start +--- + +This quick start guide is meant to be a _very_ simple introduction to Buildr and its most basic concepts. However, despite its basic level, we will still cover most of the concepts you will ever need to be productive with Buildr. We will leave out some important things (like "sub-projects":projects.html), and we will over-simplify some other concepts (such as "artifacts":artifacts.html). Nevertheless, most Buildr projects never need to go beyond the techniques contained within these pages. + +*No knowledge of Ruby is assumed.* Buildr is designed to be a very intuitive, very easy-to-use tool. You can create buildfiles which describe incredibly intricate projects, write custom tasks which do things far beyond Ant, and still never need to pick up more than a smattering of Ruby syntax. With that said, if you do know Ruby, Buildr's DSL will seem very natural and welcoming. We do assume that you have already "downloaded and installed":installing.html Buildr and are ready to put the tool to good use. + + +h2(#first-project). Your First Project + +Much like Maven, Buildr is oriented around projects and tasks. You define your project in a concise, declarative fashion and most common tasks (such as compilation and testing) will be made available to you "at no extra charge". Most of the project definition is contained within the _buildfile_ -- or _Buildfile_, if you're really in love with the Make convention -- a single file sitting at the root of your project. A project definition does not need to be any more complicated than the following: + +{% highlight ruby %} +define 'killer-app' +{% endhighlight %} + +h3. Compiling + +Of course, this isn't really giving Buildr much information. What it can't learn from the buildfile, Buildr will figure out by inspecting your directory structure. Java sources are expected to exist within the @src/main/java/@ directory. If Buildr finds these sources, it will automatically configure the compilation to source that directory, depositing the results in the @target/classes/@ directory (all under the project directory of course). We can run this compilation using the following command: + +{% highlight sh %} +$ buildr compile +{% endhighlight %} + +Information about the classpath and dependencies is described "later on":#dependencies. + +p(tip). By default, Buildr projects assume the Java language and the @src/main/java/@ source directory. You can also have projects in the Scala or Groovy language (both languages support joint compilation with Java). To use Scala, place your @.scala@ files in the @src/main/scala/@ directory and include the following invocation at the head of your buildfile: @require 'buildr/scala'@ Similarly, Groovy expects sources in the @src/main/groovy/@ directory and necessitates @require 'buildr/groovy'@ (see "languages":languages.html for more information). + +The @compile@ task will also detect _any_ files which exist under the @src/main/resources/@ directory. These resources are copied verbatim to the @target/resources/@ directory as part of the compilation task. Buildr also performs some basic change detection to minimize compilation. If your source files haven't changed since the last compile, then they will not be recompiled. + +h3. Packaging + +At some point, we're going to want to wrap up our classes and produce a single JAR file for distribution. Buildr can certainly help us with this, but we are going to need to provide it with a little bit more information. Specifically, we need to say the type of package to produce (e.g. @:jar@, @:war@, etc) as well as the current version of our project. This information is placed within the buildfile: + +{% highlight ruby %} +define 'killer-app' do + project.version = '0.1.0' + package :jar +end +{% endhighlight %} + +The @project.version@ attribute can be any value you like. Even non-numeric versions are perfectly acceptable (e.g. @'ikj-0.3.1-E'@). This version -- coupled with the packaging information -- will be used to generate a JAR file: @killer-app-0.1.0.jar@. As would be expected, this file is placed within the @target/@ directory when the following command is run: + +{% highlight sh %} +$ buildr package +{% endhighlight %} + +The @package@ task depends upon the @compile@ task, so if a rebuild is necessary prior to the creation of the JAR, Buildr will see to it. + +We can also chain tasks together in a single invocation. For example, we may want to clean all of the old compilation results prior to recompiling and generating a packaged result: + +{% highlight sh %} +$ buildr clean package +{% endhighlight %} + +The @clean@ task simply removes the @target/@ directory, effectively wiping out any compilation results like class files or resources. + +h3. Directory Structure + +As you may have noticed, Buildr does have some default notions about what a project should look like and how it should be organized. We think that these defaults are quite nice and make for a more navigable project. However, we do understand that not all projects are exactly alike. Buildr's "layouts":extending.html#layouts make it possible for any project to easily change these defaults. For example, this would allow you to easily migrate a project that had been based on a different directory structure, such as the @src/@ and @bin/@ convention often used by Ant. + + +h2(#dependencies). Dependencies + +So far, we have seen how Buildr can automatically infer what amounts to dozens of lines of @build.xml@ contents, all based on a buildfile and a directory structure. However, the best is yet to come. Buildr also provides Maven-style dependency management (but without the long loading times!). In other words, you specify each dependent library using a string descriptor and Buildr figures out how to download and configure your classpath (the library descriptors are just a Ruby array, therefore they are separated by commas (@,@)). You must specify at least one remote repository so that Buildr knows from where to download these libraries. For example, we can configure our project to reference the "Apache Commons CLI":http://commons.apache.org/cli/ library and download libraries from the Ibiblio repository: + +{% highlight ruby %} +repositories.remote << 'http://www.ibiblio.org/maven2' + +define 'killer-app' do + project.version = '0.1.0' + compile.with 'commons-cli:commons-cli:jar:1.2' + package :jar +end +{% endhighlight %} + +This sort of dependency declaration should look quite familiar if you are at all familiar with Maven. The general format for an artifact descriptor is _groupId:artifactId:packageType:version_. Any Maven artifacts included in this fashion will be retrieved from the "list of remote repositories":artifacts.html#repositories (in this case, Ibiblio) and installed in your local repository at @~/.m2/repository/@. + +p(tip). You can search the global repository of artifacts at sites like "MvnBrowser":http://www.mvnbrowser.com. Simply enter the name of the library you are looking for, and the search should pull up the groupId, artifactId and a list of available versions. + +Unfortunately, not all libraries are quite as simple as Commons CLI. Many libraries (such as Apache Wicket) have dependencies of their own. While we may be able to _compile_ against Apache Wicket without these extra libraries on our classpath, we cannot actually _run_ our application without its transitive dependencies. To avoid tracking down each of these dependencies and adding them manually, we can simply use the @transitive@ directive (this is how Maven behaves by default): + +{% highlight ruby %} +repositories.remote << 'http://www.ibiblio.org/maven2' + +define 'killer-app' do + project.version = '0.1.0' + compile.with transitive('org.apache.wicket:wicket:jar:1.4-rc6') + package :jar +end +{% endhighlight %} + +The @compile.with@ property accepts a full array of comma-separated artifacts, making it possible to specify any number of dependencies as necessary. Of course, such a long list of verbose string descriptors could get very tiresome and messy. For this reason, it is conventional to assign each dependency to a constant (e.g. @WICKET@) which is declared just above the project in the buildfile and passed to @compile.with@ in a clean, easy-to-read style: + +{% highlight ruby %} +repositories.remote << 'http://www.ibiblio.org/maven2' + +WICKET = transitive('org.apache.wicket:wicket:jar:1.4-rc6') +SLF4J = 'org.slf4j:slf4j-jdk14:jar:1.5.8' + +define 'killer-app' do + project.version = '0.1.0' + compile.with WICKET, SLF4J + package :jar +end +{% endhighlight %} + +Unfortunate as it may seem, not all libraries are available in Maven repositories. While most of the major libraries (e.g. Hibernate, Spring, etc) are kept updated by intrepid volunteers, some of the more obscure frameworks are left out in the cold. An example of one such framework is "DBPool":http://www.snaq.net/java/DBPool, a very fast connection pool designed to integrate with JDBC. However, like most Java libraries, DBPool does provide a zip archive which contains the JAR file, as well as some documentation and perhaps a license or two. + +Almost magically, we can instruct Buildr to get the DBPool artifact from this URL. Buildr will treat this download just like any other artifact, retrieving it when requried by the @compile@ task. However, unlike a normal Maven artifact, Buildr will do some extra processing once the download is complete. It will actually dig into the downloaded archive, detect and extract the JAR file, installing it into the local repository just like any other artifact: + +{% highlight ruby %} +DBPOOL = 'net.snaq:dbpool:jar:4.8.3' +download artifact(DBPOOL) => 'http://www.snaq.net/java/DBPool/DBPool_v4.8.3.zip' + +define 'killer-app' do + project.version '0.1.0' + compile.with DBPool + package :jar +end +{% endhighlight %} + +This is one area where Buildr's dependency management vastly excedes Maven's. With Maven, you would have to install the DBPool dependency manually. Buildr's auto-magical download and extraction keeps the dependency definitions centralized within the buildfile, available to your entire team and automatically resolved as needed by the compilation tasks. + + +h2(#testing). Testing + +Buildr supports auto-magical integration with a number of mainstream testing frameworks. For Java, this includes the ubiquitus JUnit4, as well as TestNG and a number of others. Scala supports Specs and ScalaTest, while Groovy supports EasyB. Configuration is as simple as placing your test sources in the appropriate directory. In the case of JUnit or TestNG, this would be @src/test/java/@. Once these tests are in place, we can run them using the @test@ task: + +{% highlight sh %} +$ buildr test +{% endhighlight %} + +When the @test@ task runs, it will ensure that your main sources are compiled, as well as the tests themselves. In the case of JUnit4, test classes are auto-detected based on which base class they extend (@TestCase@). These tests will be invoked using the special test classpath. This classpath includes all of the dependencies passed to @compile.with@ along with the dependencies required for testing. Thus, Buildr will actually go out and download JUnit 4.5 (if necessary) and place that JAR on the classpath in order to run your tests. It is also possible to add artifacts specifically required for testing. So, if your tests make use of the Commons Collections library, but your main sources do not, you can include that dependency only for the tests by using the @test.with@ property. This functions identically to @compile.with@: + +{% highlight ruby %} +define 'killer-app' do + project.version = '0.1.0' + compile.with 'commons-cli:commons-cli:jar:1.2' + test.with 'commons-collections:commons-collections:jar:3.2' + package :jar +end +{% endhighlight %} + +Of course, not everyone _likes_ JUnit4. As mentioned previously, Buildr supports a number of test frameworks. It is possible to use TestNG instead of JUnit4 by setting the @test.using@ property to @:testng@: + +{% highlight ruby %} +define 'killer-app' do + project.version = '0.1.0' + compile.with 'commons-cli:commons-cli:jar:1.2' + test.with 'commons-collections:commons-collections:jar:3.2' + test.using :testng + package :jar +end +{% endhighlight %} + +Note that only one test framework per-project may be used. This may seem like an obvious restriction given that both frameworks introduced so far have used the same directory, but other frameworks such as Specs and EasyB do not follow the same convention. In cases of ambiguity (for example, when tests are present in both @src/test/java/@ _and_ @src/spec/scala/@), only one test framework will be chosen, but this choice is not well-defined. When in doubt, explicitly specify the test framework with the @test.using@ property. This overrides Buildr's auto-detection and ensures sane behavior. + +Other test frameworks are documented "here":testing.html and "here":languages.html. + + +h2(#custom-tasks). Custom Tasks + +If there is one area in which Buildr excels, it is defining custom tasks. This is something which is notoriously difficult in both Ant and Maven, often requiring separate Java plugins and mountains of code simply to perform basic tasks. For example, let's imagine that we wanted to define a @run@ task which would compile and run our "killer-app" project. This is a simple matter of invoking the @java@ command against our main class: + +{% highlight ruby %} +define 'killer-app' do + project.version = '0.1.0' + package :jar + + task :run => :compile do + system 'java -cp target/classes org.apache.killer.Main' + end +end +{% endhighlight %} + +This code defines a new task, @run@, which depends upon the @compile@ task. This task only performs a single operation: it invokes the @system@ method, passing the relevant command as a string. Note that the @system@ method documentation may be found "here":http://www.ruby-doc.org/core/classes/Kernel.html#M005971. Tasks use real Ruby (actually, the entire buildfile is real Ruby too), so if you are familiar with that language, then you should be right at home writing custom tasks in Buildr. We can invoke this task in the following way: + +{% highlight sh %} +$ buildr killer-app:run +{% endhighlight %} + +This works, but it's clumsy. The reason we had to give the "@killer-app:@" prefix is because we defined the @run@ task _within_ our project, rather than outside of the @define@ block. However, if we define @run@ outside of the project, then we don't really have access to the @compile@ task (which is project-specific). The solution here is a bit of magic known as @local_task@. This is how tasks like @compile@ and @test@, which are technically project-specific (think: instance methods) can be invoked without the fully-qualified project name: + +{% highlight ruby %} +Project.local_task :run + +define 'killer-app' do + project.version '0.1.0' + + package :jar + + task :run => :compile do + system 'java -cp target/classes org.apache.killer.Main' + end +end +{% endhighlight %} + +Now, we can invoke @run@ exactly the way we want, with a minimum of wasted characters: + +{% highlight sh %} +$ buildr run +{% endhighlight %} + + +h2(#summary). Summary + +As long as this guide was, we have barely even scratched the surface of Buildr's true power. This was meant only to get you up and running as quickly as possible, exploiting some of Buildr's unique features to ease your build process. For more comprehensive documentation, start reading about "projects in Buildr":projects.html and work your way from there. diff --git a/buildr/doc/releasing.textile b/buildr/doc/releasing.textile new file mode 100644 index 0000000..6340f5e --- /dev/null +++ b/buildr/doc/releasing.textile @@ -0,0 +1,117 @@ +--- +layout: default +title: Releasing +--- + +Now that we built and tested our awesome software, let's tell the world and release it. + +Each buildfile can specify the current version with a constant named @VERSION_NUMBER@ or @THIS_VERSION@. + +{% highlight ruby %} +THIS_VERSION = "1.0.0-SNAPSHOT" + +define 'killer-app' do + + project.version = THIS_VERSION + + # ... +end +{% endhighlight %} + + +h2(#default). What does a release do? + +The default behavior of the @Release@ task is the following: +# Check that the version to be released and the next version are different +# Check that the project is being tracked by Git or Subversion +# Package, test and deploy the artifacts using @THIS_VERSION@ value minus the @-SNAPSHOT@ suffix (if any) +# Tag the repository with the released version number +# Update the value of @THIS_VERSION@ in the buildfile with the next version number + +Buildr will increment the last digit of the 3-digit versioni number if @THIS_VERSION@ ends with @-SNAPSHOT@. +So, at the end of a release, the buildfile now looks like this: + +{% highlight ruby %} +THIS_VERSION = "1.0.1-SNAPSHOT" + +define 'killer-app' do + + project.version = THIS_VERSION + + # ... +end +{% endhighlight %} + +And the Git repository now contains two new commits and a new tag. + +{% highlight sh %} +~/w/killer-app[master]$git ol -4 +c1af3d5 (HEAD, origin/master, master) Changed version number to 1.0.1-SNAPSHOT +dd35015 (tag: 1.0.0) Changed version number to 1.0.0 +76c96e7 Last fix before the release +{% endhighlight %} + + +h2(#custom_version). How to specify my own version number scheme? + +If @THIS_VERSION@ does not contain @-SNAPSHOT@, Buildr delegates the resolution of the next version number to the user which has 2 differents ways to express her wishes: @Release.next_version@ or the environment variable @NEXT_VERSION@. + +h3(#next_version_proc). Using Release.next_version + +The @Release@ class can receive the next version of the buildfile. This could be a string or a proc that would receive the current version and return the next version. + +{% highlight ruby %} +THIS_VERSION = "1.0.0-SNAPSHOT" + +# a string +Release.next_version = "2.0.0-SNAPSHOT" + +# or a proc +Release.next_version = lambda do |this_version| # 1.0.0-SNAPSHOT + new_version = @THIS_VERSION@.split(/\./) + new_version[0] = new_version[0] + 1 + new_version +end + +define 'killer-app' do + + project.version = THIS_VERSION + + # ... +end +{% endhighlight %} + + +h3(#next_version_envvar). Using the environment variable NEXT_VERSION + +If the environment variable @NEXT_VERSION@ is set, Buildr will use this value to update @THIS_VERSION@ at the end of the release. + +For conveniency, this variable is case insensitive. + +So, all 3 following commands will run a release with a custom new version: + +{% highlight sh %} +$ buildr release next_version="1.0.0-rc1" +$ env next_version="1.0.0-rc1" buildr release +$ env NEXT_VERSION="1.0.0-rc1" buildr release +{% endhighlight %} + +Those commands will generate the Buildfile below: + +{% highlight ruby %} +THIS_VERSION = "1.0.0-rc1" + +define 'killer-app' do + + project.version = THIS_VERSION + + # ... +end +{% endhighlight %} + +The environment variable @NEXT_VERSION@ has precedence over Release.next_version. + +h2(#custom_tag_and_msg). How to specify my own tag name and commit message? + +As explained earlier, Buildr will create two new commits and a new tag in the version control system. Similarly to @Release.next_version@, the commit message and the tag name can be customized with @Release.message@ and @Release.tag_name@. Both could be strings or procs that would receive the released version @THIS_VERSION@ without @-SNAPSHOT@. + diff --git a/buildr/doc/scripts/buildr-git.rb b/buildr/doc/scripts/buildr-git.rb new file mode 100755 index 0000000..1cdd61a --- /dev/null +++ b/buildr/doc/scripts/buildr-git.rb @@ -0,0 +1,512 @@ +#!/usr/bin/env ruby +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +# This script helps buildr developers to obtain their own git clone from +# github, and also provides GitFlow commands to keep the git mirror in sync +# with Apache SVN. +# +# If you already have a buildr clone, just do the following: +# +# git config alias.apache '!'"ruby $PWD/doc/scripts/buildr-git.rb" +# +# After this, you have a 'git apache' command and you can try (be sure to read the help) +# +# git apache help +# git apache setup svn --help +# git apache sync --help +# +# To configure your local repo for svn synchronization, +# +# git apache update-authors +# git remote add upstream git@github.com:buildr/buildr.git +# git apache setup svn --username apacheLogin --apache-git upstream +# git apache sync +# +# This script can also be run without having a local buildr clone: +# +# ruby -ropen-uri -e 'eval(open("http://svn.apache.org/viewvc/buildr/trunk/doc/scripts/buildr-git.rb?view=co").read)' help + + + +require 'yaml' +require 'open-uri' + +if $0 == '-e' # invoked from open-uri + gitflow = "http://svn.apache.org/viewvc/buildr/trunk/doc/scripts/gitflow.rb?view=co" + eval(open(gitflow).read) +else + require File.expand_path('gitflow', File.dirname(__FILE__)) +end + +GitFlow.program = 'buildr-git' + +module BuildrGit + + class UpdateUsersCommand < GitFlow/'update-users' + + @help = "Update list of Apache SVN committers from Jukka's list." + @@url = 'http://people.apache.org/~jukka/authors.txt' + + def self.authors_file + File.expand_path('.git/authors.txt', Dir.pwd) + end + + def self.user_email(apache_login, authors_file = nil) + authors_file ||= self.authors_file + authors = YAML.load(File.read(authors_file).gsub!(/\s+=\s+/, ': ')) + contact = authors[apache_login] + fail "You are not listed as apache commiter on #{authors_file}" unless contact + fail "Not a valid contact line: #{contact}" unless contact =~ /\s+<(.*)>/ + [$`, $1] + end + + def options(opts) + opts.url = @@url + opts.file = self.class.authors_file + [['-u', '--url URL', + "From URL. defaults to: #{opts.url}", lambda { |url| + opts.url = url + }], + ['-f', '--file FILE', + "Write to FILE, defaults to #{opts.file}", lambda { |path| + opts.file = path + }] + ] + end + + def execute(opts, argv) + FileUtils.mkdir_p(File.dirname(opts.file)) + content = open(opts.url).read + File.open(opts.file, "w") { |f| f.print content } + end + end + + class CloneCommand < GitFlow/:clone + @help = "Create a clone from github.com/buildr repository." + + def options(opts) + opts.origin = 'git://github.com/buildr/buildr.git' + opts.svn_prefix = 'apache' + opts.project = 'buildr' + opts.local = expand_path(opts.project) + [['--prefix SVN_PREFIX', opts.svn_prefix, lambda { |p| + opts.svn_prefix = p }], + ['--origin GIT_ORIGIN', opts.origin, lambda { |o| + opts.origin = o }], + ['-d', '--dir DIR', opts.local, lambda { |d| opts.local = d }] + ] + end + + def execute(opts, argv) + git 'clone', opts.origin, opts.local + Dir.chdir(opts.local) do + run 'update-users' + run 'setup' + end + end + end + + class SetupCommand < GitFlow/:setup + @help = "Setup your buildr clone to be used with git mirror." + def options(opt) + [] + end + + def execute(opt, argv) + run 'setup', 'alias' + run 'setup', 'svn' + end + end + + class SetupAliasCommand < SetupCommand/:alias + def execute(opt, argv) + me = expand_path('doc/scripts/buildr-git.rb') + git 'config', 'alias.apache', "!ruby #{me}" + end + end + + class SetupSvnCommand < SetupCommand/:svn + @help = "Setup for getting changes from Apache SVN." + + def options(opt) + opt.svn_prefix = 'apache' + opt.svn_path = 'buildr' + opt.townhall = 'origin' + [['--username SVN_USER', 'Use Apache svn username for this svn remote', + lambda { |e| opt.apache_login = e }], + ['--svn-prefix PREFIX', 'The name of svn remote to use for project.', + "Defaults to #{opt.svn_prefix}", + lambda{|p| opt.svn_prefix = p }], + ['--svn-uri URI', lambda {|p| opt.svn_uri = p }], + ['--svn-rev REVISION', lambda {|p| opt.svn_rev = p }], + ['--svn-path PATH', 'The path to append to svn-uri.', + "Defaults to #{opt.svn_path}", lambda {|p| opt.svn_path = p }], + ['--apache-git REMOTE', 'The name of remote you are using as town-hall git repo.', + "Defaults to #{opt.townhall}", + lambda {|p| opt.townhall = p }] + ] + end + + def execute(opt, argv) + authors_file = UpdateUsersCommand.authors_file + git 'config', 'svn.authorsfile', authors_file + git 'config', 'apache.svn', opt.svn_prefix + git 'config', 'apache.git', opt.townhall + + if opt.apache_login + user, email = UpdateUsersCommand.user_email(opt.apache_login, authors_file) + puts "You claim to be #{user} <#{email}> with apache login: #{opt.apache_login}" + git('config', 'user.name', user) + git('config', 'user.email', email) + end + + if opt.svn_rev + revision = opt.svn_rev + else + location, revision = svn_loc_rev + revision = opt.svn_rev || revision + end + + if opt.svn_uri + repo = opt.svn_uri + else + fail "No #{opt.svn_path} directory on #{location}" unless + location =~ /\/#{opt.svn_path}/ + repo = $` + end + + # Tell git where the svn repository is + git('config', "svn-remote.#{opt.svn_prefix}.url", repo) + git('config', "svn-remote.#{opt.svn_prefix}.fetch", + "#{opt.svn_path}/trunk:refs/remotes/#{opt.svn_prefix}/trunk") + git('config', "svn-remote.#{opt.svn_prefix}.branches", + "#{opt.svn_path}/branches/*:refs/remotes/#{opt.svn_prefix}/*") + git('config', "svn-remote.#{opt.svn_prefix}.tags", + "#{opt.svn_path}/tags/*:refs/remotes/#{opt.svn_prefix}/tags/*") + + # Store the user for svn dcommit + if opt.apache_login + git('config', "svn-remote.#{opt.svn_prefix}.username", opt.apache_login) + end + + # Create the svn branch, do this instead of pulling the full svn history + git('update-ref', "refs/remotes/#{opt.svn_prefix}/trunk", + 'refs/remotes/origin/master') + # create tags from git + git('tag').split.each do |tag| + git('update-ref', "refs/remotes/#{opt.svn_prefix}/tags/#{tag}", + "refs/tags/#{tag}") + end + # update svn metadata + mkdir_p(expand_path('.git/svn')) + svn_meta = expand_path('.git/svn/.metadata') + git('config', '--file', svn_meta, + "svn-remote.#{opt.svn_prefix}.branches-maxRev", revision) + git('config', '--file', svn_meta, + "svn-remote.#{opt.svn_prefix}.tags-maxRev", revision) + end + + def svn_loc_rev + meta = sh('git log -n 10 | grep git-svn-id | head -n 1').chomp + fail "No svn metadata on last 10 commits" if meta.empty? + meta.split[1].split('@') + end + end + + class FetchCommand < GitFlow/:fetch + @help = "Get changes from svn, creating tags, branches on townhall" + @documentation = <<-DOC +This command can be used to fetch changes from Apache\'s SVN repo. + +GIT CONFIG VALUES: + +apache.svn - The svn remote using to get changes from Apache SVN. + Set by setup-svn --svn-prefix. + DOC + + def options(opt) + opt.apache_svn = git('config', '--get', 'apache.svn').chomp rescue nil + [['--apache-svn SVN_REMOTE', 'The SVN remote used to get changes from Apache', + "Current value: #{opt.apache_svn}", + lambda { |r| opt.apache_svn = r }] + ] + end + + def execute(opt, argv) + fail "Missing apache.svn config value" unless opt.apache_svn + git('svn', 'fetch', opt.apache_svn) + end + end + + class SyncCommand < GitFlow/:sync + @help = "Synchronizes between Apache svn and git townhall." + @documentation = <<-DOC +This command will perform the following actions: + * fetch changes from apache svn. + * rebase them on the current branch or on the one specified with --onto + * dcommit (this will push your changes to Apache trunk) + +GIT CONFIG VALUES: + +apache.svn + The svn remote using to get changes from Apache SVN. + Set by setup-svn --svn-prefix. + +apache.git + The git remote used as townhall repository. + Set by setup-svn --townhall. + +svn-remote.APACHE_GIT.username + If configured, sync will use this svn username while dcommiting. +DOC + + def options(opt) + git('branch').split.tap { |n| opt.current = n[n.index('*')+1] } + opt.branch = opt.current + opt.svn_branch = 'trunk' + opt.git_branch = 'master' + opt.apache_git = git('config', '--get', 'apache.git').chomp rescue nil + opt.apache_svn = git('config', '--get', 'apache.svn').chomp rescue nil + opt.svn_username = git('config', '--get', + "svn-remote.#{opt.apache_svn}.username").chomp rescue nil + [['--apache-svn SVN_REMOTE', 'The SVN remote used to get changes from Apache', + "Current value: #{opt.apache_svn}", + lambda { |r| opt.apache_svn = r }], + ['--apache-git REMOTE', 'The git remote used as town-hall repository.', + "Current value: #{opt.apache_git}", + lambda { |r| opt.apache_git = r }], + ['--username SVN_USER', + 'Specify the SVN username for dcommit', + "Defaults to: #{opt.svn_username}", + lambda { |b| opt.svn_username = b }], + ['--svn-branch SVN_BRANCH', + 'Specify the SVN branch to rebase changes from, and where to dcommit', + "Defaults to: #{opt.svn_branch}", + lambda { |b| opt.svn_branch = b }], + ['--git-branch REMOTE_BRANCH', + 'Specify the remote town-hall branch (on apache.git) to update', + "Defaults to: #{opt.git_branch}", + lambda { |b| opt.git_branch = b }], + ['--branch BRANCH', 'Specify the local branch to take changes from', + "Current branch: #{opt.branch}", + lambda { |b| opt.branch = b }] + ] + end + + def execute(opt, argv) + # obtain the svn url + url = git('config', '--get', "svn-remote.#{opt.apache_svn}.url").chomp + # obtain the path for project + path = git('config', '--get', "svn-remote.#{opt.apache_svn}.branches"). + chomp.split('/branches').first + commit_url = "#{url}/#{path}/#{opt.svn_branch}" + + # obtain latest changes from svn + git('svn', 'fetch', '--svn-remote', opt.apache_svn) + # obtain latest changes from git + git('fetch', opt.apache_git, + "#{opt.git_branch}:refs/remotes/#{opt.apache_git}/#{opt.git_branch}") + + # rebase svn changes in the desired branch + git('rebase', "#{opt.apache_svn}/#{opt.svn_branch}", opt.branch) + git('rebase', "#{opt.apache_git}/#{opt.git_branch}", opt.branch) + + # dcommit to the specific svn branch + ['svn', 'dcommit', + '--svn-remote', opt.apache_svn, '--commit-url', commit_url].tap do |cmd| + if opt.svn_username + cmd << '--username' << opt.svn_username + end + git(*cmd) + end + + # update townhall remote ref + git('update-ref', + "refs/remotes/#{opt.apache_git}/#{opt.git_branch}", + "refs/remotes/#{opt.apache_svn}/#{opt.svn_branch}") + + # forward the remote townhall/master to apache/trunk + git('push', opt.apache_git, + "refs/remotes/#{opt.apache_git}/#{opt.git_branch}:#{opt.git_branch}") + + # get back to the original branch + git('checkout', opt.current) + end + end + + + # This one is displayed when the user executes this script using + # open-uri -e + HEADER = <
to see options, <-H> to see notes about configured aliases +and recommended workflow, or any other option. + +Ctrl+D or an invalid option to abort +HEADER + + # When fork is completed, we display the following notice on a + # pager, giving the user a brief overview of git aliases used + # to keep the mirror in sync. + NOTICE = <git to keep trunk upto date. + You need to be an Apache committer and have permissions on the SVN repo. + + It's VERY IMPORTANT for Buildr committers to remember that contributions from + external entities wanting to be accepted will require them to sign the Apache ICLA. + We provide the git mirror to make it easier for people to experiment and + contribute back to Buildr, before merging their code in, please remember they + have to create create a JIRA issue granting ASF permission to include their code, + just like any other contribution following Apache's guidelines. + + So, it's very important - if you care about meritocracy - to follow or at + least that you get an idea of the recommended workflow. + +RECOMMENDED WORKFLOW: + + So now that you have your local buildr copy you can create topic branches + to work on independent features, and still merge easily with head changes. + + They may seem lots of things to consider, but it's all for Buildr's healt. + As all things git, you can always follow your own workflow and even create + aliases on you .git/config file to avoid typing much. So, here they are: + + 1) get your gitflow configured + (you have already do so, this was the most difficult part) + + 2) create a topic branch to work on, say.. you want to add cool-feature: + + git checkout -b cool-feature master + # now on branch cool-feature + + 3) hack hack hack.. use the source luke. + every time you feel you have something important like added failing + spec, added part of feature, or resolved some conflict from merges, + you can commit your current progress. If you want to be selective, use: + + git commit --interactive + + 3) review your changes, get ALL specs passing, repeat step 3 as needed + + 4) let's see what are they doing on trunk + + git apache-fetch + # You can inspect the upstream changes without having to merge them + git log apache/trunk # what are they doing!! + + 5) integrate mainstream changes to your cool-feature branch, you can always + use `git cherry-pick` to select only some commits. + + git merge apache/trunk cool-feature + + 6) Go to 3 unless ALL specs are passing. + + 7.a) (Skip to 7.b you have commit bit on Apache's SVN) + Create a patch using `git format-patch` + Promote your changes, create a JIRA issue and upload it granting Apache + license to include your code: + + https://issues.apache.org/jira/browse/BUILDR + dev@buildr.apache.org + + 7.b) Now you have everyhing on staging area and merged important changes + from apache/trunk, it's time to commit them to Apache's SVN. + + git apache-push + + 8) Optional. If you are a buildr committer you may want to synchronize + the github mirror for helping others to get changes without having to + wait on Victor's cronjob to run every hour (useful for urgent changes). + + git synchronize + + 9) Pull changes from origin frequently. + + git fetch origin + git rebase --onto origin/master master master + + 10) Unconditionally, Go to step 2 ;) + Share your gitflow workflow, git tips, etc. + +RESOURCES: + + http://github.com/buildr/buildr/tree/master + http://git.or.cz/gitwiki/GitCheatSheet + http://groups.google.com/group/git-users/web/git-references + +NOTICE + #' for emacs + +end diff --git a/buildr/doc/scripts/gitflow.rb b/buildr/doc/scripts/gitflow.rb new file mode 100755 index 0000000..9b7d913 --- /dev/null +++ b/buildr/doc/scripts/gitflow.rb @@ -0,0 +1,296 @@ +#!/usr/bin/env ruby +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require 'optparse' +require 'ostruct' +require 'fileutils' + +module GitFlow + extend self + + attr_accessor :should_run, :trace, :program + + self.program = 'gitflow' + self.should_run = true # should we run at exit? + + HELP = <<-HELP + +GitFlow is a tool to create custom git commands implemented in ruby. +It is generic enougth to be used on any git based project besides Apache Buildr. + +OVERVIEW: + +gitflow is intended to help developers with their daily git workflow, +performing repetitive git commands for them. It is implemented in +ruby so you can do anything, from invoking rake tasks to telling +people on twitter you are having trouble with their code :P. + +To get help for a specific command use: + gitflow.rb help command + gitflow.rb command --help + +For convenience you can create an alias to be execute using git. +The following example registers buildr-git.rb, which provides apache +svn and git synchronization commands: + + git config alias.apache '!'"ruby $PWD/doc/scripts/buildr-git.rb" + +After that you can use + git apache command --help + +EXTENDING YOUR WORKFLOW: + +You can create your own gitflow commands, to adapt your development +workflow. + +Simply create a ruby script somewhere say ~/.buildr/gitflow.rb +And alias it in your local repo: + + git config alias.flow '!'"ruby ~/.buildr/gitflow.rb" + git config alias.work '!'"ruby ~/.buildr/gitflow.rb my-flow sub-work" + +A sample command would look like this.. (you may want to look at buildr-git.rb) + + #!/usr/bin/env ruby + require /path/to/gitflow.rb + + class MyCommand < GitFlow/'my-flow' + + @help = "Summary to be displayed when listing commands" + @documentation = "Very long help that will be paged if necessary. (for --help)" + + # takes an openstruct to place default values and option values. + # returns an array of arguments given to optparse.on + def options(opts) + opts.something = 'default' + [ + ['--name NAME', lambda { |n| opts.name = n }], + ['--yes', lambda { |n| opts.yes = true }] + ] + end + + # takes the opts openstruct after options have been parsed and + # an argv array with non-option arguments. + def execute(opts, argv) + # you can run another command using + run('other-command', '--using-this', 'arg') + some = git('config', '--get', 'some.property').chomp rescue nil + page { puts "This will be paged on terminal if needed" } + end + + class SubCommand < MyCommand/'sub-work' + ... # implement a subcommand + end + + end + +You would then get help for your command with + + git flow my-flow --help + git work --help + +Using gitflow you can customize per-project git interface. + +HELP + + # Pager from http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby + def pager + return if RUBY_PLATFORM =~ /win32/ + return unless STDOUT.tty? + + read, write = IO.pipe + + unless Kernel.fork # Child process + STDOUT.reopen(write) + STDERR.reopen(write) if STDERR.tty? + read.close + write.close + return + end + + # Parent process, become pager + STDIN.reopen(read) + read.close + write.close + + ENV['LESS'] = 'FSRX' # Don't page if the input is short enough + + Kernel.select [STDIN] # Wait until we have input before we start the pager + pager = ENV['PAGER'] || 'less' + exec pager rescue exec '/bin/sh', '-c', pager + end + + # Return a class to be extended in order to register a GitFlow command + # if command name is nil, it will be registered as the top level command. + # Classes implementing commands also provide this method, allowing for + # sub-command creation. + def /(command_name) + command_name = command_name.to_s unless command_name.nil? + cls = Class.new { include GitFlow::Mixin } + (class << cls; self; end).module_eval do + attr_accessor :help, :documentation, :command + define_method(:/) do |subcommand| + raise "Subcommand cannot be nil" unless subcommand + GitFlow/([command_name, subcommand].compact.join(' ')) + end + define_method(:inherited) do |subclass| + subclass.command = command_name + GitFlow.commands[command_name] = subclass + end + end + cls + end + + def commands + @commands ||= Hash.new + end + + def optparse + optparse = opt = OptionParser.new + opt.separator ' ' + opt.separator 'OPTIONS' + opt.separator ' ' + opt.on('-h', '--help', 'Display this help') do + GitFlow.pager; puts opt; throw :exit + end + opt.on('--trace', 'Display traces') { GitFlow.trace = true } + optparse + end + + def command(argv) + cmds = [] + argv.each_with_index do |arg, i| + arg = argv[0..i].join(' ') + cmds << commands[arg] if commands.key?(arg) + end + cmds.last || commands[nil] + end + + def run(*argv) + catch :exit do + command = self.command(argv).new + argv = argv[command.class.command.split.length..-1] if command.class.command + parser = optparse + parser.banner = "Usage: #{GitFlow.program} #{command.class.command} [options]" + options = OpenStruct.new + if command.respond_to?(:options) + command.options(options).each { |args| parser.on(*args) } + end + if command.class.documentation && command.class.documentation != '' + parser.separator ' ' + parser.separator command.class.documentation.split(/\n/) + end + parser.parse!(argv) + command.execute(options, argv) + end + end + + module Mixin + include FileUtils + + # Override this method in your command class if it + # needs to parse command line options. + # + # This method takes an openstruct object as argument + # allowing you to store default values on it, and + # set option values. + # + # The return value must be an array of arguments + # given to optparse.on + def options(opt) + [] + end + + # Override this method in your command class to implement + # the command. + # First argument is the openstruct object after + # it has been populated by the option parser. + # Second argument is the array of non-option arguments. + def execute(opt, argv) + fail "#{self.class.command} not implemented" + end + + # Run the command line given on argv + def run(*argv, &block) + GitFlow.run(*argv, &block) + end + + # Yield paging the blocks output if necessary. + def page + GitFlow.pager + yield + end + + def trace(*str) + STDERR.puts(*str) if GitFlow.trace + end + + def git(*args) + cmd = 'git ' + args.map { |arg| arg[' '] ? %Q{"#{arg}"} : arg }.join(' ') + trace cmd + `#{cmd}`.tap { + fail "GIT command `#{cmd}` failed with status #{$?.exitstatus}" unless $?.exitstatus == 0 + } + end + + def sh(*args) + `#{args.join(' ')}`.tap { + fail "Shell command `#{args.join(' ')}` failed with status #{$?.exitstatus}" unless $?.exitstatus == 0 + } + end + + def expand_path(path, dir=Dir.pwd) + File.expand_path(path, dir) + end + end + + class NoSuchCommand < GitFlow/nil + @documentation = HELP + + def execute(opts, argv) + page do + puts "Command not found: #{argv.join(' ').inspect}" + puts "Try `#{GitFlow.program} help` to obtain a list of commands." + end + end + end + + class HelpCommand < GitFlow/:help + @help = "Display help for a command or show command list" + @documentation = "Displays help for the command given as argument" + + def execute(opts, argv) + if argv.empty? + opt = GitFlow.optparse + opt.banner = "Usage: #{GitFlow.program} command [options]" + opt.separator ' ' + opt.separator 'COMMANDS' + opt.separator ' ' + commands = GitFlow.commands.map { |name, cls| [nil, name, cls.help] }. + sort_by { |a| a[1] || '' } + commands.each { |a| opt.separator("%-2s%-25s%s" % a) if a[1] } + opt.separator ' ' + opt.separator 'You can also obtain help for any command giving it --help.' + page { puts opt } + else + run(*(argv + ['--help'])) + end + end + end + +end + +at_exit { GitFlow.run(*ARGV) if GitFlow.should_run } diff --git a/buildr/doc/scripts/install-jruby.sh b/buildr/doc/scripts/install-jruby.sh new file mode 100755 index 0000000..5a2881b --- /dev/null +++ b/buildr/doc/scripts/install-jruby.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. +if [ -z `which jruby` ] ; then + version=1.6.2 + target=/opt/jruby + echo "Installing JRuby ${version} in ${target}" + sudo mkdir -p $(dirname ${target}) + wget http://jruby.org.s3.amazonaws.com/downloads/${version}/jruby-bin-${version}.tar.gz + tar -xz < jruby-bin-${version}.tar.gz + sudo mv jruby-${version} ${target} + rm jruby-bin-${version}.tar.gz + export PATH=$PATH:${target} + if [ -e ~/.bash_profile ] ; then + echo "export PATH=${target}/bin:\$PATH" >> ~/.bash_profile + elif [ -e ~/.profile ] ; then + echo "export PATH=${target}/bin:\$PATH" >> ~/.profile + else + echo "You need to add ${target}/bin to the PATH" + fi +fi + +if [ `which buildr` ] ; then + echo "Updating to the latest version of Buildr" + sudo jruby -S gem update buildr +else + echo "Installing the latest version of Buildr" + sudo jruby -S gem install buildr +fi +echo + +jruby -S buildr --version diff --git a/buildr/doc/scripts/install-linux.sh b/buildr/doc/scripts/install-linux.sh new file mode 100755 index 0000000..1180860 --- /dev/null +++ b/buildr/doc/scripts/install-linux.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. +if [ -z `which ruby` ] ; then + echo "You do not have Ruby 1.8.6 ..." + # yum comes first since some people have apt-get installed in addition to yum. + # urpmi is added in case of mandriva : apt-get or yum are working for mandriva too + if [ `which yum` ] ; then + echo "Installing Ruby using yum" + sudo yum install ruby rubygems ruby-devel gcc + elif [ `which apt-get` ] ; then + echo "Installing Ruby using apt-get" + # ruby package does not contain RDoc, IRB, etc; ruby-full is a meta-package. + # build-essentials not installed by default in Ubuntu, required for C extensions. + sudo apt-get install ruby-full ruby1.8-dev libopenssl-ruby build-essential + # RubyGems broken on Ubuntu, installing directly from source. + echo "Installing RubyGems from RubyForge" + wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.7.tgz + tar xzf rubygems-1.3.7.tgz + cd rubygems-1.3.7 + sudo ruby setup.rb + cd .. + rm -rf rubygems-1.3.7 + # ruby is same as ruby1.8, we need gem that is same as gem1.8 + sudo ln -s /usr/bin/gem1.8 /usr/bin/gem + elif [ `which urpmi` ] ; then + echo "Installing Ruby using urpmi" + sudo urpmi ruby rubygems ruby-devel gcc + if [ $? -ne 0 ] ; then + echo "URPMI install error" + exit 1 + fi + else + echo "Can only install Ruby 1.8.6 using apt-get, yum or urpmi, and can't find any of them" + exit 1 + fi + echo +fi + +if [ -z $JAVA_HOME ] ; then + echo "Please set JAVA_HOME before proceeding" + exit 1 +fi + +if [ $(gem --version) \< '1.3.7' ] ; then + echo "Upgrading to latest version of RubyGems" + sudo gem update --system + sudo update_rubygems + echo +fi + +if [ `which buildr` ] ; then + echo "Updating to the latest version of Buildr" + sudo env JAVA_HOME=$JAVA_HOME gem update buildr +else + echo "Installing the latest version of Buildr" + sudo env JAVA_HOME=$JAVA_HOME gem install buildr +fi +echo + +buildr --version diff --git a/buildr/doc/scripts/install-osx.sh b/buildr/doc/scripts/install-osx.sh new file mode 100755 index 0000000..54bb5be --- /dev/null +++ b/buildr/doc/scripts/install-osx.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. +version=$(ruby --version) +if [ ${version:5:5} \< '1.8.6' ] ; then + echo "You do not have Ruby 1.8.6 or later, attempting to install a newer version." + if [ `which port` ] ; then + echo "Installing Ruby using MacPorts" + sudo port install ruby rb-rubygems + elif [ `which fink` ] ; then + echo "Installing Ruby using Fink" + sudo fink install ruby ruby18-dev rubygems-rb18 + else + echo "Can only upgrade to Ruby 1.8.6 using either MacPorts or Fink, and can't find either one" + exit 1 + fi + echo +fi + +if [ -z $JAVA_HOME ] ; then + echo "Setting JAVA_HOME" + export JAVA_HOME=/Library/Java/Home +fi + +if [ $(gem --version) \< '1.3.1' ] ; then + echo "Upgrading to latest version of RubyGems" + sudo gem update --system + echo +fi + +if [ `which buildr` ] ; then + echo "Updating to the latest version of Buildr" + sudo env JAVA_HOME=$JAVA_HOME gem update buildr +else + echo "Installing the latest version of Buildr" + sudo env JAVA_HOME=$JAVA_HOME gem install buildr +fi +echo + +buildr --version diff --git a/buildr/doc/settings_profiles.textile b/buildr/doc/settings_profiles.textile new file mode 100644 index 0000000..f247853 --- /dev/null +++ b/buildr/doc/settings_profiles.textile @@ -0,0 +1,287 @@ +--- +layout: default +title: Settings/Profiles +--- + + +h2(#env_vars). Environment Variables + +Buildr uses several environment variables that help you control how it works. Some environment variables you will only set once or change infrequently. You can set these in your profile, OS settings or any tool you use to launch Buildr (e.g. continuous integration). + +For example: + +{% highlight sh %} +$ export HTTP_PROXY=http://myproxy:8080 +{% endhighlight %} + +There are other environment variables you will want to set when running Buildr, for example, to do a full build without running the tests: + +{% highlight sh %} +$ buildr test=no +{% endhighlight %} + +For convenience, the environment variables @TEST@ and @DEBUG@ are case insensitive, you can use either @test=no@ or @TEST=no@. Any other environment variable names are case sensitive. + +You can also set environment variables from within your Buildfile. For example, if you discover that building your project requires gobs of JVM heap space, and you want all other team members to run with the same settings: + +{% highlight ruby %} +# This project builds a lot of code. +ENV['JAVA_OPTS'] ||= '-Xms1g -Xmx1g' +{% endhighlight %} + +Make sure to set any environment variables at the very top of the Buildfile, above any Ruby statement (even @require@). + +p(tip). Using @||=@ sets the environment variable, if not already set, so it's still possible for other developers to override this environment variable without modifying the Buildfile. + +Buildr supports the following environment variables: + +|_. Variable |_. Description | +| @BUILDR_ENV@ | Environment name (development, production, test, etc). Another way to set this is using the @-e@ command line option. | +| @DEBUG@ | Set to @no/off@ if you want Buildr to compile without debugging information (default when running the @release@ task, see "Compiling":building.html#compiling). | +| @HOME@ | Your home directory. | +| @HTTP_PROXY@ | URL for HTTP proxy server (see "Specifying Repositories":artifacts.html#repositories). | +| @HTTPS_PROXY@ | URL for HTTPS proxy server (see "Specifying Repositories":artifacts.html#repositories). | +| @IGNORE_BUILDFILE@ | Set to "true" or "yes" to ignore changes in Buildfile or its dependencies when running tests. | +| @JAVA_HOME@ | Points to your JDK, required when using Java and Ant. | +| @JAVA_OPTS@ | Command line options to pass to the JDK (e.g. @'-Xms1g'@). | +| @M2_REPO@ | Location of the Maven2 local repository. Defaults to the @.m2@ directory in your home directory (@ENV['HOME']@). | +| @NO_PROXY@ | Comma separated list of hosts and domain that should not be proxied (see "Specifying Repositories":artifacts.html#repositories). | +| @TEST@ | Set to @no/off@ to tell Buildr to skip tests, or @all@ to tell Buildr to run all tests and ignore failures (see "Running Tests":testing.html#running). | +| @USER@ | Tasks that need your user name, for example to log to remote servers, will use this environment variable. | + +p(note). Buildr does not check any of the arguments in @JAVA_OPTS@. A common mistake is to pass an option like @mx512mb@, where it should be @Xmx512mb@. Make sure to double check @JAVA_OPTS@. + +Some extensions may use additional environment variables, and of course, you can always add your own. This example uses two environment variables for specifying the username and password: + +{% highlight ruby %} +repositories.release_to[:username] = ENV['USERNAME'] +repositories.release_to[:password] = ENV['PASSWORD'] +{% endhighlight %} + + +h2(#personal). Personal Settings + +Some things clearly do not belong in the Buildfile. For example, the username and password you use to upload releases. If you're working in a team or on an open source project, you'd want to keep these in a separate place. + +You may want to use personal settings for picking up a different location for the local repository, or use a different set of preferred remote repositories, and so forth. + +The prefered way to store personal settings is to create a @.buildr/settings.yaml@ file under your home directory. Settings stored there will be applied the same across all builds. + +Here's an example @settings.yaml@: + +{% highlight yaml %} +# The repositories hash is read automatically by buildr. +repositories: + + # customize user local maven2 repository location + local: some/path/to/my_repo + + # prefer the local or nearest mirrors + remote: + - https://intra.net/maven2 + - http://example.com + + release_to: + url: http://intra.net/maven2 + username: john + password: secret + +# You can place settings of your own, and reference them +# on buildfiles. +im: + server: jabber.company.com + usr: notifier@company-jabber.com + pwd: secret +{% endhighlight %} + +Later your buildfile or addons can reference user preferences using the hash returned by the @Buildr.settings.user@ accessor. + +{% highlight ruby %} +task 'release-notification' do + usr, pwd, server = settings.user['im'].values_at('usr', 'pwd', 'server') + jabber = JabberAPI.new(server, usr, pwd) + jabber.msg("We are pleased to announce the last stable version #{VERSION}") +end +{% endhighlight %} + + +h2(#build). Build Settings + +Build settings are local to the project being built, and are placed in the @build.yaml@ file located in the same directory that the @buildfile@. Normally this file would be managed by the project revision control system, so settings here are shared between developers. + +They help keep the buildfile and build.yaml file simple and readable, working to the advantages of each one. Example for build settings are gems, repositories and artifacts used by that build. + +{% highlight yaml %} +# This project requires the following ruby gems, buildr addons +gems: + # Suppose we want to notify developers when testcases fail. + - buildr-twitter-notifier-addon >=1 + # we test with ruby mock objects + - mocha + - ci_reporter + +# The artifact declarations will be automatically loaded by buildr, so that +# you can reference artifacts by name (a ruby-symbol) on your buildfile. +artifacts: + spring: org.springframework:spring:jar:2.0 + log4j: log4j:log4j:jar:1.0 + j2ee: geronimo-spec:geronimo-spec-j2ee:jar:1.4-rc4 + +# Of course project settings can be defined here +twitter: + notify: + test_failure: unless-modified + compile_failure: never + developers: + - joe + - jane + +jira: + uri: https://jira.corp.org +{% endhighlight %} + +When buildr is loaded, required ruby gems will be installed if needed, thus adding features like the imaginary twitter notifier addon. + +Artifacts defined on @build.yaml@ can be referenced on your buildfile by supplying the ruby symbol to the @Buildr.artifact@ and @Buildr.artifacts@ methods. The @compile.with@, @test.with@ methods can also be given these names. + +{% highlight ruby %} +define 'my_project' do + compile.with artifacts(:log4j, :j2ee) + test.with :spring, :j2ee +end +{% endhighlight %} + +Build settings can be retreived using the @Buildr.settings.build@ accessor. + +{% highlight ruby %} + task 'create_patch' do + patch = Git.create_patch :interactive => true + if patch && agree("Would you like to request inclusion of #{patch}") + jira = Jira.new( Buildr.settings.build['jira']['uri'] ) # submit a patch + jira.create(:improvement, patch.summary, :attachment => patch.blob) + end + end +{% endhighlight %} + + +h2(#variable). Non constant settings + +Before loading the Buildfile, Buildr will attempt to load two other files: the @buildr.rb@ file in the @.buildr@ directory under your home directory, followed by the @buildr.rb@ file it finds in the build directory. + +The loading order allows you to place global settings that affect all your builds in your @buildr.rb@, but also over-ride those with settings for a given project. + +Here's an example @buildr.rb@: + +{% highlight ruby %} +# Only I should know that +repositories.release_to[:username] = 'assaf' +repositories.release_to[:password] = 'supersecret' +# Search here first, it's faster +repositories.remote << 'http://inside-the-firewall' +{% endhighlight %} + +p(note). Buildr 1.3 and earlier used the file @buildr.rb@ directly in your home directory. Starting with version 1.4, Buildr loads @buildr.rb@ from the @.buildr@ directory under your home directory in preference. If you use Buildr 1.3 and earlier and don't want to duplicate your settings, you can move you existing @buildr.rb@ under the @.buildr@ directory and create a new @buildr.rb@ in your home directory containing: + +{% highlight ruby %} +# Backward compatibility: Buildr 1.4+ uses $HOME/.buildr/buildr.rb +load File.expand_path('buildr.rb', Buildr.application.home_dir) +{% endhighlight %} + +h2(#environments). Environments + +One common use case is adapting the build to different environments. For example, to compile code with debugging information during development and testing, but strip it for production. Another example is using different databases for development, testing and production, or running services at different URLs. + +So let's start by talking about the build environment. Buildr has a global attributes that indicates which environment it's running in, accessible from the @environment@ method. You can set the current build environment in one of two ways. Using the @-e/--environment@ command line option: + +{% highlight sh %} +$ buildr -e test +(in /home/john/project, test) +{% endhighlight %} + +Or by setting the environment variable @BUILDR_ENV@: + +{% highlight text %} +$ export BUILDR_ENV=production +$ buildr +(in /home/john/project, production) +{% endhighlight %} + +Unless you tell it otherwise, Buildr assumes you're developing and sets the environment to @development@. + +Here's a simple example for handling different environments within the Buildfile: + +{% highlight ruby %} +project 'db-module' do + db = (Buildr.environment == 'production' ? 'oracle' : 'hsql') + resources.from(_("src/main/#{db}")) +end +{% endhighlight %} + +We recommend picking a convention for your different environments and following it across all your projects. For example: + +|_. Environment |_. Use when ... | +| development | Developing on your machine. | +| test | Running in test environment, continuous integration. | +| production | Building for release/production. | + + +h2(#profiles). Profiles + +Different environments may require different configurations, some you will want to control with code, others you will want to specify in the profiles file. + +The profiles file is a YAML file called @profiles.yaml@ that you place in the same directory as the Buildfile. We selected YAML because it's easier to read and edit than XML. + +For example, to support three different database configurations, we could write: + +{% highlight yaml %} +# HSQL, don't bother storing to disk. +development: + db: hsql + jdbc: hsqldb:mem:devdb + +# Make sure we're not messing with bigstrong. +test: + db: oracle + jdbc: oracle:thin:@localhost:1521:test + +# The real deal. +production: + db: oracle + jdbc: oracle:thin:@bigstrong:1521:mighty +{% endhighlight %} + +Here's a simple example for a buildfile that uses the profile information: + +{% highlight ruby %} +project 'db-module' do + # Copy SQL files specific for the database we're using, + # for example, everything under src/main/hsql. + resources.from(_("src/main/#{Buildr.settings.profile['db']}")) + # Set the JDBC URL in copied resource files (config.xml needs this). + resources.filter.using :jdbc=>Buildr.settings.profile['jdbc'] +end +{% endhighlight %} + +The @profile@ method returns the current profile, selected based on the current "environment":#environments. You can get a list of all profiles by calling @profiles@. + +When you run the above example in @development@, the current profile will return the hash @{ 'db'=>'hsql', 'jdbc'=>'hsqldb:mem:devdb' }@. + +We recommend following conventions and using the same environments in all your projects, but sometimes the profiles end up being the same, so here's a trick you can use to keep your profiles DRY. + +YAML allows you to use anchors (@&@), similar to ID attributes in XML, reference the anchored element (@*@) elsewhere, and merge one element into another (@<<@). For example: + +{% highlight yaml %} +# We'll reference this one as common. +development: &common + db: hsql + jdbc: hsqldb:mem:devdb + resources: + copyright: Me (C) 2008 +# Merge the values from common, override JDBC URL. +test: + <<: *common + jdbc: hsqldb:file:testdb +{% endhighlight %} + + +You can "learn more about YAML here":http://www.yaml.org, and use this handy "YAML quick reference":http://www.yaml.org/refcard.html. diff --git a/buildr/doc/testing.textile b/buildr/doc/testing.textile new file mode 100644 index 0000000..0188e1e --- /dev/null +++ b/buildr/doc/testing.textile @@ -0,0 +1,247 @@ +--- +layout: default +title: Testing +--- + + +Untested code is broken code, so we take testing seriously. Off the bat you get to use either JUnit or TestNG for writing unit tests and integration tests. And you can also add your own framework, or even script tests using Ruby. But first, let's start with the basics. + + +h2(#writing). Writing Tests + +Each project has a @TestTask@ that you can access using the @test@ method. @TestTask@ reflects on the fact that each project has one task responsible for getting the tests to run and acting on the results. But in fact there are several tasks that do all the work, and a @test@ task coordinates all of that. + +The first two tasks to execute are @test.compile@ and @test.resources@. They work similar to @compile@ and @resources@, but uses a different set of directories. For example, Java tests compile from the @src/test/java@ directory into the @target/test/classes@ directory, while resources are copied from @src/test/resources@ into @target/test/resources@. + +The @test.compile@ task will run the @compile@ task first, then use the same dependencies to compile the test classes. That much you already assumed. It also adds the test framework (e.g. JUnit, TestNG) and JMock to the dependency list. Less work for you. + +If you need more dependencies, the best way to add them is by calling @test.with@. This method adds dependencies to both @compile.dependencies@ (for compiling) and @test.dependencies@ (for running). You can manage these two dependency lists separately, but using @test.with@ is good enough in more cases. + +Once compiled, the @test@ task runs all the tests. + +Different languages use different test frameworks. You can find out more about available test frameworks in the "Languages":languages.html section. + + +h2(#ignoring). Excluding Tests and Ignoring Failures + +If you have a lot of tests that are failing or just hanging there collecting dusts, you can tell Buildr to ignore them. You can either tell Buildr to only run specific tests, for example: + +{% highlight ruby %} +test.include 'com.acme.tests.passing.*' +{% endhighlight %} + +Or tell it to exclude specific tests, for example: + +{% highlight ruby %} +test.exclude '*FailingTest', '*FailingWorseTest' +{% endhighlight %} + +Note that we're always using the package qualified class name, and you can use star (@*@) to substitute for any set of characters. + +When tests fail, Buildr fails the @test@ task. This is usually a good thing, but you can also tell Buildr to ignore failures by resetting the @:fail_on_failure@ option: + +{% highlight ruby %} +test.using :fail_on_failure=>false +{% endhighlight %} + +Besides giving you a free pass to ignore failures, you can use it for other causes, for example, as a gentle reminder: + +{% highlight ruby %} +test do + warn "Did you forget something?" if test.tests.nil? || test.tests.empty? +end +{% endhighlight %} + +The @tests@ collection holds the names of all classes with tests, if any. And there's @classes@, which holds the names of all test classes. We'll let you imagine creative use for these two. + + +h2(#running). Running Tests + +It's a good idea to run tests every time you change the source code, so we wired the @build@ task to run the @test@ task at the end of the build. And conveniently enough, the @build@ task is the default task, so another way to build changes in your code and run your tests: + +{% highlight sh %} +$ buildr +{% endhighlight %} + +That only works with the local @build@ task and any local task that depends on it, like @package@, @install@ and @upload@. Each project also has its own @build@ task that does not invoke the @test@ task, so @buildr build@ will run the tests cases, but @buildr foo:build@ will not. + +While it's a good idea to always run your tests, it's not always possible. There are two ways you can get @build@ to not run the @test@ task. You can set the environment variable @test@ to @no@ (but @skip@ and @off@ will also work). You can do that when running Buildr: + +{% highlight sh %} +$ buildr test=no +{% endhighlight %} + +Or set it once in your environment: + +{% highlight sh %} +$ export TEST=no +$ buildr +{% endhighlight %} + +If you're feeling really adventurous, you can also disable tests from your Buildfile or @buildr.rb@ file, by setting @options.test = false@. We didn't say it's a good idea, we're just giving you the option. + +The @test@ task is just smart enough to run all the tests it finds, but will accept include/exclude patterns. Often enough you're only working on one broken test and you only want to run that one test. Better than changing your Buildfile, you can run the @test@ task with a pattern. For example: + +{% highlight sh %} +$ buildr test:KillerAppTest +{% endhighlight %} + +Buildr will then run only tests that match the pattern @KillerAppTest@. It uses pattern matching, so @test:Foo@ will run @com.acme.FooTest@ and @com.acme.FooBarTest@. With Java, you can use this to pick a class name, or a package name to run all tests in that package, or any such combination. In fact, you can specify several patterns separated with commas. For example: + +{% highlight sh %} +$ buildr test:FooTest,BarTest +{% endhighlight %} + +Buildr forcefully runs all tests that match the pattern. If you want to re-run all tests even if your sources have not changed, you can execute: + +{% highlight sh %} +$ buildr test:* +{% endhighlight %} + +You can exclude tests by preceeding them with a minus sign ('-'): + +{% highlight sh %} +$ buildr test:-Bar +{% endhighlight %} + +The above would run all tests except those with a name containing @Bar@. Exclusions can be combined with inclusions: + +{% highlight sh %} +$ buildr test:Foo,-Bar +{% endhighlight %} + +Buildr would then run tests with names containing @Foo@ but not @Bar@. + +As you probably noticed, Buildr will stop your build at the first test that fails. We think it's a good idea, except when it's not. If you're using a continuous build system, you'll want a report of all the failed tests without stopping at the first failure. To make that happen, set the environment variable @test@ to "all", or the Buildr @options.test@ option to @:all@. For example: + +{% highlight sh %} +$ buildr package test=all +{% endhighlight %} + +We're using @package@ and not @build@ above. When using a continuous build system, you want to make sure that packages are created, contain the right files, and also run the integration tests. + +During development, if you want to re-run only tests that have failed during the last test execution, you can execute: + +{% highlight sh %} +$ buildr test:failed +{% endhighlight %} + +One last note on running tests. By default when you run tests, Buildr will automatically run all transitive test dependencies. This mean if you run "buildr test" inside project @bar@ and @bar@ depends on project @foo@, Buildr will first run tests in project @foo@ if there have been any changes affecting @foo@ that haven't been taken into account yet. This behavior often surprises people, especially when they are trying to get things done and only care about tests in @bar@ at that moment. For those times when you'd like to focus your testing on specific projects, Buildr has the @only@ option that will only run tests for projects specified on the command line, + +{% highlight sh %} +$ buildr test=only bar:test +{% endhighlight %} + +h2(#integration). Integration Tests + +So far we talked about unit tests. Unit tests are run in isolation on the specific project they test, in an isolated environment, generally with minimal setup and teardown. You get a sense of that when we told you tests run after the @build@ task, and include JMock in the dependency list. + +In contrast, integration tests are run with a number of components, in an environment that resembles production, often with more complicates setup and teardown procedures. In this section we'll talk about the differences between running unit and integration tests. + +You write integration tests much the same way as you write unit tests, using @test.compile@ and @test.resources@. However, you need to tell Buildr that your tests will execute during integration test. To do so, add the following line in your project definition: + +{% highlight ruby %} +test.using :integration +{% endhighlight %} + +Typically you'll use unit tests in projects that create internal modules, such as JARs, and integration tests in projects that create components, such as WARs and EARs. You only need to use the @:integration@ option with the later. + +To run integration tests on the current project: + +{% highlight sh %} +$ buildr integration +{% endhighlight %} + +You can also run specific tests cases, for example: + +{% highlight sh %} +$ buildr integration:ClientTest +{% endhighlight %} + +If you run the @package@ task (or any task that depends on it, like @install@ and @upload@), Buildr will first run the @build@ task and all its unit tests, and then create the packages and run the integration tests. That gives you full coverage for your tests and ready to release packages. As with unit tests, you can set the environment variable @test@ to "no" to skip integration tests, or "all" to ignore failures. + + +h2(#setup_teardown). Using Setup and Teardown + +Some tests need you to setup an environment before they run, and tear it down afterwards. The test frameworks (JUnit, TestNG) allow you to do that for each test. Buildr provides two additional mechanisms for dealing with more complicated setup and teardown procedures. + +Integration tests run a setup task before the tests, and a teardown task afterwards. You can use this task to setup a Web server for testing your Web components, or a database server for testing persistence. You can access either task by calling @integration.setup@ and @integration.teardown@. For example: + +{% highlight ruby %} +integration.setup { server.start ; server.deploy } +integration.teardown { server.stop } +{% endhighlight %} + +Depending on your build, you may want to enhance the setup/teardown tasks from within a project, for example, to populate the database with data used by that project's test, or from outside the project definition, for example, to start and stop the Web server. + +Likewise, each project has its own setup and teardown tasks that are run before and after tests for that specific project. You can access these tasks using @test.setup@ and @test.teardown@. + + +h2(#checks). Testing Your Build + +So you got the build running and all the tests pass, binaries are shipping when you find out some glaring omissions. The license file is empty, the localized messages for Japanese are missing, the CSS files are not where you expect them to be. The fact is, some errors slip by unit and integration tests. So how do we make sure the same mistake doesn't happen again? + +Each project has a @check@ task that runs just after packaging. You can use this task to verify that your build created the files you wanted it to create. And to make it extremely convenient, we introduced the notion of expectations. + +You use the @check@ method to express and expectation. Buildr will then run all these expectations against your project, and fail at the first expectation that doesn't match. An expectation says three things. Let's look at a few examples: + +{% highlight ruby %} +check package(:war), 'should exist' do + it.should exist +end +check package(:war), 'should contain a manifest' do + it.should contain('META-INF/MANIFEST.MF') +end +check package(:war).path('WEB-INF'), 'should contain files' do + it.should_not be_empty +end +check package(:war).path('WEB-INF/classes'), 'should contain classes' do + it.should contain('**/*.class') +end +check package(:war).entry('META-INF/MANIFEST'), 'should have license' do + it.should contain(/Copyright (C) 2007/) +end +check file('target/classes'), 'should contain class files' do + it.should contain('**/*.class') +end +check file('target/classes/killerapp/Code.class'), 'should exist' do + it.should exist +end +{% endhighlight %} + +The first argument is the subject, or the project if you skip the first argument. The second argument is the description, optional, but we recommend using it. The method @it@ returns the subject. + +You can also write the first expectation like this: + +{% highlight ruby %} +check do + package(:jar).should exist +end +{% endhighlight %} + +We recommend using the subject and description, they make your build easier to read and maintain, and produce better error messages. + +There are two methods you can call on just about any object, called @should@ and @should_not@. Each method takes an argument, a matcher, and executes that matcher. If the matcher returns false, @should@ fails. You can figure out what @should_not@ does in the same case. + +Buildr provides the following matchers: + +|_. Method |_. Checks that ... | +| @exist@ | Given a file task checks that the file (or directory) exists. | +| @empty@ | Given a file task checks that the file (or directory) is empty. | +| @contain@ | Given a file task referencing a file, checks its contents, using string or regular expression. For a file task referencing a directory, checks that it contains the specified files; global patterns using @*@ and @**@ are allowed. | + +All these matchers operate against a file task. If you run them against a ZipTask (including JAR, WAR, etc) or a TarTask, they can also check the contents of the archive. And as you can see in the examples above, you can also run them against a path in an archive, checking its contents as if it was a directory, or against an entry in an archive, checking the content of that file. + +p(note). The @package@ method returns a package task based on packaging type, identifier, group, version and classifier. The last four are inferred, but if you create a package with different specifications (for example, you specify a classifier) your checks must call @package@ with the same qualifying arguments to return the very same package task. + +Buildr expectations are based on RSpec. "RSpec":http://rspec.info/ is the behavior-driven development framework we use to test Buildr itself. Check the RSpec documentation if want to see all the supported matchers, or want to write your own. + + +h2(#bdd). Behaviour-Driven Development + +Buildr supports several Behaviour-Driven Development(BDD) frameworks for testing your projects. Buildr follows each framework naming conventions, searching for files under the @src/spec/{lang}@ directory. + +You can learn more about each BDD framework in the "Languages":languages.html section. + + +Next, let's talk about "customizing your environment and using profiles":settings_profiles.html diff --git a/buildr/etc/KEYS b/buildr/etc/KEYS new file mode 100644 index 0000000..bbe4d5d --- /dev/null +++ b/buildr/etc/KEYS @@ -0,0 +1,189 @@ +This file contains the PGP keys of various developers. + +Users: pgp < KEYS + gpg --import KEYS +Developers: + pgp -kxa and append it to this file. + (pgpk -ll && pgpk -xa ) >> this file. + (gpg --list-sigs + && gpg --armor --export ) >> this file. + + +pub 1024D/4A9EA70E 2007-08-27 +uid Matthieu Riou (CODE SIGNING KEY) +sig 3 4A9EA70E 2007-08-27 Matthieu Riou (CODE SIGNING KEY) +sub 2048g/2BB53026 2007-08-27 +sig 4A9EA70E 2007-08-27 Matthieu Riou (CODE SIGNING KEY) + +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.6 (GNU/Linux) + +mQGiBEbTJ5sRBADMkb8JsY99WJrjC4UhurXgVXpZgvYZos5g1emZsNh8CE13rPOF +LQEwBUsotxbbyLdvqp5o1wzWsZWMMe/4IL6Wx0CWDAXLkank/oogRvgrlckJBJ7/ +I/yS/i8ace2rO4sondzbbG+Tg/c0+AxE9HjOSOVgvB4UqeSox2OLdhgpnwCgotmg +gtdolYzr79wYXh1IeKlBRKED/RqJZQaPTtCtQO/ac+3zQ/7y7zdjoYVcDIeifzlG +1yL77Qw/LYT8y5OkO/6vvyLQwnRFTeBqlHYTmCUa+2HMh+BJTWRpDEQbAOHMgMEo +HuQwX6H5LzuJr2KmxTvQvf50FdhOXUT86pJIyGpsK/ask3SUdu81imoAEEMb9bZs +TStPA/4kHwn5WSNtTWyQz0jkdNQU2l1PVf5fh6kLIqDoAzzrtPh9NUARGkZLS4rr +6fqHJnmVu4mVzChY5QwWKTZfBjkLXn4YiOiNxjWEou34yLGXgTEiE1UQ7cqmvvQn +gRpAwYuchX54PAye5dUecjKwsbIMDRSScaEMtIutiNa+vWo2+bQzTWF0dGhpZXUg +UmlvdSAoQ09ERSBTSUdOSU5HIEtFWSkgPG1yaW91QGFwYWNoZS5vcmc+iGAEExEC +ACAFAkbTJ5sCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRD6yNe+Sp6nDmLe +AJkBhrRz3vC2++d06A19aLcF/UVZWgCfTQ2Wlg9f6qCxSkSdAeCglBgHCMO5Ag0E +RtMnvRAIAL8lBayW+D2GJ9uydAIXBXaeT5DY55GV0FcoCy0AkwaVhFYfikL1iT0Z +vcBtL0hEufywQC9W2rvqFWft2YS0NfChbeDQtkwo7kUB+AX+sreG5FzRCWEjZ3oN +THmITusuEZwXXLJAf0Rm+JmOQEZeZywYut4VSwkp7pPVlJQ8AIgxCl8HDtQriHVs +Fls3Xa/FyXfaeXQVKp9w3WLr5ONMLhUmJxPnG+mvdUJgR6yebKdYZVFpXfwOeMhM +ZdAYT/Hg7HmfwJpoJONrtlejR1VmNYY1rzb9jVplTqCCm1BDJDsJWUfPkiI7T/bx +o8l4Rq7hNESfvjGOTO7OwBYkIR0GossAAwUIALvlj5JTYFWoj+u/pa5qovgx702G +Rrw4dgMiqKdVYWJZahjmUjs9uqCI1nVROKeSHs3j+3kxH8YGHBlkJo/6yicrZpIm +mO74Abxi0+yjN9T7GpuZ9rYnYNvBSUA5hqEuMGFOeSU8ZznPsG7fBTWhuknxxOrU +mGjHGOQZXwcq0GSzZwVGkBZ1gIb0a4wwsk86uMubi5bD3I0MSVWh2ZUBZfAr5MPg +dQ1PI9tib1G6J0JhKY+95yOwujdqrmpBxdRtxN3IMIzpe2nBwlkP9CDNKhJn+sKB +MRClYk6vHp6Q+W4ikln/P62H0CrOfh3jzAYfNcW/lxlp6ZbsRVrwhr+nM3mISQQY +EQIACQUCRtMnvQIbDAAKCRD6yNe+Sp6nDppoAJ0ZNcruq5Jc0aZ1yvNLQORewQa7 +WQCgn7JcUG0i3/7Kk4101XIhgfNgXww= +=dGgn +-----END PGP PUBLIC KEY BLOCK----- + + +pub 1024D/2EED13CE 2007-05-09 +uid Alex Boisvert (CODE SIGNING KEY) +sig 3 2EED13CE 2007-05-09 Alex Boisvert (CODE SIGNING KEY) +sub 1024g/54BC5E9F 2007-05-09 +sig 2EED13CE 2007-05-09 Alex Boisvert (CODE SIGNING KEY) + + +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.6 (GNU/Linux) + +mQGiBEZCQ80RBACdngN/JZod5XLn8Was2AbSutxP/H5WssX/StS+0BLNRn+FMhtn +xXKkJwqKtqbKoHzEKKziQHybD5AfXSBj21l2iZXPfcwfsJJVpDmwBr7p6SPXdNEY +eDoSW8ZVsA81m/Oc/kxbglVSiH39O3olrQ7bwaKyBMR2QkjxynJYGImtFwCg8FKy +tPJRTUaXHGYtoGw7Jh5fNSsEAI9qQYA8IK2AwyjJE5D/rcv0nahzFRZqT+HZwmfP +FVqsgIBUt7KKuDpWw2dzf6utmKHq9JZZb7BvDlfYv7PbPIioX+35SNu7cQUNy7al +5aCgf7+evukiQBiEHZio8AzaLquCq/A29igf4fINZ4AJKTL0iRhJAKk3gey/CrOj +p61WA/oDpGIXU4adXg4x/dbUT2pbgh+KZu8oNjaMH6ZwAQtHYJ3wabH1masz+yb3 +spAUZ3IHJmFrpZeJYCUlLXZqu+/0R7hnoNH9zaE2g1JGwtiKjLEmubCp3nme0/Ca +lt8aG9XpgcWe09rA7Sbd7p9Pkdgor/p0yaF6eJl5wETdCTTjpLQ6QWxleCBCb2lz +dmVydCAoQ09ERSBTSUdOSU5HIEtFWSkgPGFsZXguYm9pc3ZlcnRAZ21haWwuY29t +PohgBBMRAgAgBQJGQkPNAhsjBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQ9sJV +Ei7tE873UgCeKKtOd1wW8B1b7vlT8J8y5a35kNMAoJ9WjLxFmKbKOkxYrt54esl6 +mGXouQENBEZCQ84QBADI6QppW8BMMaQ4us7axSinOOgTGGkOjuv48d8JnNkgUj34 +N84PME18JlTD9jlkVf+PHjYMaA3fkiKfjlojq0D9V4l7zVDd7e8O5LRL1eOKzivl +A84a3d2574V1I3ioljCyXLA+41OxYE4DtNXH+mvcumhYGBu2Bg0I5ZuXDvhv5wAE +CwQAnSLLv0M9LptAiOXl012jgP/4yuZDnBKczvAzMzR847Sy4Shuk03H2rN12+AM +rX+unJpcGOGxlzJ8Mb40aauBc270wVSCrza9z7/i/4DLPsTUuc+ZgBLWuv+lNitD +UeieigodJqbr8EFNj6MlhJyUeGxbFmkngR80nZX06sAIILyISQQYEQIACQUCRkJD +zgIbDAAKCRD2wlUSLu0TzhPzAJ93zO3DP6/fcA1yfbFSSbgLGI7+/wCdFMH1Ptl8 +sGH4v6GYUVHyfKnWKbg= +=X6Yk +-----END PGP PUBLIC KEY BLOCK----- + + +pub 1024D/14A8A2BA 2007-06-12 +uid Assaf Arkin +sig 3 14A8A2BA 2007-06-12 Assaf Arkin +sub 2048g/8CA2779E 2007-06-12 +sig 14A8A2BA 2007-06-12 Assaf Arkin + +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.7 (GNU/Linux) + +mQGiBEZvELsRBADBaOF4FVOIkkzA6XlK4lX8TbV5J3crP4M+1I5BWuKsA5oZYCkX +ARjYfSC62K2fsTSwGTU6M684M+DOKljhjFhs24bL7Yr/1iAEh1RC+aQJ0HJw8ME4 +yxU/WSez6KzQJ8fV7M6MOg9fwI3pf27USf91cdbjLMdi4mvAIbkA/ECi7wCgveA6 +nXpXF8fsKkr+Ijl5FBWYlA0EAIQRzcX12CkFOtJsGHkzZpcInKZkimBKUxrL0lK0 +/EahOPpWjzHdV64OVspgnFECmipwQBLzNAH16XEb9ikwum0PCKj4BnIgkmFjZfFA +lHLQTqN+JeSCDHI1vxwPfKh2MOrCbCU3EBQgcWhO/WakK/AXyu9bertGum1oNAc/ +LkFkA/sE1/7A2ZMyQVYxwigcYjI5FpfA3PI/Irxup38bQ4atuQrLx/vLbNrDPStw +yECB6oo3/acjGDkoEH9dergxyID4aZaGb3vJmtuzNrxJzxIkHepodTN0hXi2kLft +K0B6Vo6Ufv9eaeHnEf/1460JbDAq7w2g5JTnFkRnnWKet+ghQLQeQXNzYWYgQXJr +aW4gPGFzc2FmQGFwYWNoZS5vcmc+iGAEExECACAFAkZvELsCGwMGCwkIBwMCBBUC +CAMEFgIDAQIeAQIXgAAKCRDXkC1vFKiiuoEUAJ9rm/RkZdJSP9bGmcE3cteTa/IG +JwCeOxg1QVZ0qv6kjOM2RxfaHJeD3EC5Ag0ERm8QuxAIAIeYs8PNbNLFnaXV7Y/N +UJ07s1D/0+USJwUVJ/SX4AVyx8CYVMXo+lu9le+JeJfVI2dRlbIes2H8o4WevOP8 +k831oUfqgLF8FKjYE/2Gt6vDWRT9kcN/mxTo0NeVhAIEVSI1Nkppj1B+05IXEJvb +ZaS4GXW/tXXthVTtgHvjEA0maWMMFvUz1HlfEoepzsKTxxiGQJeRIz4hyRE1nuV+ +bUIXItUYBBaj1Y5prQkzI2WjA75hw+4ZeHYM7wgzWP/1MZwylUicvYsEtJeKXZRw +IZrKZelffcIZB3OZMUQuJyFMcRz5AVgHLuFTfG7yMLTMxWTNa2b09p8l0TnJuhbI +0g8AAwUH/iAEbtT42jvt+EsJIz37K1yV5RMKA6ApAsbRkNOAwXQgdEqky/c26KBa +Ug4nAdAUm+5Hkx/R3F2wKsxSWiWneNM93tInQH0xdfHPCICfmyUPzTS0igEktqQw +AWYpegKNXzKg+SmKJZsME50bgEZCB2zPIp/AHmG4SbI9THYvPjHbQKf0yzIO0dIw +xPxKI0dFSASg3oebANtLAUIPIV/QLNWhPwxT/Qe3aBNc5rfl7vSkVv7loBHE/1lj +++MUlrkmzegJAg9u9GSEfoxFjVbK176IdlJoflZnv1pFkGRbBvOaaCVCllLVJMZS +HlzsreJCFPVgnK3/PMUn2XauF9Y9MjCISQQYEQIACQUCRm8QuwIbDAAKCRDXkC1v +FKiiuuCjAJ9UhVa0wHAzYXPJatNdgbkHJuQOqgCbB+lDb7ZGGjC9wToAI/ha1Pz+ +T6Y= +=uoUw +-----END PGP PUBLIC KEY BLOCK----- + + +pub 1024D/E81722E2 2008-02-26 +uid Victor Hugo Borja +sig 3 E81722E2 2008-02-26 Victor Hugo Borja +sub 1024g/699A572F 2008-02-26 +sig E81722E2 2008-02-26 Victor Hugo Borja + +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2.0.7 (GNU/Linux) + +mQGiBEfEVisRBACR0KeNHllX/Vc8nSe/MSLRqjdPpbH4GR8UBZ18+CUBbx+Q98IQ +INOAOMNGcPdBoM9yzz01DBEfml6+DMlBGrUc375ZuxaML/UuD4V1kMqgHb7AaMNF +Wnyk1OMUHiYOiK3ycrYBvr955OLFX3EhUj9F6vJ9URiWczqyze3d5TJdpwCg6ar2 +dAOdxAqhK8qRPSwU3Kt1K8EEAIGB9wifaDoaJmbHn1iV7ixnePh9I9/ZR1ZkaYS2 +uiKprkG57H9b2D04hV178JGT4scCHMXkIYi0VscceDGSgcK0wbC51K8x9h+1rCev +ZwYm7jewE0luZiD0qN3yz0MHduiTbT7E/mzVRBgkM+xsiJ+miCUniIzZLqz3uKGf +bMk7A/wM1lQHJL1xNlHU38jYB+NiRTVSJhB8oxofJp21abDHfIPz/qQqswQRSTYb +G8RvfW5z2oo4AUCYCcKZ+UWR8bSVP2EJqCdu9reTd+6uXm+hv2c2nvZn/jMFWkXW +JW+aaZWUe3N3/OPXFG8ttpl19Un+FH0OGR7l+9AWoMS/I3Htr7QlVmljdG9yIEh1 +Z28gQm9yamEgPHZib3JqYUBhcGFjaGUub3JnPohgBBMRAgAgBQJHxFYrAhsjBgsJ +CAcDAgQVAggDBBYCAwECHgECF4AACgkQkUCoHegXIuJL9ACfebcPLROkKM6XBYXL +M39R32m/NgkAn1i3x/Lp8Dzmt1uSIAQPwUXao/O+uQENBEfEVisQBAD97+MoNSit +YyGYtpXmYa30SB8rirX6zad+l2Vo6uPtT8xH8zOsmCLQuiLakbuia9QsWFkguw02 +tfZsuAQ8Q4IJvWyPsoFIYXo6Ta4MSDFO2ids6LEJ6GRT2cc7zE4b77RuZkIYNSFO +XL10qAGQmbCnSWiYADnNv2jyp/NXEVz22wADBQP/T1tmohMlY2Q93cj8amBWP3ko +dYlP6WJj80iSjLOO5NGlw89aPG2K5mw2WpN729ImZYhzP13rx7uFovb4+103nrJQ +Ajt5Ierk8kbjO9yM75Y2+84McRiR8JCld2WJFNXXwKCIWuh6+UtGPqafxHccKQYv +54Cy+AeemCKSdSlc3VqISQQYEQIACQUCR8RWKwIbDAAKCRCRQKgd6Bci4pE1AKCC +QKtVPfIqp4oI8qSfsZ0gMafFRwCfTqvRMVFyLEnygWnhpxgJRYk+8Fc= +=ryOG +-----END PGP PUBLIC KEY BLOCK----- + + +pub 2048R/191DFA62 2010-03-15 +uid Antoine Toulme +sig 3 191DFA62 2010-03-15 Antoine Toulme +sub 2048R/F82CCCA4 2010-03-15 +sig 191DFA62 2010-03-15 Antoine Toulme + +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (Darwin) + +mQENBEud3FYBCACjGYLotue6+saF39/Yim+UrSnLgsM6hwPETpwOawQOKUuEd8/N +I07bCXI7HHgeBHQ0ENTWqH4hO1CoyKDsIChY37GYLZkx7KS8HVCxg/b6DNVp5vTi +oHm8XTGvdVzkvoKj4Se0T+jpTfVYoiq8CRY0jH3Wqxs8QGfVGVkVJ9hgz8UyJUyB +7PidE0P4U/xmZ3lMzxWr9d1TabNjCQmtgRgoi8auX3VP0TXJbedPXPF3xuhbFSWl +LAZhujzZwefGvJk9XX1lsA2BnMvTBC9PJqCbfaRE+3/bl3Cwq8CtyOjJ4O9i1ie8 ++exUX8RSitlP3uxMJoFR2z/qHeRTors5d1DfABEBAAG0KEFudG9pbmUgVG91bG1l +IDxhbnRvaW5lQGx1bmFyLW9jZWFuLmNvbT6JATgEEwECACIFAkud3FYCGwMGCwkI +BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEO7QCd8ZHfpiNL4H/3oe0+MVB72FR7Bg +IRoNTxnsGb1kSarTMyYQHhWMQqUiH1hEqP0R0oPDcOumw08m91jmZFPEgk53fpZJ +OL0T8UnG4XoIHilRqc55OiFkXgDFivS1RQLG6ZkLnslD70+hURiU26JJ3tQO4Jh9 +E9fAUy6KFGvwgAC9KMaw0wVgPZ9Ippd4lDE8VOPIOqZqvJ5kcYzi983I/b7eUhL9 +IMLGRRftG9VKMCxV+1mIIv+62KTaC0SlzOqaGpaC52HjJt3ogOyIjKqyoU+U+fXY +AaP3iwHqXD2ojgbz89A5cckf99YImJlLGoWEiCNyhNh5EfuUH1QyuFssitJXsZS2 +PBzhr0G5AQ0ES53cVgEIAM/nSJsPWIq4GuLjTdOA1LyN/k2a/L2fU4jGu5VVrAJu +2e4BEBfte9WubzubOL7JMnS/v4ilZe9PQnQkXWWIZUbkK2lsqws8J5rzk2OyzjJj +aosoQfMJKgyLC0qmwhWElzlAbOU5ISCw58hCuATO5DnVCB4f3ltsLPyJ6T8RPb24 +YbBWzdxKdXrsqKhTlLkWUpBIg+yD+Z7EIBJtCOjU3/F/H+OBnyFdnT+9zR0QKzeB +v3pRvQiBiMPNtOME+kjVJqTqjpCy8xh1AFjmOPjgOpQXv8YeCj/B4OcpyGpqJdL4 +TYIvwu/HFXSrCTAPlu7Jk4hMnsHRlVaTAkKBGP5KAAUAEQEAAYkBHwQYAQIACQUC +S53cVgIbDAAKCRDu0AnfGR36YlhKB/46x/wrVl89i/jBa5iSVcIOpHhypLylGXaG +J7a9ICfyVwmL0DNpdWOKlmPXEM3NEmELfJXiOZhzGKOE8y/JM/lS+z09rXngCm96 +CAzjwjNb6FC9wMZGsQGp7M/7VthyAjS/wlhS2PExCIWHglmsaPPj4NBAQxg/M8Na +n5F6Rq2K6rOE1x882i5wNsgAEb1iOFEUE1ctYSNbmO6JQfFyvz0cKpdmW52/iC41 +sIHg4pUCB3Z7wT6vvSs/eQ3jARt7mok3JE56JL0sV2+Z2SePiFUn/ixKdn1lKP8d ++THIYJ/ZdpxUZeNKy7f1djoctqAk5T7ujVJh2d6xRXwqvQcJKoeW +=6E9O +-----END PGP PUBLIC KEY BLOCK----- diff --git a/buildr/lib/buildr.rb b/buildr/lib/buildr.rb new file mode 100644 index 0000000..d32a078 --- /dev/null +++ b/buildr/lib/buildr.rb @@ -0,0 +1,107 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +unless defined?(Buildr::VERSION) + require 'buildr/version' +end + +require 'rake' +require 'rbconfig' +require 'pathname' +autoload :Tempfile, 'tempfile' +autoload :YAML, 'yaml' +autoload :REXML, 'rexml/document' +autoload :XmlSimple, 'xmlsimple' +autoload :Builder, 'builder' # A different kind of buildr, one we use to create XML. +require 'highline/import' +autoload :RSpec, 'rspec' +require 'erb' +require 'find' +require 'uri' +require 'stringio' +require 'fileutils' +require 'yaml' # From test_result.rb: necessary to require YAML even if it is mentioned by autoload as it fails on some platforms. +require 'rspec/core/formatters/base_formatter' # From test_result.rb + + +require 'buildr/core/util' +require 'buildr/core/common' +require 'buildr/core/application' +require 'buildr/core/jrebel' +require 'buildr/core/project' +require 'buildr/core/environment' +require 'buildr/core/help' +require 'buildr/core/checks' +require 'buildr/core/build' +require 'buildr/core/filter' +require 'buildr/core/compile' +require 'buildr/core/test' +require 'buildr/shell' +require 'buildr/java/commands' +require 'buildr/core/shell' +require 'buildr/run' +require 'buildr/core/run' +require 'buildr/core/transports' +require 'buildr/java/pom' +require 'buildr/core/generate' +require 'buildr/core/cc' +require 'buildr/core/doc' +require 'buildr/core/osx' if RUBY_PLATFORM =~ /darwin/ +require 'buildr/core/linux' if RUBY_PLATFORM =~ /linux/ +require 'buildr/packaging/version_requirement' +require 'buildr/packaging/artifact_namespace' +require 'buildr/packaging/artifact' +require 'buildr/packaging/package' +require 'buildr/packaging/archive' +require 'buildr/packaging/ziptask' +require 'buildr/packaging/tar' +require 'buildr/packaging/gems' +require 'buildr/packaging/zip' +require RUBY_PLATFORM == 'java' ? 'buildr/java/jruby' : 'buildr/java/rjb' +require 'buildr/java/ant' +require 'buildr/java/compiler' +require 'buildr/java/external' +require 'buildr/java/tests' +require 'buildr/java/test_result' +require 'buildr/java/bdd' +require 'buildr/java/packaging' +require 'buildr/java/commands' +require 'buildr/java/doc' +require 'buildr/java/deprecated' +require 'buildr/ide/idea' +require 'buildr/ide/eclipse' +# Order is significant for auto-detection, from most specific to least +require 'buildr/ide/eclipse/plugin' +require 'buildr/ide/eclipse/scala' +require 'buildr/ide/eclipse/java' + +# Methods defined in Buildr are both instance methods (e.g. when included in Project) +# and class methods when invoked like Buildr.artifacts(). +module Buildr ; extend self ; end + +# The Buildfile object (self) has access to all the Buildr methods and constants. +class << self ; include Buildr ; end + +# All modules defined under Buildr::* can be referenced without Buildr:: prefix +# unless a conflict exists (e.g. Buildr::RSpec vs ::RSpec) +class Object #:nodoc: + Buildr.constants.each do |name| + const = Buildr.const_get(name) + if const.is_a?(Module) + const_set name, const unless const_defined?(name) + end + end +end + diff --git a/buildr/lib/buildr/clojure.rb b/buildr/lib/buildr/clojure.rb new file mode 100644 index 0000000..29ab519 --- /dev/null +++ b/buildr/lib/buildr/clojure.rb @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +module Buildr::Clojure + + REQUIRES = ArtifactNamespace.for(self) do |ns| + ns.clojure! 'org.clojure:clojure:jar:1.2.0' + ns.jline! 'jline:jline:jar:0.9.94' + end + + class << self + def dependencies #:nodoc: + REQUIRES.artifacts + end + end + +end + +require 'buildr/clojure/shell' diff --git a/buildr/lib/buildr/clojure/shell.rb b/buildr/lib/buildr/clojure/shell.rb new file mode 100644 index 0000000..cc8abf8 --- /dev/null +++ b/buildr/lib/buildr/clojure/shell.rb @@ -0,0 +1,52 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr::Shell + + class Clojure < Base + include JRebel + + specify :name => :clj, :lang => :clojure + + # don't build if it's *only* Clojure sources + def build? + !has_source?(:clojure) or has_source?(:java) or has_source?(:scala) or has_source?(:groovy) + end + + def launch(task) + cp = project.compile.dependencies + + ::Buildr::Clojure.dependencies + + [ build? ? project.path_to(:target, :classes) : project.path_to(:src, :main, :clojure) ] + + Java::Commands.java 'jline.ConsoleRunner', 'clojure.lang.Repl', { + :properties => jrebel_props(project).merge(task.properties), + :classpath => cp, + :java_args => jrebel_args + task.java_args + } + end + + private + def clojure_home + @home ||= ENV['CLOJURE_HOME'] + end + + def has_source?(lang) + File.exists? project.path_to(:src, :main, lang) + end + end +end + +Buildr::Shell.providers << Buildr::Shell::Clojure + diff --git a/buildr/lib/buildr/core/application.rb b/buildr/lib/buildr/core/application.rb new file mode 100644 index 0000000..2391372 --- /dev/null +++ b/buildr/lib/buildr/core/application.rb @@ -0,0 +1,691 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +# Portion of this file derived from Rake. +# Copyright (c) 2003, 2004 Jim Weirich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +# Gem::user_home is nice, but ENV['HOME'] lets you override from the environment. +ENV['HOME'] ||= File.expand_path(Gem::user_home) +ENV['BUILDR_ENV'] ||= 'development' + +module Buildr + + # Provide settings that come from three sources. + # + # User settings are placed in the .buildr/settings.yaml file located in the user's home directory. + # They should only be used for settings that are specific to the user and applied the same way + # across all builds. Example for user settings are preferred repositories, path to local repository, + # user/name password for uploading to remote repository. + # + # Build settings are placed in the build.yaml file located in the build directory. They help keep + # the buildfile and build.yaml file simple and readable, working to the advantages of each one. + # Example for build settings are gems, repositories and artifacts used by that build. + # + # Profile settings are placed in the profiles.yaml file located in the build directory. They provide + # settings that differ in each environment the build runs in. For example, URLs and database + # connections will be different when used in development, test and production environments. + # The settings for the current environment are obtained by calling #profile. + class Settings + + def initialize(application) #:nodoc: + @application = application + end + + # User settings loaded from setting.yaml file in user's home directory. + def user + @user ||= load_from('settings', @application.home_dir) + end + + # Build settings loaded from build.yaml file in build directory. + def build + @build ||= load_from('build') + end + + # Profiles loaded from profiles.yaml file in build directory. + def profiles + @profiles ||= load_from('profiles') + end + + # :call-seq: + # profile => hash + # + # Returns the profile for the current environment. + def profile + profiles[@application.environment] ||= {} + end + + private + + def load_from(name, path = nil) + unless path + fail "Internal error: attempting to access local setting before buildfile located" unless @application.rakefile + path = File.dirname(@application.rakefile) + end + file_name = ['yaml', 'yml'].map { |ext| File.join(path, "#{name}.#{ext}") }.find { |fn| File.exist?(fn) } + return {} unless file_name + yaml = YAML.load(File.read(file_name)) || {} + fail "Expecting #{file_name} to be a map (name: value)!" unless Hash === yaml + @application.buildfile.enhance [file_name] + yaml + end + + end + + + class Application < Rake::Application #:nodoc: + + # Deprecated: rakefile/Rakefile, removed in 1.5 + DEFAULT_BUILDFILES = ['buildfile', 'Buildfile', 'buildfile.rb', 'Buildfile.rb'] + DEFAULT_RAKEFILES + + attr_reader :rakefiles, :requires + private :rakefiles, :requires + + def initialize + super + @rakefiles = DEFAULT_BUILDFILES.dup + @top_level_tasks = [] + @home_dir = File.expand_path('.buildr', ENV['HOME']) + mkpath @home_dir if !File.exist?(@home_dir) && File.writable?(ENV['HOME']) + @settings = Settings.new(self) + @on_completion = [] + @on_failure = [] + end + + def run + standard_exception_handling do + init 'Buildr' + load_buildfile + top_level + end + end + + # Not for external consumption. + def switch_to_namespace(names) #:nodoc: + current, @scope = @scope, names + begin + yield + ensure + @scope = current + end + end + + # Returns list of Gems associated with this buildfile, as listed in build.yaml. + # Each entry is of type Gem::Specification. + attr_reader :gems + + # Buildr home directory, .buildr under user's home directory. + attr_reader :home_dir + + # Copied from BUILD_ENV. + def environment + ENV['BUILDR_ENV'] + end + + # Returns the Settings associated with this build. + attr_reader :settings + + # :call-seq: + # buildfile + # Returns the buildfile as a task that you can use as a dependency. + def buildfile + @buildfile_task ||= BuildfileTask.define_task(File.expand_path(rakefile)) + end + + # Files that complement the buildfile itself + def build_files #:nodoc: + deprecated 'Please call buildfile.prerequisites instead' + buildfile.prerequisites + end + + # Yields to block on successful completion. Primarily used for notifications. + def on_completion(&block) + @on_completion << block + end + + # Yields to block on failure with exception. Primarily used for notifications. + def on_failure(&block) + @on_failure << block + end + + # Call on_completion hooks with the given title and message + def build_completed(title, message) + @on_completion.each do |block| + block.call(title, message) rescue nil + end + end + + # Call on_failure hooks with the given title, message and exception + def build_failed(title, message, ex = nil) + @on_failure.each do |block| + block.call(title, message, ex) rescue nil + end + end + + # :call-seq: + # deprecated(message) + # + # Use with deprecated methods and classes. This method automatically adds the file name and line number, + # and the text 'Deprecated' before the message, and eliminated duplicate warnings. It only warns when + # running in verbose mode. + # + # For example: + # deprecated 'Please use new_foo instead of foo.' + def deprecated(message) #:nodoc: + return unless verbose + "#{caller[1]}: Deprecated: #{message}".tap do |message| + @deprecated ||= {} + unless @deprecated[message] + @deprecated[message] = true + warn message + end + end + end + + protected + + def load_buildfile # replaces load_rakefile + standard_exception_handling do + find_buildfile + load_gems + load_artifact_ns + load_tasks + raw_load_buildfile + end + end + + def top_level # adds on_completion hook + standard_exception_handling do + if options.show_tasks + display_tasks_and_comments + elsif options.show_prereqs + display_prerequisites + elsif options.execute + eval options.execute + else + @start = Time.now + top_level_tasks.each { |task_name| invoke_task(task_name) } + if verbose + elapsed = Time.now - @start + real = [] + real << ('%ih' % (elapsed / 3600)) if elapsed >= 3600 + real << ('%im' % ((elapsed / 60) % 60)) if elapsed >= 60 + real << ('%.3fs' % (elapsed % 60)) + puts $terminal.color("Completed in #{real.join}", :green) + end + # On OS X this will load Cocoa and Growl which takes half a second we + # don't want to measure, so put this after the console message. + title, message = "Your build has completed", "#{Dir.pwd}\nbuildr #{@top_level_tasks.join(' ')}" + build_completed(title, message) + end + end + end + + def handle_options + options.rakelib = ['tasks'] + + OptionParser.new do |opts| + opts.banner = "buildr [-f rakefile] {options} targets..." + opts.separator "" + opts.separator "Options are ..." + + opts.on_tail("-h", "--help", "-H", "Display this help message.") do + puts opts + exit 0 + end + standard_buildr_options.each { |args| opts.on(*args) } + end.parse! + end + + def standard_buildr_options # replaces standard_rake_options + [ + ['--describe', '-D [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.", + lambda { |value| + options.show_tasks = true + options.full_description = true + options.show_task_pattern = Regexp.new(value || '') + } + ], + ['--execute', '-E CODE', + "Execute some Ruby code after loading the buildfile", + lambda { |value| options.execute = value } + ], + ['--environment', '-e ENV', + "Environment name (e.g. development, test, production).", + lambda { |value| ENV['BUILDR_ENV'] = value } + ], + ['--generate [PATH]', + "Generate buildfile from either pom.xml file or directory path.", + lambda { |value| + value ||= File.exist?('pom.xml') ? 'pom.xml' : Dir.pwd + raw_generate_buildfile value + exit 0 + } + ], + ['--libdir', '-I LIBDIR', "Include LIBDIR in the search path for required modules.", + lambda { |value| $:.push(value) } + ], + ['--prereqs', '-P [PATTERN]', "Display the tasks and dependencies (matching optional PATTERN), then exit.", + lambda { |value| + options.show_prereqs = true + options.show_task_pattern = Regexp.new(value || '') + } + ], + ['--quiet', '-q', "Do not log messages to standard output.", + lambda { |value| verbose(false) } + ], + ['--buildfile', '-f FILE', "Use FILE as the buildfile.", + lambda { |value| + @rakefiles.clear + @rakefiles << value + } + ], + ['--rakelibdir', '--rakelib', '-R PATH', + "Auto-import any .rake files in PATH. (default is 'tasks')", + lambda { |value| options.rakelib = value.split(':') } + ], + ['--require', '-r MODULE', "Require MODULE before executing rakefile.", + lambda { |value| + begin + require value + rescue LoadError => ex + begin + rake_require value + rescue LoadError => ex2 + raise ex + end + end + } + ], + ['--rules', "Trace the rules resolution.", + lambda { |value| options.trace_rules = true } + ], + ['--no-search', '--nosearch', '-N', "Do not search parent directories for the Rakefile.", + lambda { |value| options.nosearch = true } + ], + ['--silent', '-s', "Like --quiet, but also suppresses the 'in directory' announcement.", + lambda { |value| + verbose(false) + options.silent = true + } + ], + ['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.", + lambda { |value| + options.show_tasks = true + options.show_task_pattern = Regexp.new(value || '') + options.full_description = false + } + ], + ['--trace', '-t [CATEGORIES]', "Turn on invoke/execute tracing, enable full backtrace.", + lambda { |value| + options.trace = true + options.trace_categories = value ? value.split(',').map { |v| v.downcase.to_sym } : [] + options.trace_all = options.trace_categories.include? :all + verbose(true) + } + ], + ['--verbose', '-v', "Log message to standard output (default).", + lambda { |value| verbose(true) } + ], + ['--version', '-V', "Display the program version.", + lambda { |value| + puts "Buildr #{Buildr::VERSION}#{RUBY_PLATFORM[/java/] ? " (JRuby #{JRUBY_VERSION})" : ""}" + exit 0 + } + ], + ['--offline', '-o', "Do not try to download anything", + lambda { |value| + trace 'Working in offline mode; snapshot will not be updated.' + options.work_offline = true + } + ], + ['--update-snapshots', '-u', "Force updating all dependencies whose version contains SNAPSHOT", + lambda { |value| + trace 'Force update of SNAPSHOT artifacts.' + options.update_snapshots = true + } + ] + ] + end + + def find_buildfile + buildfile, location = find_rakefile_location || (tty_output? && ask_generate_buildfile) + fail "No Buildfile found (looking for: #{@rakefiles.join(', ')})" if buildfile.nil? + @rakefile = buildfile + Dir.chdir(location) + end + + def ask_generate_buildfile + source = choose do |menu| + menu.header = "To use Buildr you need a buildfile. Do you want me to create one?" + menu.choice("From Maven2 POM file") { 'pom.xml' } if File.exist?('pom.xml') + menu.choice("From directory structure") { Dir.pwd } + menu.choice("Cancel") { } + end + if source + buildfile = raw_generate_buildfile(source) + [buildfile, File.dirname(buildfile)] + end + end + + def raw_generate_buildfile(source) + # We need rakefile to be known, for settings.build to be accessible. + @rakefile = File.expand_path(DEFAULT_BUILDFILES.first) + fail "Buildfile already exists" if File.exist?(@rakefile) && !(tty_output? && agree('Buildfile exists, overwrite?')) + script = File.directory?(source) ? Generate.from_directory(source) : Generate.from_maven2_pom(source) + File.open @rakefile, 'w' do |file| + file.puts script + end + puts "Created #{@rakefile}" if verbose + @rakefile + end + + def raw_load_buildfile # replaces raw_load_rakefile + puts "(in #{Dir.pwd}, #{environment})" unless options.silent + load File.expand_path(@rakefile) if @rakefile && @rakefile != '' + load_imports + Buildr.projects + end + + # Load/install all Gems specified in build.yaml file. + def load_gems #:nodoc: + installed, missing_deps = listed_gems + unless missing_deps.empty? + fail Gem::LoadError, "Build requires the gems #{missing_deps.join(', ')}, which cannot be found in the local repository. Please install the gems before attempting to build project." + end + installed.each { |spec| spec.activate } + @gems = installed + end + + # Returns two lists. The first contains a Gem::Specification for every listed and installed + # Gem, the second contains a Gem::Dependency for every listed and uninstalled Gem. + def listed_gems #:nodoc: + found = [] + missing = [] + Array(settings.build['gems']).each do |dep| + name, versions = parse_gem_dependency(dep) + begin + found << Gem::Specification.find_by_name(name, versions) + rescue Exception + missing << Gem::Dependency.new(name, versions) + end + end + return [found, missing] + end + + def parse_gem_dependency(dep) #:nodoc: + name, trail = dep.scan(/^\s*(\S*)\s*(.*)\s*$/).first + versions = trail.scan(/[=><~!]{0,2}\s*[\d\.]+/) + versions = ['>= 0'] if versions.empty? + return name, versions + end + + # Load artifact specs from the build.yaml file, making them available + # by name ( ruby symbols ). + def load_artifact_ns #:nodoc: + hash = settings.build['artifacts'] + return unless hash + raise "Expected 'artifacts' element to be a hash" unless Hash === hash + # Currently we only use one artifact namespace to rule them all. (the root NS) + Buildr::ArtifactNamespace.load(:root => hash) + end + + # Loads buildr.rb files from home/.buildr directory and project directory. + # Loads custom tasks from .rake files in tasks directory. + def load_tasks #:nodoc: + # TODO: this might need to be split up, look for deprecated features, better method name. + old = File.expand_path('buildr.rb', ENV['HOME']) + new = File.expand_path('buildr.rb', home_dir) + if File.exist?(old) && !File.exist?(new) + warn "Deprecated: Please move buildr.rb from your home directory to the .buildr directory in your home directory" + end + + # Load home/.buildr/buildr.rb in preference + files = [ File.exist?(new) ? new : old, 'buildr.rb' ].select { |file| File.exist?(file) } + files += [ File.expand_path('buildr.rake', ENV['HOME']), File.expand_path('buildr.rake') ]. + select { |file| File.exist?(file) }.each { |file| warn "Please use '#{file.ext('rb')}' instead of '#{file}'" } + files += (options.rakelib || []).collect { |rlib| Dir["#{rlib}/*.rake"] }.flatten + + # Load .buildr/_buildr.rb same directory as buildfile + %w{.buildr.rb _buildr.rb}.each do |f| + local_buildr = File.expand_path("#{File.dirname(Buildr.application.buildfile.to_s)}/#{f}") + files << local_buildr if File.exist?( local_buildr ) + end + + files.each do |file| + unless $LOADED_FEATURES.include?(file) + load file + $LOADED_FEATURES << file + end + end + buildfile.enhance files + true + end + + def display_tasks_and_comments + displayable_tasks = tasks.select { |t| t.comment && t.name =~ options.show_task_pattern } + if options.full_description + displayable_tasks.each do |t| + puts "buildr #{t.name_with_args}" + t.full_comment.split("\n").each do |line| + puts " #{line}" + end + puts + end + else + width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10 + max_column = truncate_output? ? terminal_width - name.size - width - 7 : nil + displayable_tasks.each do |t| + printf "buildr %-#{width}s # %s\n", + t.name_with_args, max_column ? truncate(t.comment, max_column) : t.comment + end + end + end + + def display_prerequisites + displayable_tasks = tasks.select { |t| t.name =~ options.show_task_pattern } + displayable_tasks.each do |t| + puts "buildr #{t.name}" + t.prerequisites.each { |pre| puts " #{pre}" } + end + end + + def standard_exception_handling # adds on_failure hook + begin + yield + rescue SystemExit => ex + # Exit silently with current status + exit(ex.status) + rescue OptionParser::ParseError => ex + $stderr.puts $terminal.color(ex.message, :red) + exit(1) + rescue Exception => ex + ex_msg = ex.class.name == "Exception" ? ex.message : "#{ex.class.name} : #{ex.message}" + title, message = "Your build failed with an error", "#{Dir.pwd}:\n#{ex_msg}" + build_failed(title, message, ex) + # Exit with error message + $stderr.puts "Buildr aborted!" + $stderr.puts $terminal.color(ex_msg, :red) + if options.trace + $stderr.puts ex.backtrace.join("\n") + else + $stderr.puts ex.backtrace.select { |str| str =~ /#{rakefile}/ }.map { |line| $terminal.color(line, :red) }.join("\n") if rakefile + $stderr.puts "(See full trace by running task with --trace)" + end + exit(1) + end + end + + end + + + # This task stands for the buildfile and all its associated helper files (e.g., buildr.rb, build.yaml). + # By using this task as a prerequisite for other tasks, you can ensure these tasks will be needed + # whenever the buildfile changes. + class BuildfileTask < Rake::FileTask #:nodoc: + + def timestamp + ([name] + prerequisites).map { |f| File.stat(f).mtime }.max rescue Time.now + end + end + + + class << self + + # Returns the Buildr::Application object. + def application + Rake.application + end + + def application=(app) #:nodoc: + Rake.application = app + end + + # Returns the Settings associated with this build. + def settings + Buildr.application.settings + end + + # Copied from BUILD_ENV. + def environment + Buildr.application.environment + end + + end + + Buildr.application = Buildr::Application.new + +end + + +# Add a touch of color when available and running in terminal. +HighLine.use_color = false +if $stdout.isatty + begin + require 'Win32/Console/ANSI' if Config::CONFIG['host_os'] =~ /mswin|win32|dos|cygwin|mingw/i + HighLine.use_color = true + rescue LoadError + end +end + + +alias :warn_without_color :warn + +# Show warning message. +def warn(message) + warn_without_color $terminal.color(message.to_s, :blue) if verbose +end + +# Show error message. Use this when you need to show an error message and not throwing +# an exception that will stop the build. +def error(message) + puts $terminal.color(message.to_s, :red) +end + +# Show optional information. The message is printed only when running in verbose +# mode (the default). +def info(message) + puts message if verbose +end + +# Show message. The message is printed out only when running in trace mode. +def trace(message) + puts message if Buildr.application.options.trace +end + +def trace?(*category) + options = Buildr.application.options + return options.trace if category.empty? + return true if options.trace_all + return false unless options.trace_categories + options.trace_categories.include?(category.first) +end + +module Rake #:nodoc + # Rake's circular dependency checks (InvocationChain) only applies to task prerequisites, + # all other cases result in the non too-descriptive thread sleeping error. This change can + # deal with circular dependencies that occur from direct task invocation, e.g: + # task 'foo'=>'bar' + # task 'bar' do + # task('foo').invoke + # end + class Task #:nodoc: + def invoke(*args) + task_args = TaskArguments.new(arg_names, args) + invoke_with_call_chain(task_args, Thread.current[:rake_chain] || InvocationChain::EMPTY) + end + + def invoke_with_call_chain(task_args, invocation_chain) + new_chain = InvocationChain.append(self, invocation_chain) + @lock.synchronize do + if application.options.trace + puts "** Invoke #{name} #{format_trace_flags}" + end + return if @already_invoked + @already_invoked = true + begin + invoke_prerequisites(task_args, new_chain) + rescue + trace "Exception while invoking prerequisites of task #{self.inspect}" + raise + end + begin + old_chain, Thread.current[:rake_chain] = Thread.current[:rake_chain], new_chain + execute(task_args) if needed? + ensure + Thread.current[:rake_chain] = old_chain + end + end + end + end +end + + +module RakeFileUtils #:nodoc: + FileUtils::OPT_TABLE.each do |name, opts| + default_options = [] + if opts.include?(:verbose) || opts.include?("verbose") + default_options << ':verbose => RakeFileUtils.verbose_flag == true' + end + next if default_options.empty? + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}( *args, &block ) + super( + *rake_merge_option(args, + #{default_options.join(', ')} + ), &block) + end + EOS + end +end diff --git a/buildr/lib/buildr/core/build.rb b/buildr/lib/buildr/core/build.rb new file mode 100644 index 0000000..859309e --- /dev/null +++ b/buildr/lib/buildr/core/build.rb @@ -0,0 +1,509 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + class Options + + # Runs the build in parallel when true (defaults to false). You can force a parallel build by + # setting this option directly, or by running the parallel task ahead of the build task. + # + # This option only affects recursive tasks. For example: + # buildr parallel package + # will run all package tasks (from the sub-projects) in parallel, but each sub-project's package + # task runs its child tasks (prepare, compile, resources, etc) in sequence. + attr_accessor :parallel + + end + + task('parallel') { Buildr.options.parallel = true } + + + module Build + + include Extension + + first_time do + desc 'Build the project' + Project.local_task('build') { |name| "Building #{name}" } + desc 'Clean files generated during a build' + Project.local_task('clean') { |name| "Cleaning #{name}" } + + desc 'The default task is build' + task 'default'=>'build' + end + + before_define(:build => [:compile, :test]) do |project| + project.recursive_task 'build' + project.recursive_task 'clean' + project.clean do + rm_rf project.path_to(:target) + rm_rf project.path_to(:reports) + end + end + + after_define(:build) + + # *Deprecated:* Use +path_to(:target)+ instead. + def target + Buildr.application.deprecated 'Use path_to(:target) instead' + layout.expand(:target) + end + + # *Deprecated:* Use Layout instead. + def target=(dir) + Buildr.application.deprecated 'Use Layout instead' + layout[:target] = _(dir) + end + + # *Deprecated:* Use +path_to(:reports)+ instead. + def reports() + Buildr.application.deprecated 'Use path_to(:reports) instead' + layout.expand(:reports) + end + + # *Deprecated:* Use Layout instead. + def reports=(dir) + Buildr.application.deprecated 'Use Layout instead' + layout[:reports] = _(dir) + end + + # :call-seq: + # build(*prereqs) => task + # build { |task| .. } => task + # + # Returns the project's build task. With arguments or block, also enhances that task. + def build(*prereqs, &block) + task('build').enhance prereqs, &block + end + + # :call-seq: + # clean(*prereqs) => task + # clean { |task| .. } => task + # + # Returns the project's clean task. With arguments or block, also enhances that task. + def clean(*prereqs, &block) + task('clean').enhance prereqs, &block + end + + end + + + module Git #:nodoc: + module_function + + # :call-seq: + # git(*args) + # + # Executes a Git command and returns the output. Throws exception if the exit status + # is not zero. For example: + # git 'commit' + # git 'remote', 'show', 'origin' + def git(*args) + cmd = "git #{args.shift} #{args.map { |arg| arg.inspect }.join(' ')}" + output = `#{cmd}` + fail "GIT command \"#{cmd}\" failed with status #{$?.exitstatus}\n#{output}" unless $?.exitstatus == 0 + return output + end + + # Returns list of uncommited/untracked files as reported by git status. + def uncommitted_files + `git status`.scan(/^#(\t|\s{7})(\S.*)$/).map { |match| match.last.split.last } + end + + # Commit the given file with a message. + # The file has to be known to Git meaning that it has either to have been already committed in the past + # or freshly added to the index. Otherwise it will fail. + def commit(file, message) + git 'commit', '-m', message, file + end + + # Update the remote refs using local refs + # + # By default, the "remote" destination of the push is the the remote repo linked to the current branch. + # The default remote branch is the current local branch. + def push(remote_repo = remote, remote_branch = current_branch) + git 'push', remote, current_branch + end + + # Return the name of the remote repository whose branch the current local branch tracks, + # or nil if none. + def remote(branch = current_branch) + remote = git('config', '--get', "branch.#{branch}.remote").to_s.strip + remote if !remote.empty? && git('remote').include?(remote) + end + + # Return the name of the current branch + def current_branch + git('branch')[/^\* (.*)$/, 1] + end + end + + + module Svn #:nodoc: + module_function + + # :call-seq: + # svn(*args) + # + # Executes a SVN command and returns the output. Throws exception if the exit status + # is not zero. For example: + # svn 'commit' + def svn(*args) + output = `svn #{args.shift} #{args.map { |arg| arg.inspect }.join(' ')}` + fail "SVN command failed with status #{$?.exitstatus}" unless $?.exitstatus == 0 + return output + end + + def tag(tag_name) + url = tag_url repo_url, tag_name + remove url, 'Removing old copy' rescue nil + copy Dir.pwd, url, "Release #{tag_name}" + end + + # Status check reveals modified files, but also SVN externals which we can safely ignore. + def uncommitted_files + svn('status', '--ignore-externals').split("\n").reject { |line| line =~ /^X\s/ } + end + + def commit(file, message) + svn 'commit', '-m', message, file + end + + # :call-seq: + # tag_url(svn_url, version) => tag_url + # + # Returns the SVN url for the tag. + # Can tag from the trunk or from branches. + # Can handle the two standard repository layouts. + # - http://my.repo/foo/trunk => http://my.repo/foo/tags/1.0.0 + # - http://my.repo/trunk/foo => http://my.repo/tags/foo/1.0.0 + def tag_url(svn_url, tag) + trunk_or_branches = Regexp.union(%r{^(.*)/trunk(.*)$}, %r{^(.*)/branches(.*)/([^/]*)$}) + match = trunk_or_branches.match(svn_url) + prefix = match[1] || match[3] + suffix = match[2] || match[4] + prefix + '/tags' + suffix + '/' + tag + end + + # Return the current SVN URL + def repo_url + svn('info', '--xml')[/(.*?)<\/url>/, 1].strip + end + + def copy(dir, url, message) + svn 'copy', '--parents', dir, url, '-m', message + end + + def remove(url, message) + svn 'remove', url, '-m', message + end + + end + + + class Release #:nodoc: + + THIS_VERSION_PATTERN = /(THIS_VERSION|VERSION_NUMBER)\s*=\s*(["'])(.*)\2/ + + class << self + + # Use this to specify a different tag name for tagging the release in source control. + # You can set the tag name or a proc that will be called with the version number, + # for example: + # Release.tag_name = lambda { |ver| "foo-#{ver}" } + attr_accessor :tag_name + + # Use this to specify a different commit message to commit the buildfile with the next version in source control. + # You can set the commit message or a proc that will be called with the next version number, + # for example: + # Release.commit_message = lambda { |ver| "Changed version number to #{ver}" } + attr_accessor :commit_message + + # Use this to specify the next version number to replace VERSION_NUMBER with in the buildfile. + # You can set the next version or a proc that will be called with the current version number. + # For example, with the following buildfile: + # THIS_VERSION = "1.0.0-rc1" + # Release.next_version = lambda { |version| + # version[-1] = version[-1].to_i + 1 + # version + # } + # + # Release.next_version will return "1.0.0-rc2", so at the end of the release, the buildfile will contain VERSION_NUMBER = "1.0.0-rc2" + # + attr_accessor :next_version + + # :call-seq: + # add(MyReleaseClass) + # + # Add a Release implementation to the list of available Release classes. + def add(release) + @list ||= [] + @list |= [release] + end + alias :<< :add + + # The list of supported Release implementations + def list + @list ||= [] + end + + # Finds and returns the Release instance for this project. + def find + unless @release + klass = list.detect { |impl| impl.applies_to? } + @release = klass.new if klass + end + @release + end + + end + + # :call-seq: + # make() + # + # Make a release. + def make + @this_version = extract_version + check + with_release_candidate_version do |release_candidate_buildfile| + args = '-S', 'buildr', "_#{Buildr::VERSION}_", '--buildfile', release_candidate_buildfile + args << '--environment' << Buildr.environment unless Buildr.environment.to_s.empty? + args << 'clean' << 'upload' << 'DEBUG=no' + ruby *args + end + tag_release resolve_tag + update_version_to_next if this_version != resolve_next_version(this_version) + end + + def check + if this_version == resolve_next_version(this_version) && this_version.match(/-SNAPSHOT$/) + fail "The next version can't be equal to the current version #{this_version}.\nUpdate THIS_VERSION/VERSION_NUMBER, specify Release.next_version or use NEXT_VERSION env var" + end + end + + # :call-seq: + # extract_version() => this_version + # + # Extract the current version number from the buildfile. + # Raise an error if not found. + def extract_version + buildfile = File.read(Buildr.application.buildfile.to_s) + buildfile.scan(THIS_VERSION_PATTERN)[0][2] + rescue + fail 'Looking for THIS_VERSION = "..." in your Buildfile, none found' + end + + # Use this to specify a different tag name for tagging the release in source control. + # You can set the tag name or a proc that will be called with the version number, + # for example: + # Release.find.tag_name = lambda { |ver| "foo-#{ver}" } + # Deprecated: you should use Release.tag_name instead + def tag_name=(tag_proc) + Buildr.application.deprecated "Release.find.tag_name is deprecated. You should use Release.tag_name instead" + Release.tag_name=(tag_proc) + end + + protected + + # the initial value of THIS_VERSION + attr_accessor :this_version + + # :call-seq: + # with_release_candidate_version() { |filename| ... } + # + # Yields to block with release candidate buildfile, before committing to use it. + # + # We need a Buildfile with upgraded version numbers to run the build, but we don't want the + # Buildfile modified unless the build succeeds. So this method updates the version number in + # a separate (Buildfile.next) file, yields to the block with that filename, and if successful + # copies the new file over the existing one. + # + # The release version is the current version without '-SNAPSHOT'. So: + # THIS_VERSION = 1.1.0-SNAPSHOT + # becomes: + # THIS_VERSION = 1.1.0 + # for the release buildfile. + def with_release_candidate_version + release_candidate_buildfile = Buildr.application.buildfile.to_s + '.next' + + release_candidate_buildfile_contents = change_version { |version| + version.gsub(/-SNAPSHOT$/, "") + } + File.open(release_candidate_buildfile, 'w') { |file| file.write release_candidate_buildfile_contents } + begin + yield release_candidate_buildfile + mv release_candidate_buildfile, Buildr.application.buildfile.to_s + ensure + rm release_candidate_buildfile rescue nil + end + end + + # :call-seq: + # change_version() { |this_version| ... } => buildfile + # + # Change version number in the current Buildfile, but without writing a new file (yet). + # Returns the contents of the Buildfile with the modified version number. + # + # This method yields to the block with the current (this) version number and expects + # the block to return the updated version. + def change_version + current_version = extract_version + new_version = yield(current_version) + buildfile = File.read(Buildr.application.buildfile.to_s) + buildfile.gsub(THIS_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{new_version}"}) } + end + + # Return the name of the tag to tag the release with. + def resolve_tag + version = extract_version + tag = Release.tag_name || version + tag = tag.call(version) if Proc === tag + tag + end + + # Return the new value of THIS_VERSION based on the version passed. + # + # This method receives the existing value of THIS_VERSION + def resolve_next_version(current_version) + next_version = Release.next_version + next_version ||= lambda { |v| + snapshot = v.match(/-SNAPSHOT$/) + version = v.gsub(/-SNAPSHOT$/, "").split(/\./) + if snapshot + version[-1] = sprintf("%0#{version[-1].size}d", version[-1].to_i + 1) + '-SNAPSHOT' + end + version.join('.') + } + next_version = ENV['NEXT_VERSION'] if ENV['NEXT_VERSION'] + next_version = ENV['next_version'] if ENV['next_version'] + next_version = next_version.call(current_version) if Proc === next_version + next_version + end + + # Move the version to next and save the updated buildfile + def update_buildfile + buildfile = change_version { |version| # THIS_VERSION minus SNAPSHOT + resolve_next_version(this_version) # THIS_VERSION + } + File.open(Buildr.application.buildfile.to_s, 'w') { |file| file.write buildfile } + end + + # Return the message to use to commit the buildfile with the next version + def message + version = extract_version + msg = Release.commit_message || "Changed version number to #{version}" + msg = msg.call(version) if Proc === msg + msg + end + + def update_version_to_next + update_buildfile + end + end + + + class GitRelease < Release + class << self + def applies_to? + if File.exist? '.git/config' + true + else + curr_pwd = Dir.pwd + Dir.chdir('..') do + return false if curr_pwd == Dir.pwd # Means going up one level is not possible. + applies_to? + end + end + end + end + + # Fails if one of theses 2 conditions are not met: + # 1. the repository is clean: no content staged or unstaged + # 2. some remote repositories are defined but the current branch does not track any + def check + super + uncommitted = Git.uncommitted_files + fail "Uncommitted files violate the First Principle Of Release!\n#{uncommitted.join("\n")}" unless uncommitted.empty? + fail "You are releasing from a local branch that does not track a remote!" unless Git.remote + end + + # Add a tag reference in .git/refs/tags and push it to the remote if any. + # If a tag with the same name already exists it will get deleted (in both local and remote repositories). + def tag_release(tag) + unless this_version == extract_version + info "Committing buildfile with version number #{extract_version}" + Git.commit File.basename(Buildr.application.buildfile.to_s), message + Git.push if Git.remote + end + info "Tagging release #{tag}" + Git.git 'tag', '-d', tag rescue nil + Git.git 'push', Git.remote, ":refs/tags/#{tag}" rescue nil if Git.remote + Git.git 'tag', '-a', tag, '-m', "[buildr] Cutting release #{tag}" + Git.git 'push', Git.remote, 'tag', tag if Git.remote + end + + def update_version_to_next + super + info "Current version is now #{extract_version}" + Git.commit File.basename(Buildr.application.buildfile.to_s), message + Git.push if Git.remote + end + end + + + class SvnRelease < Release + class << self + def applies_to? + File.exist?('.svn') + end + end + + def check + super + fail "Uncommitted files violate the First Principle Of Release!\n"+Svn.uncommitted_files.join("\n") unless Svn.uncommitted_files.empty? + fail "SVN URL must contain 'trunk' or 'branches/...'" unless Svn.repo_url =~ /(trunk)|(branches.*)$/ + end + + def tag_release(tag) + # Unlike Git, committing the buildfile with the released version is not necessary. + # svn tag does commit & tag. + info "Tagging release #{tag}" + Svn.tag tag + end + + def update_version_to_next + super + info "Current version is now #{extract_version}" + Svn.commit Buildr.application.buildfile.to_s, message + end + end + + Release.add SvnRelease + Release.add GitRelease + + desc 'Make a release' + task 'release' do |task| + release = Release.find + fail 'Unable to detect the Version Control System.' unless release + release.make + end + +end + + +class Buildr::Project + include Buildr::Build +end diff --git a/buildr/lib/buildr/core/cc.rb b/buildr/lib/buildr/core/cc.rb new file mode 100644 index 0000000..8638072 --- /dev/null +++ b/buildr/lib/buildr/core/cc.rb @@ -0,0 +1,161 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + class CCTask < Rake::Task + attr_accessor :delay + attr_reader :project + + def initialize(*args) + super + @delay = 0.2 + enhance do + monitor_and_compile + end + end + + private + + # run block on sub-projects depth-first, then on this project + def each_project(&block) + depth_first = lambda do |p| + p.projects.each { |c| depth_first.call(c, &block) } + block.call(p) + end + depth_first.call(@project) + end + + def associate_with(project) + @project = project + end + + def monitor_and_compile + # we don't want to actually fail if our dependencies don't succeed + begin + each_project { |p| p.test.compile.invoke } + build_completed(project) + rescue Exception => ex + $stderr.puts $terminal.color(ex.message, :red) + $stderr.puts + + build_failed(project, ex) + end + + dirs = [] + each_project do |p| + dirs += p.compile.sources.map(&:to_s) + dirs += p.test.compile.sources.map(&:to_s) + dirs += p.resources.sources.map(&:to_s) + end + if dirs.length == 1 + info "Monitoring directory: #{dirs.first}" + else + info "Monitoring directories: [#{dirs.join ', '}]" + end + + timestamps = lambda do + times = {} + dirs.each { |d| Dir.glob("#{d}/**/*").map { |f| times[f] = File.mtime f } } + times + end + + old_times = timestamps.call() + + while true + sleep delay + + new_times = timestamps.call() + changed = changed(new_times, old_times) + old_times = new_times + + unless changed.empty? + info '' # better spacing + + changed.each do |file| + info "Detected changes in #{file}" + end + + each_project do |p| + # transitively reenable prerequisites + reenable = lambda do |t| + t = task(t) + t.reenable + t.prerequisites.each { |c| reenable.call(c) } + end + reenable.call(p.test.compile) + end + + successful = true + begin + each_project { |p| p.test.compile.invoke } + build_completed(project) + rescue Exception => ex + $stderr.puts $terminal.color(ex.message, :red) + build_failed(project, ex) + successful = false + end + + puts $terminal.color("Build complete", :green) if successful + end + end + end + + def build_completed(project) + Buildr.application.build_completed('Compilation successful', project.path_to) + end + + def build_failed(project, ex = nil) + Buildr.application.build_failed('Compilation failed', project.path_to, ex) + end + + def changed(new_times, old_times) + changed = [] + new_times.each do |(fname,newtime)| + if old_times[fname].nil? || old_times[fname] < newtime + changed << fname + end + end + + # detect deletion (slower than it could be) + old_times.each_key do |fname| + changed << fname unless new_times.has_key? fname + end + + changed + end + end + + module CC + include Extension + + first_time do + desc 'Execute continuous compilation, listening to changes' + Project.local_task('cc') { |name| "Executing continuous compilation for #{name}" } + end + + before_define do |project| + cc = CCTask.define_task :cc + cc.send :associate_with, project + end + + def cc + task :cc + end + end + + class Project + include CC + end +end diff --git a/buildr/lib/buildr/core/checks.rb b/buildr/lib/buildr/core/checks.rb new file mode 100644 index 0000000..fc3fd32 --- /dev/null +++ b/buildr/lib/buildr/core/checks.rb @@ -0,0 +1,249 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + # Methods added to Project to allow checking the build. + module Checks + + module Matchers #:nodoc: + + class << self + + # Define matchers that operate by calling a method on the tested object. + # For example: + # foo.should contain(bar) + # calls: + # foo.contain(bar) + def match_using(*names) + names.each do |name| + matcher = Class.new do + # Initialize with expected arguments (i.e. contain(bar) initializes with bar). + define_method(:initialize) { |*args| @expects = args } + # Matches against actual value (i.e. foo.should exist called with foo). + define_method(:matches?) do |actual| + @actual = actual + return actual.send("#{name}?", *@expects) if actual.respond_to?("#{name}?") + return actual.send(name, *@expects) if actual.respond_to?(name) + raise "You can't check #{actual}, it doesn't respond to #{name}." + end + # Some matchers have arguments, others don't, treat appropriately. + define_method :failure_message do + args = " " + @expects.map{ |arg| "'#{arg}'" }.join(", ") unless @expects.empty? + "Expected #{@actual} to #{name}#{args}" + end + define_method :negative_failure_message do + args = " " + @expects.map{ |arg| "'#{arg}'" }.join(", ") unless @expects.empty? + "Expected #{@actual} to not #{name}#{args}" + end + end + # Define method to create matcher. + define_method(name) { |*args| matcher.new(*args) } + end + end + + end + + # Define delegate matchers for exist and contain methods. + match_using :exist, :contain + + end + + + # An expectation has subject, description and block. The expectation is validated by running the block, + # and can access the subject from the method #it. The description is used for reporting. + # + # The expectation is run by calling #run_against. You can share expectations by running them against + # different projects (or any other context for that matter). + # + # If the subject is missing, it is set to the argument of #run_against, typically the project itself. + # If the description is missing, it is set from the project. If the block is missing, the default behavior + # prints "Pending" followed by the description. You can use this to write place holders and fill them later. + class Expectation + + attr_reader :description, :subject, :block + + # :call-seq: + # initialize(subject, description?) { .... } + # initialize(description?) { .... } + # + # First argument is subject (returned from it method), second argument is description. If you omit the + # description, it will be set from the subject. If you omit the subject, it will be set from the object + # passed to run_against. + def initialize(*args, &block) + @description = args.pop if String === args.last + @subject = args.shift + raise ArgumentError, "Expecting subject followed by description, and either one is optional. Not quite sure what to do with this list of arguments." unless args.empty? + @block = block || lambda { |klass| info "Pending: #{description}" } + end + + # :call-seq: + # run_against(context) + # + # Runs this expectation against the context object. The context object is different from the subject, + # but used as the subject if no subject specified (i.e. returned from the it method). + # + # This method creates a new context object modeled after the context argument, but a separate object + # used strictly for running this expectation, and used only once. The context object will pass methods + # to the context argument, so you can call any method, e.g. package(:jar). + # + # It also adds all matchers defined in Buildr and RSpec, and two additional methods: + # * it() -- Returns the subject. + # * description() -- Returns the description. + def run_against(context) + subject = @subject || context + description = @description ? "#{subject} #{@description}" : subject.to_s + # Define anonymous class and load it with: + # - All instance methods defined in context, so we can pass method calls to the context. + # - it() method to return subject, description() method to return description. + # - All matchers defined by Buildr and RSpec. + klass = Class.new + klass.instance_eval do + context.class.instance_methods.each do |method| + define_method(method) { |*args| context.send(method, *args) } unless instance_methods.include?(method) + end + define_method(:it) { subject } + define_method(:description) { description } + include ::RSpec::Matchers + include Matchers + end + + # Run the expectation. We only print the expectation name when tracing (to know they all ran), + # or when we get a failure. + begin + trace description + klass.new.instance_eval &@block + rescue Exception=>error + raise error.exception("#{description}\n#{error}").tap { |wrapped| wrapped.set_backtrace(error.backtrace) } + end + end + + end + + + include Extension + + before_define(:check => :package) do |project| + # The check task can do any sort of interesting things, but the most important is running expectations. + project.task("check") do |task| + project.expectations.inject(true) do |passed, expect| + begin + expect.run_against project + passed + rescue Exception=>ex + if verbose + error ex + error ex.backtrace.select { |line| line =~ /#{Buildr.application.buildfile}/ }.join("\n") + end + false + end + end or fail "Checks failed for project #{project.name} (see errors above)." + end + project.task("package").enhance do |task| + # Run all actions before checks. + task.enhance { project.task("check").invoke } + end + end + + + # :call-seq: + # check(description) { ... } + # check(subject, description) { ... } + # + # Adds an expectation. The expectation is run against the project by the check task, executed after packaging. + # You can access any package created by the project. + # + # An expectation is written using a subject, description and block to validate the expectation. For example: + # + # For example: + # check package(:jar), "should exist" do + # it.should exist + # end + # check package(:jar), "should contain a manifest" do + # it.should contain("META-INF/MANIFEST.MF") + # end + # check package(:jar).path("com/acme"), "should contain classes" do + # it.should_not be_empty + # end + # check package(:jar).entry("META-INF/MANIFEST"), "should be a recent license" do + # it.should contain(/Copyright (C) 2007/) + # end + # + # If you omit the subject, the project is used as the subject. If you omit the description, the subject is + # used as description. + # + # During development you can write placeholder expectations by omitting the block. This will simply report + # the expectation as pending. + def check(*args, &block) + expectations << Checks::Expectation.new(*args, &block) + end + + # :call-seq: + # expectations() => Expectation* + # + # Returns a list of expectations (see #check). + def expectations() + @expectations ||= [] + end + + end + +end + + +module Rake #:nodoc: + class FileTask + + # :call-seq: + # exist?() => boolean + # + # Returns true if this file exists. + def exist?() + File.exist?(name) + end + + # :call-seq: + # empty?() => boolean + # + # Returns true if file/directory is empty. + def empty?() + File.directory?(name) ? Dir.glob("#{name}/*").empty? : File.read(name).empty? + end + + # :call-seq: + # contain?(pattern*) => boolean + # contain?(file*) => boolean + # + # For a file, returns true if the file content matches against all the arguments. An argument may be + # a string or regular expression. + # + # For a directory, return true if the directory contains the specified files. You can use relative + # file names and glob patterns (using *, **, etc). + def contain?(*patterns) + if File.directory?(name) + patterns.map { |pattern| "#{name}/#{pattern}" }.all? { |pattern| !Dir[pattern].empty? } + else + contents = File.read(name) + patterns.map { |pattern| Regexp === pattern ? pattern : Regexp.new(Regexp.escape(pattern.to_s)) }. + all? { |pattern| contents =~ pattern } + end + end + + end +end + + +class Buildr::Project + include Buildr::Checks +end diff --git a/buildr/lib/buildr/core/common.rb b/buildr/lib/buildr/core/common.rb new file mode 100644 index 0000000..8941741 --- /dev/null +++ b/buildr/lib/buildr/core/common.rb @@ -0,0 +1,146 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # :call-seq: + # struct(hash) => Struct + # + # Convenience method for creating an anonymous Struct. + # + # For example: + # COMMONS = struct( + # :collections =>'commons-collections:commons-collections:jar:3.1', + # :lang =>'commons-lang:commons-lang:jar:2.1', + # :logging =>'commons-logging:commons-logging:jar:1.0.3', + # ) + # + # compile.with COMMONS.logging + def struct(hash) + Struct.new(nil, *hash.keys).new(*hash.values) + end + + # :call-seq: + # write(name, content) + # write(name) { ... } + # + # Write the contents into a file. The second form calls the block and writes the result. + # + # For example: + # write 'TIMESTAMP', Time.now + # write('TIMESTAMP') { Time.now } + # + # Yields to the block before writing the file, so you can chain read and write together. + # For example: + # write('README') { read('README').sub("${build}", Time.now) } + def write(name, content = nil) + mkpath File.dirname(name) + content = yield if block_given? + File.open(name.to_s, 'wb') { |file| file.write content.to_s } + content.to_s + end + + # :call-seq: + # read(args) => string + # read(args) { |string| ... } => result + # + # Reads and returns the contents of a file. The second form yields to the block and returns + # the result of the block. The args passed to read are passed on to File.open. + # + # For example: + # puts read('README') + # read('README') { |text| puts text } + def read(*args) + args[0] = args[0].to_s + contents = File.open(*args) { |f| f.read } + if block_given? + yield contents + else + contents + end + end + + # :call-seq: + # download(url_or_uri) => task + # download(path=>url_or_uri) =>task + # + # Create a task that will download a file from a URL. + # + # Takes a single argument, a hash with one pair. The key is the file being + # created, the value if the URL to download. The task executes only if the + # file does not exist; the URL is not checked for updates. + # + # The task will show download progress on the console; if there are MD5/SHA1 + # checksums on the server it will verify the download before saving it. + # + # For example: + # download 'image.jpg'=>'http://example.com/theme/image.jpg' + def download(args) + args = URI.parse(args) if String === args + if URI === args + # Given only a download URL, download into a temporary file. + # You can infer the file from task name. + temp = Tempfile.open(File.basename(args.to_s)) + file(temp.path).tap do |task| + # Since temporary file exists, force a download. + class << task ; def needed? ; true ; end ; end + task.sources << args + task.enhance { args.download temp } + end + else + # Download to a file created by the task. + fail unless args.keys.size == 1 + uri = URI.parse(args.values.first.to_s) + file(args.keys.first.to_s).tap do |task| + task.sources << uri + task.enhance { uri.download task.name } + end + end + + end + + # A file task that concatenates all its prerequisites to create a new file. + # + # For example: + # concat("master.sql"=>["users.sql", "orders.sql", reports.sql"] + # + # See also Buildr#concat. + class ConcatTask < Rake::FileTask + def initialize(*args) #:nodoc: + super + enhance do |task| + content = prerequisites.inject("") do |content, prereq| + content << File.read(prereq.to_s) if File.exists?(prereq) && !File.directory?(prereq) + content + end + File.open(task.name, "wb") { |file| file.write content } + end + end + end + + # :call-seq: + # concat(target=>files) => task + # + # Creates and returns a file task that concatenates all its prerequisites to create + # a new file. See #ConcatTask. + # + # For example: + # concat("master.sql"=>["users.sql", "orders.sql", reports.sql"] + def concat(args) + file, arg_names, deps = Buildr.application.resolve_args([args]) + ConcatTask.define_task(File.expand_path(file)=>deps) + end + +end diff --git a/buildr/lib/buildr/core/compile.rb b/buildr/lib/buildr/core/compile.rb new file mode 100644 index 0000000..6464a12 --- /dev/null +++ b/buildr/lib/buildr/core/compile.rb @@ -0,0 +1,617 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # The underlying compiler used by CompileTask. + # To add a new compiler, extend Compiler::Base and add your compiler using: + # Buildr::Compiler.add MyCompiler + module Compiler + + class << self + + # Returns true if the specified compiler exists. + def has?(name) + compilers.any? { |compiler| compiler.to_sym == name.to_sym } + end + + # Select a compiler by its name. + def select(name) + compilers.detect { |compiler| compiler.to_sym == name.to_sym } + end + + # Adds a compiler to the list of supported compiler. + # + # For example: + # Buildr::Compiler << Buildr::Javac + def add(compiler) + @compilers ||= [] + @compilers |= [compiler] + end + alias :<< :add + + # Returns a list of available compilers. + def compilers + @compilers ||= [] + end + + private + + # Only used by our specs. + def compilers=(compilers) + @compilers = compilers + end + end + + # Base class for all compilers, with common functionality. Extend and over-ride as you see fit + # (see Javac as an example). + class Base #:nodoc: + + class << self + + # The compiler's identifier (e.g. :javac). Inferred from the class name. + def to_sym + @symbol ||= name.split('::').last.downcase.to_sym + end + + # The compiled language (e.g. :java). + attr_reader :language + # Source directories to use if none were specified (e.g. 'java'). Defaults to #language. + attr_reader :sources + # Extension for source files (e.g. 'java'). Defaults to language. + attr_reader :source_ext + # The target path (e.g. 'classes') + attr_reader :target + # Extension for target files (e.g. 'class'). + attr_reader :target_ext + # The default packaging type (e.g. :jar). + attr_reader :packaging + + # Returns true if this compiler applies to any source code found in the listed source + # directories. For example, Javac returns true if any of the source directories contains + # a .java file. The default implementation looks to see if there are any files in the + # specified path with the extension #source_ext. + def applies_to?(project, task) + paths = task.sources + [sources].flatten.map { |src| Array(project.path_to(:source, task.usage, src.to_sym)) } + paths.flatten! + ext_glob = Array(source_ext).join(',') + + paths.each { |path| + Find.find(path) {|found| + if (!File.directory?(found)) && found.match(/.*\.#{Array(source_ext).join('|')}/) + return true + end + } if File.exist? path + } + false + end + + # Implementations can use this method to specify various compiler attributes. + # For example: + # specify :language=>:java, :target=>'classes', :target_ext=>'class', :packaging=>:jar + def specify(attrs) + attrs[:sources] ||= attrs[:language].to_s + attrs[:source_ext] ||= attrs[:language].to_s + attrs.each { |name, value| instance_variable_set("@#{name}", value) } + end + + # Returns additional dependencies required by this language. For example, since the + # test framework picks on these, you can use the JUnit framework with Scala. + # Defaults to obtaining a list of artifact specifications from the REQUIRES constant. + def dependencies + [] + end + + end + + # Construct a new compiler with the specified options. Note that options may + # change before the compiler is run. + def initialize(project, options) + @project = project + @options = options + end + + # Options for this compiler. + attr_reader :options + + # Determines if the compiler needs to run by checking if the target files exist, + # and if any source files or dependencies are newer than corresponding target files. + def needed?(sources, target, dependencies) + map = compile_map(sources, target) + return false if map.empty? + return true unless File.exist?(target.to_s) + source_files_not_yet_compiled = map.select { |source, target| !File.exist?(target) }.to_a + trace "Compile needed because source file #{source_files_not_yet_compiled[0][0]} has no corresponding #{source_files_not_yet_compiled[0][1]}" unless source_files_not_yet_compiled.empty? + return true if map.any? { |source, target| !File.exist?(target) || File.stat(source).mtime > File.stat(target).mtime } + oldest = map.map { |source, target| File.stat(target).mtime }.min + return dependencies.any? { |path| file(path).timestamp > oldest } + end + + # Compile all files lists in sources (files and directories) into target using the + # specified dependencies. + def compile(sources, target, dependencies) + raise 'Not implemented' + end + + # Returns additional dependencies required by this language. For example, since the + # test framework picks on these, you can use the JUnit framework with Scala. + def dependencies + self.class.dependencies + end + + protected + + # Use this to complain about CompileTask options not supported by this compiler. + # + # For example: + # def compile(files, task) + # check_options task, OPTIONS + # . . . + # end + def check_options(options, *supported) + unsupported = options.to_hash.keys - supported.flatten + raise ArgumentError, "No such option: #{unsupported.join(' ')}" unless unsupported.empty? + end + + # Expands a list of source directories/files into a list of files that have the #source_ext extension. + def files_from_sources(sources) + ext_glob = Array(self.class.source_ext).join(',') + sources.flatten.map { |source| File.directory?(source) ? FileList["#{source}/**/*.{#{ext_glob}}"] : source }. + flatten.reject { |file| File.directory?(file) }.map { |file| File.expand_path(file) }.uniq + end + + # The compile map is a hash that associates source files with target files based + # on a list of source directories and target directory. The compile task uses this + # to determine if there are source files to compile, and which source files to compile. + # The default method maps all files in the source directories with #source_ext into + # paths in the target directory with #target_ext (e.g. 'source/foo.java'=>'target/foo.class'). + def compile_map(sources, target) + target_ext = self.class.target_ext + ext_glob = Array(self.class.source_ext).join(',') + sources.flatten.map{|f| File.expand_path(f)}.inject({}) do |map, source| + if File.directory?(source) + FileList["#{source}/**/*.{#{ext_glob}}"].reject { |file| File.directory?(file) }. + each { |file| map[file] = File.join(target, Util.relative_path(file, source).ext(target_ext)) } + else + # try to extract package name from .java or .scala files + if ['.java', '.scala', '.groovy'].include? File.extname(source) + package = findFirst(source, /^\s*package\s+([^\s;]+)\s*;?\s*/) + map[source] = package ? File.join(target, package[1].gsub('.', '/'), File.basename(source).ext(target_ext)) : target + elsif + map[source] = target + end + end + map + end + end + + private + + def findFirst(file, pattern) + match = nil + File.open(file, "r") do |infile| + while (line = infile.gets) + match = line.match(pattern) + break if match + end + end + match + end + + end + end + + + # Compile task. + # + # Attempts to determine which compiler to use based on the project layout, for example, + # uses the Javac compiler if it finds any .java files in src/main/java. You can also + # select the compiler explicitly: + # compile.using(:scalac) + # + # Accepts multiple source directories that are invoked as prerequisites before compilation. + # You can pass a task as a source directory: + # compile.from(apt) + # + # Likewise, dependencies are invoked before compiling. All dependencies are evaluated as + # #artifacts, so you can pass artifact specifications and even projects: + # compile.with('module1.jar', 'log4j:log4j:jar:1.0', project('foo')) + # + # Creates a file task for the target directory, so executing that task as a dependency will + # execute the compile task first. + # + # Compiler options are inherited form a parent task, e.g. the foo:bar:compile task inherits + # its options from the foo:compile task. Even if foo is an empty project that does not compile + # any classes itself, you can use it to set compile options for all its sub-projects. + # + # Normally, the project will take care of setting the source and target directory, and you + # only need to set options and dependencies. See Project#compile. + class CompileTask < Rake::Task + + def initialize(*args) #:nodoc: + super + parent_task = Project.parent_task(name) + inherit = lambda { |hash, key| parent_task.options[key] } if parent_task.respond_to?(:options) + @options = OpenObject.new &inherit + @sources = FileList[] + @dependencies = FileList[] + + enhance do |task| + unless sources.empty? + raise 'No compiler selected and can\'t determine which compiler to use' unless compiler + raise 'No target directory specified' unless target + mkpath target.to_s + info "Compiling #{task.name.gsub(/:[^:]*$/, '')} into #{target.to_s}" + @compiler.compile(sources.map(&:to_s), target.to_s, dependencies.map(&:to_s)) + # By touching the target we let other tasks know we did something, + # and also prevent recompiling again for dependencies. + touch target.to_s + end + end + end + + # Source directories. + attr_accessor :sources + + # :call-seq: + # from(*sources) => self + # + # Adds source directories and files to compile, and returns self. + # + # For example: + # compile.from('src/java').into('classes').with('module1.jar') + def from(*sources) + @sources |= sources.flatten + guess_compiler if @compiler.nil? && sources.flatten.any? { |source| File.exist?(source.to_s) } + self + end + + # *Deprecated*: Use dependencies instead. + def classpath + Buildr.application.deprecated 'Use dependencies instead.' + dependencies + end + + # *Deprecated*: Use dependencies= instead. + def classpath=(artifacts) + Buildr.application.deprecated 'Use dependencies= instead.' + self.dependencies = artifacts + end + + # Compilation dependencies. + attr_accessor :dependencies + + # :call-seq: + # with(*artifacts) => self + # + # Adds files and artifacts as dependencies, and returns self. + # + # Calls #artifacts on the arguments, so you can pass artifact specifications, + # tasks, projects, etc. Use this rather than setting the dependencies array directly. + # + # For example: + # compile.with('module1.jar', 'log4j:log4j:jar:1.0', project('foo')) + def with(*specs) + @dependencies |= Buildr.artifacts(specs.flatten).uniq + self + end + + # The target directory for the compiled code. + attr_reader :target + + # :call-seq: + # into(path) => self + # + # Sets the target directory and returns self. This will also set the compile task + # as a prerequisite to a file task on the target directory. + # + # For example: + # compile(src_dir).into(target_dir).with(artifacts) + # Both compile.invoke and file(target_dir).invoke will compile the source files. + def into(path) + @target = file(path.to_s).enhance([self]) unless @target.to_s == path.to_s + self + end + + # Returns the compiler options. + attr_reader :options + + # :call-seq: + # using(options) => self + # + # Sets the compiler options from a hash and returns self. Can also be used to + # select the compiler. + # + # For example: + # compile.using(:warnings=>true, :source=>'1.5') + # compile.using(:scala) + def using(*args) + args.pop.each { |key, value| options.send "#{key}=", value } if Hash === args.last + self.compiler = args.pop until args.empty? + self + end + + # Returns the compiler if known. The compiler is either automatically selected + # based on existing source directories (e.g. src/main/java), or by requesting + # a specific compiler (see #using). + def compiler + guess_compiler unless @compiler + @compiler && @compiler.class.to_sym + end + + # Returns the compiled language, if known. See also #compiler. + def language + compiler && @compiler.class.language + end + + # Returns the default packaging type for this compiler, if known. + def packaging + compiler && @compiler.class.packaging + end + + def timestamp #:nodoc: + # If we compiled successfully, then the target directory reflects that. + # If we didn't, see needed? + target ? target.timestamp : Rake::EARLY + end + + # The project this task belongs to. + attr_reader :project + + # The usage, one of :main or :test. + attr_reader :usage + + protected + + # Selects which compiler to use. + def compiler=(name) #:nodoc: + cls = Compiler.select(name) or raise ArgumentError, "No #{name} compiler available. Did you install it?" + return self if cls === @compiler + @compiler = cls.new(project, options) + from Array(cls.sources).map { |path| project.path_to(:source, usage, path) }. + select { |path| File.exist?(path) } if sources.empty? + into project.path_to(:target, usage, cls.target) unless target + with Array(@compiler.dependencies) + self + end + + # Associates this task with project and particular usage (:main, :test). + def associate_with(project, usage) #:nodoc: + @project, @usage = project, usage + guess_compiler + end + + # Try to guess if we have a compiler to match source files. + def guess_compiler #:nodoc: + candidate = Compiler.compilers.detect { |cls| cls.applies_to?(project, self) } + self.compiler = candidate if candidate + end + + private + + def needed? #:nodoc: + return false if sources.empty? + # Fail during invoke. + return true unless @compiler && target + return @compiler.needed?(sources.map(&:to_s), target.to_s, dependencies.map(&:to_s)) + end + + def invoke_prerequisites(args, chain) #:nodoc: + @sources = Array(@sources).map(&:to_s).uniq + @dependencies = FileList[@dependencies.uniq] + @prerequisites |= @dependencies + @sources + super + end + + end + + + # The resources task is executed by the compile task to copy resource files over + # to the target directory. You can enhance this task in the normal way, but mostly + # you will use the task's filter. + # + # For example: + # resources.filter.using 'Copyright'=>'Acme Inc, 2007' + class ResourcesTask < Rake::Task + + # Returns the filter used to copy resources over. See Buildr::Filter. + attr_reader :filter + + def initialize(*args) #:nodoc: + super + @filter = Buildr::Filter.new + @filter.using Buildr.settings.profile['filter'] if Hash === Buildr.settings.profile['filter'] + enhance do + target.invoke if target + end + end + + # :call-seq: + # include(*files) => self + # + # Includes the specified files in the filter and returns self. + def include(*files) + filter.include *files + self + end + + # :call-seq: + # exclude(*files) => self + # + # Excludes the specified files in the filter and returns self. + def exclude(*files) + filter.exclude *files + self + end + + # :call-seq: + # from(*sources) => self + # + # Adds additional directories from which to copy resources. + # + # For example: + # resources.from _('src/etc') + def from(*sources) + filter.from *sources + self + end + + # Returns the list of source directories (each being a file task). + def sources + filter.sources + end + + # :call-seq: + # target => task + # + # Returns the filter's target directory as a file task. + def target + filter.into @project.path_to(:target, @usage, :resources) unless filter.target || sources.empty? + filter.target + end + + def prerequisites #:nodoc: + super + filter.sources.flatten + end + + protected + + # Associates this task with project and particular usage (:main, :test). + def associate_with(project, usage) #:nodoc: + @project, @usage = project, usage + end + + end + + + # Methods added to Project for compiling, handling of resources and generating source documentation. + module Compile + + include Extension + + first_time do + desc 'Compile all projects' + Project.local_task('compile') { |name| "Compiling #{name}" } + end + + before_define(:compile) do |project| + resources = ResourcesTask.define_task('resources') + resources.send :associate_with, project, :main + project.path_to(:source, :main, :resources).tap { |dir| resources.from dir if File.exist?(dir) } + + compile = CompileTask.define_task('compile'=>resources) + compile.send :associate_with, project, :main + project.recursive_task('compile') + end + + after_define(:compile) do |project| + if project.compile.target + # This comes last because the target path is set inside the project definition. + project.build project.compile.target + project.clean do + rm_rf project.compile.target.to_s, :verbose=>false + end + end + end + + + # :call-seq: + # compile(*sources) => CompileTask + # compile(*sources) { |task| .. } => CompileTask + # + # The compile task does what its name suggests. This method returns the project's + # CompileTask. It also accepts a list of source directories and files to compile + # (equivalent to calling CompileTask#from on the task), and a block for any + # post-compilation work. + # + # The compile task attempts to guess which compiler to use. For example, if it finds + # any Java files in the src/main/java directory, it will use the Java compiler and + # create class files in the target/classes directory. + # + # You can also configure it yourself by telling it which compiler to use, pointing + # it as source directories and chooing a different target directory. + # + # For example: + # # Include Log4J and the api sub-project artifacts. + # compile.with 'log4j:log4j:jar:1.2', project('api') + # # Include Apt-generated source files. + # compile.from apt + # # For JavaC, force target compatibility. + # compile.options.source = '1.6' + # # Run the OpenJPA bytecode enhancer after compilation. + # compile { open_jpa_enhance } + # # Pick a given compiler. + # compile.using(:scalac).from('src/scala') + # + # For more information, see CompileTask. + def compile(*sources, &block) + task('compile').from(sources).enhance &block + end + + # :call-seq: + # resources(*prereqs) => ResourcesTask + # resources(*prereqs) { |task| .. } => ResourcesTask + # + # The resources task is executed by the compile task to copy resources files + # from the resource directory into the target directory. By default the resources + # task copies files from the src/main/resources into the target/resources directory. + # + # This method returns the project's resources task. It also accepts a list of + # prerequisites and a block, used to enhance the resources task. + # + # Resources files are copied and filtered (see Buildr::Filter for more information). + # The default filter uses the profile properties for the current environment. + # + # For example: + # resources.from _('src/etc') + # resources.filter.using 'Copyright'=>'Acme Inc, 2007' + # + # Or in your profiles.yaml file: + # common: + # Copyright: Acme Inc, 2007 + def resources(*prereqs, &block) + task('resources').enhance prereqs, &block + end + + end + + + class Options + + # Returns the debug option (environment variable DEBUG). + def debug + (ENV['DEBUG'] || ENV['debug']) !~ /(no|off|false)/ + end + + # Sets the debug option (environment variable DEBUG). + # + # You can turn this option off directly, or by setting the environment variable + # DEBUG to +no+. For example: + # buildr build DEBUG=no + # + # The release tasks runs a build with DEBUG=no. + def debug=(flag) + ENV['debug'] = nil + ENV['DEBUG'] = flag.to_s + end + + end + +end + + +class Buildr::Project + include Buildr::Compile +end diff --git a/buildr/lib/buildr/core/doc.rb b/buildr/lib/buildr/core/doc.rb new file mode 100644 index 0000000..808f286 --- /dev/null +++ b/buildr/lib/buildr/core/doc.rb @@ -0,0 +1,276 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Doc + include Extension + + class << self + def select_by_lang(lang) + fail 'Unable to define doc task for nil language' if lang.nil? + engines.detect { |e| e.language.to_sym == lang.to_sym } + end + + alias_method :select, :select_by_lang + + def select_by_name(name) + fail 'Unable to define doc task for nil' if name.nil? + engines.detect { |e| e.to_sym == name.to_sym } + end + + def engines + @engines ||= [] + end + end + + + # Base class for any documentation provider. Defines most + # common functionality (things like @into@, @from@ and friends). + class Base + class << self + attr_accessor :language, :source_ext + + def specify(options) + @language = options[:language] + @source_ext = options[:source_ext] + end + + def to_sym + @symbol ||= name.split('::').last.downcase.to_sym + end + end + + attr_reader :project + + def initialize(project) + @project = project + end + end + + + class DocTask < Rake::Task + + # The target directory for the generated documentation files. + attr_reader :target + + # Classpath dependencies. + attr_accessor :classpath + + # Additional sourcepaths that are not part of the documented files. + attr_accessor :sourcepath + + # Returns the documentation tool options. + attr_reader :options + + attr_reader :project # :nodoc: + + def initialize(*args) #:nodoc: + super + @options = {} + @classpath = [] + @sourcepath = [] + @files = FileList[] + enhance do |task| + rm_rf target.to_s + mkdir_p target.to_s + + engine.generate(source_files, File.expand_path(target.to_s), + options.merge(:classpath => classpath, :sourcepath => sourcepath)) + + touch target.to_s + end + end + + # :call-seq: + # into(path) => self + # + # Sets the target directory and returns self. This will also set the Javadoc task + # as a prerequisite to a file task on the target directory. + # + # For example: + # package :zip, :classifier=>'docs', :include=>doc.target + def into(path) + @target = file(path.to_s).enhance([self]) unless @target && @target.to_s == path.to_s + self + end + + # :call-seq: + # include(*files) => self + # + # Includes additional source files and directories when generating the documentation + # and returns self. When specifying a directory, includes all source files in that directory. + def include(*files) + @files.include *files.flatten.compact + self + end + + # :call-seq: + # exclude(*files) => self + # + # Excludes source files and directories from generating the documentation. + def exclude(*files) + @files.exclude *files + self + end + + # :call-seq: + # with(*artifacts) => self + # + # Adds files and artifacts as classpath dependencies, and returns self. + def with(*specs) + @classpath |= Buildr.artifacts(specs.flatten).uniq + self + end + + # :call-seq: + # using(options) => self + # + # Sets the documentation tool options from a hash and returns self. + # + # For example: + # doc.using :windowtitle=>'My application' + # doc.using :vscaladoc + def using(*args) + args.pop.each { |key, value| @options[key.to_sym] = value } if Hash === args.last + + until args.empty? + new_engine = Doc.select_by_name(args.pop) + @engine = new_engine.new(project) unless new_engine.nil? + end + + self + end + + def engine + @engine ||= guess_engine + end + + # :call-seq: + # engine?(clazz) => boolean + # + # Check if the underlying engine is an instance of the given class + def engine?(clazz) + begin + @engine ||= guess_engine if project.compile.language + rescue + return false + end + @engine.is_a?(clazz) if @engine + end + + # :call-seq: + # from(*sources) => self + # + # Includes files, directories and projects in the documentation and returns self. + # + # You can call this method with source files and directories containing source files + # to include these files in the documentation, similar to #include. You can also call + # this method with projects. When called with a project, it includes all the source files compiled + # by that project and classpath dependencies used when compiling. + # + # For example: + # doc.from projects('myapp:foo', 'myapp:bar') + def from(*sources) + sources.flatten.each do |source| + case source + when Project + self.enhance source.prerequisites + self.include source.compile.sources + self.with source.compile.dependencies + when Rake::Task, String + self.include source + else + fail "Don't know how to generate documentation from #{source || 'nil'}" + end + end + self + end + + def prerequisites #:nodoc: + super + @files + classpath + sourcepath + end + + def source_files #:nodoc: + @source_files ||= @files.map(&:to_s).map do |file| + Array(engine.class.source_ext).map do |ext| + File.directory?(file) ? FileList[File.join(file, "**/*.#{ext}")] : file + end + end.flatten.reject { |file| @files.exclude?(file) } + end + + def needed? #:nodoc: + return false if source_files.empty? + return true unless File.exist?(target.to_s) + source_files.map { |src| File.stat(src.to_s).mtime }.max > File.stat(target.to_s).mtime + end + + private + + def guess_engine + doc_engine = Doc.select project.compile.language + fail 'Unable to guess documentation provider for project.' unless doc_engine + doc_engine.new project + end + + def associate_with(project) + @project ||= project + end + end + + + first_time do + desc 'Create the documentation for this project' + Project.local_task :doc + end + + before_define(:doc) do |project| + DocTask.define_task('doc').tap do |doc| + doc.send(:associate_with, project) + doc.into project.path_to(:target, :doc) + end + end + + after_define(:doc) do |project| + project.doc.from project + end + + # :call-seq: + # doc(*sources) => JavadocTask + # + # This method returns the project's documentation task. It also accepts a list of source files, + # directories and projects to include when generating the docs. + # + # By default the doc task uses all the source directories from compile.sources and generates + # documentation in the target/doc directory. This method accepts sources and adds them by calling + # Buildr::Doc::Base#from. + # + # For example, if you want to generate documentation for a given project that includes all source files + # in two of its sub-projects: + # doc projects('myapp:foo', 'myapp:bar').using(:windowtitle=>'Docs for foo and bar') + def doc(*sources, &block) + task('doc').from(*sources).enhance &block + end + + def javadoc(*sources, &block) + warn 'The javadoc method is deprecated and will be removed in a future release.' + doc(*sources, &block) + end + end + + + class Project + include Doc + end +end diff --git a/buildr/lib/buildr/core/environment.rb b/buildr/lib/buildr/core/environment.rb new file mode 100644 index 0000000..ac215e8 --- /dev/null +++ b/buildr/lib/buildr/core/environment.rb @@ -0,0 +1,128 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Collection of options for controlling Buildr. + class Options + + # We use this to present environment variable as arrays. + class EnvArray < Array #:nodoc: + + def initialize(name) + @name = name.upcase + replace((ENV[@name] || ENV[@name.downcase] || '').split(/\s*,\s*/).reject(&:empty?)) + end + + (Array.instance_methods - Object.instance_methods - Enumerable.instance_methods - ['each']).sort.each do |method| + class_eval %{def #{method}(*args, &block) ; result = super ; write_envarray ; result ; end} + end + + private + + def write_envarray + ENV[@name.downcase] = nil + ENV[@name] = map(&:to_s).join(',') + end + + end + + + # Wraps around the proxy environment variables: + # * :http -- HTTP_PROXY + # * :https -- HTTPS_PROXY + # * :exclude -- NO_PROXY + class Proxies + + # Returns the HTTP_PROXY URL. + def http + ENV['HTTP_PROXY'] || ENV['http_proxy'] + end + + # Sets the HTTP_PROXY URL. + def http=(url) + ENV['http_proxy'] = nil + ENV['HTTP_PROXY'] = url + end + + # Returns the HTTPS_PROXY URL. + def https + ENV['HTTPS_PROXY'] || ENV['https_proxy'] + end + + # Sets the HTTPS_PROXY URL. + def https=(url) + ENV['https_proxy'] = nil + ENV['HTTPS_PROXY'] = url + end + + # Returns list of hosts to exclude from proxying (NO_PROXY). + def exclude + @exclude ||= EnvArray.new('NO_PROXY') + end + + # Sets list of hosts to exclude from proxy (NO_PROXY). Accepts host name, array of names, + # or nil to clear the list. + def exclude=(url) + exclude.clear + exclude.concat [url].flatten if url + exclude + end + + end + + # :call-seq: + # proxy => options + # + # Returns the proxy options. Currently supported options are: + # * :http -- HTTP proxy for use when downloading. + # * :exclude -- Do not use proxy for these hosts/domains. + # + # For example: + # options.proxy.http = 'http://proxy.acme.com:8080' + # You can also set it using the environment variable HTTP_PROXY. + # + # You can exclude individual hosts from being proxied, or entire domains, for example: + # options.proxy.exclude = 'optimus' + # options.proxy.exclude = ['optimus', 'prime'] + # options.proxy.exclude << '*.internal' + def proxy + @proxy ||= Proxies.new + end + + end + + + class << self + + # :call-seq: + # options => Options + # + # Returns the Buildr options. See Options. + def options + @options ||= Options.new + end + + end + + # :call-seq: + # options => Options + # + # Returns the Buildr options. See Options. + def options + Buildr.options + end + +end diff --git a/buildr/lib/buildr/core/filter.rb b/buildr/lib/buildr/core/filter.rb new file mode 100644 index 0000000..7123a9c --- /dev/null +++ b/buildr/lib/buildr/core/filter.rb @@ -0,0 +1,399 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # A filter knows how to copy files from one directory to another, applying mappings to the + # contents of these files. + # + # You can specify the mapping using a Hash, and it will map ${key} fields found in each source + # file into the appropriate value in the target file. For example: + # + # filter.using 'version'=>'1.2', 'build'=>Time.now + # + # will replace all occurrences of ${version} with 1.2, and ${build} + # with the current date/time. + # + # You can also specify the mapping by passing a proc or a method, that will be called for + # each source file, with the file name and content, returning the modified content. + # + # Without any mapping, the filter simply copies files from the source directory into the target + # directory. + # + # A filter has one target directory, but you can specify any number of source directories, + # either when creating the filter or calling #from. Include/exclude patterns are specified + # relative to the source directories, so: + # filter.include '*.png' + # will only include PNG files from any of the source directories. + # In the same way, you can use regular expressions, so: + # filter.include /picture_.*\.png/ + # will only include PNG files starting with picture_ from any of the sources directories. + # + # See Buildr#filter. + class Filter + + def initialize #:nodoc: + clear + end + + # Returns the list of source directories (each being a file task). + attr_reader :sources + + # :call-seq: + # clear => self + # + # Clear filter sources and include/exclude patterns + def clear + @include = [] + @exclude = [] + @sources = FileList[] + @mapper = Mapper.new + self + end + + # :call-seq: + # from(*sources) => self + # + # Adds additional directories from which to copy resources. + # + # For example: + # filter.from('src').into('target').using('build'=>Time.now) + def from(*sources) + @sources |= sources.flatten.map { |dir| file(File.expand_path(dir.to_s)) } + self + end + + # The target directory as a file task. + def target + return nil unless @target_dir + unless @target + @target = file(File.expand_path(@target_dir)) { |task| run if @target == task } + @target.enhance @include.select {|f| f.is_a?(Rake::FileTask)} + @target.enhance @exclude.select {|f| f.is_a?(Rake::FileTask)} + @target.enhance copy_map.values + end + @target + end + + # :call-seq: + # into(dir) => self + # + # Sets the target directory into which files are copied and returns self. + # + # For example: + # filter.from('src').into('target').using('build'=>Time.now) + def into(dir) + @target_dir = dir.to_s + @target = nil + self + end + + # :call-seq: + # include(*files) => self + # + # Specifies files to include and returns self. See FileList#include. + # + # By default all files are included. You can use this method to only include specific + # files from the source directory. + def include(*files) + @include += files.flatten + self + end + alias :add :include + + # :call-seq: + # exclude(*files) => self + # + # Specifies files to exclude and returns self. See FileList#exclude. + def exclude(*files) + @exclude += files.flatten + self + end + + # The mapping. See #using. + def mapping #:nodoc: + @mapper.config + end + + # The mapper to use. See #using. + def mapper #:nodoc: + @mapper.mapper_type + end + + # :call-seq: + # using(mapping) => self + # using { |file_name, contents| ... } => self + # + # Specifies the mapping to use and returns self. + # + # The most typical mapping uses a Hash, and the default mapping uses the Maven style, so + # ${key} are mapped to the values. You can change that by passing a different + # format as the first argument. Currently supports: + # * :ant -- Map @key@. + # * :maven -- Map ${key} (default). + # * :ruby -- Map #{key}. + # * :erb -- Map <%= key %>. + # * Regexp -- Maps the matched data (e.g. /=(.*?)=/ + # + # For example: + # filter.using 'version'=>'1.2' + # Is the same as: + # filter.using :maven, 'version'=>'1.2' + # + # You can also pass a proc or method. It will be called with the file name and content, + # to return the mapped content. + # + # Without any mapping, all files are copied as is. + # + # To register new mapping type see the Mapper class. + def using(*args, &block) + @mapper.using(*args, &block) + self + end + + # :call-seq: + # run => boolean + # + # Runs the filter. + def run + copy_map = copy_map() + + mkpath target.to_s + return false if copy_map.empty? + + copy_map.each do |path, source| + dest = File.expand_path(path, target.to_s) + if File.directory?(source) + mkpath dest + else + mkpath File.dirname(dest) + if @mapper.mapper_type + mapped = @mapper.transform(File.open(source, 'rb') { |file| file.read }, path) + File.open(dest, 'wb') { |file| file.write mapped } + else # no mapping + cp source, dest + end + end + File.chmod(File.stat(source).mode | 0200, dest) + end + touch target.to_s + true + end + + # Returns the target directory. + def to_s + target.to_s + end + + protected + + # :call-seq: + # pattern_match(file, pattern) => boolean + # + # This method returns true if the file name matches the pattern. + # The pattern may be a String, a Regexp or a Proc. + # + def pattern_match(file, pattern) + case + when pattern.is_a?(Regexp) + return file.match(pattern) + when pattern.is_a?(String) + return File.fnmatch(pattern, file) + when pattern.is_a?(Proc) + return pattern.call(file) + when pattern.is_a?(Rake::FileTask) + return pattern.to_s.match(file) + else + raise "Cannot interpret pattern #{pattern}" + end + end + + private + def copy_map + sources.each { |source| raise "Source directory #{source} doesn't exist" unless File.exist?(source.to_s) } + raise 'No target directory specified, where am I going to copy the files to?' if target.nil? + + sources.flatten.map(&:to_s).inject({}) do |map, source| + files = Util.recursive_with_dot_files(source). + map { |file| Util.relative_path(file, source) }. + select { |file| @include.empty? || @include.any? { |pattern| pattern_match(file, pattern) } }. + reject { |file| @exclude.any? { |pattern| pattern_match(file, pattern) } } + files.each do |file| + src, dest = File.expand_path(file, source), File.expand_path(file, target.to_s) + map[file] = src if !File.exist?(dest) || File.stat(src).mtime >= File.stat(dest).mtime + end + map + end + end + + # This class implements content replacement logic for Filter. + # + # To register a new template engine @:foo@, extend this class with a method like: + # + # def foo_transform(content, path = nil) + # # if this method yields a key, the value comes from the mapping hash + # content.gsub(/world/) { |str| yield :bar } + # end + # + # Then you can use :foo mapping type on a Filter + # + # filter.using :foo, :bar => :baz + # + # Or all by your own, simply + # + # Mapper.new(:foo, :bar => :baz).transform("Hello world") # => "Hello baz" + # + # You can handle configuration arguments by providing a @*_config@ method like: + # + # # The return value of this method is available with the :config accessor. + # def moo_config(*args, &block) + # raise ArgumentError, "Expected moo block" unless block_given? + # { :moos => args, :callback => block } + # end + # + # def moo_transform(content, path = nil) + # content.gsub(/moo+/i) do |str| + # moos = yield :moos # same than config[:moos] + # moo = moos[str.size - 3] || str + # config[:callback].call(moo) + # end + # end + # + # Usage for the @:moo@ mapper would be something like: + # + # mapper = Mapper.new(:moo, 'ooone', 'twoo') do |str| + # i = nil; str.capitalize.gsub(/\w/) { |s| s.send( (i = !i) ? 'upcase' : 'downcase' ) } + # end + # mapper.transform('Moo cow, mooo cows singing mooooo') # => 'OoOnE cow, TwOo cows singing MoOoOo' + class Mapper + + attr_reader :mapper_type, :config + + def initialize(*args, &block) #:nodoc: + using(*args, &block) + end + + def using(*args, &block) + case args.first + when Hash # Maven hash mapping + using :maven, *args + when Binding # Erb binding + using :erb, *args + when Symbol # Mapping from a method + raise ArgumentError, "Unknown mapping type: #{args.first}" unless respond_to?("#{args.first}_transform", true) + configure(*args, &block) + when Regexp # Mapping using a regular expression + raise ArgumentError, 'Expected regular expression followed by mapping hash' unless args.size == 2 && Hash === args[1] + @mapper_type, @config = *args + else + unless args.empty? && block.nil? + raise ArgumentError, 'Expected proc, method or a block' if args.size > 1 || (args.first && block) + @mapper_type = :callback + config = args.first || block + raise ArgumentError, 'Expected proc, method or callable' unless config.respond_to?(:call) + @config = config + end + end + self + end + + def transform(content, path = nil) + type = Regexp === mapper_type ? :regexp : mapper_type + raise ArgumentError, "Invalid mapper type: #{type.inspect}" unless respond_to?("#{type}_transform", true) + self.__send__("#{type}_transform", content, path) { |key| config[key] || config[key.to_s.to_sym] } + end + + private + def configure(mapper_type, *args, &block) + configurer = method("#{mapper_type}_config") rescue nil + if configurer + @config = configurer.call(*args, &block) + else + raise ArgumentError, "Missing hash argument after :#{mapper_type}" unless args.size == 1 && Hash === args[0] + @config = {} unless Hash === @config + args.first.each_pair { |k, v| @config[k] = v.to_s } + end + @mapper_type = mapper_type + end + + def maven_transform(content, path = nil) + content.gsub(/\$\{.*?\}/) { |str| yield(str[2..-2]) || str } + end + + def ant_transform(content, path = nil) + content.gsub(/@.*?@/) { |str| yield(str[1..-2]) || str } + end + + def ruby_transform(content, path = nil) + content.gsub(/#\{.*?\}/) { |str| yield(str[2..-2]) || str } + end + + def regexp_transform(content, path = nil) + content.gsub(mapper_type) { |str| yield(str.scan(mapper_type).join) || str } + end + + def callback_transform(content, path = nil) + config.call(path, content) + end + + def erb_transform(content, path = nil) + case config + when Binding + bnd = config + when Hash + bnd = OpenStruct.new + table = config.inject({}) { |h, e| h[e.first.to_sym] = e.last; h } + bnd.instance_variable_set(:@table, table) + bnd = bnd.instance_eval { binding } + else + bnd = config.instance_eval { binding } + end + ERB.new(content).result(bnd) + end + + def erb_config(*args, &block) + if block_given? + raise ArgumentError, "Expected block or single argument, but both given." unless args.empty? + block + elsif args.size > 1 + raise ArgumentError, "Expected block or single argument." + else + args.first + end + end + + end # class Mapper + + end + + # :call-seq: + # filter(*source) => Filter + # + # Creates a filter that will copy files from the source directory(ies) into the target directory. + # You can extend the filter to modify files by mapping ${key} into values in each + # of the copied files, and by including or excluding specific files. + # + # A filter is not a task, you must call the Filter#run method to execute it. + # + # For example, to copy all files from one directory to another: + # filter('src/files').into('target/classes').run + # To include only the text files, and replace each instance of ${build} with the current + # date/time: + # filter('src/files').into('target/classes').include('*.txt').using('build'=>Time.now).run + def filter(*sources) + Filter.new.from(*sources) + end + +end diff --git a/buildr/lib/buildr/core/generate.rb b/buildr/lib/buildr/core/generate.rb new file mode 100644 index 0000000..815aaac --- /dev/null +++ b/buildr/lib/buildr/core/generate.rb @@ -0,0 +1,193 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Generate #:nodoc: + + task 'generate' do + script = nil + choose do |menu| + menu.header = "To use Buildr you need a buildfile. Do you want me to create one?" + + menu.choice("From maven2 pom file") { script = Generate.from_maven2_pom('pom.xml').join("\n") } if File.exists?("pom.xml") + menu.choice("From directory structure") { script = Generate.from_directory(Dir.pwd).join("\n") } + menu.choice("Skip") { } + end + + if script + buildfile = File.expand_path(Buildr::Application::DEFAULT_BUILDFILES.first) + File.open(buildfile, "w") { |file| file.write script } + puts "Created #{buildfile}" + end + end + + class << self + + HEADER = "# Generated by Buildr #{Buildr::VERSION}, change to your liking\n\n" + + def from_directory(path = Dir.pwd, root = true) + Dir.chdir(path) do + name = File.basename(path) + if root + script = HEADER.split("\n") + header = <<-EOF +#{"require 'buildr/scala'\n" if Dir.glob(path + "/**/*.scala").size > 0} +#{"require 'buildr/groovy'\n" if Dir.glob(path + "/**/*.groovy").size > 0} +# Version number for this release +VERSION_NUMBER = "1.0.0" +# Group identifier for your projects +GROUP = "#{name}" +COPYRIGHT = "" + +# Specify Maven 2.0 remote repositories here, like this: +repositories.remote << "http://repo1.maven.org/maven2" + +desc "The #{name.capitalize} project" +define "#{name}" do + + project.version = VERSION_NUMBER + project.group = GROUP + manifest["Implementation-Vendor"] = COPYRIGHT + EOF + script += header.split("\n") + else + script = [ %{define "#{name}" do} ] + end + script << " compile.with # Add classpath dependencies" if File.exist?("src/main/java") + script << " resources" if File.exist?("src/main/resources") + script << " test.compile.with # Add classpath dependencies" if File.exist?("src/test/java") + script << " test.resources" if File.exist?("src/test/resources") + if File.exist?("src/main/webapp") + script << " package(:war)" + elsif File.exist?("src/main/java") + script << " package(:jar)" + end + dirs = FileList["*"].exclude("src", "target", "report"). + select { |file| File.directory?(file) && File.exist?(File.join(file, "src")) } + unless dirs.empty? + script << "" + dirs.sort.each do |dir| + script << from_directory(dir, false).flatten.map { |line| " " + line } << "" + end + end + script << "end" + script.flatten + end + end + + def from_maven2_pom(path = 'pom.xml', root = true) + pom = Buildr::POM.load(path) + project = pom.project + + artifactId = project['artifactId'].first + description = project['name'] || "The #{artifactId} project" + project_name = File.basename(Dir.pwd) + + if root + script = HEADER.split("\n") + + settings_file = ENV["M2_SETTINGS"] || File.join(ENV['HOME'], ".m2/settings.xml") + settings = XmlSimple.xml_in(IO.read(settings_file)) if File.exists?(settings_file) + + if settings + proxy = settings['proxies'].first['proxy'].find { |proxy| + proxy["active"].nil? || proxy["active"].to_s =~ /true/ + } rescue nil + + if proxy + url = %{#{proxy["protocol"].first}://#{proxy["host"].first}:#{proxy["port"].first}} + exclude = proxy["nonProxyHosts"].to_s.gsub("|", ",") if proxy["nonProxyHosts"] + script << "options.proxy.http = '#{url}'" + script << "options.proxy.exclude << '#{exclude}'" if exclude + script << '' + # In addition, we need to use said proxies to download artifacts. + Buildr.options.proxy.http = url + Buildr.options.proxy.exclude << exclude if exclude + end + end + + repositories = project["repositories"].first["repository"].select { |repository| + legacy = repository["layout"].to_s =~ /legacy/ + !legacy + } rescue nil + repositories = [{"name" => "Standard maven2 repository", "url" => "http://repo1.maven.org/maven2"}] if repositories.nil? || repositories.empty? + repositories.each do |repository| + name, url = repository["name"], repository["url"] + script << "# #{name}" + script << "repositories.remote << '#{url}'" + # In addition we need to use said repositores to download artifacts. + Buildr.repositories.remote << url.to_s + end + script << "" + else + script = [] + end + + script << "desc '#{description}'" + script << "define '#{project_name}' do" + + groupId = project['groupId'] + script << " project.group = '#{groupId}'" if groupId + + version = project['version'] + script << " project.version = '#{version}'" if version + + #get plugins configurations + plugins = project['build'].first['plugins'].first['plugin'] rescue {} + if plugins + compile_plugin = plugins.find{|pl| (pl['groupId'].nil? or pl['groupId'].first == 'org.apache.maven.plugins') and pl['artifactId'].first == 'maven-compiler-plugin'} + if compile_plugin + source = compile_plugin.first['configuration'].first['source'] rescue nil + target = compile_plugin.first['configuration'].first['target'] rescue nil + + script << " compile.options.source = '#{source}'" if source + script << " compile.options.target = '#{target}'" if target + end + end + + compile_dependencies = pom.dependencies + dependencies = compile_dependencies.sort.map{|d| "'#{d}'"}.join(', ') + script << " compile.with #{dependencies}" unless dependencies.empty? + + test_dependencies = (pom.dependencies(['test']) - compile_dependencies).reject{|d| d =~ /^junit:junit:jar:/ } + #check if we have testng + use_testng = test_dependencies.find{|d| d =~ /^org.testng:testng:jar:/} + if use_testng + script << " test.using :testng" + test_dependencies = pom.dependencies(['test']).reject{|d| d =~ /^org.testng:testng:jar:/ } + end + + test_dependencies = test_dependencies.sort.map{|d| "'#{d}'"}.join(', ') + script << " test.with #{test_dependencies}" unless test_dependencies.empty? + + packaging = project['packaging'] ? project['packaging'].first : 'jar' + if %w(jar war).include?(packaging) + script << " package :#{packaging}, :id => '#{artifactId}'" + end + + modules = project['modules'].first['module'] rescue nil + if modules + script << "" + modules.each do |mod| + script << from_maven2_pom(File.join(File.dirname(path), mod, 'pom.xml'), false).flatten.map { |line| " " + line } << "" + end + end + script << "end" + script.flatten + end + + end + end +end diff --git a/buildr/lib/buildr/core/help.rb b/buildr/lib/buildr/core/help.rb new file mode 100644 index 0000000..389f8d1 --- /dev/null +++ b/buildr/lib/buildr/core/help.rb @@ -0,0 +1,114 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + module Help #:nodoc: + class << self + + def <<(arg) + if arg.respond_to?(:call) + texters << arg + else + texters << lambda { arg } + end + end + + def to_s + texters.map(&:call).join("\n") + end + + protected + def texters + @texters ||= [] + end + + end + end + + class << self + def help(&block) + Help << block if block_given? + Help + end + end + +end + + +task 'help' do + # Greeter + puts 'Usage:' + puts ' buildr [-f rakefile] {options} targets...' + puts + + # Show only the top-level projects. + projects.reject(&:parent).tap do |top_level| + unless top_level.empty? + puts 'Top-level projects (buildr help:projects for full list):' + width = [top_level.map(&:name).map(&:size), 20].flatten.max + top_level.each do |project| + puts project.comment.to_s.empty? ? project.name : (" %-#{width}s # %s" % [project.name, project.comment]) + end + puts + end + end + + # Show all the top-level tasks, excluding projects. + puts 'Common tasks:' + task('help:tasks').invoke + puts + puts 'For help on command line options:' + puts ' buildr --help' + puts Buildr.help.to_s +end + + +module Buildr + + # :call-seq: + # help() { ... } + # + # Use this to enhance the help task, e.g. to print some important information about your build, + # configuration options, build instructions, etc. + def help(&block) + Buildr.help << block + end + +end + + +namespace 'help' do + + desc 'List all projects defined by this buildfile' + task 'projects' do + width = projects.map(&:name).map(&:size).max + projects.each do |project| + puts project.comment.to_s.empty? ? " #{project.name}" : (" %-#{width}s # %s" % [project.name, project.comment]) + end + end + + desc 'List all tasks available from this buildfile' + task 'tasks' do + Buildr.application.tasks.select(&:comment).reject { |task| Project === task }.tap do |tasks| + width = [tasks.map(&:name).map(&:size), 20].flatten.max + tasks.each do |task| + printf " %-#{width}s # %s\n", task.name, task.comment + end + puts + end + end + +end diff --git a/buildr/lib/buildr/core/jrebel.rb b/buildr/lib/buildr/core/jrebel.rb new file mode 100644 index 0000000..9128cb2 --- /dev/null +++ b/buildr/lib/buildr/core/jrebel.rb @@ -0,0 +1,42 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module JRebel + def jrebel_home + unless @jrebel_home + @jrebel_home = ENV['REBEL_HOME'] || ENV['JREBEL'] || ENV['JREBEL_HOME'] + end + + (@jrebel_home && File.exists?(@jrebel_home)) ? @jrebel_home : nil + end + + def rebel_jar + if jrebel_home + # jrebel_home may point to jrebel.jar directly + File.directory?(jrebel_home) ? File.join(jrebel_home, 'jrebel.jar') : jrebel_home + end + end + + def jrebel_args + rebel_jar ? [ '-noverify', "-javaagent:#{rebel_jar}" ] : [] + end + + def jrebel_props(project) + {} + end + end +end + diff --git a/buildr/lib/buildr/core/linux.rb b/buildr/lib/buildr/core/linux.rb new file mode 100644 index 0000000..2d948bb --- /dev/null +++ b/buildr/lib/buildr/core/linux.rb @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +# Let's see if we can use notify-send. Must be running from console in verbose mode. +if $stdout.isatty && verbose + system("which notify-send > /dev/null 2>/dev/null") + if $?.exitstatus == 0 + def notify_send(type, title, message) + icon = File.join(File.dirname(__FILE__), '../resources/', type.to_s + '.png') + system "notify-send -i #{icon} \"#{title}\" \"#{message}\"" + end + + Buildr.application.on_completion { |title, message| notify_send(:completed, title, message) if verbose } + Buildr.application.on_failure { |title, message, ex| notify_send(:failed, title, message) if verbose } + end +end + diff --git a/buildr/lib/buildr/core/osx.rb b/buildr/lib/buildr/core/osx.rb new file mode 100644 index 0000000..d8bd6d8 --- /dev/null +++ b/buildr/lib/buildr/core/osx.rb @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +# Let's see if we can use Growl. Must be running from console in verbose mode. +if $stdout.isatty && verbose + def growl_notify(type, title, message) + begin + # Loading Ruby Cocoa can slow the build down (hooks on Object class), so we're + # saving the best for last and only requiring it at the very end. + require 'osx/cocoa' + icon = OSX::NSApplication.sharedApplication.applicationIconImage + icon = OSX::NSImage.alloc.initWithContentsOfFile(File.join(File.dirname(__FILE__), '../resources/buildr.icns')) + + # Register with Growl, that way you can turn notifications on/off from system preferences. + OSX::NSDistributedNotificationCenter.defaultCenter. + postNotificationName_object_userInfo_deliverImmediately(:GrowlApplicationRegistrationNotification, nil, + { :ApplicationName=>'Buildr', :AllNotifications=>['Completed', 'Failed'], + :ApplicationIcon=>icon.TIFFRepresentation }, true) + + OSX::NSDistributedNotificationCenter.defaultCenter. + postNotificationName_object_userInfo_deliverImmediately(:GrowlNotification, nil, + { :ApplicationName=>'Buildr', :NotificationName=>type, + :NotificationTitle=>title, :NotificationDescription=>message }, true) + rescue Exception + # We get here in two cases: system doesn't have Growl installed so one of the OSX + # calls raises an exception; system doesn't have osx/cocoa, e.g. MacPorts Ruby 1.9, + # so we also need to rescue LoadError. + end + end + + Buildr.application.on_completion { |title, message| growl_notify('Completed', title, message) if verbose } + Buildr.application.on_failure { |title, message, ex| growl_notify('Failed', title, message) if verbose } +end diff --git a/buildr/lib/buildr/core/progressbar.rb b/buildr/lib/buildr/core/progressbar.rb new file mode 100644 index 0000000..62b8557 --- /dev/null +++ b/buildr/lib/buildr/core/progressbar.rb @@ -0,0 +1,161 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +class ProgressBar + + class << self + + def start(args, &block) + new(args).start &block + end + + def width + @width ||= $terminal.output_cols || 0 + end + + end + + def initialize(args = {}) + @title = args[:title] || '' + @total = args[:total] || 0 + @mark = args[:mark] || '.' + @format = args[:format] || default_format + @output = args[:output] || $stderr unless args[:hidden] || !$stdout.isatty + clear + end + + def start + @start = @last_time = Time.now + @count = 0 + @finished = false + render + if block_given? + result = yield(self) if block_given? + finish + result + else + self + end + end + + def inc(count) + set @count + count + end + + def <<(bytes) + inc bytes.size + end + + def set(count) + @count = [count, 0].max + @count = [count, @total].min unless @total == 0 + render if changed? + end + + def title + return @title if ProgressBar.width <= 10 + @title.size > ProgressBar.width / 5 ? (@title[0, ProgressBar.width / 5 - 2] + '..') : @title + end + + def count + human(@count) + end + + def total + human(@total) + end + + def percentage + '%3d%%' % (@total == 0 ? 100 : (@count * 100 / @total)) + end + + def time + @finished ? elapsed : eta + end + + def eta + return 'ETA: --:--:--' if @count == 0 + elapsed = Time.now - @start + eta = elapsed * @total / @count - elapsed + 'ETA: %s' % duration(eta.ceil) + end + + def elapsed + 'Time: %s' % duration(Time.now - @start) + end + + def rate + '%s/s' % human(@count / (Time.now - @start)) + end + + def progress(width) + width -= 2 + marks = @total == 0 ? width : (@count * width / @total) + "|%-#{width}s|" % (@mark * marks) + end + + def human(bytes) + magnitude = (0..3).find { |i| bytes < (1024 << i * 10) } || 3 + return '%dB' % bytes if magnitude == 0 + return '%.1f%s' % [ bytes.to_f / (1 << magnitude * 10), [nil, 'KB', 'MB', 'GB'][magnitude] ] + end + + def duration(seconds) + '%02d:%02d:%02d' % [seconds / 3600, (seconds / 60) % 60, seconds % 60] + end + + def finish + unless @finished + @finished = true + render + end + end + +protected + + def clear + return if @output == nil || ProgressBar.width <= 0 + @output.print "\r", " " * (ProgressBar.width - 1), "\r" + @output.flush + end + + def render + return unless @output + format, *args = @format + line = format % args.map { |arg| send(arg) } + if ProgressBar.width >= line.size + @output.print line.sub('|--|') { progress(ProgressBar.width - line.size + 3) } + else + @output.print line.sub('|--|', '') + end + @output.print @finished ? "\n" : "\r" + @output.flush + @previous = @count + @last_time = Time.now + end + + def changed? + return false unless @output && Time.now - @last_time > 0.1 + return human(@count) != human(@previous) if @total == 0 + return true if (@count - @previous) >= @total / 100 + return Time.now - @last_time > 1 + end + + def default_format + @total == 0 ? ['%s %8s %s', :title, :count, :elapsed] : ['%s: %s |--| %8s/%s %s', :title, :percentage, :count, :total, :time] + end + +end \ No newline at end of file diff --git a/buildr/lib/buildr/core/project.rb b/buildr/lib/buildr/core/project.rb new file mode 100644 index 0000000..c55db83 --- /dev/null +++ b/buildr/lib/buildr/core/project.rb @@ -0,0 +1,971 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Symbolic mapping for directory layout. Used for both the default and custom layouts. + # + # For example, the default layout maps [:source, :main, :java] to 'src/main/java', and + # [:target, :main, :classes] to 'target/classes'. You can use this to change the layout + # of your projects. + # + # To map [:source, :main] into the 'sources' directory: + # my_layout = Layout.new + # my_layout[:source, :main] = 'sources' + # + # define 'foo', :layout=>my_layout do + # ... + # end + # + # To map [:source, :main, :java] to 'java/main': + # class MainLast < Layout + # def expand(*args) + # if args[0..1] == [:source, :main] + # super args[2], :main, *args[3,] + # else + # super + # end + # end + # end + # + # define 'foo', :layout=>MainLast do + # ... + # end + class Layout + + class << self + + # Default layout used by new projects. + attr_accessor :default + + end + + def initialize #:nodoc: + @mapping = {} + end + + # Expands list of symbols and path names into a full path, for example: + # puts default.expand(:source, :main, :java) + # => "src/main/java" + def expand(*args) + args = args.compact.reject { |s| s.to_s.empty? }.map(&:to_sym) + return '' if args.empty? + @mapping[args] ||= File.join(*[expand(*args[0..-2]), args.last.to_s].reject(&:empty?)) if args.size > 1 + return @mapping[args] || args.first.to_s + end + + # Resolves a list of symbols into a path. + def [](*args) + @mapping[args.map(&:to_sym)] + end + + # Specifies the path resolved from a list of symbols. + def []=(*args) + @mapping[args[0...-1].map(&:to_sym)] = args.last + end + + def initialize_copy(copy) + copy.instance_variable_set :@mapping, @mapping.clone + end + + # Default layout has the following properties: + # * :source maps to the 'src' directory. + # * Anything under :source maps verbatim (e.g. :source, :main becomes 'src/main') + # * :target maps to the 'target' directory. + # * :target, :main maps to the 'target' directory as well. + # * Anything under :target, :main maps verbatim (e.g. :target, :main, :classes becomes 'target/classes') + # * Anything else under :target also maps verbatim (e.g. :target, :test becomes 'target/test') + class Default < Layout + + def initialize + super + self[:source] = 'src' + self[:target, :main] = 'target' + end + + end + + self.default = Default.new + + end + + + # A project definition is where you define all the tasks associated with + # the project you're building. + # + # The project itself will define several life cycle tasks for you. For example, + # it automatically creates a compile task that will compile all the source files + # found in src/main/java into target/classes, a test task that will compile source + # files from src/test/java and run all the JUnit tests found there, and a build + # task to compile and then run the tests. + # + # You use the project definition to enhance these tasks, for example, telling the + # compile task which class path dependencies to use. Or telling the project how + # to package an artifact, e.g. creating a JAR using package :jar. + # + # You can also define additional tasks that are executed by project tasks, + # or invoked from rake. + # + # Tasks created by the project are all prefixed with the project name, e.g. + # the project foo creates the task foo:compile. If foo contains a sub-project bar, + # the later will define the task foo:bar:compile. Since the compile task is + # recursive, compiling foo will also compile foo:bar. + # + # If you run: + # buildr compile + # from the command line, it will execute the compile task of the current project. + # + # Projects and sub-projects follow a directory heirarchy. The Buildfile is assumed to + # reside in the same directory as the top-level project, and each sub-project is + # contained in a sub-directory in the same name. For example: + # /home/foo + # |__ Buildfile + # |__ src/main/java + # |__ foo + # |__ src/main/java + # + # The default structure of each project is assumed to be: + # src + # |__main + # | |__java <-- Source files to compile + # | |__resources <-- Resources to copy + # | |__webapp <-- For WARs + # |__test + # | |__java <-- Source files to compile (tests) + # | |__resources <-- Resources to copy (tests) + # |__target <-- Packages created here + # | |__classes <-- Generated when compiling + # | |__resources <-- Copied (and filtered) from resources + # | |__test/classes <-- Generated when compiling tests + # | |__test/resources <-- Copied (and filtered) from resources + # |__reports <-- Test, coverage and other reports + # + # You can change the project layout by passing a new Layout to the project definition. + # + # You can only define a project once using #define. Afterwards, you can obtain the project + # definition using #project. The order in which you define projects is not important, + # project definitions are evaluated when you ask for them. Circular dependencies will not + # work. Rake tasks are only created after the project is evaluated, so if you need to access + # a task (e.g. compile) use project('foo').compile instead of task('foo:compile'). + # + # For example: + # define 'myapp', :version=>'1.1' do + # + # define 'wepapp' do + # compile.with project('myapp:beans') + # package :war + # end + # + # define 'beans' do + # compile.with DEPENDS + # package :jar + # end + # end + # + # puts projects.map(&:name) + # => [ 'myapp', 'myapp:beans', 'myapp:webapp' ] + # puts project('myapp:webapp').parent.name + # => 'myapp' + # puts project('myapp:webapp').compile.classpath.map(&:to_spec) + # => 'myapp:myapp-beans:jar:1.1' + class Project < Rake::Task + + class << self + + # :call-seq: + # define(name, properties?) { |project| ... } => project + # + # See Buildr#define. + def define(name, properties, &block) #:nodoc: + # Make sure a sub-project is only defined within the parent project, + # to prevent silly mistakes that lead to inconsistencies (e.g. + # namespaces will be all out of whack). + Buildr.application.current_scope == name.split(':')[0...-1] or + raise "You can only define a sub project (#{name}) within the definition of its parent project" + + @projects ||= {} + raise "You cannot define the same project (#{name}) more than once" if @projects[name] + # Projects with names like: compile, test, build are invalid, so we have + # to make sure the project has not the name of an already defined task + raise "Invalid project name: #{name.inspect} is already used for a task" if Buildr.application.lookup(name) + + Project.define_task(name).tap do |project| + # Define the project to prevent duplicate definition. + @projects[name] = project + # Set the project properties first, actions may use them. + properties.each { |name, value| project.send "#{name}=", value } if properties + # Setup to call before/after define extension callbacks + # Don't cache list of extensions, since project may add new extensions. + project.enhance do |project| + project.send :call_callbacks, :before_define + project.enhance do |project| + project.send :call_callbacks, :after_define + end + end + project.enhance do |project| + @on_define.each { |extension| extension[project] } + end if @on_define + # Enhance the project using the definition block. + project.enhance { project.instance_exec project, &block } if block + + # Top-level project? Invoke the project definition. Sub-project? We don't invoke + # the project definiton yet (allow project calls to establish order of evaluation), + # but must do so before the parent project's definition is done. + project.parent.enhance { project.invoke } if project.parent + end + end + + # :call-seq: + # project(name) => project + # + # See Buildr#project. + def project(*args, &block) #:nodoc: + options = args.pop if Hash === args.last + return define(args.first, options, &block) if block + rake_check_options options, :scope if options + raise ArgumentError, 'Only one project name at a time' unless args.size == 1 + @projects ||= {} + name = args.first.to_s + # Make sure parent project is evaluated (e.g. if looking for foo:bar, find foo first) + unless @projects[name] + parts = name.split(':') + project(parts.first, options || {}) if parts.size > 1 + end + if options && options[:scope] + # We assume parent project is evaluated. + project = options[:scope].split(':').inject([[]]) { |scopes, scope| scopes << (scopes.last + [scope]) }. + map { |scope| @projects[(scope + [name]).join(':')] }. + select { |project| project }.last + end + project ||= @projects[name] # Not found in scope. + raise "No such project #{name}" unless project + project.invoke + project + end + + # :call-seq: + # projects(*names) => projects + # + # See Buildr#projects. + def projects(*names) #:nodoc: + options = names.pop if Hash === names.last + rake_check_options options, :scope if options + @projects ||= {} + names = names.flatten + if options && options[:scope] + # We assume parent project is evaluated. + if names.empty? + parent = @projects[options[:scope].to_s] or raise "No such project #{options[:scope]}" + @projects.values.select { |project| project.parent == parent }.each { |project| project.invoke }. + map { |project| [project] + projects(:scope=>project) }.flatten.sort_by(&:name) + else + names.uniq.map { |name| project(name, :scope=>options[:scope]) } + end + elsif names.empty? + # Parent project(s) not evaluated so we don't know all the projects yet. + @projects.values.each(&:invoke) + @projects.keys.map { |name| project(name) or raise "No such project #{name}" }.sort_by(&:name) + else + # Parent project(s) not evaluated, for the sub-projects we may need to find. + names.map { |name| name.split(':') }.select { |name| name.size > 1 }.map(&:first).uniq.each { |name| project(name) } + names.uniq.map { |name| project(name) or raise "No such project #{name}" }.sort_by(&:name) + end + end + + # :call-seq: + # clear + # + # Discard all project definitions. + def clear + @projects.clear if @projects + end + + # :call-seq: + # local_task(name) + # local_task(name) { |name| ... } + # + # Defines a local task with an optional execution message. + # + # A local task is a task that executes a task with the same name, defined in the + # current project, the project's with a base directory that is the same as the + # current directory. + # + # Complicated? Try this: + # buildr build + # is the same as: + # buildr foo:build + # But: + # cd bar + # buildr build + # is the same as: + # buildr foo:bar:build + # + # The optional block is called with the project name when the task executes + # and returns a message that, for example "Building project #{name}". + def local_task(*args, &block) + task *args do |task, args| + args = task.arg_names.map {|n| args[n]} + local_projects do |project| + info block.call(project.name) if block + task("#{project.name}:#{task.name}").invoke *args + end + end + end + + # *Deprecated* Check the Extension module to see how extensions are handled. + def on_define(&block) + Buildr.application.deprecated 'This method is deprecated, see Extension' + (@on_define ||= []) << block if block + end + + def scope_name(scope, task_name) #:nodoc: + task_name + end + + def local_projects(dir = nil, &block) #:nodoc: + dir = File.expand_path(dir || Buildr.application.original_dir) + projects = @projects ? @projects.values : [] + projects = projects.select { |project| project.base_dir == dir } + if projects.empty? && dir != Dir.pwd && File.dirname(dir) != dir + local_projects(File.dirname(dir), &block) + elsif block + if projects.empty? + warn "No projects defined for directory #{Buildr.application.original_dir}" + else + projects.each { |project| block[project] } + end + else + projects + end + end + + # :call-seq: + # parent_task(task_name) => task_name or nil + # + # Returns a parent task, basically a task in a higher namespace. For example, the parent + # of 'foo:test:compile' is 'foo:compile' and the parent of 'foo:compile' is 'compile'. + def parent_task(task_name) #:nodoc: + namespace = task_name.split(':') + last_name = namespace.pop + namespace.pop + Buildr.application.lookup((namespace + [last_name]).join(':'), []) unless namespace.empty? + end + + # :call-seq: + # project_from_task(task) => project + # + # Figure out project associated to this task and return it. + def project_from_task(task) #:nodoc: + project = Buildr.application.lookup('rake:' + task.to_s.gsub(/:[^:]*$/, '')) + project if Project === project + end + + # Loaded extension modules. + def extension_modules #:nodoc: + @extension_modules ||= [] + end + + # Extension callbacks that apply to all projects + def global_callbacks #:nodoc: + @global_callbacks ||= [] + end + end + + + # Project has visibility to everything in the Buildr namespace. + include Buildr + + # The project name. For example, 'foo' for the top-level project, and 'foo:bar' + # for its sub-project. + attr_reader :name + + # The parent project if this is a sub-project. + attr_reader :parent + + def initialize(*args) #:nodoc: + super + split = name.split(':') + if split.size > 1 + # Get parent project, but do not invoke it's definition to prevent circular + # dependencies (it's being invoked right now, so calling project will fail). + @parent = task(split[0...-1].join(':')) + raise "No parent project #{split[0...-1].join(':')}" unless @parent && Project === parent + end + # Inherit all global callbacks + @callbacks = Project.global_callbacks.dup + end + + # :call-seq: + # base_dir => path + # + # Returns the project's base directory. + # + # The Buildfile defines top-level project, so it's logical that the top-level project's + # base directory is the one in which we find the Buildfile. And each sub-project has + # a base directory that is one level down, with the same name as the sub-project. + # + # For example: + # /home/foo/ <-- base_directory of project 'foo' + # /home/foo/Buildfile <-- builds 'foo' + # /home/foo/bar <-- sub-project 'foo:bar' + def base_dir + if @base_dir.nil? + if parent + # For sub-project, a good default is a directory in the parent's base_dir, + # using the same name as the project. + @base_dir = File.expand_path(name.split(':').last, parent.base_dir) + else + # For top-level project, a good default is the directory where we found the Buildfile. + @base_dir = Dir.pwd + end + end + @base_dir + end + + # Returns the layout associated with this project. + def layout + @layout ||= (parent ? parent.layout : Layout.default).clone + end + + # :call-seq: + # path_to(*names) => path + # + # Returns a path from a combination of name, relative to the project's base directory. + # Essentially, joins all the supplied names and expands the path relative to #base_dir. + # Symbol arguments are converted to paths based on the layout, so whenever possible stick + # to these. For example: + # path_to(:source, :main, :java) + # => 'src/main/java' + # + # Keep in mind that all tasks are defined and executed relative to the Buildfile directory, + # so you want to use #path_to to get the actual path within the project as a matter of practice. + # + # For example: + # path_to('foo', 'bar') + # => foo/bar + # path_to('/tmp') + # => /tmp + # path_to(:base_dir, 'foo') # same as path_to('foo") + # => /home/project1/foo + def path_to(*names) + File.expand_path(layout.expand(*names), base_dir) + end + alias :_ :path_to + + # :call-seq: + # file(path) => Task + # file(path=>prereqs) => Task + # file(path) { |task| ... } => Task + # + # Creates and returns a new file task in the project. Similar to calling Rake's + # file method, but the path is expanded relative to the project's base directory, + # and the task executes in the project's base directory. + # + # For example: + # define 'foo' do + # define 'bar' do + # file('src') { ... } + # end + # end + # + # puts project('foo:bar').file('src').to_s + # => '/home/foo/bar/src' + def file(*args, &block) + task_name, arg_names, deps = Buildr.application.resolve_args(args) + task = Rake::FileTask.define_task(path_to(task_name)) + task.set_arg_names(arg_names) unless arg_names.empty? + task.enhance Array(deps), &block + end + + # :call-seq: + # task(name) => Task + # task(name=>prereqs) => Task + # task(name) { |task| ... } => Task + # + # Creates and returns a new task in the project. Similar to calling Rake's task + # method, but prefixes the task name with the project name and executes the task + # in the project's base directory. + # + # For example: + # define 'foo' do + # task 'doda' + # end + # + # puts project('foo').task('doda').name + # => 'foo:doda' + # + # When called from within the project definition, creates a new task if the task + # does not already exist. If called from outside the project definition, returns + # the named task and raises an exception if the task is not defined. + # + # As with Rake's task method, calling this method enhances the task with the + # prerequisites and optional block. + def task(*args, &block) + task_name, arg_names, deps = Buildr.application.resolve_args(args) + if task_name =~ /^:/ + task = Buildr.application.switch_to_namespace [] do + Rake::Task.define_task(task_name[1..-1]) + end + elsif Buildr.application.current_scope == name.split(':') + task = Rake::Task.define_task(task_name) + else + unless task = Buildr.application.lookup(task_name, name.split(':')) + raise "You cannot define a project task outside the project definition, and no task #{name}:#{task_name} defined in the project" + end + end + task.set_arg_names(arg_names) unless arg_names.empty? + task.enhance Array(deps), &block + end + + # :call-seq: + # recursive_task(name=>prereqs) { |task| ... } + # + # Define a recursive task. A recursive task executes itself and the same task + # in all the sub-projects. + def recursive_task(*args, &block) + task_name, arg_names, deps = Buildr.application.resolve_args(args) + task = Buildr.options.parallel ? multitask(task_name) : task(task_name) + parent.task(task_name).enhance [task] if parent + task.set_arg_names(arg_names) unless arg_names.empty? + task.enhance Array(deps), &block + end + + # :call-seq: + # project(name) => project + # project => self + # + # Same as Buildr#project. This method is called on a project, so a relative name is + # sufficient to find a sub-project. + # + # When called on a project without a name, returns the project itself. You can use that when + # setting project properties, for example: + # define 'foo' do + # project.version = '1.0' + # end + def project(*args, &block) + if Hash === args.last + options = args.pop + else + options = {} + end + if args.empty? + self + else + Project.project *(args + [{ :scope=>self.name }.merge(options)]), &block + end + end + + # :call-seq: + # projects(*names) => projects + # + # Same as Buildr#projects. This method is called on a project, so relative names are + # sufficient to find sub-projects. + def projects(*args) + if Hash === args.last + options = args.pop + else + options = {} + end + Project.projects *(args + [{ :scope=>self.name }.merge(options)]) + end + + def inspect #:nodoc: + %Q{project(#{name.inspect})} + end + + def callbacks #:nodoc: + # global + project_local callbacks for this project + @callbacks ||= [] + end + + def calledback #:nodoc: + # project-local callbacks that have been called + @calledback ||= {} + end + + protected + + # :call-seq: + # base_dir = dir + # + # Sets the project's base directory. Allows you to specify a base directory by calling + # this accessor, or with the :base_dir property when calling #define. + # + # You can only set the base directory once for a given project, and only before accessing + # the base directory (for example, by calling #file or #path_to). + # Set the base directory. Note: you can only do this once for a project, + # and only before accessing the base directory. If you try reading the + # value with #base_dir, the base directory cannot be set again. + def base_dir=(dir) + raise 'Cannot set base directory twice, or after reading its value' if @base_dir + @base_dir = File.expand_path(dir) + end + + # Sets the project layout. Accepts Layout object or class (or for that matter, anything + # that can expand). + def layout=(layout) + raise 'Cannot set directory layout twice, or after reading its value' if @layout + @layout = layout.is_a?(Class) ? layout.new : layout + end + + # :call-seq: + # define(name, properties?) { |project| ... } => project + # + # Define a new sub-project within this project. See Buildr#define. + def define(name, properties = nil, &block) + Project.define "#{self.name}:#{name}", properties, &block + end + + def execute(args) #:nodoc: + Buildr.application.switch_to_namespace name.split(':') do + super + end + end + + # Call all extension callbacks for a particular phase, e.g. :before_define, :after_define. + def call_callbacks(phase) #:nodoc: + remaining = @callbacks.select { |cb| cb.phase == phase } + known_callbacks = remaining.map { |cb| cb.name } + + # call each extension in order + until remaining.empty? + callback = first_satisfied(remaining, known_callbacks) + if callback.nil? + hash = remaining.map { |cb| { cb.name => cb.dependencies} } + fail "Unsatisfied dependencies in extensions for #{phase}: #{hash.inspect}" + end + callback.blocks.each { |b| b.call(self) } + end + end + + private + + # find first callback with satisfied dependencies + def first_satisfied(r, known_callbacks) + remaining_names = r.map { |cb| cb.name } + res = r.find do |cb| + cb.dependencies.each do |dep| + fail "Unknown #{phase.inspect} extension dependency: #{dep.inspect}" unless known_callbacks.index(dep) + end + satisfied = cb.dependencies.find { |dep| remaining_names.index(dep) } == nil + cb if satisfied + end + r.delete res + end + + end + + + # The basic mechanism for extending projects in Buildr are Ruby modules. In fact, + # base features like compiling and testing are all developed in the form of modules, + # and then added to the core Project class. + # + # A module defines instance methods that are then mixed into the project and become + # instance methods of the project. There are two general ways for extending projects. + # You can extend all projects by including the module in Project: + # class Project + # include MyExtension + # end + # You can also extend a given project instance and only that instance by extending + # it with the module: + # define 'foo' do + # extend MyExtension + # end + # + # Some extensions require tighter integration with the project, specifically for + # setting up tasks and properties, or for configuring tasks based on the project + # definition. You can do that by adding callbacks to the process. + # + # The easiest way to add callbacks is by incorporating the Extension module in your + # own extension, and using the various class methods to define callback behavior: + # * first_time -- This block will be called once for any particular extension. + # You can use this to setup top-level and local tasks. + # * before_define -- This block is called once for the project with the project + # instance, right before running the project definition. You can use this + # to add tasks and set properties that will be used in the project definition. + # * after_define -- This block is called once for the project with the project + # instance, right after running the project definition. You can use this to + # do any post-processing that depends on the project definition. + # + # This example illustrates how to write a simple extension: + # module LinesOfCode + # include Extension + # + # first_time do + # # Define task not specific to any projet. + # desc 'Count lines of code in current project' + # Project.local_task('loc') + # end + # + # before_define do |project| + # # Define the loc task for this particular project. + # Rake::Task.define_task 'loc' do |task| + # lines = task.prerequisites.map { |path| Dir['#{path}/**/*'] }.flatten.uniq. + # inject(0) { |total, file| total + File.readlines(file).count } + # puts "Project #{project.name} has #{lines} lines of code" + # end + # end + # + # after_define do |project| + # # Now that we know all the source directories, add them. + # task('loc'=>compile.sources + compile.test.sources) + # end + # + # # To use this method in your project: + # # loc path_1, path_2 + # def loc(*paths) + # task('loc'=>paths) + # end + # + # end + # + # class Buildr::Project + # include LinesOfCode + # end + module Extension + + # Extension callback details + class Callback #:nodoc: + attr_accessor :phase, :name, :dependencies, :blocks + + def initialize(phase, name, dependencies, blocks) + @phase = phase + @name = name + @dependencies = dependencies + @blocks = (blocks ? (Array === blocks ? blocks : [blocks]) : []) + end + + def merge(callback) + Callback.new(phase, name, @dependencies + callback.dependencies, @blocks + callback.blocks) + end + end + + def self.included(base) #:nodoc: + base.extend ClassMethods + end + + # Methods added to the extension module when including Extension. + module ClassMethods + + def included(base) #:nodoc: + # When included in Project, add module instance, merge callbacks and call first_time. + if Project == base && !base.extension_modules.include?(module_callbacks) + base.extension_modules << module_callbacks + merge_callbacks(base.global_callbacks, module_callbacks) + first_time = module_callbacks.select { |c| c.phase == :first_time } + first_time.each do |c| + c.blocks.each { |b| b.call } + end + end + end + + def extended(base) #:nodoc: + # When extending project, merge after_define callbacks and call before_define callback(s) + # immediately + if Project === base + merge_callbacks(base.callbacks, module_callbacks.select { |cb| cb.phase == :after_define }) + calls = module_callbacks.select { |cb| cb.phase == :before_define } + calls.each do |cb| + cb.blocks.each { |b| b.call(base) } unless base.calledback[cb] + base.calledback[cb] = cb + end + end + end + + # This block will be called once for any particular extension included in Project. + # You can use this to setup top-level and local tasks. + def first_time(&block) + module_callbacks << Callback.new(:first_time, self.name, [], block) + end + + # This block is called once for the project with the project instance, + # right before running the project definition. You can use this to add + # tasks and set properties that will be used in the project definition. + # + # The block may be named and dependencies may be declared similar to Rake + # task dependencies: + # + # before_define(:my_setup) do |project| + # # do stuff on project + # end + # + # # my_setup code must run before :compile + # before_define(:compile => :my_setup) + # + def before_define(*args, &block) + if args.empty? + name = self.name + deps = [] + else + name, args, deps = Buildr.application.resolve_args(args) + end + module_callbacks << Callback.new(:before_define, name, deps, block) + end + + # This block is called once for the project with the project instance, + # right after running the project definition. You can use this to do + # any post-processing that depends on the project definition. + # + # The block may be named and dependencies may be declared similar to Rake + # task dependencies: + # + # after_define(:my_setup) do |project| + # # do stuff on project + # end + # + # # my_setup code must run before :compile (but only after project is defined) + # after_define(:compile => :my_setup) + # + def after_define(*args, &block) + if args.empty? + name = self.name + deps = [] + else + name, args, deps = Buildr.application.resolve_args(args) + end + module_callbacks << Callback.new(:after_define, name, deps, block) + end + + private + + def module_callbacks + begin + const_get('Callbacks') + rescue + callbacks = [] + const_set('Callbacks', callbacks) + end + end + + def merge_callbacks(base, merge) + # index by phase and name + index = base.inject({}) { |hash,cb| { [cb.phase, cb.name] => cb } } + merge.each do |cb| + existing = index[[cb.phase, cb.name]] + if existing + base[base.index(existing)] = existing.merge(cb) + else + base << cb + end + index[[cb.phase, cb.name]] = cb + end + base + end + end + + end + + + # :call-seq: + # define(name, properties?) { |project| ... } => project + # + # Defines a new project. + # + # The first argument is the project name. Each project must have a unique name. + # For a sub-project, the actual project name is created by prefixing the parent + # project's name. + # + # The second argument is optional and contains a hash or properties that are set + # on the project. You can only use properties that are supported by the project + # definition, e.g. :group and :version. You can also set these properties from the + # project definition. + # + # You pass a block that is executed in the context of the project definition. + # This block is used to define the project and tasks that are part of the project. + # Do not perform any work inside the project itself, as it will execute each time + # the Buildfile is loaded. Instead, use it to create and extend tasks that are + # related to the project. + # + # For example: + # define 'foo', :version=>'1.0' do + # + # define 'bar' do + # compile.with 'org.apache.axis2:axis2:jar:1.1' + # end + # end + # + # puts project('foo').version + # => '1.0' + # puts project('foo:bar').compile.classpath.map(&:to_spec) + # => 'org.apache.axis2:axis2:jar:1.1' + # % buildr build + # => Compiling 14 source files in foo:bar + def define(name, properties = nil, &block) #:yields:project + Project.define(name, properties, &block) + end + + # :call-seq: + # project(name) => project + # + # Returns a project definition. + # + # When called from outside a project definition, must reference the project by its + # full name, e.g. 'foo:bar' to access the sub-project 'bar' in 'foo'. When called + # from inside a project, relative names are sufficient, e.g. project('foo').project('bar') + # will find the sub-project 'bar' in 'foo'. + # + # You cannot reference a project before the project is defined. When working with + # sub-projects, the project definition is stored by calling #define, and evaluated + # before a call to the parent project's #define method returns. + # + # However, if you call #project with the name of another sub-project, its definition + # is evaluated immediately. So the returned project definition is always complete, + # and you can access its definition (e.g. to find files relative to the base directory, + # or packages created by that project). + # + # For example: + # define 'myapp' do + # self.version = '1.1' + # + # define 'webapp' do + # # webapp is defined first, but beans is evaluated first + # compile.with project('beans') + # package :war + # end + # + # define 'beans' do + # package :jar + # end + # end + # + # puts project('myapp:beans').version + def project(*args, &block) + Project.project *args, &block + end + + # :call-seq: + # projects(*names) => projects + # + # With no arguments, returns a list of all projects defined so far. When called on a project, + # returns all its sub-projects (direct descendants). + # + # With arguments, returns a list of named projects, fails on any name that does not exist. + # As with #project, you can use relative names when calling this method on a project. + # + # Like #project, this method evaluates the definition of each project before returning it. + # Be advised of circular dependencies. + # + # For example: + # files = projects.map { |prj| FileList[prj.path_to('src/**/*.java') }.flatten + # puts "There are #{files.size} source files in #{projects.size} projects" + # + # puts projects('myapp:beans', 'myapp:webapp').map(&:name) + # Same as: + # puts project('myapp').projects.map(&:name) + def projects(*args) + Project.projects *args + end + +end diff --git a/buildr/lib/buildr/core/run.rb b/buildr/lib/buildr/core/run.rb new file mode 100644 index 0000000..1a26562 --- /dev/null +++ b/buildr/lib/buildr/core/run.rb @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Run + + class JavaRunner < Base + include Buildr::JRebel + + specify :name => :java, :languages => [:java, :scala, :groovy, :clojure] + + def run(task) + fail "Missing :main option" unless task.options[:main] + cp = project.compile.dependencies + [project.path_to(:target, :classes), project.path_to(:target, :resources)] + task.classpath + Java::Commands.java(task.options[:main], { + :properties => jrebel_props(project).merge(task.options[:properties] || {}), + :classpath => cp, + :java_args => jrebel_args + (task.options[:java_args] || []) + }) + end + end # JavaRunnner + + end +end + +Buildr::Run.runners << Buildr::Run::JavaRunner + diff --git a/buildr/lib/buildr/core/shell.rb b/buildr/lib/buildr/core/shell.rb new file mode 100644 index 0000000..215ad64 --- /dev/null +++ b/buildr/lib/buildr/core/shell.rb @@ -0,0 +1,132 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + module Shell + + class BeanShell < Base + include Buildr::JRebel + + VERSION = '2.0b4' + + specify :name => :bsh, :languages => [:java] + + class << self + def version + Buildr.settings.build['bsh'] || VERSION + end + + def artifact + "org.beanshell:bsh:jar:#{version}" + end + end + + def launch(task) + cp = ( project.compile.dependencies + + [project.path_to(:target, :classes), Buildr.artifact(BeanShell.artifact)] + + task.classpath ) + Java::Commands.java 'bsh.Console', { + :properties => jrebel_props(project).merge(task.properties), + :classpath => cp, + :java_args => jrebel_args + task.java_args + } + end + + end # BeanShell + + + class JIRB < Base + include JRebel + + JRUBY_VERSION = '1.6.2' + + def launch(task) + if jruby_home # if JRuby is installed, use it + cp = project.compile.dependencies + + [project.path_to(:target, :classes)] + + Dir.glob("#{jruby_home}#{File::SEPARATOR}lib#{File::SEPARATOR}*.jar") + + task.classpath + + props = { + 'jruby.home' => jruby_home, + 'jruby.lib' => "#{jruby_home}#{File::SEPARATOR}lib" + } + props.merge! jrebel_props(project) + props.merge! task.properties + + if not Util.win_os? + uname = `uname -m` + cpu = if uname =~ /i[34567]86/ + 'i386' + elsif uname == 'i86pc' + 'x86' + elsif uname =~ /amd64|x86_64/ + 'amd64' + end + + os = `uname -s | tr '[A-Z]' '[a-z]'` + path = if os == 'darwin' + 'darwin' + else + "#{os}-#{cpu}" + end + + props['jna.boot.library.path'] = "#{jruby_home}/lib/native/#{path}" + end + + props['jruby.script'] = if Util.win_os? then 'jruby.bat' else 'jruby' end + props['jruby.shell'] = if Util.win_os? then 'cmd.exe' else '/bin/sh' end + + args = [ + "-Xbootclasspath/a:#{Dir.glob("#{jruby_home}#{File::SEPARATOR}lib#{File::SEPARATOR}jruby*.jar").join File::PATH_SEPARATOR}" + ] + jrebel_args + task.java_args + + Java::Commands.java 'org.jruby.Main', "#{jruby_home}#{File::SEPARATOR}bin#{File::SEPARATOR}jirb", { + :properties => props, + :classpath => cp, + :java_args => args + } + else + cp = project.compile.dependencies + [ jruby_artifact, project.path_to(:target, :classes) ] + + task.classpath + props = jrebel_props(project).merge(task.properties) + args = jrebel_args + task.java_args + + Java::Commands.java 'org.jruby.Main', '--command', 'irb', { + :properties => props, + :classpath => cp, + :java_args => args + } + end + end + + private + def jruby_home + @jruby_home ||= RUBY_PLATFORM =~ /java/ ? Config::CONFIG['prefix'] : ENV['JRUBY_HOME'] + end + + def jruby_artifact + version = Buildr.settings.build['jruby'] || JRUBY_VERSION + "org.jruby:jruby-complete:jar:#{version}" + end + + end + end +end + +Buildr::Shell.providers << Buildr::Shell::BeanShell +Buildr::Shell.providers << Buildr::Shell::JIRB + diff --git a/buildr/lib/buildr/core/test.rb b/buildr/lib/buildr/core/test.rb new file mode 100644 index 0000000..7fb53f6 --- /dev/null +++ b/buildr/lib/buildr/core/test.rb @@ -0,0 +1,837 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # The underlying test framework used by TestTask. + # To add a new test framework, extend TestFramework::Base and add your framework using: + # Buildr::TestFramework << MyFramework + module TestFramework + + class << self + + # Returns true if the specified test framework exists. + def has?(name) + frameworks.any? { |framework| framework.to_sym == name.to_sym } + end + + # Select a test framework by its name. + def select(name) + frameworks.detect { |framework| framework.to_sym == name.to_sym } + end + + # Identify which test framework applies for this project. + def select_from(project) + # Look for a suitable test framework based on the compiled language, + # which may return multiple candidates, e.g. JUnit and TestNG for Java. + # Pick the one used in the parent project, if not, whichever comes first. + candidates = frameworks.select { |framework| framework.applies_to?(project) } + parent = project.parent + parent && candidates.detect { |framework| framework.to_sym == parent.test.framework } || candidates.first + end + + # Adds a test framework to the list of supported frameworks. + # + # For example: + # Buildr::TestFramework << Buildr::JUnit + def add(framework) + @frameworks ||= [] + @frameworks |= [framework] + end + alias :<< :add + + # Returns a list of available test frameworks. + def frameworks + @frameworks ||= [] + end + + end + + # Base class for all test frameworks, with common functionality. Extend and over-ride as you see fit + # (see JUnit as an example). + class Base + + class << self + + # The framework's identifier (e.g. :junit). Inferred from the class name. + def to_sym + @symbol ||= name.split('::').last.downcase.to_sym + end + + # Returns true if this framework applies to the current project. For example, JUnit returns + # true if the tests are written in Java. + def applies_to?(project) + raise 'Not implemented' + end + + # Returns a list of dependencies for this framework. Default is an empty list, + # override to add dependencies. + def dependencies + @dependencies ||= [] + end + + end + + # Construct a new test framework with the specified options. Note that options may + # change before the framework is run. + def initialize(test_task, options) + @options = options + @task = test_task + end + + # Options for this test framework. + attr_reader :options + # The test task we belong to + attr_reader :task + + # Returns a list of dependenices for this framework. Defaults to calling the #dependencies + # method on the class. + def dependencies + self.class.dependencies + end + + # TestTask calls this method to return a list of test names that can be run in this project. + # It then applies the include/exclude patterns to arrive at the list of tests that will be + # run, and call the #run method with that list. + # + # This method should return a list suitable for using with the #run method, but also suitable + # for the user to manage. For example, JUnit locates all the tests in the test.compile.target + # directory, and returns the class names, which are easier to work with than file names. + def tests(dependencies) + raise 'Not implemented' + end + + # TestTask calls this method to run the named (and only those) tests. This method returns + # the list of tests that ran successfully. + def run(tests, dependencies) + raise 'Not implemented' + end + + end + + end + + + # The test task controls the entire test lifecycle. + # + # You can use the test task in three ways. You can access and configure specific test tasks, + # e.g. enhance the #compile task, or run code during #setup/#teardown. + # + # You can use convenient methods that handle the most common settings. For example, + # add dependencies using #with, or include only specific tests using #include. + # + # You can also enhance this task directly. This task will first execute the #compile task, followed + # by the #setup task, run the unit tests, any other enhancements, and end by executing #teardown. + # + # The test framework is determined based on the available test files, for example, if the test + # cases are written in Java, then JUnit is selected as the test framework. You can also select + # a specific test framework, for example, to use TestNG instead of JUnit: + # test.using :testng + class TestTask < Rake::Task + + class << self + + # Used by the local test and integration tasks to + # a) Find the local project(s), + # b) Find all its sub-projects and narrow down to those that have either unit or integration tests, + # c) Run all the (either unit or integration) tests, and + # d) Ignore failure if necessary. + def run_local_tests(integration) #:nodoc: + Project.local_projects do |project| + # !(foo ^ bar) tests for equality and accepts nil as false (and select is less obfuscated than reject on ^). + projects = ([project] + project.projects).select { |project| !(project.test.options[:integration] ^ integration) } + projects.each do |project| + info "Testing #{project.name}" + begin + project.test.invoke + rescue + raise unless Buildr.options.test == :all + end + end + end + end + + # Used by the test/integration rule to only run tests that match the specified names. + def only_run(tests) #:nodoc: + tests = wildcardify(tests) + # Since the tests may reside in a sub-project, we need to set the include/exclude pattern on + # all sub-projects, but only invoke test on the local project. + Project.projects.each { |project| project.test.send :only_run, tests } + end + + # Used by the test/integration rule to only run tests that failed the last time. + def only_run_failed() #:nodoc: + # Since the tests may reside in a sub-project, we need to set the include/exclude pattern on + # all sub-projects, but only invoke test on the local project. + Project.projects.each { |project| project.test.send :only_run_failed } + end + + # Used by the test/integration rule to clear all previously included/excluded tests. + def clear() + Project.projects.each do |project| + project.test.send :clear + end + end + + # Used by the test/integration to include specific tests + def include(includes) + includes = wildcardify(Array(includes)) + Project.projects.each do |project| + project.test.send :include, *includes if includes.size > 0 + project.test.send :forced_need=, true + end + end + + # Used by the test/integration to exclude specific tests + def exclude(excludes) + excludes = wildcardify(Array(excludes)) + Project.projects.each do |project| + project.test.send :exclude, *excludes if excludes.size > 0 + project.test.send :forced_need=, true + end + end + + private + + def wildcardify(strings) + strings.map { |name| name =~ /\*/ ? name : "*#{name}*" } + end + end + + # Default options already set on each test task. + def default_options + { :fail_on_failure=>true, :fork=>:once, :properties=>{}, :environment=>{} } + end + + def initialize(*args) #:nodoc: + super + @dependencies = FileList[] + @include = [] + @exclude = [] + @forced_need = false + parent_task = Project.parent_task(name) + if parent_task.respond_to?(:options) + @options = OpenObject.new { |hash, key| hash[key] = parent_task.options[key].clone rescue hash[key] = parent_task.options[key] } + else + @options = OpenObject.new(default_options) + end + + unless ENV["IGNORE_BUILDFILE"] =~ /(true)|(yes)/i + enhance [ application.buildfile.name ] + enhance application.buildfile.prerequisites + end + enhance do + run_tests if framework + end + end + + # The dependencies used for running the tests. Includes the compiled files (compile.target) + # and their dependencies. Will also include anything you pass to #with, shared between the + # testing compile and run dependencies. + attr_accessor :dependencies + + # *Deprecated*: Use dependencies instead. + def classpath + Buildr.application.deprecated 'Use dependencies instead.' + @dependencies + end + + # *Deprecated*: Use dependencies= instead. + def classpath=(artifacts) + Buildr.application.deprecated 'Use dependencies= instead.' + @dependencies = artifacts + end + + def execute(args) #:nodoc: + if Buildr.options.test == false + info "Skipping tests for #{project.name}" + return + end + setup.invoke + begin + super + rescue RuntimeError + raise if options[:fail_on_failure] && Buildr.options.test != :all + ensure + teardown.invoke + end + end + + # :call-seq: + # compile(*sources) => CompileTask + # compile(*sources) { |task| .. } => CompileTask + # + # The compile task is similar to the Project's compile task. However, it compiles all + # files found in the src/test/{source} directory into the target/test/{code} directory. + # This task is executed by the test task before running any tests. + # + # Once the project definition is complete, all dependencies from the regular + # compile task are copied over, so you only need to specify dependencies + # specific to your tests. You can do so by calling #with on the test task. + # The dependencies used here are also copied over to the junit task. + def compile(*sources, &block) + @project.task('test:compile').from(sources).enhance &block + end + + # :call-seq: + # resources(*prereqs) => ResourcesTask + # resources(*prereqs) { |task| .. } => ResourcesTask + # + # Executes by the #compile task to copy resource files over. See Project#resources. + def resources(*prereqs, &block) + @project.task('test:resources').enhance prereqs, &block + end + + # :call-seq: + # setup(*prereqs) => task + # setup(*prereqs) { |task| .. } => task + # + # Returns the setup task. The setup task is executed at the beginning of the test task, + # after compiling the test files. + def setup(*prereqs, &block) + @project.task('test:setup').enhance prereqs, &block + end + + # :call-seq: + # teardown(*prereqs) => task + # teardown(*prereqs) { |task| .. } => task + # + # Returns the teardown task. The teardown task is executed at the end of the test task. + def teardown(*prereqs, &block) + @project.task('test:teardown').enhance prereqs, &block + end + + # :call-seq: + # with(*specs) => self + # + # Specify artifacts (specs, tasks, files, etc) to include in the dependencies list + # when compiling and running tests. + def with(*artifacts) + @dependencies |= Buildr.artifacts(artifacts.flatten).uniq + compile.with artifacts + self + end + + # Returns various test options. + attr_reader :options + + # :call-seq: + # using(options) => self + # + # Sets various test options from a hash and returns self. For example: + # test.using :fork=>:each, :properties=>{ 'url'=>'http://localhost:8080' } + # + # Can also be used to select the test framework, or to run these tests as + # integration tests. For example: + # test.using :testng + # test.using :integration + # + # The :fail_on_failure option specifies whether the task should fail if + # any of the tests fail (default), or should report the failures but continue + # running the build (when set to false). + # + # All other options depend on the capability of the test framework. These options + # should be used the same way across all frameworks that support them: + # * :fork -- Fork once for each project (:once, default), for each test in each + # project (:each), or don't fork at all (false). + # * :properties -- Properties pass to the test, e.g. in Java as system properties. + # * :environment -- Environment variables. This hash is made available in the + # form of environment variables. + def using(*args) + args.pop.each { |key, value| options[key.to_sym] = value } if Hash === args.last + args.each do |name| + if TestFramework.has?(name) + self.framework = name + elsif name == :integration + options[:integration] = true + else + Buildr.application.deprecated "Please replace with using(:#{name}=>true)" + options[name.to_sym] = true + end + end + self + end + + # :call-seq: + # include(*names) => self + # + # Include only the specified tests. Unless specified, the default is to include + # all tests identified by the test framework. This method accepts multiple arguments + # and returns self. + # + # Tests are specified by their full name, but you can use glob patterns to select + # multiple tests, for example: + # test.include 'com.example.FirstTest' # FirstTest only + # test.include 'com.example.*' # All tests under com/example + # test.include 'com.example.Module*' # All tests starting with Module + # test.include '*.{First,Second}Test' # FirstTest, SecondTest + def include(*names) + @include += names + self + end + + # :call-seq: + # exclude(*names) => self + # + # Exclude the specified tests. This method accepts multiple arguments and returns self. + # See #include for the type of arguments you can use. + def exclude(*names) + @exclude += names + self + end + + # Clear all test includes and excludes and returns self + def clear + @include = [] + @exclude = [] + self + end + + # *Deprecated*: Use tests instead. + def classes + Buildr.application.deprecated 'Call tests instead of classes' + tests + end + + # After running the task, returns all tests selected to run, based on availability and include/exclude pattern. + attr_reader :tests + # After running the task, returns all the tests that failed, empty array if all tests passed. + attr_reader :failed_tests + # After running the task, returns all the tests that passed, empty array if no tests passed. + attr_reader :passed_tests + + # :call-seq: + # framework => symbol + # + # Returns the test framework, e.g. :junit, :testng. + def framework + unless @framework + # Start with all frameworks that apply (e.g. JUnit and TestNG for Java), + # and pick the first (default) one, unless already specified in parent project. + candidates = TestFramework.frameworks.select { |cls| cls.applies_to?(@project) } + candidate = @project.parent && candidates.detect { |framework| framework.to_sym == @project.parent.test.framework } || + candidates.first + self.framework = candidate if candidate + end + @framework && @framework.class.to_sym + end + + # :call-seq: + # report_to => file + # + # Test frameworks that can produce reports, will write them to this directory. + # + # This is framework dependent, so unless you use the default test framework, call this method + # after setting the test framework. + def report_to + @report_to ||= file(@project.path_to(:reports, framework)=>self) + end + + # :call-seq: + # failures_to => file + # + # We record the list of failed tests for the current framework in this file. + # + # + def failures_to + @failures_to ||= file(@project.path_to(:target, "#{framework}-failed")=>self) + end + + # :call-seq: + # last_failures => array + # + # We read the last test failures if any and return them. + # + def last_failures + @last_failures ||= failures_to.exist? ? File.read(failures_to.to_s).split("\n") : [] + end + + # The path to the file that stores the time stamp of the last successful test run. + def last_successful_run_file #:nodoc: + File.join(report_to.to_s, 'last_successful_run') + end + + # The time stamp of the last successful test run. Or Rake::EARLY if no successful test run recorded. + def timestamp #:nodoc: + File.exist?(last_successful_run_file) ? File.mtime(last_successful_run_file) : Rake::EARLY + end + + # The project this task belongs to. + attr_reader :project + + # Whether the tests are forced + attr_accessor :forced_need + + protected + + def associate_with(project) + @project = project + end + + def framework=(name) + cls = TestFramework.select(name) or raise ArgumentError, "No #{name} test framework available. Did you install it?" + #cls.inherit_options.reject { |name| options.has_key?(name) }. + # each { |name| options[name] = @parent_task.options[name] } if @parent_task.respond_to?(:options) + @framework = cls.new(self, options) + # Test framework dependency. + with @framework.dependencies + end + + # :call-seq: + # include?(name) => boolean + # + # Returns true if the specified test name matches the inclusion/exclusion pattern. Used to determine + # which tests to execute. + def include?(name) + ((@include.empty? && !@forced_need)|| @include.any? { |pattern| File.fnmatch(pattern, name) }) && + !@exclude.any? { |pattern| File.fnmatch(pattern, name) } + end + + # Runs the tests using the selected test framework. + def run_tests + dependencies = (Buildr.artifacts(self.dependencies + compile.dependencies) + [compile.target]).map(&:to_s).uniq + rm_rf report_to.to_s + rm_rf failures_to.to_s + @tests = @framework.tests(dependencies).select { |test| include?(test) }.sort + if @tests.empty? + @passed_tests, @failed_tests = [], [] + else + info "Running tests in #{@project.name}" + begin + # set the baseDir system property if not set + @framework.options[:properties] = { 'baseDir' => compile.target.to_s }.merge(@framework.options[:properties] || {}) + @passed_tests = @framework.run(@tests, dependencies) + rescue Exception=>ex + error "Test framework error: #{ex.message}" + error ex.backtrace.join("\n") if trace? + @passed_tests = [] + end + @failed_tests = @tests - @passed_tests + unless @failed_tests.empty? + Buildr::write(failures_to.to_s, @failed_tests.join("\n")) + error "The following tests failed:\n#{@failed_tests.join("\n")}" + fail 'Tests failed!' + end + end + record_successful_run unless @forced_need + end + + # Call this method when a test run is successful to record the current system time. + def record_successful_run #:nodoc: + mkdir_p report_to.to_s + touch last_successful_run_file + end + + # Limit running tests to specific list. + def only_run(tests) + @include = Array(tests) + @exclude.clear + @forced_need = true + end + + # Limit running tests to those who failed the last time. + def only_run_failed() + @include = Array(last_failures) + @forced_need = true + end + + def invoke_prerequisites(args, chain) #:nodoc: + @prerequisites |= FileList[@dependencies.uniq] + super + end + + def needed? #:nodoc: + latest_prerequisite = @prerequisites.map { |p| application[p, @scope] }.max { |a,b| a.timestamp<=>b.timestamp } + needed = (timestamp == Rake::EARLY) || latest_prerequisite.timestamp > timestamp + trace "Testing#{needed ? ' ' : ' not '}needed. " + + "Latest prerequisite change: #{latest_prerequisite.timestamp} (#{latest_prerequisite.to_s}). " + + "Last successful test run: #{timestamp}." + return needed || @forced_need || Buildr.options.test == :all + end + end + + + # The integration tests task. Buildr has one such task (see Buildr#integration) that runs + # all tests marked with :integration=>true, and has a setup/teardown tasks separate from + # the unit tests. + class IntegrationTestsTask < Rake::Task + + def initialize(*args) #:nodoc: + super + @setup = task("#{name}:setup") + @teardown = task("#{name}:teardown") + enhance do + info 'Running integration tests...' + TestTask.run_local_tests true + end + end + + def execute(args) #:nodoc: + setup.invoke + begin + super + ensure + teardown.invoke + end + end + + # :call-seq: + # setup(*prereqs) => task + # setup(*prereqs) { |task| .. } => task + # + # Returns the setup task. The setup task is executed before running the integration tests. + def setup(*prereqs, &block) + @setup.enhance prereqs, &block + end + + # :call-seq: + # teardown(*prereqs) => task + # teardown(*prereqs) { |task| .. } => task + # + # Returns the teardown task. The teardown task is executed after running the integration tests. + def teardown(*prereqs, &block) + @teardown.enhance prereqs, &block + end + + end + + + # Methods added to Project to support compilation and running of tests. + module Test + + include Extension + + first_time do + desc 'Run all tests' + task('test') { TestTask.run_local_tests false } + + desc 'Run failed tests' + task('test:failed') { + TestTask.only_run_failed + task('test').invoke + } + + # This rule takes a suffix and runs that tests in the current project. For example; + # buildr test:MyTest + # will run the test com.example.MyTest, if such a test exists for this project. + # + # If you want to run multiple test, separate them with a comma. You can also use glob + # (* and ?) patterns to match multiple tests, see the TestTask#include method. + rule /^test:.*$/ do |task| + # The map works around a JRuby bug whereby the string looks fine, but fails in fnmatch. + tests = task.name.scan(/test:(.*)/)[0][0].split(',').map(&:to_s) + excludes, includes = tests.partition { |t| t =~ /^-/ } + if excludes.empty? + TestTask.only_run includes + else + # remove leading '-' + excludes.map! { |t| t[1..-1] } + + TestTask.clear + TestTask.include(includes.empty? ? ['*'] : includes) + TestTask.exclude excludes + end + task('test').invoke + end + + IntegrationTestsTask.define_task('integration') + + # Similar to test:[pattern] but for integration tests. + rule /^integration:.*$/ do |task| + unless task.name.split(':')[1] =~ /^(setup|teardown)$/ + # The map works around a JRuby bug whereby the string looks fine, but fails in fnmatch. + TestTask.only_run task.name[/integration:(.*)/, 1].split(',').map { |t| "#{t}" } + task('integration').invoke + end + end + + end + + before_define(:test) do |project| + # Define a recursive test task, and pass it a reference to the project so it can discover all other tasks. + test = TestTask.define_task('test') + test.send :associate_with, project + + # Similar to the regular resources task but using different paths. + resources = ResourcesTask.define_task('test:resources') + resources.send :associate_with, project, :test + project.path_to(:source, :test, :resources).tap { |dir| resources.from dir if File.exist?(dir) } + + # We define a module inline that will inject cancelling the task if tests are skipped. + module SkipIfNoTest + + def self.extended(base) + base.instance_eval {alias :execute_before_skip_if_no_test :execute} + base.instance_eval {alias :execute :execute_after_skip_if_no_test} + end + + def execute_after_skip_if_no_test(args) #:nodoc: + if Buildr.options.test == false + trace "Skipping #{to_s} for #{project.name} as tests are skipped" + return + end + execute_before_skip_if_no_test(args) + end + end + + # Similar to the regular compile task but using different paths. + compile = CompileTask.define_task('test:compile'=>[project.compile, resources]) + compile.extend SkipIfNoTest + compile.send :associate_with, project, :test + test.enhance [compile] + + # Define these tasks once, otherwise we may get a namespace error. + test.setup ; test.teardown + end + + + + after_define(:test => :compile) do |project| + test = project.test + # Dependency on compiled tests and resources. Dependencies added using with. + test.dependencies.concat [test.compile.target, test.resources.target].compact + test.dependencies.concat test.compile.dependencies + # Dependency on compiled code, its dependencies and resources. + test.with [project.compile.target, project.resources.target].compact + test.with project.compile.dependencies + # Picking up the test frameworks adds further dependencies. + test.framework + + project.build test unless test.options[:integration] || Buildr.options.test == :only + + project.clean do + rm_rf test.compile.target.to_s if test.compile.target + rm_rf test.report_to.to_s + end + end + + + # :call-seq: + # test(*prereqs) => TestTask + # test(*prereqs) { |task| .. } => TestTask + # + # Returns the test task. The test task controls the entire test lifecycle. + # + # You can use the test task in three ways. You can access and configure specific + # test tasks, e.g. enhance the compile task by calling test.compile, setup for + # the tests by enhancing test.setup and so forth. + # + # You can use convenient methods that handle the most common settings. For example, + # add dependencies using test.with, or include only specific tests using test.include. + # + # You can also enhance this task directly. This method accepts a list of arguments + # that are used as prerequisites and an optional block that will be executed by the + # test task. + # + # This task compiles the project and the tests (in that order) before running any tests. + # It execute the setup task, runs all the tests, any enhancements, and ends with the + # teardown tasks. + def test(*prereqs, &block) + task('test').enhance prereqs, &block + end + + # :call-seq: + # integration { |task| .... } + # integration => IntegrationTestTask + # + # Use this method to return the integration tests task, or enhance it with a block to execute. + # + # There is one integration tests task you can execute directly, or as a result of running the package + # task (or tasks that depend on it, like install and upload). It contains all the tests marked with + # :integration=>true, all other tests are considered unit tests and run by the test task before packaging. + # So essentially: build=>test=>packaging=>integration=>install/upload. + # + # You add new tests from projects that define integration tests using the regular test task, + # but with the following addition: + # test.using :integration + # + # Use this method to enhance the setup and teardown tasks that are executed before (and after) all + # integration tests are run, for example, to start a Web server or create a database. + def integration(*deps, &block) + Rake::Task['rake:integration'].enhance deps, &block + end + + end + + + # :call-seq: + # integration { |task| .... } + # integration => IntegrationTestTask + # + # Use this method to return the integration tests task. + def integration(*deps, &block) + Rake::Task['rake:integration'].enhance deps, &block + end + + class Options + + # Runs tests after the build when true (default). This forces tests to execute + # after the build, including when running build related tasks like install, upload and release. + # + # Set to false to not run any tests. Set to :all to run all tests, ignoring failures. + # + # This option is set from the environment variable 'test', so you can also do: + + # Returns the test option (environment variable TEST). Possible values are: + # * :false -- Do not run any tests (also accepts 'no' and 'skip'). + # * :true -- Run all tests, stop on failure (default if not set). + # * :all -- Run all tests, ignore failures. + def test + case value = ENV['TEST'] || ENV['test'] + when /^(no|off|false|skip)$/i + false + when /^all$/i + :all + when /^only$/i + :only + when /^(yes|on|true)$/i, nil + true + else + warn "Expecting the environment variable test to be 'no' or 'all', not sure what to do with #{value}, so I'm just going to run all the tests and stop at failure." + true + end + end + + # Sets the test option (environment variable TEST). Possible values are true, false or :all. + # + # You can also set this from the environment variable, e.g.: + # + # buildr # With tests + # buildr test=no # Without tests + # buildr test=all # Ignore failures + # set TEST=no + # buildr # Without tests + def test=(flag) + ENV['test'] = nil + ENV['TEST'] = flag.to_s + end + + end + + Buildr.help << <<-HELP +To run a full build without running any tests: + buildr test=no +To run specific test: + buildr test:MyTest +To run integration tests: + buildr integration + HELP + +end + + +class Buildr::Project + include Buildr::Test +end diff --git a/buildr/lib/buildr/core/transports.rb b/buildr/lib/buildr/core/transports.rb new file mode 100644 index 0000000..970b525 --- /dev/null +++ b/buildr/lib/buildr/core/transports.rb @@ -0,0 +1,563 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require 'net/http' +# PATCH: On Windows, Net::SSH 2.0.2 attempts to load the Pageant DLLs which break on JRuby. +$LOADED_FEATURES << 'net/ssh/authentication/pageant.rb' if RUBY_PLATFORM =~ /java/ +Net.autoload :SSH, 'net/ssh' +Net.autoload :SFTP, 'net/sftp' +autoload :CGI, 'cgi' +require 'digest/md5' +require 'digest/sha1' +autoload :ProgressBar, 'buildr/core/progressbar' + +# Not quite open-uri, but similar. Provides read and write methods for the resource represented by the URI. +# Currently supports reads for URI::HTTP and writes for URI::SFTP. Also provides convenience methods for +# downloads and uploads. +module URI + + # Raised when trying to read/download a resource that doesn't exist. + class NotFoundError < RuntimeError + end + + # How many bytes to read/write at once. Do not change without checking BUILDR-214 first. + RW_CHUNK_SIZE = 128 * 1024 #:nodoc: + + class << self + + # :call-seq: + # read(uri, options?) => content + # read(uri, options?) { |chunk| ... } + # + # Reads from the resource behind this URI. The first form returns the content of the resource, + # the second form yields to the block with each chunk of content (usually more than one). + # + # For example: + # File.open 'image.jpg', 'w' do |file| + # URI.read('http://example.com/image.jpg') { |chunk| file.write chunk } + # end + # Shorter version: + # File.open('image.jpg', 'w') { |file| file.write URI.read('http://example.com/image.jpg') } + # + # Supported options: + # * :modified -- Only download if file modified since this timestamp. Returns nil if not modified. + # * :progress -- Show the progress bar while reading. + def read(uri, options = nil, &block) + uri = URI.parse(uri.to_s) unless URI === uri + uri.read options, &block + end + + # :call-seq: + # download(uri, target, options?) + # + # Downloads the resource to the target. + # + # The target may be a file name (string or task), in which case the file is created from the resource. + # The target may also be any object that responds to +write+, e.g. File, StringIO, Pipe. + # + # Use the progress bar when running in verbose mode. + def download(uri, target, options = nil) + uri = URI.parse(uri.to_s) unless URI === uri + uri.download target, options + end + + # :call-seq: + # write(uri, content, options?) + # write(uri, options?) { |bytes| .. } + # + # Writes to the resource behind the URI. The first form writes the content from a string or an object + # that responds to +read+ and optionally +size+. The second form writes the content by yielding to the + # block. Each yield should return up to the specified number of bytes, the last yield returns nil. + # + # For example: + # File.open 'killer-app.jar', 'rb' do |file| + # write('sftp://localhost/jars/killer-app.jar') { |chunk| file.read(chunk) } + # end + # Or: + # write 'sftp://localhost/jars/killer-app.jar', File.read('killer-app.jar') + # + # Supported options: + # * :progress -- Show the progress bar while reading. + def write(uri, *args, &block) + uri = URI.parse(uri.to_s) unless URI === uri + uri.write *args, &block + end + + # :call-seq: + # upload(uri, source, options?) + # + # Uploads from source to the resource. + # + # The source may be a file name (string or task), in which case the file is uploaded to the resource. + # The source may also be any object that responds to +read+ (and optionally +size+), e.g. File, StringIO, Pipe. + # + # Use the progress bar when running in verbose mode. + def upload(uri, source, options = nil) + uri = URI.parse(uri.to_s) unless URI === uri + uri.upload source, options + end + + end + + class Generic + + # :call-seq: + # read(options?) => content + # read(options?) { |chunk| ... } + # + # Reads from the resource behind this URI. The first form returns the content of the resource, + # the second form yields to the block with each chunk of content (usually more than one). + # + # For options, see URI::read. + def read(options = nil, &block) + fail 'This protocol doesn\'t support reading (yet, how about helping by implementing it?)' + end + + # :call-seq: + # download(target, options?) + # + # Downloads the resource to the target. + # + # The target may be a file name (string or task), in which case the file is created from the resource. + # The target may also be any object that responds to +write+, e.g. File, StringIO, Pipe. + # + # Use the progress bar when running in verbose mode. + def download(target, options = nil) + case target + when Rake::Task + download target.name, options + when String + # If download breaks we end up with a partial file which is + # worse than not having a file at all, so download to temporary + # file and then move over. + modified = File.stat(target).mtime if File.exist?(target) + temp = Tempfile.new(File.basename(target)) + temp.binmode + read({:progress=>verbose}.merge(options || {}).merge(:modified=>modified)) { |chunk| temp.write chunk } + temp.close + mkpath File.dirname(target) + mv temp.path, target + when File + read({:progress=>verbose}.merge(options || {}).merge(:modified=>target.mtime)) { |chunk| target.write chunk } + target.flush + else + raise ArgumentError, 'Expecting a target that is either a file name (string, task) or object that responds to write (file, pipe).' unless target.respond_to?(:write) + read({:progress=>verbose}.merge(options || {})) { |chunk| target.write chunk } + target.flush + end + end + + # :call-seq: + # write(content, options?) + # write(options?) { |bytes| .. } + # + # Writes to the resource behind the URI. The first form writes the content from a string or an object + # that responds to +read+ and optionally +size+. The second form writes the content by yielding to the + # block. Each yield should return up to the specified number of bytes, the last yield returns nil. + # + # For options, see URI::write. + def write(*args, &block) + options = args.pop if Hash === args.last + options ||= {} + if String === args.first + ios = StringIO.new(args.first, 'r') + write(options.merge(:size=>args.first.size)) { |bytes| ios.read(bytes) } + elsif args.first.respond_to?(:read) + size = args.first.size rescue nil + write({:size=>size}.merge(options)) { |bytes| args.first.read(bytes) } + elsif args.empty? && block + write_internal options, &block + else + raise ArgumentError, 'Either give me the content, or pass me a block, otherwise what would I upload?' + end + end + + # :call-seq: + # upload(source, options?) + # + # Uploads from source to the resource. + # + # The source may be a file name (string or task), in which case the file is uploaded to the resource. + # If the source is a directory, uploads all files inside the directory (including nested directories). + # The source may also be any object that responds to +read+ (and optionally +size+), e.g. File, StringIO, Pipe. + # + # Use the progress bar when running in verbose mode. + def upload(source, options = nil) + source = source.name if Rake::Task === source + options ||= {} + if String === source + raise NotFoundError, 'No source file/directory to upload.' unless File.exist?(source) + if File.directory?(source) + Dir.glob("#{source}/**/*").reject { |file| File.directory?(file) }.each do |file| + uri = self + (File.join(self.path, file.sub(source, ''))) + uri.upload file, {:digests=>[]}.merge(options) + end + else + File.open(source, 'rb') { |input| upload input, options } + end + elsif source.respond_to?(:read) + digests = (options[:digests] || [:md5, :sha1]). + inject({}) { |hash, name| hash[name] = Digest.const_get(name.to_s.upcase).new ; hash } + size = source.stat.size rescue nil + write (options).merge(:progress=>verbose && size, :size=>size) do |bytes| + source.read(bytes).tap do |chunk| + digests.values.each { |digest| digest << chunk } if chunk + end + end + digests.each do |key, digest| + self.merge("#{self.path}.#{key}").write digest.hexdigest, + (options).merge(:progress=>false) + end + else + raise ArgumentError, 'Expecting source to be a file name (string, task) or any object that responds to read (file, pipe).' + end + end + + protected + + # :call-seq: + # with_progress_bar(show, file_name, size) { |progress| ... } + # + # Displays a progress bar while executing the block. The first argument must be true for the + # progress bar to show (TTY output also required), as a convenient for selectively using the + # progress bar from a single block. + # + # The second argument provides a filename to display, the third its size in bytes. + # + # The block is yielded with a progress object that implements a single method. + # Call << for each block of bytes down/uploaded. + def with_progress_bar(show, file_name, size, &block) #:nodoc: + options = { :total=>size || 0, :title=>file_name } + options[:hidden] = true unless show + ProgressBar.start options, &block + end + + # :call-seq: + # proxy_uri => URI? + # + # Returns the proxy server to use. Obtains the proxy from the relevant environment variable (e.g. HTTP_PROXY). + # Supports exclusions based on host name and port number from environment variable NO_PROXY. + def proxy_uri + proxy = ENV["#{scheme.upcase}_PROXY"] + proxy = URI.parse(proxy) if String === proxy + excludes = ENV['NO_PROXY'].to_s.split(/\s*,\s*/).compact + excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" } + return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") } + end + + def write_internal(options, &block) #:nodoc: + fail 'This protocol doesn\'t support writing (yet, how about helping by implementing it?)' + end + + end + + + class HTTP #:nodoc: + + # See URI::Generic#read + def read(options = nil, &block) + options ||= {} + connect do |http| + trace "Requesting #{self}" + headers = { 'If-Modified-Since' => CGI.rfc1123_date(options[:modified].utc) } if options[:modified] + request = Net::HTTP::Get.new(request_uri.empty? ? '/' : request_uri, headers) + request.basic_auth self.user, self.password if self.user + http.request request do |response| + case response + when Net::HTTPNotModified + # No modification, nothing to do. + trace 'Not modified since last download' + return nil + when Net::HTTPRedirection + # Try to download from the new URI, handle relative redirects. + trace "Redirected to #{response['Location']}" + rself = self + URI.parse(response['Location']) + rself.user, rself.password = self.user, self.password + return rself.read(options, &block) + when Net::HTTPOK + info "Downloading #{self}" + result = nil + with_progress_bar options[:progress], path.split('/').last, response.content_length do |progress| + if block + response.read_body do |chunk| + block.call chunk + progress << chunk + end + else + result = '' + response.read_body do |chunk| + result << chunk + progress << chunk + end + end + end + return result + when Net::HTTPUnauthorized + raise NotFoundError, "Looking for #{self} but repository says Unauthorized/401." + when Net::HTTPNotFound + raise NotFoundError, "Looking for #{self} and all I got was a 404!" + else + raise RuntimeError, "Failed to download #{self}: #{response.message}" + end + end + end + end + + private + + def write_internal(options, &block) #:nodoc: + options ||= {} + connect do |http| + trace "Uploading to #{path}" + content = StringIO.new + while chunk = yield(RW_CHUNK_SIZE) + content << chunk + end + headers = { 'Content-MD5'=>Digest::MD5.hexdigest(content.string), 'Content-Type'=>'application/octet-stream' } + request = Net::HTTP::Put.new(request_uri.empty? ? '/' : request_uri, headers) + request.basic_auth self.user, self.password if self.user + response = nil + with_progress_bar options[:progress], path.split('/').last, content.size do |progress| + request.content_length = content.size + content.rewind + stream = Object.new + class << stream ; self ;end.send :define_method, :read do |count| + bytes = content.read(count) + progress << bytes if bytes + bytes + end + request.body_stream = stream + response = http.request(request) + end + + case response + when Net::HTTPRedirection + # Try to download from the new URI, handle relative redirects. + trace "Redirected to #{response['Location']}" + content.rewind + return (self + URI.parse(response['location'])).write_internal(options) { |bytes| content.read(bytes) } + when Net::HTTPSuccess + else + raise RuntimeError, "Failed to upload #{self}: #{response.message}" + end + end + end + + def connect + if proxy = proxy_uri + proxy = URI.parse(proxy) if String === proxy + http = Net::HTTP.new(host, port, proxy.host, proxy.port, proxy.user, proxy.password) + else + http = Net::HTTP.new(host, port) + end + if self.instance_of? URI::HTTPS + require 'net/https' + http.use_ssl = true + end + yield http + end + + end + + + class SFTP < Generic #:nodoc: + + DEFAULT_PORT = 22 + COMPONENT = [ :scheme, :userinfo, :host, :port, :path ].freeze + + class << self + # Caching of passwords, so we only need to ask once. + def passwords + @passwords ||= {} + end + end + + def initialize(*arg) + super + end + + def read(options = {}, &block) + # SSH options are based on the username/password from the URI. + ssh_options = { :port=>port, :password=>password }.merge(options[:ssh_options] || {}) + ssh_options[:password] ||= SFTP.passwords[host] + begin + trace "Connecting to #{host}" + if block + result = nil + else + result = '' + block = lambda { |chunk| result << chunk } + end + Net::SFTP.start(host, user, ssh_options) do |sftp| + SFTP.passwords[host] = ssh_options[:password] + trace 'connected' + + with_progress_bar options[:progress] && options[:size], path.split('/').last, options[:size] || 0 do |progress| + trace "Downloading from #{path}" + sftp.file.open(path, 'r') do |file| + while chunk = file.read(RW_CHUNK_SIZE) + block.call chunk + progress << chunk + break if chunk.size < RW_CHUNK_SIZE + end + end + end + end + return result + rescue Net::SSH::AuthenticationFailed=>ex + # Only if running with console, prompt for password. + if !ssh_options[:password] && $stdout.isatty + password = ask("Password for #{host}:") { |q| q.echo = '*' } + ssh_options[:password] = password + retry + end + raise + end + end + + protected + + def write_internal(options, &block) #:nodoc: + # SSH options are based on the username/password from the URI. + ssh_options = { :port=>port, :password=>password }.merge(options[:ssh_options] || {}) + ssh_options[:password] ||= SFTP.passwords[host] + begin + trace "Connecting to #{host}" + Net::SFTP.start(host, user, ssh_options) do |sftp| + SFTP.passwords[host] = ssh_options[:password] + trace 'Connected' + + # To create a path, we need to create all its parent. We use realpath to determine if + # the path already exists, otherwise mkdir fails. + trace "Creating path #{path}" + File.dirname(path).split('/').reject(&:empty?).inject('/') do |base, part| + combined = base + part + sftp.close(sftp.opendir!(combined)) rescue sftp.mkdir! combined, {} + "#{combined}/" + end + + with_progress_bar options[:progress] && options[:size], path.split('/').last, options[:size] || 0 do |progress| + trace "Uploading to #{path}" + sftp.file.open(path, 'w') do |file| + while chunk = yield(RW_CHUNK_SIZE) + file.write chunk + progress << chunk + end + sftp.setstat(path, :permissions => options[:permissions]) if options[:permissions] + end + end + end + rescue Net::SSH::AuthenticationFailed=>ex + # Only if running with console, prompt for password. + if !ssh_options[:password] && $stdout.isatty + password = ask("Password for #{host}:") { |q| q.echo = '*' } + ssh_options[:password] = password + retry + end + raise + end + end + + end + + @@schemes['SFTP'] = SFTP + + + # File URL. Keep in mind that file URLs take the form of file://host/path, although the host + # is not used, so typically all you will see are three backslashes. This methods accept common variants, + # like file:/path but always returns a valid URL. + class FILE < Generic + + COMPONENT = [ :host, :path ].freeze + + def upload(source, options = nil) + super + if File === source then + File.chmod(source.stat.mode, real_path) + end + end + + def initialize(*args) + super + # file:something (opaque) becomes file:///something + if path.nil? + set_path "/#{opaque}" + unless opaque.nil? + set_opaque nil + warn "#{caller[2]}: We'll accept this URL, but just so you know, it needs three slashes, as in: #{to_s}" + end + end + # Sadly, file://something really means file://something/ (something being server) + set_path '/' if path.empty? + + # On windows, file://c:/something is not a valid URL, but people do it anyway, so if we see a drive-as-host, + # we'll just be nice enough to fix it. (URI actually strips the colon here) + if host =~ /^[a-zA-Z]$/ + set_path "/#{host}:#{path}" + set_host nil + end + end + + # See URI::Generic#read + def read(options = nil, &block) + options ||= {} + raise ArgumentError, 'Either you\'re attempting to read a file from another host (which we don\'t support), or you used two slashes by mistake, where you should have file:///.' if host + + path = real_path + # TODO: complain about clunky URLs + raise NotFoundError, "Looking for #{self} and can't find it." unless File.exists?(path) + raise NotFoundError, "Looking for the file #{self}, and it happens to be a directory." if File.directory?(path) + File.open path, 'rb' do |input| + with_progress_bar options[:progress], path.split('/').last, input.stat.size do |progress| + block ? block.call(input.read) : input.read + end + end + end + + def to_s + "file://#{host}#{path}" + end + + # Returns the file system path based that corresponds to the URL path. + # On windows this method strips the leading slash off of the path. + # On all platforms this method unescapes the URL path. + def real_path #:nodoc: + real_path = Buildr::Util.win_os? && path =~ /^\/[a-zA-Z]:\// ? path[1..-1] : path + URI.unescape(real_path) + end + + protected + + def write_internal(options, &block) #:nodoc: + raise ArgumentError, 'Either you\'re attempting to write a file to another host (which we don\'t support), or you used two slashes by mistake, where you should have file:///.' if host + temp = Tempfile.new(File.basename(path)) + temp.binmode + with_progress_bar options[:progress] && options[:size], path.split('/').last, options[:size] || 0 do |progress| + while chunk = yield(RW_CHUNK_SIZE) + temp.write chunk + progress << chunk + end + end + temp.close + mkpath File.dirname(real_path) + mv temp.path, real_path + real_path + end + + @@schemes['FILE'] = FILE + + end + +end diff --git a/buildr/lib/buildr/core/util.rb b/buildr/lib/buildr/core/util.rb new file mode 100644 index 0000000..8ff2a7b --- /dev/null +++ b/buildr/lib/buildr/core/util.rb @@ -0,0 +1,474 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + module Util + extend self + + def java_platform? + RUBY_PLATFORM =~ /java/ + end + + # In order to determine if we are running on a windows OS, + # prefer this function instead of using Gem.win_platform?. + # + # Gem.win_platform? only checks these RUBY_PLATFORM global, + # that in some cases like when running on JRuby is not + # succifient for our purpose: + # + # For JRuby, the value for RUBY_PLATFORM will always be 'java' + # That's why this function checks on Config::CONFIG['host_os'] + def win_os? + Config::CONFIG['host_os'] =~ /windows|cygwin|bccwin|cygwin|djgpp|mingw|mswin|wince/i + end + + # Runs Ruby with these command line arguments. The last argument may be a hash, + # supporting the following keys: + # :command -- Runs the specified script (e.g., :command=>'gem') + # :sudo -- Run as sudo on operating systems that require it. + # :verbose -- Override Rake's verbose flag. + def ruby(*args) + options = Hash === args.last ? args.pop : {} + cmd = [] + ruby_bin = normalize_path(Config::CONFIG['ruby_install_name'], Config::CONFIG['bindir']) + if options.delete(:sudo) && !(win_os? || Process.uid == File.stat(ruby_bin).uid) + cmd << 'sudo' << '-u' << "##{File.stat(ruby_bin).uid}" + end + cmd << ruby_bin + cmd << '-S' << options.delete(:command) if options[:command] + cmd.concat args.flatten + cmd.push options + sh *cmd do |ok, status| + ok or fail "Command ruby failed with status (#{status ? status.exitstatus : 'unknown'}): [#{cmd.join(" ")}]" + end + end + + # Just like File.expand_path, but for windows systems it + # capitalizes the drive name and ensures backslashes are used + def normalize_path(path, *dirs) + path = File.expand_path(path, *dirs) + if win_os? + path.gsub!('/', '\\').gsub!(/^[a-zA-Z]+:/) { |s| s.upcase } + else + path + end + end + + # Return the timestamp of file, without having to create a file task + def timestamp(file) + if File.exist?(file) + File.mtime(file) + else + Rake::EARLY + end + end + + # Return the path to the first argument, starting from the path provided by the + # second argument. + # + # For example: + # relative_path('foo/bar', 'foo') + # => 'bar' + # relative_path('foo/bar', 'baz') + # => '../foo/bar' + # relative_path('foo/bar') + # => 'foo/bar' + # relative_path('/foo/bar', 'baz') + # => '/foo/bar' + def relative_path(to, from = '.') + to = Pathname.new(to).cleanpath + return to.to_s if from.nil? + to_path = Pathname.new(File.expand_path(to.to_s, "/")) + from_path = Pathname.new(File.expand_path(from.to_s, "/")) + to_path.relative_path_from(from_path).to_s + end + + # Generally speaking, it's not a good idea to operate on dot files (files starting with dot). + # These are considered invisible files (.svn, .hg, .irbrc, etc). Dir.glob/FileList ignore them + # on purpose. There are few cases where we do have to work with them (filter, zip), a better + # solution is welcome, maybe being more explicit with include. For now, this will do. + def recursive_with_dot_files(*dirs) + FileList[dirs.map { |dir| File.join(dir, '/**/{*,.*}') }].reject { |file| File.basename(file) =~ /^[.]{1,2}$/ } + end + + # :call-seq: + # replace_extension(filename) => filename_with_updated_extension + # + # Replace the file extension, e.g., + # replace_extension("foo.zip", "txt") => "foo.txt" + def replace_extension(filename, new_ext) + ext = File.extname(filename) + if filename =~ /\.$/ + filename + new_ext + elsif ext == "" + filename + "." + new_ext + else + filename[0..-ext.length] + new_ext + end + end + + end # Util +end + + +class Object #:nodoc: + unless defined? instance_exec # 1.9 + module InstanceExecMethods #:nodoc: + end + include InstanceExecMethods + + # Evaluate the block with the given arguments within the context of + # this object, so self is set to the method receiver. + # + # From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec + def instance_exec(*args, &block) + begin + old_critical, Thread.critical = Thread.critical, true + n = 0 + n += 1 while respond_to?(method_name = "__instance_exec#{n}") + InstanceExecMethods.module_eval { define_method(method_name, &block) } + ensure + Thread.critical = old_critical + end + + begin + send(method_name, *args) + ensure + InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil + end + end + end +end + +module Kernel #:nodoc: + unless defined? tap # 1.9 + def tap + yield self if block_given? + self + end + end +end + +class Symbol #:nodoc: + unless defined? to_proc # 1.9 + # Borrowed from Ruby 1.9. + def to_proc + Proc.new{|*args| args.shift.__send__(self, *args)} + end + end +end + +unless defined? BasicObject # 1.9 + class BasicObject #:nodoc: + (instance_methods - ['__send__', '__id__', '==', 'send', 'send!', 'respond_to?', 'equal?', 'object_id']). + each do |method| + undef_method method + end + + def self.ancestors + [Kernel] + end + end +end + + +class OpenObject < Hash + + def initialize(source=nil, &block) + super &block + update source if source + end + + def method_missing(symbol, *args) + if symbol.to_s =~ /=$/ + self[symbol.to_s[0..-2].to_sym] = args.first + else + self[symbol] + end + end +end + + +class Hash + + class << self + + # :call-seq: + # Hash.from_java_properties(string) + # + # Returns a hash from a string in the Java properties file format. For example: + # str = 'foo=bar\nbaz=fab' + # Hash.from_properties(str) + # => { 'foo'=>'bar', 'baz'=>'fab' }.to_properties + def from_java_properties(string) + hash = {} + input_stream = Java.java.io.StringBufferInputStream.new(string) + java_properties = Java.java.util.Properties.new + java_properties.load input_stream + keys = java_properties.keySet.iterator + while keys.hasNext + # Calling key.next in JRuby returns a java.lang.String, behaving as a Ruby string and life is good. + # MRI, unfortunately, treats next() like the interface says returning an object that's not a String, + # and the Hash doesn't work the way we need it to. Unfortunately, we can call toString on MRI's object, + # but not on the JRuby one; calling to_s on the JRuby object returns what we need, but ... you guessed it. + # So this seems like the one hack to unite them both. + #key = Java.java.lang.String.valueOf(keys.next.to_s) + key = keys.next + key = key.toString unless String === key + hash[key] = java_properties.getProperty(key) + end + hash + end + + end + + # :call-seq: + # only(keys*) => hash + # + # Returns a new hash with only the specified keys. + # + # For example: + # { :a=>1, :b=>2, :c=>3, :d=>4 }.only(:a, :c) + # => { :a=>1, :c=>3 } + def only(*keys) + keys.inject({}) { |hash, key| has_key?(key) ? hash.merge(key=>self[key]) : hash } + end + + + # :call-seq: + # except(keys*) => hash + # + # Returns a new hash without the specified keys. + # + # For example: + # { :a=>1, :b=>2, :c=>3, :d=>4 }.except(:a, :c) + # => { :b=>2, :d=>4 } + def except(*keys) + (self.keys - keys).inject({}) { |hash, key| hash.merge(key=>self[key]) } + end + + # :call-seq: + # to_java_properties => string + # + # Convert hash to string format used for Java properties file. For example: + # { 'foo'=>'bar', 'baz'=>'fab' }.to_properties + # => foo=bar + # baz=fab + def to_java_properties + keys.sort.map { |key| + value = self[key].gsub(/[\t\r\n\f\\]/) { |escape| "\\" + {"\t"=>"t", "\r"=>"r", "\n"=>"n", "\f"=>"f", "\\"=>"\\"}[escape] } + "#{key}=#{value}" + }.join("\n") + end + +end + +if Buildr::Util.java_platform? + require 'ffi' + + # Workaround for BUILDR-535: when requiring 'ffi', JRuby defines an :error + # method with arity 0. + class Module + remove_method :error if method_defined?(:error) + end + + # Fix for BUILDR-292. + # JRuby fails to rename a file on different devices + # this monkey-patch wont be needed when JRUBY-3381 gets resolved. + module FileUtils #:nodoc: + alias_method :__mv_native, :mv + + def mv(from, to, options = nil) + dir_to = File.directory?(to) ? to : File.dirname(to) + Array(from).each do |from| + dir_from = File.dirname(from) + if File.stat(dir_from).dev != File.stat(dir_to).dev + cp from, to, options + rm from, options + else + __mv_native from, to, options + end + end + end + private :mv + end + + module RakeFileUtils #:nodoc: + def rake_merge_option(args, defaults) + defaults[:verbose] = false if defaults[:verbose] == :default + + if Hash === args.last + defaults.update(args.last) + args.pop + end + args.push defaults + args + end + private :rake_merge_option + end + + module Buildr + class ProcessStatus + attr_reader :pid, :termsig, :stopsig, :exitstatus + + def initialize(pid, success, exitstatus) + @pid = pid + @success = success + @exitstatus = exitstatus + + @termsig = nil + @stopsig = nil + end + + def &(num) + pid & num + end + + def ==(other) + pid == other.pid + end + + def >>(num) + pid >> num + end + + def coredump? + false + end + + def exited? + true + end + + def stopped? + false + end + + def success? + @success + end + + def to_i + pid + end + + def to_int + pid + end + + def to_s + pid.to_s + end + end + end + + module FileUtils + extend FFI::Library + + ffi_lib FFI::Platform::LIBC + + alias_method :__jruby_system__, :system + attach_function :system, [:string], :int + alias_method :__native_system__, :system + alias_method :system, :__jruby_system__ + + # code "borrowed" directly from Rake + def sh(*cmd, &block) + options = (Hash === cmd.last) ? cmd.pop : {} + unless block_given? + show_command = cmd.join(" ") + show_command = show_command[0,42] + "..." + + block = lambda { |ok, status| + ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]" + } + end + if RakeFileUtils.verbose_flag == :default + options[:verbose] = false + else + options[:verbose] ||= RakeFileUtils.verbose_flag + end + options[:noop] ||= RakeFileUtils.nowrite_flag + rake_check_options options, :noop, :verbose + rake_output_message cmd.join(" ") if options[:verbose] + unless options[:noop] + if Buildr::Util.win_os? + # Ruby uses forward slashes regardless of platform, + # unfortunately cd c:/some/path fails on Windows + pwd = Dir.pwd.gsub(%r{/}, '\\') + cd = "cd /d \"#{pwd}\" && " + else + cd = "cd '#{Dir.pwd}' && " + end + args = if cmd.size > 1 then cmd[1..cmd.size] else [] end + + res = if Buildr::Util.win_os? && cmd.size == 1 + __native_system__("#{cd} call #{cmd.first}") + else + arg_str = args.map { |a| "'#{a}'" } + __native_system__(cd + cmd.first + ' ' + arg_str.join(' ')) + end + status = Buildr::ProcessStatus.new(0, res == 0, res) # KLUDGE + block.call(res == 0, status) + end + end + + end +else + module FileUtils + # code "borrowed" directly from Rake + def sh(*cmd, &block) + options = (Hash === cmd.last) ? cmd.pop : {} + unless block_given? + show_command = cmd.join(" ") + show_command = show_command[0,42] + "..." + + block = lambda { |ok, status| + ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]" + } + end + if RakeFileUtils.verbose_flag == :default + options[:verbose] = false + else + options[:verbose] ||= RakeFileUtils.verbose_flag + end + options[:noop] ||= RakeFileUtils.nowrite_flag + rake_check_options options, :noop, :verbose + rake_output_message cmd.join(" ") if options[:verbose] + unless options[:noop] + if Buildr::Util.win_os? + # Ruby uses forward slashes regardless of platform, + # unfortunately cd c:/some/path fails on Windows + pwd = Dir.pwd.gsub(%r{/}, '\\') + cd = "cd /d \"#{pwd}\" && " + else + cd = "cd '#{Dir.pwd}' && " + end + + args = if cmd.size > 1 then cmd[1..cmd.size] else [] end + + res = if Buildr::Util.win_os? && cmd.size == 1 + system("#{cd} call #{cmd.first}") + else + arg_str = args.map { |a| "'#{a}'" } + system(cd + cmd.first + ' ' + arg_str.join(' ')) + end + + block.call(res, $?) + end + end + end +end diff --git a/buildr/lib/buildr/groovy.rb b/buildr/lib/buildr/groovy.rb new file mode 100644 index 0000000..1c7050b --- /dev/null +++ b/buildr/lib/buildr/groovy.rb @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require 'buildr/groovy/compiler' +require 'buildr/groovy/bdd' +require 'buildr/groovy/doc' +require 'buildr/groovy/shell' diff --git a/buildr/lib/buildr/groovy/bdd.rb b/buildr/lib/buildr/groovy/bdd.rb new file mode 100644 index 0000000..58ed222 --- /dev/null +++ b/buildr/lib/buildr/groovy/bdd.rb @@ -0,0 +1,106 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +module Buildr::Groovy + + # EasyB is a Groovy based BDD framework. + # To use in your project: + # + # test.using :easyb + # + # This framework will search in your project for: + # src/spec/groovy/**/*Story.groovy + # src/spec/groovy/**/*Specification.groovy + # + # Support the following options: + # * :format -- Report format :txt or :xml, default is :txt + # * :properties -- Hash of properties passed to the test suite. + # * :java_args -- Arguments passed to the JVM. + class EasyB < TestFramework::JavaBDD + @lang = :groovy + @bdd_dir = :spec + + VERSION = "0.9" + TESTS_PATTERN = [ /(Story|Specification).groovy$/ ] + OPTIONS = [:format, :properties, :java_args] + + class << self + def version + Buildr.settings.build['jbehave'] || VERSION + end + + def dependencies + @dependencies ||= ["org.easyb:easyb:jar:#{version}", + 'org.codehaus.groovy:groovy:jar:1.5.3','asm:asm:jar:2.2.3', + 'commons-cli:commons-cli:jar:1.0','antlr:antlr:jar:2.7.7'] + end + + def applies_to?(project) #:nodoc: + %w{ + **/*Specification.groovy **/*Story.groovy + }.any? { |glob| !Dir[project.path_to(:source, bdd_dir, lang, glob)].empty? } + end + + private + def const_missing(const) + return super unless const == :REQUIRES # TODO: remove in 1.5 + Buildr.application.deprecated "Please use JBehave.dependencies/.version instead of JBehave::REQUIRES/VERSION" + dependencies + end + end + + def tests(dependencies) #:nodoc: + Dir[task.project.path_to(:source, bdd_dir, lang, "**/*.groovy")]. + select { |name| TESTS_PATTERN.any? { |pat| pat === name } } + end + + def run(tests, dependencies) #:nodoc: + options = { :format => :txt }.merge(self.options).only(*OPTIONS) + + if :txt == options[:format] + easyb_format, ext = 'txtstory', '.txt' + elsif :xml == options[:format] + easyb_format, ext = 'xmlbehavior', '.xml' + else + raise "Invalid format #{options[:format]} expected one of :txt :xml" + end + + cmd_args = [ 'org.disco.easyb.BehaviorRunner' ] + cmd_options = { :properties => options[:properties], + :java_args => options[:java_args], + :classpath => dependencies } + + tests.inject([]) do |passed, test| + name = test.sub(/.*?groovy[\/\\]/, '').pathmap('%X') + report = File.join(task.report_to.to_s, name + ext) + mkpath report.pathmap('%d') + begin + Java::Commands.java cmd_args, + "-#{easyb_format}", report, + test, cmd_options.merge(:name => name) + rescue => e + passed + else + passed << test + end + end + end + + end # EasyB + +end + +Buildr::TestFramework << Buildr::Groovy::EasyB diff --git a/buildr/lib/buildr/groovy/compiler.rb b/buildr/lib/buildr/groovy/compiler.rb new file mode 100644 index 0000000..8268488 --- /dev/null +++ b/buildr/lib/buildr/groovy/compiler.rb @@ -0,0 +1,153 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +module Buildr::Groovy + + REQUIRES = ArtifactNamespace.for(self) do |ns| + ns.jansi! 'org.fusesource.jansi:jansi:jar:1.2.1' + ns.jline! 'jline:jline:jar:0.9.94' + end + + class << self + def dependencies #:nodoc: + REQUIRES.artifacts + Groovyc.dependencies + end + end + + # Groovyc compiler: + # compile.using(:groovyc) + # + # You need to require 'buildr/groovy/compiler' if you need to use this compiler. + # + # Used by default if .groovy files are found in the src/main/groovy directory (or src/test/groovy) + # and sets the target directory to target/classes (or target/test/classes). + # + # Groovyc is a joint compiler, this means that when selected for a project, this compiler is used + # to compile both groovy and java sources. It's recommended that Groovy sources are placed in the + # src/main/groovy directory, even though this compiler also looks in src/main/java + # + # Groovyc accepts the following options: + # + # * :encoding -- Encoding of source files + # * :verbose -- Asks the compiler for verbose output, true when running in verbose mode. + # * :fork -- Whether to execute groovyc using a spawned instance of the JVM; defaults to no + # * :memoryInitialSize -- The initial size of the memory for the underlying VM, if using fork mode; ignored otherwise. + # Defaults to the standard VM memory setting. (Examples: 83886080, 81920k, or 80m) + # * :memoryMaximumSize -- The maximum size of the memory for the underlying VM, if using fork mode; ignored otherwise. + # Defaults to the standard VM memory setting. (Examples: 83886080, 81920k, or 80m) + # * :listfiles -- Indicates whether the source files to be compiled will be listed; defaults to no + # * :stacktrace -- If true each compile error message will contain a stacktrace + # * :warnings -- Issue warnings when compiling. True when running in verbose mode. + # * :debug -- Generates bytecode with debugging information. Set from the debug + # environment variable/global option. + # * :deprecation -- If true, shows deprecation messages. False by default. + # * :optimise -- Generates faster bytecode by applying optimisations to the program. + # * :source -- Source code compatibility. + # * :target -- Bytecode compatibility. + # * :javac -- Hash of options passed to the ant javac task + # + class Groovyc < Compiler::Base + + # The groovyc compiler jars are added to classpath at load time, + # if you want to customize artifact versions, you must set them on the + # + # artifact_ns(Buildr::Groovy::Groovyc).groovy = '1.7.1' + # + # namespace before this file is required. + REQUIRES = ArtifactNamespace.for(self) do |ns| + ns.groovy! 'org.codehaus.groovy:groovy:jar:>=1.7.5' + ns.commons_cli! 'commons-cli:commons-cli:jar:>=1.2' + ns.asm! 'asm:asm:jar:>=3.2' + ns.antlr! 'antlr:antlr:jar:>=2.7.7' + end + + ANT_TASK = 'org.codehaus.groovy.ant.Groovyc' + GROOVYC_OPTIONS = [:encoding, :verbose, :fork, :memoryInitialSize, :memoryMaximumSize, :listfiles, :stacktrace] + JAVAC_OPTIONS = [:optimise, :warnings, :debug, :deprecation, :source, :target, :javac] + OPTIONS = GROOVYC_OPTIONS + JAVAC_OPTIONS + + class << self + def dependencies #:nodoc: + REQUIRES.artifacts + end + + def applies_to?(project, task) #:nodoc: + paths = task.sources + [sources].flatten.map { |src| Array(project.path_to(:source, task.usage, src.to_sym)) } + paths.flatten! + # Just select if we find .groovy files + paths.any? { |path| !Dir["#{path}/**/*.groovy"].empty? } + end + end + + # Groovy dependencies don't need to go into JVM's system classpath. + # In fact, if they end up there it causes trouble because Groovy has issues + # loading other classes such as test frameworks (e.g. JUnit). + # + # Java.classpath << lambda { dependencies } + + specify :language => :groovy, :sources => [:groovy, :java], :source_ext => [:groovy, :java], + :target => 'classes', :target_ext => 'class', :packaging => :jar + + def initialize(project, options) #:nodoc: + super + options[:debug] = Buildr.options.debug if options[:debug].nil? + options[:deprecation] ||= false + options[:optimise] ||= false + options[:verbose] ||= trace?(:groovyc) if options[:verbose].nil? + options[:warnings] = verbose if options[:warnings].nil? + options[:javac] = OpenObject.new if options[:javac].nil? + end + + # http://groovy.codehaus.org/The+groovyc+Ant+Task + def compile(sources, target, dependencies) #:nodoc: + return if Buildr.application.options.dryrun + Buildr.ant 'groovyc' do |ant| + classpath = dependencies + ant.taskdef :name => 'groovyc', :classname => ANT_TASK, :classpath => classpath.join(File::PATH_SEPARATOR) + ant.groovyc groovyc_options(sources, target) do + sources.each { |src| ant.src :path => src } + ant.classpath do + classpath.each { |dep| ant.pathelement :path => dep } + end + ant.javac(javac_options) + end + end + end + + private + def groovyc_options(sources, target) + check_options options, OPTIONS + groovyc_options = options.to_hash.only(*GROOVYC_OPTIONS) + groovyc_options[:destdir] = File.expand_path(target) + groovyc_options + end + + def javac_options + check_options options, OPTIONS + javac_options = options.to_hash.only(*JAVAC_OPTIONS) + javac_options[:optimize] = (javac_options.delete(:optimise) || false) + javac_options[:nowarn] = (javac_options.delete(:warnings) || verbose).to_s !~ /^(true|yes|on)$/i + other = javac_options.delete(:javac) || {} + javac_options.merge!(other) + javac_options + end + + end +end + +# Groovy compiler comes first, ahead of Javac, this allows it to pick +# projects that mix Groovy and Java code by spotting Groovy code first. +Buildr::Compiler.compilers.unshift Buildr::Groovy::Groovyc diff --git a/buildr/lib/buildr/groovy/doc.rb b/buildr/lib/buildr/groovy/doc.rb new file mode 100644 index 0000000..a20ad8c --- /dev/null +++ b/buildr/lib/buildr/groovy/doc.rb @@ -0,0 +1,73 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Doc + + module GroovydocDefaults + include Extension + + # Default groovydoc -doc-title to project's comment or name + after_define(:groovydoc => :doc) do |project| + if project.doc.engine? Groovydoc + options = project.doc.options + options[:windowtitle] = (project.comment || project.name) unless options[:windowtitle] + end + end + end + + class Groovydoc < Base + specify :language => :groovy, :source_ext => ['java', 'groovy'] + + def generate(sources, target, options = {}) + mkdir_p target + cmd_args = [ '-d', target, trace?(:groovydoc) ? '-verbose' : nil ].compact + options.reject { |key, value| [:sourcepath, :classpath].include?(key) }. + each { |key, value| value.invoke if value.respond_to?(:invoke) }. + each do |key, value| + case value + when true, nil + cmd_args << "-#{key}" + when false + cmd_args << "-no#{key}" + when Hash + value.each { |k,v| cmd_args << "-#{key}" << k.to_s << v.to_s } + else + cmd_args += Array(value).map { |item| ["-#{key}", item.to_s] }.flatten + end + end + [:sourcepath, :classpath].each do |option| + Array(options[option]).flatten.tap do |paths| + cmd_args << "-#{option}" << paths.flatten.map(&:to_s).join(File::PATH_SEPARATOR) unless paths.empty? + end + end + cmd_args += sources.flatten.uniq + unless Buildr.application.options.dryrun + info "Generating Groovydoc for #{project.name}" + trace (['groovydoc'] + cmd_args).join(' ') + result = Java::Commands.java('org.codehaus.groovy.tools.groovydoc.Main', cmd_args, + :classpath => Buildr::Groovy.dependencies) + end + end + end + end + + class Project + include GroovydocDefaults + end +end + +Buildr::Doc.engines << Buildr::Doc::Groovydoc + diff --git a/buildr/lib/buildr/groovy/shell.rb b/buildr/lib/buildr/groovy/shell.rb new file mode 100644 index 0000000..76787eb --- /dev/null +++ b/buildr/lib/buildr/groovy/shell.rb @@ -0,0 +1,55 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Groovy + class GroovySH < Buildr::Shell::Base + include JRebel + + SUFFIX = if Util.win_os? then '.bat' else '' end + + specify :name => :groovy, :languages => [:groovy] + + def launch(task) + cp = Groovy.dependencies + + project.compile.dependencies + + [ project.path_to(:target, :classes) ] + + task.classpath + props = jrebel_props(project).merge(task.properties) + java_args = jrebel_args + task.java_args + + groovy_home = nil + if groovy_home + cmd_args = " -classpath '#{cp.join(File::SEPARATOR)}'" + trace "groovysh #{cmd_args}" + system(File.expand_path("bin#{File::SEPARATOR}groovysh#{SUFFIX}", groovy_home) + cmd_args) + else + Java::Commands.java 'org.codehaus.groovy.tools.shell.Main', { + :properties => props, + :classpath => cp, + :java_args => java_args + } + end + end + + private + def groovy_home + @home ||= ENV['GROOVY_HOME'] + end + end + end +end + +Buildr::Shell.providers << Buildr::Groovy::GroovySH diff --git a/buildr/lib/buildr/ide/eclipse.rb b/buildr/lib/buildr/ide/eclipse.rb new file mode 100644 index 0000000..546a647 --- /dev/null +++ b/buildr/lib/buildr/ide/eclipse.rb @@ -0,0 +1,424 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Eclipse #:nodoc: + include Extension + + class Eclipse + + attr_reader :options + attr_writer :name + + def initialize(project) + @project = project + @options = Options.new(project) + end + + def name + return @name if @name + return @project.id.split('-').last if @options.short_names + @project.id + end + + # :call-seq: + # classpath_variables :VAR => '/path/to/location' + # Sets classpath variables to be used for library path substitution + # on the project. + # + def classpath_variables(*values) + fail "eclipse.classpath_variables expects a single hash argument" if values.size > 1 + if values.size == 1 + fail "eclipse.classpath_variables expects a Hash argument" unless values[0].is_a? Hash + # convert keys to strings + values = values[0].inject({}) { |h, (k,v)| h[k.to_s] = @project.path_to(v); h } + @variables = values.merge(@variables || {}) + end + @variables || (@project.parent ? @project.parent.eclipse.classpath_variables : default_classpath_variables) + end + + def default_classpath_variables + vars = {} + vars[:SCALA_HOME] = ENV['SCALA_HOME'] if ENV['SCALA_HOME'] + vars[:JAVA_HOME] = ENV['JAVA_HOME'] if ENV['JAVA_HOME'] + vars + end + + # :call-seq: + # natures=(natures) + # Sets the Eclipse project natures on the project. + # + def natures=(var) + @natures = arrayfy(var) + end + + # :call-seq: + # natures() => [n1, n2] + # Returns the Eclipse project natures on the project. + # They may be derived from the parent project if no specific natures have been set + # on the project. + # + # An Eclipse project nature is used internally by Eclipse to determine the aspects of a project. + def natures(*values) + if values.size > 0 + @natures ||= [] + @natures += values.flatten + else + @natures || (@project.parent ? @project.parent.eclipse.natures : []) + end + end + + # :call-seq: + # classpath_containers=(cc) + # Sets the Eclipse project classpath containers on the project. + # + def classpath_containers=(var) + @classpath_containers = arrayfy(var) + end + + # :call-seq: + # classpath_containers() => [con1, con2] + # Returns the Eclipse project classpath containers on the project. + # They may be derived from the parent project if no specific classpath containers have been set + # on the project. + # + # A classpath container is an Eclipse pre-determined ensemble of dependencies made available to + # the project classpath. + def classpath_containers(*values) + if values.size > 0 + @classpath_containers ||= [] + @classpath_containers += values.flatten + else + @classpath_containers || (@project.parent ? @project.parent.eclipse.classpath_containers : []) + end + end + + # :call-seq: + # exclude_libs() => [lib1, lib2] + # Returns the an array of libraries to be excluded from the generated Eclipse classpath + def exclude_libs(*values) + if values.size > 0 + @exclude_libs ||= [] + @exclude_libs += values.flatten + else + @exclude_libs || (@project.parent ? @project.parent.eclipse.exclude_libs : []) + end + end + + # :call-seq: + # exclude_libs=(lib1, lib2) + # Sets libraries to be excluded from the generated Eclipse classpath + # + def exclude_libs=(libs) + @exclude_libs = arrayfy(libs) + end + + # :call-seq: + # builders=(builders) + # Sets the Eclipse project builders on the project. + # + def builders=(var) + @builders = arrayfy(var) + end + + # :call-seq: + # builders() => [b1, b2] + # Returns the Eclipse project builders on the project. + # They may be derived from the parent project if no specific builders have been set + # on the project. + # + # A builder is an Eclipse background job that parses the source code to produce built artifacts. + def builders(*values) + if values.size > 0 + @builders ||= [] + @builders += values.flatten + else + @builders || (@project.parent ? @project.parent.eclipse.builders : []) + end + end + + private + + def arrayfy(obj) + obj.is_a?(Array) ? obj : [obj] + end + end + + class Options + + attr_writer :m2_repo_var, :short_names + + def initialize(project) + @project = project + end + + # The classpath variable used to point at the local maven2 repository. + # Example: + # eclipse.options.m2_repo_var = 'M2_REPO' + def m2_repo_var(*values) + fail "m2_repo_var can only accept one value: #{values}" if values.size > 1 + if values.size > 0 + @m2_repo_var = values[0] + else + @m2_repo_var || (@project.parent ? @project.parent.eclipse.options.m2_repo_var : 'M2_REPO') + end + end + + def short_names + @short_names || (@project.parent ? @project.parent.eclipse.options.short_names : false) + end + end + + def eclipse + @eclipse ||= Eclipse.new(self) + @eclipse + end + + first_time do + # Global task "eclipse" generates artifacts for all projects. + desc 'Generate Eclipse artifacts for all projects' + Project.local_task('eclipse'=>'artifacts') { |name| "Generating Eclipse project for #{name}" } + end + + before_define do |project| + project.recursive_task('eclipse') + end + + after_define(:eclipse => :package) do |project| + # Need to enhance because using project.projects during load phase of the + # buildfile has harmful side-effects on project definition order + project.enhance do + eclipse = project.task('eclipse') + # We don't create the .project and .classpath files if the project contains projects. + if project.projects.empty? + + eclipse.enhance [ file(project.path_to('.classpath')), file(project.path_to('.project')) ] + + # The only thing we need to look for is a change in the Buildfile. + file(project.path_to('.classpath')=>Buildr.application.buildfile) do |task| + if (project.eclipse.natures.reject { |x| x.is_a?(Symbol) }.size > 0) + info "Writing #{task.name}" + + m2repo = Buildr::Repositories.instance.local + + File.open(task.name, 'w') do |file| + classpathentry = ClasspathEntryWriter.new project, file + classpathentry.write do + # Note: Use the test classpath since Eclipse compiles both "main" and "test" classes using the same classpath + cp = project.test.compile.dependencies.map(&:to_s) - [ project.compile.target.to_s, project.resources.target.to_s ] + cp = cp.uniq + + # Convert classpath elements into applicable Project objects + cp.collect! { |path| Buildr.projects.detect { |prj| prj.packages.detect { |pkg| pkg.to_s == path } } || path } + + # Remove excluded libs + cp -= project.eclipse.exclude_libs.map(&:to_s) + + # project_libs: artifacts created by other projects + project_libs, others = cp.partition { |path| path.is_a?(Project) } + + # Separate artifacts under known classpath variable paths + # including artifacts located in local Maven2 repository + vars = [] + project.eclipse.classpath_variables.merge(project.eclipse.options.m2_repo_var => m2repo).each do |name, path| + matching, others = others.partition { |f| File.expand_path(f.to_s).index(path) == 0 } + matching.each do |m| + vars << [m, name, path] + end + end + + # Generated: Any non-file classpath elements in the project are assumed to be generated + libs, generated = others.partition { |path| File.file?(path.to_s) } + + classpathentry.src project.compile.sources + generated + classpathentry.src project.resources + + if project.test.compile.target + classpathentry.src project.test.compile + classpathentry.src project.test.resources + end + + # Classpath elements from other projects + classpathentry.src_projects project_libs + + classpathentry.output project.compile.target if project.compile.target + classpathentry.lib libs + classpathentry.var vars + + project.eclipse.classpath_containers.each { |container| + classpathentry.con container + } + end + end + end + end + + # The only thing we need to look for is a change in the Buildfile. + file(project.path_to('.project')=>Buildr.application.buildfile) do |task| + info "Writing #{task.name}" + File.open(task.name, 'w') do |file| + xml = Builder::XmlMarkup.new(:target=>file, :indent=>2) + xml.projectDescription do + xml.name project.eclipse.name + xml.projects + unless project.eclipse.builders.empty? + xml.buildSpec do + project.eclipse.builders.each { |builder| + xml.buildCommand do + xml.name builder + end + } + end + end + unless project.eclipse.natures.empty? + xml.natures do + project.eclipse.natures.each { |nature| + xml.nature nature unless nature.is_a? Symbol + } + end + end + end + end + end + end + end + end + + + # Writes 'classpathentry' tags in an xml file. + # It converts tasks to paths. + # It converts absolute paths to relative paths. + # It ignores duplicate directories. + class ClasspathEntryWriter #:nodoc: + def initialize project, target + @project = project + @xml = Builder::XmlMarkup.new(:target=>target, :indent=>2) + @excludes = [ '**/.svn/', '**/CVS/' ].join('|') + @paths_written = [] + end + + def write &block + @xml.classpath &block + end + + def con path + @xml.classpathentry :kind=>'con', :path=>path + end + + def lib libs + libs.map(&:to_s).sort.uniq.each do |path| + @xml.classpathentry :kind=>'lib', :path=>relative(path) + end + end + + # Write a classpathentry of kind 'src'. + # Accept an array of absolute paths or a task. + def src arg + if [:sources, :target].all? { |message| arg.respond_to?(message) } + src_from_task arg + else + src_from_absolute_paths arg + end + end + + # Write a classpathentry of kind 'src' for dependent projects. + # Accept an array of projects. + def src_projects project_libs + project_libs.map { |project| project.eclipse.name }.sort.uniq.each do |eclipse_name| + @xml.classpathentry :kind=>'src', :combineaccessrules=>'false', :path=>"/#{eclipse_name}" + end + end + + def output target + @xml.classpathentry :kind=>'output', :path=>relative(target) + end + + # Write a classpathentry of kind 'var' (variable) for a library in a local repo. + # * +libs+ is an array of library paths. + # * +var_name+ is a variable name as defined in Eclipse (e.g., 'M2_REPO'). + # * +var_value+ is the value of this variable (e.g., '/home/me/.m2'). + # E.g., var([lib1, lib2], 'M2_REPO', '/home/me/.m2/repo') + def var(libs) + libs.each do |lib_path, var_name, var_value| + lib_artifact = file(lib_path) + + attribs = { :kind => 'var', :path => lib_path } + + if lib_artifact.respond_to? :sources_artifact + attribs[:sourcepath] = lib_artifact.sources_artifact + end + + if lib_artifact.respond_to? :javadoc_artifact + attribs[:javadocpath] = lib_artifact.javadoc_artifact + end + + # make all paths relative + attribs.each_key do |k| + attribs[k] = attribs[k].to_s.sub(var_value, var_name.to_s) if k.to_s =~ /path/ + end + + @xml.classpathentry attribs + end + end + + private + + # Find a path relative to the project's root directory if possible. If the + # two paths do not share the same root the absolute path is returned. This + # can happen on Windows, for instance, when the two paths are not on the + # same drive. + def relative path + path or raise "Invalid path '#{path.inspect}'" + msg = [:to_path, :to_str, :to_s].find { |msg| path.respond_to? msg } + path = path.__send__(msg) + begin + relative = Util.relative_path(File.expand_path(path), @project.path_to) + if relative['..'] + # paths don't share same root + Util.normalize_path(path) + else + relative + end + rescue ArgumentError + Util.normalize_path(path) + end + end + + def src_from_task task + src_from_absolute_paths task.sources, task.target + end + + def src_from_absolute_paths absolute_paths, output=nil + relative_paths = absolute_paths.map { |src| relative(src) } + relative_paths.sort.uniq.each do |path| + unless @paths_written.include?(path) + attributes = { :kind=>'src', :path=>path, :excluding=>@excludes } + attributes[:output] = relative(output) if output + @xml.classpathentry attributes + @paths_written << path + end + end + end + end + + end + +end # module Buildr + +class Buildr::Project + include Buildr::Eclipse +end + + diff --git a/buildr/lib/buildr/ide/eclipse/java.rb b/buildr/lib/buildr/ide/eclipse/java.rb new file mode 100644 index 0000000..f058a61 --- /dev/null +++ b/buildr/lib/buildr/ide/eclipse/java.rb @@ -0,0 +1,49 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Eclipse + module Java + include Extension + + NATURE = 'org.eclipse.jdt.core.javanature' + CONTAINER = 'org.eclipse.jdt.launching.JRE_CONTAINER' + BUILDER = 'org.eclipse.jdt.core.javabuilder' + + after_define do |project| + eclipse = project.eclipse + + # smart defaults + if project.compile.language == :java || project.test.compile.language == :java + eclipse.natures = NATURE if eclipse.natures.empty? + eclipse.classpath_containers = CONTAINER if eclipse.classpath_containers.empty? + eclipse.builders = BUILDER if eclipse.builders.empty? + end + + # :java nature explicitly set + if eclipse.natures.include? :java + eclipse.natures += [NATURE] unless eclipse.natures.include? NATURE + eclipse.classpath_containers += [CONTAINER] unless eclipse.classpath_containers.include? CONTAINER + eclipse.builders += [BUILDER] unless eclipse.builders.include? BUILDER + end + end + + end + end +end + +class Buildr::Project + include Buildr::Eclipse::Java +end diff --git a/buildr/lib/buildr/ide/eclipse/plugin.rb b/buildr/lib/buildr/ide/eclipse/plugin.rb new file mode 100644 index 0000000..cd1b9d9 --- /dev/null +++ b/buildr/lib/buildr/ide/eclipse/plugin.rb @@ -0,0 +1,67 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Eclipse + module Plugin + include Extension + + NATURE = 'org.eclipse.pde.PluginNature' + CONTAINER = 'org.eclipse.pde.core.requiredPlugins' + BUILDERS = ['org.eclipse.pde.ManifestBuilder', 'org.eclipse.pde.SchemaBuilder'] + + after_define do |project| + eclipse = project.eclipse + + # smart defaults + if eclipse.natures.empty? && ( + (File.exists? project.path_to("plugin.xml")) || + (File.exists? project.path_to("OSGI-INF")) || + (File.exists?(project.path_to("META-INF/MANIFEST.MF")) && File.read(project.path_to("META-INF/MANIFEST.MF")).match(/^Bundle-SymbolicName:/))) + eclipse.natures = [NATURE, Buildr::Eclipse::Java::NATURE] + eclipse.classpath_containers = [CONTAINER, Buildr::Eclipse::Java::CONTAINER] if eclipse.classpath_containers.empty? + eclipse.builders = BUILDERS + [Buildr::Eclipse::Java::BUILDER] if eclipse.builders.empty? + end + + # :plugin nature explicitly set + if eclipse.natures.include? :plugin + unless eclipse.natures.include? NATURE + # plugin nature must be before java nature + eclipse.natures += [Buildr::Eclipse::Java::NATURE] unless eclipse.natures.include? Buildr::Eclipse::Java::NATURE + index = eclipse.natures.index(Buildr::Eclipse::Java::NATURE) || -1 + eclipse.natures = eclipse.natures.insert(index, NATURE) + end + unless eclipse.classpath_containers.include? CONTAINER + # plugin container must be before java container + index = eclipse.classpath_containers.index(Buildr::Eclipse::Java::CONTAINER) || -1 + eclipse.classpath_containers = eclipse.classpath_containers.insert(index, CONTAINER) + end + unless (eclipse.builders.include?(BUILDERS[0]) && eclipse.builders.include?(BUILDERS[1])) + # plugin builder must be before java builder + index = eclipse.classpath_containers.index(Buildr::Eclipse::Java::BUILDER) || -1 + eclipse.builders = eclipse.builders.insert(index, BUILDERS[1]) unless eclipse.builders.include? BUILDERS[1] + index = eclipse.classpath_containers.index(BUILDERS[1]) || -1 + eclipse.builders = eclipse.builders.insert(index, BUILDERS[0]) unless eclipse.builders.include? BUILDERS[0] + end + end + end + + end + end +end + +class Buildr::Project + include Buildr::Eclipse::Plugin +end diff --git a/buildr/lib/buildr/ide/eclipse/scala.rb b/buildr/lib/buildr/ide/eclipse/scala.rb new file mode 100644 index 0000000..555229e --- /dev/null +++ b/buildr/lib/buildr/ide/eclipse/scala.rb @@ -0,0 +1,64 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Eclipse + module Scala + include Extension + + NATURE = 'ch.epfl.lamp.sdt.core.scalanature' + CONTAINER = 'ch.epfl.lamp.sdt.launching.SCALA_CONTAINER' + BUILDER = 'ch.epfl.lamp.sdt.core.scalabuilder' + + after_define :eclipse => :eclipse_scala + after_define :eclipse_scala do |project| + eclipse = project.eclipse + # smart defaults + if eclipse.natures.empty? && (project.compile.language == :scala || project.test.compile.language == :scala) + eclipse.natures = [NATURE, Buildr::Eclipse::Java::NATURE] + eclipse.classpath_containers = [CONTAINER, Buildr::Eclipse::Java::CONTAINER] if eclipse.classpath_containers.empty? + eclipse.builders = BUILDER if eclipse.builders.empty? + eclipse.exclude_libs += Buildr::Scala::Scalac.dependencies + end + + # :scala nature explicitly set + if eclipse.natures.include? :scala + unless eclipse.natures.include? NATURE + # scala nature must be before java nature + eclipse.natures += [Buildr::Eclipse::Java::NATURE] unless eclipse.natures.include? Buildr::Eclipse::Java::NATURE + index = eclipse.natures.index(Buildr::Eclipse::Java::NATURE) || -1 + eclipse.natures = eclipse.natures.insert(index, NATURE) + end + unless eclipse.classpath_containers.include? CONTAINER + # scala container must be before java container + index = eclipse.classpath_containers.index(Buildr::Eclipse::Java::CONTAINER) || -1 + eclipse.classpath_containers = eclipse.classpath_containers.insert(index, CONTAINER) + end + unless eclipse.builders.include? BUILDER + # scala builder overrides java builder + eclipse.builders -= [Buildr::Eclipse::Java::BUILDER] + eclipse.builders += [BUILDER] + end + eclipse.exclude_libs += Buildr::Scala::Scalac.dependencies + end + end + + end + end +end + +class Buildr::Project + include Buildr::Eclipse::Scala +end diff --git a/buildr/lib/buildr/ide/idea.rb b/buildr/lib/buildr/ide/idea.rb new file mode 100644 index 0000000..2dd317a --- /dev/null +++ b/buildr/lib/buildr/ide/idea.rb @@ -0,0 +1,812 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module IntellijIdea + def self.new_document(value) + REXML::Document.new(value, :attribute_quote => :quote) + end + + # Abstract base class for IdeaModule and IdeaProject + class IdeaFile + DEFAULT_SUFFIX = "" + DEFAULT_LOCAL_REPOSITORY_ENV_OVERRIDE = "MAVEN_REPOSITORY" + + attr_reader :buildr_project + attr_writer :suffix + attr_writer :id + attr_accessor :template + attr_accessor :local_repository_env_override + + def initialize + @local_repository_env_override = DEFAULT_LOCAL_REPOSITORY_ENV_OVERRIDE + end + + def suffix + @suffix ||= DEFAULT_SUFFIX + end + + def filename + buildr_project.path_to("#{name}.#{extension}") + end + + def id + @id ||= buildr_project.name.split(':').last + end + + def add_component(name, attrs = {}, &xml) + self.components << create_component(name, attrs, &xml) + end + + # IDEA can not handle text content with indents so need to removing indenting + # Can not pass true as third argument as the ruby library seems broken + def write(f) + document.write(f, -1, false, true) + end + + protected + + def name + "#{self.id}#{suffix}" + end + + def relative(path) + ::Buildr::Util.relative_path(File.expand_path(path.to_s), self.base_directory) + end + + def base_directory + buildr_project.path_to + end + + def resolve_path_from_base(path, base_variable) + m2repo = Buildr::Repositories.instance.local + if path.to_s.index(m2repo) == 0 && !self.local_repository_env_override.nil? + return path.sub(m2repo, "$#{self.local_repository_env_override}$") + else + begin + return "#{base_variable}/#{relative(path)}" + rescue ArgumentError + # ArgumentError happens on windows when self.base_directory and path are on different drives + return path + end + end + end + + def file_path(path) + "file://#{resolve_path(path)}" + end + + def create_component(name, attrs = {}) + target = StringIO.new + Builder::XmlMarkup.new(:target => target, :indent => 2).component(attrs.merge({:name => name})) do |xml| + yield xml if block_given? + end + Buildr::IntellijIdea.new_document(target.string).root + end + + def components + @components ||= self.default_components.compact + end + + def create_composite_component(name, components) + return nil if components.empty? + component = self.create_component(name) + components.each do |element| + element = element.call if element.is_a?(Proc) + component.add_element element + end + component + end + + def add_to_composite_component(components) + components << lambda do + target = StringIO.new + yield Builder::XmlMarkup.new(:target => target, :indent => 2) + Buildr::IntellijIdea.new_document(target.string).root + end + end + + def load_document(filename) + Buildr::IntellijIdea.new_document(File.read(filename)) + end + + def document + if File.exist?(self.filename) + doc = load_document(self.filename) + else + doc = base_document + inject_components(doc, self.initial_components) + end + if self.template + template_doc = load_document(self.template) + REXML::XPath.each(template_doc, "//component") do |element| + inject_component(doc, element) + end + end + inject_components(doc, self.components) + doc + end + + def inject_components(doc, components) + components.each do |component| + # execute deferred components + component = component.call if Proc === component + inject_component(doc, component) if component + end + end + + # replace overridden component (if any) with specified component + def inject_component(doc, component) + doc.root.delete_element("//component[@name='#{component.attributes['name']}']") + doc.root.add_element component + end + end + + # IdeaModule represents an .iml file + class IdeaModule < IdeaFile + DEFAULT_TYPE = "JAVA_MODULE" + + attr_accessor :type + attr_accessor :group + attr_reader :facets + + def initialize + super() + @type = DEFAULT_TYPE + end + + def buildr_project=(buildr_project) + @id = nil + @facets = [] + @skip_content = false + @buildr_project = buildr_project + end + + def extension + "iml" + end + + def main_source_directories + @main_source_directories ||= [ + buildr_project.compile.sources, + buildr_project.resources.sources + ].flatten.compact + end + + def test_source_directories + @test_source_directories ||= [ + buildr_project.test.compile.sources, + buildr_project.test.resources.sources + ].flatten.compact + end + + def excluded_directories + @excluded_directories ||= [ + buildr_project.resources.target, + buildr_project.test.resources.target, + buildr_project.path_to(:target, :main), + buildr_project.path_to(:target, :test), + buildr_project.path_to(:reports) + ].flatten.compact + end + + attr_writer :main_output_dir + + def main_output_dir + @main_output_dir ||= buildr_project._(:target, :main, :idea, :classes) + end + + attr_writer :test_output_dir + + def test_output_dir + @test_output_dir ||= buildr_project._(:target, :test, :idea, :classes) + end + + def main_dependencies + @main_dependencies ||= buildr_project.compile.dependencies + end + + def test_dependencies + @test_dependencies ||= buildr_project.test.compile.dependencies + end + + def add_facet(name, type) + add_to_composite_component(self.facets) do |xml| + xml.facet(:name => name, :type => type) do |xml| + yield xml if block_given? + end + end + end + + def skip_content? + !!@skip_content + end + + def skip_content! + @skip_content = true + end + + + def add_gwt_facet(modules = {}, options = {}) + name = options[:name] || "GWT" + settings = + { + :webFacet => "Web", + :compilerMaxHeapSize => "512", + :compilerParameters => "-draftCompile -localWorkers 2", + :gwtSdkUrl => "file://$GWT_TOOLS$", + :gwtScriptOutputStyle => "PRETTY" + }.merge(options[:settings] || {}) + + add_facet(name, "gwt") do |f| + f.configuration do |c| + settings.each_pair do |k, v| + c.setting :name => k.to_s, :value => v.to_s + end + c.packaging do |d| + modules.each_pair do |k, v| + d.module :name => v, :path => k + end + end + end + end + end + + def add_web_facet(options = {}) + name = options[:name] || "Web" + url_base = options[:url_base] || "/" + webroot = options[:webroot] || buildr_project._(:source, :main, :webapp) + web_xml = options[:web_xml] || "#{webroot}/WEB-INF/web.xml" + version = options[:version] || "3.0" + + add_facet(name, "web") do |f| + f.configuration do |c| + c.descriptors do |d| + d.deploymentDescriptor :name => 'web.xml', :url => file_path(web_xml), :optional => "true", :version => version + end + c.webroots do |w| + w.root :url => file_path(webroot), :relative => url_base + end + end + end + end + + + def add_jruby_facet(options = {}) + name = options[:name] || "JRuby" + jruby_version = options[:jruby_version] || "jruby-1.5.2-p249" + add_facet(name, "JRUBY") do |f| + f.configuration(:number => 0) do |c| + c.JRUBY_FACET_CONFIG_ID :NAME => "JRUBY_SDK_NAME", :VALUE => jruby_version + end + end + end + + def add_jpa_facet(options = {}) + name = options[:name] || "JPA" + factory_entry = options[:factory_entry] || buildr_project.name.to_s + validation_enabled = options[:validation_enabled].nil? ? true : options[:validation_enabled] + provider_enabled = options[:provider_enabled] || 'Hibernate' + persistence_xml = options[:persistence_xml] || buildr_project._(:source, :main, :resources, "META-INF/persistence.xml") + orm_xml = options[:orm_xml] || buildr_project._(:source, :main, :resources, "META-INF/orm.xml") + add_facet(name, "jpa") do |f| + f.configuration do |c| + c.setting :name => "validation-enabled", :value => validation_enabled + c.setting :name => "provider-name", :value => provider_enabled + c.tag!('datasource-mapping') do |ds| + ds.tag!('factory-entry', :name => factory_entry) + end + if File.exist?(persistence_xml) + c.deploymentDescriptor :name => 'persistence.xml', :url => file_path(persistence_xml) + end + if File.exist?(orm_xml) + c.deploymentDescriptor :name => 'orm.xml', :url => file_path(orm_xml) + end + end + end + end + + def add_ejb_facet(options = {}) + name = options[:name] || "EJB" + ejb_xml = options[:ejb_xml] || buildr_project._(:source, :main, :resources, "WEB-INF/ejb-jar.xml") + ejb_roots = options[:ejb_roots] || [buildr_project.packages, buildr_project.compile.target, buildr_project.resources.target].flatten + + add_facet(name, "ejb") do |facet| + facet.configuration do |c| + c.descriptors do |d| + if File.exist?(ejb_xml) + d.deploymentDescriptor :name => 'ejb-jar.xml', :url => ejb_xml + end + end + c.ejbRoots do |e| + ejb_roots.each do |ejb_root| + e.root :url => file_path(ejb_root) + end + end + end + end + end + + protected + + def test_dependency_details + main_dependencies_paths = main_dependencies.map(&:to_s) + target_dir = buildr_project.compile.target.to_s + test_dependencies.select { |d| d.to_s != target_dir }.collect do |d| + dependency_path = d.to_s + export = main_dependencies_paths.include?(dependency_path) + source_path = nil + if d.respond_to?(:to_spec_hash) + source_spec = d.to_spec_hash.merge(:classifier => 'sources') + source_path = Buildr.artifact(source_spec).to_s + source_path = nil unless File.exist?(source_path) + end + [dependency_path, export, source_path] + end + end + + def base_document + target = StringIO.new + Builder::XmlMarkup.new(:target => target).module(:version => "4", :relativePaths => "true", :type => self.type) + Buildr::IntellijIdea.new_document(target.string) + end + + def initial_components + [] + end + + def default_components + [ + lambda { module_root_component }, + lambda { facet_component } + ] + end + + def facet_component + create_composite_component("FacetManager", self.facets) + end + + def module_root_component + create_component("NewModuleRootManager", "inherit-compiler-output" => "false") do |xml| + generate_compile_output(xml) + generate_content(xml) unless skip_content? + generate_initial_order_entries(xml) + project_dependencies = [] + + + self.test_dependency_details.each do |dependency_path, export, source_path| + next unless export + generate_lib(xml, dependency_path, export, source_path, project_dependencies) + end + + self.test_dependency_details.each do |dependency_path, export, source_path| + next if export + generate_lib(xml, dependency_path, export, source_path, project_dependencies) + end + + xml.orderEntryProperties + end + end + + def generate_lib(xml, dependency_path, export, source_path, project_dependencies) + project_for_dependency = Buildr.projects.detect do |project| + [project.packages, project.compile.target, project.resources.target, project.test.compile.target, project.test.resources.target].flatten. + detect { |artifact| artifact.to_s == dependency_path } + end + if project_for_dependency + if project_for_dependency.iml? && + !project_dependencies.include?(project_for_dependency) && + project_for_dependency != self.buildr_project + generate_project_dependency(xml, project_for_dependency.iml.name, export, !export) + end + project_dependencies << project_for_dependency + else + generate_module_lib(xml, url_for_path(dependency_path), export, (source_path ? url_for_path(source_path) : nil), !export) + end + end + + def jar_path(path) + "jar://#{resolve_path(path)}!/" + end + + def url_for_path(path) + if path =~ /jar$/i + jar_path(path) + else + file_path(path) + end + end + + def resolve_path(path) + resolve_path_from_base(path, "$MODULE_DIR$") + end + + def generate_compile_output(xml) + xml.output(:url => file_path(self.main_output_dir.to_s)) + xml.tag!("output-test", :url => file_path(self.test_output_dir.to_s)) + xml.tag!("exclude-output") + end + + def generate_content(xml) + xml.content(:url => "file://$MODULE_DIR$") do + # Source folders + { + :main => self.main_source_directories, + :test => self.test_source_directories + }.each do |kind, directories| + directories.map { |dir| dir.to_s }.compact.sort.uniq.each do |dir| + xml.sourceFolder :url => file_path(dir), :isTestSource => (kind == :test ? 'true' : 'false') + end + end + + # Exclude target directories + self.net_excluded_directories. + collect { |dir| file_path(dir) }. + select { |dir| relative_dir_inside_dir?(dir) }. + sort.each do |dir| + xml.excludeFolder :url => dir + end + end + end + + def relative_dir_inside_dir?(dir) + !dir.include?("../") + end + + def generate_initial_order_entries(xml) + xml.orderEntry :type => "sourceFolder", :forTests => "false" + xml.orderEntry :type => "inheritedJdk" + end + + def generate_project_dependency(xml, other_project, export, test = false) + attribs = {:type => 'module', "module-name" => other_project} + attribs[:exported] = '' if export + attribs[:scope] = 'TEST' if test + xml.orderEntry attribs + end + + def generate_module_lib(xml, path, export, source_path, test = false) + attribs = {:type => 'module-library'} + attribs[:exported] = '' if export + attribs[:scope] = 'TEST' if test + xml.orderEntry attribs do + xml.library do + xml.CLASSES do + xml.root :url => path + end + xml.JAVADOC + xml.SOURCES do + if source_path + xml.root :url => source_path + end + end + end + end + end + + # Don't exclude things that are subdirectories of other excluded things + def net_excluded_directories + net = [] + all = self.excluded_directories.map { |dir| buildr_project._(dir.to_s) }.sort_by { |d| d.size } + all.each_with_index do |dir, i| + unless all[0 ... i].find { |other| dir =~ /^#{other}/ } + net << dir + end + end + net + end + end + + # IdeaModule represents an .ipr file + class IdeaProject < IdeaFile + attr_accessor :vcs + attr_accessor :extra_modules + attr_accessor :artifacts + attr_accessor :configurations + attr_writer :jdk_version + + def initialize(buildr_project) + super() + @buildr_project = buildr_project + @vcs = detect_vcs + @extra_modules = [] + @artifacts = [] + @configurations = [] + end + + def jdk_version + @jdk_version ||= buildr_project.compile.options.source || "1.6" + end + + def add_artifact(name, type, build_on_make = false) + add_to_composite_component(self.artifacts) do |xml| + xml.artifact(:name => name, :type => type, :"build-on-make" => build_on_make) do |xml| + yield xml if block_given? + end + end + end + + def add_configuration(name, type, factory_name, default = false) + add_to_composite_component(self.configurations) do |xml| + xml.configuration(:name => name, :type => type, :factoryName => factory_name, :default => default) do |xml| + yield xml if block_given? + end + end + end + + def add_exploded_war_artifact(project, options = {}) + artifact_name = options[:name] || project.iml.id + build_on_make = options[:build_on_make].nil? ? false : options[:build_on_make] + + add_artifact(artifact_name, "exploded-war", build_on_make) do |xml| + dependencies = (options[:dependencies] || ([project] + project.compile.dependencies)).flatten + libraries, projects = partition_dependencies(dependencies) + + ## The content here can not be indented + output_dir = options[:output_dir] || project._(:artifacts, artifact_name) + xml.tag!('output-path', output_dir) + + xml.root :id => "root" do + xml.element :id => "directory", :name => "WEB-INF" do + xml.element :id => "directory", :name => "classes" do + projects.each do |p| + xml.element :id => "module-output", :name => p.iml.id + end + if options[:enable_jpa] + module_names = options[:jpa_module_names] || [project.iml.id] + module_names.each do |module_name| + facet_name = options[:jpa_facet_name] || "JPA" + xml.element :id => "jpa-descriptors", :facet => "#{module_name}/jpa/#{facet_name}" + end + end + end + xml.element :id => "directory", :name => "lib" do + libraries.each(&:invoke).map(&:to_s).each do |dependency_path| + xml.element :id => "file-copy", :path => resolve_path(dependency_path) + end + end + end + + if options[:enable_war].nil? || options[:enable_war] + module_names = options[:war_module_names] || [project.iml.id] + module_names.each do |module_name| + facet_name = options[:war_facet_name] || "Web" + xml.element :id => "javaee-facet-resources", :facet => "#{module_name}/web/#{facet_name}" + end + end + + if options[:enable_gwt] + module_names = options[:gwt_module_names] || [project.iml.id] + module_names.each do |module_name| + facet_name = options[:gwt_facet_name] || "GWT" + xml.element :id => "gwt-compiler-output", :facet => "#{module_name}/gwt/#{facet_name}" + end + end + end + end + end + + def add_gwt_configuration(launch_page, project, options = {}) + name = options[:name] || "Run #{launch_page}" + shell_parameters = options[:shell_parameters] || "" + vm_parameters = options[:vm_parameters] || "-Xmx512m" + + add_configuration(name, "GWT.ConfigurationType", "GWT Configuration") do |xml| + xml.module(:name => project.iml.id) + xml.option(:name => "RUN_PAGE", :value => launch_page) + xml.option(:name => "SHELL_PARAMETERS", :value => shell_parameters) + xml.option(:name => "VM_PARAMETERS", :value => vm_parameters) + + xml.RunnerSettings(:RunnerId => "Run") + xml.ConfigurationWrapper(:RunnerId => "Run") + xml.method() + end + end + + + protected + + def extension + "ipr" + end + + def detect_vcs + if File.directory?(buildr_project._('.svn')) + "svn" + elsif File.directory?(buildr_project._('.git')) + "Git" + end + end + + def base_document + target = StringIO.new + Builder::XmlMarkup.new(:target => target).project(:version => "4", :relativePaths => "false") + Buildr::IntellijIdea.new_document(target.string) + end + + def default_components + [ + lambda { modules_component }, + vcs_component, + artifacts_component, + configurations_component + ] + end + + def initial_components + [ + lambda { project_root_manager_component }, + lambda { project_details_component } + ] + end + + def project_root_manager_component + attribs = {"version" => "2", + "assert-keyword" => "true", + "jdk-15" => "true", + "project-jdk-name" => self.jdk_version, + "project-jdk-type" => "JavaSDK", + "languageLevel" => "JDK_#{self.jdk_version.gsub('.', '_')}"} + create_component("ProjectRootManager", attribs) do |xml| + xml.output("url" => "file://$PROJECT_DIR$/out") + end + end + + def project_details_component + create_component("ProjectDetails") do |xml| + xml.option("name" => "projectName", "value" => self.name) + end + end + + def modules_component + create_component("ProjectModuleManager") do |xml| + xml.modules do + buildr_project.projects.select { |subp| subp.iml? }.each do |subproject| + module_path = subproject.base_dir.gsub(/^#{buildr_project.base_dir}\//, '') + path = "#{module_path}/#{subproject.iml.name}.iml" + attribs = {:fileurl => "file://$PROJECT_DIR$/#{path}", :filepath => "$PROJECT_DIR$/#{path}"} + if subproject.iml.group == true + attribs[:group] = subproject.parent.name.gsub(':', '/') + elsif !subproject.iml.group.nil? + attribs[:group] = subproject.iml.group.to_s + end + xml.module attribs + end + self.extra_modules.each do |iml_file| + xml.module :fileurl => "file://$PROJECT_DIR$/#{iml_file}", + :filepath => "$PROJECT_DIR$/#{iml_file}" + end + if buildr_project.iml? + xml.module :fileurl => "file://$PROJECT_DIR$/#{buildr_project.iml.name}.iml", + :filepath => "$PROJECT_DIR$/#{buildr_project.iml.name}.iml" + end + end + end + end + + def vcs_component + if vcs + create_component("VcsDirectoryMappings") do |xml| + xml.mapping :directory => "", :vcs => vcs + end + end + end + + def artifacts_component + create_composite_component("ArtifactManager", self.artifacts) + end + + def configurations_component + create_composite_component("ProjectRunConfigurationManager", self.configurations) + end + + def resolve_path(path) + resolve_path_from_base(path, "$PROJECT_DIR$") + end + end + + module ProjectExtension + include Extension + + first_time do + desc "Generate Intellij IDEA artifacts for all projects" + Project.local_task "idea" => "artifacts" + + desc "Delete the generated Intellij IDEA artifacts" + Project.local_task "idea:clean" + end + + before_define do |project| + project.recursive_task("idea") + project.recursive_task("idea:clean") + end + + after_define do |project| + idea = project.task("idea") + + files = [ + (project.iml if project.iml?), + (project.ipr if project.ipr?) + ].compact + + files.each do |ideafile| + module_dir = File.dirname(ideafile.filename) + idea.enhance do |task| + mkdir_p module_dir + info "Writing #{ideafile.filename}" + t = Tempfile.open("buildr-idea") + temp_filename = t.path + t.close! + File.open(temp_filename, "w") do |f| + ideafile.write f + end + mv temp_filename, ideafile.filename + end + end + + project.task("idea:clean") do + files.each do |f| + info "Removing #{f.filename}" if File.exist?(f.filename) + rm_rf f.filename + end + end + end + + def ipr + if ipr? + @ipr ||= IdeaProject.new(self) + else + raise "Only the root project has an IPR" + end + end + + def ipr? + !@no_ipr && self.parent.nil? + end + + def iml + if iml? + unless @iml + inheritable_iml_source = self.parent + while inheritable_iml_source && !inheritable_iml_source.iml? + inheritable_iml_source = inheritable_iml_source.parent; + end + @iml = inheritable_iml_source ? inheritable_iml_source.iml.clone : IdeaModule.new + @iml.buildr_project = self + end + return @iml + else + raise "IML generation is disabled for #{self.name}" + end + end + + def no_ipr + @no_ipr = true + end + + def no_iml + @has_iml = false + end + + def iml? + @has_iml = @has_iml.nil? ? true : @has_iml + end + end + end +end + +class Buildr::Project + include Buildr::IntellijIdea::ProjectExtension +end diff --git a/buildr/lib/buildr/java/ant.rb b/buildr/lib/buildr/java/ant.rb new file mode 100644 index 0000000..970e594 --- /dev/null +++ b/buildr/lib/buildr/java/ant.rb @@ -0,0 +1,90 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +gem 'atoulme-Antwrap' +autoload :Antwrap, 'antwrap' +autoload :Logger, 'logger' + +module Buildr + module Ant + + # Which version of Ant we're using by default. + VERSION = '1.8.3' + + class << self + # Current version of Ant being used. + def version + Buildr.settings.build['ant'] || VERSION + end + + # Ant classpath dependencies. + def dependencies + # Ant-Trax required for running the JUnitReport task, and there's no other place + # to put it but the root classpath. + @dependencies ||= ["org.apache.ant:ant:jar:#{version}", "org.apache.ant:ant-launcher:jar:#{version}"] + end + + private + def const_missing(const) + return super unless const == :REQUIRES # TODO: remove in 1.5 + Buildr.application.deprecated "Please use Ant.dependencies/.version instead of Ant::REQUIRES/VERSION" + dependencies + end + end + + + Java.classpath << lambda { Ant.dependencies } + + # :call-seq: + # ant(name) { |AntProject| ... } => AntProject + # + # Creates a new AntProject with the specified name, yield to the block for defining various + # Ant tasks, and executes each task as it's defined. + # + # For example: + # ant("hibernatedoclet') do |doclet| + # doclet.taskdef :name=>'hibernatedoclet', + # :classname=>'xdoclet.modules.hibernate.HibernateDocletTask', :classpath=>DOCLET + # doclet.hibernatedoclet :destdir=>dest_dir, :force=>'true' do + # hibernate :version=>'3.0' + # fileset :dir=>source, :includes=>'**/*.java' + # end + # end + def ant(name, &block) + options = { :name=>name, :basedir=>Dir.pwd, :declarative=>true } + options.merge!(:logger=> Logger.new(STDOUT), :loglevel=> Logger::DEBUG) if trace?(:ant) + Java.load + Antwrap::AntProject.new(options).tap do |project| + # Set Ant logging level to debug (--trace), info (default) or error only (--quiet). + project.project.getBuildListeners().get(0). + setMessageOutputLevel((trace?(:ant) && 4) || (verbose && 2) || 0) + yield project if block_given? + end + end + + end + + include Ant + class Project + include Ant + end + + Buildr.help do + Java.load + "\nUsing Java #{ENV_JAVA['java.version']}, Ant #{Ant.version}." + end + +end diff --git a/buildr/lib/buildr/java/bdd.rb b/buildr/lib/buildr/java/bdd.rb new file mode 100644 index 0000000..404adcb --- /dev/null +++ b/buildr/lib/buildr/java/bdd.rb @@ -0,0 +1,345 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Mixin for test frameworks using src/spec/{lang} + class TestFramework::JavaBDD < TestFramework::Java #:nodoc: + + class << self + attr_reader :lang, :bdd_dir + end + attr_accessor :lang, :bdd_dir + + def initialize(task, options) + self.bdd_dir = self.class.bdd_dir + project = task.project + project.task('test:compile').tap do |comp| + comp.send :associate_with, project, bdd_dir + self.lang = comp.language || self.class.lang + end + project.task('test:resources').tap do |res| + res.send :associate_with, project, bdd_dir + res.filter.clear + project.path_to(:source, bdd_dir, :resources).tap { |dir| res.from dir if File.exist?(dir) } + end + super + end + + end + + module TestFramework::JRubyBased + extend self + + VERSION = '1.6.2' + + class << self + def version + Buildr.settings.build['jruby'] || VERSION + end + + def jruby_artifact + "org.jruby:jruby-complete:jar:#{version}" + end + + def dependencies + unless @dependencies + @dependencies = [jruby_artifact] + end + @dependencies + end + + def included(mod) + mod.extend ClassMethods + super + end + end + + module ClassMethods + def dependencies + unless @dependencies + super + if !RUBY_PLATFORM[/java/] && !TestFramework::JRubyBased.jruby_installed? + @dependencies |= TestFramework::JRubyBased.dependencies + end + end + @dependencies + end + end + + def run(tests, dependencies) + maybe_install_jruby + dependencies |= [task.compile.target.to_s] + + spec_dir = task.project.path_to(:source, :spec, :ruby) + report_dir = task.report_to.to_s + rm_rf report_dir + mkdir_p report_dir + ENV['CI_REPORTS'] = report_dir + + runner = runner_config + runner.content = runner_content(binding) + + Buildr.write(runner.file, runner.content) + rm_f runner.result + + if RUBY_PLATFORM[/java/] && !options.fork + runtime = new_runtime + runtime.getObject.java.lang.System.getProperties().putAll(options[:properties] || {}) + runtime.getLoadService.require runner.file + else + cmd_options = task.options.only(:properties, :java_args) + cmd_options.update(:classpath => dependencies, :project => task.project) + jruby runner.file, tests, cmd_options rescue nil + end + + fail "Missing result YAML file: #{runner.result}" unless File.exist? runner.result + + result = YAML.load(File.read(runner.result)) + if Exception === result + raise [result.message, result.backtrace].flatten.join("\n") + end + tests - result.failed + end + + def jruby_home + @jruby_home ||= RUBY_PLATFORM =~ /java/ ? Config::CONFIG['prefix'] : + ( ENV['JRUBY_HOME'] || File.expand_path('~/.jruby') ) + end + + def jruby_installed? + !Dir.glob(File.join(jruby_home, 'lib', 'jruby*.jar')).empty? + end + + protected + def maybe_install_jruby + unless jruby_installed? + jruby_artifact = Buildr.artifact(TestFramework::JRubyBased.jruby_artifact) + msg = "JRUBY_HOME is not correctly set or points to an invalid JRuby installation: #{jruby_home}" + say msg + say '' + say "You need to install JRuby version #{jruby_artifact.version} using your system package manager." + + fail msg unless jruby_installed? + end + end + + def jruby(*args) + java_args = ['org.jruby.Main', *args] + java_args << {} unless Hash === args.last + cmd_options = java_args.last + project = cmd_options.delete(:project) + cmd_options[:classpath] ||= [] + if jruby_home && jruby_home != '' + Dir.glob(File.join(jruby_home, 'lib', '*.jar')) { |jar| cmd_options[:classpath] << jar } + cmd_options[:properties]['jruby.home'] = jruby_home + end + cmd_options[:java_args] ||= [] + cmd_options[:java_args] << '-Xmx512m' unless cmd_options[:java_args].detect {|a| a =~ /^-Xmx/} + cmd_options[:properties] ||= {} + Java::Commands.java(*java_args) + end + + def new_runtime(cfg = {}) + config = Java.org.jruby.RubyInstanceConfig.new + cfg.each_pair do |name, value| + config.send("#{name}=", value) + end + yield config if block_given? + Java.org.jruby.Ruby.newInstance config + end + + def jruby_gem + %{ + require 'jruby' + def JRuby.gem(name, version = '>0', *args) + require 'rbconfig' + jruby_home = Config::CONFIG['prefix'] + expected_version = '#{TestFramework::JRubyBased.version}' + unless JRUBY_VERSION >= expected_version + fail "Expected JRuby version \#{expected_version} installed at \#{jruby_home} but got \#{JRUBY_VERSION}" + end + require 'rubygems' + begin + Kernel.send :gem, name, version + rescue LoadError, Gem::LoadError => e + require 'rubygems/gem_runner' + args = ['install', name, '--version', version] + args + Gem::GemRunner.new.run(args) + Kernel.send :gem, name, version + end + end + } + end + + def runner_config(runner = OpenStruct.new) + [:requires, :gems, :output, :format].each do |key| + runner.send("#{key}=", options[key]) + end + runner.html_report ||= File.join(task.report_to.to_s, 'report.html') + runner.result ||= task.project.path_to(:target, :spec, 'result.yaml') + runner.file ||= task.project.path_to(:target, :spec, 'runner.rb') + runner.requires ||= [] + runner.requires.unshift File.join(File.dirname(__FILE__), 'test_result') + runner.gems ||= {} + runner.rspec ||= ['--format', 'progress', '--format', "html:#{runner.html_report}"] + runner.format.each { |format| runner.rspec << '--format' << format } if runner.format + runner.rspec.push '--format', "Buildr::TestFramework::TestResult::YamlFormatter" + runner.rspec.push '-o', runner.result + runner + end + + end + + # RSpec is the defacto BDD framework for ruby. + # To test your project with RSpec use: + # test.using :rspec + # + # + # Support the following options: + # * :gems -- A hash of gems to install before running the tests. + # The keys of this hash are the gem name, the value must be the required version. + # * :requires -- A list of ruby files to require before running the specs + # Mainly used if an rspec format needs to require some file. + # * :format -- A list of valid Rspec --format option values. (defaults to 'progress') + # * :output -- File path to output dump. @false@ to suppress output + # * :fork -- Create a new JavaVM to run the tests on + # * :properties -- Hash of properties passed to the test suite. + # * :java_args -- Arguments passed to the JVM. + class RSpec < TestFramework::JavaBDD + @lang = :ruby + @bdd_dir = :spec + + include TestFramework::JRubyBased + + TESTS_PATTERN = [ /_spec.rb$/ ] + OPTIONS = [:properties, :java_args] + + def self.applies_to?(project) #:nodoc: + !Dir[project.path_to(:source, bdd_dir, lang, '**/*_spec.rb')].empty? + end + + def tests(dependencies) #:nodoc: + Dir[task.project.path_to(:source, bdd_dir, lang, '**/*_spec.rb')].select do |name| + selector = ENV['SPEC'] + selector.nil? || Regexp.new(selector) === name + end + end + + def runner_config + runner = super + runner.gems.update 'rspec' => '~> 2.1.0' + runner.requires.unshift 'rspec' + runner + end + + def runner_content(binding) + runner_erb = %q{ + <%= jruby_gem %> + <%= dependencies.inspect %>.each { |dep| $CLASSPATH << dep } + <%= runner.gems.inspect %>.each { |ary| JRuby.gem(*ary.flatten) } + <%= runner.requires.inspect %>.each { |rb| Kernel.require rb } + <% if runner.output == false %> + output = StringIO.new + <% elsif runner.output.kind_of?(String) %> + output = File.open(<%= result.output.inspect %>, 'w') + <% else %> + output = STDOUT + <% end %> + parser = ::RSpec::Core::Parser.new + argv = <%= runner.rspec.inspect %> || [] + argv.push *<%= tests.inspect %> + + Buildr::TestFramework::TestResult::Error.guard('<%= runner.result %>') do + ::RSpec::Core::CommandLine.new(argv).run(output, output) + end + exit 0 # let buildr figure the result from the yaml file + } + Filter::Mapper.new(:erb, binding).transform(runner_erb) + end + + end + + # JBehave is a Java BDD framework. To use in your project: + # test.using :jbehave + # + # This framework will search in your project for: + # src/spec/java/**/*Behaviour.java + # + # JMock libraries are included on runtime. + # + # Support the following options: + # * :properties -- Hash of properties to the test suite + # * :java_args -- Arguments passed to the JVM + class JBehave < TestFramework::JavaBDD + @lang = :java + @bdd_dir = :spec + + VERSION = '1.0.1' + TESTS_PATTERN = [ /Behaviou?r$/ ] #:nodoc: + + class << self + def version + Buildr.settings.build['jbehave'] || VERSION + end + + def dependencies + unless @dependencies + super + @dependencies |= ["org.jbehave:jbehave:jar:#{version}", 'cglib:cglib-full:jar:2.0.2'] + + JMock.dependencies + JUnit.dependencies + end + @dependencies + end + + def applies_to?(project) #:nodoc: + %w{ + **/*Behaviour.java **/*Behavior.java + }.any? { |glob| !Dir[project.path_to(:source, bdd_dir, lang, glob)].empty? } + end + + private + def const_missing(const) + return super unless const == :REQUIRES # TODO: remove in 1.5 + Buildr.application.deprecated 'Please use JBehave.dependencies/.version instead of JBehave::REQUIRES/VERSION' + dependencies + end + end + + def tests(dependencies) #:nodoc: + filter_classes(dependencies, :class_names => TESTS_PATTERN, + :interfaces => %w{ org.jbehave.core.behaviour.Behaviours }) + end + + def run(tests, dependencies) #:nodoc: + cmd_args = ['org.jbehave.core.BehaviourRunner'] + cmd_options = { :properties=>options[:properties], :java_args=>options[:java_args], :classpath=>dependencies } + tests.inject([]) do |passed, test| + begin + Java::Commands.java cmd_args, test, cmd_options + passed << test + rescue + passed + end + end + end + + end + +end + +Buildr::TestFramework << Buildr::RSpec +Buildr::TestFramework << Buildr::JBehave + diff --git a/buildr/lib/buildr/java/cobertura.rb b/buildr/lib/buildr/java/cobertura.rb new file mode 100644 index 0000000..6544550 --- /dev/null +++ b/buildr/lib/buildr/java/cobertura.rb @@ -0,0 +1,303 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Provides the cobertura:html, cobertura:xml and cobertura:check tasks. + # Require explicitly using require "buildr/java/cobertura". + # + # You can generate cobertura reports for a single project + # using the project name as prefix: + # + # project_name:cobertura:html + # + # You can also specify which classes to include/exclude from instrumentation by + # passing a class name regexp to the cobertura.include or + # cobertura.exclude methods. + # + # define 'someModule' do + # cobertura.include 'some.package.*' + # cobertura.include /some.(foo|bar).*/ + # cobertura.exclude 'some.foo.util.SimpleUtil' + # cobertura.exclude /*.Const(ants)?/i + # end + # + # You can also specify the top level directory to which the top level cobertura tasks + # will generate reports by setting the value of the Buildr::Cobertura.report_dir + # configuration parameter. + # + module Cobertura + + VERSION = '1.9.4.1' + + class << self + def version + Buildr.settings.build['cobertura'] || VERSION + end + end + + REQUIRES = ArtifactNamespace.for(self).tap do |ns| + ns.cobertura! "net.sourceforge.cobertura:cobertura:jar:#{version}", '>=1.9' + ns.log4j! 'log4j:log4j:jar:1.2.9', ">=1.2.9" + ns.asm! 'asm:asm:jar:2.2.1', '>=2.2.1' + ns.asm_tree! 'asm:asm-tree:jar:2.2.1', '>=2.2.1' + ns.oro! 'oro:oro:jar:2.0.8', '>=2.0.8' + end + + class << self + def dependencies + if (VersionRequirement.create('>=1.9.1').satisfied_by?(REQUIRES.cobertura.version)) + [:asm, :asm_tree].each { |s| REQUIRES[s] = '3.0' unless REQUIRES[s].selected? } + end + + REQUIRES.artifacts + end + + attr_writer :report_dir + + def report_dir + @report_dir || "reports/cobertura" + end + + def report_to(file = nil) + File.expand_path(File.join(*[report_dir, file.to_s].compact)) + end + + def data_file + File.expand_path("#{report_dir}.ser") + end + + end + + class CoberturaConfig # :nodoc: + + def initialize(project) + @project = project + end + + attr_reader :project + private :project + + attr_writer :data_file, :instrumented_dir, :report_dir + + def data_file + @data_file ||= project.path_to(:reports, 'cobertura.ser') + end + + def instrumented_dir + @instrumented_dir ||= project.path_to(:target, :instrumented, :classes) + end + + def report_dir + @report_dir ||= project.path_to(:reports, :cobertura) + end + + def report_to(file = nil) + File.expand_path(File.join(*[report_dir, file.to_s].compact)) + end + + # :call-seq: + # project.cobertura.include(*classPatterns) + # + def include(*classPatterns) + includes.push(*classPatterns.map { |p| String === p ? Regexp.new(p) : p }) + self + end + + def includes + @includeClasses ||= [] + end + + # :call-seq: + # project.cobertura.exclude(*classPatterns) + # + def exclude(*classPatterns) + excludes.push(*classPatterns.map { |p| String === p ? Regexp.new(p) : p }) + self + end + + def excludes + @excludeClasses ||= [] + end + + def ignore(*regexps) + ignores.push(*regexps) + end + + def ignores + @ignores ||= [] + end + + def sources + project.compile.sources + end + + def check + @check ||= CoberturaCheck.new + end + end + + class CoberturaCheck + attr_writer :branch_rate, :line_rate, :total_branch_rate, :total_line_rate, :package_line_rate, :package_branch_rate + attr_reader :branch_rate, :line_rate, :total_branch_rate, :total_line_rate, :package_line_rate, :package_branch_rate + end + + module CoberturaExtension # :nodoc: + include Buildr::Extension + + def cobertura + @cobertura_config ||= CoberturaConfig.new(self) + end + + after_define do |project| + cobertura = project.cobertura + + namespace 'cobertura' do + unless project.compile.target.nil? + # Instrumented bytecode goes in a different directory. This task creates before running the test + # cases and monitors for changes in the generate bytecode. + instrumented = project.file(cobertura.instrumented_dir => project.compile.target) do |task| + mkdir_p task.to_s + unless project.compile.sources.empty? + info "Instrumenting classes with cobertura data file #{cobertura.data_file}" + Buildr.ant "cobertura" do |ant| + ant.taskdef :resource=>"tasks.properties", + :classpath=>Buildr.artifacts(Cobertura.dependencies).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR) + ant.send "cobertura-instrument", :todir=>task.to_s, :datafile=>cobertura.data_file do + includes, excludes = cobertura.includes, cobertura.excludes + + classes_dir = project.compile.target.to_s + if includes.empty? && excludes.empty? + ant.fileset :dir => classes_dir do + ant.include :name => "**/*.class" + end + else + includes = [//] if includes.empty? + Dir.glob(File.join(classes_dir, "**/*.class")) do |cls| + cls_name = cls.gsub(/#{classes_dir}\/?|\.class$/, '').gsub('/', '.') + if includes.any? { |p| p === cls_name } && !excludes.any? { |p| p === cls_name } + ant.fileset :file => cls + end + end + end + + cobertura.ignores.each { |r| ant.ignore :regex => r } + end + end + end + touch task.to_s + end + + task 'instrument' => instrumented + + # We now have two target directories with bytecode. It would make sense to remove compile.target + # and add instrumented instead, but apparently Cobertura only creates some of the classes, so + # we need both directories and instrumented must come first. + project.test.dependencies.unshift cobertura.instrumented_dir + project.test.with Cobertura.dependencies + project.test.options[:properties]["net.sourceforge.cobertura.datafile"] = cobertura.data_file + + unless project.compile.sources.empty? + [:xml, :html].each do |format| + task format => ['instrument', 'test'] do + info "Creating test coverage reports in #{cobertura.report_to(format)}" + Buildr.ant "cobertura" do |ant| + ant.taskdef :resource=>"tasks.properties", + :classpath=>Buildr.artifacts(Cobertura.dependencies).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR) + ant.send "cobertura-report", :format=>format, + :destdir=>cobertura.report_to(format), :datafile=>cobertura.data_file do + cobertura.sources.flatten.each do |src| + ant.fileset(:dir=>src.to_s) if File.exist?(src.to_s) + end + end + end + end + end + end + + task :check => [:instrument, :test] do + Buildr.ant "cobertura" do |ant| + ant.taskdef :classpath=>Cobertura.dependencies.join(File::PATH_SEPARATOR), :resource=>"tasks.properties" + params = { :datafile => Cobertura.data_file } + + # oh so ugly... + params[:branchrate] = cobertura.check.branch_rate if cobertura.check.branch_rate + params[:linerate] = cobertura.check.line_rate if cobertura.check.line_rate + params[:totalbranchrate] = cobertura.check.total_branch_rate if cobertura.check.total_branch_rate + params[:totallinerate] = cobertura.check.total_line_rate if cobertura.check.total_line_rate + params[:packagebranchrate] = cobertura.check.package_branch_rate if cobertura.check.package_branch_rate + params[:packagelinerate] = cobertura.check.package_line_rate if cobertura.check.package_line_rate + + ant.send("cobertura-check", params) do + end + end + end + + end + end + + project.clean do + rm_rf [cobertura.report_to, cobertura.data_file, cobertura.instrumented_dir] + end + + end + + end + + class Buildr::Project + include CoberturaExtension + end + + namespace "cobertura" do + + task "instrument" do + Buildr.projects.each do |project| + project.cobertura.data_file = data_file + project.test.options[:properties]["net.sourceforge.cobertura.datafile"] = data_file + instrument_task ="#{project.name}:cobertura:instrument" + task(instrument_task).invoke if Rake::Task.task_defined?(instrument_task) + end + end + + [:xml, :html].each do |format| + desc "Run the test cases and produce code coverage reports" + task format => ["instrument", "test"] do + report_target = report_to(format) + if Buildr.projects.detect { |project| !project.compile.sources.empty? } + info "Creating test coverage reports in #{report_target}" + Buildr.ant "cobertura" do |ant| + ant.taskdef :resource=>"tasks.properties", + :classpath=>Buildr.artifacts(Cobertura.dependencies).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR) + ant.send "cobertura-report", :destdir=>report_target, :format=>format, :datafile=>data_file do + Buildr.projects.map(&:cobertura).map(&:sources).flatten.each do |src| + ant.fileset :dir=>src.to_s if File.exist?(src.to_s) + end + end + end + end + end + end + + task "clean" do + rm_rf [report_to, data_file] + end + end + + task "clean" do + task("cobertura:clean").invoke if Dir.pwd == Rake.application.original_dir + end + + end +end diff --git a/buildr/lib/buildr/java/commands.rb b/buildr/lib/buildr/java/commands.rb new file mode 100644 index 0000000..0de1572 --- /dev/null +++ b/buildr/lib/buildr/java/commands.rb @@ -0,0 +1,249 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +# Base module for all things Java. +module Java + + # JDK commands: java, javac, javadoc, etc. + module Commands + + class << self + + # :call-seq: + # java(class, *args, options?) + # + # Runs Java with the specified arguments. + # + # Each argument should be provided as separate array value, e.g. + # + # java("-jar", "yuicompressor-2.4.2.jar", "--type","css", + # "src/main/webapp/styles/styles-all.css", + # "-o", "src/main/webapp/styles/styles-all-min.css") + # + # The last argument may be a Hash with additional options: + # * :classpath -- One or more file names, tasks or artifact specifications. + # These are all expanded into artifacts, and all tasks are invoked. + # * :java_args -- Any additional arguments to pass (e.g. -hotspot, -xms) + # * :properties -- Hash of system properties (e.g. 'path'=>base_dir). + # * :name -- Shows this name, otherwise shows the first argument (the class name). + # * :verbose -- If true, prints the command and all its argument. + def java(*args, &block) + options = Hash === args.last ? args.pop : {} + options[:verbose] ||= trace?(:java) + rake_check_options options, :classpath, :java_args, :properties, :name, :verbose + + name = options[:name] + if name.nil? + name = "java #{args.first}" + end + + cmd_args = [path_to_bin('java')] + cp = classpath_from(options) + cmd_args << '-classpath' << cp.join(File::PATH_SEPARATOR) unless cp.empty? + options[:properties].each { |k, v| cmd_args << "-D#{k}=#{v}" } if options[:properties] + cmd_args += (options[:java_args] || (ENV['JAVA_OPTS'] || ENV['JAVA_OPTIONS']).to_s.split).flatten + cmd_args += args.flatten.compact + + tmp = nil + begin + # Windows can't handle cmd lines greater than 2048/8192 chars. + # If our cmd line is longer, we create a batch file and execute it instead. + if Util.win_os? && cmd_args.map(&:inspect).join(' ').size > 2048 + # remove '-classpath' and the classpath itself from the cmd line. + cp_i = cmd_args.index{|x| x.starts_with('-classpath')} + 2.times do + cmd_args.delete_at cp_i unless cp_i.nil? + end + # create tmp batch file. + tmp = Tempfile.new(['starter', '.bat']) + tmp.write "@echo off\n" + tmp.write "SET CLASSPATH=#{cp.join(File::PATH_SEPARATOR).gsub(%r{/}, '\\')}\n" + tmp.write cmd_args.map(&:inspect).join(' ') + tmp.close + # set new cmd line. + cmd_args = [tmp.path] + end + + unless Buildr.application.options.dryrun + info "Running #{name}" if name && options[:verbose] + block = lambda { |ok, res| fail "Failed to execute #{name}, see errors above" unless ok } unless block + cmd_args = cmd_args.map(&:inspect).join(' ') if Util.win_os? + sh(*cmd_args) do |ok, ps| + block.call ok, ps + end + end + ensure + unless tmp.nil? + tmp.close + tmp.unlink + end + end + end + + # :call-seq: + # apt(*files, options) + # + # Runs Apt with the specified arguments. + # + # The last argument may be a Hash with additional options: + # * :compile -- If true, compile source files to class files. + # * :source -- Specifies source compatibility with a given JVM release. + # * :output -- Directory where to place the generated source files, or the + # generated class files when compiling. + # * :classpath -- One or more file names, tasks or artifact specifications. + # These are all expanded into artifacts, and all tasks are invoked. + def apt(*args) + options = Hash === args.last ? args.pop : {} + rake_check_options options, :compile, :source, :output, :classpath + + files = args.flatten.map(&:to_s). + collect { |arg| File.directory?(arg) ? FileList["#{arg}/**/*.java"] : arg }.flatten + cmd_args = [ trace?(:apt) ? '-verbose' : '-nowarn' ] + if options[:compile] + cmd_args << '-d' << options[:output].to_s + else + cmd_args << '-nocompile' << '-s' << options[:output].to_s + end + cmd_args << '-source' << options[:source] if options[:source] + cp = classpath_from(options) + cmd_args << '-classpath' << cp.join(File::PATH_SEPARATOR) unless cp.empty? + cmd_args += files + unless Buildr.application.options.dryrun + info 'Running apt' + trace (['apt'] + cmd_args).join(' ') + Java.load + ::Java::com.sun.tools.apt.Main.process(cmd_args.to_java(::Java::java.lang.String)) == 0 or + fail 'Failed to process annotations, see errors above' + end + end + + # :call-seq: + # javac(*files, options) + # + # Runs Javac with the specified arguments. + # + # The last argument may be a Hash with additional options: + # * :output -- Target directory for all compiled class files. + # * :classpath -- One or more file names, tasks or artifact specifications. + # These are all expanded into artifacts, and all tasks are invoked. + # * :sourcepath -- Additional source paths to use. + # * :javac_args -- Any additional arguments to pass (e.g. -extdirs, -encoding) + # * :name -- Shows this name, otherwise shows the working directory. + def javac(*args) + options = Hash === args.last ? args.pop : {} + rake_check_options options, :classpath, :sourcepath, :output, :javac_args, :name + + files = args.flatten.each { |f| f.invoke if f.respond_to?(:invoke) }.map(&:to_s). + collect { |arg| File.directory?(arg) ? FileList["#{File.expand_path(arg)}/**/*.java"] : File.expand_path(arg) }.flatten + name = options[:name] || Dir.pwd + + cmd_args = [] + cp = classpath_from(options) + cmd_args << '-classpath' << cp.join(File::PATH_SEPARATOR) unless cp.empty? + cmd_args << '-sourcepath' << [options[:sourcepath]].flatten.join(File::PATH_SEPARATOR) if options[:sourcepath] + cmd_args << '-d' << File.expand_path(options[:output].to_s) if options[:output] + cmd_args += options[:javac_args].flatten if options[:javac_args] + cmd_args += files + unless Buildr.application.options.dryrun + mkdir_p options[:output] if options[:output] + info "Compiling #{files.size} source files in #{name}" + trace (['javac'] + cmd_args).join(' ') + Java.load + ::Java::com.sun.tools.javac.Main.compile(cmd_args.to_java(::Java::java.lang.String)) == 0 or + fail 'Failed to compile, see errors above' + end + end + + # :call-seq: + # javadoc(*files, options) + # + # Runs Javadocs with the specified files and options. + # + # This method accepts the following special options: + # * :output -- The output directory + # * :classpath -- Array of classpath dependencies. + # * :sourcepath -- Array of sourcepaths (paths or tasks). + # * :name -- Shows this name, otherwise shows the working directory. + # + # All other options are passed to Javadoc as following: + # * true -- As is, for example, :author=>true becomes -author + # * false -- Prefixed, for example, :index=>false becomes -noindex + # * string -- Option with value, for example, :windowtitle=>'My project' becomes -windowtitle "My project" + # * array -- Option with set of values separated by spaces. + def javadoc(*args) + options = Hash === args.last ? args.pop : {} + fail "No output defined for javadoc" if options[:output].nil? + options[:output] = File.expand_path(options[:output].to_s) + cmd_args = [ '-d', options[:output], trace?(:javadoc) ? '-verbose' : '-quiet' ] + options.reject { |key, value| [:output, :name, :sourcepath, :classpath].include?(key) }. + each { |key, value| value.invoke if value.respond_to?(:invoke) }. + each do |key, value| + case value + when true, nil + cmd_args << "-#{key}" + when false + cmd_args << "-no#{key}" + when Hash + value.each { |k,v| cmd_args << "-#{key}" << k.to_s << v.to_s } + else + cmd_args += Array(value).map { |item| ["-#{key}", item.to_s] }.flatten + end + end + [:sourcepath, :classpath].each do |option| + options[option].to_a.flatten.tap do |paths| + cmd_args << "-#{option}" << paths.flatten.map(&:to_s).join(File::PATH_SEPARATOR) unless paths.empty? + end + end + files = args.each {|arg| arg.invoke if arg.respond_to?(:invoke)}.collect {|arg| arg.is_a?(Project) ? arg.compile.sources.collect{|dir| Dir["#{File.expand_path(dir.to_s)}/**/*.java"]} : File.expand_path(arg.to_s) } + cmd_args += files.flatten.uniq.map(&:to_s) + name = options[:name] || Dir.pwd + unless Buildr.application.options.dryrun + info "Generating Javadoc for #{name}" + trace (['javadoc'] + cmd_args).join(' ') + Java.load + ::Java::com.sun.tools.javadoc.Main.execute(cmd_args.to_java(::Java::java.lang.String)) == 0 or + fail 'Failed to generate Javadocs, see errors above' + end + end + + protected + + # :call-seq: + # path_to_bin(cmd?) => path + # + # Returns the path to the specified Java command (with no argument to java itself). + def path_to_bin(name = nil) + home = ENV['JAVA_HOME'] or fail 'Are we forgetting something? JAVA_HOME not set.' + bin = Util.normalize_path(File.join(home, 'bin')) + fail 'JAVA_HOME environment variable does not point to a valid JRE/JDK installation.' unless File.exist? bin + Util.normalize_path(File.join(bin, name.to_s)) + end + + # :call-seq: + # classpath_from(options) => files + # + # Extracts the classpath from the options, expands it by calling artifacts, invokes + # each of the artifacts and returns an array of paths. + def classpath_from(options) + Buildr.artifacts(options[:classpath] || []).map(&:to_s). + map { |t| task(t).invoke; File.expand_path(t) } + end + + end + + end + +end + diff --git a/buildr/lib/buildr/java/compiler.rb b/buildr/lib/buildr/java/compiler.rb new file mode 100644 index 0000000..bc711ae --- /dev/null +++ b/buildr/lib/buildr/java/compiler.rb @@ -0,0 +1,128 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Compiler + + # Javac compiler: + # compile.using(:javac) + # Used by default if .java files are found in the src/main/java directory (or src/test/java) + # and sets the target directory to target/classes (or target/test/classes). + # + # Accepts the following options: + # * :warnings -- Issue warnings when compiling. True when running in verbose mode. + # * :debug -- Generates bytecode with debugging information. Set from the debug + # environment variable/global option. + # * :deprecation -- If true, shows deprecation messages. False by default. + # * :source -- Source code compatibility. + # * :target -- Bytecode compatibility. + # * :lint -- Lint option is one of true, false (default), name (e.g. 'cast') or array. + # * :other -- Array of options passed to the compiler + # (e.g. ['-implicit:none', '-encoding', 'iso-8859-1']) + class Javac < Base + + OPTIONS = [:warnings, :debug, :deprecation, :source, :target, :lint, :other] + + specify :language=>:java, :target=>'classes', :target_ext=>'class', :packaging=>:jar + + def initialize(project, options) #:nodoc: + super + options[:debug] = Buildr.options.debug if options[:debug].nil? + options[:warnings] ||= false + options[:deprecation] ||= false + options[:lint] ||= false + end + + def compile(sources, target, dependencies) #:nodoc: + check_options options, OPTIONS + cmd_args = [] + # tools.jar contains the Java compiler. + dependencies << Java.tools_jar if Java.tools_jar + cmd_args << '-classpath' << dependencies.join(File::PATH_SEPARATOR) unless dependencies.empty? + source_paths = sources.select { |source| File.directory?(source) } + cmd_args << '-sourcepath' << source_paths.join(File::PATH_SEPARATOR) unless source_paths.empty? + cmd_args << '-d' << File.expand_path(target) + cmd_args += javac_args + cmd_args += files_from_sources(sources) + unless Buildr.application.options.dryrun + trace((['javac'] + cmd_args).join(' ')) + Java.load + Java.com.sun.tools.javac.Main.compile(cmd_args.to_java(Java.java.lang.String)) == 0 or + fail 'Failed to compile, see errors above' + end + end + + private + + def javac_args #:nodoc: + args = [] + args << '-nowarn' unless options[:warnings] + args << '-verbose' if trace? :javac + args << '-g' if options[:debug] + args << '-deprecation' if options[:deprecation] + args << '-source' << options[:source].to_s if options[:source] + args << '-target' << options[:target].to_s if options[:target] + case options[:lint] + when Array then args << "-Xlint:#{options[:lint].join(',')}" + when String then args << "-Xlint:#{options[:lint]}" + when true then args << '-Xlint' + end + args + Array(options[:other]) + end + + end + + end + + + # Methods added to Project to support the Java Annotation Processor. + module Apt + + # :call-seq: + # apt(*sources) => task + # + # Returns a task that will use Java#apt to generate source files in target/generated/apt, + # from all the source directories passed as arguments. Uses the compile.sources list if + # on arguments supplied. + # + # For example: + # + def apt(*sources) + sources = compile.sources if sources.empty? + file(path_to(:target, 'generated/apt')=>sources) do |task| + cmd_args = [ trace?(:apt) ? '-verbose' : '-nowarn' ] + cmd_args << '-nocompile' << '-s' << task.name + cmd_args << '-source' << compile.options.source if compile.options.source + classpath = Buildr.artifacts(compile.dependencies).map(&:to_s).each { |t| task(t).invoke } + cmd_args << '-classpath' << classpath.join(File::PATH_SEPARATOR) unless classpath.empty? + cmd_args += (sources.map(&:to_s) - [task.name]). + map { |file| File.directory?(file) ? FileList["#{file}/**/*.java"] : file }.flatten + unless Buildr.application.options.dryrun + info 'Running apt' + trace (['apt'] + cmd_args).join(' ') + Java.com.sun.tools.apt.Main.process(cmd_args.to_java(Java.java.lang.String)) == 0 or + fail 'Failed to process annotations, see errors above' + end + end + end + + end + +end + +Buildr::Compiler << Buildr::Compiler::Javac +class Buildr::Project + include Buildr::Apt +end diff --git a/buildr/lib/buildr/java/deprecated.rb b/buildr/lib/buildr/java/deprecated.rb new file mode 100644 index 0000000..6f00dd0 --- /dev/null +++ b/buildr/lib/buildr/java/deprecated.rb @@ -0,0 +1,137 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Java + + # *Deprecated:* In earlier versions, Java.wrapper served as a wrapper around RJB/JRuby. + # From this version forward, we apply with JRuby style for importing Java classes: + # Java.java.lang.String.new('hai!') + # You still need to call Java.load before using any Java code: it resolves, downloads + # and installs various dependencies that are required on the classpath before calling + # any Java code (e.g. Ant and its tasks). + class JavaWrapper + + include Singleton + + # *Deprecated:* Append to Java.classpath directly. + def classpath + Buildr.application.deprecated 'Append to Java.classpath instead.' + ::Java.classpath + end + + def classpath=(paths) + fail 'Deprecated: Append to Java.classpath, you cannot replace the classpath.' + end + + # *Deprecated:* No longer necessary. + def setup + Buildr.application.deprecated 'See documentation for new way to access Java code.' + yield self if block_given? + end + + # *Deprecated:* Use Java.load instead. + def load + Buildr.application.deprecated 'Use Java.load instead.' + ::Java.load + end + + alias :onload :setup + + # *Deprecated:* Use Java.pkg.pkg.ClassName to import a Java class. + def import(class_name) + Buildr.application.deprecated 'Use Java.pkg.pkg.ClassName to import a Java class.' + ::Java.instance_eval(class_name) + end + end + + + class << self + + # *Deprecated*: Use Java::Commands.java instead. + def java(*args, &block) + return send(:method_missing, :java) if args.empty? + Buildr.application.deprecated 'Use Java::Commands.java instead.' + Commands.java(*args, &block) + end + + # *Deprecated*: Use Java::Commands.apt instead. + def apt(*args) + Buildr.application.deprecated 'Use Java::Commands.apt instead.' + Commands.apt(*args) + end + + # *Deprecated*: Use Java::Commands.javac instead. + def javac(*args) + Buildr.application.deprecated 'Use Java::Commands.javac instead.' + Commands.javac(*args) + end + + # *Deprecated*: Use Java::Commands.javadoc instead. + def javadoc(*args) + Buildr.application.deprecated 'Use Java::Commands.javadoc instead.' + Commands.javadoc(*args) + end + + # *Deprecated:* Use ENV_JAVA['java.version'] instead. + def version + Buildr.application.deprecated 'Use ENV_JAVA[\'java.version\'] instead.' + Java.load + ENV_JAVA['java.version'] + end + + # *Deprecated:* Use ENV['JAVA_HOME'] instead + def home + Buildr.application.deprecated 'Use ENV[\'JAVA_HOME\'] instead.' + ENV['JAVA_HOME'] + end + + # *Deprecated:* In earlier versions, Java.wrapper served as a wrapper around RJB/JRuby. + # From this version forward, we apply with JRuby style for importing Java classes: + # Java.java.lang.String.new('hai!') + # You still need to call Java.load before using any Java code: it resolves, downloads + # and installs various dependencies that are required on the classpath before calling + # any Java code (e.g. Ant and its tasks). + def wrapper + Buildr.application.deprecated 'See documentation for new way to access Java code.' + if block_given? + Java.load + yield JavaWrapper.instance + else + JavaWrapper.instance + end + end + + alias :rjb :wrapper + + end + + + class Options + + # *Deprecated:* Use ENV['JAVA_OPTS'] instead. + def java_args + Buildr.application.deprecated "Use ENV['JAVA_OPTS'] instead" + (ENV["JAVA_OPTS"] || ENV["JAVA_OPTIONS"]).to_s.split + end + + # *Deprecated:* Use ENV['JAVA_OPTS'] instead. + def java_args=(args) + Buildr.application.deprecated "Use ENV['JAVA_OPTS'] instead" + ENV['JAVA_OPTS'] = Array(args).join(' ') + end + + end + +end diff --git a/buildr/lib/buildr/java/doc.rb b/buildr/lib/buildr/java/doc.rb new file mode 100644 index 0000000..c00bbe6 --- /dev/null +++ b/buildr/lib/buildr/java/doc.rb @@ -0,0 +1,84 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Doc + + module JavadocDefaults + include Extension + + # Default javadoc -windowtitle to project's comment or name + after_define(:javadoc => :doc) do |project| + if project.doc.engine? Javadoc + options = project.doc.options + options[:windowtitle] = (project.comment || project.name) unless options[:windowtitle] + end + end + end + + # A convenient task for creating Javadocs from the project's compile task. Minimizes all + # the hard work to calling #from and #using. + # + # For example: + # doc.from(projects('myapp:foo', 'myapp:bar')).using(:windowtitle=>'My App') + # Or, short and sweet: + # desc 'My App' + # define 'myapp' do + # . . . + # doc projects('myapp:foo', 'myapp:bar') + # end + class Javadoc < Base + + specify :language => :java, :source_ext => 'java' + + def generate(sources, target, options = {}) + cmd_args = [ '-d', target, trace?(:javadoc) ? '-verbose' : '-quiet' ] + options.reject { |key, value| [:sourcepath, :classpath].include?(key) }. + each { |key, value| value.invoke if value.respond_to?(:invoke) }. + each do |key, value| + case value + when true, nil + cmd_args << "-#{key}" + when false + cmd_args << "-no#{key}" + when Hash + value.each { |k,v| cmd_args << "-#{key}" << k.to_s << v.to_s } + else + cmd_args += Array(value).map { |item| ["-#{key}", item.to_s] }.flatten + end + end + [:sourcepath, :classpath].each do |option| + Array(options[option]).flatten.tap do |paths| + cmd_args << "-#{option}" << paths.flatten.map(&:to_s).join(File::PATH_SEPARATOR) unless paths.empty? + end + end + cmd_args += sources.flatten.uniq + unless Buildr.application.options.dryrun + info "Generating Javadoc for #{project.name}" + trace (['javadoc'] + cmd_args).join(' ') + Java.load + Java.com.sun.tools.javadoc.Main.execute(cmd_args.to_java(Java.java.lang.String)) == 0 or + fail 'Failed to generate Javadocs, see errors above' + end + end + end + end + + class Project + include JavadocDefaults + end +end + +Buildr::Doc.engines << Buildr::Doc::Javadoc diff --git a/buildr/lib/buildr/java/ecj.rb b/buildr/lib/buildr/java/ecj.rb new file mode 100644 index 0000000..a9f7b1c --- /dev/null +++ b/buildr/lib/buildr/java/ecj.rb @@ -0,0 +1,69 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Compiler + + class Ecj < Javac + + OPTIONS = Buildr::Compiler::Javac::OPTIONS + + specify :language=>:java, :sources => 'java', :source_ext => 'java', + :target=>'classes', :target_ext=>'class', :packaging=>:jar + + def compile(sources, target, dependencies) #:nodoc: + check_options options, OPTIONS + cmd_args = [] + # tools.jar contains the Java compiler. + dependencies << Java.tools_jar if Java.tools_jar + cmd_args << '-classpath' << ('"' + dependencies.join(File::PATH_SEPARATOR) + '"') unless dependencies.empty? + source_paths = sources.select { |source| File.directory?(source) } + cmd_args << '-sourcepath' << source_paths.join(File::PATH_SEPARATOR) unless source_paths.empty? + cmd_args << '-d' << File.expand_path(target) + cmd_args += ecj_args + cmd_args += files_from_sources(sources) + unless Buildr.application.options.dryrun + trace((%w[javac -classpath org.eclipse.jdt.internal.compiler.batch.Main] + cmd_args).join(' ')) + Java.load + Java.org.eclipse.jdt.internal.compiler.batch.Main.compile(cmd_args.join(" ")) or + fail 'Failed to compile, see errors above' + end + end + + private + + # See arg list here: http://publib.boulder.ibm.com/infocenter/rsahelp/v7r0m0/index.jsp?topic=/org.eclipse.jdt.doc.isv/guide/jdt_api_compile.htm + def ecj_args #:nodoc: + args = [] + args << '-warn:none' unless options[:warnings] + args << '-verbose' if trace?(:ecj) + args << '-g' if options[:debug] + args << '-deprecation' if options[:deprecation] + args << '-source' << options[:source].to_s if options[:source] + args << '-target' << options[:target].to_s if options[:target] + case options[:lint] + when Array then args << "-Xlint:#{options[:lint].join(',')}" + when String then args << "-Xlint:#{options[:lint]}" + when true then args << '-Xlint' + end + args + Array(options[:other]) + end + end + end +end + +Java.classpath << "org.eclipse.jdt.core.compiler:ecj:jar:3.5.1" +# Adding ecj before javac +Buildr::Compiler.compilers.unshift Buildr::Compiler::Ecj \ No newline at end of file diff --git a/buildr/lib/buildr/java/emma.rb b/buildr/lib/buildr/java/emma.rb new file mode 100644 index 0000000..9f8820f --- /dev/null +++ b/buildr/lib/buildr/java/emma.rb @@ -0,0 +1,240 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Provides the emma:html and emma:xml tasks. + # Require explicitly using require "buildr/emma". + # + # You can generate emma reports for a single project + # using the project name as prefix: + # + # project_name:emma:html + # + # You can also specify which classes to include/exclude from instrumentation by + # passing a class name regexp to the emma.include or + # emma.exclude methods. + # + # define 'someModule' do + # emma.include 'some.package.*' + # emma.exclude 'some.foo.util.SimpleUtil' + # end + module Emma + + VERSION = '2.0.5312' + + class << self + + def version + Buildr.settings.build['emma'] || VERSION + end + + def dependencies + @dependencies ||= ["emma:emma_ant:jar:#{version}", "emma:emma:jar:#{version}"] + end + + def report_to format=nil + File.expand_path('reports/emma') + end + + def data_file() + File.join(report_to, 'coverage.es') + end + + def ant + + Buildr.ant 'emma' do |ant| + ant.taskdef :resource=>'emma_ant.properties', + :classpath=>Buildr.artifacts(dependencies).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR) + ant.emma :verbosity=>(trace?(:emma) ? 'verbose' : 'warning') do + yield ant + end + end + end + end + + class EmmaConfig # :nodoc: + + def initialize(project) + @project = project + end + + attr_reader :project + private :project + + attr_writer :metadata_file, :coverage_file, :instrumented_dir, :report_dir + + def coverage_file + @coverage_file ||= File.join(report_dir, 'coverage.ec') + end + + def metadata_file + @metadata_file ||= File.join(report_dir, 'coverage.em') + end + + def instrumented_dir + @instrumented_dir ||= project.path_to(:target, :instrumented, :classes) + end + + def report_dir + @report_dir ||= project.path_to(:reports, :emma) + end + + def report_to format + report_dir + end + + # :call-seq: + # project.emma.include(*classPatterns) + # + def include(*classPatterns) + includes.push(*classPatterns) + self + end + + def includes + @includeClasses ||= [] + end + + # :call-seq: + # project.emma.exclude(*classPatterns) + # + def exclude(*classPatterns) + excludes.push(*classPatterns) + self + end + + def excludes + @excludeClasses ||= [] + end + + def sources + project.compile.sources + end + end + + module EmmaExtension # :nodoc: + include Buildr::Extension + + def emma + @emma_config ||= EmmaConfig.new(self) + end + + after_define do |project| + emma = project.emma + + namespace 'emma' do + unless project.compile.target.nil? + # Instrumented bytecode goes in a different directory. This task creates before running the test + # cases and monitors for changes in the generate bytecode. + instrumented = project.file(emma.instrumented_dir => project.compile.target) do |task| + unless project.compile.sources.empty? + info "Instrumenting classes with emma metadata file #{emma.metadata_file}" + Emma.ant do |ant| + ant.instr :instrpath=>project.compile.target.to_s, :destdir=>task.to_s, :metadatafile=>emma.metadata_file do + ant.filter :includes=>emma.includes.join(', ') unless emma.includes.empty? + ant.filter :excludes=>emma.excludes.join(', ') unless emma.excludes.empty? + end + end + touch task.to_s + end + end + + task 'instrument' => instrumented + + # We now have two target directories with bytecode. + project.test.dependencies.unshift emma.instrumented_dir + project.test.with Emma.dependencies + project.test.options[:properties]["emma.coverage.out.file"] = emma.coverage_file + + [:xml, :html].each do |format| + task format => ['instrument', 'test'] do + missing_required_files = [emma.metadata_file, emma.coverage_file].reject { |f| File.exist?(f) } + if missing_required_files.empty? + info "Creating test coverage reports in #{emma.report_dir}" + mkdir_p emma.report_dir + Emma.ant do |ant| + ant.report do + ant.infileset :file=>emma.metadata_file + ant.infileset :file=>emma.coverage_file + ant.send format, :outfile=>File.join(emma.report_to(format),"coverage.#{format}") + ant.sourcepath do + emma.sources.flatten.each do |src| + ant.dirset(:dir=>src.to_s) if File.exist?(src.to_s) + end + end + end + end + else + info "No test coverage report for #{project}. Missing: #{missing_required_files.join(', ')}" + end + end + end + end + end + + project.clean do + rm_rf [emma.report_dir, emma.coverage_file, emma.metadata_file, emma.instrumented_dir] + end + + end + + end + + class Buildr::Project + include EmmaExtension + end + + namespace "emma" do + + Project.local_task('instrument') { |name| "Instrumenting #{name}" } + + [:xml, :html].each do |format| + desc "Run the test cases and produce code coverage reports in #{format}" + task format => ['instrument', 'test'] do + info "Creating test coverage reports in #{format}" + mkdir_p report_to(format) + Emma.ant do |ant| + ant.merge :outfile=>data_file do + Buildr.projects.each do |project| + [project.emma.metadata_file, project.emma.coverage_file].each do |data_file| + ant.fileset :file=>data_file if File.exist?(data_file) + end + end + end + ant.report do + ant.infileset :file=>data_file + ant.send format, :outfile=>File.join(report_to(format), "coverage.#{format}") + ant.sourcepath do + Buildr.projects.map(&:emma).map(&:sources).flatten.map(&:to_s).each do |src| + ant.dirset :dir=>src if File.exist?(src) + end + end + end + end + end + end + + task :clean do + rm_rf [report_to, data_file] + end + end + + task :clean do + task('emma:clean').invoke if Dir.pwd == Rake.application.original_dir + end + + end +end diff --git a/buildr/lib/buildr/java/external.rb b/buildr/lib/buildr/java/external.rb new file mode 100644 index 0000000..b188eaf --- /dev/null +++ b/buildr/lib/buildr/java/external.rb @@ -0,0 +1,73 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Compiler + class ExternalJavac< Buildr::Compiler::Javac + + OPTIONS = [:jvm, :warnings, :debug, :deprecation, :source, :target, :lint, :other] + + specify :language=>:java, :sources => 'java', :source_ext => 'java', + :target=>'classes', :target_ext=>'class', :packaging=>:jar + + + def compile(sources, target, dependencies) #:nodoc: + check_options options, OPTIONS + cmd_args = [] + # tools.jar contains the Java compiler. + dependencies << Java.tools_jar if Java.tools_jar + cmd_args << '-classpath' << dependencies.join(File::PATH_SEPARATOR) unless dependencies.empty? + source_paths = sources.select { |source| File.directory?(source) } + cmd_args << '-sourcepath' << source_paths.join(File::PATH_SEPARATOR) unless source_paths.empty? + cmd_args << '-d' << File.expand_path(target) + cmd_args += externaljavac_args + Tempfile.open("external") {|tmp| + tmp.write files_from_sources(sources).join(' ') + cmd_args << "@#{tmp.path}" + } + unless Buildr.application.options.dryrun + javac_path = File.join(options[:jvm] || ENV['JAVA_HOME'], "bin", "javac") + final_args = cmd_args.insert(0, javac_path).push('2>&1').join(' ') + trace(final_args) + info %x[#{final_args}] + fail 'Failed to compile, see errors above' unless $?.success? + end + end + + private + + # See arg list here: http://publib.boulder.ibm.com/infocenter/rsahelp/v7r0m0/index.jsp?topic=/org.eclipse.jdt.doc.isv/guide/jdt_api_compile.htm + def externaljavac_args #:nodoc: + args = [] + args << '-nowarn' unless options[:warnings] + args << '-verbose' if trace?(:javac) + args << '-g' if options[:debug] + args << '-deprecation' if options[:deprecation] + args << '-source' << options[:source].to_s if options[:source] + args << '-target' << options[:target].to_s if options[:target] + case options[:lint] + when Array then args << "-Xlint:#{options[:lint].join(',')}" + when String then args << "-Xlint:#{options[:lint]}" + when true then args << '-Xlint' + end + args + Array(options[:other]) + end + + end + + end +end + +Buildr::Compiler.compilers << Buildr::Compiler::ExternalJavac \ No newline at end of file diff --git a/buildr/lib/buildr/java/jruby.rb b/buildr/lib/buildr/java/jruby.rb new file mode 100644 index 0000000..609865f --- /dev/null +++ b/buildr/lib/buildr/java/jruby.rb @@ -0,0 +1,120 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require 'java' +require 'jruby' + +# Buildr runs along side a JVM, using either RJB or JRuby. The Java module allows +# you to access Java classes and create Java objects. +# +# Java classes are accessed as static methods on the Java module, for example: +# str = Java.java.lang.String.new('hai!') +# str.toUpperCase +# => 'HAI!' +# Java.java.lang.String.isInstance(str) +# => true +# Java.com.sun.tools.javac.Main.compile(args) +# +# The classpath attribute allows Buildr to add JARs and directories to the classpath, +# for example, we use it to load Ant and various Ant tasks, code generators, test +# frameworks, and so forth. +# +# When using an artifact specification, Buildr will automatically download and +# install the artifact before adding it to the classpath. +# +# For example, Ant is loaded as follows: +# Java.classpath << 'org.apache.ant:ant:jar:1.7.0' +# +# Artifacts can only be downloaded after the Buildfile has loaded, giving it +# a chance to specify which remote repositories to use, so adding to classpath +# does not by itself load any libraries. You must call Java.load before accessing +# any Java classes to give Buildr a chance to load the libraries specified in the +# classpath. +# +# When building an extension, make sure to follow these rules: +# 1. Add to the classpath when the extension is loaded (i.e. in module or class +# definition), so the first call to Java.load anywhere in the code will include +# the libraries you specify. +# 2. Call Java.load once before accessing any Java classes, allowing Buildr to +# set up the classpath. +# 3. Only call Java.load when invoked, otherwise you may end up loading the JVM +# with a partial classpath, or before all remote repositories are listed. +# 4. Check on a clean build with empty local repository. +module Java + + # Since we already have a JVM loaded, we can use it to guess where JAVA_HOME is. + # We set JAVA_HOME early so we can use it without calling Java.load first. + ENV['JAVA_HOME'] ||= java.lang.System.getProperty("java.home") + + # This version is the minimal version Buildr will support. + # Any older version of JRuby will raise an exception. + JRUBY_MIN_VERSION = '1.5.1' + raise "JRuby must be at least at version #{JRUBY_MIN_VERSION}" unless JRUBY_VERSION >= JRUBY_MIN_VERSION + + class << self + + # Returns the classpath, an array listing directories, JAR files and + # artifacts. Use when loading the extension to add any additional + # libraries used by that extension. + # + # For example, Ant is loaded as follows: + # Java.classpath << 'org.apache.ant:ant:jar:1.7.0' + def classpath + @classpath ||= [] + end + + # Most platforms requires tools.jar to be on the classpath, tools.jar contains the + # Java compiler (OS X and AIX are two exceptions we know about, may be more). + # Guess where tools.jar is from JAVA_HOME, which hopefully points to the JDK, + # but maybe the JRE. Return nil if not found. + def tools_jar #:nodoc: + @tools_jar ||= ['lib/tools.jar', '../lib/tools.jar'].map { |path| File.expand_path(path, ENV['JAVA_HOME']) }. + find { |path| File.exist?(path) } + end + + # Loads the JVM and all the libraries listed on the classpath. Call this + # method before accessing any Java class, but only call it from methods + # used in the build, giving the Buildfile a chance to load all extensions + # that append to the classpath and specify which remote repositories to use. + def load + return self if @loaded + + # Adding jars to the jruby's $CLASSPATH should be the correct thing, however it + # seems like some tools require their jars on system class loader (javadoc, junit, etc) + # cp.each { |path| $CLASSPATH << path } + + # Use system ClassLoader to add classpath + sysloader = java.lang.ClassLoader.getSystemClassLoader + add_url_method = java.lang.Class.forName('java.net.URLClassLoader'). + getDeclaredMethod('addURL', [java.net.URL.java_class].to_java(java.lang.Class)) + add_url_method.setAccessible(true) + add_path = lambda { |path| add_url_method.invoke(sysloader, [java.io.File.new(path).toURI.toURL].to_java(java.net.URL)) } + + # Most platforms requires tools.jar to be on the classpath. + add_path[tools_jar] if tools_jar + + classpath.map! { |path| Proc === path ? path.call : path } + Buildr.artifacts(classpath).map(&:to_s).each do |path| + file(path).invoke + add_path[path] + end + + @loaded = true + self + end + + end + +end diff --git a/buildr/lib/buildr/java/org/apache/buildr/JavaTestFilter.class b/buildr/lib/buildr/java/org/apache/buildr/JavaTestFilter.class new file mode 100644 index 0000000000000000000000000000000000000000..2ed852482100ba0f569d673312cdb398b57336fa GIT binary patch literal 3552 zcma)9?{^f{8Ga_an@NTtV1Pi7^nWH+p&QfmSc1OmZiNhJh>KX9@;Nd`AFVRj;k zqEbv-wZCh%4XxVRAE|9x(aJ`Q#-7u2I6Z!8|A6*OJ?*Fd0sUn9ytA{LT}X>(&%XE0 zz3+YA`@Zk<-aGU0r|&NT*o6-?>_$?>F%1<+R^u-8i}1uJXwvWaJ~vpSK%2A&*FlBeMQAZ zX`fRuqvERy)YBQqNSm&L&An%gCyYeKutyUIGDbGr>rZqlxMke*#+>wi+jcy|vm86C zpf;~P=~rjBpn+qCJgpi0lNV#G-lf7Itm zK3VrkJcUBrlTMU=no-A%rY+Z}#dP$X<&8zXF*9m2@lhS$!FNS`PsjK1QUNa5RtfQR z`~WZOcm+R{&HYHn7jd_WSsj<~V;w)it2$o8>neV#<7aq7$InIl0>4!8D;;y{os z*|V}+A6&<8(4*tGBIfZs6~EW<7A~uJTgN+iNyi@q`3QOn?)#1ojJeJ^A)ktObzDJC z!H%P4{OO3Eutg#2IZ-L8Xi91!s-Q_m(k3Z3P98@+vV;VW8pBq`@>r(}I^L7mU&ZT% zRF=e6$NN}Rz=-~T`j#y{HhhM$=c?;`PdZ+=Gij$wk{N22O50RQ+L8)fKWcgh4BN4- zl#$8vaD8)2p5Os~vWm<8oPuiN6wdI*)nWW;qLiF_1q=QZy(5U+YElYkCyflZls}X6+?>vOCLIZx zF5|YTSUvr`=sdF2G{%WuJZnpai98`n8Jls-T2In|>N)#e*OMe{K zE8WOSw{oSc#+E)rRbQy9DYo$GyQI%KdtCP3?~rBW7vuF9J|Qbm-*hepDV+}sp0=V90)wWA0dtc z+ha?68)qt(UqeHzu|3)PE-LwNXv^VqIW(S}TmEP3Wvb=3QRfHiF36i+4GrJQkZ)ta z3nmUy@6cy35xJR(Jv6bGCc0?iUYgifW}^EumNZ?7W~Z)&e6`W*qt-{+P;<_h&1x`@*( zWe{f>?2@r^lr3DDLrZ{_L+kAFzryoA;r|kvFY@XPV+0IfBMDrGK{VkcN&Fz0HAKpu zVxAsGFCO9VQHD*d;$mm zVgFNP8TO;NKPZB?m{}?R(V+7CVugqtp)Ub7#QhP;`QJLi@1U(v z(K}hu{#0#kf?d{i4tLgvKZ@tD>qE3B 0;) { + String name = names[i]; + interfaces.add(_loader.loadClass(name)); + } + return this; + } + + public JavaTestFilter addClassAnnotations(String[] names) throws ClassNotFoundException { + for (int i = names.length; i -- > 0;) { + String name = names[i]; + classAnnotations.add(_loader.loadClass(name)); + } + return this; + } + + public JavaTestFilter addMethodAnnotations(String[] names) throws ClassNotFoundException { + for (int i = names.length; i -- > 0;) { + String name = names[i]; + methodAnnotations.add(_loader.loadClass(name)); + } + return this; + } + + public JavaTestFilter addFields(String[] names) { + for (int i = names.length; i -- > 0;) { + String name = names[i]; + fieldNames.add(name); + } + return this; + } + + private boolean isTest(Class cls) { + if (Modifier.isAbstract(cls.getModifiers()) || !Modifier.isPublic(cls.getModifiers())) + return false; + if (interfaces != null) { + for (Iterator it = interfaces.iterator(); it.hasNext(); ) { + Class iface = (Class) it.next(); + if (iface.isAssignableFrom(cls)) { return true; } + } + } + if (classAnnotations != null) { + for (Iterator it = classAnnotations.iterator(); it.hasNext(); ) { + Class annotation = (Class) it.next(); + if (cls.isAnnotationPresent(annotation)) { return true; } + } + } + if (methodAnnotations != null) { + Method[] methods = cls.getMethods(); + for (int j = methods.length ; j-- > 0 ;) { + for (Iterator it = methodAnnotations.iterator(); it.hasNext(); ) { + Class annotation = (Class) it.next(); + if (methods[j].isAnnotationPresent(annotation)) { return true; } + } + } + } + if (fieldNames != null) { + Field[] fields = cls.getFields(); + for (int j = 0; j < fields.length; j++) { + for (Iterator it = fieldNames.iterator(); it.hasNext(); ) { + if (fields[j].getName().equals(it.next())) { return true; } + } + } + } + return false; + } + + + public String[] filter(String[] names) throws Throwable { + Vector testCases = new Vector(); + for (int i = names.length ; i-- > 0 ;) { + try { + Class cls = _loader.loadClass(names[i]); + if (isTest(cls)) { testCases.add(names[i]); } + } catch (Throwable e) { + System.err.println("JavaTestFilter: Unable to load class "+names[i]+" to determine testing ability"); + throw e; + } + } + String[] result = new String[testCases.size()]; + testCases.toArray(result); + return result; + } + +} + +/* + * Local Variables: + * indent-tabs-mode: nil + * c-basic-offset: 2 + * End: + */ diff --git a/buildr/lib/buildr/java/packaging.rb b/buildr/lib/buildr/java/packaging.rb new file mode 100644 index 0000000..1d714d6 --- /dev/null +++ b/buildr/lib/buildr/java/packaging.rb @@ -0,0 +1,730 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Packaging #:nodoc: + + # Adds packaging for Java projects: JAR, WAR, AAR, EAR, Javadoc. + module Java + + class Manifest + + STANDARD_HEADER = { 'Manifest-Version'=>'1.0', 'Created-By'=>'Buildr' } + LINE_SEPARATOR = /\r\n|\n|\r[^\n]/ #:nodoc: + SECTION_SEPARATOR = /(#{LINE_SEPARATOR}){2}/ #:nodoc: + + class << self + + # :call-seq: + # parse(str) => manifest + # + # Parse a string in MANIFEST.MF format and return a new Manifest. + def parse(str) + sections = str.split(SECTION_SEPARATOR).reject { |s| s.strip.empty? } + new sections.map { |section| + lines = section.split(LINE_SEPARATOR).inject([]) { |merged, line| + if line[/^ /] == ' ' + merged.last << line[1..-1] + else + merged << line + end + merged + } + lines.map { |line| line.scan(/(.*?):\s*(.*)/).first }. + inject({}) { |map, (key, value)| map.merge(key=>value) } + } + end + + # :call-seq: + # from_zip(file) => manifest + # + # Parse the MANIFEST.MF entry of a ZIP (or JAR) file and return a new Manifest. + def from_zip(file) + Zip::ZipInputStream::open(file.to_s) do |zip| + while (entry = zip.get_next_entry) + if entry.name == 'META-INF/MANIFEST.MF' + return Manifest.parse zip.read + end + end + end + Manifest.new + end + + # :call-seq: + # update_manifest(file) { |manifest| ... } + # + # Updates the MANIFEST.MF entry of a ZIP (or JAR) file. Reads the MANIFEST.MF, + # yields to the block with the Manifest object, and writes the modified object + # back to the file. + def update_manifest(file) + manifest = from_zip(file) + result = yield manifest + Zip::ZipFile.open(file.to_s) do |zip| + zip.get_output_stream('META-INF/MANIFEST.MF') do |out| + out.write manifest.to_s + out.write "\n" + end + end + result + end + + end + + # Returns a new Manifest object based on the argument: + # * nil -- Empty Manifest. + # * Hash -- Manifest with main section using the hash name/value pairs. + # * Array -- Manifest with one section from each entry (must be hashes). + # * String -- Parse (see Manifest#parse). + # * Proc/Method -- New Manifest from result of calling proc/method. + def initialize(arg = nil) + case arg + when nil, Hash then @sections = [arg || {}] + when Array then @sections = arg + when String then @sections = Manifest.parse(arg).sections + when Proc, Method then @sections = Manifest.new(arg.call).sections + else + fail 'Invalid manifest, expecting Hash, Array, file name/task or proc/method.' + end + # Add Manifest-Version and Created-By, if not specified. + STANDARD_HEADER.each do |name, value| + sections.first[name] ||= value + end + end + + # The sections of this manifest. + attr_reader :sections + + # The main (first) section of this manifest. + def main + sections.first + end + + include Enumerable + + # Iterate over each section and yield to block. + def each(&block) + @sections.each(&block) + end + + # Convert to MANIFEST.MF format. + def to_s + @sections.map { |section| + keys = section.keys + keys.unshift('Name') if keys.delete('Name') + lines = keys.map { |key| manifest_wrap_at_72("#{key}: #{section[key]}") } + lines + [''] + }.flatten.join("\n") + end + + private + + def manifest_wrap_at_72(line) + return [line] if line.size < 72 + [ line[0..70] ] + manifest_wrap_at_72(' ' + line[71..-1]) + end + + end + + + # Adds support for MANIFEST.MF and other META-INF files. + module WithManifest #:nodoc: + + class << self + def included(base) + base.class_eval do + alias :initialize_without_manifest :initialize + alias :initialize :initialize_with_manifest + end + end + + end + + # Specifies how to create the manifest file. + attr_accessor :manifest + + # Specifies files to include in the META-INF directory. + attr_accessor :meta_inf + + def initialize_with_manifest(*args) #:nodoc: + initialize_without_manifest *args + @manifest = false + @meta_inf = [] + @dependencies = FileList[] + + prepare do + @prerequisites << manifest if String === manifest || Rake::Task === manifest + [meta_inf].flatten.map { |file| file.to_s }.uniq.each { |file| path('META-INF').include file } + end + + enhance do + if manifest + # Tempfiles gets deleted on garbage collection, so we're going to hold on to it + # through instance variable not closure variable. + @manifest_tmp = Tempfile.new('MANIFEST.MF') + File.chmod 0644, @manifest_tmp.path + self.manifest = File.read(manifest.to_s) if String === manifest || Rake::Task === manifest + self.manifest = Manifest.new(manifest) unless Manifest === manifest + #@manifest_tmp.write Manifest::STANDARD_HEADER + @manifest_tmp.write manifest.to_s + @manifest_tmp.write "\n" + @manifest_tmp.close + path('META-INF').include @manifest_tmp.path, :as=>'MANIFEST.MF' + end + end + end + + end + + class ::Buildr::ZipTask + include WithManifest + end + + + # Extends the ZipTask to create a JAR file. + # + # This task supports two additional attributes: manifest and meta-inf. + # + # The manifest attribute specifies how to create the MANIFEST.MF file. + # * A hash of manifest properties (name/value pairs). + # * An array of hashes, one for each section of the manifest. + # * A string providing the name of an existing manifest file. + # * A file task can be used the same way. + # * Proc or method called to return the contents of the manifest file. + # * False to not generate a manifest file. + # + # The meta-inf attribute lists one or more files that should be copied into + # the META-INF directory. + # + # For example: + # package(:jar).with(:manifest=>'src/MANIFEST.MF') + # package(:jar).meta_inf << file('README') + class JarTask < ZipTask + + def initialize(*args) #:nodoc: + super + end + + # :call-seq: + # with(options) => self + # + # Additional + # Pass options to the task. Returns self. ZipTask itself does not support any options, + # but other tasks (e.g. JarTask, WarTask) do. + # + # For example: + # package(:jar).with(:manifest=>'MANIFEST_MF') + def with(*args) + super args.pop if Hash === args.last + fail "package.with() should not contain nil values" if args.include? nil + include :from=>args if args.size > 0 + self + end + + end + + + # Extends the JarTask to create a WAR file. + # + # Supports all the same options as JarTask, in additon to these two options: + # * :libs -- An array of files, tasks, artifact specifications, etc that will be added + # to the WEB-INF/lib directory. + # * :classes -- A directory containing class files for inclusion in the WEB-INF/classes + # directory. + # + # For example: + # package(:war).with(:libs=>'log4j:log4j:jar:1.1') + class WarTask < JarTask + + # Directories with class files to include under WEB-INF/classes. + attr_accessor :classes + + # Artifacts to include under WEB-INF/libs. + attr_accessor :libs + + def initialize(*args) #:nodoc: + super + @classes = [] + @libs = [] + enhance do |war| + @libs.each {|lib| lib.invoke if lib.respond_to?(:invoke) } + @classes.to_a.flatten.each { |classes| include classes, :as => 'WEB-INF/classes' } + path('WEB-INF/lib').include Buildr.artifacts(@libs) unless @libs.nil? || @libs.empty? + end + end + + def libs=(value) #:nodoc: + @libs = Buildr.artifacts(value) + end + + def classes=(value) #:nodoc: + @classes = [value].flatten.map { |dir| file(dir.to_s) } + end + + end + + + # Extends the JarTask to create an AAR file (Axis2 service archive). + # + # Supports all the same options as JarTask, with the addition of :wsdls, :services_xml and :libs. + # + # * :wsdls -- WSDL files to include (under META-INF). By default packaging will include all WSDL + # files found under src/main/axis2. + # * :services_xml -- Location of services.xml file (included under META-INF). By default packaging + # takes this from src/main/axis2/services.xml. Use a different path if you genereate the services.xml + # file as part of the build. + # * :libs -- Array of files, tasks, artifact specifications, etc that will be added to the /lib directory. + # + # For example: + # package(:aar).with(:libs=>'log4j:log4j:jar:1.1') + # + # filter.from('src/main/axis2').into('target').include('services.xml', '*.wsdl').using('http_port'=>'8080') + # package(:aar).wsdls.clear + # package(:aar).with(:services_xml=>_('target/services.xml'), :wsdls=>_('target/*.wsdl')) + class AarTask < JarTask + # Artifacts to include under /lib. + attr_accessor :libs + # WSDLs to include under META-INF (defaults to all WSDLs under src/main/axis2). + attr_accessor :wsdls + # Location of services.xml file (defaults to src/main/axis2/services.xml). + attr_accessor :services_xml + + def initialize(*args) #:nodoc: + super + @libs = [] + @wsdls = [] + prepare do + path('META-INF').include @wsdls + path('META-INF').include @services_xml, :as=>'services.xml' if @services_xml + path('lib').include Buildr.artifacts(@libs) unless @libs.nil? || @libs.empty? + end + end + + def libs=(value) #:nodoc: + @libs = Buildr.artifacts(value) + end + + def wsdls=(value) #:nodoc: + @wsdls |= Array(value) + end + end + + + # Extend the JarTask to create an EAR file. + # + # The following component types are supported by the EARTask: + # + # * :war -- A J2EE Web Application + # * :ejb -- An Enterprise Java Bean + # * :jar -- A J2EE Application Client.[1] + # * :lib -- An ear scoped shared library[2] (for things like logging, + # spring, etc) common to the ear components + # + # The EarTask uses the "Mechanism 2: Bundled Optional Classes" as described on [2]. + # All specified libraries are added to the EAR archive and the Class-Path manifiest entry is + # modified for each EAR component. Special care is taken with WebApplications, as they can + # contain libraries on their WEB-INF/lib directory, libraries already included in a war file + # are not referenced by the Class-Path entry of the war in order to avoid class collisions + # + # EarTask supports all the same options as JarTask, in additon to these two options: + # + # * :display_name -- The displayname to for this ear on application.xml + # + # * :map -- A Hash used to map component type to paths within the EAR. + # By default each component type is mapped to a directory with the same name, + # for example, EJBs are stored in the /ejb path. To customize: + # package(:ear).map[:war] = 'web-applications' + # package(:ear).map[:lib] = nil # store shared libraries on root of archive + # + # EAR components are added by means of the EarTask#add, EarTask#<<, EarTask#push methods + # Component type is determined from the artifact's type. + # + # package(:ear) << project('coolWebService').package(:war) + # + # The << method is just an alias for push, with the later you can add multiple components + # at the same time. For example.. + # + # package(:ear).push 'org.springframework:spring:jar:2.6', + # projects('reflectUtils', 'springUtils'), + # project('coolerWebService').package(:war) + # + # The add method takes a single component with an optional hash. You can use it to override + # some component attributes. + # + # You can override the component type for a particular artifact. The following example + # shows how you can tell the EarTask to treat a JAR file as an EJB: + # + # # will add an ejb entry for the-cool-ejb-2.5.jar in application.xml + # package(:ear).add 'org.coolguys:the-cool-ejb:jar:2.5', :type=>:ejb + # # A better syntax for this is: + # package(:ear).add :ejb=>'org.coolguys:the-cool-ejb:jar:2.5' + # + # By default, every JAR package is assumed to be a library component, so you need to specify + # the type when including an EJB (:ejb) or Application Client JAR (:jar). + # + # For WebApplications (:war)s, you can customize the context-root that appears in application.xml. + # The following example also specifies a different directory inside the EAR where to store the webapp. + # + # package(:ear).add project(:remoteService).package(:war), + # :path=>'web-services', :context_root=>'/Some/URL/Path' + # + # [1] http://java.sun.com/j2ee/sdk_1.2.1/techdocs/guides/ejb/html/Overview5.html#10106 + # [2] http://java.sun.com/j2ee/verified/packaging.html + class EarTask < JarTask + + SUPPORTED_TYPES = [:war, :ejb, :jar, :rar, :lib] + + # The display-name entry for application.xml + attr_accessor :display_name + # The description entry for application.xml + attr_accessor :description + # Map from component type to path inside the EAR. + attr_accessor :dirs + # Security roles entry for application.xml + attr_accessor :security_roles + + def initialize(*args) + super + @dirs = Hash.new { |h, k| k.to_s } + @libs, @components, @security_roles = [], [], [] + prepare do + @components.each do |component| + path(component[:path]).include(component[:clone] || component[:artifact]) + end + path('META-INF').include(descriptor) + end + end + + # Add an artifact to this EAR. + def add(*args) + options = Hash === args.last ? args.pop.clone : {} + args.flatten! + args.map! do |pkg| + case pkg + when Project + pkg.packages.select { |pp| JarTask === pp && SUPPORTED_TYPES.include?(pp.type) } + when Rake::FileTask + pkg # add the explicitly provided file + when Hash + Buildr.artifact(pkg) + when String + begin + Buildr.artifact(pkg) + rescue # not an artifact spec, it must me a filename + file(pkg) + end + else + raise "Invalid EAR component #{pkg.class}: #{pkg}" + end + end + args.flatten! + args.compact! + if args.empty? + raise ":type must not be specified for type=>component argument style" if options.key?(:type) + raise ":as must not be specified for type=>component argument style" if options.key?(:as) + comps = {} + options.delete_if { |k, v| comps[k] = v if SUPPORTED_TYPES.include?(k) } + raise "You must specify at least one valid component to add" if comps.empty? + comps.each { |k, v| add(v, {:as => k}.merge(options)) } + else + args.each do |artifact| + type = options[:as] || options[:type] + unless type + type = artifact.respond_to?(:type) ? artifact.type : artifact.to_s.pathmap('%x').to_sym + type = :lib if type == :jar + end + raise "Unknown EAR component type: #{type}. Perhaps you may explicity tell what component type to use." unless + SUPPORTED_TYPES.include?(type) + component = options.merge(:artifact => artifact, :type => type, + :id=>artifact.respond_to?(:to_spec) ? artifact.id : artifact.to_s.pathmap('%n'), + :path=>options[:path] || dirs[type].to_s) + component[:clone] = component_clone(component) unless :lib == type + # update_classpath(component) unless :lib == type || Artifact === artifact + @components << component + end + end + self + end + + alias_method :push, :add + alias_method :<<, :push + + protected + + def component_clone(component) + file(path_to(component[:path], component[:artifact].to_s.pathmap('%f')) => component[:artifact]) do |task| + mkpath task.to_s.pathmap('%d') + cp component[:artifact].to_s, task.to_s + Manifest.update_manifest(task) do |manifest| + class_path = manifest.main['Class-Path'].to_s.split + included_libs = class_path.map { |fn| fn.pathmap('%f') } + Zip::ZipFile.foreach(task.to_s) do |entry| + included_libs << entry.name.pathmap('%f') if entry.file? && entry.name =~ /^WEB-INF\/lib\/[^\/]+$/ + end + # Include all other libraries in the classpath. + class_path += libs_classpath(component).reject { |path| included_libs.include?(File.basename(path)) } + manifest.main['Class-Path'] = class_path.join(' ') + end + end + end + + def associate(project) + @project = project + end + + def path_to(*args) #:nodoc: + @project.path_to(:target, :ear, name.pathmap('%n'), *args) + end + alias_method :_, :path_to + + def update_classpath(component) + package = file(component[:artifact].to_s) + package.manifest = (package.manifest || {}).dup # avoid mofifying parent projects manifest + package.prepare do + header = case package.manifest + when Hash then package.manifest + when Array then package.manifest.first + end + if header + # Determine which libraries are already included. + class_path = header['Class-Path'].to_s.split + included_libs = class_path.map { |fn| File.basename(fn) } + included_libs += package.path('WEB-INF/lib').sources.map { |fn| File.basename(fn) } + # Include all other libraries in the classpath. + class_path += libs_classpath(component).reject { |path| included_libs.include?(File.basename(path)) } + header['Class-Path'] = class_path.join(' ') + end + end + end + + private + + # Classpath of all packages included as libraries (type :lib). + def libs_classpath(component) + from = component[:path] + @classpath = @components.select { |comp| comp[:type] == :lib }. + map do |lib| + basename = lib[:artifact].to_s.pathmap('%f') + full_path = lib[:path].empty? ? basename : File.join(lib[:path], basename) + Util.relative_path(full_path, from) + end + end + + def descriptor_xml + buffer = "" + xml = Builder::XmlMarkup.new(:target=>buffer, :indent => 2) + xml.declare! :DOCTYPE, :application, :PUBLIC, + "-//Sun Microsystems, Inc.//DTD J2EE Application 1.2//EN", + "http://java.sun.com/j2ee/dtds/application_1_2.dtd" + xml.application do + xml.tag! 'display-name', display_name + desc = self.description || @project.comment + xml.tag! 'description', desc if desc + @components.each do |comp| + basename = comp[:artifact].to_s.pathmap('%f') + uri = comp[:path].empty? ? basename : File.join(comp[:path], basename) + case comp[:type] + when :war + xml.module :id=>comp[:id] do + xml.web do + xml.tag! 'web-uri', uri + xml.tag! 'context-root', File.join('', (comp[:context_root] || comp[:id])) unless comp[:context_root] == false + end + end + when :ejb + xml.module :id=>comp[:id] do + xml.ejb uri + end + when :jar + xml.jar uri + end + end + @security_roles.each do |role| + xml.tag! 'security-role', :id=>role[:id] do + xml.description role[:description] + xml.tag! 'role-name', role[:name] + end + end + end + buffer + end + + # return a FileTask to build the ear application.xml file + def descriptor + return @descriptor if @descriptor + descriptor_path = path_to('META-INF/application.xml') + @descriptor = file(descriptor_path) do |task| + trace "Creating EAR Descriptor: #{task.to_s}" + mkpath File.dirname(task.name) + File.open(task.name, 'w') { |file| file.print task.xml } + end + class << @descriptor + attr_accessor :ear + + def xml + @xml ||= ear.send :descriptor_xml + end + + def needed? + super || xml != File.read(self.to_s) rescue true + end + end + @descriptor.ear = self + @descriptor + end + + end + + + include Extension + + before_define(:package => :build) do |project| + if project.parent && project.parent.manifest + project.manifest = project.parent.manifest.dup + else + project.manifest = { + 'Build-By'=>ENV['USER'], 'Build-Jdk'=>ENV_JAVA['java.version'], + 'Implementation-Title'=>project.comment || project.name, + 'Implementation-Version'=>project.version } + end + if project.parent && project.parent.meta_inf + project.meta_inf = project.parent.meta_inf.dup + else + project.meta_inf = [project.file('LICENSE')].select { |file| File.exist?(file.to_s) } + end + end + + + # Manifest used for packaging. Inherited from parent project. The default value is a hash that includes + # the Build-By, Build-Jdk, Implementation-Title and Implementation-Version values. + # The later are taken from the project's comment (or name) and version number. + attr_accessor :manifest + + # Files to always include in the package META-INF directory. The default value include + # the LICENSE file if one exists in the project's base directory. + attr_accessor :meta_inf + + # :call-seq: + # package_with_sources(options?) + # + # Call this when you want the project (and all its sub-projects) to create a source distribution. + # You can use the source distribution in an IDE when debugging. + # + # A source distribution is a jar package with the classifier 'sources', which includes all the + # sources used by the compile task. + # + # Packages use the project's manifest and meta_inf properties, which you can override by passing + # different values (e.g. false to exclude the manifest) in the options. + # + # To create source distributions only for specific projects, use the :only and :except options, + # for example: + # package_with_sources :only=>['foo:bar', 'foo:baz'] + # + # (Same as calling package :sources on each project/sub-project that has source directories.) + def package_with_sources(options = nil) + options ||= {} + enhance do + selected = options[:only] ? projects(options[:only]) : + options[:except] ? ([self] + projects - projects(options[:except])) : + [self] + projects + selected.reject { |project| project.compile.sources.empty? && project.resources.target.nil? }. + each { |project| project.package(:sources) } + end + end + + # :call-seq: + # package_with_javadoc(options?) + # + # Call this when you want the project (and all its sub-projects) to create a JavaDoc distribution. + # You can use the JavaDoc distribution in an IDE when coding against the API. + # + # A JavaDoc distribution is a ZIP package with the classifier 'javadoc', which includes all the + # sources used by the compile task. + # + # Packages use the project's manifest and meta_inf properties, which you can override by passing + # different values (e.g. false to exclude the manifest) in the options. + # + # To create JavaDoc distributions only for specific projects, use the :only and :except options, + # for example: + # package_with_javadoc :only=>['foo:bar', 'foo:baz'] + # + # (Same as calling package :javadoc on each project/sub-project that has source directories.) + def package_with_javadoc(options = nil) + options ||= {} + enhance do + selected = options[:only] ? projects(options[:only]) : + options[:except] ? ([self] + projects - projects(options[:except])) : + [self] + projects + selected.reject { |project| project.compile.sources.empty? }. + each { |project| project.package(:javadoc) } + end + end + + protected + + def package_as_jar(file_name) #:nodoc: + Java::JarTask.define_task(file_name).tap do |jar| + jar.with :manifest=>manifest, :meta_inf=>meta_inf + jar.with [compile.target, resources.target].compact + end + end + + def package_as_war(file_name) #:nodoc: + Java::WarTask.define_task(file_name).tap do |war| + war.with :manifest=>manifest, :meta_inf=>meta_inf + # Add libraries in WEB-INF lib, and classes in WEB-INF classes + war.with :classes=>[compile.target, resources.target].compact + war.with :libs=>compile.dependencies + # Add included files, or the webapp directory. + webapp = path_to(:source, :main, :webapp) + war.with webapp if File.exist?(webapp) + end + end + + def package_as_aar(file_name) #:nodoc: + Java::AarTask.define_task(file_name).tap do |aar| + aar.with :manifest=>manifest, :meta_inf=>meta_inf + aar.with :wsdls=>path_to(:source, :main, :axis2, '*.wsdl') + aar.with :services_xml=>path_to(:source, :main, :axis2, 'services.xml') + aar.with [compile.target, resources.target].compact + aar.with :libs=>compile.dependencies + end + end + + def package_as_ear(file_name) #:nodoc: + Java::EarTask.define_task(file_name).tap do |ear| + ear.send :associate, self + ear.with :display_name=>id, :manifest=>manifest, :meta_inf=>meta_inf + end + end + + def package_as_javadoc_spec(spec) #:nodoc: + spec.merge(:type=>:jar, :classifier=>'javadoc') + end + + def package_as_javadoc(file_name) #:nodoc: + ZipTask.define_task(file_name).tap do |zip| + zip.include :from=>doc.target + end + end + + end + + end +end + + +class Buildr::Project + include Buildr::Packaging::Java +end diff --git a/buildr/lib/buildr/java/pom.rb b/buildr/lib/buildr/java/pom.rb new file mode 100644 index 0000000..5a1dc8e --- /dev/null +++ b/buildr/lib/buildr/java/pom.rb @@ -0,0 +1,185 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + class POM + + POM_TO_SPEC_MAP = { :group=>"groupId", :id=>"artifactId", :type=>"type", + :version=>"version", :classifier=>"classifier", :scope=>"scope" } + SCOPES_TRANSITIVE = [nil, "compile", "runtime"] + SCOPES_WE_USE = SCOPES_TRANSITIVE + ["provided"] + + # POM project as Hash (using XmlSimple). + attr_reader :project + # Parent POM if referenced by this POM. + attr_reader :parent + + class << self + + # :call-seq: + # POM.load(arg) + # + # Load new POM object form various kind of sources such as artifact, hash representing spec, filename, XML. + def load(source) + case source + when Hash + load(Buildr.artifact(source).pom) + when Artifact + pom = source.pom + pom.invoke + load(pom.to_s) + when Rake::FileTask + source.invoke + load(source.to_s) + when String + filename = File.expand_path(source) + unless pom = cache[filename] + trace "Loading m2 pom file from #{filename}" + begin + pom = POM.new(IO.read(filename)) + rescue REXML::ParseException => e + fail "Could not parse #{filename}, #{e.continued_exception}" + end + cache[filename] = pom + end + pom + else + raise ArgumentError, "Expecting Hash spec, Artifact, file name or file task" + end + end + + private + + def cache() + @cache ||= {} + end + + end + + def initialize(xml) #:nodoc: + @project = XmlSimple.xml_in(xml) + @parent = POM.load(pom_to_hash(project["parent"].first).merge(:type=>'pom')) if project['parent'] + end + + # :call-seq: + # dependencies(scopes?) => artifacts + # dependencies(:scopes = [:runtime, :test, ...], :optional = true) => artifacts + # + # Returns list of required dependencies as specified by the POM. You can specify which scopes + # to use (e.g. "compile", "runtime"); use +nil+ for dependencies with unspecified scope. + # The default scopes are +nil+, "compile" and "runtime" (aka SCOPES_WE_USE) and no optional dependencies. + # Specifying optional = true will return all optional dependencies matching the given scopes. + def dependencies(options = {}) + # backward compatibility + options = { :scopes => options } if Array === options + + # support symbols, but don't fidget with nil + options[:scopes] = (options[:scopes] || SCOPES_WE_USE).map { |s| s.to_s if s } + + # try to cache dependencies also + @depends_for_scopes ||= {} + unless depends = @depends_for_scopes[options] + declared = project["dependencies"].first["dependency"] rescue nil + depends = (declared || []) + depends = depends.reject { |dep| value_of(dep["optional"]) =~ /true/ } unless options[:optional] + depends = depends.map { |dep| + spec = pom_to_hash(dep, properties) + apply = managed(spec) + spec = apply.merge(spec) if apply + + # calculate transitive dependencies + if options[:scopes].include?(spec[:scope]) + spec.delete(:scope) + + exclusions = dep["exclusions"].first["exclusion"] rescue nil + transitive_deps = POM.load(spec).dependencies(options[:scopes_transitive] || SCOPES_TRANSITIVE) + transitive_deps = transitive_deps.reject{|dep| + exclusions.find {|ex| dep.index("#{ex['groupId'].first}:#{ex['artifactId'].first}:") == 0} + } if exclusions + + [Artifact.to_spec(spec)] + transitive_deps + end + }.flatten.compact #.uniq_by{|spec| art = spec.split(':'); "#{art[0]}:#{art[1]}"} + @depends_for_scopes[options] = depends + end + depends + end + + # :call-seq: + # properties() => hash + # + # Returns properties available to this POM as hash. Includes explicit properties and pom.xxx/project.xxx + # properties for groupId, artifactId, version and packaging. + def properties() + @properties ||= begin + pom = ["groupId", "artifactId", "version", "packaging"].inject({}) { |hash, key| + value = project[key] || (parent ? parent.project[key] : nil) + hash[key] = hash["pom.#{key}"] = hash["project.#{key}"] = value_of(value) if value + hash + } + props = project["properties"].first rescue {} + props = props.inject({}) { |mapped, pair| mapped[pair.first] = value_of(pair.last, pom) ; mapped } + (parent ? parent.properties.merge(props) : props).merge(pom) + end + end + + # :call-seq: + # managed() => hash + # managed(hash) => hash + # + # The first form returns all the managed dependencies specified by this POM in dependencyManagement. + # The second form uses a single spec hash and expands it from the current/parent POM. Used to determine + # the version number if specified in dependencyManagement instead of dependencies. + def managed(spec = nil) + if spec + managed.detect { |dep| [:group, :id, :type, :classifier].all? { |key| spec[key] == dep[key] } } || + (parent ? parent.managed(spec) : nil) + else + @managed ||= begin + managed = project["dependencyManagement"].first["dependencies"].first["dependency"] rescue nil + managed ? managed.map { |dep| pom_to_hash(dep, properties) } : [] + end + end + end + + private + + # :call-seq: + # value_of(element) => string + # value_of(element, true) => string + # + # Returns the normalized text value of an element from its XmlSimple value. The second form performs + # property substitution. + def value_of(element, substitute = nil) + value = element.to_a.join.strip + substitute ? value.gsub(/\$\{([^}]+)\}/) { |key| substitute[$1] } : value + end + + # :call-seq: + # pom_to_hash(element) => hash + # pom_to_hash(element, true) => hash + # + # Return the spec hash from an XmlSimple POM referencing element (e.g. project, parent, dependency). + # The second form performs property substitution. + def pom_to_hash(element, substitute = nil) + hash = POM_TO_SPEC_MAP.inject({}) { |spec, pair| + spec[pair.first] = value_of(element[pair.last], substitute) if element[pair.last] + spec + } + { :type=>"jar" }.merge(hash) + end + + end +end diff --git a/buildr/lib/buildr/java/rjb.rb b/buildr/lib/buildr/java/rjb.rb new file mode 100644 index 0000000..b2a41e0 --- /dev/null +++ b/buildr/lib/buildr/java/rjb.rb @@ -0,0 +1,154 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require 'rjb' + + +# Equivalent to Java system properties. For example: +# ENV_JAVA['java.version'] +# ENV_JAVA['java.class.version'] +ENV_JAVA = {} + + +# Buildr runs along side a JVM, using either RJB or JRuby. The Java module allows +# you to access Java classes and create Java objects. +# +# Java classes are accessed as static methods on the Java module, for example: +# str = Java.java.lang.String.new('hai!') +# str.toUpperCase +# => 'HAI!' +# Java.java.lang.String.isInstance(str) +# => true +# Java.com.sun.tools.javac.Main.compile(args) +# +# The classpath attribute allows Buildr to add JARs and directories to the classpath, +# for example, we use it to load Ant and various Ant tasks, code generators, test +# frameworks, and so forth. +# +# When using an artifact specification, Buildr will automatically download and +# install the artifact before adding it to the classpath. +# +# For example, Ant is loaded as follows: +# Java.classpath << 'org.apache.ant:ant:jar:1.7.0' +# +# Artifacts can only be downloaded after the Buildfile has loaded, giving it +# a chance to specify which remote repositories to use, so adding to classpath +# does not by itself load any libraries. You must call Java.load before accessing +# any Java classes to give Buildr a chance to load the libraries specified in the +# classpath. +# +# When building an extension, make sure to follow these rules: +# 1. Add to the classpath when the extension is loaded (i.e. in module or class +# definition), so the first call to Java.load anywhere in the code will include +# the libraries you specify. +# 2. Call Java.load once before accessing any Java classes, allowing Buildr to +# set up the classpath. +# 3. Only call Java.load when invoked, otherwise you may end up loading the JVM +# with a partial classpath, or before all remote repositories are listed. +# 4. Check on a clean build with empty local repository. +module Java + + module Package #:nodoc: + + def method_missing(sym, *args, &block) + raise ArgumentError, 'No arguments expected' unless args.empty? + name = "#{@name}.#{sym}" + return ::Rjb.import(name) if sym.to_s =~ /^[[:upper:]]/ + ::Java.send :__package__, name + end + + end + + # On OS X we know where the default JDK is. We can try to guess for other OS. + # We set JAVA_HOME early so we can use it without calling Java.load first. + ENV['JAVA_HOME'] ||= '/System/Library/Frameworks/JavaVM.framework/Home' if Config::CONFIG['host_os'] =~ /darwin/i + + class << self + + # Returns the classpath, an array listing directories, JAR files and + # artifacts. Use when loading the extension to add any additional + # libraries used by that extension. + # + # For example, Ant is loaded as follows: + # Java.classpath << 'org.apache.ant:ant:jar:1.7.0' + def classpath + @classpath ||= [] + end + + # Most platforms requires tools.jar to be on the classpath, tools.jar contains the + # Java compiler (OS X and AIX are two exceptions we know about, may be more). + # Guess where tools.jar is from JAVA_HOME, which hopefully points to the JDK, + # but maybe the JRE. Return nil if not found. + def tools_jar #:nodoc: + @tools_jar ||= begin + home = ENV['JAVA_HOME'] or fail 'Are we forgetting something? JAVA_HOME not set.' + ['lib/tools.jar', '../lib/tools.jar'].map { |path| File.expand_path(path, home) }. + find { |path| File.exist?(path) } + end + end + + # Loads the JVM and all the libraries listed on the classpath. Call this + # method before accessing any Java class, but only call it from methods + # used in the build, giving the Buildfile a chance to load all extensions + # that append to the classpath and specify which remote repositories to use. + def load + return self if @loaded + classpath << tools_jar if tools_jar + + classpath.map! { |path| Proc === path ? path.call : path } + cp = Buildr.artifacts(classpath).map(&:to_s).each { |path| file(path).invoke } + java_opts = (ENV['JAVA_OPTS'] || ENV['JAVA_OPTIONS']).to_s.split + ::Rjb.load cp.join(File::PATH_SEPARATOR), java_opts + + props = ::Rjb.import('java.lang.System').getProperties + enum = props.propertyNames + while enum.hasMoreElements + name = enum.nextElement.toString + ENV_JAVA[name] = props.getProperty(name) + end + @loaded = true + self + end + + def method_missing(sym, *args, &block) #:nodoc: + raise ArgumentError, 'No arguments expected' unless args.empty? + Java.load # need to load RJB's classpath now! + name = sym.to_s + return ::Rjb.import(name) if name =~ /^[[:upper:]]/ + __package__ name + end + + private + + def __package__(name) #:nodoc: + Module.new.tap do |m| + m.extend Package + m.instance_variable_set :@name, name + end + end + + end + +end + + +class Array + # Converts a Ruby array into a typed Java array, argument specifies the element type. + # This is necessary for JRuby and causes no harm on RJB. + def to_java(cls) + map { |item| cls.new(item) } + end +end diff --git a/buildr/lib/buildr/java/test_result.rb b/buildr/lib/buildr/java/test_result.rb new file mode 100644 index 0000000..c4c4d92 --- /dev/null +++ b/buildr/lib/buildr/java/test_result.rb @@ -0,0 +1,96 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr #:nodoc: + module TestFramework + + # A class used by buildr for jruby based frameworks, so that buildr can know + # which tests succeeded/failed. + class TestResult + + class Error < ::Exception + attr_reader :message, :backtrace + def initialize(message, backtrace) + @message = message + @backtrace = backtrace + set_backtrace backtrace + end + + def self.dump_yaml(file, e) + FileUtils.mkdir_p File.dirname(file) + File.open(file, 'w') { |f| f.puts(YAML.dump(Error.new(e.message, e.backtrace))) } + end + + def self.guard(file) + begin + yield + rescue => e + dump_yaml(file, e) + end + end + end + + attr_accessor :failed, :succeeded + + def initialize + @failed, @succeeded = [], [] + end + + # An Rspec formatter used by buildr + class YamlFormatter < ::RSpec::Core::Formatters::BaseFormatter + attr_reader :result + + def initialize(output) + super(output) + @result = Hash.new + @result[:succeeded] = [] + @result[:failed] = [] + end + + def example_passed(example) + super(example) + result.succeeded << example_name(example) + end + + def example_pending(example) + super(example) + result.succeeded << example_name(example) + end + + def example_failed(example) + super(example) + result.failed << example_name(example) + end + + def start(example_count) + super(example_count) + @result = TestResult.new + end + + def close + super + result.succeeded = result.succeeded - result.failed + output.puts YAML.dump(result) + end + + private + def example_name(example) + example.file_path + end + end # YamlFormatter + + end # TestResult + end +end diff --git a/buildr/lib/buildr/java/tests.rb b/buildr/lib/buildr/java/tests.rb new file mode 100644 index 0000000..e6d0cbf --- /dev/null +++ b/buildr/lib/buildr/java/tests.rb @@ -0,0 +1,424 @@ + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + class TestFramework::Java < TestFramework::Base + + class << self + + def applies_to?(project) #:nodoc: + project.test.compile.language == :java || project.test.compile.language == :groovy + end + + def dependencies + unless @dependencies + super + # Add buildr utility classes (e.g. JavaTestFilter) + @dependencies |= [ File.join(File.dirname(__FILE__)) ] + end + @dependencies + end + end + + private + + # Add buildr utilities (JavaTestFilter) to classpath + Java.classpath << lambda { dependencies } + + # :call-seq: + # filter_classes(dependencies, criteria) + # + # Return a list of classnames that match the given criteria. + # The criteria parameter is a hash that must contain at least one of: + # + # * :class_names -- List of patterns to match against class name + # * :interfaces -- List of java interfaces or java classes + # * :class_annotations -- List of annotations on class level + # * :method_annotations -- List of annotations on method level + # * :fields -- List of java field names + # + def filter_classes(dependencies, criteria = {}) + return [] unless task.compile.target + target = task.compile.target.to_s + candidates = Dir["#{target}/**/*.class"]. + map { |file| Util.relative_path(file, target).ext('').gsub(File::SEPARATOR, '.') }. + reject { |name| name =~ /\$./ } + result = [] + if criteria[:class_names] + result.concat candidates.select { |name| criteria[:class_names].flatten.any? { |pat| pat === name } } + end + begin + Java.load + filter = Java.org.apache.buildr.JavaTestFilter.new(dependencies.to_java(Java.java.lang.String)) + if criteria[:interfaces] + filter.add_interfaces(criteria[:interfaces].to_java(Java.java.lang.String)) + end + if criteria[:class_annotations] + filter.add_class_annotations(criteria[:class_annotations].to_java(Java.java.lang.String)) + end + if criteria[:method_annotations] + filter.add_method_annotations(criteria[:method_annotations].to_java(Java.java.lang.String)) + end + if criteria[:fields] + filter.add_fields(criteria[:fields].to_java(Java.java.lang.String)) + end + result.concat filter.filter(candidates.to_java(Java.java.lang.String)).map(&:to_s) + rescue =>ex + info "#{ex.class}: #{ex.message}" + raise + end + end + + end + + + # JMock is available when using JUnit and TestNG, JBehave. + module JMock + + VERSION = '2.5.1' + + class << self + def version + Buildr.settings.build['jmock'] || VERSION + end + + def dependencies + two_or_later = version[0,1].to_i >= 2 + group = two_or_later ? "org.jmock" : "jmock" + + @dependencies ||= ["#{group}:jmock:jar:#{version}"] + if two_or_later + @dependencies << "org.jmock:jmock-junit#{Buildr::JUnit.version.to_s[0,1]}:jar:#{version}" + @dependencies << "org.hamcrest:hamcrest-core:jar:1.1" + @dependencies << "org.hamcrest:hamcrest-library:jar:1.1" + end + @dependencies + end + + private + def const_missing(const) + return super unless const == :REQUIRES # TODO: remove in 1.5 + Buildr.application.deprecated "Please use JMock.dependencies/.version instead of JMock::REQUIRES/VERSION" + dependencies + end + end + end + + + # JUnit test framework, the default test framework for Java tests. + # + # Support the following options: + # * :fork -- If true/:once (default), fork for each test class. If :each, fork for each individual + # test case. If false, run all tests in the same VM (fast, but dangerous). + # * :clonevm -- If true clone the VM each time it is forked. + # * :properties -- Hash of system properties available to the test case. + # * :environment -- Hash of environment variables available to the test case. + # * :java_args -- Arguments passed as is to the JVM. + class JUnit < TestFramework::Java + + # Used by the junit:report task. Access through JUnit#report if you want to set various + # options for that task, for example: + # JUnit.report.frames = false + class Report + + # Parameters passed to the Ant JUnitReport task. + attr_reader :params + # True (default) to produce a report using frames, false to produce a single-page report. + attr_accessor :frames + # Directory for the report style (defaults to using the internal style). + attr_accessor :style_dir + # Target directory for generated report. + attr_accessor :target + + def initialize + @params = {} + @frames = true + @target = 'reports/junit' + end + + # :call-seq: + # generate(projects, target?) + # + # Generates a JUnit report for these projects (must run JUnit tests first) into the + # target directory. You can specify a target, or let it pick the default one from the + # target attribute. + def generate(projects, target = @target.to_s) + html_in = File.join(target, 'html') + rm_rf html_in ; mkpath html_in + + Buildr.ant('junit-report') do |ant| + ant.taskdef :name=>'junitreport', :classname=>'org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator', + :classpath=>Buildr.artifacts(JUnit.ant_taskdef).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR) + ant.junitreport :todir=>target do + projects.select { |project| project.test.framework == :junit }. + map { |project| project.test.report_to.to_s }.select { |path| File.exist?(path) }. + each { |path| ant.fileset(:dir=>path) { ant.include :name=>'TEST-*.xml' } } + options = { :format=>frames ? 'frames' : 'noframes' } + options[:styledir] = style_dir if style_dir + ant.report options.merge(:todir=>html_in) do + params.each { |key, value| ant.param :name=>key, :expression=>value } + end + end + end + end + + end + + # JUnit version number. + VERSION = '4.8.2' + + class << self + # :call-seq: + # report() + # + # Returns the Report object used by the junit:report task. You can use this object to set + # various options that affect your report, for example: + # JUnit.report.frames = false + # JUnit.report.params['title'] = 'My App' + def report + @report ||= Report.new + end + + def version + Buildr.settings.build['junit'] || VERSION + end + + def dependencies + @dependencies ||= ["junit:junit:jar:#{version}"]+ JMock.dependencies + end + + def ant_taskdef #:nodoc: + "org.apache.ant:ant-junit:jar:#{Ant.version}" + end + + private + def const_missing(const) + return super unless const == :REQUIRES # TODO: remove in 1.5 + Buildr.application.deprecated "Please use JUnit.dependencies/.version instead of JUnit::REQUIRES/VERSION" + dependencies + end + end + + def tests(dependencies) #:nodoc: + if (self.class.version.to_s[0,1].to_i < 4) + filter_classes(dependencies, :interfaces => %w{junit.framework.TestCase}) + else + filter_classes(dependencies, + :interfaces => %w{junit.framework.TestCase}, + :class_annotations => %w{org.junit.runner.RunWith}, + :method_annotations => %w{org.junit.Test}) + end + + end + + def run(tests, dependencies) #:nodoc: + # Use Ant to execute the Junit tasks, gives us performance and reporting. + Buildr.ant('junit') do |ant| + case options[:fork] + when false + forking = {} + when :each + forking = { :fork=>true, :forkmode=>'perTest' } + when true, :once + forking = { :fork=>true, :forkmode=>'once' } + else + fail 'Option fork must be :once, :each or false.' + end + mkpath task.report_to.to_s + + taskdef = Buildr.artifact(JUnit.ant_taskdef) + taskdef.invoke + ant.taskdef :name=>'junit', :classname=>'org.apache.tools.ant.taskdefs.optional.junit.JUnitTask', :classpath=>taskdef.to_s + + ant.junit forking.merge(:clonevm=>options[:clonevm] || false, :dir=>task.send(:project).path_to) do + ant.classpath :path=>dependencies.join(File::PATH_SEPARATOR) + (options[:properties] || []).each { |key, value| ant.sysproperty :key=>key, :value=>value } + (options[:environment] || []).each { |key, value| ant.env :key=>key, :value=>value } + Array(options[:java_args]).each { |value| ant.jvmarg :value=>value } + ant.formatter :type=>'plain' + ant.formatter :type=>'plain', :usefile=>false # log test + ant.formatter :type=>'xml' + ant.batchtest :todir=>task.report_to.to_s, :failureproperty=>'failed' do + ant.fileset :dir=>task.compile.target.to_s do + tests.each { |test| ant.include :name=>File.join(*test.split('.')).ext('class') } + end + end + end + return tests unless ant.project.getProperty('failed') + end + # But Ant doesn't tell us what went kaput, so we'll have to parse the test files. + tests.inject([]) do |passed, test| + report_file = File.join(task.report_to.to_s, "TEST-#{test}.txt") + if File.exist?(report_file) + report = File.read(report_file) + # The second line (if exists) is the status line and we scan it for its values. + status = (report.split("\n")[1] || '').scan(/(run|failures|errors):\s*(\d+)/i). + inject(Hash.new(0)) { |hash, pair| hash[pair[0].downcase.to_sym] = pair[1].to_i ; hash } + passed << test if status[:failures] == 0 && status[:errors] == 0 + end + passed + end + end + + namespace 'junit' do + desc "Generate JUnit tests report in #{report.target}" + task('report') do |task| + report.generate Project.projects + info "Generated JUnit tests report in #{report.target}" + end + end + + task('clean') { rm_rf report.target.to_s } + + end + + + # TestNG test framework. To use in your project: + # test.using :testng + # + # Support the following options: + # * :properties -- Hash of properties passed to the test suite. + # * :java_args -- Arguments passed to the JVM. + class TestNG < TestFramework::Java + + VERSION = '5.10' + + class << self + def version + Buildr.settings.build['testng'] || VERSION + end + + def dependencies + ["org.testng:testng:jar:jdk15:#{version}"]+ JMock.dependencies + end + + private + def const_missing(const) + return super unless const == :REQUIRES # TODO: remove in 1.5 + Buildr.application.deprecated "Please use TestNG.dependencies/.version instead of TestNG::REQUIRES/VERSION" + dependencies + end + end + + def tests(dependencies) #:nodoc: + filter_classes(dependencies, + :class_annotations => %w{org.testng.annotations.Test}, + :method_annotations => %w{org.testng.annotations.Test}) + end + + def run(tests, dependencies) #:nodoc: + cmd_args = ['-log', '2', '-sourcedir', task.compile.sources.join(';'), '-suitename', task.project.id ] + cmd_args << '-d' << task.report_to.to_s + exclude_args = options[:excludegroups] || [] + if !exclude_args.empty? + cmd_args << '-excludegroups' << exclude_args.join(",") + end + groups_args = options[:groups] || [] + if !groups_args.empty? + cmd_args << '-groups' << groups_args.join(",") + end + # run all tests in the same suite + cmd_args << '-testclass' << tests + + cmd_options = { :properties=>options[:properties], :java_args=>options[:java_args], + :classpath=>dependencies, :name => "TestNG in #{task.send(:project).name}" } + + tmp = nil + begin + tmp = Tempfile.open("testNG") + tmp.write cmd_args.join("\n") + tmp.close + Java::Commands.java ['org.testng.TestNG', "@#{tmp.path}"], cmd_options + return tests + rescue + # testng-failed.xml contains the list of failed tests *only* + report = File.read(File.join(task.report_to.to_s, 'testng-failed.xml')) + failed = report.scan(//im).flatten + error "TestNG regexp returned unexpected failed tests #{failed.inspect}" unless (failed - tests).empty? + # return the list of passed tests + return tests - failed + ensure + tmp.close unless tmp.nil? + end + end + + end + + # A composite test framework that runs multiple other test frameworks. + # + # e.g., + # test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ], :options = { + # :junit => { :fork => true }, + # :testng => { ... } + # } + # + class MultiTest < Buildr::TestFramework::Java + # TODO: Support multiple test report locations, one per framework + + class << self + def applies_to?(project) #:nodoc: + false # no auto-detection, should be set explicitly + end + end + + attr_accessor :frameworks + + def initialize(task, options) #:nodoc: + super + fail "Missing :frameworks option" unless options[:frameworks] + @frameworks = options[:frameworks].map do |f| + framework_options = (options[:options] || {})[f.to_sym] || {} + f.new(task, options) + end + end + + def dependencies #:nodoc: + unless @dependencies + @dependencies = TestFramework::Java.dependencies + @dependencies += @frameworks.map { |f| f.dependencies }.flatten + end + @dependencies + end + + + def tests(dependencies) + @frameworks.map { |f| f.tests(dependencies) }.flatten + end + + def run(tests, dependencies) #:nodoc: + framework_for_test = @frameworks.inject({}) do |hash, f| + f.tests(dependencies).each { |t| hash[t] = f } + hash + end + + tests_by_framework = tests.group_by { |t| framework_for_test[t] } + + passed = [] + tests_by_framework.each do |f, tests| + passed += f.run(tests, dependencies) + end + passed + end + end # MultiTest + +end # Buildr + + +Buildr::TestFramework << Buildr::JUnit +Buildr::TestFramework << Buildr::TestNG +Buildr::TestFramework << Buildr::MultiTest + diff --git a/buildr/lib/buildr/java/version_requirement.rb b/buildr/lib/buildr/java/version_requirement.rb new file mode 100644 index 0000000..d04614a --- /dev/null +++ b/buildr/lib/buildr/java/version_requirement.rb @@ -0,0 +1,172 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +module Buildr + + # + # See ArtifactNamespace#need + class VersionRequirement + + CMP_PROCS = Gem::Requirement::OPS.dup + CMP_REGEX = Gem::Requirement::OP_RE.dup + CMP_CHARS = CMP_PROCS.keys.join + BOOL_CHARS = '\|\&\!' + VER_CHARS = '\w\.\-' + + class << self + # is +str+ a version string? + def version?(str) + /^\s*[#{VER_CHARS}]+\s*$/ === str + end + + # is +str+ a version requirement? + def requirement?(str) + /[#{BOOL_CHARS}#{CMP_CHARS}\(\)]/ === str + end + + # :call-seq: + # VersionRequirement.create(" >1 <2 !(1.5) ") -> requirement + # + # parse the +str+ requirement + def create(str) + instance_eval normalize(str) + rescue StandardError => e + raise "Failed to parse #{str.inspect} due to: #{e}" + end + + private + def requirement(req) + unless req =~ /^\s*(#{CMP_REGEX})?\s*([#{VER_CHARS}]+)\s*$/ + raise "Invalid requirement string: #{req}" + end + comparator, version = $1, $2 + version = Gem::Version.new(0).tap { |v| v.version = version } + VersionRequirement.new(nil, [$1, version]) + end + + def negate(vreq) + vreq.negative = !vreq.negative + vreq + end + + def normalize(str) + str = str.strip + if str[/[^\s\(\)#{BOOL_CHARS + VER_CHARS + CMP_CHARS}]/] + raise "version string #{str.inspect} contains invalid characters" + end + str.gsub!(/\s+(and|\&\&)\s+/, ' & ') + str.gsub!(/\s+(or|\|\|)\s+/, ' | ') + str.gsub!(/(^|\s*)not\s+/, ' ! ') + pattern = /(#{CMP_REGEX})?\s*[#{VER_CHARS}]+/ + left_pattern = /[#{VER_CHARS}\)]$/ + right_pattern = /^(#{pattern}|\()/ + str = str.split.inject([]) do |ary, i| + ary << '&' if ary.last =~ left_pattern && i =~ right_pattern + ary << i + end + str = str.join(' ') + str.gsub!(/!([^=])?/, ' negate \1') + str.gsub!(pattern) do |expr| + case expr.strip + when 'not', 'negate' then 'negate ' + else 'requirement("' + expr + '")' + end + end + str.gsub!(/negate\s+\(/, 'negate(') + str + end + end + + def initialize(op, *requirements) #:nodoc: + @op, @requirements = op, requirements + end + + # Is this object a composed requirement? + # VersionRequirement.create('1').composed? -> false + # VersionRequirement.create('1 | 2').composed? -> true + # VersionRequirement.create('1 & 2').composed? -> true + def composed? + requirements.size > 1 + end + + # Return the last requirement on this object having an = operator. + def default + default = nil + requirements.reverse.find do |r| + if Array === r + if !negative && (r.first.nil? || r.first.include?('=')) + default = r.last.to_s + end + else + default = r.default + end + end + default + end + + # Test if this requirement can be satisfied by +version+ + def satisfied_by?(version) + return false unless version + unless version.kind_of?(Gem::Version) + raise "Invalid version: #{version.inspect}" unless self.class.version?(version) + version = Gem::Version.new(0).tap { |v| v.version = version.strip } + end + message = op == :| ? :any? : :all? + result = requirements.send message do |req| + if Array === req + cmp, rv = *req + CMP_PROCS[cmp || '='].call(version, rv) + else + req.satisfied_by?(version) + end + end + negative ? !result : result + end + + # Either modify the current requirement (if it's already an or operation) + # or create a new requirement + def |(other) + operation(:|, other) + end + + # Either modify the current requirement (if it's already an and operation) + # or create a new requirement + def &(other) + operation(:&, other) + end + + # return the parsed expression + def to_s + str = requirements.map(&:to_s).join(" " + @op.to_s + " ").to_s + str = "( " + str + " )" if negative || requirements.size > 1 + str = "!" + str if negative + str + end + + attr_accessor :negative + protected + attr_reader :requirements, :op + def operation(op, other) + @op ||= op + if negative == other.negative && @op == op && other.requirements.size == 1 + @requirements << other.requirements.first + self + else + self.class.new(op, self, other) + end + end + end # VersionRequirement +end diff --git a/buildr/lib/buildr/packaging/archive.rb b/buildr/lib/buildr/packaging/archive.rb new file mode 100644 index 0000000..e48ab80 --- /dev/null +++ b/buildr/lib/buildr/packaging/archive.rb @@ -0,0 +1,534 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # Base class for ZipTask, TarTask and other archives. + class ArchiveTask < Rake::FileTask + + # Which files go where. All the rules for including, excluding and merging files + # are handled by this object. + class Path #:nodoc: + + # Returns the archive from this path. + attr_reader :root + + def initialize(root, path) + @root = root + @path = path.empty? ? path : "#{path}/" + @includes = FileList[] + @excludes = [] + # Expand source files added to this path. + expand_src = proc { @includes.map{ |file| file.to_s }.uniq } + @sources = [ expand_src ] + # Add files and directories added to this path. + @actions = [] << proc do |file_map| + expand_src.call.each do |path| + unless excluded?(path) + if File.directory?(path) + in_directory path do |file, rel_path| + dest = "#{@path}#{rel_path}" + unless excluded?(dest) + trace "Adding #{dest}" + file_map[dest] = file + end + end + end + unless File.basename(path) == "." + trace "Adding #{@path}#{File.basename(path)}" + file_map["#{@path}#{File.basename(path)}"] = path + end + end + end + end + end + + # :call-seq: + # include(*files) => self + # include(*files, :path=>path) => self + # include(file, :as=>name) => self + # include(:from=>path) => self + # include(*files, :merge=>true) => self + def include(*args) + options = args.pop if Hash === args.last + files = to_artifacts(args) + raise 'AchiveTask.include() values should not include nil' if files.include? nil + + if options.nil? || options.empty? + @includes.include *files.flatten + elsif options[:path] + sans_path = options.reject { |k,v| k == :path } + path(options[:path]).include *files + [sans_path] + elsif options[:as] + raise 'You can only use the :as option in combination with the :path option' unless options.size == 1 + raise 'You can only use one file with the :as option' unless files.size == 1 + include_as files.first.to_s, options[:as] + elsif options[:from] + raise 'You can only use the :from option in combination with the :path option' unless options.size == 1 + raise 'You cannot use the :from option with file names' unless files.empty? + fail 'AchiveTask.include() :from value should not be nil' if [options[:from]].flatten.include? nil + [options[:from]].flatten.each { |path| include_as path.to_s, '.' } + elsif options[:merge] + raise 'You can only use the :merge option in combination with the :path option' unless options.size == 1 + files.each { |file| merge file } + else + raise "Unrecognized option #{options.keys.join(', ')}" + end + self + end + alias :add :include + alias :<< :include + + # :call-seq: + # exclude(*files) => self + def exclude(*files) + files = to_artifacts(files) + @excludes |= files + @excludes |= files.reject { |f| f =~ /\*$/ }.map { |f| "#{f}/*" } + self + end + + # :call-seq: + # merge(*files) => Merge + # merge(*files, :path=>name) => Merge + def merge(*args) + options = Hash === args.last ? args.pop : {} + files = to_artifacts(args) + rake_check_options options, :path + raise ArgumentError, "Expected at least one file to merge" if files.empty? + path = options[:path] || @path + expanders = files.collect do |file| + @sources << proc { file.to_s } + expander = ZipExpander.new(file) + @actions << proc do |file_map| + file.invoke() if file.is_a?(Rake::Task) + expander.expand(file_map, path) + end + expander + end + Merge.new(expanders) + end + + # Returns a Path relative to this one. + def path(path) + return self if path.nil? + return root.path(path[1..-1]) if path[0] == ?/ + root.path("#{@path}#{path}") + end + + # Returns all the source files. + def sources #:nodoc: + @sources.map{ |source| source.call }.flatten + end + + def add_files(file_map) #:nodoc: + @actions.each { |action| action.call(file_map) } + end + + # :call-seq: + # exist => boolean + # + # Returns true if this path exists. This only works if the path has any entries in it, + # so exist on path happens to be the opposite of empty. + def exist? + !entries.empty? + end + + # :call-seq: + # empty? => boolean + # + # Returns true if this path is empty (has no other entries inside). + def empty? + entries.all? { |entry| entry.empty? } + end + + # :call-seq: + # contain(file*) => boolean + # + # Returns true if this ZIP file path contains all the specified files. You can use relative + # file names and glob patterns (using *, **, etc). + def contain?(*files) + files.all? { |file| entries.detect { |entry| File.fnmatch(file, entry.to_s) } } + end + + # :call-seq: + # entry(name) => ZipEntry + # + # Returns a ZIP file entry. You can use this to check if the entry exists and its contents, + # for example: + # package(:jar).path("META-INF").entry("LICENSE").should contain(/Apache Software License/) + def entry(name) + root.entry("#{@path}#{name}") + end + + def to_s + @path + end + + protected + + # Convert objects to artifacts, where applicable + def to_artifacts(files) + files.flatten.inject([]) do |set, file| + case file + when ArtifactNamespace + set |= file.artifacts + when Symbol, Hash + set |= [artifact(file)] + when /([^:]+:){2,4}/ # A spec as opposed to a file name. + set |= [Buildr.artifact(file)] + when Project + set |= Buildr.artifacts(file.packages) + when Rake::Task + set |= [file] + when Struct + set |= Buildr.artifacts(file.values) + else + # non-artifacts passed as-is; in particular, String paths are + # unmodified since Rake FileTasks don't use absolute paths + set |= [file] + end + end + end + + def include_as(source, as) + @sources << proc { source } + @actions << proc do |file_map| + file = source.to_s + unless excluded?(file) + if File.directory?(file) + in_directory file do |file, rel_path| + path = rel_path.split('/')[1..-1] + path.unshift as unless as == '.' + dest = "#{@path}#{path.join('/')}" + unless excluded?(dest) + trace "Adding #{dest}" + file_map[dest] = file + end + end + unless as == "." + trace "Adding #{@path}#{as}/" + file_map["#{@path}#{as}/"] = nil # :as is a folder, so the trailing / is required. + end + else + file_map["#{@path}#{as}"] = file + end + + end + end + end + + def in_directory(dir) + prefix = Regexp.new('^' + Regexp.escape(File.dirname(dir) + File::SEPARATOR)) + Util.recursive_with_dot_files(dir).reject { |file| excluded?(file) }. + each { |file| yield file, file.sub(prefix, '') } + end + + def excluded?(file) + @excludes.any? { |exclude| File.fnmatch(exclude, file) } + end + + def entries #:nodoc: + return root.entries unless @path + @entries ||= root.entries.inject([]) { |selected, entry| + selected << entry.name.sub(@path, "") if entry.name.index(@path) == 0 + selected + } + end + + end + + + class Merge + def initialize(expanders) + @expanders = expanders + end + + def include(*files) + @expanders.each { |expander| expander.include(*files) } + self + end + alias :<< :include + + def exclude(*files) + @expanders.each { |expander| expander.exclude(*files) } + self + end + end + + + # Extend one Zip file into another. + class ZipExpander #:nodoc: + + def initialize(zip_file) + @zip_file = zip_file.to_s + @includes = [] + @excludes = [] + end + + def include(*files) + @includes |= files + self + end + alias :<< :include + + def exclude(*files) + @excludes |= files + self + end + + def expand(file_map, path) + @includes = ['*'] if @includes.empty? + Zip::ZipFile.open(@zip_file) do |source| + source.entries.reject { |entry| entry.directory? }.each do |entry| + if @includes.any? { |pattern| File.fnmatch(pattern, entry.name) } && + !@excludes.any? { |pattern| File.fnmatch(pattern, entry.name) } + dest = path =~ /^\/?$/ ? entry.name : Util.relative_path(path + "/" + entry.name) + trace "Adding #{dest}" + file_map[dest] = lambda { |output| output.write source.read(entry) } + end + end + end + end + + end + + + def initialize(*args) #:nodoc: + super + clean + + # Make sure we're the last enhancements, so other enhancements can add content. + enhance do + @file_map = {} + enhance do + send 'create' if respond_to?(:create) + # We're here because the archive file does not exist, or one of the files is newer than the archive contents; + # we need to make sure the archive doesn't exist (e.g. opening an existing Zip will add instead of create). + # We also want to protect against partial updates. + rm name rescue nil + mkpath File.dirname(name) + begin + @paths.each do |name, object| + @file_map[name] = nil unless name.empty? + object.add_files(@file_map) + end + create_from @file_map + rescue + rm name rescue nil + raise + end + end + end + end + + # :call-seq: + # clean => self + # + # Removes all previously added content from this archive. + # Use this method if you want to remove default content from a package. + # For example, package(:jar) by default includes compiled classes and resources, + # using this method, you can create an empty jar and afterwards add the + # desired content to it. + # + # package(:jar).clean.include path_to('desired/content') + def clean + @paths = { '' => Path.new(self, '') } + @prepares = [] + self + end + + # :call-seq: + # include(*files) => self + # include(*files, :path=>path) => self + # include(file, :as=>name) => self + # include(:from=>path) => self + # include(*files, :merge=>true) => self + # + # Include files in this archive, or when called on a path, within that path. Returns self. + # + # The first form accepts a list of files, directories and glob patterns and adds them to the archive. + # For example, to include the file foo, directory bar (including all files in there) and all files under baz: + # zip(..).include('foo', 'bar', 'baz/*') + # + # The second form is similar but adds files/directories under the specified path. For example, + # to add foo as bar/foo: + # zip(..).include('foo', :path=>'bar') + # The :path option is the same as using the path method: + # zip(..).path('bar').include('foo') + # All other options can be used in combination with the :path option. + # + # The third form adds a file or directory under a different name. For example, to add the file foo under the + # name bar: + # zip(..).include('foo', :as=>'bar') + # + # The fourth form adds the contents of a directory using the directory as a prerequisite: + # zip(..).include(:from=>'foo') + # Unlike include('foo') it includes the contents of the directory, not the directory itself. + # Unlike include('foo/*'), it uses the directory timestamp for dependency management. + # + # The fifth form includes the contents of another archive by expanding it into this archive. For example: + # zip(..).include('foo.zip', :merge=>true).include('bar.zip') + # You can also use the method #merge. + def include(*files) + fail "AchiveTask.include() called with nil values" if files.include? nil + @paths[''].include *files if files.compact.size > 0 + self + end + alias :add :include + alias :<< :include + + # :call-seq: + # exclude(*files) => self + # + # Excludes files and returns self. Can be used in combination with include to prevent some files from being included. + def exclude(*files) + @paths[''].exclude *files + self + end + + # :call-seq: + # merge(*files) => Merge + # merge(*files, :path=>name) => Merge + # + # Merges another archive into this one by including the individual files from the merged archive. + # + # Returns an object that supports two methods: include and exclude. You can use these methods to merge + # only specific files. For example: + # zip(..).merge('src.zip').include('module1/*') + def merge(*files) + @paths[''].merge *files + end + + # :call-seq: + # path(name) => Path + # + # Returns a path object. Use the path object to include files under a path, for example, to include + # the file 'foo' as 'bar/foo': + # zip(..).path('bar').include('foo') + # + # Returns a Path object. The Path object implements all the same methods, like include, exclude, merge + # and so forth. It also implements path and root, so that: + # path('foo').path('bar') == path('foo/bar') + # path('foo').root == root + def path(name) + return @paths[''] if name.nil? + normalized = name.split('/').inject([]) do |path, part| + case part + when '.', nil, '' + path + when '..' + path[0...-1] + else + path << part + end + end.join('/') + @paths[normalized] ||= Path.new(self, normalized) + end + + # :call-seq: + # root => ArchiveTask + # + # Call this on an archive to return itself, and on a path to return the archive. + def root + self + end + + # :call-seq: + # with(options) => self + # + # Passes options to the task and returns self. Some tasks support additional options, for example, + # the WarTask supports options like :manifest, :libs and :classes. + # + # For example: + # package(:jar).with(:manifest=>'MANIFEST_MF') + def with(options) + options.each do |key, value| + begin + send "#{key}=", value + rescue NoMethodError + raise ArgumentError, "#{self.class.name} does not support the option #{key}" + end + end + self + end + + def invoke_prerequisites(args, chain) #:nodoc: + @prepares.each { |prepare| prepare.call(self) } + @prepares.clear + + file_map = {} + @paths.each do |name, path| + path.add_files(file_map) + end + + # filter out Procs (dynamic content), nils and others + @prerequisites |= file_map.values.select { |src| src.is_a?(String) || src.is_a?(Rake::Task) } + + super + end + + def needed? #:nodoc: + return true unless File.exist?(name) + # You can do something like: + # include('foo', :path=>'foo').exclude('foo/bar', path=>'foo'). + # include('foo/bar', :path=>'foo/bar') + # This will play havoc if we handled all the prerequisites together + # under the task, so instead we handle them individually for each path. + # + # We need to check that any file we include is not newer than the + # contents of the Zip. The file itself but also the directory it's + # coming from, since some tasks touch the directory, e.g. when the + # content of target/classes is included into a WAR. + most_recent = @paths.collect { |name, path| path.sources }.flatten. + select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max + File.stat(name).mtime < (most_recent || Rake::EARLY) || super + end + + # :call-seq: + # empty? => boolean + # + # Returns true if this ZIP file is empty (has no other entries inside). + def empty? + path("").empty + end + + # :call-seq: + # contain(file*) => boolean + # + # Returns true if this ZIP file contains all the specified files. You can use absolute + # file names and glob patterns (using *, **, etc). + def contain?(*files) + path("").contain?(*files) + end + + protected + + # Adds a prepare block. These blocks are called early on for adding more content to + # the archive, before invoking prerequsities. Anything you add here will be invoked + # as a prerequisite and used to determine whether or not to generate this archive. + # In contrast, enhance blocks are evaluated after it was decided to create this archive. + def prepare(&block) + @prepares << block + end + + def []=(key, value) #:nodoc: + raise ArgumentError, "This task does not support the option #{key}." + end + + end + + +end diff --git a/buildr/lib/buildr/packaging/artifact.rb b/buildr/lib/buildr/packaging/artifact.rb new file mode 100644 index 0000000..e7f0568 --- /dev/null +++ b/buildr/lib/buildr/packaging/artifact.rb @@ -0,0 +1,908 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + desc 'Download all artifacts' + task 'artifacts' + + desc "Download all artifacts' sources" + task 'artifacts:sources' + + desc "Download all artifacts' javadoc" + task 'artifacts:javadoc' + + # Mixin with a task to make it behave like an artifact. Implemented by the packaging tasks. + # + # An artifact has an identifier, group identifier, type, version number and + # optional classifier. All can be used to locate it in the local repository, + # download from or upload to a remote repository. + # + # The #to_spec and #to_hash methods allow it to be used everywhere an artifact is + # accepted. + module ActsAsArtifact + + ARTIFACT_ATTRIBUTES = [:group, :id, :type, :classifier, :version] + + class << self + private + + # :stopdoc: + def included(mod) + mod.extend self + end + + def extend_object(base) + base.instance_eval { alias :install_old :install } if base.respond_to? :install + base.instance_eval { alias :uninstall_old :uninstall } if base.respond_to? :uninstall + base.instance_eval { alias :upload_old :upload } if base.respond_to? :upload + super + end + + def extended(base) + #We try to keep the previous instance methods defined on the base instance if there were ones. + base.instance_eval { alias :install :install_old } if base.respond_to? :install_old + base.instance_eval { alias :uninstall :uninstall_old } if base.respond_to? :uninstall_old + base.instance_eval { alias :upload :upload_old } if base.respond_to? :upload_old + end + + # :startdoc: + end + + # The artifact identifier. + attr_reader :id + # The group identifier. + attr_reader :group + # The file type. (Symbol) + attr_reader :type + # The version number. + attr_reader :version + # Optional artifact classifier. + attr_reader :classifier + + def snapshot? + version =~ /-SNAPSHOT$/ + end + + # :call-seq: + # to_spec_hash => Hash + # + # Returns the artifact specification as a hash. For example: + # com.example:app:jar:1.2 + # becomes: + # { :group=>'com.example', + # :id=>'app', + # :type=>:jar, + # :version=>'1.2' } + def to_spec_hash + base = { :group=>group, :id=>id, :type=>type, :version=>version } + classifier ? base.merge(:classifier=>classifier) : base + end + alias_method :to_hash, :to_spec_hash + + # :call-seq: + # to_spec => String + # + # Returns the artifact specification, in the structure: + # ::: + # or + # :::: + def to_spec + classifier ? "#{group}:#{id}:#{type}:#{classifier}:#{version}" : "#{group}:#{id}:#{type}:#{version}" + end + + # :call-seq: + # pom => Artifact + # + # Convenience method that returns a POM artifact. + def pom + return self if type == :pom + Buildr.artifact(:group=>group, :id=>id, :version=>version, :type=>:pom) + end + + # :call-seq: + # sources_artifact => Artifact + # + # Convenience method that returns a sources artifact. + def sources_artifact + sources_spec = to_spec_hash.merge(:classifier=>'sources') + sources_task = OptionalArtifact.define_task(Buildr.repositories.locate(sources_spec)) + sources_task.send :apply_spec, sources_spec + sources_task + end + + # :call-seq: + # javadoc_artifact => Artifact + # + # Convenience method that returns the associated javadoc artifact + def javadoc_artifact + javadoc_spec = to_spec_hash.merge(:classifier=>'javadoc') + javadoc_task = OptionalArtifact.define_task(Buildr.repositories.locate(javadoc_spec)) + javadoc_task.send :apply_spec, javadoc_spec + javadoc_task + end + + # :call-seq: + # pom_xml => string + # + # Creates POM XML for this artifact. + def pom_xml + xml = Builder::XmlMarkup.new(:indent=>2) + xml.instruct! + xml.project do + xml.modelVersion '4.0.0' + xml.groupId group + xml.artifactId id + xml.version version + xml.classifier classifier if classifier + end + end + + def install + invoke + in_local_repository = Buildr.repositories.locate(self) + if pom && pom != self && classifier.nil? + pom.invoke + pom.install + end + if name != in_local_repository + mkpath File.dirname(in_local_repository) + cp name, in_local_repository, :preserve => false + info "Installed #{name} to #{in_local_repository}" + end + end + + def uninstall + installed = Buildr.repositories.locate(self) + rm installed if File.exist?(installed) + pom.uninstall if pom && pom != self && classifier.nil? + end + + # :call-seq: + # upload + # upload(url) + # upload(options) + # + # Uploads the artifact, its POM and digital signatures to remote server. + # + # In the first form, uses the upload options specified by repositories.release_to. + # In the second form, uses a URL that includes all the relevant information. + # In the third form, uses a hash with the options :url, :username, :password, + # and :permissions. All but :url are optional. + def upload(upload_to = nil) + upload_task(upload_to).invoke + end + + def upload_task(upload_to = nil) + upload_to ||= Buildr.repositories.release_to + upload_to = { :url=>upload_to } unless Hash === upload_to + raise ArgumentError, 'Don\'t know where to upload, perhaps you forgot to set repositories.release_to' unless upload_to[:url] + + # Set the upload URI, including mandatory slash (we expect it to be the base directory). + # Username/password may be part of URI, or separate entities. + uri = URI.parse(upload_to[:url].clone) + uri.path = uri.path + '/' unless uri.path[-1] == '/' + uri.user = upload_to[:username] if upload_to[:username] + uri.password = upload_to[:password] if upload_to[:password] + + path = group.gsub('.', '/') + "/#{id}/#{version}/#{File.basename(name)}" + + unless task = Buildr.application.lookup(uri+path) + deps = [self] + deps << pom.upload_task( upload_to ) if pom && pom != self && classifier.nil? + + task = Rake::Task.define_task uri + path => deps do + # Upload artifact relative to base URL, need to create path before uploading. + options = upload_to[:options] || {:permissions => upload_to[:permissions]} + info "Deploying #{to_spec}" + URI.upload uri + path, name, options + end + end + task + end + + protected + + # Apply specification to this artifact. + def apply_spec(spec) + spec = Artifact.to_hash(spec) + ARTIFACT_ATTRIBUTES.each { |key| instance_variable_set("@#{key}", spec[key]) } + self + end + + def group_path + group.gsub('.', '/') + end + + end + + + # A file task referencing an artifact in the local repository. + # + # This task includes all the artifact attributes (group, id, version, etc). It points + # to the artifact's path in the local repository. When invoked, it will download the + # artifact into the local repository if the artifact does not already exist. + # + # Note: You can enhance this task to create the artifact yourself, e.g. download it from + # a site that doesn't have a remote repository structure, copy it from a different disk, etc. + class Artifact < Rake::FileTask + + # The default artifact type. + DEFAULT_TYPE = :jar + + include ActsAsArtifact, Buildr + + class << self + + # :call-seq: + # lookup(spec) => Artifact + # + # Lookup a previously registered artifact task based on its specification (String or Hash). + def lookup(spec) + @artifacts ||= {} + @artifacts[to_spec(spec)] + end + + # :call-seq: + # list => specs + # + # Returns an array of specs for all the registered artifacts. (Anything created from artifact, or package). + def list + @artifacts ||= {} + @artifacts.keys + end + + # :call-seq: + # register(artifacts) => artifacts + # + # Register an artifact task(s) for later lookup (see #lookup). + def register(*tasks) + @artifacts ||= {} + fail 'You can only register an artifact task, one of the arguments is not a Task that responds to to_spec' unless + tasks.all? { |task| task.respond_to?(:to_spec) && task.respond_to?(:invoke) } + tasks.each { |task| @artifacts[task.to_spec] = task } + tasks + end + + # :call-seq: + # to_hash(spec_hash) => spec_hash + # to_hash(spec_string) => spec_hash + # to_hash(artifact) => spec_hash + # + # Turn a spec into a hash. This method accepts a String, Hash or any object that responds to + # the method to_spec. There are several reasons to use this method: + # * You can pass anything that could possibly be a spec, and get a hash. + # * It will check that the spec includes the group identifier, artifact + # identifier and version number and set the file type, if missing. + # * It will always return a new specs hash. + def to_hash(spec) + if spec.respond_to?(:to_spec) + to_hash spec.to_spec + elsif Hash === spec + rake_check_options spec, :id, :group, :type, :classifier, :version + # Sanitize the hash and check it's valid. + spec = ARTIFACT_ATTRIBUTES.inject({}) { |h, k| h[k] = spec[k].to_s if spec[k] ; h } + fail "Missing group identifier for #{spec.inspect}" unless spec[:group] + fail "Missing artifact identifier for #{spec.inspect}" unless spec[:id] + fail "Missing version for #{spec.inspect}" unless spec[:version] + spec[:type] = (spec[:type] || DEFAULT_TYPE).to_sym + spec + elsif String === spec + group, id, type, version, *rest = spec.split(':').map { |part| part.empty? ? nil : part } + unless rest.empty? + # Optional classifier comes before version. + classifier, version = version, rest.shift + fail "Expecting or , found <#{spec}>" unless rest.empty? + end + to_hash :group=>group, :id=>id, :type=>type, :version=>version, :classifier=>classifier + else + fail 'Expecting a String, Hash or object that responds to to_spec' + end + end + + # :call-seq: + # to_spec(spec_hash) => spec_string + # + # Convert a hash back to a spec string. This method accepts + # a string, hash or any object that responds to to_spec. + def to_spec(hash) + hash = to_hash(hash) unless Hash === hash + version = ":#{hash[:version]}" if hash[:version] + classifier = ":#{hash[:classifier]}" if hash[:classifier] + "#{hash[:group]}:#{hash[:id]}:#{hash[:type] || DEFAULT_TYPE}#{classifier}#{version}" + end + + # :call-seq: + # hash_to_file_name(spec_hash) => file_name + # + # Convert a hash spec to a file name. + def hash_to_file_name(hash) + version = "-#{hash[:version]}" if hash[:version] + classifier = "-#{hash[:classifier]}" if hash[:classifier] + "#{hash[:id]}#{version}#{classifier}.#{hash[:type] || DEFAULT_TYPE}" + end + + end + + def initialize(*args) #:nodoc: + super + enhance do |task| + # Default behavior: download the artifact from one of the remote repositories + # if the file does not exist. But this default behavior is counter productive + # if the artifact knows how to build itself (e.g. download from a different location), + # so don't perform it if the task found a different way to create the artifact. + task.enhance do + if download_needed? task + info "Downloading #{to_spec}" + download + pom.invoke rescue nil if pom && pom != self && classifier.nil? + end + end + end + end + + # :call-seq: + # from(path) => self + # + # Use this when you want to install or upload an artifact from a given file, for example: + # test = artifact('group:id:jar:1.0').from('test.jar') + # install test + # See also Buildr#install and Buildr#upload. + def from(path) + @from = path.is_a?(Rake::Task) ? path : File.expand_path(path.to_s) + enhance [@from] do + mkpath File.dirname(name) + cp @from.to_s, name + end + pom.content pom_xml unless pom == self || pom.has_content? + self + end + + # :call-seq: + # content(string) => self + # + # Use this when you want to install or upload an artifact from a given content, for example: + # readme = artifact('com.example:readme:txt:1.0').content(<<-EOF + # Please visit our website at http://example.com/readme + # <error + info error + trace error.backtrace.join("\n") + false + end + end + + if exact_success + return + elsif snapshot? + download_m2_snapshot(remote) + else + fail_download(remote) + end + end + + def download_m2_snapshot(remote_uris) + remote_uris.find do |repo_url| + snapshot_url = current_snapshot_repo_url(repo_url) + if snapshot_url + begin + download_artifact snapshot_url + true + rescue URI::NotFoundError + false + end + else + false + end + end or fail_download(remote_uris) + end + + def current_snapshot_repo_url(repo_url) + begin + metadata_path = "#{group_path}/#{id}/#{version}/maven-metadata.xml" + metadata_xml = StringIO.new + URI.download repo_url + metadata_path, metadata_xml + metadata = REXML::Document.new(metadata_xml.string).root + timestamp = REXML::XPath.first(metadata, '//timestamp') + build_number = REXML::XPath.first(metadata, '//buildNumber') + error "No timestamp provided for the snapshot #{to_spec}" if timestamp.nil? + error "No build number provided for the snapshot #{to_spec}" if build_number.nil? + return nil if timestamp.nil? || build_number.nil? + snapshot_of = version[0, version.size - 9] + classifier_snippet = (classifier != nil) ? "-#{classifier}" : "" + repo_url + "#{group_path}/#{id}/#{version}/#{id}-#{snapshot_of}-#{timestamp.text}-#{build_number.text}#{classifier_snippet}.#{type}" + rescue URI::NotFoundError + nil + end + end + + def fail_download(remote_uris) + fail "Failed to download #{to_spec}, tried the following repositories:\n#{remote_uris.join("\n")}" + end + + protected + + # :call-seq: + # needed? + # + # Validates whether artifact is required to be downloaded from repository + def needed? + return true if snapshot? && File.exist?(name) && (update_snapshot? || old?) + super + end + + private + + # :call-seq: + # download_artifact + # + # Downloads artifact from given repository, + # supports downloading snapshot artifact with relocation on succeed to local repository + def download_artifact(path) + download_file = "#{name}.#{Time.new.to_i}" + begin + URI.download path, download_file + if File.exist?(download_file) + FileUtils.mkdir_p(File.dirname(name)) + FileUtils.mv(download_file, name) + end + ensure + File.delete(download_file) if File.exist?(download_file) + end + end + + # :call-seq: + # :download_needed? + # + # Validates whether artifact is required to be downloaded from repository + def download_needed?(task) + return true if !File.exist?(name) + + if snapshot? + return false if offline? && File.exist?(name) + return true if update_snapshot? || old? + end + + return false + end + + def update_snapshot? + Buildr.application.options.update_snapshots + end + + def offline? + Buildr.application.options.work_offline + end + + # :call-seq: + # old? + # + # Checks whether existing artifact is older than period from build settings or one day + def old? + settings = Buildr.application.settings + time_to_be_old = settings.user[:expire_time] || settings.build[:expire_time] || 60 * 60 * 24 + File.mtime(name).to_i < (Time.new.to_i - time_to_be_old) + end + + end + + + # An artifact that is optional. + # If downloading fails, the user will be informed but it will not raise an exception. + class OptionalArtifact < Artifact + + protected + + # If downloading fails, the user will be informed but it will not raise an exception. + def download + super + rescue + info "Failed to download #{to_spec}. Skipping it." + end + + end + + + # Holds the path to the local repository, URLs for remote repositories, and settings for release server. + # + # You can access this object from the #repositories method. For example: + # puts repositories.local + # repositories.remote << 'http://example.com/repo' + # repositories.release_to = 'sftp://example.com/var/www/public/repo' + class Repositories + include Singleton + + # :call-seq: + # local => path + # + # Returns the path to the local repository. + # + # The default path is .m2/repository relative to the home directory. + # You can set this using the M2_REPO environment variable or the repositories/local + # value in your settings.yaml file. + def local + @local ||= File.expand_path(ENV['M2_REPO'] || ENV['local_repo'] || + (Buildr.settings.user['repositories'] && Buildr.settings.user['repositories']['local']) || + File.join(ENV['HOME'], '.m2/repository')) + end + + # :call-seq: + # local = path + # + # Sets the path to the local repository. + # + # The best place to set the local repository path is from a buildr.rb file + # located in the .buildr directory under your home directory. That way all + # your projects will share the same path, without affecting other developers + # collaborating on these projects. + def local=(dir) + @local = dir ? File.expand_path(dir) : nil + end + + # :call-seq: + # locate(spec) => path + # + # Locates an artifact in the local repository based on its specification, and returns + # a file path. + # + # For example: + # locate :group=>'log4j', :id=>'log4j', :version=>'1.1' + # => ~/.m2/repository/log4j/log4j/1.1/log4j-1.1.jar + def locate(spec) + spec = Artifact.to_hash(spec) + File.join(local, spec[:group].split('.'), spec[:id], spec[:version], Artifact.hash_to_file_name(spec)) + end + + # :call-seq: + # remote => Array + # + # Returns an array of all the remote repository URLs. + # + # When downloading artifacts, repositories are accessed in the order in which they appear here. + # The best way is to add repositories individually, for example: + # repositories.remote << 'http://example.com/repo' + # + # You can also specify remote repositories in the settings.yaml (per user) and build.yaml (per build) + # files. Both sets of URLs are loaded by default into this array, URLs from the personal setting + # showing first. + # + # For example: + # repositories: + # remote: + # - http://example.com/repo + # - http://elsewhere.com/repo + def remote + unless @remote + @remote = [Buildr.settings.user, Buildr.settings.build].inject([]) { |repos, hash| + repos | Array(hash['repositories'] && hash['repositories']['remote']) + } + end + @remote + end + + # :call-seq: + # remote = Array + # remote = url + # remote = nil + # + # With a String argument, clears the array and set it to that single URL. + # + # With an Array argument, clears the array and set it to these specific URLs. + # + # With nil, clears the array. + def remote=(urls) + case urls + when nil then @remote = nil + when Array then @remote = urls.dup + else @remote = [urls.to_s] + end + end + + # :call-seq: + # release_to = url + # release_to = hash + # + # Specifies the release server. Accepts a Hash with different repository settings + # (e.g. url, username, password), or a String to only set the repository URL. + # + # Besides the URL, all other settings depend on the transport protocol in use. + # + # For example: + # repositories.release_to = 'sftp://john:secret@example.com/var/www/repo/' + # + # repositories.release_to = { :url=>'sftp://example.com/var/www/repo/', + # :username='john', :password=>'secret' } + # Or in the settings.yaml file: + # repositories: + # release_to: sftp://john:secret@example.com/var/www/repo/ + # + # repositories: + # release_to: + # url: sftp://example.com/var/www/repo/ + # username: john + # password: secret + def release_to=(options) + options = { :url=>options } unless Hash === options + @release_to = options + end + + # :call-seq: + # release_to => hash + # + # Returns the current release server setting as a Hash. This is a more convenient way to + # configure the settings, as it allows you to specify the settings progressively. + # + # For example, the Buildfile will contain the repository URL used by all developers: + # repositories.release_to[:url] ||= 'sftp://example.com/var/www/repo' + # Your private buildr.rb will contain your credentials: + # repositories.release_to[:username] = 'john' + # repositories.release_to[:password] = 'secret' + def release_to + unless @release_to + value = (Buildr.settings.user['repositories'] && Buildr.settings.user['repositories']['release_to']) \ + || (Buildr.settings.build['repositories'] && Buildr.settings.build['repositories']['release_to']) + @release_to = Hash === value ? value.inject({}) { |hash, (key, value)| hash.update(key.to_sym=>value) } : { :url=>Array(value).first } + end + @release_to + end + + end + + # :call-seq: + # repositories => Repositories + # + # Returns an object you can use for setting the local repository path, remote repositories + # URL and release server settings. + # + # See Repositories. + def repositories + Repositories.instance + end + + # :call-seq: + # artifact(spec) => Artifact + # artifact(spec) { |task| ... } => Artifact + # + # Creates a file task to download and install the specified artifact in the local repository. + # + # You can use a String or a Hash for the artifact specification. The file task will point at + # the artifact's path inside the local repository. You can then use this tasks as a prerequisite + # for other tasks. + # + # This task will download and install the artifact only once. In fact, it will download and + # install the artifact if the artifact does not already exist. You can enhance it if you have + # a different way of creating the artifact in the local repository. See Artifact for more details. + # + # For example, to specify an artifact: + # artifact('log4j:log4j:jar:1.1') + # + # To use the artifact in a task: + # compile.with artifact('log4j:log4j:jar:1.1') + # + # To specify an artifact and the means for creating it: + # download(artifact('dojo:dojo-widget:zip:2.0')=> + # 'http://download.dojotoolkit.org/release-2.0/dojo-2.0-widget.zip') + def artifact(spec, path = nil, &block) #:yields:task + spec = artifact_ns.fetch(spec) if spec.kind_of?(Symbol) + spec = Artifact.to_hash(spec) + unless task = Artifact.lookup(spec) + task = Artifact.define_task(path || repositories.locate(spec)) + task.send :apply_spec, spec + Rake::Task['rake:artifacts'].enhance [task] + Artifact.register(task) + unless spec[:type] == :pom + Rake::Task['artifacts:sources'].enhance [task.sources_artifact] + Rake::Task['artifacts:javadoc'].enhance [task.javadoc_artifact] + end + end + task.enhance &block + end + + # :call-seq: + # artifacts(*spec) => artifacts + # + # Handles multiple artifacts at a time. This method is the plural equivalent of + # #artifact, but can do more things. + # + # Returns an array of artifacts built using the supplied + # specifications, each of which can be: + # * An artifact specification (String or Hash). Returns the appropriate Artifact task. + # * An artifact of any other task. Returns the task as is. + # * A project. Returns all artifacts created (packaged) by that project. + # * A string. Returns that string, assumed to be a file name. + # * An array of artifacts or a Struct. + # * A symbol. Returns the named artifact from the current ArtifactNamespace + # + # For example, handling a collection of artifacts: + # xml = [ xerces, xalan, jaxp ] + # ws = [ axis, jax-ws, jaxb ] + # db = [ jpa, mysql, sqltools ] + # artifacts(xml, ws, db) + # + # Using artifacts created by a project: + # artifacts project('my-app') # All packages + # artifacts project('my-app').package(:war) # Only the WAR + def artifacts(*specs, &block) + specs.flatten.inject([]) do |set, spec| + case spec + when ArtifactNamespace + set |= spec.artifacts + when Symbol, Hash + set |= [artifact(spec)] + when /([^:]+:){2,4}/ # A spec as opposed to a file name. + set |= [artifact(spec)] + when String # Must always expand path. + set |= [File.expand_path(spec)] + when Project + set |= artifacts(spec.packages) + when Rake::Task + set |= [spec] + when Struct + set |= artifacts(spec.values) + else + if spec.respond_to? :to_spec + set |= artifacts(spec.to_spec) + else + fail "Invalid artifact specification in #{specs.inspect}" + end + end + end + end + + def transitive(*args) + options = Hash === args.last ? args.pop : {} + dep_opts = { + :scopes => options[:scopes] || [nil, "compile", "runtime", "provided"], + :optional => options[:optional] + } + specs = args.flatten + specs.inject([]) do |set, spec| + case spec + when /([^:]+:){2,4}/ # A spec as opposed to a file name. + artifact = artifact(spec) + set |= [artifact] unless artifact.type == :pom + set |= POM.load(artifact.pom).dependencies(dep_opts).map { |spec| artifact(spec) } + when Hash + set |= [transitive(spec, options)] + when String # Must always expand path. + set |= transitive(file(File.expand_path(spec)), options) + when Project + set |= transitive(spec.packages, options) + when Rake::Task + set |= spec.respond_to?(:to_spec) ? transitive(spec.to_spec, options) : [spec] + when Struct + set |= transitive(spec.values, options) + else + fail "Invalid artifact specification in: #{specs.to_s}" + end + end + end + + # :call-seq: + # group(ids, :under=>group_name, :version=>number) => artifacts + # + # Convenience method for defining multiple artifacts that belong to the same group, type and version. + # Accepts multiple artifact identifiers followed by two or three hash values: + # * :under -- The group identifier + # * :version -- The version number + # * :type -- The artifact type (optional) + # * :classifier -- The artifact classifier (optional) + # + # For example: + # group 'xbean', 'xbean_xpath', 'xmlpublic', :under=>'xmlbeans', :version=>'2.1.0' + # Or: + # group %w{xbean xbean_xpath xmlpublic}, :under=>'xmlbeans', :version=>'2.1.0' + def group(*args) + hash = args.pop + args.flatten.map do |id| + artifact :group => hash[:under], + :type => hash[:type], + :version => hash[:version], + :classifier => hash[:classifier], + :id => id + end + end + + # :call-seq: + # install(artifacts) => install_task + # + # Installs the specified artifacts in the local repository as part of the install task. + # + # You can use this to install various files in the local repository, for example: + # install artifact('group:id:jar:1.0').from('some_jar.jar') + # $ buildr install + def install(*args, &block) + artifacts = artifacts(args).uniq + raise ArgumentError, 'This method can only install artifacts' unless artifacts.all? { |f| f.respond_to?(:to_spec) } + task('install').tap do |install| + install.enhance(artifacts) do + artifacts.each(&:install) + end + install.enhance &block if block + task('uninstall') do + artifacts.map(&:to_s ).each { |file| rm file if File.exist?(file) } + end + end + end + + # :call-seq: + # upload(artifacts) + # + # Uploads the specified artifacts to the release server as part of the upload task. + # + # You can use this to upload various files to the release server, for example: + # upload artifact('group:id:jar:1.0').from('some_jar.jar') + # $ buildr upload + def upload(*args, &block) + artifacts = artifacts(args) + raise ArgumentError, 'This method can only upload artifacts' unless artifacts.all? { |f| f.respond_to?(:to_spec) } + upload_artifacts_tasks = artifacts.map { |artifact| artifact.upload_task } + task('upload').tap do |task| + task.enhance &block if block + task.enhance upload_artifacts_tasks + end + end + +end diff --git a/buildr/lib/buildr/packaging/artifact_namespace.rb b/buildr/lib/buildr/packaging/artifact_namespace.rb new file mode 100644 index 0000000..12b24e0 --- /dev/null +++ b/buildr/lib/buildr/packaging/artifact_namespace.rb @@ -0,0 +1,1011 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # An ArtifactNamespace is a hierarchical dictionary used to manage ArtifactRequirements. + # It can be used to have different artifact versions per project + # or to allow users to select a version for addons or modules. + # + # Namespaces are opened using the Buildr.artifact_ns method, most important methods are: + # [need] Used to create a requirement on the namespace. + # [use] Set the artifact version to use for a requirement. + # [values_at] Reference requirements by name. + # [each] Return each ArtifactRequirement in the namespace. + # The method_missing method for instances provides some syntactic sugar to these. + # See the following examples, and the methods for ArtifactRequirement. + # + # = Avoiding constant pollution on buildfile + # + # Each project has its own ArtifactNamespace inheriting the one from the + # parent project up to the root namespace. + # + # Consider the following snippet, as project grows, each subproject + # may need different artifact combinations and/or versions. Assigning + # artifact specifications to constants can make it painful to maintain + # their references even if using structs/hashes. + # + # -- buildfile -- + # SPRING = 'org.springframework:spring:jar:2.5' + # SPRING_OLD = 'org.springframework:spring:jar:1.0' + # LOGGING = ['comons-logging:commons-logging:jar:1.1.1', + # 'log4j:log4j:jar:1.2.15'] + # WL_LOGGING = artifact('bea:wlcommons-logging:jar:8.1').from('path/to/wlcommons-logging.jar') + # LOGGING_WEBLOGIC = ['comons-logging:commons-logging:jar:1.1.1', + # WL_LOGGING] + # COMMONS = struct :collections => 'commons-collection:commons-collection:jar:3.1', + # :net => 'commons-net:commons-net:jar:1.4.0' + # + # define 'example1' do + # define 'one' do + # compile.with SPRING, LOGGING_WEBLOGIC, COMMONS + # end + # define 'two' do + # compile.with SPRING_OLD, LOGGING, COMMONS + # end + # define 'three' do + # compile.with "commons-collections:commons-collections:jar:2.2" + # end + # end + # + # + # With ArtifactNamespace you can do some more advanced stuff, the following + # annotated snipped could still be reduced if default artifact definitions were + # loaded from yaml file (see section below and ArtifactNamespace.load). + # + # -- buildfile -- + # artifact_ns do |ns| # the current namespace (root if called outside a project) + # # default artifacts + # ns.spring = 'org.springframework:spring:jar:2.5' + # # default logger is log4j + # ns.logger = 'log4j:log4j:jar:1.2.15' + # + # # create a sub namespace by calling the #ns method, + # # artifacts defined on the sub-namespace can be referenced by + # # name :commons_net or by calling commons.net + # ns.ns :commons, :net => 'commons-net:commons-net:jar:1.4.0', + # :logging => 'comons-logging:commons-logging:jar:1.1.1' + # + # + # # When a child namespace asks for the :log artifact, + # # these artifacts will be searched starting from the :current namespace. + # ns.virtual :log, :logger, :commons_logging + # end + # + # artifact_ns('example2:one') do |ns| # namespace for the one subproject + # ns.logger = artifact('bea:wlcommons-logging:jar:8.1').from('path/to/wlcommons-logging.jar') + # end + # artifact_ns('example2:two') do |ns| + # ns.spring = '1.0' # for project two use an older spring version (just for an example) + # end + # artifact_ns('example2:three').commons_collections = 2.2' + # artifact_ns('example2:four') do |ns| + # ns.beanutils = 'commons-beanutils:commons-beanutils:jar:1.5' # just for this project + # ns.ns(:compilation).use :commons_logging, :beanutils, :spring # compile time dependencies + # ns.ns(:testing).use :log, :beanutils, 'cglib:cglib-nodep:jar:2.1.3' # run time dependencies + # end + # + # define 'example2' do + # define 'one' do + # compile.with :spring, :log, :commons # uses weblogic logging + # end + # define 'two' do + # compile.with :spring, :log, :commons # will take old spring + # end + # define 'three' do + # compile.with :commons_collections + # test.with artifact_ns('example2:two').spring # use spring from project two + # end + # define 'four' do + # compile.with artifact_ns.compilation + # test.with artifact_ns.testing + # end + # task(:down_them_all) do # again, just to fill this space with something ;) + # parent.projects.map(&method(:artifact_ns)).map(&:artifacts).map(&:invoke) + # end + # end + # + # = Loading from a yaml file (e. profiles.yaml) + # + # If your projects use lots of jars (after all we are using java ;) you may prefer + # to have constant artifact definitions on an external file. + # Doing so would allow an external tool (or future Buildr feature) to maintain + # an artifacts.yaml for you. + # An example usage is documented on the ArtifactNamespace.load method. + # + # = For addon/plugin writers & Customizing artifact versions + # + # Sometimes users would need to change the default artifact versions used by some + # module, for example, the XMLBeans compiler needs this, because of compatibility + # issues. Another example would be to select the groovy version to use on all our + # projects so that Buildr modules requiring groovy jars can use user prefered versions. + # + # To meet this goal, an ArtifactNamespace allows to specify ArtifactRequirement objects. + # In fact the only difference with the examples you have already seen is that requirements + # have an associated VersionRequirement, so that each time a user tries to select a version, + # buildr checks if it satisfies the requirements. + # + # Requirements are declared using the ArtifactNamespace#need method, but again, + # syntactic sugar is provided by ArtifactNamespace#method_missing. + # + # The following example is taken from the XMLBeans compiler module. + # And illustrates how addon authors should specify their requirements, + # provide default versions, and document the namespace for users to customize. + # + # module Buildr::XMLBeans + # + # # You need to document this constant, giving users some hints + # # about when are (maybe some of) these artifacts used. I mean, + # # some modules, add jars to the Buildr classpath when its file + # # is required, you would need to tell your users, so that they + # # can open the namespace and specify their defaults. Of course + # # when the requirements are defined, buildr checks if any compatible + # # version has been already defined, if so, uses it. + # # + # # Some things here have been changed to illustrate their meaning. + # REQUIRES = ArtifactNamespace.for(self).tap do |ns| + # + # # This jar requires a >2.0 version, default being 2.3.0 + # ns.xmlbeans! 'org.apache.xmlbeans:xmlbeans:jar:2.3.0', '>2' + # + # # Users can customize with Buildr::XMLBeans::REQUIRES.stax_api = '1.2' + # # This is a non-flexible requirement, only satisfied by version 1.0.1 + # ns.stax_api! 'stax:stax-api:jar:1.0.1' + # + # # This one is not part of XMLBeans, but is just another example + # # illustrating an `artifact requirement spec`. + # + # ns.need " some_name -> ar:ti:fact:3.2.5 -> ( >2 & <4)" + # + # # As you can see it's just an artifact spec, prefixed with + # # ' some_name -> ', this means users can use that name to + # # reference the requirement, also this string has a VersionRequirement + # # just after another ->. + # end + # + # # The REQUIRES constant is an ArtifactNamespace instance, + # # that means we can use it directly. Note that calling + # # Buildr.artifact_ns would lead to the currently executing context, + # # not the one for this module. + # def use + # # test if user specified his own version, if so, we could perform some + # # functionallity based on this. + # REQUIRES.some_name.selected? # => false + # + # REQUIRES.some_name.satisfied_by?('1.5') # => false + # puts REQUIRES.some_name.requirement # => ( >2 & <4 ) + # + # REQUIRES.artifacts # get the Artifact tasks + # end + # + # end + # + # A more advanced example using ArtifactRequirement listeners is included + # in the artifact_namespace_spec.rb description for 'Extension using ArtifactNamespace' + # That's it for addon writers, now, users can select their prefered version with + # something like: + # + # require 'buildr/xmlbeans' + # Buildr::XMLBeans::REQUIRES.xmlbeans = '2.2.0' + # + # More advanced stuff, if users really need to select an xmlbeans version + # per project, they can do so letting :current (that is, the currently running + # namespace) be parent of the REQUIRES namespace: + # + # Buildr::XMLBeans::REQUIRES.parent = :current + # + # Now, provided that the compiler does not caches its artifacts, it will + # select the correct version. (See the first section for how to select per project + # artifacts). + # + # + class ArtifactNamespace + class << self + # Forget all namespaces, create a new ROOT + def clear + @instances = nil + remove_const(:ROOT) rescue nil + const_set(:ROOT, new('root')) + end + + # Differs from Artifact.to_hash in that 1) it does not choke when version isn't present + # and 2) it assumes that if an artifact spec ends with a colon, e.g. "org.example:library:jdk5:" + # it indicates the last segment ("jdk5") is a classifier. + def to_hash(spec) + if spec.respond_to?(:to_spec) + to_hash spec.to_spec + elsif Hash === spec + return spec + elsif String === spec || Symbol === spec + spec = spec.to_s + if spec[-1,1] == ':' + group, id, type, classifier, *rest = spec.split(':').map { |part| part.empty? ? nil : part } + else + group, id, type, version, *rest = spec.split(':').map { |part| part.empty? ? nil : part } + unless rest.empty? + # Optional classifier comes before version. + classifier, version = version, rest.shift + end + end + fail "Expecting or , found <#{spec}>" unless rest.empty? + { :group => group, :id => id, :type => type, :version => version, :classifier => classifier }.reject { |k,v| v == nil } + else + fail "Unexpected artifact spec: #{spec.inspect}" + end + end + + # Populate namespaces from a hash of hashes. + # The following example uses the profiles yaml to achieve this. + # + # -- profiles.yaml -- + # development: + # artifacts: + # root: # root namespace + # spring: org.springframework:spring:jar:2.5 + # groovy: org.codehaus.groovy:groovy:jar:1.5.4 + # logging: # define a named group + # - log4j:log4j:jar:1.2.15 + # - commons-logging:commons-logging:jar:1.1.1 + # + # # open Buildr::XMLBeans namespace + # Buildr::XMLBeans: + # xmlbeans: 2.2 + # + # # for subproject one:oldie + # one:oldie: + # spring: org.springframework:spring:jar:1.0 + # + # -- buildfile -- + # ArtifactNamespace.load(Buildr.settings.profile['artifacts']) + def load(namespaces = {}) + namespaces.each_pair { |name, uses| instance(name).use(uses) } + end + + # :call-seq: + # ArtifactNamespace.instance { |current_ns| ... } -> current_ns + # ArtifactNamespace.instance(name) { |ns| ... } -> ns + # ArtifactNamespace.instance(:current) { |current_ns| ... } -> current_ns + # ArtifactNamespace.instance(:root) { |root_ns| ... } -> root_ns + # + # Obtain an instance for the given name + def instance(name = nil) + case name + when :root, 'root' then return ROOT + when ArtifactNamespace then return name + when Array then name = name.join(':') + when Module, Project then name = name.name + when :current, 'current', nil then + task = Thread.current[:rake_chain] + task = task.instance_variable_get(:@value) if task + name = case task + when Project then task.name + when Rake::Task then task.scope.join(':') + when nil then Buildr.application.current_scope.join(':') + end + end + name = name.to_s + if name.size == 0 + instance = ROOT + else + name = name.to_s + @instances ||= Hash.new { |h, k| h[k] = new(k) } + instance = @instances[name] + end + yield(instance) if block_given? + instance + end + + alias_method :[], :instance + alias_method :for, :instance + + # :call-seq: + # ArtifactNamespace.root { |ns| ... } -> ns + # + # Obtain the root namespace, returns the ROOT constant + def root + yield ROOT if block_given? + ROOT + end + end + + module DClone #:nodoc: + def dclone + clone = self.clone + clone.instance_variables.each do |i| + value = clone.instance_variable_get(i) + value = value.dclone rescue + clone.instance_variable_set(i, value) + end + clone + end + end + + class Registry < Hash #:nodoc: + include DClone + + attr_accessor :parent + def alias(new_name, old_name) + new_name = new_name.to_sym + old_name = old_name.to_sym + if obj = get(old_name, true) + self[new_name] = obj + @aliases ||= [] + group = @aliases.find { |a| a.include?(new_name) } + group.delete(new_name) if group + group = @aliases.find { |a| a.include?(old_name) } + @aliases << (group = [old_name]) unless group + group << new_name unless group.include?(new_name) + end + obj + end + + def aliases(name) + return [] unless name + name = name.to_sym + ((@aliases ||= []).find { |a| a.include?(name) } || [name]).dup + end + + def []=(key, value) + return unless key + super(key.to_sym, value) + end + + def get(key, include_parent = nil) + [].tap { |a| aliases(key).select { |n| a[0] = self[n] } }.first || + (include_parent && parent && parent.get(key, include_parent)) + end + + def keys(include_parent = nil) + (super() | (include_parent && parent && parent.keys(include_parent) || [])).uniq + end + + def values(include_parent = nil) + (super() | (include_parent && parent && parent.values(include_parent) || [])).uniq + end + + def key?(key, include_parent = nil) + return false unless key + super(key.to_sym) || (include_parent && parent && parent.key?(key, include_parent)) + end + + def delete(key, include_parent = nil) + aliases(key).map {|n| super(n) } && include_parent && parent && parent.delete(key, include_parent) + end + end + + # An artifact requirement is an object that ActsAsArtifact and has + # an associated VersionRequirement. It also knows the name (some times equal to the + # artifact id) that is used to store it in an ArtifactNamespace. + class ArtifactRequirement + attr_accessor :version + attr_reader :name, :requirement + + include DClone + + # Create a requirement from an `artifact requirement spec`. + # This spec has three parts, separated by -> + # + # some_name -> ar:ti:fact:3.2.5 -> ( >2 & <4) + # + # As you can see it's just an artifact spec, prefixed with + # some_name -> + # the :some_name symbol becomes this object's name and + # is used to store it on an ArtifactNamespace. + # + # ar:ti:fact:3.2.5 + # + # The second part is an artifact spec by itself, and specifies + # all remaining attributes, the version of this spec becomes + # the default version of this requirement. + # + # The last part consist of a VersionRequirement. + # -> ( >2 & <4) + # + # VersionRequirement supports RubyGem's comparision operators + # in adition to parens, logical and, logical or and negation. + # See the docs for VersionRequirement for more info on operators. + def initialize(spec) + self.class.send :include, ActsAsArtifact unless ActsAsArtifact === self + if ArtifactRequirement === spec + copy_attrs(spec) + else + spec = requirement_hash(spec) + apply_spec_no_validation(spec[:spec]) + self.name = spec[:name] + @requirement = spec[:requirement] + @version = @requirement.default if VersionRequirement.requirement?(@version) + end + end + + def apply_spec_no_validation(spec) + spec = ArtifactNamespace.to_hash(spec) + ActsAsArtifact::ARTIFACT_ATTRIBUTES.each { |key| instance_variable_set("@#{key}", spec[key]) } + self + end + + # Copy attributes from other to this object + def copy_attrs(other) + (ActsAsArtifact::ARTIFACT_ATTRIBUTES + [:name, :requirement]).each do |attr| + value = other.instance_variable_get("@#{attr}") + value = value.dup if value && !value.kind_of?(Numeric) && !value.kind_of?(Symbol) + instance_variable_set("@#{attr}", value) + end + end + + def name=(name) + @name = name.to_s + end + + # Set a the requirement, this must be an string formatted for + # VersionRequirement#create to parse. + def requirement=(version_requirement) + @requirement = VersionRequirement.create(version_requirement.to_s) + end + + # Return a hash consisting of :name, :spec, :requirement + def requirement_hash(spec = self) + result = {} + if String === spec + parts = spec.split(/\s*->\s*/, 3).map(&:strip) + case parts.size + when 1 + result[:spec] = ArtifactNamespace.to_hash(parts.first) + when 2 + if /^\w+$/ === parts.first + result[:name] = parts.first + result[:spec] = ArtifactNamespace.to_hash(parts.last) + else + result[:spec] = ArtifactNamespace.to_hash(parts.first) + result[:requirement] = VersionRequirement.create(parts.last) + end + when 3 + result[:name] = parts.first + result[:spec] = ArtifactNamespace.to_hash(parts[1]) + result[:requirement] = VersionRequirement.create(parts.last) + end + else + result[:spec] = ArtifactNamespace.to_hash(spec) + end + result[:name] ||= result[:spec][:id].to_s.to_sym + result[:requirement] ||= VersionRequirement.create(result[:spec][:version]) + result + end + + # Test if this requirement is satisfied by an artifact spec. + def satisfied_by?(spec) + return false unless requirement + spec = ArtifactNamespace.to_hash(spec) + hash = to_spec_hash + hash.delete(:version) + version = spec.delete(:version) + hash == spec && requirement.satisfied_by?(version) + end + + # Has user selected a version for this requirement? + def selected? + @selected + end + + def selected! #:nodoc: + @selected = true + @listeners.each { |l| l.call(self) } if @listeners + self + end + + def add_listener(&callback) + (@listeners ||= []) << callback + end + + # Return the Artifact object for the currently selected version + def artifact + ::Buildr.artifact(self) + end + + # Format this requirement as an `artifact requirement spec` + def to_requirement_spec + result = to_spec + result = "#{name} -> #{result}" if name + result = "#{result} -> #{requirement}" if requirement + result + end + + def to_s #:nodoc: + id ? to_requirement_spec : version + end + + # Return an artifact spec without the version part. + def unversioned_spec + hash = to_spec_hash + return nil if hash.values.compact.length <= 1 + if hash[:classifier] + "#{hash[:group]}:#{hash[:id]}:#{hash[:type]}:#{hash[:classifier]}:" + else + "#{hash[:group]}:#{hash[:id]}:#{hash[:type]}" + end + end + + class << self + def unversioned_spec(spec) + hash = ArtifactNamespace.to_hash(spec) + return nil if hash.values.compact.length <= 1 + if hash[:classifier] + "#{hash[:group]}:#{hash[:id]}:#{hash[:type]}:#{hash[:classifier]}:" + else + "#{hash[:group]}:#{hash[:id]}:#{hash[:type]}" + end + end + end + end + + include DClone + include Enumerable + attr_reader :name + + def initialize(name = nil) #:nodoc: + @name = name.to_s if name + end + clear + + def root + yield ROOT if block_given? + ROOT + end + + # ROOT namespace has no parent + def parent + if root? + nil + elsif @parent.kind_of?(ArtifactNamespace) + @parent + elsif @parent + ArtifactNamespace.instance(@parent) + elsif name + parent_name = name.gsub(/::?[^:]+$/, '') + parent_name == name ? root : ArtifactNamespace.instance(parent_name) + else + root + end + end + + # Set the parent for the current namespace, except if it is ROOT + def parent=(other) + raise 'Cannot set parent of root namespace' if root? + @parent = other + @registry = nil + end + + # Is this the ROOT namespace? + def root? + ROOT == self + end + + # Create a named sub-namespace, sub-namespaces are themselves + # ArtifactNamespace instances but cannot be referenced by + # the Buildr.artifact_ns, ArtifactNamespace.instance methods. + # Reference needs to be through this object using the given +name+ + # + # artifact_ns('foo').ns(:bar).need :thing => 'some:thing:jar:1.0' + # artifact_ns('foo').bar # => the sub-namespace 'foo.bar' + # artifact_ns('foo').bar.thing # => the some thing artifact + # + # See the top level ArtifactNamespace documentation for examples + def ns(name, *uses, &block) + name = name.to_sym + sub = registry[name] + if sub + raise TypeError.new("#{name} is not a sub namespace of #{self}") unless sub.kind_of?(ArtifactNamespace) + else + sub = ArtifactNamespace.new("#{self.name}.#{name}") + sub.parent = self + registry[name] = sub + end + sub.use(*uses) + yield sub if block_given? + sub + end + + # Test if a sub-namespace by the given name exists + def ns?(name) + sub = registry[name.to_sym] + ArtifactNamespace === sub + end + + # :call-seq: + # artifact_ns.need 'name -> org:foo:bar:jar:~>1.2.3 -> 1.2.5' + # artifact_ns.need :name => 'org.foo:bar:jar:1.0' + # + # Create a new ArtifactRequirement on this namespace. + # ArtifactNamespace#method_missing provides syntactic sugar for this. + def need(*specs) + named = specs.flatten.inject({}) do |seen, spec| + if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty? + spec.each_pair do |name, spec| + if Array === spec # a group + seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) } + else + artifact = ArtifactRequirement.new(spec) + artifact.name = name + seen[artifact.name] ||= artifact + end + end + else + artifact = ArtifactRequirement.new(spec) + seen[artifact.name] ||= artifact + end + seen + end + named.each_pair do |name, artifact| + if Array === artifact # a group + artifact.each do |a| + unvers = a.unversioned_spec + previous = registry[unvers] + if previous && previous.selected? && a.satisfied_by?(previous) + a.version = previous.version + end + registry[unvers] = a + end + group(name, *(artifact.map { |a| a.unversioned_spec } + [{:namespace => self}])) + else + unvers = artifact.unversioned_spec + previous = registry.get(unvers, true) + if previous && previous.selected? && artifact.satisfied_by?(previous) + artifact.version = previous.version + artifact.selected! + end + registry[unvers] = artifact + registry.alias name, unvers unless name.to_s[/^\s*$/] + end + end + self + end + + # :call-seq: + # artifact_ns.use 'name -> org:foo:bar:jar:1.2.3' + # artifact_ns.use :name => 'org:foo:bar:jar:1.2.3' + # artifact_ns.use :name => '2.5.6' + # + # First and second form are equivalent, the third is used when an + # ArtifactRequirement has been previously defined with :name, so it + # just selects the version. + # + # ArtifactNamespace#method_missing provides syntactic sugar for this. + def use(*specs) + named = specs.flatten.inject({}) do |seen, spec| + if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty? + spec.each_pair do |name, spec| + if ArtifactNamespace === spec # create as subnamespace + raise ArgumentError.new("Circular reference") if self == spec + registry[name.to_sym] = spec + elsif Numeric === spec || (String === spec && VersionRequirement.version?(spec)) + artifact = ArtifactRequirement.allocate + artifact.name = name + artifact.version = spec.to_s + seen[artifact.name] ||= artifact + elsif Symbol === spec + self.alias name, spec + elsif Array === spec # a group + seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) } + else + artifact = ArtifactRequirement.new(spec) + artifact.name = name + seen[artifact.name] ||= artifact + end + end + else + if Symbol === spec + artifact = get(spec).dclone + else + artifact = ArtifactRequirement.new(spec) + end + seen[artifact.name] ||= artifact + end + seen + end + named.each_pair do |name, artifact| + is_group = Array === artifact + artifact = [artifact].flatten.map do |artifact| + unvers = artifact.unversioned_spec + previous = get(unvers, false) || get(name, false) + if previous # have previous on current namespace + if previous.requirement # we must satisfy the requirement + unless unvers # we only have the version + satisfied = previous.requirement.satisfied_by?(artifact.version) + else + satisfied = previous.satisfied_by?(artifact) + end + raise "Unsatisfied dependency #{previous} " + + "not satisfied by #{artifact}" unless satisfied + previous.version = artifact.version # OK, set new version + artifact = previous # use the same object for aliases + else # not a requirement, set the new values + unless artifact.id == previous.id && name != previous.name + previous.copy_attrs(artifact) + artifact = previous + end + end + else + if unvers.nil? && # we only have the version + (previous = get(unvers, true, false, false)) + version = artifact.version + artifact.copy_attrs(previous) + artifact.version = version + end + artifact.requirement = nil + end + artifact.selected! + end + artifact = artifact.first unless is_group + if is_group + names = artifact.map do |art| + unv = art.unversioned_spec + registry[unv] = art + unv + end + group(name, *(names + [{:namespace => self}])) + elsif artifact.id + unvers = artifact.unversioned_spec + registry[name] = artifact + registry.alias unvers, name + else + registry[name] = artifact + end + end + self + end + + # Like Hash#fetch + def fetch(name, default = nil, &block) + block ||= proc { raise IndexError.new("No artifact found by name #{name.inspect} in namespace #{self}") } + real_name = name.to_s[/^[\w\-\.]+$/] ? name : ArtifactRequirement.unversioned_spec(name) + get(real_name.to_sym) || default || block.call(name) + end + + # :call-seq: + # artifact_ns[:name] -> ArtifactRequirement + # artifact_ns[:many, :names] -> [ArtifactRequirement] + def [](*names) + ary = values_at(*names) + names.size == 1 ? ary.first : ary + end + + # :call-seq: + # artifact_ns[:name] = 'some:cool:jar:1.0.2' + # artifact_ns[:name] = '1.0.2' + # + # Just like the use method + def []=(*names) + values = names.pop + values = [values] unless Array === values + names.each_with_index do |name, i| + use name => (values[i] || values.last) + end + end + + # yield each ArtifactRequirement + def each(&block) + values.each(&block) + end + + # return Artifact objects for each requirement + def artifacts(*names) + (names.empty? && values || values_at(*names)).map(&:artifact) + end + + # Return all requirements for this namespace + def values(include_parents = false, include_groups = true) + seen, dict = {}, registry + while dict + dict.each do |k, v| + v = v.call if v.respond_to?(:call) + v = v.values if v.kind_of?(ArtifactNamespace) + if Array === v && include_groups + v.compact.each { |v| seen[v.name] = v unless seen.key?(v.name) } + else + seen[v.name] = v unless seen.key?(v.name) + end + end + dict = include_parents ? dict.parent : nil + end + seen.values + end + + # Return only the named requirements + def values_at(*names) + names.map do |name| + catch :artifact do + unless name.to_s[/^[\w\-\.]+$/] + unvers = ArtifactRequirement.unversioned_spec(name) + unless unvers.to_s == name.to_s + req = ArtifactRequirement.new(name) + reg = self + while reg + candidate = reg.send(:get, unvers, false, false, true) + throw :artifact, candidate if req.satisfied_by?(candidate) + reg = reg.parent + end + end + end + get(name.to_sym) + end + end + end + + def key?(name, include_parents = false) + name = ArtifactRequirement.unversioned_spec(name) unless name.to_s[/^[\w\-\.]+$/] + registry.key?(name, include_parents) + end + + def keys + values.map(&:name) + end + + def delete(name, include_parents = false) + registry.delete(name, include_parents) + self + end + + def clear + keys.each { |k| delete(k) } + end + + # :call-seq: + # group :who, :me, :you + # group :them, :me, :you, :namespace => ns + # + # Create a virtual group on this namespace. When the namespace + # is asked for the +who+ artifact, it's value is an array made from + # the remaining names. These names are searched by default from the current + # namespace. + # Second form specified the starting namespace to search from. + def group(group_name, *members) + namespace = (Hash === members.last && members.pop[:namespace]) || :current + registry[group_name] = lambda do + artifacts = self.class[namespace].values_at(*members) + artifacts = artifacts.first if members.size == 1 + artifacts + end + self + end + + alias_method :virtual, :group + + # Create an alias for a named requirement. + def alias(new_name, old_name) + registry.alias(new_name, old_name) or + raise NameError.new("Undefined artifact name: #{old_name}") + end + + def to_s #:nodoc: + name.to_s + end + + # :call-seq: + # artifact_ns.cool_aid!('cool:aid:jar:2.3.4', '~>2.3') -> artifact_requirement + # artifact_ns.cool_aid = '2.3.5' + # artifact_ns.cool_aid -> artifact_requirement + # artifact_ns.cool_aid? -> true | false + # + # First form creates an ArtifactRequirement on the namespace. + # It is equivalent to providing a requirement_spec to the #need method: + # artifact_ns.need "cool_aid -> cool:aid:jar:2.3.4 -> ~>2.3" + # the second argument is optional. + # + # Second form can be used to select an artifact version + # and is equivalent to: + # artifact_ns.use :cool_aid => '1.0' + # + # Third form obtains the named ArtifactRequirement, can be + # used to test if a named requirement has been defined. + # It is equivalent to: + # artifact_ns.fetch(:cool_aid) { nil } + # + # Last form tests if the ArtifactRequirement has been defined + # and a version has been selected for use. + # It is equivalent to: + # + # artifact_ns.has_cool_aid? + # artifact_ns.values_at(:cool_aid).flatten.all? { |a| a && a.selected? } + # + def method_missing(name, *args, &block) + case name.to_s + when /!$/ then + name = $`.intern + if args.size < 1 || args.size > 2 + raise ArgumentError.new("wrong number of arguments for #{name}!(spec, version_requirement?)") + end + need name => args.first + get(name).tap { |r| r.requirement = args.last if args.size == 2 } + when /=$/ then use $` => args.first + when /\?$/ then + name = $`.gsub(/^(has|have)_/, '').intern + [get(name)].flatten.all? { |a| a && a.selected? } + else + if block || args.size > 0 + raise ArgumentError.new("wrong number of arguments #{args.size} for 0 or block given") + end + get(name) + end + end + + # Return an anonymous module + # # first create a requirement + # artifact_ns.cool_aid! 'cool:aid:jar:>=1.0' + # + # # extend an object as a cool_aid delegator + # jars = Object.new.extend(artifact_ns.accessor(:cool_aid)) + # jars.cool_aid = '2.0' + # + # artifact_ns.cool_aid.version # -> '2.0' + def accessor(*names) + ns = self + Module.new do + names.each do |name| + define_method("#{name}") { ns.send("#{name}") } + define_method("#{name}?") { ns.send("#{name}?") } + define_method("#{name}=") { |vers| ns.send("#{name}=", vers) } + end + end + end + + private + def get(name, include_parents = true, include_subs = true, include_self = true) #:nodoc: + if include_subs && name.to_s[/_/] # try sub namespaces first + sub, parts = self, name.to_s.split('_') + sub_name = parts.shift.to_sym + until sub != self || parts.empty? + if registry[sub_name].kind_of?(ArtifactNamespace) + sub = registry[sub_name] + artifact = sub[parts.join('_')] + else + sub_name = [sub_name, parts.shift].join('_').to_sym + end + end + end + unless artifact + if include_self + artifact = registry.get(name, include_parents) + elsif include_parents && registry.parent + artifact = registry.parent.get(name, true) + end + end + artifact = artifact.call if artifact.respond_to?(:call) + artifact + end + + def registry + @registry ||= Registry.new.tap do |m| + m.parent = parent.send(:registry) unless root? + end + end + + end # ArtifactNamespace + + # :call-seq: + # project.artifact_ns -> ArtifactNamespace + # Buildr.artifact_ns(name) -> ArtifactNamespace + # Buildr.artifact_ns -> ArtifactNamespace for the currently running Project + # + # Open an ArtifactNamespace. + # If a block is provided, the namespace is yielded to it. + # + # See also ArtifactNamespace.instance + def artifact_ns(name = nil, &block) + name = self if name.nil? && self.kind_of?(Project) + ArtifactNamespace.instance(name, &block) + end + +end + + diff --git a/buildr/lib/buildr/packaging/artifact_search.rb b/buildr/lib/buildr/packaging/artifact_search.rb new file mode 100644 index 0000000..b7abe75 --- /dev/null +++ b/buildr/lib/buildr/packaging/artifact_search.rb @@ -0,0 +1,139 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +autoload :Hpricot, 'hpricot' + +module Buildr + + # Search best artifact version from remote repositories + module ArtifactSearch + extend self + + def include(method = nil) + (@includes ||= []).tap { push method if method } + end + + def exclude(method = nil) + (@excludes ||= []).tap { push method if method } + end + + # TODO: return the url for best matching repo + def best_version(spec, *methods) + spec = Artifact.to_hash(spec) + spec[:version] = requirement = VersionRequirement.create(spec[:version]) + select = lambda do |candidates| + candidates.find { |candidate| requirement.satisfied_by?(candidate) } + end + result = nil + methods = search_methods if methods.empty? + if requirement.composed? + until result || methods.empty? + method = methods.shift + type = method.keys.first + from = method[type] + if (include.empty? || !(include & [:all, type, from]).empty?) && + (exclude & [:all, type, from]).empty? + if from.respond_to?(:call) + versions = from.call(spec.dup) + else + versions = send("#{type}_versions", spec.dup, *from) + end + result = select[versions] + end + end + end + result ||= requirement.default + raise "Could not find #{Artifact.to_spec(spec)}" + + "\n You may need to use an specific version instead of a requirement" unless result + spec.merge :version => result + end + + def requirement?(spec) + VersionRequirement.requirement?(spec[:version]) + end + + private + def search_methods + [].tap do + push :runtime => [Artifact.list] + push :local => Buildr.repositories.local + Buildr.repositories.remote.each { |remote| push :remote => remote } + push :mvnrepository => [] + end + end + + def depend_version(spec) + spec[:version][/[\w\.]+/] + end + + def runtime_versions(spec, artifacts) + spec_classif = spec.values_at(:group, :id, :type) + artifacts.inject([]) do |in_memory, str| + candidate = Artifact.to_hash(str) + if spec_classif == candidate.values_at(:group, :id, :type) + in_memory << candidate[:version] + end + in_memory + end + end + + def local_versions(spec, repo) + path = (spec[:group].split(/\./) + [spec[:id]]).flatten.join('/') + Dir[File.expand_path(path + "/*", repo)].map { |d| d.pathmap("%f") }.sort.reverse + end + + def remote_versions(art, base, from = :metadata, fallback = true) + path = (art[:group].split(/\./) + [art[:id]]).flatten.join('/') + base ||= "http://mirrors.ibiblio.org/pub/mirrors/maven2" + uris = {:metadata => "#{base}/#{path}/maven-metadata.xml"} + uris[:listing] = "#{base}/#{path}/" if base =~ /^https?:/ + xml = nil + until xml || uris.empty? + begin + xml = URI.read(uris.delete(from)) + rescue URI::NotFoundError => e + from = fallback ? uris.keys.first : nil + end + end + return [] unless xml + doc = Hpricot(xml) + case from + when :metadata then + doc.search("versions/version").map(&:innerHTML).reverse + when :listing then + doc.search("a[@href]").inject([]) { |vers, a| + vers << a.innerHTML.chop if a.innerHTML[-1..-1] == '/' + vers + }.sort.reverse + else + fail "Don't know how to parse #{from}: \n#{xml.inspect}" + end + end + + def mvnrepository_versions(art) + uri = "http://www.mvnrepository.com/artifact/#{art[:group]}/#{art[:id]}" + xml = begin + URI.read(uri) + rescue URI::NotFoundError => e + puts e.class, e + return [] + end + doc = Hpricot(xml) + doc.search("table.grid/tr/td[1]/a").map(&:innerHTML) + end + + end # ArtifactSearch +end diff --git a/buildr/lib/buildr/packaging/gems.rb b/buildr/lib/buildr/packaging/gems.rb new file mode 100644 index 0000000..b9623b6 --- /dev/null +++ b/buildr/lib/buildr/packaging/gems.rb @@ -0,0 +1,93 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +autoload :RubyForge, 'rubyforge' +Gem.autoload :Package, 'rubygems/package' + +module Buildr + + class PackageGemTask < ArchiveTask + + def initialize(*args) + super + @spec = Gem::Specification.new + prepare do + include(changelog) if changelog + end + end + + attr_accessor :changelog + + def spec + yield @spec if block_given? + @spec + end + + def upload + rubyforge = RubyForge.new + rubyforge.login + rubyforge.userconfig.merge!('release_changes'=>changelog.to_s, 'preformatted'=>true) if changelog + rubyforge.add_release spec.rubyforge_project.downcase, spec.name.downcase, spec.version, package(:gem).to_s + end + + private + + def create_from(file_map) + spec.mark_version + spec.validate + + File.open(name, 'wb') do |io| + Gem::Package.open(io, 'w', nil) do |pkg| + pkg.metadata = spec.to_yaml + file_map.each do |path, content| + next if content.nil? || File.directory?(content.to_s) + pkg.add_file_simple(path, File.stat(content.to_s).mode & 0777, File.size(content.to_s)) do |os| + File.open(content.to_s, "rb") do |file| + os.write file.read(4096) until file.eof? + end + end + end + end + end + end + + end + + + module PackageAsGem #:nodoc: + + def package_as_gem(file_name) #:nodoc: + PackageGemTask.define_task(file_name).tap do |gem| + %w{ lib test doc }.each do |dir| + gem.include :from=>_(dir), :path=>dir if File.directory?(_(dir)) + end + gem.spec do |spec| + spec.name = id + spec.version = version.gsub('-','.') # RubyGems doesn't like '-' in version numbers + spec.summary = full_comment + spec.has_rdoc = true + spec.rdoc_options << '--title' << comment + spec.require_path = 'lib' + end + end + end + + end + + class Project + include PackageAsGem + end + +end diff --git a/buildr/lib/buildr/packaging/package.rb b/buildr/lib/buildr/packaging/package.rb new file mode 100644 index 0000000..761a31b --- /dev/null +++ b/buildr/lib/buildr/packaging/package.rb @@ -0,0 +1,243 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + # Methods added to Project to support packaging and tasks for packaging, + # installing and uploading packages. + module Package + + include Extension + + first_time do + desc 'Create packages' + Project.local_task('package'=>'build') { |name| "Packaging #{name}" } + desc 'Install packages created by the project' + Project.local_task('install'=>'package') { |name| "Installing packages from #{name}" } + desc 'Remove previously installed packages' + Project.local_task('uninstall') { |name| "Uninstalling packages from #{name}" } + desc 'Upload packages created by the project' + Project.local_task('upload'=>'package') { |name| "Deploying packages from #{name}" } + # Anything that comes after local packaging (install, upload) executes the integration tests, + # which do not conflict with integration invoking the project's own packaging (package=> + # integration=>foo:package is not circular, just confusing to debug.) + task 'package' do + task('integration').invoke if Buildr.options.test && Buildr.application.original_dir == Dir.pwd + end + end + + before_define(:package => :build) do |project| + [ :package, :install, :uninstall, :upload ].each { |name| project.recursive_task name } + # Need to run build before package, since package is often used as a dependency by tasks that + # expect build to happen. + project.task('package'=>project.task('build')) + project.group ||= project.parent && project.parent.group || project.name + project.version ||= project.parent && project.parent.version + end + + after_define(:package) + + # The project's identifier. Same as the project name, with colons replaced by dashes. + # The ID for project foo:bar is foo-bar. + def id + name.gsub(':', '-') + end + + # Group used for packaging. Inherited from parent project. Defaults to the top-level project name. + attr_accessor :group + + # Version used for packaging. Inherited from parent project. + attr_accessor :version + + # :call-seq: + # package(type, spec?) => task + # + # Defines and returns a package created by this project. + # + # The first argument declares the package type. For example, :jar to create a JAR file. + # The package is an artifact that takes its artifact specification from the project. + # You can override the artifact specification by passing various options in the second + # argument, for example: + # package(:zip, :classifier=>'sources') + # + # Packages that are ZIP files provides various ways to include additional files, directories, + # and even merge ZIPs together. Have a look at ZipTask for more information. In case you're + # wondering, JAR and WAR packages are ZIP files. + # + # You can also enhance a JAR package using the ZipTask#with method that accepts the following options: + # * :manifest -- Specifies how to create the MANIFEST.MF. By default, uses the project's + # #manifest property. + # * :meta_inf -- Specifies files to be included in the META-INF directory. By default, + # uses the project's #meta-inf property. + # + # The WAR package supports the same options and adds a few more: + # * :classes -- Directories of class files to include in WEB-INF/classes. Includes the compile + # target directory by default. + # * :libs -- Artifacts and files to include in WEB-INF/libs. Includes the compile classpath + # dependencies by default. + # + # For example: + # define 'project' do + # define 'beans' do + # package :jar + # end + # define 'webapp' do + # compile.with project('beans') + # package(:war).with :libs=>MYSQL_JDBC + # end + # package(:zip, :classifier=>'sources').include path_to('.') + # end + # + # Two other packaging types are: + # * package :sources -- Creates a JAR file with the source code and classifier 'sources', for use by IDEs. + # * package :javadoc -- Creates a ZIP file with the Javadocs and classifier 'javadoc'. You can use the + # javadoc method to further customize it. + # + # A package is also an artifact. The following tasks operate on packages created by the project: + # buildr upload # Upload packages created by the project + # buildr install # Install packages created by the project + # buildr package # Create packages + # buildr uninstall # Remove previously installed packages + # + # If you want to add additional packaging types, implement a method with the name package_as_[type] + # that accepts a file name and returns an appropriate Rake task. For example: + # def package_as_zip(file_name) #:nodoc: + # ZipTask.define_task(file_name) + # end + # + # The file name is determined from the specification passed to the package method, however, some + # packagers need to override this. For example, package(:sources) produces a file with the extension + # 'jar' and the classifier 'sources'. If you need to overwrite the default implementation, you should + # also include a method named package_as_[type]_spec. For example: + # def package_as_sources_spec(spec) #:nodoc: + # # Change the source distribution to .zip extension + # spec.merge({ :type=>:zip, :classifier=>'sources' }) + # end + def package(*args) + spec = Hash === args.last ? args.pop.dup : {} + no_options = spec.empty? # since spec is mutated + if spec[:file] + rake_check_options spec, :file, :type + spec[:type] = args.shift || spec[:type] || spec[:file].split('.').last.to_sym + file_name = spec[:file] + else + rake_check_options spec, *ActsAsArtifact::ARTIFACT_ATTRIBUTES + spec[:id] ||= self.id + spec[:group] ||= self.group + spec[:version] ||= self.version + spec[:type] = args.shift || spec[:type] || compile.packaging || :zip + end + + packager = method("package_as_#{spec[:type]}") rescue fail("Don't know how to create a package of type #{spec[:type]}") + if packager.arity == 1 + unless file_name + spec = send("package_as_#{spec[:type]}_spec", spec) if respond_to?("package_as_#{spec[:type]}_spec") + file_name = path_to(:target, Artifact.hash_to_file_name(spec)) + end + package = (no_options && packages.detect { |pkg| pkg.type == spec[:type] && (pkg.id.nil? || pkg.id == spec[:id]) && + (pkg.respond_to?(:classifier) ? pkg.classifier : nil) == spec[:classifier]}) || + packages.find { |pkg| pkg.name == file_name } || + packager.call(file_name) + else + Buildr.application.deprecated "We changed the way package_as methods are implemented. See the package method documentation for more details." + file_name ||= path_to(:target, Artifact.hash_to_file_name(spec)) + package = packager.call(file_name, spec) + end + + # First time: prepare package for install, uninstall and upload tasks. + unless packages.include?(package) + # We already run build before package, but we also need to do so if the package itself is + # used as a dependency, before we get to run the package task. + task 'package'=>package + package.enhance [task('build')] + package.enhance { info "Packaging #{File.basename(file_name)}" } + if spec[:file] + class << package ; self ; end.send(:define_method, :type) { spec[:type] } + class << package ; self ; end.send(:define_method, :id) { nil } + else + # Make it an artifact using the specifications, and tell it how to create a POM. + package.extend ActsAsArtifact + package.send :apply_spec, spec.only(*Artifact::ARTIFACT_ATTRIBUTES) + + # Create pom associated with package + class << package + def pom + unless @pom + pom_filename = Util.replace_extension(self.name, 'pom') + spec = {:group=>group, :id=>id, :version=>version, :type=>:pom} + @pom = Buildr.artifact(spec, pom_filename) + @pom.content @pom.pom_xml + end + @pom + end + end + + file(Buildr.repositories.locate(package)=>package) { package.install } + + # Add the package to the list of packages created by this project, and + # register it as an artifact. The later is required so if we look up the spec + # we find the package in the project's target directory, instead of finding it + # in the local repository and attempting to install it. + Artifact.register package, package.pom + end + + task('install') { package.install if package.respond_to?(:install) } + task('uninstall') { package.uninstall if package.respond_to?(:uninstall) } + task('upload') { package.upload if package.respond_to?(:upload) } + + packages << package + end + package + end + + # :call-seq: + # packages => tasks + # + # Returns all packages created by this project. A project may create any number of packages. + # + # This method is used whenever you pass a project to Buildr#artifact or any other method + # that accepts artifact specifications and projects. You can use it to list all packages + # created by the project. If you want to return a specific package, it is often more + # convenient to call #package with the type. + def packages + @packages ||= [] + end + + protected + + def package_as_zip(file_name) #:nodoc: + ZipTask.define_task(file_name) + end + + def package_as_tar(file_name) #:nodoc: + TarTask.define_task(file_name) + end + alias :package_as_tgz :package_as_tar + + def package_as_sources_spec(spec) #:nodoc: + spec.merge(:type=>:jar, :classifier=>'sources') + end + + def package_as_sources(file_name) #:nodoc: + ZipTask.define_task(file_name).tap do |zip| + zip.include :from=>[compile.sources, resources.target].compact + end + end + + end +end + +class Buildr::Project + include Buildr::Package +end diff --git a/buildr/lib/buildr/packaging/tar.rb b/buildr/lib/buildr/packaging/tar.rb new file mode 100644 index 0000000..e01ad71 --- /dev/null +++ b/buildr/lib/buildr/packaging/tar.rb @@ -0,0 +1,187 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +autoload :Archive, 'archive/tar/minitar' +autoload :Zlib, 'zlib' + +module Buildr + + # The TarTask creates a new Tar file. You can include any number of files and and directories, + # use exclusion patterns, and include files into specific directories. + # + # To create a GZipped Tar, either set the gzip option to true, or use the .tgz or .gz suffix. + # + # For example: + # tar("test.tgz").tap do |task| + # task.include "srcs" + # task.include "README", "LICENSE" + # end + # + # See Buildr#tar and ArchiveTask. + class TarTask < ArchiveTask + + # To create a GZipped Tar, either set this option to true, or use the .tgz/.gz suffix. + attr_accessor :gzip + # Permission mode for files contained in the Tar. Defaults to 0755. + attr_accessor :mode + + def initialize(*args, &block) #:nodoc: + super + self.gzip = name =~ /\.t?gz$/ + self.mode = '0755' + end + + # :call-seq: + # entry(name) => Entry + # + # Returns a Tar file entry. You can use this to check if the entry exists and its contents, + # for example: + # package(:tar).entry("src/LICENSE").should contain(/Apache Software License/) + def entry(entry_name) + Buildr::TarEntry.new(self, entry_name) + end + + def entries() #:nodoc: + tar_entries = nil + with_uncompressed_tar { |tar| tar_entries = tar.entries } + tar_entries + end + + # :call-seq: + # with_uncompressed_tar { |tar_entries| ... } + # + # Yields an Archive::Tar::Minitar::Input object to the provided block. + # Opening, closing and Gzip-decompressing is automatically taken care of. + def with_uncompressed_tar &block + if gzip + Zlib::GzipReader.open(name) { |tar| Archive::Tar::Minitar.open(tar, &block) } + else + Archive::Tar::Minitar.open(name, &block) + end + end + + private + + def create_from(file_map) + if gzip + StringIO.new.tap do |io| + create_tar io, file_map + io.seek 0 + Zlib::GzipWriter.open(name) { |gzip| gzip.write io.read } + end + else + File.open(name, 'wb') { |file| create_tar file, file_map } + end + end + + def create_tar(out, file_map) + Archive::Tar::Minitar::Writer.open(out) do |tar| + options = { :mode=>mode || '0755', :mtime=>Time.now } + + file_map.each do |path, content| + if content.respond_to?(:call) + tar.add_file(path, options) { |os, opts| content.call os } + elsif content.nil? + elsif File.directory?(content.to_s) + stat = File.stat(content.to_s) + tar.mkdir(path, options.merge(:mode=>stat.mode, :mtime=>stat.mtime, :uid=>stat.uid, :gid=>stat.gid)) + else + File.open content.to_s, 'rb' do |is| + tar.add_file path, options.merge(:mode=>is.stat.mode, :mtime=>is.stat.mtime, :uid=>is.stat.uid, :gid=>is.stat.gid) do |os, opts| + while data = is.read(4096) + os.write(data) + end + end + end + end + end + end + end + + end + + + class TarEntry #:nodoc: + + def initialize(tar_task, entry_name) + @tar_task = tar_task + @entry_name = entry_name + end + + # :call-seq: + # contain?(*patterns) => boolean + # + # Returns true if this Tar file entry matches against all the arguments. An argument may be + # a string or regular expression. + def contain?(*patterns) + content = read_content_from_tar + patterns.map { |pattern| Regexp === pattern ? pattern : Regexp.new(Regexp.escape(pattern.to_s)) }. + all? { |pattern| content =~ pattern } + end + + # :call-seq: + # empty?() => boolean + # + # Returns true if this entry is empty. + def empty?() + read_content_from_tar.nil? + end + + # :call-seq: + # exist() => boolean + # + # Returns true if this entry exists. + def exist?() + exist = false + @tar_task.with_uncompressed_tar { |tar| exist = tar.any? { |entry| entry.name == @entry_name } } + exist + end + + def to_s #:nodoc: + @entry_name + end + + private + + def read_content_from_tar + content = Errno::ENOENT.new("No such file or directory - #{@entry_name}") + @tar_task.with_uncompressed_tar do |tar| + content = tar.inject(content) { |content, entry| entry.name == @entry_name ? entry.read : content } + end + raise content if Exception === content + content + end + end + +end + + +# :call-seq: +# tar(file) => TarTask +# +# The TarTask creates a new Tar file. You can include any number of files and +# and directories, use exclusion patterns, and include files into specific +# directories. +# +# To create a GZipped Tar, either set the gzip option to true, or use the .tgz or .gz suffix. +# +# For example: +# tar("test.tgz").tap do |tgz| +# tgz.include "srcs" +# tgz.include "README", "LICENSE" +# end +def tar(file) + TarTask.define_task(file) +end diff --git a/buildr/lib/buildr/packaging/version_requirement.rb b/buildr/lib/buildr/packaging/version_requirement.rb new file mode 100644 index 0000000..47b7cbf --- /dev/null +++ b/buildr/lib/buildr/packaging/version_requirement.rb @@ -0,0 +1,192 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +# Rubygems 1.3.6 removed the 'version' accessor so monkey-patch it back to +# circumvent version validation. This is needed because Gem::Version doesn't +# accept version specs with dashes. +unless Gem::Version.new(0).respond_to?(:version=) + class Gem::Version + def version=(version) + @version = version.to_s + @version.strip! + + # re-prime @segments + @segments = nil + segments + end + end +end + +module Buildr + + # + # See ArtifactNamespace#need + class VersionRequirement + + CMP_PROCS = Gem::Requirement::OPS.dup + CMP_REGEX = if defined?(Gem::Requirement::OP_RE) + Gem::Requirement::OP_RE + else + Gem::Requirement::OPS.keys.map { |k| Regexp.quote k }.join "|" + end + CMP_CHARS = CMP_PROCS.keys.join + BOOL_CHARS = '\|\&\!' + VER_CHARS = '\w\.\-' + + class << self + # is +str+ a version string? + def version?(str) + /^\s*r?\d[#{VER_CHARS}]*\s*$/ === str + end + + # is +str+ a version requirement? + def requirement?(str) + /[#{BOOL_CHARS}#{CMP_CHARS}\(\)]/ === str + end + + # :call-seq: + # VersionRequirement.create(" >1 <2 !(1.5) ") -> requirement + # + # parse the +str+ requirement + def create(str) + instance_eval normalize(str) + rescue StandardError => e + raise "Failed to parse #{str.inspect} due to: #{e}" + end + + private + def requirement(req) + unless req =~ /^\s*(#{CMP_REGEX})?\s*([#{VER_CHARS}]+)\s*$/ + raise "Invalid requirement string: #{req}" + end + comparator, version = $1, $2 + version = Gem::Version.new(0).tap { |v| v.version = version } + VersionRequirement.new(nil, [$1, version]) + end + + def negate(vreq) + vreq.negative = !vreq.negative + vreq + end + + def normalize(str) + str = str.strip + if str[/[^\s\(\)#{BOOL_CHARS + VER_CHARS + CMP_CHARS}]/] + raise "version string #{str.inspect} contains invalid characters" + end + str.gsub!(/\s+(and|\&\&)\s+/, ' & ') + str.gsub!(/\s+(or|\|\|)\s+/, ' | ') + str.gsub!(/(^|\s*)not\s+/, ' ! ') + pattern = /(#{CMP_REGEX})?\s*[#{VER_CHARS}]+/ + left_pattern = /[#{VER_CHARS}\)]$/ + right_pattern = /^(#{pattern}|\()/ + str = str.split.inject([]) do |ary, i| + ary << '&' if ary.last =~ left_pattern && i =~ right_pattern + ary << i + end + str = str.join(' ') + str.gsub!(/!([^=])?/, ' negate \1') + str.gsub!(pattern) do |expr| + case expr.strip + when 'not', 'negate' then 'negate ' + else 'requirement("' + expr + '")' + end + end + str.gsub!(/negate\s+\(/, 'negate(') + str + end + end + + def initialize(op, *requirements) #:nodoc: + @op, @requirements = op, requirements + end + + # Is this object a composed requirement? + # VersionRequirement.create('1').composed? -> false + # VersionRequirement.create('1 | 2').composed? -> true + # VersionRequirement.create('1 & 2').composed? -> true + def composed? + requirements.size > 1 + end + + # Return the last requirement on this object having an = operator. + def default + default = nil + requirements.reverse.find do |r| + if Array === r + if !negative && (r.first.nil? || r.first.include?('=')) + default = r.last.to_s + end + else + default = r.default + end + end + default + end + + # Test if this requirement can be satisfied by +version+ + def satisfied_by?(version) + return false unless version + unless version.kind_of?(Gem::Version) + raise "Invalid version: #{version.inspect}" unless self.class.version?(version) + version = Gem::Version.new(0).tap { |v| v.version = version.strip } + end + message = op == :| ? :any? : :all? + result = requirements.send message do |req| + if Array === req + cmp, rv = *req + CMP_PROCS[cmp || '='].call(version, rv) + else + req.satisfied_by?(version) + end + end + negative ? !result : result + end + + # Either modify the current requirement (if it's already an or operation) + # or create a new requirement + def |(other) + operation(:|, other) + end + + # Either modify the current requirement (if it's already an and operation) + # or create a new requirement + def &(other) + operation(:&, other) + end + + # return the parsed expression + def to_s + str = requirements.map(&:to_s).join(" " + @op.to_s + " ").to_s + str = "( " + str + " )" if negative || requirements.size > 1 + str = "!" + str if negative + str + end + + attr_accessor :negative + protected + attr_reader :requirements, :op + def operation(op, other) + @op ||= op + if negative == other.negative && @op == op && other.requirements.size == 1 + @requirements << other.requirements.first + self + else + self.class.new(op, self, other) + end + end + end # VersionRequirement +end diff --git a/buildr/lib/buildr/packaging/zip.rb b/buildr/lib/buildr/packaging/zip.rb new file mode 100644 index 0000000..4255a42 --- /dev/null +++ b/buildr/lib/buildr/packaging/zip.rb @@ -0,0 +1,183 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +if RUBY_VERSION >= '1.9.0' # Required to properly load RubyZip under Ruby 1.9 + $LOADED_FEATURES.unshift 'ftools' + require 'fileutils' + + def File.move(source, dest) + FileUtils.move source, dest + end + + def File.rm_rf(path) + FileUtils.rm_rf path + end +end + +require 'zip/zip' +require 'zip/zipfilesystem' + +module Zip #:nodoc: + + class ZipCentralDirectory #:nodoc: + # Patch to add entries in alphabetical order. + def write_to_stream(io) + offset = io.tell + @entrySet.sort { |a,b| a.name <=> b.name }.each { |entry| entry.write_c_dir_entry(io) } + write_e_o_c_d(io, offset) + end + end + + + class ZipEntry + + # :call-seq: + # exist() => boolean + # + # Returns true if this entry exists. + def exist?() + Zip::ZipFile.open(zipfile) { |zip| zip.file.exist?(@name) } + end + + # :call-seq: + # empty?() => boolean + # + # Returns true if this entry is empty. + def empty?() + Zip::ZipFile.open(zipfile) { |zip| zip.file.read(@name) }.empty? + end + + # :call-seq: + # contain(patterns*) => boolean + # + # Returns true if this ZIP file entry matches against all the arguments. An argument may be + # a string or regular expression. + def contain?(*patterns) + content = Zip::ZipFile.open(zipfile) { |zip| zip.file.read(@name) } + patterns.map { |pattern| Regexp === pattern ? pattern : Regexp.new(Regexp.escape(pattern.to_s)) }. + all? { |pattern| content =~ pattern } + end + + # Override of write_c_dir_entry to fix comments being set to a fixnum instead of string + def write_c_dir_entry(io) #:nodoc:all + case @fstype + when FSTYPE_UNIX + ft = nil + case @ftype + when :file + ft = 010 + @unix_perms ||= 0644 + when :directory + ft = 004 + @unix_perms ||= 0755 + when :symlink + ft = 012 + @unix_perms ||= 0755 + else + raise ZipInternalError, "unknown file type #{self.inspect}" + end + + @externalFileAttributes = (ft << 12 | (@unix_perms & 07777)) << 16 + end + + io << + [0x02014b50, + @version, # version of encoding software + @fstype, # filesystem type + 10, # @versionNeededToExtract + 0, # @gp_flags + @compression_method, + @time.to_binary_dos_time, # @lastModTime + @time.to_binary_dos_date, # @lastModDate + @crc, + @compressed_size, + @size, + @name ? @name.length : 0, + @extra ? @extra.c_dir_length : 0, + @comment ? comment.to_s.length : 0, + 0, # disk number start + @internalFileAttributes, # file type (binary=0, text=1) + @externalFileAttributes, # native filesystem attributes + @localHeaderOffset, + @name, + @extra, + @comment + ].pack('VCCvvvvvVVVvvvvvVV') + + io << @name + io << (@extra ? @extra.to_c_dir_bin : "") + io << @comment + end + + # Override write_c_dir_entry to fix comments being set to a fixnum instead of string + def write_c_dir_entry(io) #:nodoc:all + @comment = "" if @comment.nil? || @comment == -1 # Hack fix @comment being nil or fixnum -1 + + case @fstype + when FSTYPE_UNIX + ft = nil + case @ftype + when :file + ft = 010 + @unix_perms ||= 0644 + when :directory + ft = 004 + @unix_perms ||= 0755 + when :symlink + ft = 012 + @unix_perms ||= 0755 + else + raise ZipInternalError, "unknown file type #{self.inspect}" + end + + @externalFileAttributes = (ft << 12 | (@unix_perms & 07777)) << 16 + end + + io << + [0x02014b50, + @version, # version of encoding software + @fstype, # filesystem type + 10, # @versionNeededToExtract + 0, # @gp_flags + @compression_method, + @time.to_binary_dos_time, # @lastModTime + @time.to_binary_dos_date, # @lastModDate + @crc, + @compressed_size, + @size, + @name ? @name.length : 0, + @extra ? @extra.c_dir_length : 0, + @comment ? @comment.length : 0, + 0, # disk number start + @internalFileAttributes, # file type (binary=0, text=1) + @externalFileAttributes, # native filesystem attributes + @localHeaderOffset, + @name, + @extra, + @comment].pack('VCCvvvvvVVVvvvvvVV') + + io << @name + io << (@extra ? @extra.to_c_dir_bin : "") + io << @comment + + end + end + + class ZipEntrySet + def <<(entry) + @entrySet[entry.to_s] = entry if entry != nil + end + end +end diff --git a/buildr/lib/buildr/packaging/ziptask.rb b/buildr/lib/buildr/packaging/ziptask.rb new file mode 100644 index 0000000..d2a5ecf --- /dev/null +++ b/buildr/lib/buildr/packaging/ziptask.rb @@ -0,0 +1,352 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + + # The ZipTask creates a new Zip file. You can include any number of files and and directories, + # use exclusion patterns, and include files into specific directories. + # + # For example: + # zip('test.zip').tap do |task| + # task.include 'srcs' + # task.include 'README', 'LICENSE' + # end + # + # See Buildr#zip and ArchiveTask. + class ZipTask < ArchiveTask + + # Compression leve for this Zip. + attr_accessor :compression_level + + def initialize(*args) #:nodoc: + self.compression_level = Zlib::DEFAULT_COMPRESSION + super + end + + # :call-seq: + # entry(name) => Entry + # + # Returns a ZIP file entry. You can use this to check if the entry exists and its contents, + # for example: + # package(:jar).entry("META-INF/LICENSE").should contain(/Apache Software License/) + def entry(entry_name) + ::Zip::ZipEntry.new(name, entry_name) + end + + def entries #:nodoc: + @entries ||= Zip::ZipFile.open(name) { |zip| zip.entries } + end + + private + + def create_from(file_map) + Zip::ZipOutputStream.open name do |zip| + seen = {} + mkpath = lambda do |dir| + dirname = (dir[-1..-1] =~ /\/$/) ? dir : dir + '/' + unless dir == '.' || seen[dirname] + mkpath.call File.dirname(dirname) + zip.put_next_entry(dirname, compression_level) + seen[dirname] = true + end + end + + file_map.each do |path, content| + warn "Warning: Path in zipfile #{name} contains backslash: #{path}" if path =~ /\\/ + mkpath.call File.dirname(path) + if content.respond_to?(:call) + zip.put_next_entry(path, compression_level) + content.call zip + elsif content.nil? || File.directory?(content.to_s) + mkpath.call path + else + entry = zip.put_next_entry(path, compression_level) + File.open content.to_s, 'rb' do |is| + entry.unix_perms = is.stat.mode & 07777 + while data = is.read(4096) + zip << data + end + end + end + end + end + end + + end + + + # :call-seq: + # zip(file) => ZipTask + # + # The ZipTask creates a new Zip file. You can include any number of files and + # and directories, use exclusion patterns, and include files into specific + # directories. + # + # For example: + # zip('test.zip').tap do |task| + # task.include 'srcs' + # task.include 'README', 'LICENSE' + # end + def zip(file) + ZipTask.define_task(file) + end + + + # An object for unzipping/untarring a file into a target directory. You can tell it to include + # or exclude only specific files and directories, and also to map files from particular + # paths inside the zip file into the target directory. Once ready, call #extract. + # + # Usually it is more convenient to create a file task for extracting the zip file + # (see #unzip) and pass this object as a prerequisite to other tasks. + # + # See Buildr#unzip. + class Unzip + + # The zip file to extract. + attr_accessor :zip_file + # The target directory to extract to. + attr_accessor :target + + # Initialize with hash argument of the form target=>zip_file. + def initialize(args) + @target, arg_names, zip_file = Buildr.application.resolve_args([args]) + @zip_file = zip_file.first + @paths = {} + end + + # :call-seq: + # extract + # + # Extract the zip/tgz file into the target directory. + # + # You can call this method directly. However, if you are using the #unzip method, + # it creates a file task for the target directory: use that task instead as a + # prerequisite. For example: + # build unzip(dir=>zip_file) + # Or: + # unzip(dir=>zip_file).target.invoke + def extract + # If no paths specified, then no include/exclude patterns + # specified. Nothing will happen unless we include all files. + if @paths.empty? + @paths[nil] = FromPath.new(self, nil) + end + + # Otherwise, empty unzip creates target as a file when touching. + mkpath target.to_s + if zip_file.to_s.match /\.t?gz$/ + #un-tar.gz + Zlib::GzipReader.open(zip_file.to_s) { |tar| + Archive::Tar::Minitar::Input.open(tar) do |inp| + inp.each do |tar_entry| + @paths.each do |path, patterns| + patterns.map([tar_entry]).each do |dest, entry| + next if entry.directory? + dest = File.expand_path(dest, target.to_s) + trace "Extracting #{dest}" + mkpath File.dirname(dest) rescue nil + #entry.restore_permissions = true + File.open(dest, 'wb') {|f| f.write entry.read} + end + end + end + end + } + else + Zip::ZipFile.open(zip_file.to_s) do |zip| + entries = zip.collect + @paths.each do |path, patterns| + patterns.map(entries).each do |dest, entry| + next if entry.directory? + dest = File.expand_path(dest, target.to_s) + trace "Extracting #{dest}" + mkpath File.dirname(dest) rescue nil + entry.restore_permissions = true + entry.extract(dest) { true } + end + end + end + end + # Let other tasks know we updated the target directory. + touch target.to_s + end + + #reads the includes/excludes and apply them to the entry_name + def included?(entry_name) + @paths.each do |path, patterns| + return true if path.nil? + if entry_name =~ /^#{path}/ + short = entry_name.sub(path, '') + if patterns.include.any? { |pattern| File.fnmatch(pattern, entry_name) } && + !patterns.exclude.any? { |pattern| File.fnmatch(pattern, entry_name) } + # trace "tar_entry.full_name " + entry_name + " is included" + return true + end + end + end + # trace "tar_entry.full_name " + entry_name + " is excluded" + return false + end + + + # :call-seq: + # include(*files) => self + # include(*files, :path=>name) => self + # + # Include all files that match the patterns and returns self. + # + # Use include if you only want to unzip some of the files, by specifying + # them instead of using exclusion. You can use #include in combination + # with #exclude. + def include(*files) + if Hash === files.last + from_path(files.pop[:path]).include *files + else + from_path(nil).include *files + end + self + end + alias :add :include + + # :call-seq: + # exclude(*files) => self + # + # Exclude all files that match the patterns and return self. + # + # Use exclude to unzip all files except those that match the pattern. + # You can use #exclude in combination with #include. + def exclude(*files) + if Hash === files.last + from_path(files.pop[:path]).exclude *files + else + from_path(nil).exclude *files + end + self + end + + # :call-seq: + # from_path(name) => Path + # + # Allows you to unzip from a path. Returns an object you can use to + # specify which files to include/exclude relative to that path. + # Expands the file relative to that path. + # + # For example: + # unzip(Dir.pwd=>'test.jar').from_path('etc').include('LICENSE') + # will unzip etc/LICENSE into ./LICENSE. + # + # This is different from: + # unzip(Dir.pwd=>'test.jar').include('etc/LICENSE') + # which unzips etc/LICENSE into ./etc/LICENSE. + def from_path(name) + @paths[name] ||= FromPath.new(self, name) + end + alias :path :from_path + + # :call-seq: + # root => Unzip + # + # Returns the root path, essentially the Unzip object itself. In case you are wondering + # down paths and want to go back. + def root + self + end + + # Returns the path to the target directory. + def to_s + target.to_s + end + + class FromPath #:nodoc: + + def initialize(unzip, path) + @unzip = unzip + if path + @path = path[-1] == ?/ ? path : path + '/' + else + @path = '' + end + end + + # See UnzipTask#include + def include(*files) #:doc: + @include ||= [] + @include |= files + self + end + + # See UnzipTask#exclude + def exclude(*files) #:doc: + @exclude ||= [] + @exclude |= files + self + end + + def map(entries) + includes = @include || ['*'] + excludes = @exclude || [] + entries.inject({}) do |map, entry| + if entry.name =~ /^#{@path}/ + short = entry.name.sub(@path, '') + if includes.any? { |pat| File.fnmatch(pat, short) } && + !excludes.any? { |pat| File.fnmatch(pat, short) } + map[short] = entry + end + end + map + end + end + + # Documented in Unzip. + def root + @unzip + end + + # The target directory to extract to. + def target + @unzip.target + end + + end + + end + + # :call-seq: + # unzip(to_dir=>zip_file) => Zip + # + # Creates a task that will unzip a file into the target directory. The task name + # is the target directory, the prerequisite is the file to unzip. + # + # This method creates a file task to expand the zip file. It returns an Unzip object + # that specifies how the file will be extracted. You can include or exclude specific + # files from within the zip, and map to different paths. + # + # The Unzip object's to_s method return the path to the target directory, so you can + # use it as a prerequisite. By keeping the Unzip object separate from the file task, + # you overlay additional work on top of the file task. + # + # For example: + # unzip('all'=>'test.zip') + # unzip('src'=>'test.zip').include('README', 'LICENSE') + # unzip('libs'=>'test.zip').from_path('libs') + def unzip(args) + target, arg_names, zip_file = Buildr.application.resolve_args([args]) + task = file(File.expand_path(target.to_s)=>zip_file) + Unzip.new(task=>zip_file).tap do |setup| + task.enhance { setup.extract } + end + end + +end diff --git a/buildr/lib/buildr/resources/buildr.icns b/buildr/lib/buildr/resources/buildr.icns new file mode 100644 index 0000000000000000000000000000000000000000..fb2a6e3344ed6005e3ae881e3c8446689686ee1a GIT binary patch literal 72357 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 Ss{h$(V{TRe000a^@BjdWWcTR+ literal 0 HcmV?d00001 diff --git a/buildr/lib/buildr/resources/completed.png b/buildr/lib/buildr/resources/completed.png new file mode 100644 index 0000000000000000000000000000000000000000..abf644d99c7e7c4510e7d1857709d24e66660863 GIT binary patch literal 12805 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 X0}At0ROj){00000NkvXXu0mjf$;WAF literal 0 HcmV?d00001 diff --git a/buildr/lib/buildr/resources/failed.png b/buildr/lib/buildr/resources/failed.png new file mode 100644 index 0000000000000000000000000000000000000000..c4c2860a8559a5ef33f6d997cd20b6e23a1031ad GIT binary patch literal 11469 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�KZFgVoSTjtolJSgmenvCAs{j$~OLTz~vCoSny&4#taU`kOFHoshS8;o=KGV2Z28 zx7%lXHH#xTWcST3lS*6hC4p&r3rnrP7hXbX&ItehRiWcPFMYW`?RKp?7Q&4kLO05X z(v@08Oo`a~)RSpU>qPkyMT#GOg$wXeOC5Cf^E@J}`OIpg?Nz~({7V&NP#?_p_yjuB zaMhpoAfV0gTi=k1g*w5j_0ywk;bl{^o^_)OOL%vjI%Nzj z=;jTvw4w&@tk=DLVc;|hf?(y@JnIhJHztlidT<>Zb)M@%{41<8TP*0SL}Ysv$(i&R zb5-CrUnbG(aSQ6c=_uDDB>~T?QCUEqB7|g}u~ zrk6U8!s`O9#n!jC*4FEs!ao+4Q^tgY2)aWHELNe2{GqsVD?t9uE{(fG!fxmTS%4oM zCws@Zkf1l^aHiS~dc0F(Ea#Py_N$<&Wl~DXuAPsiOfGb1X|**<~OA;OC-rNhlFry3Ls z(5oVH7``OCL$ecgfa$6R%G;l=w8_aP7D8}I+w&O9@*ZJdvr+-U8h}vYRj%1Sq4-Y? ziD@(hvZ}0jl$OZ_7QnZtd5t)0}rL<2Uek47@ZbJ>Q^ku z)U<$zVj!tc;aoP+H1f?atNGS)i`5W(6Wj$v#LF{X-Q0F`Hnu7s&>x-7hRUv!YsUKL zdse#~J+EoviEus|OVJ>tm6D?G?qwILx7y%8;TgPh@+HQ9C#!h$?-Bs1KcsV#t&QbA z=qCN1>sUUxr30qN!`TCE0WMH(Lgi3fyu}Lr+zMf)l!mUSr2K&O{4}}6(W9`DUB&B) zyQ+p{sXQFc5ACxfx{6n*Bw%eSj=5`3`CPYg05?%e9<mEW6T)nfnbym?c)MHy)CW zM5>~lM!C3NunpAh)~i65xBqtX&D3ZSJbvCW>e|*a(0R%NaI~KY!c_QqW)yPTsSC6J zYvszh8XSg!1w?Wm+KE!`9eGN#d*q9W&42}5L;=sk5PUZ_ zdoII~_;HGs>mw~)uKQ?ZR|AGEp)J=W(Nf5$hct&Yo!s)9Wo&+FSTH~LnPaENzbo2@ zJmZ+FIF{8eVa6=>KCf5lk*o>{p~pJ>Qdlr7HEqfuK1J+puFyqK)3g;a;xE{a*W}|7 z(oz)@dSW9&Gh(J?>#~D%Ov)kW8Ex{0ody3zRO2KqwWqG>Wyls!f8?BcdV{d)+1{T~ z!@SdsI<-v?=%jP{K4$4Y@uGl>#Gk882)f=;vsV0Db!VJ`9$-Cc{$QIh;I6{0SB?>^ zL1(QS5x41fjm9qE$~RseH?y4(nU6u<7_D>IX<*+tt_idKPYx z9*~PxC(H5?MVCLS#jC$!&HXep3f9_^{GQS0a{GN>u;|cK$<^2453(7ji<+N4p(AE) zq3Tt+30%#BA4gH9WbJPL9?q#<4qh<%6ioe5m<8`0^k7WY`ZVW~L8NFclfWT{VLie{ zP}4i6yzCi@v)_pL9|s?X<=fpOE97mJr~2vDPAW{#^ya9&e_Fy1!|Ldo#XDvZAL7d; z>SP)+)%W_2X-@B5sF07!=H4pEr1XJVsU0=onER8xVmpoF(EZCz5ly$l!v{p>T8-O& ziY&6v%=XcTFx7=9OXiP*a1fq8gk#HEX{HjwTJIe5c?Gp$-jrKC&4S8M&dn-67{IA4 zWL6s)t)ZSwt$*PqUH%7e8m=BG$c&FOcRt})mQ|NA zVUF`hy5m4$IA_tjl}f!say!%2niFX~f4+<#W%Rz9^ru_R%-A{Fq$TBO5p%?QJ#!1L zcW0*=>C#pb{QeFwH6Y5%Xz+kQC?y5!=L(nccm8O)q&Q9dd`#^_!4Q}ddpYgk@=)0t zeKz6?!dfIQwM#ouAt2R=GWFxZ)uY;8)b96@^8dD$@{l)&MN*K17WNTwb9arjPnhHP z$_!uqYi78bl)GX}3Q01mTXO8woVD19pr9KKAG{a-#!P=w+^F<5N^ua3{SNOUOV1#Z zUsrU`zP)i{`j_61#}xm>jY~#PQ=9Ub)ymX;i?h6_*SpIf3a7-r`Qk5@7P#JPnRJrN zjx1B`SQVWI9aCi-tbGTd?N!@^enwYQ#;?-%NJx_j19;wVH707VX^Zph{<=el+!MH% zV7%Y2OYLcjQZ04wFEMy2kTK&QDEAzg6`rz&m-8 z1Q^o-0O7!0Pn+RGY`Y`25v9uX;;FCoMrjv$_Te@~;tebN*xqzC0nS;7prz2EF2`=( za(-uu{B`{eE-j`&)N5Qgvc01n3>$y=kda@dRPT{CR=kFlz>Zl@KH~94Wt4nHc(C9~ za|t%%GBhmTK7L-{P299=8r7JiMGyUY<7VDgnRGLIxMyTqAQ_BF_r)iBm!j$+8(qBa z%cK28YXBYSkp6XNi5C|GA7%;LLRH+^T+kytRCGyZa{jlZlt}^X8wC_EukAD5QL%2r zgZPHj_+AlH32zWFi~U!+eyCbq@Igq(jf^LFDJvdjc zT-%wjr4wueC{yylR%XWWIAAn&4yHgTy}Gt7jbx3O19R`2l?pZcdZjlzgPHzW6+*dq zIkp9zqQ44^d|{!VaQlwDUv>TW-_@S=RnXD;pkU9eG=<;cYtdcUtH~H+w;+F)3#8 zhWpa@%x)z_0m9YB`R>PVTn}oj#*NTqq-Kk69s`AWTm4&b|#F>px=kf~7L2%o5;)l1=%65x>7KTG4J+ zOt*ZV3Zq?kmwo6pedM%^|3p8*rbM5m5Uj-72S4LMc$S`)PZ&(|ygm!c|G6!pph%2= zavS@i#RjRgJ-emn(P9*Jw8#kFD)8sT*5tW4)~S^cWA%RT64?@erusc(G;dzg*@z3-!I7!`asAaj zgO$WFt-FLz6i^G@aWl6Kd<8IWoGMsZpZ^E!n^xjJ_Cs7B9QQOj+~ z_%J1qaLo1349iJ^IYQtcGGw*Z6yz!5_0sf5;{iC4jSNJl8 zyPpdNxwJ!^zLUmW+1WzW_%L=O6jVJb)(5kE2WdnHzf3P9PG@<2ZYssQ7H)X=#V??| z`22@CL^Ik#c&c@&BvorkUaK5V&%md`6tqiz|s= z4Ye4#yh*R1PHE5|)R~pl|BjtjU6Rf(W;&F*DZ}TRVfJb5z@U$7@TIgrbp1{O()f>W zn8Shn$)iC7ko?Gvhu~xDVI_88lb@uvpTXaBkX8Ote3obl{b)TJDI~5Vr%v%by>sqm znfy&Cr_wX1ruM$A?%q2peyQJv>8kMOXA$hw6QlZxh5~9 z%Tn#rN4Gz>_PN#WJhNGbjJX$wt!rta7908m$^$6R&yC(gpPM;KVN2L)uUw#y7AYFh z@CWh=eC?Li;uNLblPvwr7FM%$;vRdiv(4Q0ks;YOMGunDP#b za<8)M{y&IB)x=;}B(lkj?CY{4xsA%P8foI9H-#|_qa}>u^o-YNyKl6H<^7)VOPi-9 zV@S~3qFMO_A|aO*TN=%rDAl{$1D?=d@^=V1##w(feZ`}aX6Q%1_>IK)L5YatIb@K2 z;y=804CO=ueaIzfsgJF|hyDMEw0?;ZNXHNB z6kj#%quzeSmTx8sReWr3N=0r64iJ7s<9<)-l#%=jqR*{HK;1$=RnD@=k;8*$W>F8a zj4mRlk86DZqdM?SgvIl0lahcAcibQesJ6=M!_zZR5f7%VSLNEun(oB zq;k`=xoGf?lUh1>T2CtCQd}s^qN|OTrftODEWA-U76K$8H#Sy|KA-u$z(XL&Ub60~ z0yXdjZDQG__JF{UWbU)j8`Ok@wj0*Y)^*}h6H)pK^Oo*WP8mQrukbs=El%`;c(CA; z8(G&polGpt|McYX%Y1A@-%{)`nP=u2y#yDF9tsIvzy8JfQW6q+qLH7m{#Qx=c}gVn zlK$qKk9Q3`;?x6CYO|6?Ju)~?d^{%bn<-5euq`?7-!nyPT3>C9VXHS&RteaT8jB~iN@lDOP zmJhWY{Aa3i7=if#+^R)87K7uW8EVjtz8M#)G4YP)$B+WB&+ss^J<-1a63gle@W0YA%G0$ zhugm%0fVt2^0NLfIMfBq1h2hgBvJUfMW#`?ybh4}pNn^N+~KZ_&_zb+eo{t}ES^5t2N_4N;Uche zeF)!~@mc7M27>#`R#;ed9-A1109hWI&Mu0ju;byOrGh)_u{AJ|jI;={rf0q72;A=J zd4rl5V`9{`xNbVuvyV8}kWgCiA){!r#lF>+G(H)xq(b@M^IMun#kWve$-{tQnqYWVh5k*u;Z1D5fqQu zGt}W;?1vx_g5~pL7h$JC9X`Rg&x^qVwyFd^&Cfc0V+t{kX1v0WzlG$TQHeS}ov1#o ufQ}i<)#lrHw-rA5Oe6xHf&cIDgmIkEXLY99f{DbG0R self + # + # Adds files and artifacts as classpath dependencies, and returns self. + def with(*specs) + @classpath |= Buildr.artifacts(specs.flatten).uniq + self + end + + # :call-seq: + # using(options) => self + # + # Sets the run options from a hash and returns self. + # + # For example: + # run.using :main=>'org.example.Main' + def using(*args) + args.pop.each { |key, value| @options[key.to_sym] = value } if Hash === args.last + + until args.empty? + new_runner = Run.select_by_name(args.pop) + @runner = new_runner.new(project) unless new_runner.nil? + end + + self + end + + # :call-seq: + # requires(*files) => self + # + # Adds additional files and directories as dependencies to the task and returns self. + # When specifying a directory, includes all files in that directory. + def requires(*files) + @files.include *files.flatten.compact + self + end + + def runner + @runner ||= guess_runner + end + + def runner? + @runner ||= begin + guess_runner if project.compile.language + rescue + nil + end + end + + def run + runner.run(self) if runner? + end + + def prerequisites #:nodoc: + super + @files + classpath + end + + private + + def guess_runner + runner = Run.select project.compile.language + fail 'Unable to guess runner for project.' unless runner + runner.new project + end + + def associate_with(project) + @project ||= project + end + end + + first_time do + Project.local_task 'run' + end + + before_define(:run => :test) do |project| + RunTask.define_task('run').tap do |run| + run.send(:associate_with, project) + run.enhance([project.compile, project.test]) do |t| + # double-enhance to execute the runner last + run.enhance { |t| t.run } + end + end + end + + after_define(:run => :test) do |project| + project.run.with project.compile.dependencies + project.run.with project.resources.target if project.resources.target + project.run.with project.compile.target if project.compile.target + end + + # :call-seq: + # run(&block) => RunTask + # + # This method returns the project's run task. It also accepts a block to be executed + # when the run task is invoked. + def run(&block) + task('run').tap do |t| + t.enhance &block if block + end + end + + end + + class Project + include Run + end +end diff --git a/buildr/lib/buildr/scala.rb b/buildr/lib/buildr/scala.rb new file mode 100644 index 0000000..df28674 --- /dev/null +++ b/buildr/lib/buildr/scala.rb @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +ENV['SCALA_HOME'] ||= '/opt/local/share/scala/' if File.exist?('/opt/local/share/scala/lib/scala-compiler.jar') +Buildr.repositories.remote << 'http://scala-tools.org/repo-releases' + +require 'buildr/scala/compiler' +require 'buildr/scala/tests' +require 'buildr/scala/bdd' +require 'buildr/scala/doc' +require 'buildr/scala/shell' + +Object::Scala = Buildr::Scala diff --git a/buildr/lib/buildr/scala/bdd.rb b/buildr/lib/buildr/scala/bdd.rb new file mode 100644 index 0000000..71e0245 --- /dev/null +++ b/buildr/lib/buildr/scala/bdd.rb @@ -0,0 +1,249 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr::Scala + + # Specs is a Scala based BDD framework. + # To use in your project: + # + # test.using :specs + # + # This framework will search in your project for: + # src/spec/scala/**/*.scala + class Specs < Buildr::TestFramework::JavaBDD + @lang = :scala + @bdd_dir = :spec + + VERSION = case + when Buildr::Scala.version?("2.8.0") + '1.6.5' + when Buildr::Scala.version?("2.8.1"), Buildr::Scala.version?("2.8.2"), Buildr::Scala.version?("2.9.0") + '1.6.8' + else + '1.6.9' + end + + class << self + def version + custom = Buildr.settings.build['scala.specs'] + (custom =~ /:/) ? Buildr.artifact(custom).version : VERSION + end + + def specs + custom = Buildr.settings.build['scala.specs'] + [ (custom =~ /:/) ? custom : "org.scala-tools.testing:#{artifact}:jar:#{version}" ] + end + + def artifact + Buildr.settings.build['scala.specs.artifact'] || "specs_#{Buildr::Scala.version_without_build}" + end + + def dependencies + unless @dependencies + super + # Add utility classes (e.g. SpecsSingletonRunner) and other dependencies + @dependencies |= [ File.join(File.dirname(__FILE__)) ] + + specs + + Check.dependencies + JUnit.dependencies + Scalac.dependencies + end + @dependencies + end + + def applies_to?(project) #:nodoc: + scala_files = Dir[project.path_to(:source, bdd_dir, lang, '**/*.scala')] + return false if scala_files.empty? + scala_files.detect { |f| find(f, /\s+(org\.specs\.)/) } + end + + private + def const_missing(const) + return super unless const == :REQUIRES # TODO: remove in 1.5 + Buildr.application.deprecated "Please use Scala::Specs.dependencies/.version instead of ScalaSpecs::REQUIRES/VERSION" + dependencies + end + + def find(file, pattern) + File.open(file, "r") do |infile| + while (line = infile.gets) + return true if line.match(pattern) + end + end + false + end + end + + def initialize(task, options) #:nodoc: + super + + specs = task.project.path_to(:source, :spec, :scala) + task.compile.from specs if File.directory?(specs) + + resources = task.project.path_to(:source, :spec, :resources) + task.resources.from resources if File.directory?(resources) + end + + def tests(dependencies) + candidates = filter_classes(dependencies, :interfaces => ['org.specs.Specification']) + + Java.load # Java is already loaded, but just in case + + filter = Java.org.apache.buildr.JavaTestFilter.new(dependencies.to_java(Java.java.lang.String)) + filter.add_fields ['MODULE$'].to_java(Java.java.lang.String) + filter.filter(candidates.to_java(Java.java.lang.String)).map { |s| s[0..(s.size - 2)] } + end + + def run(specs, dependencies) #:nodoc: + cmd_options = { :properties => options[:properties], + :java_args => options[:java_args], + :classpath => dependencies, + :name => false } + + runner = 'org.apache.buildr.SpecsSingletonRunner' + specs.inject [] do |passed, spec| + begin + unless Util.win_os? + Java::Commands.java(runner, task.compile.target.to_s, '-c', spec + '$', cmd_options) + else + Java::Commands.java(runner, task.compile.target.to_s, spec + '$', cmd_options) + end + rescue => e + passed + else + passed << spec + end + end + end + end + + class Specs2 < Buildr::TestFramework::JavaBDD + @lang = :scala + @bdd_dir = :spec + + VERSION = case + when Buildr::Scala.version?("2.8.0"), Buildr::Scala.version?("2.8.1"), Buildr::Scala.version?("2.8.2") + '1.5' + else + '1.6.1' + end + + class << self + def version + custom = Buildr.settings.build['scala.specs2'] + (custom =~ /:/) ? Buildr.artifact(custom).version : VERSION + end + + def specs + custom = Buildr.settings.build['scala.specs2'] + [ (custom =~ /:/) ? custom : "org.specs2:#{artifact}:jar:#{version}" ] + end + + def artifact + Buildr.settings.build['scala.specs2.artifact'] || "specs2_#{Buildr::Scala.version_without_build}" + end + + def scalaz_dependencies + if Buildr::Scala.version?("2.8") + [] + else + default_version = "6.0.1" + custom_version = Buildr.settings.build['scala.specs2-scalaz'] + version = (custom_version =~ /:/) ? Buildr.artifact(custom_version).version : default_version + + artifact = Buildr.settings.build['scala.specs2-scalaz.artifact'] || "specs2-scalaz-core_#{Buildr::Scala.version_without_build}" + + custom_spec = Buildr.settings.build['scala.specs2-scalaz'] + spec = [ (custom_spec =~ /:/) ? custom_spec : "org.specs2:#{artifact}:jar:#{version}" ] + Buildr.transitive(spec, :scopes => [nil, "compile", "runtime", "provided", "optional"], :optional => true) + end + end + + def dependencies + unless @dependencies + super + + # Add utility classes (e.g. SpecsSingletonRunner) and other dependencies + options = { + :scopes => [nil, "compile", "runtime", "provided", "optional"], + :optional => true + } + @dependencies |= [ File.join(File.dirname(__FILE__)) ] + Buildr.transitive(specs, options) + + scalaz_dependencies + Check.dependencies + JUnit.dependencies + + Scalac.dependencies + end + @dependencies + end + + def applies_to?(project) #:nodoc: + scala_files = Dir[project.path_to(:source, bdd_dir, lang, '**/*.scala')] + return false if scala_files.empty? + scala_files.detect { |f| find(f, /\s+(org\.specs2\.)/) } + end + + private + + def find(file, pattern) + File.open(file, "r") do |infile| + while (line = infile.gets) + return true if line.match(pattern) + end + end + false + end + end + + def initialize(task, options) #:nodoc: + super + + specs = task.project.path_to(:source, :spec, :scala) + task.compile.from specs if File.directory?(specs) + + resources = task.project.path_to(:source, :spec, :resources) + task.resources.from resources if File.directory?(resources) + end + + def tests(dependencies) + filter_classes(dependencies, :interfaces => ['org.specs2.Specification', 'org.specs2.mutable.Specification']) + end + + def run(specs, dependencies) #:nodoc: + properties = { "specs2.outDir" => task.compile.target.to_s } + + cmd_options = { :properties => options[:properties].merge(properties), + :java_args => options[:java_args], + :classpath => dependencies, + :name => false } + + runner = 'org.apache.buildr.Specs2Runner' + specs.inject [] do |passed, spec| + begin + Java::Commands.java(runner, spec, cmd_options) + rescue => e + passed + else + passed << spec + end + end + end + end +end + +# Backwards compatibility stuff. Remove in 1.5. +module Buildr + ScalaSpecs = Scala::Specs +end + +Buildr::TestFramework << Buildr::Scala::Specs +Buildr::TestFramework << Buildr::Scala::Specs2 + diff --git a/buildr/lib/buildr/scala/compiler.rb b/buildr/lib/buildr/scala/compiler.rb new file mode 100644 index 0000000..b29ba1e --- /dev/null +++ b/buildr/lib/buildr/scala/compiler.rb @@ -0,0 +1,295 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr::Scala + DEFAULT_VERSION = '2.9.1' + + class << self + + def version_str + warn "Use of Scala.version_str is deprecated. Use Scala.version instead" + version + end + + def installed_version + unless @installed_version + @installed_version = if Scalac.installed? + begin + # try to read the value from the properties file + props = Zip::ZipFile.open(File.expand_path('lib/scala-library.jar', Scalac.scala_home)) do |zipfile| + zipfile.read 'library.properties' + end + + version_str = props.match(/version\.number\s*=\s*([^\s]+)/).to_a[1] + + if version_str + md = version_str.match(/\d+\.\d[\d\.]*/) or + fail "Unable to parse Scala version: #{version_str}" + + md[0].sub(/.$/, "") # remove trailing dot, if any + end + rescue => e + warn "Unable to parse library.properties in $SCALA_HOME/lib/scala-library.jar: #{e}" + nil + end + end + end + + @installed_version + end + + def version + Buildr.settings.build['scala.version'] || installed_version || DEFAULT_VERSION + end + + # check if version matches any of the given prefixes + def version?(*v) + v.any? { |v| version.index(v.to_s) == 0 } + end + + # returns Scala version without build number. + # e.g. "2.9.0-1" => "2.9.0" + def version_without_build + version.split('-')[0] + end + end + + # Scalac compiler: + # compile.using(:scalac) + # Used by default if .scala files are found in the src/main/scala directory (or src/test/scala) + # and sets the target directory to target/classes (or target/test/classes). + # + # Accepts the following options: + # * :warnings -- Generate warnings if true (opposite of -nowarn). + # * :deprecation -- Output source locations where deprecated APIs are used. + # * :optimise -- Generates faster bytecode by applying optimisations to the program. + # * :target -- Class file compatibility with specified release. + # * :debug -- Generate debugging info. + # * :other -- Array of options to pass to the Scalac compiler as is, e.g. -Xprint-types + class Scalac < Buildr::Compiler::Base + + # The scalac compiler jars are added to classpath at load time, + # if you want to customize artifact versions, you must set them on the + # + # artifact_ns['Buildr::Compiler::Scalac'].library = '2.7.5' + # + # namespace before this file is required. This is of course, only + # if SCALA_HOME is not set or invalid. + REQUIRES = ArtifactNamespace.for(self) do |ns| + version = Buildr.settings.build['scala.version'] || DEFAULT_VERSION + ns.library! 'org.scala-lang:scala-library:jar:>=' + version + ns.compiler! 'org.scala-lang:scala-compiler:jar:>=' + version + end + + class << self + def scala_home + env_home = ENV['SCALA_HOME'] + + @home ||= (if !env_home.nil? && File.exists?(env_home + '/lib/scala-library.jar') && File.exists?(env_home + '/lib/scala-compiler.jar') + env_home + else + nil + end) + end + + def installed? + !scala_home.nil? + end + + def use_installed? + if installed? && Buildr.settings.build['scala.version'] + Buildr.settings.build['scala.version'] == Scala.installed_version + else + Buildr.settings.build['scala.version'].nil? && installed? + end + end + + def dependencies + if use_installed? + ['scala-library', 'scala-compiler'].map { |s| File.expand_path("lib/#{s}.jar", scala_home) } + else + REQUIRES.artifacts.map(&:to_s) + end + end + + def use_fsc + use_installed? && ENV["USE_FSC"] =~ /^(yes|on|true)$/i + end + + def applies_to?(project, task) #:nodoc: + paths = task.sources + [sources].flatten.map { |src| Array(project.path_to(:source, task.usage, src.to_sym)) } + paths.flatten! + + # Just select if we find .scala files + paths.any? { |path| !Dir["#{path}/**/*.scala"].empty? } + end + end + + Javac = Buildr::Compiler::Javac + + OPTIONS = [:warnings, :deprecation, :optimise, :target, :debug, :other, :javac] + + # Lazy evaluation to allow change in buildfile + Java.classpath << lambda { dependencies } + + specify :language=>:scala, :sources => [:scala, :java], :source_ext => [:scala, :java], + :target=>'classes', :target_ext=>'class', :packaging=>:jar + + def initialize(project, options) #:nodoc: + super + options[:debug] = Buildr.options.debug if options[:debug].nil? + options[:warnings] = verbose if options[:warnings].nil? + options[:deprecation] ||= false + options[:optimise] ||= false + options[:make] ||= :transitivenocp if Scala.version? 2.8 + options[:javac] ||= {} + + @java = Javac.new(project, options[:javac]) + end + + def compile(sources, target, dependencies) #:nodoc: + check_options(options, OPTIONS + (Scala.version?(2.8) ? [:make] : [])) + + java_sources = java_sources(sources) + enable_dep_tracing = Scala.version?(2.8) && java_sources.empty? + + dependencies.unshift target if enable_dep_tracing + + cmd_args = [] + cmd_args << '-classpath' << dependencies.join(File::PATH_SEPARATOR) + source_paths = sources.select { |source| File.directory?(source) } + cmd_args << '-sourcepath' << source_paths.join(File::PATH_SEPARATOR) unless source_paths.empty? + cmd_args << '-d' << File.expand_path(target) + cmd_args += scalac_args + + if enable_dep_tracing + dep_dir = File.expand_path(target) + Dir.mkdir dep_dir unless File.exists? dep_dir + + cmd_args << '-make:' + options[:make].to_s + cmd_args << '-dependencyfile' + cmd_args << File.expand_path('.scala-deps', dep_dir) + end + + cmd_args += files_from_sources(sources) + + unless Buildr.application.options.dryrun + trace((['scalac'] + cmd_args).join(' ')) + + if Scalac.use_fsc + system(([File.expand_path('bin/fsc', Scalac.scala_home)] + cmd_args).join(' ')) or + fail 'Failed to compile, see errors above' + else + Java.load + begin + Java.scala.tools.nsc.Main.process(cmd_args.to_java(Java.java.lang.String)) + rescue => e + fail "Scala compiler crashed:\n#{e.inspect}" + end + fail 'Failed to compile, see errors above' if Java.scala.tools.nsc.Main.reporter.hasErrors + end + + unless java_sources.empty? + trace 'Compiling mixed Java/Scala sources' + + # TODO includes scala-compiler.jar + deps = dependencies + Scalac.dependencies + [ File.expand_path(target) ] + @java.compile(java_sources, target, deps) + end + end + end + + protected + + # :nodoc: see Compiler:Base + def compile_map(sources, target) + target_ext = self.class.target_ext + ext_glob = Array(self.class.source_ext).join(',') + sources.flatten.map{|f| File.expand_path(f)}.inject({}) do |map, source| + sources = if File.directory?(source) + FileList["#{source}/**/*.{#{ext_glob}}"].reject { |file| File.directory?(file) } + else + [source] + end + + sources.each do |source| + # try to extract package name from .java or .scala files + if ['.java', '.scala'].include? File.extname(source) + name = File.basename(source).split(".")[0] + package = findFirst(source, /^\s*package\s+([^\s;]+)\s*;?\s*/) + packages = count(source, /^\s*package\s+([^\s;]+)\s*;?\s*/) + found = findFirst(source, /((trait)|(class)|(object))\s+(#{name})/) + + # if there's only one package statement and we know the target name, then we can depend + # directly on a specific file, otherwise, we depend on the general target + if (found && packages == 1) + map[source] = package ? File.join(target, package[1].gsub('.', '/'), name.ext(target_ext)) : target + else + map[source] = target + end + + elsif + map[source] = target + end + end + + map.each do |key,value| + map[key] = first_file unless map[key] + end + + map + end + end + + private + + def count(file, pattern) + count = 0 + File.open(file, "r") do |infile| + while (line = infile.gets) + count += 1 if line.match(pattern) + end + end + count + end + + def java_sources(sources) + sources.flatten.map { |source| File.directory?(source) ? FileList["#{source}/**/*.java"] : source } . + flatten.reject { |file| File.directory?(file) || File.extname(file) != '.java' }.map { |file| File.expand_path(file) }.uniq + end + + # Returns Scalac command line arguments from the set of options. + def scalac_args #:nodoc: + args = [] + args << "-nowarn" unless options[:warnings] + args << "-verbose" if trace?(:scalac) + if options[:debug] == true + args << (Scala.version?(2.7, 2.8) ? "-g" : "-g:vars") + elsif options[:debug] + args << "-g:#{options[:debug]}" + end + args << "-deprecation" if options[:deprecation] + args << "-optimise" if options[:optimise] + args << "-target:jvm-" + options[:target].to_s if options[:target] + args + Array(options[:other]) + end + + end + +end + +# Scala compiler comes first, ahead of Javac, this allows it to pick +# projects that mix Scala and Java code by spotting Scala code first. +Buildr::Compiler.compilers.unshift Buildr::Scala::Scalac diff --git a/buildr/lib/buildr/scala/doc.rb b/buildr/lib/buildr/scala/doc.rb new file mode 100644 index 0000000..2a6f4fe --- /dev/null +++ b/buildr/lib/buildr/scala/doc.rb @@ -0,0 +1,144 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Doc + + module ScaladocDefaults + include Extension + + # Default scaladoc -doc-title to project's comment or name + after_define(:scaladoc => :doc) do |project| + if project.doc.engine? Scaladoc + options = project.doc.options + key = Scala.version?(2.7) ? :windowtitle : "doc-title".to_sym + options[key] = (project.comment || project.name) unless options[key] + end + end + end + + class Scaladoc < Base + specify :language => :scala, :source_ext => 'scala' + + def generate(sources, target, options = {}) + cmd_args = [ '-d', target, trace?(:scaladoc) ? '-verbose' : '' ] + options.reject { |key, value| [:sourcepath, :classpath].include?(key) }. + each { |key, value| value.invoke if value.respond_to?(:invoke) }. + each do |key, value| + case value + when true, nil + cmd_args << "-#{key}" + when false + cmd_args << "-no#{key}" + when Hash + value.each { |k,v| cmd_args << "-#{key}" << k.to_s << v.to_s } + else + cmd_args += Array(value).map { |item| ["-#{key}", item.to_s] }.flatten + end + end + [:sourcepath, :classpath].each do |option| + Array(options[option]).flatten.tap do |paths| + cmd_args << "-#{option}" << paths.flatten.map(&:to_s).join(File::PATH_SEPARATOR) unless paths.empty? + end + end + cmd_args += sources.flatten.uniq + unless Buildr.application.options.dryrun + info "Generating Scaladoc for #{project.name}" + trace (['scaladoc'] + cmd_args).join(' ') + Java.load + begin + if Scala.version?(2.7, 2.8) + Java.scala.tools.nsc.ScalaDoc.process(cmd_args.to_java(Java.java.lang.String)) + else + scaladoc = Java.scala.tools.nsc.ScalaDoc.new + scaladoc.process(cmd_args.to_java(Java.java.lang.String)) + end + rescue => e + fail 'Failed to generate Scaladocs, see errors above: ' + e + end + end + end + end + + class VScaladoc < Base + VERSION = '1.2-m1' + Buildr.repositories.remote << 'http://scala-tools.org/repo-snapshots' + + class << self + def dependencies + [ "org.scala-tools:vscaladoc:jar:#{VERSION}" ] + end + end + + Java.classpath << dependencies + + specify :language => :scala, :source_ext => 'scala' + + def generate(sources, target, options = {}) + cmd_args = [ '-d', target, (trace?(:vscaladoc) ? '-verbose' : ''), + '-sourcepath', project.compile.sources.join(File::PATH_SEPARATOR) ] + options.reject { |key, value| [:sourcepath, :classpath].include?(key) }. + each { |key, value| value.invoke if value.respond_to?(:invoke) }. + each do |key, value| + case value + when true, nil + cmd_args << "-#{key}" + when false + cmd_args << "-no#{key}" + when Hash + value.each { |k,v| cmd_args << "-#{key}" << k.to_s << v.to_s } + else + cmd_args += Array(value).map { |item| ["-#{key}", item.to_s] }.flatten + end + end + [:sourcepath, :classpath].each do |option| + Array(options[option]).flatten.tap do |paths| + cmd_args << "-#{option}" << paths.flatten.map(&:to_s).join(File::PATH_SEPARATOR) unless paths.empty? + end + end + cmd_args += sources.flatten.uniq + unless Buildr.application.options.dryrun + info "Generating VScaladoc for #{project.name}" + trace (['vscaladoc'] + cmd_args).join(' ') + Java.load + Java.org.scala_tools.vscaladoc.Main.main(cmd_args.to_java(Java.java.lang.String)) == 0 or + fail 'Failed to generate VScaladocs, see errors above' + end + end + end + end + + module Packaging + module Scala + def package_as_scaladoc_spec(spec) #:nodoc: + spec.merge(:type=>:jar, :classifier=>'scaladoc') + end + + def package_as_scaladoc(file_name) #:nodoc: + ZipTask.define_task(file_name).tap do |zip| + zip.include :from=>doc.target + end + end + end + end + + class Project + include ScaladocDefaults + include Packaging::Scala + end +end + +Buildr::Doc.engines << Buildr::Doc::Scaladoc +Buildr::Doc.engines << Buildr::Doc::VScaladoc diff --git a/buildr/lib/buildr/scala/org/apache/buildr/Specs2Runner.java b/buildr/lib/buildr/scala/org/apache/buildr/Specs2Runner.java new file mode 100644 index 0000000..bba33b8 --- /dev/null +++ b/buildr/lib/buildr/scala/org/apache/buildr/Specs2Runner.java @@ -0,0 +1,38 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + +package org.apache.buildr; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Specs2Runner { + + public static void main(String[] args) { + try { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Class clazz = loader.loadClass("org.specs2.runner.ClassRunner"); + Object instance = clazz.newInstance(); + Method main = clazz.getMethod("main", String[].class); + main.invoke(instance, new Object[] { args }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} + + diff --git a/buildr/lib/buildr/scala/org/apache/buildr/SpecsSingletonRunner.class b/buildr/lib/buildr/scala/org/apache/buildr/SpecsSingletonRunner.class new file mode 100644 index 0000000000000000000000000000000000000000..7a27b57ba999b1e661fc1aacccc675b7fe6ea14f GIT binary patch literal 1891 zcma)7+jiST6y1tsk1Z4uoaW*tfs`iHc718wyB$iYn^3?>AWkSwxyjDdQL0ErkxRdT zU+5~lmX+te*j;2%-uWATfW;YG!g8Gjyv&TwIs2Tw_Bk{90P(>Vf zRFrTxf(s0*3~R#l5yL%(k5znvPZ`P#>nawo!LTVIykb~}drN|xNPZzfDcO5m!dTg` z_(ton#+_}wW|BoY)D26*RN_JTK!2>~OxWwIT&_Mz(vTGgz49L z-Dw%-9(OcUu&rSSR5YBeoBGj_hWq$Zz^WQ{@xaGg+|BK5l}jR|Z0m&eD|sGOzysB4@?4cc+=GD)YYj(sgxYXT;$OLoyIff8kBa0Fu zuI+1cCUI)&^Ik@8Hi|>Zy3-nRZE7?L>?iRWOeF$s*xq-zPAo_3Eyv-O>wgcH?pX;r z+2C%;wp{+i9T=gwU!;3y0xrTN{rlt-2=EO?iW?I(X(qinTKW&+x?w^v;v}aSVl;?=0lzeQY4fz?EatuXYnSF+6 zDwID)m|W{AIX|0z38kMi28GZIm?knyL?J^YBH~T-^8ZPH3zZ^%IYFpObiy0rkU0UX zb`VLAXUB7GMB7kfZ_v_)*1>2SZx)mjj8&E2FkY48O64WqY9rRcL?ILprQ0}D2*<;* z$u_2>Hl_=VK4vI5UX>?sw$jFIAwtB*$TU}#V 1 && args[1].equals("-c")); + String spec = colors ? args[2] : args[1]; + + run(args[0], colors, spec); + } + + // Incompatible with JVM 1.4 target + // @throws(classOf[Throwable]) + static void run(String path, boolean colors, String spec) { + try { + File parent = new File(path); + URL specURL = new File(parent, spec.replace('.', '/') + ".class").toURL(); + URLClassLoader loader = new URLClassLoader(new URL[] { specURL }, Thread.currentThread().getContextClassLoader()); + + Class clazz = loader.loadClass(spec); + Object instance = clazz.getField("MODULE$").get(null); + + Method main = clazz.getMethod("main", String[].class); + + String[] args = colors ? new String[] { "-c" } : new String[] {}; + main.invoke(instance, new Object[] { args }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/buildr/lib/buildr/scala/shell.rb b/buildr/lib/buildr/scala/shell.rb new file mode 100644 index 0000000..8be0fa7 --- /dev/null +++ b/buildr/lib/buildr/scala/shell.rb @@ -0,0 +1,48 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Scala + class ScalaShell < Buildr::Shell::Base + include Buildr::JRebel + + specify :name => :scala, :languages => [:scala] + + def launch(task) + jline = [File.expand_path("lib/jline.jar", Scalac.scala_home)].find_all { |f| File.exist? f } + jline = ['jline:jline:jar:0.9.94'] if jline.empty? + + cp = project.compile.dependencies + + Scalac.dependencies + + project.test.dependencies + + task.classpath + + java_args = jrebel_args + task.java_args + + props = jrebel_props(project).merge(task.properties) + + Java::Commands.java 'scala.tools.nsc.MainGenericRunner', + '-cp', cp.join(File::PATH_SEPARATOR), + { + :properties => props, + :classpath => cp + jline, + :java_args => java_args + } + end + end + end +end + +Buildr::Shell.providers << Buildr::Scala::ScalaShell diff --git a/buildr/lib/buildr/scala/tests.rb b/buildr/lib/buildr/scala/tests.rb new file mode 100644 index 0000000..0191a97 --- /dev/null +++ b/buildr/lib/buildr/scala/tests.rb @@ -0,0 +1,209 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr::Scala + + # Mockito is available when running ScalaTest + module Mockito + VERSION = '1.8.5' + + class << self + def version + Buildr.settings.build['scalatest-mockito'] || Buildr.settings.build['mockito'] || VERSION + end + + def dependencies + @dependencies ||= ["org.mockito:mockito-all:jar:#{version}"] + end + end + end + + # Scala::Check is available when using Scala::Test or Scala::Specs + module Check + VERSION = case + when Buildr::Scala.version?("2.7") + '1.6' + when Buildr::Scala.version?("2.8.0") + '1.7' + when Buildr::Scala.version?("2.8.1") + '1.8' + else + '1.9' + end + + class << self + def version + Buildr.settings.build['scala.check'] || VERSION + end + + def classifier + Buildr.settings.build['scala.check.classifier'] + end + + def artifact + Buildr.settings.build['scala.check.artifact'] || "scalacheck_#{Buildr::Scala.version_without_build}" + end + + def dependencies + return [version] if (version =~ /:/) + if classifier + ["org.scala-tools.testing:#{artifact}:jar:#{classifier}:#{version}"] + else + ["org.scala-tools.testing:#{artifact}:jar:#{version}"] + end + end + + private + def const_missing(const) + return super unless const == :REQUIRES # TODO: remove in 1.5 + Buildr.application.deprecated "Please use Scala::Check.dependencies/.version instead of ScalaCheck::REQUIRES/VERSION" + dependencies + end + end + end + + + # ScalaTest framework, the default test framework for Scala tests. + # + # Support the following options: + # * :properties -- Hash of system properties available to the test case. + # * :environment -- Hash of environment variables available to the test case. + # * :java_args -- Arguments passed as is to the JVM. + class ScalaTest < Buildr::TestFramework::Java + + VERSION = Buildr::Scala.version?(2.7, 2.8) ? '1.3' : '1.6.1' + + class << self + def version + custom = Buildr.settings.build['scala.test'] + (custom =~ /:/) ? Buildr.artifact(custom).version : VERSION + end + + def specs + custom = Buildr.settings.build['scala.test'] + return custom if (custom =~ /:/) + if Buildr::Scala.version?(2.7, 2.8) + "org.scalatest:scalatest:jar:#{version}" + else + "org.scalatest:scalatest_#{Buildr::Scala.version_without_build}:jar:#{version}" + end + end + + def dependencies + [specs] + Check.dependencies + JMock.dependencies + JUnit.dependencies + Mockito.dependencies + end + + def applies_to?(project) #:nodoc: + !Dir[project.path_to(:source, :test, :scala, '**/*.scala')].empty? + end + + private + def const_missing(const) + return super unless const == :REQUIRES # TODO: remove in 1.5 + Buildr.application.deprecated "Please use Scala::Test.dependencies/.version instead of ScalaTest::REQUIRES/VERSION" + dependencies + end + end + + # annotation-based group inclusion + attr_accessor :group_includes + + # annotation-based group exclusion + attr_accessor :group_excludes + + def initialize(test_task, options) + super + @group_includes = [] + @group_excludes = [] + end + + def tests(dependencies) #:nodoc: + filter_classes(dependencies, :interfaces => %w{org.scalatest.Suite}) + end + + def run(scalatest, dependencies) #:nodoc: + mkpath task.report_to.to_s + success = [] + + reporter_options = if (ScalaTest.version =~ /^0\./) + 'TFGBSAR' # testSucceeded, testFailed, testIgnored, suiteAborted, runStopped, runAborted, runCompleted + else + '' + end + + scalatest.each do |suite| + info "ScalaTest #{suite.inspect}" + # Use Ant to execute the ScalaTest task, gives us performance and reporting. + reportDir = task.report_to.to_s + reportFile = File.join(reportDir, "TEST-#{suite}.txt") + taskdef = Buildr.artifacts(self.class.dependencies).each(&:invoke).map(&:to_s) + Buildr.ant('scalatest') do |ant| + # ScalaTestTask was deprecated in 1.2, in favor of ScalaTestAntTask + classname = (ScalaTest.version =~ /^1\.[01]/) ? \ + 'org.scalatest.tools.ScalaTestTask' : 'org.scalatest.tools.ScalaTestAntTask' + ant.taskdef :name=>'scalatest', :classname=>classname, + :classpath=>taskdef.join(File::PATH_SEPARATOR) + ant.scalatest :runpath=>dependencies.join(File::PATH_SEPARATOR) do + ant.suite :classname=>suite + ant.reporter :type=>'stdout', :config=>reporter_options + ant.reporter :type=>'file', :filename=> reportFile, :config=>reporter_options + ant.reporter :type=>(ScalaTest.version == "1.0") ? "xml" : "junitxml", + :directory=> reportDir, :config=>reporter_options + # TODO: This should be name=>value pairs! + #ant.includes group_includes.join(" ") if group_includes + #ant.excludes group_excludes.join(" ") if group_excludes + (options[:properties] || []).each { |name, value| ant.config :name=>name, :value=>value } + end + end + + # Parse for failures, errors, etc. + # This is a bit of a pain right now because ScalaTest doesn't flush its + # output synchronously before the Ant test finishes so we have to loop + # and wait for an indication that the test run was completed. + failed = false + completed = false + wait = 0 + while (!completed) do + File.open(reportFile, "r") do |input| + while (line = input.gets) do + failed = (line =~ /(TESTS? FAILED)|(RUN STOPPED)|(RUN ABORTED)/) unless failed + completed |= (line =~ /Run completed/) + break if (failed) + end + end + wait += 1 + break if (failed || wait > 10) + unless completed + sleep(1) + end + end + success << suite if (completed && !failed) + end + + success + end # run + + end # ScalaTest + +end + + +# Backwards compatibility stuff. Remove in 1.5. +module Buildr + ScalaCheck = Scala::Check + ScalaTest = Scala::ScalaTest +end + +Buildr::TestFramework << Buildr::Scala::ScalaTest diff --git a/buildr/lib/buildr/shell.rb b/buildr/lib/buildr/shell.rb new file mode 100644 index 0000000..1dcf27d --- /dev/null +++ b/buildr/lib/buildr/shell.rb @@ -0,0 +1,184 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + module Shell + include Extension + + class << self + def providers + @providers ||= [] + end + + def select_by_lang(lang) + fail 'Unable to define shell task for nil language' if lang.nil? + providers.detect { |e| e.languages.nil? ? false : e.languages.include?(lang.to_sym) } + end + + alias_method :select, :select_by_lang + + def select_by_name(name) + fail 'Unable to define run task for nil' if name.nil? + providers.detect { |e| e.to_sym == name.to_sym } + end + + def define_task(project, name, provider = nil) + ShellTask.define_task(name).tap do |t| + t.send(:associate_with, project) + t.enhance([project.compile]) do |t| + # double-enhance to execute the provider last + t.enhance { |t| t.run } + end + t.using provider.to_sym if provider + end + end + end + + first_time do + Project.local_task 'shell' + + providers.each { |provider| Project.local_task "shell:#{provider.to_sym}" } + end + + before_define(:shell => :compile) do |project| + define_task(project, "shell") + providers.each { |provider| define_task(project, "shell:#{provider.to_sym}", provider) } + end + + after_define(:shell => :compile) do |project| + unless project.shell.provider + provider = providers.find { |p| p.languages.include? project.compile.language if p.languages } + if provider + project.shell.using provider.to_sym + project.shell.with project.test.compile.dependencies + end + end + end + + # Base class for any shell provider. + class Base + attr_reader :project # :nodoc: + + class << self + attr_accessor :shell_name, :languages + + def specify(options) + @shell_name ||= options[:name] + @languages ||= options[:languages] + end + + def to_sym + @shell_name || name.split('::').last.downcase.to_sym + end + end + + def initialize(project) + @project = project + end + + def launch(task) + fail 'Not implemented' + end + + end + + class ShellTask < Rake::Task + attr_reader :project # :nodoc: + + # Classpath dependencies. + attr_accessor :classpath + + # Returns the run options. + attr_reader :options + + # Underlying shell provider + attr_reader :provider + + def initialize(*args) # :nodoc: + super + @options = {} + @classpath = [] + end + + # :call-seq: + # with(*artifacts) => self + # + # Adds files and artifacts as classpath dependencies, and returns self. + def with(*specs) + @classpath |= Buildr.artifacts(specs.flatten).uniq + self + end + + # :call-seq: + # using(options) => self + # + # Sets the run options from a hash and returns self. + # + # For example: + # shell.using :properties => {'foo' => 'bar'} + # shell.using :bsh + def using(*args) + if Hash === args.last + args.pop.each { |key, value| @options[key.to_sym] = value } + end + + until args.empty? + new_shell = Shell.select_by_name(args.pop) + @provider = new_shell.new(project) unless new_shell.nil? + end + + self + end + + def run + fail "No shell provider defined in project '#{project.name}' for language '#{project.compile.language.inspect}'" unless provider + provider.launch(self) + end + + def prerequisites #:nodoc: + super + classpath + end + + def java_args + @options[:java_args] || (ENV['JAVA_OPTS'] || ENV['JAVA_OPTIONS']).to_s.split + end + + def properties + @options[:properties] || {} + end + + private + def associate_with(project) + @project ||= project + end + + end + + # :call-seq: + # shell(&block) => ShellTask + # + # This method returns the project's shell task. It also accepts a block to be executed + # when the shell task is invoked. + def shell(&block) + task('shell').tap do |t| + t.enhance &block if block + end + end + end + + class Project + include Shell + end +end diff --git a/buildr/lib/buildr/version.rb b/buildr/lib/buildr/version.rb new file mode 100644 index 0000000..e84e789 --- /dev/null +++ b/buildr/lib/buildr/version.rb @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +module Buildr + VERSION = '1.4.7'.freeze +end \ No newline at end of file diff --git a/buildr/rakelib/all-in-one.rake b/buildr/rakelib/all-in-one.rake new file mode 100644 index 0000000..d86891e --- /dev/null +++ b/buildr/rakelib/all-in-one.rake @@ -0,0 +1,122 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +desc "Create JRuby all-in-one distribution" +task "all-in-one" => 'all-in-one:all-in-one' + +namespace :'all-in-one' do + + version = "1.6.4" + jruby_distro = "jruby-bin-#{version}.tar.gz" + url = "http://jruby.org.s3.amazonaws.com/downloads/#{version}/#{jruby_distro}" + dir = "jruby-#{version}" + + task "all-in-one" => [:gem, + # Prepare to run + :prepare, + # Download and extract JRuby + :download_and_extract, + # Cleanup JRuby distribution + :clean_dist, + # Install Buildr gem and dependencies + :install_dependencies, + # Add Buildr executables/scripts + :add_execs, + # Package distribution + :package + ] + + desc 'Prepare to run' + task :prepare do + mkpath '_all-in-one' + cd '_all-in-one' + end + + desc 'Download and extract JRuby' + task :download_and_extract do + unless File.exist? jruby_distro + puts "Downloading JRuby from #{url} ..." + sh 'wget', url + puts "[X] Downloaded JRuby" + end + + rm_rf dir if File.exist? dir + + puts "Extracting JRuby to #{dir} ..." + sh 'tar', 'xzf', jruby_distro + puts "[X] Extracted JRuby" + cd dir + end + + desc 'Cleanup JRuby distribution' + task :clean_dist do + puts 'Cleaning...' + rm_rf 'docs' + mkpath 'jruby-docs' + mv Dir["COPYING*"], 'jruby-docs' + mv Dir["LICENSE*"], 'jruby-docs' + mv 'README', 'jruby-docs' + rm_rf 'lib/ruby/1.9' + rm_rf 'lib/ruby/gems/1.8/doc' + rm_rf 'samples' + rm_rf 'share' + end + + desc 'Install Buildr gem and dependencies' + task :install_dependencies do + puts "Install rubygems-update" + sh "bin/jruby -S gem install rubygems-update" + + puts "Upgrade Rubygems" + sh "bin/jruby -S gem update --system" + + puts "Install ffi-ncurses" + sh "bin/jruby -S gem install ffi-ncurses" + + puts "Install Buildr gem ..." + sh "bin/jruby", '-S', 'gem', 'install', FileList['../../pkg/*-java.gem'].first, + '--no-rdoc', '--no-ri' + puts "[X] Install Buildr gem" + end + + desc 'Add Buildr executables/scripts' + task :add_execs do + cp 'bin/jruby.exe', 'bin/_buildr.exe' + cp Dir["../../all-in-one/*"], 'bin' + end + + desc 'Package distribution' + task :package do + puts "Zipping distribution ..." + cd '..' + new_dir = "#{spec.name}-all-in-one-#{spec.version}" + mv dir, new_dir + zip = "#{new_dir}.zip" + rm zip if File.exist? zip + sh 'zip', '-q', '-r', zip, new_dir + puts "[X] Zipped distribution" + + puts "Tarring distribution ..." + tar = "#{new_dir}.tar.gz" + rm tar if File.exist? tar + sh 'tar', 'czf', tar, new_dir + puts "[X] Tarred distribution" + + rm_rf new_dir + end + +end + +task(:clobber) { rm_rf '_all-in-one' } diff --git a/buildr/rakelib/checks.rake b/buildr/rakelib/checks.rake new file mode 100644 index 0000000..65d1e78 --- /dev/null +++ b/buildr/rakelib/checks.rake @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +desc "Check that source files contain the Apache license" +task :license=>FileList["**/*.{rb,rake,java,gemspec,buildfile}", 'Rakefile'] do |task| + puts "Checking that files contain the Apache license ... " + required = task.prerequisites.select { |fn| File.file?(fn) } + missing = required.reject { |fn| + comments = File.read(fn).scan(/(\/\*(.*?)\*\/)|^#\s+(.*?)$|^-#\s+(.*?)$|/m). + map { |match| match.compact }.flatten.join("\n") + comments =~ /Licensed to the Apache Software Foundation/ && comments =~ /http:\/\/www.apache.org\/licenses\/LICENSE-2.0/ + } + fail "#{missing.join(', ')} missing Apache License, please add it before making a release!" unless missing.empty? + puts "[x] Source files contain the Apache license" +end diff --git a/buildr/rakelib/doc.rake b/buildr/rakelib/doc.rake new file mode 100644 index 0000000..5f0b78b --- /dev/null +++ b/buildr/rakelib/doc.rake @@ -0,0 +1,123 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +if !RUBY_PLATFORM[/java/] + gem 'rdoc' + require 'rdoc/task' + desc "Creates a symlink to rake's lib directory to support combined rdoc generation" + file "rake/lib" do + rake_path = $LOAD_PATH.find { |p| File.exist? File.join(p, "rake.rb") } + mkdir_p "rake" + File.symlink(rake_path, "rake/lib") + end + + desc "Generate RDoc documentation in rdoc/" + RDoc::Task.new :rdoc do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = spec.name + rdoc.options = spec.rdoc_options.clone + rdoc.rdoc_files.include('lib/**/*.rb') + rdoc.rdoc_files.include spec.extra_rdoc_files + + # include rake source for better inheritance rdoc + rdoc.rdoc_files.include('rake/lib/**.rb') + end + task :rdoc => ["rake/lib"] + + begin + require 'jekylltask' + module TocFilter + def toc(input) + output = "
    " + input.scan(/<(h2)(?:>|\s+(.*?)>)([^<]*)<\/\1\s*>/mi).each do |entry| + id = (entry[1][/^id=(['"])(.*)\1$/, 2] rescue nil) + title = entry[2].gsub(/<(\w*).*?>(.*?)<\/\1\s*>/m, '\2').strip + if id + output << %{
  1. #{title}
  2. } + else + output << %{
  3. #{title}
  4. } + end + end + output << "
" + output + end + end + Liquid::Template.register_filter(TocFilter) + + desc "Generate Buildr documentation in _site/" + JekyllTask.new :jekyll do |task| + task.source = 'doc' + task.target = '_site' + end + + rescue LoadError + puts "Buildr uses the jekyll gem to generate the Web site. You can install it by running bundler" + end + + if `pygmentize -V`.empty? + puts "Buildr uses the Pygments python library. You can install it by running 'sudo easy_install Pygments'" + end + + desc "Generate Buildr documentation as buildr.pdf" + file 'buildr.pdf'=>'_site' do |task| + pages = File.read('_site/preface.html').scan(/
  • ['_site', :rdoc, '_reports/specs.html', '_reports/coverage', 'buildr.pdf'] do + cp_r 'rdoc', '_site' + fail 'No RDocs in site directory' unless File.exist?('_site/rdoc/lib/buildr_rb.html') + cp '_reports/specs.html', '_site' + cp_r '_reports/coverage', '_site' + fail 'No coverage report in site directory' unless File.exist?('_site/coverage/index.html') + cp 'CHANGELOG', '_site' + open("_site/.htaccess", "w") do |htaccess| + htaccess << %Q{ + +ForceType 'text/plain; charset=UTF-8' + +} + end + cp 'buildr.pdf', '_site' + fail 'No PDF in site directory' unless File.exist?('_site/buildr.pdf') + puts 'OK' + end + +# Publish prerequisites to Web site. + task 'publish'=>:site do + target = "people.apache.org:/www/#{spec.name}.apache.org/" + puts "Uploading new site to #{target} ..." + sh 'rsync', '--progress', '--recursive', '--delete', '_site/', target + sh 'ssh', 'people.apache.org', 'chmod', '-f', '-R', 'g+w', "/www/#{spec.name}.apache.org/*" + puts "Done" + end + +# Update HTML + PDF documentation (but not entire site; no specs, coverage, etc.) + task 'publish-doc' => ['buildr.pdf', '_site'] do + cp 'buildr.pdf', '_site' + target = "people.apache.org:/www/#{spec.name}.apache.org/" + puts "Uploading new site to #{target} ..." + sh 'rsync', '--progress', '--recursive', '_site/', target # Note: no --delete + sh 'ssh', 'people.apache.org', 'chmod', '-f', '-R', 'g+w', "/www/#{spec.name}.apache.org/*" + puts "Done" + end + + task :clobber do + rm_rf '_site' + rm_f 'buildr.pdf' + rm_f 'prince_errors.log' + end +end diff --git a/buildr/rakelib/metrics.rake b/buildr/rakelib/metrics.rake new file mode 100644 index 0000000..f3982d5 --- /dev/null +++ b/buildr/rakelib/metrics.rake @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +namespace :metrics do + desc 'run Saikuro reports' + task :saikuro do + gem 'atoulme-Saikuro' + require 'saikuro' + output_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "_reports", "saikuro")) + base_dir = Pathname.new(File.expand_path(File.join(File.dirname(__FILE__), ".."))) + rb_files = ["lib", "addon"].collect { |folder| + FileList[File.expand_path(File.join(File.dirname(__FILE__), "..", folder, "**", "*.rb"))] + }.flatten.collect {|path| + Pathname.new(path).relative_path_from(base_dir).to_s + } + SaikuroRunner.new.run(rb_files, output_dir) + end + + desc 'generate ccn treemap' + task :ccn_treemap do + require 'saikuro_treemap' + SaikuroTreemap.generate_treemap :code_dirs => ['lib', 'addon'], :output_file => "_reports/saikuro_treemap.html" + end +end + +desc 'Run all metrics tools' +task :metrics => ["metrics:saikuro", "metrics:ccn_treemap"] \ No newline at end of file diff --git a/buildr/rakelib/package.rake b/buildr/rakelib/package.rake new file mode 100644 index 0000000..9b816f0 --- /dev/null +++ b/buildr/rakelib/package.rake @@ -0,0 +1,73 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require 'rubygems/package_task' + + +package = Gem::PackageTask.new(spec) do |pkg| + pkg.need_tar = true + pkg.need_zip = true +end + +desc "Install Buildr from source" +task :install=>["#{package.package_dir}/#{package.gem_spec.file_name}"] do |task| + print "Installing #{spec.name} ... " + args = Config::CONFIG['ruby_install_name'], '-S', 'gem', 'install', "#{package.package_dir}/#{package.gem_spec.file_name}" + args.unshift('sudo') if sudo_needed? + sh *args + puts "[x] Installed Buildr #{spec.version}" +end + +desc "Uninstall previous rake install" +task :uninstall do |task| + puts "Uninstalling #{spec.name} ... " + args = Config::CONFIG['ruby_install_name'], '-S', 'gem', 'uninstall', spec.name, '--version', spec.version.to_s + args.unshift('sudo') if sudo_needed? + sh *args + puts "[x] Uninstalled Buildr #{spec.version}" +end + + +desc "Compile Java libraries used by Buildr" +task :compile do + puts "Compiling Java libraries ..." + args = Config::CONFIG['ruby_install_name'], File.expand_path(RUBY_PLATFORM[/java/] ? '_jbuildr' : '_buildr'), '--buildfile', 'buildr.buildfile', 'compile' + args << '--trace' if Rake.application.options.trace + sh *args +end +file Gem::PackageTask.new(spec).package_dir => :compile +file Gem::PackageTask.new(spec).package_dir_path => :compile + +# We also need the other packages (JRuby if building on Ruby, and vice versa) +# Must call new with block, even if block does nothing, otherwise bad things happen. +@specs.values.each do |s| + Gem::PackageTask.new(s) { |task| } +end + + +desc "Upload snapshot packages over to people.apache.org" +task :snapshot=>[:package] do + rm_rf '_snapshot' # Always start with empty directory + puts "Copying existing gems from Apache" + sh 'rsync', '--progress', '--recursive', 'people.apache.org:public_html/buildr/snapshot/', '_snapshot/' + puts "Copying new gems over" + cp FileList['pkg/{*.gem,*.tgz,*.zip}'], '_snapshot/gems' + puts "Generating gem index ..." + sh 'gem', 'generate_index', '--directory', '_snapshot' + puts "Copying gem and index back to Apache" + sh 'rsync', '--progress', '--recursive', '_snapshot/', 'people.apache.org:public_html/buildr/snapshot/' +end +task(:clobber) { rm_rf '_snapshot' } diff --git a/buildr/rakelib/release.rake b/buildr/rakelib/release.rake new file mode 100644 index 0000000..e305fef --- /dev/null +++ b/buildr/rakelib/release.rake @@ -0,0 +1,160 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +task :release do + # First, we need to get all the staged files from Apache to _release. + mkpath '_release' + lambda do + url = "people.apache.org:~/public_html/#{spec.name}/#{spec.version}" + puts "Populating _release directory from #{url} ..." + sh 'rsync', '--progress', '--recursive', url, '_release' + puts "[X] Staged files are now in _release" + end.call + + + # Upload binary and source packages and new Web site + lambda do + target = "people.apache.org:/www/www.apache.org/dist/#{spec.name}/#{spec.version}" + puts "Uploading packages to www.apache.org/dist ..." + host, remote_dir = target.split(':') + sh 'ssh', host, 'rm', '-rf', remote_dir rescue nil + sh 'ssh', host, 'mkdir', remote_dir + sh 'rsync', '--progress', '--recursive', "_release/#{spec.version}/dist/", target + puts "[X] Uploaded packages to www.apache.org/dist" + + target = "people.apache.org:/www/#{spec.name}.apache.org/" + puts "Uploading new site to #{spec.name}.apache.org ..." + sh 'rsync', '--progress', '--recursive', '--delete', "_release/#{spec.version}/site/", target + sh 'ssh', 'people.apache.org', 'chmod', '-f', '-R', 'g+w', "/www/#{spec.name}.apache.org/*" + puts "[X] Uploaded new site to #{spec.name}.apache.org" + end.call + + + # Upload binary and source packages to RubyForge. + lambda do + # update rubyforge projects, processors, etc. in local config + sh 'rubyforge', 'config' + files = FileList["_release/#{spec.version}/dist/*.{gem,tgz,zip}"] + puts "Uploading #{spec.version} to RubyForge ... " + rubyforge = RubyForge.new.configure + rubyforge.login + rubyforge.userconfig.merge!('release_changes'=>"_release/#{spec.version}/CHANGES", 'preformatted' => true) + rubyforge.add_release spec.rubyforge_project.downcase, spec.name.downcase, spec.version.to_s, *files + + puts "Posting news to RubyForge ... " + changes = File.read("_release/#{spec.version}/CHANGES")[/.*?\n(.*)/m, 1] + rubyforge.post_news spec.rubyforge_project.downcase, "Buildr #{spec.version} released", + "#{spec.description}\n\nNew in Buildr #{spec.version}:\n#{changes.gsub(/^/, ' ')}\n" + puts "[X] Uploaded gems and source files to #{spec.name}.rubyforge.org" + end.call + + # Push gems to Rubyforge.org / Gemcutter + lambda do + files = FileList["_release/#{spec.version}/dist/*.{gem}"] + files.each do |f| + puts "Push gem #{f} to RubyForge.org / Gemcutter ... " + `gem push #{f}` + end + puts "[X] Pushed gems to Rubyforge.org / Gemcutter" + end + + # Create an SVN tag for this release. + lambda do + info = `svn info` + `git svn info` # Using either svn or git-svn + if url = info[/^URL:/] && info.scan(/^URL: (.*)/)[0][0] + new_url = url.sub(/(trunk$)|(branches\/\w*)$/, "tags/#{spec.version}") + unless url == new_url + sh 'svn', 'copy', url, new_url, '-m', "Release #{spec.version}" do |ok, res| + if ok + puts "[X] Tagged this release as tags/#{spec.version} ... " + else + puts "Could not create tag, please do it yourself!" + puts %{ svn copy #{url} #{new_url} -m "Release #{spec.version}"} + end + end + end + end + end.call + + + # Update CHANGELOG to next release number. + lambda do + next_version = spec.version.to_s.split('.').map { |v| v.to_i }. + zip([0, 0, 1]).map { |a| a.inject(0) { |t,i| t + i } }.join('.') + modified = "#{next_version} (Pending)\n\n" + File.read('CHANGELOG') + File.open 'CHANGELOG', 'w' do |file| + file.write modified + end + puts "[X] Updated CHANGELOG and added entry for next release" + end.call + + + # Update source files to next release number. + lambda do + next_version = spec.version.to_s.split('.').map { |v| v.to_i }. + zip([0, 0, 1]).map { |a| a.inject(0) { |t,i| t + i } }.join('.') + + ver_file = "lib/#{spec.name}.rb" + if File.exist?(ver_file) + modified = File.read(ver_file).sub(/(VERSION\s*=\s*)(['"])(.*)\2/) { |line| "#{$1}#{$2}#{next_version}#{$2}" } + File.open ver_file, 'w' do |file| + file.write modified + end + puts "[X] Updated #{ver_file} to next release" + end + + spec_file = "#{spec.name}.gemspec" + if File.exist?(spec_file) + modified = File.read(spec_file).sub(/(s(?:pec)?\.version\s*=\s*)(['"])(.*)\2/) { |line| "#{$1}#{$2}#{next_version}#{$2}" } + File.open spec_file, 'w' do |file| + file.write modified + end + puts "[X] Updated #{spec_file} to next release" + end + end.call + + + # Prepare release announcement email. + lambda do + changes = File.read("_release/#{spec.version}/CHANGES")[/.*?\n(.*)/m, 1] + email = <<-EMAIL +To: users@buildr.apache.org, announce@apache.org +Subject: [ANNOUNCE] Apache Buildr #{spec.version} released + +#{spec.description} + +New in this release: + +#{changes.gsub(/^/, ' ')} + +To learn more about Buildr and get started: +http://buildr.apache.org/ + +Thanks! +The Apache Buildr Team + + EMAIL + File.open 'announce-email.txt', 'w' do |file| + file.write email + end + puts "[X] Created release announce email template in 'announce-email.txt'" + puts email + end + +end + + +task(:clobber) { rm_rf '_release' } diff --git a/buildr/rakelib/rspec.rake b/buildr/rakelib/rspec.rake new file mode 100644 index 0000000..7f52302 --- /dev/null +++ b/buildr/rakelib/rspec.rake @@ -0,0 +1,90 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +begin + require 'rspec/core/rake_task' + directory '_reports' + + def default_spec_opts + default = %w{--format documentation --out _reports/specs.txt --backtrace} + default << '--colour' if $stdout.isatty && !(Config::CONFIG['host_os'] =~ /mswin|win32|dos/i) + default + end + + # RSpec doesn't support file exclusion, so hack our own. + class RSpec::Core::RakeTask + attr_accessor :rspec_files + private + def files_to_run + @rspec_files + end + end + + desc "Run all specs" + RSpec::Core::RakeTask.new :spec=>['_reports', :compile] do |task| + ENV['USE_FSC'] = 'no' + task.rspec_files = FileList['spec/**/*_spec.rb'] + task.rspec_files.exclude('spec/groovy/*') if RUBY_PLATFORM[/java/] + task.rspec_opts = default_spec_opts + task.rspec_opts = %w{--format html --out _reports/specs.html --backtrace} + end + file('_reports/specs.html') { task(:spec).invoke } + + desc 'Run RSpec and generate Spec and coverage reports (slow)' + RSpec::Core::RakeTask.new :coverage=>['_reports', :compile] do |task| + ENV['USE_FSC'] = 'no' + task.rspec_files = FileList['spec/**/*_spec.rb'] + task.rspec_files.exclude('spec/groovy/*') if RUBY_PLATFORM[/java/] + task.rspec_opts = default_spec_opts + task.rcov = true + task.rcov_opts = %w{-o _reports/coverage --exclude / --include-file ^lib --text-summary} + end + file('_reports/coverage') { task(:coverage).invoke } + + task :load_ci_reporter do + gem 'ci_reporter' + ENV['CI_REPORTS'] = '_reports/ci' + # CI_Reporter does not quote the path to rspec_loader which causes problems when ruby is installed in C:/Program Files. + # However, newer versions of rspec don't like double quotes escaping as well, so removing them for now. + ci_rep_path = Gem.loaded_specs['ci_reporter'].full_gem_path + ENV["SPEC_OPTS"] = [ENV["SPEC_OPTS"], default_spec_opts, "--require", "#{ci_rep_path}/lib/ci/reporter/rake/rspec_loader.rb", "--format", "CI::Reporter::RSpec"].join(" ") + end + + desc 'Run all specs with CI reporter' + task :ci=>[:load_ci_reporter, :spec] + + # Useful for testing with JRuby when using Ruby and vice versa. + namespace :spec do + desc "Run all specs specifically with Ruby" + task :ruby do + puts "Running test suite using Ruby ..." + sh 'ruby -S rake spec' + end + + desc "Run all specs specifically with JRuby" + task :jruby do + puts "Running test suite using JRuby ..." + sh 'jruby -S rake spec' + end + end + + task :clobber do + rm_f 'failed' + rm_rf '_reports' + end + +rescue LoadError => e + puts "Buildr uses RSpec. You can install it by running bundler" +end diff --git a/buildr/rakelib/stage.rake b/buildr/rakelib/stage.rake new file mode 100644 index 0000000..c6d7df0 --- /dev/null +++ b/buildr/rakelib/stage.rake @@ -0,0 +1,217 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require 'rubyforge' +require 'digest/md5' +require 'digest/sha1' + +gpg_cmd = 'gpg2' + +task :prepare do |task, args| + gpg_arg = args.gpg || ENV['gpg'] + + # Make sure we're doing a release from checked code. + lambda do + puts "Checking there are no local changes ... " + svn = `svn status` + fail "Cannot release unless all local changes are in SVN:\n#{svn}" unless svn.empty? + git = `git status -s` + fail "Cannot release unless all local changes are in Git:\n#{git}" if git[/^ M/] && ENV["IGNORE_GIT"].nil? + puts "[X] There are no local changes, everything is in source control" + end.call + + # Make sure we have a valid CHANGELOG entry for this release. + lambda do + puts "Checking that CHANGELOG indicates most recent version and today's date ... " + expecting = "#{spec.version} (#{Time.now.strftime('%Y-%m-%d')})" + header = File.readlines('CHANGELOG').first.chomp + fail "Expecting CHANGELOG to start with #{expecting}, but found #{header} instead" unless expecting == header + puts "[x] CHANGELOG indicates most recent version and today's date" + end.call + + # Need GPG to sign the packages. + lambda do + gpg_arg or fail "Please run with gpg=" + gpg_ok = `gpg2 --list-keys #{gpg_arg}` rescue nil + if !$?.success? + gpg_ok = `gpg --list-keys #{gpg_arg}` + gpg_cmd = 'gpg' + end + fail "No GPG user #{gpg_arg}" if gpg_ok.empty? + end.call + + task(:license).invoke + + # Need JRuby, Scala and Groovy installed to run all the specs. + lambda do + puts "Checking that we have JRuby, Scala and Groovy available ... " + sh 'jruby --version' + `scala -version` + $?.exitstatus == 1 or fail "Scala is not installed" + sh 'groovy -version' + puts "[X] We have JRuby, Scala and Groovy" + end.call + + # Need Prince to generate PDF + lambda do + puts "Checking that we have prince available ... " + sh 'prince --version' + puts "[X] We have prince available" + end.call + + # Need RubyForge to upload new release files. + lambda do + puts "[!] Make sure you have admin privileges to make a release on RubyForge" + rubyforge = RubyForge.new.configure + rubyforge.login + rubyforge.scrape_project(spec.name) + end.call + + # We will be speccing in one platform, so also spec the other one. + task(RUBY_PLATFORM =~ /java/ ? 'spec:ruby' : 'spec:jruby').invoke # Test the *other* platform +end + + +task :stage=>[:clobber, :prepare] do |task, args| + gpg_arg = args.gpg || ENV['gpg'] + mkpath '_staged' + + # Start by figuring out what has changed. + lambda do + puts "Looking for changes between this release and previous one ..." + pattern = /(^(\d+\.\d+(?:\.\d+)?)\s+\(\d{4}-\d{2}-\d{2}\)\s*((:?^[^\n]+\n)*))/ + changes = File.read('CHANGELOG').scan(pattern).inject({}) { |hash, set| hash[set[1]] = set[2] ; hash } + current = changes[spec.version.to_s] + fail "No changeset found for version #{spec.version}" unless current + File.open '_staged/CHANGES', 'w' do |file| + file.write "#{spec.version} (#{Time.now.strftime('%Y-%m-%d')})\n" + file.write current + end + puts "[X] Listed most recent changed in _staged/CHANGES" + end.call + + # Create the packages (gem, tarball) and sign them. This requires user + # intervention so the earlier we do it the better. + lambda do + puts "Creating and signing release packages ..." + task(:package).invoke + mkpath '_staged/dist' + FileList['pkg/*.{gem,zip,tgz}'].each do |source| + pkg = source.pathmap('_staged/dist/%n%x') + cp source, pkg + bytes = File.open(pkg, 'rb') { |file| file.read } + File.open(pkg + '.md5', 'w') { |file| file.write Digest::MD5.hexdigest(bytes) << ' ' << File.basename(pkg) } + File.open(pkg + '.sha1', 'w') { |file| file.write Digest::SHA1.hexdigest(bytes) << ' ' << File.basename(pkg) } + sh gpg_cmd, '--local-user', gpg_arg, '--armor', '--output', pkg + '.asc', '--detach-sig', pkg, :verbose=>true + end + cp 'etc/KEYS', '_staged/dist' + puts "[X] Created and signed release packages in _staged/dist" + end.call + + # The download page should link to the new binaries/sources, and we + # want to do that before generating the site/documentation. + lambda do + puts "Updating download page with links to release packages ... " + mirror = "http://www.apache.org/dyn/closer.cgi/#{spec.name}/#{spec.version}" + official = "http://www.apache.org/dist/#{spec.name}/#{spec.version}" + rows = FileList['_staged/dist/*.{gem,tgz,zip}'].map { |pkg| + name, md5 = File.basename(pkg), Digest::MD5.file(pkg).to_s + %{| "#{name}":#{mirror}/#{name} | "#{md5}":#{official}/#{name}.md5 | "Sig":#{official}/#{name}.asc |} + } + textile = <<-TEXTILE +h3. #{spec.name} #{spec.version} (#{Time.now.strftime('%Y-%m-%d')}) + +|_. Package |_. MD5 Checksum |_. PGP | +#{rows.join("\n")} + +p>. ("Release signing keys":#{official}/KEYS) + TEXTILE + file_name = 'doc/download.textile' + print "Adding download links to #{file_name} ... " + modified = File.read(file_name).sub(/^h2\(#dist\).*$/) { |header| "#{header}\n\n#{textile}" } + File.open file_name, 'w' do |file| + file.write modified + end + puts "[X] Updated #{file_name}" + end.call + + + # Now we can create the Web site, this includes running specs, coverage report, etc. + # This will take a while, so we want to do it as last step before upload. + lambda do + puts "Creating new Web site" + task(:site).invoke + cp_r '_site', '_staged/site' + puts "[X] Created new Web site in _staged/site" + end.call + + + # Move everything over to people.apache.org so we can vote on it. + lambda do + url = "people.apache.org:~/public_html/#{spec.name}/#{spec.version}" + puts "Uploading _staged directory to #{url} ..." + sh 'rsync', '--progress', '--recursive', '_staged/', url + puts "[X] Uploaded _staged directory to #{url}" + end.call + + + # Prepare a release vote email. In the distant future this will also send the + # email for you and vote on it. + lambda do + # Need to know who you are on Apache, local user may be different (see .ssh/config). + whoami = `ssh people.apache.org whoami`.strip + base_url = "http://people.apache.org/~#{whoami}/buildr/#{spec.version}" + # Need changes for this release only. + changelog = File.read('CHANGELOG').scan(/(^(\d+\.\d+(?:\.\d+)?)\s+\(\d{4}-\d{2}-\d{2}\)\s*((:?^[^\n]+\n)*))/) + changes = changelog[0][2] + previous_version = changelog[1][1] + + email = <<-EMAIL +To: dev@buildr.apache.org +Subject: [VOTE] Buildr #{spec.version} release + +We're voting on the source distributions available here: +#{base_url}/dist/ + +Specifically: +#{base_url}/dist/buildr-#{spec.version}.tgz +#{base_url}/dist/buildr-#{spec.version}.zip + +The documentation generated for this release is available here: +#{base_url}/site/ +#{base_url}/site/buildr.pdf + +The official specification against which this release was tested: +#{base_url}/site/specs.html + +Test coverage report: +#{base_url}/site/coverage/index.html + + +The following changes were made since #{previous_version}: + +#{changes.gsub(/^/, ' ')} + EMAIL + File.open 'vote-email.txt', 'w' do |file| + file.write email + end + puts "[X] Created release vote email template in 'vote-email.txt'" + puts email + end.call + +end + + +task(:clobber) { rm_rf '_staged' } diff --git a/buildr/spec/addon/bnd_spec.rb b/buildr/spec/addon/bnd_spec.rb new file mode 100644 index 0000000..5ff7373 --- /dev/null +++ b/buildr/spec/addon/bnd_spec.rb @@ -0,0 +1,330 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path('../spec_helpers', File.dirname(__FILE__)) +Sandbox.require_optional_extension 'buildr/bnd' + +def open_zip_file(file = 'target/foo-2.1.3.jar') + jar_filename = @foo._(file) + File.should be_exist(jar_filename) + Zip::ZipFile.open(jar_filename) do |zip| + yield zip + end +end + +def open_main_manifest_section(file = 'target/foo-2.1.3.jar') + jar_filename = @foo._(file) + File.should be_exist(jar_filename) + yield Buildr::Packaging::Java::Manifest.from_zip(jar_filename).main +end + +describe Buildr::Bnd do + + describe "package :bundle" do + describe "with a valid bundle" do + before do + write "src/main/java/com/biz/Foo.java", < stdin, :out => stdout, :err => stderr + end + + def remote_run(*argv) + cfg.update :argv => argv + drb.remote_run(cfg) + end + + def output + cfg[:out].string + end + + def write_buildfile(content = nil) + write 'buildfile', content || %q{ + define('foo') do + + rule '.rbc' => '.rb' do |t| + $stdout.puts "#{t.name} from #{t.source}" + end + + task('hello') do + $stdout.puts 'hi' + end + + task('empty') + + task('no') do + task('empty').enhance ['delete_me'] + task('empty') { $stdout.puts 'no' } + end + + task('delete_me') + + task('create') do + Rake::Task.define_task('created') + rule '.rbc' => '.rb' do |t| + $stdout.puts "#{t.name} from #{t.source}" + end + end + + task('exists') do + $stdout.puts !!Buildr.application.lookup('created') + end + + task('setopt', :name, :value) do |task, args| + Buildr.application.options.send("#{args[:name]}=", args[:value]) + end + end + } + end + end + + include DRbHelper + + before(:each) do + @in, @out, @err = $stdin, $stdout, $stderr + @cfg = { + :dir => Dir.pwd, :argv => [], + :in => @in, :out => @out, :err => @err + } + @drb = Buildr::DRbApplication.clone + @drb.send :setup + @app = Buildr.application + end + + after(:each) do + $stdin, $stdout, $stderr = @in, @out, @err + end + + describe '.run' do + it 'starts server if no server is running' do + drb.should_receive(:connect).and_raise DRb::DRbConnError + drb.should_receive(:run_server!) + drb.should_not_receive(:run_client) + drb.run + end + + it 'connects to an already started server' do + drb.should_receive(:connect).and_return "client" + drb.should_receive(:run_client).with "client" + drb.should_not_receive(:run_server!) + drb.run + end + end + + describe '.remote_run' do + + describe 'stdout' do + it 'is redirected to client' do + use_stdio + Buildr.application.should_receive(:remote_run) do + $stdout.puts "HELLO" + end + remote_run + output.should eql("HELLO\n") + end + end + + describe 'stderr' do + it 'is redirected to client' do + use_stdio + Buildr.application.should_receive(:remote_run) do + $stderr.puts "HELLO" + end + remote_run + cfg[:err].string.should eql("HELLO\n") + end + end + + describe 'stdin' do + it 'is redirected to client' do + use_stdio + cfg[:in].should_receive(:gets).and_return("HELLO\n") + result = nil + Buildr.application.should_receive(:remote_run) do + result = $stdin.gets + end + remote_run + result.should eql("HELLO\n") + end + end + + describe 'server ARGV' do + it 'is replaced with client argv' do + Buildr.application.should_receive(:remote_run) do + ARGV.should eql(['hello']) + end + remote_run 'hello' + end + end + + describe 'without buildfile loaded' do + before(:each) do + app.instance_eval { @rakefile = nil } + write_buildfile + end + + it 'should load the buildfile' do + app.should_receive(:top_level) + lambda { remote_run }.should run_task('foo') + end + end + + describe 'with unmodified buildfile' do + + before(:each) do + write_buildfile + app.options.rakelib = [] + app.send :load_buildfile + drb.save_snapshot(app) + end + + it 'should not reload the buildfile' do + app.should_not_receive(:reload_buildfile) + app.should_receive(:top_level) + remote_run + end + + it 'should not define projects again' do + use_stdio + lambda { 2.times { remote_run 'foo:hello' } }.should_not run_task('foo') + output.should eql("hi\nhi\n") + end + + it 'should restore task actions' do + use_stdio + remote_run 'foo:empty' + output.should be_empty + 2.times { remote_run 'foo:no' } + remote_run 'foo:empty' + actions = app.lookup('foo:empty').instance_eval { @actions } + actions.should be_empty # as originally defined + output.should be_empty + end + + it 'should restore task prerequisites' do + use_stdio + remote_run 'foo:empty' + output.should be_empty + 2.times { remote_run 'foo:no' } + remote_run 'foo:empty' + pres = app.lookup('foo:empty').send(:prerequisites).map(&:to_s) + pres.should be_empty # as originally defined + output.should be_empty + end + + it 'should drop runtime created tasks' do + remote_run 'foo:create' + app.lookup('created').should_not be_nil + remote_run 'foo:empty' + app.lookup('created').should be_nil + end + + it 'should restore options' do + remote_run 'foo:setopt[bar,baz]' + app.options.bar.should eql("baz") + remote_run 'foo:empty' + app.options.bar.should be_nil + end + + it 'should restore rules' do + orig = app.instance_eval { @rules.size } + remote_run 'foo:create' + app.instance_eval { @rules.size }.should eql(orig + 1) + remote_run 'foo:empty' + app.instance_eval { @rules.size }.should eql(orig) + end + + end + + describe 'with modified buildfile' do + + before(:each) do + write_buildfile + app.options.rakelib = [] + app.send :load_buildfile + drb.save_snapshot(app) + app.instance_eval { @last_loaded = Time.now - 10 } + write_buildfile %q{ + rule '.rbc' => '.rb' do |t| + $stdout.puts "#{t.name} from #{t.source}" + end + define('foo') do + task('hello') do + $stdout.puts 'bye' + end + task('empty') + define('bar') do + + end + end + } + end + + it 'should reload the buildfile' do + app.should_receive(:reload_buildfile) + app.should_receive(:top_level) + remote_run + end + + it 'should redefine projects' do + lambda { remote_run }.should run_tasks('foo', 'foo:bar') + end + + it 'should remove tasks deleted from buildfile' do + app.lookup('foo:delete_me').should_not be_nil + remote_run + app.lookup('foo:delete_me').should be_nil + end + + it 'should redefine tasks actions' do + actions = app.lookup('foo:empty').instance_eval { @actions } + actions.should be_empty # no action + app.lookup('foo:no').invoke # enhance the empty task + actions = app.lookup('foo:empty').instance_eval { @actions } + actions.should_not be_empty + remote_run # cause to reload the buildfile + actions = app.lookup('foo:empty').instance_eval { @actions } + actions.should be_empty # as defined on the new buildfile + end + + it 'should redefine task prerequisites' do + pres = app.lookup('foo:empty').send(:prerequisites).map(&:to_s) + pres.should be_empty # no action + app.lookup('foo:no').invoke # enhance the empty task + pres = app.lookup('foo:empty').send(:prerequisites).map(&:to_s) + pres.should_not be_empty + remote_run # cause to reload the buildfile + pres = app.lookup('foo:empty').send(:prerequisites).map(&:to_s) + pres.should be_empty # as defined on the new buildfile + end + + it 'should drop runtime created tasks' do + app.lookup('foo:create').invoke + app.lookup('created').should_not be_nil + remote_run 'foo:empty' + app.lookup('created').should be_nil + end + + it 'should restore options' do + app.options.bar = 'baz' + remote_run 'foo:empty' + app.options.bar.should be_nil + end + + it 'should redefine rules' do + orig = app.instance_eval { @rules.size } + app.lookup('foo:create').invoke + app.instance_eval { @rules.size }.should eql(orig + 1) + remote_run 'foo:empty' + app.instance_eval { @rules.size }.should eql(orig) + end + + end + + end +end + diff --git a/buildr/spec/addon/jaxb_xjc_spec.rb b/buildr/spec/addon/jaxb_xjc_spec.rb new file mode 100644 index 0000000..88e1033 --- /dev/null +++ b/buildr/spec/addon/jaxb_xjc_spec.rb @@ -0,0 +1,130 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +if Java.java.lang.System.getProperty("java.runtime.version") >= "1.6" + +require File.expand_path('../spec_helpers', File.dirname(__FILE__)) +Sandbox.require_optional_extension 'buildr/jaxb_xjc' + +XSD_CONTENT = < + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the district WITHOUT the "FIRE DISTRICT" suffix. + + + + + + + + + + + + + + + + + + + + + + + + + This is a grid reference in lat/long format. + + + + + + + + + + + + + + + + + + + + + + + +XSD + +describe Buildr::JaxbXjc do + describe "compiled with specified xsd" do + before do + write "src/main/xsd/wildfire-1.3.xsd", XSD_CONTENT + @foo = define "foo" do + project.version = "2.1.3" + compile.from compile_jaxb("src/main/xsd/wildfire-1.3.xsd", "-quiet", :package => "org.foo.api") + package :jar + end + task('compile').invoke + end + + it "produce .java files in the correct location" do + File.should be_exist(@foo._("target/generated/jaxb/org/foo/api/Agency.java")) + File.should be_exist(@foo._("target/generated/jaxb/org/foo/api/LatLongCoordinate.java")) + File.should be_exist(@foo._("target/generated/jaxb/org/foo/api/ObjectFactory.java")) + File.should be_exist(@foo._("target/generated/jaxb/org/foo/api/Wildfire.java")) + end + + it "produce .class files in the correct location" do + File.should be_exist(@foo._("target/classes/org/foo/api/Agency.class")) + File.should be_exist(@foo._("target/classes/org/foo/api/LatLongCoordinate.class")) + File.should be_exist(@foo._("target/classes/org/foo/api/ObjectFactory.class")) + File.should be_exist(@foo._("target/classes/org/foo/api/Wildfire.class")) + end + end +end + +elsif Buildr::VERSION >= '1.5' + raise "JVM version guard in #{__FILE__} should be removed since it is assumed that Java 1.5 is no longer supported." +end diff --git a/buildr/spec/core/application_spec.rb b/buildr/spec/core/application_spec.rb new file mode 100644 index 0000000..b5092f8 --- /dev/null +++ b/buildr/spec/core/application_spec.rb @@ -0,0 +1,578 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Buildr::Application do + + describe 'home_dir' do + it 'should point to ~/.buildr' do + Buildr.application.home_dir.should eql(File.expand_path('.buildr', ENV['HOME'])) + end + + it 'should point to existing directory' do + File.directory?(Buildr.application.home_dir).should be_true + end + end + + describe '#run' do + it 'should execute *_load methods in order' do + order = [:load_gems, :load_artifact_ns, :load_tasks, :raw_load_buildfile] + order.each { |method| Buildr.application.should_receive(method).ordered } + Buildr.application.stub!(:exit) # With this, shows the correct error instead of SystemExit. + Buildr.application.run + end + + it 'should load imports after loading buildfile' do + method = Buildr.application.method(:raw_load_buildfile) + Buildr.application.should_receive(:raw_load_buildfile) do + Buildr.application.should_receive(:load_imports) + method.call + end + Buildr.application.stub!(:exit) # With this, shows the correct error instead of SystemExit. + Buildr.application.run + end + + it 'should evaluate all projects after loading buildfile' do + Buildr.application.should_receive(:load_imports) do + Buildr.should_receive(:projects) + end + Buildr.application.stub!(:exit) # With this, shows the correct error instead of SystemExit. + Buildr.application.run + end + end + + describe 'environment' do + it 'should return value of BUILDR_ENV' do + ENV['BUILDR_ENV'] = 'qa' + Buildr.application.environment.should eql('qa') + end + + it 'should default to development' do + Buildr.application.environment.should eql('development') + end + + it 'should set environment name from -e argument' do + ARGV.push('-e', 'test') + Buildr.application.send(:handle_options) + Buildr.application.environment.should eql('test') + ENV['BUILDR_ENV'].should eql('test') + end + + it 'should be echoed to user' do + write 'buildfile' + ENV['BUILDR_ENV'] = 'spec' + Buildr.application.send(:handle_options) + lambda { Buildr.application.send :load_buildfile }.should show(%r{(in .*, spec)}) + end + end + + describe 'options' do + it "should have 'tasks' as the sole default rakelib" do + Buildr.application.send(:handle_options) + Buildr.application.options.rakelib.should == ['tasks'] + end + + it 'should show the version when prompted with -V' do + ARGV.push('-V') + test_exit(0) { Buildr.application.send(:handle_options) }.should show(/Buildr #{Buildr::VERSION}.*/) + end + + it 'should show the version when prompted with --version' do + ARGV.push('--version') + test_exit(0) { Buildr.application.send(:handle_options) }.should show(/Buildr #{Buildr::VERSION}.*/) + end + + it 'should enable tracing with --trace' do + ARGV.push('--trace') + Buildr.application.send(:handle_options) + Buildr.application.options.trace.should == true + end + + it 'should enable tracing of [:foo, :bar] categories with --trace=foo,bar' do + ARGV.push('--trace=foo,bar') + Buildr.application.send(:handle_options) + Buildr.application.options.trace.should == true + Buildr.application.options.trace_categories.should == [:foo, :bar] + trace?(:foo).should == true + trace?(:not).should == false + end + + it 'should enable tracing for all categories with --trace=all' do + ARGV.push('--trace=all') + Buildr.application.send(:handle_options) + Buildr.application.options.trace.should == true + Buildr.application.options.trace_all.should == true + trace?(:foo).should == true + end + + end + + describe 'gems' do + before do + class << Buildr.application + public :load_gems + end + end + + def load_with_yaml + write 'build.yaml', <<-YAML + gems: + - rake + - rspec ~> 2.1.0 + YAML + Buildr.application.should_receive(:listed_gems).and_return([[Gem.loaded_specs['rspec'],Gem.loaded_specs['rake']],[]]) + Buildr.application.load_gems + end + + it 'should return empty array if no gems specified' do + Buildr.application.load_gems + Buildr.application.gems.should be_empty + end + + it 'should return one entry for each gem specified in buildr.yaml' do + load_with_yaml + Buildr.application.gems.size.should be(2) + end + + it 'should return a Gem::Specification for each installed gem' do + load_with_yaml + Buildr.application.gems.each { |gem| gem.should be_kind_of(Gem::Specification) } + end + + it 'should parse Gem name correctly' do + load_with_yaml + Buildr.application.gems.map(&:name).should include('rspec', 'rake') + end + + it 'should find installed version of Gem' do + load_with_yaml + Buildr.application.gems.each { |gem| gem.version.should eql(Gem.loaded_specs[gem.name].version) } + end + end + + describe 'load_gems' do + before do + class << Buildr.application + public :load_gems + end + @spec = Gem::Specification.new do |spec| + spec.name = 'buildr-foo' + spec.version = '1.2' + end + $stdout.stub!(:isatty).and_return(true) + end + + it 'should do nothing if no gems specified' do + lambda { Buildr.application.load_gems }.should_not raise_error + end + + it 'should install nothing if specified gems already installed' do + Buildr.application.should_receive(:listed_gems).and_return([[Gem.loaded_specs['rspec']],[]]) + Util.should_not_receive(:ruby) + lambda { Buildr.application.load_gems }.should_not raise_error + end + + it 'should fail if required gem not installed' do + Buildr.application.should_receive(:listed_gems).and_return([[],[Gem::Dependency.new('buildr-foo', '>=1.1')]]) + lambda { Buildr.application.load_gems }.should raise_error(LoadError, /cannot be found/i) + end + + it 'should load previously installed gems' do + Gem.loaded_specs['rspec'].should_receive(:activate) + Buildr.application.should_receive(:listed_gems).and_return([[Gem.loaded_specs['rspec']],[]]) + #Buildr.application.should_receive(:gem).with('rspec', Gem.loaded_specs['rspec'].version.to_s) + Buildr.application.load_gems + end + + it 'should default to >=0 version requirement if not specified' do + write 'build.yaml', 'gems: buildr-foo' + should_attempt_to_load_dependency(Gem::Dependency.new('buildr-foo', '>= 0')) + end + + it 'should parse exact version requirement' do + write 'build.yaml', 'gems: buildr-foo 2.5' + should_attempt_to_load_dependency(Gem::Dependency.new('buildr-foo', '=2.5')) + end + + it 'should parse range version requirement' do + write 'build.yaml', 'gems: buildr-foo ~>2.3' + should_attempt_to_load_dependency(Gem::Dependency.new('buildr-foo', '~>2.3')) + end + + it 'should parse multiple version requirements' do + write 'build.yaml', 'gems: buildr-foo >=2.0 !=2.1' + should_attempt_to_load_dependency(Gem::Dependency.new('buildr-foo', ['>=2.0', '!=2.1'])) + end + + def should_attempt_to_load_dependency(dep) + missing_gems = Buildr.application.send(:listed_gems)[1] + missing_gems.size.should eql(1) + missing_gems[0].eql?(dep) + end + end + + describe 'load_tasks' do + before do + class << Buildr.application + public :load_tasks + end + @original_loaded_features = $LOADED_FEATURES.dup + Buildr.application.options.rakelib = ["tasks"] + end + + after do + $taskfiles = nil + ($LOADED_FEATURES - @original_loaded_features).each do |new_load| + $LOADED_FEATURES.delete(new_load) + end + end + + def write_task(filename) + write filename, <<-RUBY + $taskfiles ||= [] + $taskfiles << __FILE__ + RUBY + end + + def loaded_tasks + @loaded ||= Buildr.application.load_tasks + $taskfiles + end + + it "should load {options.rakelib}/foo.rake" do + write_task 'tasks/foo.rake' + loaded_tasks.should have(1).task + loaded_tasks.first.should =~ %r{tasks/foo\.rake$} + end + + it 'should load all *.rake files from the rakelib' do + write_task 'tasks/bar.rake' + write_task 'tasks/quux.rake' + loaded_tasks.should have(2).tasks + end + + it 'should not load files which do not have the .rake extension' do + write_task 'tasks/foo.rb' + write_task 'tasks/bar.rake' + loaded_tasks.should have(1).task + loaded_tasks.first.should =~ %r{tasks/bar\.rake$} + end + + it 'should load files only from the directory specified in the rakelib option' do + Buildr.application.options.rakelib = ['extensions'] + write_task 'extensions/amp.rake' + write_task 'tasks/bar.rake' + write_task 'extensions/foo.rake' + loaded_tasks.should have(2).tasks + %w[amp foo].each do |filename| + loaded_tasks.select{|x| x =~ %r{extensions/#{filename}\.rake}}.should have(1).entry + end + end + + it 'should load files from all the directories specified in the rakelib option' do + Buildr.application.options.rakelib = ['ext', 'more', 'tasks'] + write_task 'ext/foo.rake' + write_task 'tasks/bar.rake' + write_task 'tasks/zeb.rake' + write_task 'more/baz.rake' + loaded_tasks.should have(4).tasks + end + + it 'should not load files from the rakelib more than once' do + write_task 'tasks/new_one.rake' + write_task 'tasks/already.rake' + $LOADED_FEATURES << 'tasks/already.rake' + + loaded_tasks.should have(1).task + loaded_tasks.first.should =~ %r{tasks/new_one\.rake$} + end + end + + describe 'exception handling' do + + it 'should exit when given a SystemExit exception' do + test_exit(3) { Buildr.application.standard_exception_handling { raise SystemExit.new(3) } } + end + + it 'should exit with status 1 when given an OptionParser::ParseError exception' do + test_exit(1) { Buildr.application.standard_exception_handling { raise OptionParser::ParseError.new() } } + end + + it 'should exit with status 1 when given any other type of exception exception' do + test_exit(1) { Buildr.application.standard_exception_handling { raise Exception.new() } } + end + + it 'should print the class name and the message when receiving an exception (except when the exception is named Exception)' do + + # Our fake $stderr for the exercise. We could start it with a matcher instead + class FakeStdErr + + attr_accessor :messages + + def puts(*args) + @messages ||= [] + @messages += args + end + + alias :write :puts + end + + # Save the old $stderr and make sure to restore it in the end. + old_stderr = $stderr + begin + + $stderr = FakeStdErr.new + test_exit(1) { + Buildr.application.send :standard_exception_handling do + class MyOwnNicelyNamedException < Exception + end + raise MyOwnNicelyNamedException.new('My message') + end + }.call + $stderr.messages.select {|msg| msg =~ /.*MyOwnNicelyNamedException : My message.*/}.size.should == 1 + $stderr.messages.clear + test_exit(1) { + Buildr.application.send :standard_exception_handling do + raise Exception.new('My message') + end + }.call + $stderr.messages.select {|msg| msg =~ /.*My message.*/ && !(msg =~ /Exception/)}.size.should == 1 + end + $stderr = old_stderr + end + end + +end + + +describe Buildr, 'settings' do + + describe 'user' do + + it 'should be empty hash if no settings.yaml file' do + Buildr.settings.user.should == {} + end + + it 'should return loaded settings.yaml file' do + write 'home/.buildr/settings.yaml', 'foo: bar' + Buildr.settings.user.should == { 'foo'=>'bar' } + end + + it 'should return loaded settings.yml file' do + write 'home/.buildr/settings.yml', 'foo: bar' + Buildr.settings.user.should == { 'foo'=>'bar' } + end + + it 'should fail if settings.yaml file is not a hash' do + write 'home/.buildr/settings.yaml', 'foo bar' + lambda { Buildr.settings.user }.should raise_error(RuntimeError, /expecting.*settings.yaml/i) + end + + it 'should be empty hash if settings.yaml file is empty' do + write 'home/.buildr/settings.yaml' + Buildr.settings.user.should == {} + end + end + + describe 'configuration' do + it 'should be empty hash if no build.yaml file' do + Buildr.settings.build.should == {} + end + + it 'should return loaded build.yaml file' do + write 'build.yaml', 'foo: bar' + Buildr.settings.build.should == { 'foo'=>'bar' } + end + + it 'should return loaded build.yml file' do + write 'build.yml', 'foo: bar' + Buildr.settings.build.should == { 'foo'=>'bar' } + end + + it 'should fail if build.yaml file is not a hash' do + write 'build.yaml', 'foo bar' + lambda { Buildr.settings.build }.should raise_error(RuntimeError, /expecting.*build.yaml/i) + end + + it 'should be empty hash if build.yaml file is empty' do + write 'build.yaml' + Buildr.settings.build.should == {} + end + end + + describe 'profiles' do + it 'should be empty hash if no profiles.yaml file' do + Buildr.settings.profiles.should == {} + end + + it 'should return loaded profiles.yaml file' do + write 'profiles.yaml', <<-YAML + development: + foo: bar + YAML + Buildr.settings.profiles.should == { 'development'=> { 'foo'=>'bar' } } + end + + it 'should return loaded profiles.yml file' do + write 'profiles.yml', <<-YAML + development: + foo: bar + YAML + Buildr.settings.profiles.should == { 'development'=> { 'foo'=>'bar' } } + end + + it 'should fail if profiles.yaml file is not a hash' do + write 'profiles.yaml', 'foo bar' + lambda { Buildr.settings.profiles }.should raise_error(RuntimeError, /expecting.*profiles.yaml/i) + end + + it 'should be empty hash if profiles.yaml file is empty' do + write 'profiles.yaml' + Buildr.settings.profiles.should == {} + end + end + + describe 'profile' do + before do + end + + it 'should be empty hash if no profiles.yaml' do + Buildr.settings.profile.should == {} + end + + it 'should be empty hash if no matching profile' do + write 'profiles.yaml', <<-YAML + test: + foo: bar + YAML + Buildr.settings.profile.should == {} + end + + it 'should return profile matching environment name' do + write 'profiles.yaml', <<-YAML + development: + foo: bar + test: + foo: baz + YAML + Buildr.settings.profile.should == { 'foo'=>'bar' } + end + end + + describe 'buildfile task' do + before do + @buildfile_time = Time.now - 10 + write 'buildfile'; File.utime(@buildfile_time, @buildfile_time, 'buildfile') + end + + it 'should point to the buildfile' do + Buildr.application.buildfile.should point_to_path('buildfile') + end + + it 'should be a defined task' do + Buildr.application.buildfile.should == file(File.expand_path('buildfile')) + end + + it 'should ignore any rake namespace' do + namespace 'dummy_ns' do + Buildr.application.buildfile.should point_to_path('buildfile') + end + end + + it 'should have the same timestamp as the buildfile' do + Buildr.application.buildfile.timestamp.should be_close(@buildfile_time, 1) + end + + it 'should have the same timestamp as build.yaml if the latter is newer' do + write 'build.yaml'; File.utime(@buildfile_time + 5, @buildfile_time + 5, 'build.yaml') + Buildr.application.run + Buildr.application.buildfile.timestamp.should be_close(@buildfile_time + 5, 1) + end + + it 'should have the same timestamp as the buildfile if build.yaml is older' do + write 'build.yaml'; File.utime(@buildfile_time - 5, @buildfile_time - 5, 'build.yaml') + Buildr.application.run + Buildr.application.buildfile.timestamp.should be_close(@buildfile_time, 1) + end + + it 'should have the same timestamp as build.rb in home dir if the latter is newer (until version 1.6)' do + Buildr::VERSION.should < '1.6' + buildfile_should_have_same_timestamp_as 'home/buildr.rb' + end + + it 'should have the same timestamp as build.rb in home dir if the latter is newer' do + buildfile_should_have_same_timestamp_as 'home/.buildr/buildr.rb' + end + + it 'should have the same timestamp as .buildr.rb in buildfile dir if the latter is newer' do + buildfile_should_have_same_timestamp_as '.buildr.rb' + end + + it 'should have the same timestamp as _buildr.rb in buildfile dir if the latter is newer' do + buildfile_should_have_same_timestamp_as '_buildr.rb' + end + + def buildfile_should_have_same_timestamp_as(file) + write file; File.utime(@buildfile_time + 5, @buildfile_time + 5, file) + Buildr.application.send :load_tasks + Buildr.application.buildfile.timestamp.should be_close(@buildfile_time + 5, 1) + end + end +end + + +describe Buildr do + + describe 'environment' do + it 'should be same as Buildr.application.environment' do + Buildr.environment.should eql(Buildr.application.environment) + end + end + + describe 'application' do + it 'should be same as Rake.application' do + Buildr.application.should == Rake.application + end + end + + describe 'settings' do + it 'should be same as Buildr.application.settings' do + Buildr.settings.should == Buildr.application.settings + end + end + +end + +describe Rake do + describe 'define_task' do + it 'should restore call chain when invoke is called' do + task1 = Rake::Task.define_task('task1') do + end + + task2 = Rake::Task.define_task('task2') do + chain1 = Thread.current[:rake_chain] + task1.invoke + chain2 = Thread.current[:rake_chain] + chain2.should == chain1 + end + + task2.invoke + end + end +end + diff --git a/buildr/spec/core/build_spec.rb b/buildr/spec/core/build_spec.rb new file mode 100644 index 0000000..0f22ed0 --- /dev/null +++ b/buildr/spec/core/build_spec.rb @@ -0,0 +1,837 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +shared_examples_for 'local task' do + it "should execute task for project in current directory" do + define 'foobar' + lambda { @task.invoke }.should run_task("foobar:#{@task.name}") + end + + it "should not execute task for projects in other directory" do + define 'foobar', :base_dir=>'elsewhere' + lambda { task('build').invoke }.should_not run_task('foobar:build') + end +end + + +describe 'build task' do + it_should_behave_like 'local task' + before(:each) { @task = task('build') } +end + +describe 'clean task' do + it_should_behave_like 'local task' + before(:each) { @task = task('clean') } +end + +describe 'package task' do + it_should_behave_like 'local task' + before(:each) { @task = task('package') } + + it 'should execute build task as prerequisite' do + lambda { @task.invoke }.should run_task('build') + end +end + +describe 'install task' do + it_should_behave_like 'local task' + before(:each) { @task = task('install') } + + it 'should execute package task as prerequisite' do + lambda { @task.invoke }.should run_task('package') + end +end + +describe 'uninstall task' do + it_should_behave_like 'local task' + before(:each) { @task = task('uninstall') } +end + +describe 'upload task' do + it_should_behave_like 'local task' + before(:each) { @task = task('upload') } + + it 'should execute package task as prerequisite' do + lambda { @task.invoke }.should run_task('package') + end +end + + +describe Project, '#build' do + it 'should return the project\'s build task' do + define('foo').build.should eql(task('foo:build')) + end + + it 'should enhance the project\'s build task' do + task 'prereq' + task 'action' + define 'foo' do + build 'prereq' do + task('action').invoke + end + end + lambda { project('foo').build.invoke }.should run_tasks('prereq', 'action') + end + + it 'should execute build task for sub-project' do + define 'foo' do + define 'bar' + end + lambda { task('foo:build').invoke }.should run_task('foo:bar:build') + end + + it 'should not execute build task of other projects' do + define 'foo' + define 'bar' + lambda { task('foo:build').invoke }.should_not run_task('bar:build') + end +end + + +describe Project, '#clean' do + it 'should return the project\'s clean task' do + define('foo').clean.should eql(task('foo:clean')) + end + + it 'should enhance the project\'s clean task' do + task 'prereq' + task 'action' + define 'foo' do + clean 'prereq' do + task('action').invoke + end + end + lambda { project('foo').clean.invoke }.should run_tasks('prereq', 'action') + end + + it 'should remove target directory' do + define 'foo' do + self.layout[:target] = 'targeted' + end + mkpath 'targeted' + lambda { project('foo').clean.invoke }.should change { File.exist?('targeted') }.from(true).to(false) + end + + it 'should remove reports directory' do + define 'foo' do + self.layout[:reports] = 'reported' + end + mkpath 'reported' + lambda { project('foo').clean.invoke }.should change { File.exist?('reported') }.from(true).to(false) + end + + it 'should execute clean task for sub-project' do + define 'foo' do + define 'bar' + end + lambda { task('foo:clean').invoke }.should run_task('foo:bar:clean') + end + + it 'should not execute clean task of other projects' do + define 'foo' + define 'bar' + lambda { task('foo:clean').invoke }.should_not run_task('bar:clean') + end +end + + +describe Project, '#target' do + before :each do + @project = define('foo', :layout=>Layout.new) + end + + it 'should default to target' do + @project.target.should eql('target') + end + + it 'should set layout :target' do + @project.target = 'bar' + @project.layout.expand(:target).should point_to_path('bar') + end + + it 'should come from layout :target' do + @project.layout[:target] = 'baz' + @project.target.should eql('baz') + end + + it 'should be removed in version 1.5 since it was deprecated in version 1.3' do + Buildr::VERSION.should < '1.5' + end +end + + +describe Project, '#reports' do + before :each do + @project = define('foo', :layout=>Layout.new) + end + + it 'should default to reports' do + @project.reports.should eql('reports') + end + + it 'should set layout :reports' do + @project.reports = 'bar' + @project.layout.expand(:reports).should point_to_path('bar') + end + + it 'should come from layout :reports' do + @project.layout[:reports] = 'baz' + @project.reports.should eql('baz') + end + + it 'should be removed in version 1.5 since it was deprecated in version 1.3' do + Buildr::VERSION.should < '1.5' + end +end + + +describe Git do + describe '#uncommitted_files' do + it 'should return an empty array on a clean repository' do + Git.should_receive(:`).with('git status').and_return <<-EOF +# On branch master +nothing to commit (working directory clean) + EOF + Git.uncommitted_files.should be_empty + end + + it 'should reject a dirty repository, Git 1.4.2 or former' do + Git.should_receive(:`).with('git status').and_return <<-EOF +# On branch master +# +# Changed but not updated: +# (use "git add ..." to update what will be committed) +# (use "git checkout -- ..." to discard changes in working directory) +# +# modified: lib/buildr.rb +# modified: spec/buildr_spec.rb +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# error.log + EOF + Git.uncommitted_files.should include('lib/buildr.rb', 'error.log') + end + + it 'should reject a dirty repository, Git 1.4.3 or higher' do + Git.should_receive(:`).with('git status').and_return <<-EOF +# On branch master +# Changed but not updated: +# (use "git add ..." to update what will be committed) +# +#\tmodified: lib/buildr.rb +#\tmodified: spec/buildr_spec.rb +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +#\terror.log +no changes added to commit (use "git add" and/or "git commit -a") + EOF + Git.uncommitted_files.should include('lib/buildr.rb', 'error.log') + end + end + + describe '#remote' do + it 'should return the name of the corresponding remote' do + Git.should_receive(:git).with('config', '--get', 'branch.master.remote').and_return "origin\n" + Git.should_receive(:git).with('remote').and_return "upstream\norigin\n" + Git.send(:remote, 'master').should == 'origin' + end + + it 'should return nil if no remote for the given branch' do + Git.should_receive(:git).with('config', '--get', 'branch.master.remote').and_return "\n" + Git.should_not_receive(:git).with('remote') + Git.send(:remote, 'master').should be_nil + end + end + + describe '#current_branch' do + it 'should return the current branch' do + Git.should_receive(:git).with('branch').and_return(" master\n* a-clever-idea\n ze-great-idea") + Git.send(:current_branch).should == 'a-clever-idea' + end + end + +end # of Git + + +describe Svn do + describe '#tag' do + it 'should remove any existing tag with the same name' do + Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk') + Svn.stub!(:copy) + Svn.should_receive(:remove).with('http://my.repo.org/foo/tags/1.0.0', 'Removing old copy') + + Svn.tag '1.0.0' + end + + it 'should do an svn copy with the release version' do + Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk') + Svn.stub!(:remove) + Svn.should_receive(:copy).with(Dir.pwd, 'http://my.repo.org/foo/tags/1.0.0', 'Release 1.0.0') + + Svn.tag '1.0.0' + end + end + + # Reference: http://svnbook.red-bean.com/en/1.4/svn.reposadmin.planning.html#svn.reposadmin.projects.chooselayout + describe '#tag_url' do + it 'should accept to tag foo/trunk' do + Svn.tag_url('http://my.repo.org/foo/trunk', '1.0.0').should == 'http://my.repo.org/foo/tags/1.0.0' + end + + it 'should accept to tag foo/branches/1.0' do + Svn.tag_url('http://my.repo.org/foo/branches/1.0', '1.0.1').should == 'http://my.repo.org/foo/tags/1.0.1' + end + + it 'should accept to tag trunk/foo' do + Svn.tag_url('http://my.repo.org/trunk/foo', '1.0.0').should == 'http://my.repo.org/tags/foo/1.0.0' + end + + it 'should accept to tag branches/foo/1.0' do + Svn.tag_url('http://my.repo.org/branches/foo/1.0', '1.0.0').should == 'http://my.repo.org/tags/foo/1.0.0' + end + + describe '#repo_url' do + it 'should extract the SVN URL from svn info' do + Svn.should_receive(:svn).and_return <<-XML + + + +http://my.repo.org/foo/trunk + +http://my.repo.org +13f79535-47bb-0310-9956-ffa450edef68 + + +normal +infinity + + +boisvert +2008-12-10T01:53:51.240936Z + + + + XML + Svn.repo_url.should == 'http://my.repo.org/foo/trunk' + end + end + + end + +end # of Buildr::Svn + + +describe Release do + describe 'find' do + it 'should return GitRelease if project uses Git' do + write '.git/config' + Release.find.should be_instance_of(GitRelease) + end + + it 'should return SvnRelease if project uses SVN' do + write '.svn/xml' + Release.find.should be_instance_of(SvnRelease) + end + + it 'should return nil if no known release process' do + Dir.chdir(Dir.tmpdir) do + Release.find.should be_nil + end + end + + after :each do + Release.instance_exec { @release = nil } + end + end +end + + +shared_examples_for 'a release process' do + + describe '#make' do + before do + write 'buildfile', "VERSION_NUMBER = '1.0.0-SNAPSHOT'" + # Prevent a real call to a spawned buildr process. + @release.stub!(:buildr) + @release.stub!(:check) + @release.should_receive(:ruby).with('-S', 'buildr', "_#{Buildr::VERSION}_", '--buildfile', File.expand_path('buildfile.next'), + '--environment', 'development', 'clean', 'upload', 'DEBUG=no') + end + + it 'should tag a release with the release version' do + @release.stub!(:update_version_to_next) + @release.should_receive(:tag_release).with('1.0.0') + @release.make + end + + it 'should not alter the buildfile before tagging' do + @release.stub!(:update_version_to_next) + @release.should_receive(:tag_release).with('1.0.0') + @release.make + file('buildfile').should contain('VERSION_NUMBER = "1.0.0"') + end + + it 'should update the buildfile with the next version number' do + @release.stub!(:tag_release) + @release.make + file('buildfile').should contain('VERSION_NUMBER = "1.0.1-SNAPSHOT"') + end + + it 'should keep leading zeros in the next version number' do + write 'buildfile', "VERSION_NUMBER = '1.0.001-SNAPSHOT'" + @release.stub!(:tag_release) + @release.make + file('buildfile').should contain('VERSION_NUMBER = "1.0.002-SNAPSHOT"') + end + + it 'should commit the updated buildfile' do + @release.stub!(:tag_release) + @release.make + file('buildfile').should contain('VERSION_NUMBER = "1.0.1-SNAPSHOT"') + end + + it 'should not consider "-rc" as "-SNAPSHOT"' do + write 'buildfile', "VERSION_NUMBER = '1.0.0-rc1'" + @release.stub!(:tag_release) + @release.make + file('buildfile').should contain('VERSION_NUMBER = "1.0.0-rc1"') + end + + it 'should only commit the updated buildfile if the version changed' do + write 'buildfile', "VERSION_NUMBER = '1.0.0-rc1'" + @release.should_not_receive(:update_version_to_next) + @release.stub!(:tag_release) + @release.make + end + end + + describe '#resolve_next_version' do + + it 'should increment the version number if SNAPSHOT' do + @release.send(:resolve_next_version, "1.0.0-SNAPSHOT").should == '1.0.1-SNAPSHOT' + end + + it 'should NOT increment the version number if no SNAPSHOT' do + @release.send(:resolve_next_version, "1.0.0").should == '1.0.0' + end + + it 'should return the version specified by NEXT_VERSION env var' do + ENV['NEXT_VERSION'] = "version_from_env" + @release.send(:resolve_next_version, "1.0.0").should == 'version_from_env' + end + + it 'should return the version specified by next_version' do + Release.next_version = "ze_next_version" + @release.send(:resolve_next_version, "1.0.0").should == 'ze_next_version' + end + + it 'should return the version specified by next_version if next_version is a proc' do + Release.next_version = lambda {|version| "#{version}++"} + @release.send(:resolve_next_version, "1.0.0").should == '1.0.0++' + end + + it "should return the version specified by 'NEXT_VERSION' env var even if next_version is non nil" do + ENV['NEXT_VERSION'] = "ze_version_from_env" + Release.next_version = lambda {|version| "#{version}++"} + @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env' + end + + it "should return the version specified by 'next_version' env var even if next_version is non nil" do + ENV['NEXT_VERSION'] = nil + ENV['next_version'] = "ze_version_from_env_lowercase" + Release.next_version = lambda {|version| "#{version}++"} + @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env_lowercase' + end + after { + Release.next_version = nil + ENV['NEXT_VERSION'] = nil + ENV['next_version'] = nil + } + end + + describe '#resolve_next_version' do + + it 'should increment the version number if SNAPSHOT' do + @release.send(:resolve_next_version, "1.0.0-SNAPSHOT").should == '1.0.1-SNAPSHOT' + end + + it 'should NOT increment the version number if no SNAPSHOT' do + @release.send(:resolve_next_version, "1.0.0").should == '1.0.0' + end + + it 'should return the version specified by NEXT_VERSION env var' do + ENV['NEXT_VERSION'] = "version_from_env" + @release.send(:resolve_next_version, "1.0.0").should == 'version_from_env' + end + + it 'should return the version specified by next_version' do + Release.next_version = "ze_next_version" + @release.send(:resolve_next_version, "1.0.0").should == 'ze_next_version' + end + + it 'should return the version specified by next_version if next_version is a proc' do + Release.next_version = lambda {|version| "#{version}++"} + @release.send(:resolve_next_version, "1.0.0").should == '1.0.0++' + end + + it "should return the version specified by 'NEXT_VERSION' env var even if next_version is non nil" do + ENV['NEXT_VERSION'] = "ze_version_from_env" + Release.next_version = lambda {|version| "#{version}++"} + @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env' + end + + it "should return the version specified by 'next_version' env var even if next_version is non nil" do + ENV['NEXT_VERSION'] = nil + ENV['next_version'] = "ze_version_from_env_lowercase" + Release.next_version = lambda {|version| "#{version}++"} + @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env_lowercase' + end + after { + Release.next_version = nil + ENV['NEXT_VERSION'] = nil + ENV['next_version'] = nil + } + end + + describe '#resolve_next_version' do + + it 'should increment the version number if SNAPSHOT' do + @release.send(:resolve_next_version, "1.0.0-SNAPSHOT").should == '1.0.1-SNAPSHOT' + end + + it 'should NOT increment the version number if no SNAPSHOT' do + @release.send(:resolve_next_version, "1.0.0").should == '1.0.0' + end + + it 'should return the version specified by NEXT_VERSION env var' do + ENV['NEXT_VERSION'] = "version_from_env" + @release.send(:resolve_next_version, "1.0.0").should == 'version_from_env' + end + + it 'should return the version specified by next_version' do + Release.next_version = "ze_next_version" + @release.send(:resolve_next_version, "1.0.0").should == 'ze_next_version' + end + + it 'should return the version specified by next_version if next_version is a proc' do + Release.next_version = lambda {|version| "#{version}++"} + @release.send(:resolve_next_version, "1.0.0").should == '1.0.0++' + end + + it "should return the version specified by 'NEXT_VERSION' env var even if next_version is non nil" do + ENV['NEXT_VERSION'] = "ze_version_from_env" + Release.next_version = lambda {|version| "#{version}++"} + @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env' + end + + it "should return the version specified by 'next_version' env var even if next_version is non nil" do + ENV['NEXT_VERSION'] = nil + ENV['next_version'] = "ze_version_from_env_lowercase" + Release.next_version = lambda {|version| "#{version}++"} + @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env_lowercase' + end + after { + Release.next_version = nil + ENV['NEXT_VERSION'] = nil + ENV['next_version'] = nil + } + end + + describe '#resolve_tag' do + before do + @release.stub!(:extract_version).and_return('1.0.0') + end + + it 'should return tag specified by tag_name' do + Release.tag_name = 'first' + @release.send(:resolve_tag).should == 'first' + end + + it 'should use tag returned by tag_name if tag_name is a proc' do + Release.tag_name = lambda { |version| "buildr-#{version}" } + @release.send(:resolve_tag).should == 'buildr-1.0.0' + end + after { Release.tag_name = nil } + end + + describe '#tag_release' do + it 'should inform the user' do + @release.stub!(:extract_version).and_return('1.0.0') + lambda { @release.tag_release('1.0.0') }.should show_info('Tagging release 1.0.0') + end + end + + describe '#extract_version' do + it 'should extract VERSION_NUMBER with single quotes' do + write 'buildfile', "VERSION_NUMBER = '1.0.0-SNAPSHOT'" + @release.extract_version.should == '1.0.0-SNAPSHOT' + end + + it 'should extract VERSION_NUMBER with double quotes' do + write 'buildfile', %{VERSION_NUMBER = "1.0.1-SNAPSHOT"} + @release.extract_version.should == '1.0.1-SNAPSHOT' + end + + it 'should extract VERSION_NUMBER without any spaces' do + write 'buildfile', "VERSION_NUMBER='1.0.2-SNAPSHOT'" + @release.extract_version.should == '1.0.2-SNAPSHOT' + end + + it 'should extract THIS_VERSION as an alternative to VERSION_NUMBER' do + write 'buildfile', "THIS_VERSION = '1.0.3-SNAPSHOT'" + @release.extract_version.should == '1.0.3-SNAPSHOT' + end + + it 'should complain if no current version number' do + write 'buildfile', 'define foo' + lambda { @release.extract_version }.should raise_error('Looking for THIS_VERSION = "..." in your Buildfile, none found') + end + end + + describe '#with_release_candidate_version' do + before do + Buildr.application.stub!(:buildfile).and_return(file('buildfile')) + write 'buildfile', "THIS_VERSION = '1.1.0-SNAPSHOT'" + end + + it 'should yield the name of the release candidate buildfile' do + @release.send :with_release_candidate_version do |new_filename| + File.read(new_filename).should == %{THIS_VERSION = "1.1.0"} + end + end + + it 'should yield a name different from the original buildfile' do + @release.send :with_release_candidate_version do |new_filename| + new_filename.should_not point_to_path('buildfile') + end + end + end + + describe '#update_version_to_next' do + before do + write 'buildfile', "VERSION_NUMBER = '1.0.5-SNAPSHOT'" + @release.send(:this_version=, "1.0.5-SNAPSHOT") + end + + it 'should update the buildfile with a new version number' do + @release.send :update_version_to_next + `cp buildfile /tmp/out` + file('buildfile').should contain('VERSION_NUMBER = "1.0.6-SNAPSHOT"') + end + + it 'should commit the new buildfile on the trunk' do + @release.should_receive(:message).and_return('Changed version number to 1.0.1-SNAPSHOT') + @release.update_version_to_next + end + + it 'should use the commit message specified by commit_message' do + Release.commit_message = 'Here is my custom message' + @release.should_receive(:message).and_return('Here is my custom message') + @release.update_version_to_next + end + + it 'should use the commit message returned by commit_message if commit_message is a proc' do + Release.commit_message = lambda { |new_version| + new_version.should == '1.0.1-SNAPSHOT' + "increment version number to #{new_version}" + } + @release.should_receive(:message).and_return('increment version number to 1.0.1-SNAPSHOT') + @release.update_version_to_next + end + + it 'should inform the user of the new version' do + lambda { @release.update_version_to_next }.should show_info('Current version is now 1.0.6-SNAPSHOT') + end + after { Release.commit_message = nil } + end + + + describe '#check' do + before { @release.send(:this_version=, "1.0.0-SNAPSHOT") } + it 'should fail if THIS_VERSION equals the next_version' do + @release.stub!(:resolve_next_version).and_return('1.0.0-SNAPSHOT') + lambda { @release.check }.should raise_error("The next version can't be equal to the current version 1.0.0-SNAPSHOT.\nUpdate THIS_VERSION/VERSION_NUMBER, specify Release.next_version or use NEXT_VERSION env var") + end + end +end + + +describe GitRelease do + it_should_behave_like 'a release process' + + before do + write 'buildfile', "VERSION_NUMBER = '1.0.0-SNAPSHOT'" + @release = GitRelease.new + Git.stub!(:git) + Git.stub!(:current_branch).and_return('master') + end + + describe '#applies_to?' do + it 'should reject a non-git repo' do + Dir.chdir(Dir.tmpdir) do + GitRelease.applies_to?.should be_false + end + end + + it 'should accept a git repo' do + FileUtils.mkdir '.git' + FileUtils.touch File.join('.git', 'config') + GitRelease.applies_to?.should be_true + end + end + + describe '#check' do + before do + @release = GitRelease.new + @release.send(:this_version=, '1.0.0-SNAPSHOT') + end + + it 'should accept a clean repository' do + Git.should_receive(:`).with('git status').and_return <<-EOF +# On branch master +nothing to commit (working directory clean) + EOF + Git.should_receive(:remote).and_return('master') + lambda { @release.check }.should_not raise_error + end + + it 'should reject a dirty repository' do + Git.should_receive(:`).with('git status').and_return <<-EOF +# On branch master +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# foo.temp +EOF + lambda { @release.check }.should raise_error(RuntimeError, /uncommitted files/i) + end + + it 'should reject a repository not tracking remote branch' do + Git.should_receive(:uncommitted_files).and_return([]) + Git.should_receive(:remote).and_return(nil) + lambda{ @release.check }.should raise_error(RuntimeError, + "You are releasing from a local branch that does not track a remote!") + end + end + + describe '#tag_release' do + before do + @release = GitRelease.new + @release.stub!(:extract_version).and_return('1.0.1') + @release.stub!(:resolve_tag).and_return('TEST_TAG') + Git.stub!(:git).with('tag', '-a', 'TEST_TAG', '-m', '[buildr] Cutting release TEST_TAG') + Git.stub!(:git).with('push', 'origin', 'tag', 'TEST_TAG') + Git.stub!(:commit) + Git.stub!(:push) + Git.stub!(:remote).and_return('origin') + end + + it 'should delete any existing tag with the same name' do + Git.should_receive(:git).with('tag', '-d', 'TEST_TAG') + Git.should_receive(:git).with('push', 'origin', ':refs/tags/TEST_TAG') + @release.tag_release 'TEST_TAG' + end + + it 'should commit the buildfile before tagging' do + Git.should_receive(:commit).with(File.basename(Buildr.application.buildfile.to_s), "Changed version number to 1.0.1") + @release.tag_release 'TEST_TAG' + end + + it 'should push the tag if a remote is tracked' do + Git.should_receive(:git).with('tag', '-d', 'TEST_TAG') + Git.should_receive(:git).with('push', 'origin', ':refs/tags/TEST_TAG') + Git.should_receive(:git).with('tag', '-a', 'TEST_TAG', '-m', '[buildr] Cutting release TEST_TAG') + Git.should_receive(:git).with('push', 'origin', 'tag', 'TEST_TAG') + @release.tag_release 'TEST_TAG' + end + + it 'should NOT push the tag if no remote is tracked' do + Git.stub!(:remote).and_return(nil) + Git.should_not_receive(:git).with('push', 'origin', 'tag', 'TEST_TAG') + @release.tag_release 'TEST_TAG' + end + end +end + + +describe SvnRelease do + it_should_behave_like 'a release process' + + before do + write 'buildfile', "VERSION_NUMBER = '1.0.0-SNAPSHOT'" + @release = SvnRelease.new + Svn.stub!(:svn) + Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk') + Svn.stub!(:tag) + end + + describe '#applies_to?' do + it 'should reject a non-git repo' do + SvnRelease.applies_to?.should be_false + end + + it 'should accept a git repo' do + FileUtils.touch '.svn' + SvnRelease.applies_to?.should be_true + end + end + + describe '#check' do + before do + Svn.stub!(:uncommitted_files).and_return([]) + @release = SvnRelease.new + @release.send(:this_version=, "1.0.0-SNAPSHOT") + end + + it 'should accept to release from the trunk' do + Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk') + lambda { @release.check }.should_not raise_error + end + + it 'should accept to release from a branch' do + Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/branches/1.0') + lambda { @release.check }.should_not raise_error + end + + it 'should reject releasing from a tag' do + Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/tags/1.0.0') + lambda { @release.check }.should raise_error(RuntimeError, "SVN URL must contain 'trunk' or 'branches/...'") + end + + it 'should reject a non standard repository layout' do + Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/bar') + lambda { @release.check }.should raise_error(RuntimeError, "SVN URL must contain 'trunk' or 'branches/...'") + end + + it 'should reject an uncommitted file' do + Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk') + Svn.stub!(:uncommitted_files).and_return(['foo.rb']) + lambda { @release.check }.should raise_error(RuntimeError, + "Uncommitted files violate the First Principle Of Release!\n" + + "foo.rb") + end + end +end diff --git a/buildr/spec/core/cc_spec.rb b/buildr/spec/core/cc_spec.rb new file mode 100644 index 0000000..782284d --- /dev/null +++ b/buildr/spec/core/cc_spec.rb @@ -0,0 +1,223 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +module CCHelper + + # monkey-patch task instance to track number of times it is run + def instrument_task(task) + class << task + attr_accessor :run_count + end + task.run_count = 0 + task.enhance do |t| + t.run_count += 1 + end + task + end + + def instrument_project(project) + instrument_task project.compile + instrument_task project.test.compile + instrument_task project.resources + project + end + + def define_foo() + @foo = define('foo') + instrument_project @foo + @foo + end + + def foo() + @foo + end +end + + +describe Buildr::CCTask do + include CCHelper + + it 'should default to a delay of 0.2' do + define('foo').cc.delay.should == 0.2 + end + + it 'should compile and test:compile on initial start' do + ['Test1.java', 'Test2.java'].map { |f| File.join('src/main/java/thepackage', f) }. + each { |src| write src, "package thepackage; class #{src.pathmap('%n')} {}" } + + ['Test1.java', 'Test2.java'].map { |f| File.join('src/test/java/thepackage', f) }. + each { |src| write src, "package thepackage; class #{src.pathmap('%n')} {}" } + + ['Test1.html', 'Test2.html'].map { |f| File.join('src/main/resources', f) }. + each { |src| write src, '' } + + define_foo() + + thread = Thread.new do + foo.cc.invoke + end + + sleep 1 + + foo.compile.run_count.should == 1 + foo.test.compile.run_count.should == 1 + + thread.exit + end + + it 'should detect a file change' do |spec| + write 'src/main/resources/my.properties', "# comment" + write 'src/main/java/Example.java', "public class Example {}" + write 'src/test/java/ExampleTest.java', "public class ExampleTest {}" + + define_foo + + thread = Thread.new do + begin + foo.cc.invoke + rescue => e + p "unexpected exception #{e.inspect}" + p e.backtrace.join("\n").inspect + end + end + + sleep 1 + + foo.compile.run_count.should == 1 + foo.test.compile.run_count.should == 1 + foo.resources.run_count.should == 1 + + sleep 1 # Wait one sec as the timestamp needs to be different. + + touch File.join(Dir.pwd, 'src/main/java/Example.java') + + sleep 1 + + foo.compile.run_count.should == 2 + foo.test.compile.run_count.should == 2 + foo.resources.run_count.should == 2 + + thread.exit + end + + it 'should support subprojects' do |spec| + write 'foo/src/main/java/Example.java', "public class Example {}" + write 'foo/src/test/java/ExampleTest.java', "public class ExampleTest {}" + + define 'container' do + define('foo') + end + + foo = instrument_project project("container:foo") + + thread = Thread.new do + begin + project("container").cc.invoke + rescue => e + p "unexpected exception #{e.inspect}" + p e.backtrace.join("\n").inspect + end + end + + sleep 1 + + foo.compile.run_count.should == 1 + foo.test.compile.run_count.should == 1 + foo.resources.run_count.should == 1 + + file("foo/target/classes/Example.class").should exist + tstamp = File.mtime("foo/target/classes/Example.class") + touch File.join(Dir.pwd, 'foo/src/main/java/Example.java') + + sleep 1 + + foo.compile.run_count.should == 2 + foo.test.compile.run_count.should == 2 + foo.resources.run_count.should == 2 + File.mtime("foo/target/classes/Example.class").should_not == tstamp + + thread.exit + end + + it 'should support parent and subprojects' do |spec| + write 'foo/src/main/java/Example.java', "public class Example {}" + write 'foo/src/test/java/ExampleTest.java', "public class ExampleTest {}" + + write 'bar/src/main/java/Example.java', "public class Example {}" + write 'bar/src/test/java/ExampleTest.java', "public class ExampleTest {}" + + write 'src/main/java/Example.java', "public class Example {}" + write 'src/test/java/ExampleTest.java', "public class ExampleTest {}" + + write 'src/main/resources/foo.txt', "content" + + define 'container' do + define('foo') + define('bar') + end + + all = projects("container", "container:foo", "container:bar") + all.each { |p| instrument_project(p) } + + thread = Thread.new do + begin + project("container").cc.invoke + rescue => e + p "unexpected exception #{e.inspect}" + p e.backtrace.join("\n").inspect + end + end + + sleep 2 + + all.each do |p| + p.compile.run_count.should == 1 + p.test.compile.run_count.should == 1 + p.resources.run_count.should == 1 + end + + file("foo/target/classes/Example.class").should exist + tstamp = File.mtime("foo/target/classes/Example.class") + + touch 'foo/src/main/java/Example.java' + sleep 2 + + project("container:foo").tap do |p| + p.compile.run_count.should == 2 + p.test.compile.run_count.should == 2 + p.resources.run_count.should == 2 + end + project("container").tap do |p| + p.compile.run_count.should == 1 # not_needed + p.test.compile.run_count.should == 1 # not_needed + p.resources.run_count.should == 2 + end + File.mtime("foo/target/classes/Example.class").should_not == tstamp + + touch 'src/main/java/Example.java' + sleep 2 + + project("container").tap do |p| + p.compile.run_count.should == 2 + p.test.compile.run_count.should == 2 + p.resources.run_count.should == 3 + end + + thread.exit + end +end diff --git a/buildr/spec/core/checks_spec.rb b/buildr/spec/core/checks_spec.rb new file mode 100644 index 0000000..4232bff --- /dev/null +++ b/buildr/spec/core/checks_spec.rb @@ -0,0 +1,519 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Project, 'check task' do + + it "should execute last thing from package task" do + task 'action' + define 'foo', :version=>'1.0' do + package :jar + task('package').enhance { task('action').invoke } + end + lambda { project('foo').task('package').invoke }.should run_tasks(['foo:package', 'action', 'foo:check']) + end + + it "should execute all project's expectations" do + task 'expectation' + define 'foo', :version=>'1.0' do + check { task('expectation').invoke } + end + lambda { project('foo').task('package').invoke }.should run_task('expectation') + end + + it "should succeed if there are no expectations" do + define 'foo', :version=>'1.0' + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should succeed if all expectations passed" do + define 'foo', :version=>'1.0' do + check { true } + check { false } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail if any expectation failed" do + define 'foo', :version=>'1.0' do + check + check { fail 'sorry' } + check + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end +end + + +describe Project, '#check' do + + it "should add expectation" do + define 'foo' do + expectations.should be_empty + check + expectations.size.should be(1) + end + end + + it "should treat no arguments as expectation against project" do + define 'foo' do + subject = self + check do + it.should be(subject) + description.should eql(subject.to_s) + end + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should treat single string argument as description, expectation against project" do + define 'foo' do + subject = self + check "should be project" do + it.should be(subject) + description.should eql("#{subject} should be project") + end + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should treat single object argument as subject" do + define 'foo' do + subject = Object.new + check subject do + it.should be(subject) + description.should eql(subject.to_s) + end + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should treat first object as subject, second object as description" do + define 'foo' do + subject = Object.new + check subject, "should exist" do + it.should be(subject) + description.should eql("#{subject} should exist") + end + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should work without block" do + define 'foo' do + check "implement later" + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it 'should pass method calls to context' do + define 'foo', :version=>'1.0' do + subject = self + check "should be project" do + it.should be(subject) + name.should eql(subject.name) + package(:jar).should eql(subject.package(:jar)) + end + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end +end + + +describe Buildr::Checks::Expectation, 'matchers' do + + it "should include Buildr matchers exist and contain" do + define 'foo' do + check do + self.should respond_to(:exist) + self.should respond_to(:contain) + end + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should include RSpec matchers like be and eql" do + define 'foo' do + check do + self.should respond_to(:be) + self.should respond_to(:eql) + end + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should include RSpec predicates like be_nil and be_empty" do + define 'foo' do + check do + nil.should be_nil + [].should be_empty + end + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end +end + + +describe Buildr::Checks::Expectation, 'exist' do + + it "should pass if file exists" do + define 'foo' do + build file('test') { |task| write task.name } + check(file('test')) { it.should exist } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail if file does not exist" do + define 'foo' do + check(file('test')) { it.should exist } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should not attempt to invoke task" do + define 'foo' do + file('test') { |task| write task.name } + check(file('test')) { it.should exist } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end +end + + +describe Buildr::Checks::Expectation, " be_empty" do + + it "should pass if file has no content" do + define 'foo' do + build file('test') { write 'test' } + check(file('test')) { it.should be_empty } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail if file has content" do + define 'foo' do + build file('test') { write 'test', "something" } + check(file('test')) { it.should be_empty } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should fail if file does not exist" do + define 'foo' do + check(file('test')) { it.should be_empty } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should pass if directory is empty" do + define 'foo' do + build file('test') { mkpath 'test' } + check(file('test')) { it.should be_empty } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail if directory has any files" do + define 'foo' do + build file('test') { write 'test/file' } + check(file('test')) { it.should be_empty } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end +end + + +describe Buildr::Checks::Expectation, " contain(file)" do + + it "should pass if file content matches string" do + define 'foo' do + build file('test') { write 'test', 'something' } + check(file('test')) { it.should contain('thing') } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should pass if file content matches pattern" do + define 'foo' do + build file('test') { write 'test', "something\nor\nanother" } + check(file('test')) { it.should contain(/or/) } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should pass if file content matches all arguments" do + define 'foo' do + build file('test') { write 'test', "something\nor\nanother" } + check(file('test')) { it.should contain(/or/, /other/) } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail unless file content matchs all arguments" do + define 'foo' do + build file('test') { write 'test', 'something' } + check(file('test')) { it.should contain(/some/, /other/) } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should fail if file content does not match" do + define 'foo' do + build file('test') { write 'test', "something" } + check(file('test')) { it.should contain(/other/) } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should fail if file does not exist" do + define 'foo' do + check(file('test')) { it.should contain(/anything/) } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end +end + + +describe Buildr::Checks::Expectation, 'contain(directory)' do + + it "should pass if directory contains file" do + write 'resources/test' + define 'foo' do + check(file('resources')) { it.should contain('test') } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should pass if directory contains glob pattern" do + write 'resources/with/test' + define 'foo' do + check(file('resources')) { it.should contain('**/t*st') } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should pass if directory contains all arguments" do + write 'resources/with/test' + define 'foo' do + check(file('resources')) { it.should contain('**/test', '**/*') } + end + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail unless directory contains all arguments" do + write 'resources/test' + define 'foo' do + check(file('resources')) { it.should contain('test', 'or-not') } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should fail if directory is empty" do + mkpath 'resources' + define 'foo' do + check(file('resources')) { it.should contain('test') } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should fail if directory does not exist" do + define 'foo' do + check(file('resources')) { it.should contain } + end + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end +end + + +describe Buildr::Checks::Expectation do + + shared_examples_for 'all archive types' do + + before do + archive = @archive + define 'foo', :version=>'1.0' do + package(archive).include('resources') + end + end + + def check *args, &block + project('foo').check *args, &block + end + + def package + project('foo').package(@archive) + end + + describe '#exist' do + + it "should pass if archive path exists" do + write 'resources/test' + check(package.path('resources')) { it.should exist } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail if archive path does not exist" do + mkpath 'resources' + check(package) { it.path('not-resources').should exist } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should pass if archive entry exists" do + write 'resources/test' + check(package.entry('resources/test')) { it.should exist } + check(package.path('resources').entry('test')) { it.should exist } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail if archive path does not exist" do + mkpath 'resources' + check(package.entry('resources/test')) { it.should exist } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + end + + describe '#be_empty' do + it "should pass if archive path is empty" do + mkpath 'resources' + check(package.path('resources')) { it.should be_empty } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail if archive path has any entries" do + write 'resources/test' + check(package.path('resources')) { it.should be_empty } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should pass if archive entry has no content" do + write 'resources/test' + check(package.entry('resources/test')) { it.should be_empty } + check(package.path('resources').entry('test')) { it.should be_empty } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail if archive entry has content" do + write 'resources/test', 'something' + check(package.entry('resources/test')) { it.should be_empty } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should fail if archive entry does not exist" do + mkpath 'resources' + check(package.entry('resources/test')) { it.should be_empty } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + end + + describe '#contain(entry)' do + + it "should pass if archive entry content matches string" do + write 'resources/test', 'something' + check(package.entry('resources/test')) { it.should contain('thing') } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should pass if archive entry content matches pattern" do + write 'resources/test', "something\nor\another" + check(package.entry('resources/test')) { it.should contain(/or/) } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should pass if archive entry content matches all arguments" do + write 'resources/test', "something\nor\nanother" + check(package.entry('resources/test')) { it.should contain(/or/, /other/) } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail unless archive path contains all arguments" do + write 'resources/test', 'something' + check(package.entry('resources/test')) { it.should contain(/some/, /other/) } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should fail if archive entry content does not match" do + write 'resources/test', 'something' + check(package.entry('resources/test')) { it.should contain(/other/) } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should fail if archive entry does not exist" do + mkpath 'resources' + check(package.entry('resources/test')) { it.should contain(/anything/) } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + end + + describe '#contain(path)' do + + it "should pass if archive path contains file" do + write 'resources/test' + check(package.path('resources')) { it.should contain('test') } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should handle deep nesting" do + write 'resources/test/test2.efx' + check(package) { it.should contain('resources/test/test2.efx') } + check(package.path('resources')) { it.should contain('test/test2.efx') } + check(package.path('resources/test')) { it.should contain('test2.efx') } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should pass if archive path contains pattern" do + write 'resources/with/test' + check(package.path('resources')) { it.should contain('**/t*st') } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should pass if archive path contains all arguments" do + write 'resources/with/test' + check(package.path('resources')) { it.should contain('**/test', '**/*') } + lambda { project('foo').task('package').invoke }.should_not raise_error + end + + it "should fail unless archive path contains all arguments" do + write 'resources/test' + check(package.path('resources')) { it.should contain('test', 'or-not') } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + + it "should fail if archive path is empty" do + mkpath 'resources' + check(package.path('resources')) { it.should contain('test') } + lambda { project('foo').task('package').invoke }.should raise_error(RuntimeError, /Checks failed/) + end + end + end + + describe 'ZIP' do + before { @archive = :jar } + it_should_behave_like 'all archive types' + end + + describe 'tar' do + before { @archive = :tar } + it_should_behave_like 'all archive types' + end + + describe 'tgz' do + before { @archive = :tgz } + it_should_behave_like 'all archive types' + end +end diff --git a/buildr/spec/core/common_spec.rb b/buildr/spec/core/common_spec.rb new file mode 100644 index 0000000..21e77c6 --- /dev/null +++ b/buildr/spec/core/common_spec.rb @@ -0,0 +1,725 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe Buildr.method(:struct) do + before do + @hash = { :foo=>'foo:jar', :bar=>'bar:jar' } + @struct = struct(@hash) + end + + it 'should be object with key-value pairs' do + @struct.foo.should eql('foo:jar') + @struct.bar.should eql('bar:jar') + end + + it 'should fail when requesting non-existent key' do + lambda { @struct.foobar }.should raise_error(NoMethodError) + end + + it 'should return members when requested' do + @struct.members.map(&:to_s).sort.should eql(@hash.keys.map(&:to_s).sort) + end + + it 'should return valued when requested' do + @struct.values.sort.should eql(@hash.values.sort) + end +end + + +describe Buildr.method(:write) do + it 'should create path' do + write 'foo/test' + File.directory?('foo').should be_true + File.exist?('foo/test').should be_true + end + + it 'should write content to file' do + write 'test', 'content' + File.read('test').should eql('content') + end + + it 'should retrieve content from block, if block given' do + write('test') { 'block' } + File.read('test').should eql('block') + end + + it 'should write empty file if no content provided' do + write 'test' + File.read('test').should eql('') + end + + it 'should return content as a string' do + write('test', 'content').should eql('content') + end + + it 'should return empty string if no content provided' do + write('test').should eql('') + end +end + + +describe Buildr.method(:read) do + before do + write @file = 'test', @content = 'content' + end + + it 'should return contents of named file' do + read(@file).should eql(@content) + end + + it 'should yield to block if block given' do + read @file do |content| + content.should eql(@content) + end + end + + it 'should return block response if block given' do + read(@file) { 5 }.should be(5) + end +end + + +describe Buildr.method(:download) do + before do + @content = 'we has download!' + @http = mock('http') + @http.stub!(:request).and_return(Net::HTTPNotModified.new(nil, nil, nil)) + end + + def tasks() + [ download('http://localhost/download'), download('downloaded'=>'http://localhost/download') ] + end + + it 'should be a file task' do + tasks.each { |task| task.should be_kind_of(Rake::FileTask) } + end + + it 'should accept a String and download from that URL' do + define 'foo' do + download('http://localhost/download').tap do |task| + task.source.should_receive(:read).and_yield [@content] + task.invoke + task.should contain(@content) + end + end + end + + it 'should accept a URI and download from that URL' do + define 'foo' do + download(URI.parse('http://localhost/download')).tap do |task| + task.source.should_receive(:read).and_yield [@content] + task.invoke + task.should contain(@content) + end + end + end + + it 'should accept a path and String and download from that URL' do + define 'foo' do + download('downloaded'=>'http://localhost/download').tap do |task| + task.source.should_receive(:read).and_yield [@content] + task.invoke + task.should contain(@content) + end + end + end + + it 'should accept an artifact and String and download from that URL' do + define 'foo' do + artifact('com.example:library:jar:2.0').tap do |artifact| + download(artifact=>'http://localhost/download').source.should_receive(:read).and_yield [@content] + artifact.invoke + artifact.should contain(@content) + end + end + end + + it 'should accept a path and URI and download from that URL' do + define 'foo' do + download('downloaded'=>URI.parse('http://localhost/download')).tap do |task| + task.source.should_receive(:read).and_yield [@content] + task.invoke + task.should contain(@content) + end + end + end + + it 'should create path for download' do + define 'foo' do + download('path/downloaded'=>URI.parse('http://localhost/download')).tap do |task| + task.source.should_receive(:read).and_yield [@content] + task.invoke + task.should contain(@content) + end + end + end + + it 'should fail if resource not found' do + tasks.each do |task| + task.source.should_receive(:read).and_raise URI::NotFoundError + lambda { task.invoke }.should raise_error(URI::NotFoundError) + end + tasks.last.should_not exist + end + + it 'should fail on any other error' do + tasks.each do |task| + task.source.should_receive(:read).and_raise RuntimeError + lambda { task.invoke }.should raise_error(RuntimeError) + end + tasks.last.should_not exist + end + + it 'should execute only if file does not already exist' do + define 'foo' do + download('downloaded'=>'http://localhost/download').tap do |task| + task.source.should_not_receive(:read) + write task.to_s, 'not really' + task.invoke + end + end + end + + it 'should execute without a proxy if none specified' do + Net::HTTP.should_receive(:new).with('localhost', 80).twice.and_return(@http) + tasks.each(&:invoke) + end + + it 'should pass Buildr proxy options' do + Buildr.options.proxy.http = 'http://proxy:8080' + Net::HTTP.should_receive(:new).with('localhost', 80, 'proxy', 8080, nil, nil).twice.and_return(@http) + tasks.each(&:invoke) + end + + it 'should set HTTP proxy from HTTP_PROXY environment variable' do + ENV['HTTP_PROXY'] = 'http://proxy:8080' + Net::HTTP.should_receive(:new).with('localhost', 80, 'proxy', 8080, nil, nil).twice.and_return(@http) + tasks.each(&:invoke) + end +end + + +describe Buildr.method(:filter) do + def source + File.expand_path('src') + end + + it 'should return a Filter for the source' do + filter(source).should be_kind_of(Filter) + end + + it 'should use the source directory' do + filter(source).sources.should include(file(source)) + end + + it 'should use the source directories' do + dirs = ['first', 'second'] + filter('first', 'second').sources.should include(*dirs.map { |dir| file(File.expand_path(dir)) }) + end + + it 'should accept a file task' do + task = file(source) + filter(task).sources.each { |source| source.should be(task) } + end +end + + +describe Buildr::Filter do + before do + @filter = Filter.new + 1.upto(4) do |i| + write "src/file#{i}", "file#{i} raw" + end + @early = Time.now - 1000 + end + + it 'should respond to :from and return self' do + @filter.from('src').should be(@filter) + end + + it 'should respond to :from and add source directory' do + lambda { @filter.from('src') }.should change { @filter.sources } + end + + it 'should respond to :from and add source directories' do + dirs = ['first', 'second'] + @filter.from(*dirs) + @filter.sources.should include(*dirs.map { |dir| file(File.expand_path(dir)) }) + end + + it 'should return source directories as file task' do + @filter.from('src').sources.each { |source| source.should be_kind_of(Rake::FileTask) } + end + + it 'should return source directories as expanded path' do + @filter.from('src').sources.each { |source| source.to_s.should eql(File.expand_path('src')) } + end + + it 'should respond to :into and return self' do + @filter.into('target').should be(@filter) + end + + it 'should respond to :into and set target directory' do + lambda { @filter.into('src') }.should change { @filter.target } + @filter.into('target').target.should be(file(File.expand_path('target'))) + end + + it 'should return target directory as file task' do + @filter.into('target').target.should be_kind_of(Rake::FileTask) + end + + it 'should return target directory as expanded path' do + @filter.into('target').target.to_s.should eql(File.expand_path('target')) + end + + it 'should respond to :using and return self' do + @filter.using().should be(@filter) + end + + it 'should respond to :using and set mapping from the argument' do + mapping = { 'foo'=>'bar' } + lambda { @filter.using mapping }.should change { @filter.mapping }.to(mapping) + end + + it 'should respond to :using and set mapping from the block' do + @filter.using { 5 }.mapping.call.should be(5) + end + + it 'should respond to :include and return self' do + @filter.include('file').should be(@filter) + end + + it 'should respond to :include and use these inclusion patterns' do + @filter.from('src').into('target').include('file2', 'file3').run + Dir['target/*'].sort.should eql(['target/file2', 'target/file3']) + end + + it 'should respond to :include with regular expressions and use these inclusion patterns' do + @filter.from('src').into('target').include(/file[2|3]/).run + Dir['target/*'].sort.should eql(['target/file2', 'target/file3']) + end + + it 'should respond to :include with a Proc and use these inclusion patterns' do + @filter.from('src').into('target').include(lambda {|file| file[-1, 1].to_i%2 == 0}).run + Dir['target/*'].sort.should eql(['target/file2', 'target/file4']) + end + + it 'should respond to :include with a FileTask and use these inclusion patterns' do + @filter.from('src').into('target').include(file('target/file2'), file('target/file4')).run + Dir['target/*'].sort.should eql(['target/file2', 'target/file4']) + end + + it 'should respond to :exclude and return self' do + @filter.exclude('file').should be(@filter) + end + + it 'should respond to :exclude and use these exclusion patterns' do + @filter.from('src').into('target').exclude('file2', 'file3').run + Dir['target/*'].sort.should eql(['target/file1', 'target/file4']) + end + + it 'should respond to :exclude with regular expressions and use these exclusion patterns' do + @filter.from('src').into('target').exclude(/file[2|3]/).run + Dir['target/*'].sort.should eql(['target/file1', 'target/file4']) + end + + it 'should respond to :exclude with a Proc and use these exclusion patterns' do + @filter.from('src').into('target').exclude(lambda {|file| file[-1, 1].to_i%2 == 0}).run + Dir['target/*'].sort.should eql(['target/file1', 'target/file3']) + end + + it 'should respond to :exclude with a FileTask and use these exclusion patterns' do + @filter.from('src').into('target').exclude(file('target/file1'), file('target/file3')).run + Dir['target/*'].sort.should eql(['target/file2', 'target/file4']) + end + + it 'should respond to :exclude with a FileTask, use these exclusion patterns and depend on those tasks' do + file1 = false + file2 = false + @filter.from('src').into('target').exclude(file('target/file1').enhance { file1 = true }, file('target/file3').enhance {file2 = true }).run + Dir['target/*'].sort.should eql(['target/file2', 'target/file4']) + @filter.target.invoke + file1.should be_true + file2.should be_true + end + + it 'should copy files over' do + @filter.from('src').into('target').run + Dir['target/*'].sort.each do |file| + read(file).should eql("#{File.basename(file)} raw") + end + end + + it 'should copy dot files over' do + write 'src/.config', '# configuration' + @filter.from('src').into('target').run + read('target/.config').should eql('# configuration') + end + + it 'should copy empty directories as well' do + mkpath 'src/empty' + @filter.from('src').into('target').run + File.directory?('target/empty').should be_true + end + + it 'should copy files from multiple source directories' do + 4.upto(6) { |i| write "src2/file#{i}", "file#{i} raw" } + @filter.from('src', 'src2').into('target').run + Dir['target/*'].each do |file| + read(file).should eql("#{File.basename(file)} raw") + end + Dir['target/*'].should include(*(1..6).map { |i| "target/file#{i}" }) + end + + it 'should copy files recursively' do + mkpath 'src/path1' ; write 'src/path1/left' + mkpath 'src/path2' ; write 'src/path2/right' + @filter.from('src').into('target').run + Dir['target/**/*'].should include(*(1..4).map { |i| "target/file#{i}" }) + Dir['target/**/*'].should include('target/path1/left', 'target/path2/right') + end + + it 'should apply hash mapping using Maven style' do + 1.upto(4) { |i| write "src/file#{i}", "file#{i} with ${key1} and ${key2}" } + @filter.from('src').into('target').using('key1'=>'value1', 'key2'=>'value2').run + Dir['target/*'].each do |file| + read(file).should eql("#{File.basename(file)} with value1 and value2") + end + end + + it 'should apply hash mapping using Ant style' do + 1.upto(4) { |i| write "src/file#{i}", "file#{i} with @key1@ and @key2@" } + @filter.from('src').into('target').using(:ant, 'key1'=>'value1', 'key2'=>'value2').run + Dir['target/*'].each do |file| + read(file).should eql("#{File.basename(file)} with value1 and value2") + end + end + + it 'should apply hash mapping using Ruby style' do + 1.upto(4) { |i| write "src/file#{i}", "file#{i} with \#{key1} and \#{key2}" } + @filter.from('src').into('target').using(:ruby, 'key1'=>'value1', 'key2'=>'value2').run + Dir['target/*'].each do |file| + read(file).should eql("#{File.basename(file)} with value1 and value2") + end + end + + it 'should use erb when given a binding' do + 1.upto(4) { |i| write "src/file#{i}", "file#{i} with <%= key1 %> and <%= key2 * 2 %>" } + key1 = 'value1' + key2 = 12 + @filter.from('src').into('target').using(binding).run + Dir['target/*'].each do |file| + read(file).should eql("#{File.basename(file)} with value1 and 24") + end + end + + it 'should apply hash mapping using erb' do + 1.upto(4) { |i| write "src/file#{i}", "file#{i} with <%= key1 %> and <%= key2 * 2 %>" } + @filter.from('src').into('target').using(:erb, 'key1'=>'value1', 'key2'=> 12).run + Dir['target/*'].each do |file| + read(file).should eql("#{File.basename(file)} with value1 and 24") + end + end + + it 'should use an object binding when using erb' do + 1.upto(4) { |i| write "src/file#{i}", "file#{i} with <%= key1 %> and <%= key2 * 2 %>" } + obj = Struct.new(:key1, :key2).new('value1', 12) + @filter.from('src').into('target').using(:erb, obj).run + Dir['target/*'].each do |file| + read(file).should eql("#{File.basename(file)} with value1 and 24") + end + end + + it 'should use a given block context when using erb' do + 1.upto(4) { |i| write "src/file#{i}", "file#{i} with <%= key1 %> and <%= key2 * 2 %>" } + key1 = 'value1' + key2 = 12 + @filter.from('src').into('target').using(:erb){}.run + Dir['target/*'].each do |file| + read(file).should eql("#{File.basename(file)} with value1 and 24") + end + end + + it 'should using Maven mapper by default' do + @filter.using('key1'=>'value1', 'key2'=>'value2').mapper.should eql(:maven) + end + + it 'should apply hash mapping with boolean values' do + write "src/file", "${key1} and ${key2}" + @filter.from('src').into('target').using(:key1=>true, :key2=>false).run + read("target/file").should eql("true and false") + end + + it 'should apply hash mapping using regular expression' do + 1.upto(4) { |i| write "src/file#{i}", "file#{i} with #key1# and #key2#" } + @filter.from('src').into('target').using(/#(.*?)#/, 'key1'=>'value1', 'key2'=>'value2').run + Dir['target/*'].each do |file| + read(file).should eql("#{File.basename(file)} with value1 and value2") + end + end + + it 'should apply proc mapping' do + @filter.from('src').into('target').using { |file, content| 'proc mapped' }.run + Dir['target/*'].each do |file| + read(file).should eql('proc mapped') + end + end + + it 'should apply proc mapping with relative file name' do + @filter.from('src').into('target').using { |file, content| file.should =~ /^file\d$/ }.run + end + + it 'should apply proc mapping with file content' do + @filter.from('src').into('target').using { |file, content| content.should =~ /^file\d raw/ }.run + end + + it 'should make target directory' do + lambda { @filter.from('src').into('target').run }.should change { File.exist?('target') }.to(true) + end + + it 'should touch target directory' do + mkpath 'target' ; File.utime @early, @early, 'target' + @filter.from('src').into('target').run + File.stat('target').mtime.should be_close(Time.now, 10) + end + + it 'should not touch target directory unless running' do + mkpath 'target' ; File.utime @early, @early, 'target' + @filter.from('src').into('target').exclude('*').run + File.mtime('target').should be_close(@early, 10) + end + + it 'should run only on new files' do + # Make source files older so they're not copied twice. + Dir['src/**/*'].each { |file| File.utime(@early, @early, file) } + @filter.from('src').into('target').run + @filter.from('src').into('target').using { |file, content| file.should eql('file2') }.run + end + + it 'should return true when run copies any files' do + @filter.from('src').into('target').run.should be(true) + end + + it 'should return false when run does not copy any files' do + # Make source files older so they're not copied twice. + Dir['src/**/*'].each { |file| File.utime(@early, @early, file) } + @filter.from('src').into('target').run + @filter.from('src').into('target').run.should be(false) + end + + it 'should fail if source directory doesn\'t exist' do + lambda { Filter.new.from('srced').into('target').run }.should raise_error(RuntimeError, /doesn't exist/) + end + + it 'should fail is target directory not set' do + lambda { Filter.new.from('src').run }.should raise_error(RuntimeError, /No target directory/) + end + + it 'should copy read-only files as writeable' do + Dir['src/*'].each { |file| File.chmod(0444, file) } + @filter.from('src').into('target').run + Dir['target/*'].sort.each do |file| + File.readable?(file).should be_true + File.writable?(file).should be_true + (File.stat(file).mode & 0o200).should == 0o200 + end + end + + it 'should preserve mode bits except readable' do + # legacy: pending "Pending the release of the fix for JRUBY-4927" if RUBY_PLATFORM =~ /java/ + Dir['src/*'].each { |file| File.chmod(0o755, file) } + @filter.from('src').into('target').run + Dir['target/*'].sort.each do |file| + (File.stat(file).mode & 0o755).should == 0o755 + end + end +end + +describe Filter::Mapper do + + module MooMapper + def moo_config(*args, &block) + raise ArgumentError, "Expected moo block" unless block_given? + { :moos => args, :callback => block } + end + + def moo_transform(content, path = nil) + content.gsub(/moo+/i) do |str| + moos = yield :moos # same than config[:moos] + moo = moos[str.size - 3] || str + config[:callback].call(moo) + end + end + end + + it 'should allow plugable mapping types' do + mapper = Filter::Mapper.new.extend(MooMapper) + mapper.using(:moo, 'ooone', 'twoo') do |str| + i = nil; str.capitalize.gsub(/\w/) { |s| s.send( (i = !i) ? 'upcase' : 'downcase' ) } + end + mapper.transform('Moo cow, mooo cows singing mooooo').should == 'OoOnE cow, TwOo cows singing MoOoOo' + end + +end + +describe Buildr.method(:options) do + it 'should return an Options object' do + options.should be_kind_of(Options) + end + + it 'should return an Options object each time' do + options.should be(options) + end + + it 'should return the same Options object when called on Object, Buildr or Project' do + options.should be(Buildr.options) + define('foo') { options.should be(Buildr.options) } + end +end + +describe Buildr::Options, 'proxy.exclude' do + before do + options.proxy.http = 'http://myproxy:8080' + @domain = 'domain' + @host = "host.#{@domain}" + @uri = URI("http://#{@host}") + @no_proxy_args = [@host, 80] + @proxy_args = @no_proxy_args + ['myproxy', 8080, nil, nil] + @http = mock('http') + @http.stub!(:request).and_return(Net::HTTPNotModified.new(nil, nil, nil)) + end + + it 'should be an array' do + options.proxy.exclude.should be_empty + options.proxy.exclude = @domain + options.proxy.exclude.should include(@domain) + end + + it 'should support adding to array' do + options.proxy.exclude << @domain + options.proxy.exclude.should include(@domain) + end + + it 'should support resetting array' do + options.proxy.exclude = @domain + options.proxy.exclude = nil + options.proxy.exclude.should be_empty + end + + it 'should use proxy when not excluded' do + Net::HTTP.should_receive(:new).with(*@proxy_args).and_return(@http) + @uri.read :proxy=>options.proxy + end + + it 'should use proxy unless excluded' do + options.proxy.exclude = "not.#{@domain}" + Net::HTTP.should_receive(:new).with(*@proxy_args).and_return(@http) + @uri.read :proxy=>options.proxy + end + + it 'should not use proxy if excluded' do + options.proxy.exclude = @host + Net::HTTP.should_receive(:new).with(*@no_proxy_args).and_return(@http) + @uri.read :proxy=>options.proxy + end + + it 'should support multiple host names' do + options.proxy.exclude = ['optimus', 'prime'] + Net::HTTP.should_receive(:new).with('optimus', 80).and_return(@http) + URI('http://optimus').read :proxy=>options.proxy + Net::HTTP.should_receive(:new).with('prime', 80).and_return(@http) + URI('http://prime').read :proxy=>options.proxy + Net::HTTP.should_receive(:new).with('bumblebee', *@proxy_args[1..-1]).and_return(@http) + URI('http://bumblebee').read :proxy=>options.proxy + end + + it 'should support glob pattern on host name' do + options.proxy.exclude = "*.#{@domain}" + Net::HTTP.should_receive(:new).with(*@no_proxy_args).and_return(@http) + @uri.read :proxy=>options.proxy + end +end + + +describe Hash, '::from_java_properties' do + it 'should return hash' do + hash = Hash.from_java_properties(<<-PROPS) +name1=value1 +name2=value2 + PROPS + hash.should == {'name1'=>'value1', 'name2'=>'value2'} + end + + it 'should ignore comments and empty lines' do + hash = Hash.from_java_properties(<<-PROPS) + +name1=value1 + +name2=value2 + +PROPS + hash.should == {'name1'=>'value1', 'name2'=>'value2'} + end + + it 'should allow multiple lines' do + hash = Hash.from_java_properties(<<-PROPS) +name1=start\ + end + +name2=first\ + second\ + third + +PROPS + hash.should == {'name1'=>'start end', 'name2'=>'first second third'} + end + + it 'should handle \t, \r, \n and \f' do + hash = Hash.from_java_properties(<<-PROPS) + +name1=with\tand\r + +name2=with\\nand\f + +name3=double\\\\hash +PROPS + hash.should == {'name1'=>"with\tand", 'name2'=>"with\nand\f", 'name3'=>'double\hash'} + end + + it 'should ignore whitespace' do + hash = Hash.from_java_properties('name1 = value1') + hash.should == {'name1'=>'value1'} + end +end + + +describe Hash, '#to_java_properties' do + it 'should return name/value pairs' do + props = {'name1'=>'value1', 'name2'=>'value2'}.to_java_properties + props.split("\n").size.should be(2) + props.split("\n").should include('name1=value1') + props.split("\n").should include('name2=value2') + end + + it 'should handle \t, \r, \n and \f' do + props = {'name1'=>"with\tand\r", 'name2'=>"with\nand\f", 'name3'=>'double\hash'}.to_java_properties + props.split("\n").should include("name1=with\\tand\\r") + props.split("\n").should include("name2=with\\nand\\f") + props.split("\n").should include("name3=double\\\\hash") + end +end diff --git a/buildr/spec/core/compile_spec.rb b/buildr/spec/core/compile_spec.rb new file mode 100644 index 0000000..05a45d4 --- /dev/null +++ b/buildr/spec/core/compile_spec.rb @@ -0,0 +1,669 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +module CompilerHelper + def compile_task + @compile_task ||= define('foo').compile.using(:javac) + end + + def compile_task_without_compiler + @compile_task ||= define('foo').compile + end + + def file_task + @file_taks ||= define('bar').file('src') + end + + def sources + @sources ||= ['Test1.java', 'Test2.java'].map { |f| File.join('src/main/java/thepackage', f) }. + each { |src| write src, "package thepackage; class #{src.pathmap('%n')} {}" } + end + + def jars + @jars ||= begin + write 'jars/src/main/java/Dependency.java', 'class Dependency { }' + define 'jars', :version=>'1.0', :base_dir => 'jars' do + package(:jar, :id=>'jar1') + package(:jar, :id=>'jar2') + end + project('jars').packages.map(&:to_s) + end + end +end + + +describe Buildr::CompileTask do + include CompilerHelper + + it 'should respond to from() and return self' do + compile_task.from(sources).should be(compile_task) + end + + it 'should respond to from() with FileTask having no compiler set and return self' do + compile_task_without_compiler.from(file_task).should be(compile_task) + end + + it 'should respond to from() and add sources' do + compile_task.from sources, File.dirname(sources.first) + compile_task.sources.should == sources + [File.dirname(sources.first)] + end + + it 'should respond to with() and return self' do + compile_task.with('test.jar').should be(compile_task) + end + + it 'should respond to with() and add dependencies' do + jars = (1..3).map { |i| "test#{i}.jar" } + compile_task.with *jars + compile_task.dependencies.should == artifacts(jars) + end + + it 'should respond to into() and return self' do + compile_task.into('code').should be(compile_task) + end + + it 'should respond to into() and create file task' do + compile_task.from(sources).into('code') + lambda { file('code').invoke }.should run_task('foo:compile') + end + + it 'should respond to using() and return self' do + compile_task.using(:source=>'1.4').should eql(compile_task) + end + + it 'should respond to using() and set options' do + compile_task.using(:source=>'1.4', 'target'=>'1.5') + compile_task.options.source.should eql('1.4') + compile_task.options.target.should eql('1.5') + end + + it 'should attempt to identify compiler' do + Compiler.compilers.first.should_receive(:applies_to?).at_least(:once) + define('foo') + end + + it 'should only support existing compilers' do + lambda { define('foo') { compile.using(:unknown) } }.should raise_error(ArgumentError, /unknown compiler/i) + end + + it 'should allow overriding the guessed compiler' do + write "src/main/java/com/example/Hello.java", "" + old_compiler = nil + new_compiler = nil + define('foo') { + old_compiler = compile.compiler + compile.using(:scalac) + new_compiler = compile.compiler + } + old_compiler.should == :javac + new_compiler.should == :scalac + end +end + + +describe Buildr::CompileTask, '#compiler' do + it 'should be nil if no compiler identifier' do + define('foo').compile.compiler.should be_nil + end + + it 'should return the selected compiler' do + define('foo') { compile.using(:javac) } + project('foo').compile.compiler.should eql(:javac) + end + + it 'should attempt to identify compiler if sources are specified' do + define 'foo' do + Compiler.compilers.first.should_receive(:applies_to?).at_least(:once) + compile.from('sources').compiler + end + end + + it 'should allow supressing compilation' do + write 'src/main/java/package/Test.java', 'class Test {}' + define 'foo' do + compile.sources.clear + end + project('foo').compile.invoke + Dir['target/classes/*'].should be_empty + end +end + + +describe Buildr::CompileTask, '#language' do + it 'should be nil if no compiler identifier' do + define('foo').compile.language.should be_nil + end + + it 'should return the appropriate language' do + define('foo') { compile.using(:javac) } + project('foo').compile.language.should eql(:java) + end +end + + +describe Buildr::CompileTask, '#sources' do + include CompilerHelper + + it 'should be empty if no sources in default directory' do + compile_task.sources.should be_empty + end + + it 'should point to default directory if it contains sources' do + write 'src/main/java', '' + compile_task.sources.first.should point_to_path('src/main/java') + end + + it 'should be an array' do + compile_task.sources += sources + compile_task.sources.should == sources + end + + it 'should allow files' do + compile_task.from(sources).into('classes').invoke + sources.each { |src| file(src.pathmap('classes/thepackage/%n.class')).should exist } + end + + it 'should allow directories' do + compile_task.from(File.dirname(sources.first)).into('classes').invoke + sources.each { |src| file(src.pathmap('classes/thepackage/%n.class')).should exist } + end + + it 'should allow tasks' do + lambda { compile_task.from(file(sources.first)).into('classes').invoke }.should run_task('foo:compile') + end + + it 'should act as prerequisites' do + file('src2') { |task| task('prereq').invoke ; mkpath task.name } + lambda { compile_task.from('src2').into('classes').invoke }.should run_task('prereq') + end +end + + +describe Buildr::CompileTask, '#dependencies' do + include CompilerHelper + + it 'should be empty' do + compile_task.dependencies.should be_empty + end + + it 'should be an array' do + compile_task.dependencies += jars + compile_task.dependencies.should == jars + end + + it 'should allow files' do + compile_task.from(sources).with(jars).into('classes').invoke + sources.each { |src| file(src.pathmap('classes/thepackage/%n.class')).should exist } + end + + it 'should allow tasks' do + compile_task.from(sources).with(file(jars.first)).into('classes').invoke + end + + it 'should allow artifacts' do + artifact('group:id:jar:1.0') { |task| mkpath File.dirname(task.to_s) ; cp jars.first.to_s, task.to_s }.enhance jars + compile_task.from(sources).with('group:id:jar:1.0').into('classes').invoke + end + + it 'should allow projects' do + define('bar', :version=>'1', :group=>'self') { package :jar } + compile_task.with project('bar') + compile_task.dependencies.should == project('bar').packages + end + + it 'should be accessible as classpath up to version 1.5 since it was deprecated in version 1.3' do + Buildr::VERSION.should < '1.5' + lambda { compile_task.classpath = jars }.should change(compile_task, :dependencies).to(jars) + lambda { compile_task.dependencies = [] }.should change(compile_task, :classpath).to([]) + end + +end + + +describe Buildr::CompileTask, '#target' do + include CompilerHelper + + it 'should be a file task' do + compile_task.from(@sources).into('classes') + compile_task.target.should be_kind_of(Rake::FileTask) + end + + it 'should accept a task' do + task = file('classes') + compile_task.into(task).target.should be(task) + end + + it 'should create dependency in file task when set' do + compile_task.from(sources).into('classes') + lambda { file('classes').invoke }.should run_task('foo:compile') + end +end + + +describe Buildr::CompileTask, '#options' do + include CompilerHelper + + it 'should have getter and setter methods' do + compile_task.options.foo = 'bar' + compile_task.options.foo.should eql('bar') + end + + it 'should have bracket accessors' do + compile_task.options[:foo] = 'bar' + compile_task.options[:foo].should eql('bar') + end + + it 'should map from bracket accessor to get/set accessor' do + compile_task.options[:foo] = 'bar' + compile_task.options.foo.should eql('bar') + end + + it 'should be independent of parent' do + define 'foo' do + compile.using(:javac, :source=>'1.4') + define 'bar' do + compile.using(:javac, :source=>'1.5') + end + end + project('foo').compile.options.source.should eql('1.4') + project('foo:bar').compile.options.source.should eql('1.5') + end +end + + +describe Buildr::CompileTask, '#invoke' do + include CompilerHelper + + it 'should compile into target directory' do + compile_task.from(sources).into('code').invoke + Dir['code/thepackage/*.class'].should_not be_empty + end + + it 'should compile only once' do + compile_task.from(sources) + lambda { compile_task.target.invoke }.should run_task('foo:compile') + lambda { compile_task.invoke }.should_not run_task('foo:compile') + end + + it 'should compile if there are source files to compile' do + lambda { compile_task.from(sources).invoke }.should run_task('foo:compile') + end + + it 'should not compile unless there are source files to compile' do + lambda { compile_task.invoke }.should_not run_task('foo:compile') + end + + it 'should require source file or directory to exist' do + lambda { compile_task.from('empty').into('classes').invoke }.should raise_error(RuntimeError, /Don't know how to build/) + end + + it 'should run all source files as prerequisites' do + mkpath 'src' + file('src').should_receive :invoke_prerequisites + compile_task.from('src').invoke + end + + it 'should require dependencies to exist' do + lambda { compile_task.from(sources).with('no-such.jar').into('classes').invoke }.should \ + raise_error(RuntimeError, /Don't know how to build/) + end + + it 'should run all dependencies as prerequisites' do + file(File.expand_path('no-such.jar')) { |task| task('prereq').invoke } + lambda { compile_task.from(sources).with('no-such.jar').into('classes').invoke }.should run_tasks(['prereq', 'foo:compile']) + end + + it 'should force compilation if no target' do + lambda { compile_task.from(sources).invoke }.should run_task('foo:compile') + end + + it 'should force compilation if target empty' do + time = Time.now + mkpath compile_task.target.to_s + File.utime(time - 1, time - 1, compile_task.target.to_s) + lambda { compile_task.from(sources).invoke }.should run_task('foo:compile') + end + + it 'should force compilation if sources newer than compiled' do + # Simulate class files that are older than source files. + time = Time.now + sources.each { |src| File.utime(time + 1, time + 1, src) } + sources.map { |src| src.pathmap("#{compile_task.target}/thepackage/%n.class") }. + each { |kls| write kls ; File.utime(time, time, kls) } + File.utime(time - 1, time - 1, project('foo').compile.target.to_s) + lambda { compile_task.from(sources).invoke }.should run_task('foo:compile') + end + + it 'should not force compilation if sources older than compiled' do + # When everything has the same timestamp, nothing is compiled again. + time = Time.now + sources.map { |src| src.pathmap("#{compile_task.target}/thepackage/%n.class") }. + each { |kls| write kls ; File.utime(time, time, kls) } + lambda { compile_task.from(sources).invoke }.should_not run_task('foo:compile') + end + + it 'should not force compilation if dependencies older than compiled' do + jars; project('jars').task("package").invoke + time = Time.now + jars.each { |jar| File.utime(time - 1 , time - 1, jar) } + sources.map { |src| File.utime(time, time, src); src.pathmap("#{compile_task.target}/thepackage/%n.class") }. + each { |kls| write kls ; File.utime(time, time, kls) } + lambda { compile_task.from(sources).with(jars).invoke }.should_not run_task('foo:compile') + end + + it 'should force compilation if dependencies newer than compiled' do + jars; project('jars').task("package").invoke + # On my machine the times end up the same, so need to push dependencies in the past. + time = Time.now + sources.map { |src| src.pathmap("#{compile_task.target}/thepackage/%n.class") }. + each { |kls| write kls ; File.utime(time - 1, time - 1, kls) } + File.utime(time - 1, time - 1, project('foo').compile.target.to_s) + jars.each { |jar| File.utime(time + 1, time + 1, jar) } + lambda { compile_task.from(sources).with(jars).invoke }.should run_task('foo:compile') + end + + it 'should timestamp target directory if specified' do + time = Time.now - 10 + mkpath compile_task.target.to_s + File.utime(time, time, compile_task.target.to_s) + compile_task.timestamp.should be_close(time, 1) + end + + it 'should touch target if anything compiled' do + mkpath compile_task.target.to_s + File.utime(Time.now - 10, Time.now - 10, compile_task.target.to_s) + compile_task.from(sources).invoke + File.stat(compile_task.target.to_s).mtime.should be_close(Time.now, 2) + end + + it 'should not touch target if nothing compiled' do + mkpath compile_task.target.to_s + File.utime(Time.now - 10, Time.now - 10, compile_task.target.to_s) + compile_task.invoke + File.stat(compile_task.target.to_s).mtime.should be_close(Time.now - 10, 2) + end + + it 'should not touch target if failed to compile' do + mkpath compile_task.target.to_s + File.utime(Time.now - 10, Time.now - 10, compile_task.target.to_s) + write 'failed.java', 'not a class' + suppress_stdout { compile_task.from('failed.java').invoke rescue nil } + File.stat(compile_task.target.to_s).mtime.should be_close(Time.now - 10, 2) + end + + it 'should complain if source directories and no compiler selected' do + mkpath 'sources' + define 'bar' do + lambda { compile.from('sources').invoke }.should raise_error(RuntimeError, /no compiler selected/i) + end + end + + it 'should not unnecessarily recompile files explicitly added to compile list (BUILDR-611)' do + mkpath 'src/other' + write 'src/other/Foo.java', 'package foo; public class Foo {}' + compile_task.from FileList['src/other/**.java'] + mkpath 'target/classes/foo' + touch 'target/classes/foo/Foo.class' + File.utime(Time.now - 10, Time.now - 10, compile_task.target.to_s) + compile_task.invoke + File.stat(compile_task.target.to_s).mtime.should be_close(Time.now - 10, 2) + end +end + + +shared_examples_for 'accessor task' do + it 'should return a task' do + define('foo').send(@task_name).should be_kind_of(Rake::Task) + end + + it 'should always return the same task' do + task_name, task = @task_name, nil + define('foo') { task = self.send(task_name) } + project('foo').send(task_name).should be(task) + end + + it 'should be unique for the project' do + define('foo') { define 'bar' } + project('foo').send(@task_name).should_not eql(project('foo:bar').send(@task_name)) + end + + it 'should be named after the project' do + define('foo') { define 'bar' } + project('foo:bar').send(@task_name).name.should eql("foo:bar:#{@task_name}") + end +end + + +describe Project, '#compile' do + before { @task_name = 'compile' } + it_should_behave_like 'accessor task' + + it 'should return a compile task' do + define('foo').compile.should be_instance_of(CompileTask) + end + + it 'should accept sources and add to source list' do + define('foo') { compile('file1', 'file2') } + project('foo').compile.sources.should include('file1', 'file2') + end + + it 'should accept block and enhance task' do + write 'src/main/java/Test.java', 'class Test {}' + action = task('action') + define('foo') { compile { action.invoke } } + lambda { project('foo').compile.invoke }.should run_tasks('foo:compile', action) + end + + it 'should execute resources task' do + define 'foo' + lambda { project('foo').compile.invoke }.should run_task('foo:resources') + end + + it 'should be recursive' do + write 'bar/src/main/java/Test.java', 'class Test {}' + define('foo') { define 'bar' } + lambda { project('foo').compile.invoke }.should run_task('foo:bar:compile') + end + + it 'should be a local task' do + write 'bar/src/main/java/Test.java', 'class Test {}' + define('foo') { define 'bar' } + lambda do + in_original_dir project('foo:bar').base_dir do + task('compile').invoke + end + end.should run_task('foo:bar:compile').but_not('foo:compile') + end + + it 'should run from build task' do + write 'bar/src/main/java/Test.java', 'class Test {}' + define('foo') { define 'bar' } + lambda { task('build').invoke }.should run_task('foo:bar:compile') + end + + it 'should clean after itself' do + mkpath 'code' + define('foo') { compile.into('code') } + lambda { task('clean').invoke }.should change { File.exist?('code') }.to(false) + end +end + + +describe Project, '#resources' do + before { @task_name = 'resources' } + it_should_behave_like 'accessor task' + + it 'should return a resources task' do + define('foo').resources.should be_instance_of(ResourcesTask) + end + + it 'should provide a filter' do + define('foo').resources.filter.should be_instance_of(Filter) + end + + it 'should include src/main/resources as source directory' do + write 'src/main/resources/test' + define('foo').resources.sources.first.should point_to_path('src/main/resources') + end + + it 'should include src/main/resources directory only if it exists' do + define('foo').resources.sources.should be_empty + end + + it 'should accept prerequisites' do + tasks = ['task1', 'task2'].each { |name| task(name) } + define('foo') { resources 'task1', 'task2' } + lambda { project('foo').resources.invoke }.should run_tasks('task1', 'task2') + end + + it 'should respond to from and add additional sources' do + write 'src/main/resources/original' + write 'extra/spicy' + define('foo') { resources.from 'extra' } + project('foo').resources.invoke + FileList['target/resources/*'].sort.should == ['target/resources/original', 'target/resources/spicy'] + end + + it 'should pass include pattern to filter' do + 3.times { |i| write "src/main/resources/test#{i + 1}" } + define('foo') { resources.include('test2') } + project('foo').resources.invoke + FileList['target/resources/*'].should == ['target/resources/test2'] + end + + it 'should pass exclude pattern to filter' do + 3.times { |i| write "src/main/resources/test#{i + 1}" } + define('foo') { resources.exclude('test2') } + project('foo').resources.invoke + FileList['target/resources/*'].sort.should == ['target/resources/test1', 'target/resources/test3'] + end + + it 'should accept block and enhance task' do + action = task('action') + define('foo') { resources { action.invoke } } + lambda { project('foo').resources.invoke }.should run_tasks('foo:resources', action) + end + + it 'should set target directory to target/resources' do + write 'src/main/resources/foo' + define('foo').resources.target.to_s.should point_to_path('target/resources') + end + + it 'should use provided target directoy' do + define('foo') { resources.filter.into('the_resources') } + project('foo').resources.target.to_s.should point_to_path('the_resources') + end + + it 'should create file task for target directory' do + write 'src/main/resources/foo' + define 'foo' + project('foo').file('target/resources').invoke + file('target/resources/foo').should exist + end + + it 'should copy resources to target directory' do + write 'src/main/resources/foo', 'Foo' + define('foo').compile.invoke + file('target/resources/foo').should contain('Foo') + end + + it 'should copy new resources to target directory' do + time = Time.now + mkdir_p 'target/resources' + File.utime(time-1, time-1, 'target/resources') + + write 'src/main/resources/foo', 'Foo' + + define('foo') + project('foo').file('target/resources').invoke + file('target/resources/foo').should exist + end + + it 'should copy updated resources to target directory' do + time = Time.now + mkdir_p 'target/resources' + write 'target/resources/foo', 'Foo' + File.utime(time-1, time-1, 'target/resources') + File.utime(time-1, time-1, 'target/resources/foo') + + write 'src/main/resources/foo', 'Foo2' + define('foo') + project('foo').file('target/resources').invoke + file('target/resources/foo').should contain('Foo2') + end + + it 'should not create target directory unless there are resources' do + define('foo').compile.invoke + file('target/resources').should_not exist + end + + it 'should run from target/resources' do + write 'src/main/resources/test' + define('foo') + lambda { project('foo').resources.target.invoke }.should change { File.exist?('target/resources/test') }.to(true) + end + + it 'should not be recursive' do + define('foo') { define 'bar' } + lambda { project('foo').resources.invoke }.should_not run_task('foo:bar:resources') + end + + it 'should use current profile for filtering' do + write 'profiles.yaml', <<-YAML + development: + filter: + foo: bar + test: + filter: + foo: baz + YAML + write 'src/main/resources/foo', '${foo}' + define('foo').compile.invoke + file('target/resources/foo').should contain('bar') + end + + it 'should use current profile as default for filtering' do + write 'profiles.yaml', <<-YAML + development: + filter: + foo: bar + YAML + write 'src/main/resources/foo', '${foo} ${baz}' + define('foo') do + resources.filter.using 'baz' => 'qux' + end + project('foo').compile.invoke + file('target/resources/foo').should contain('bar qux') + end + + it 'should allow clearing default filter mapping' do + write 'profiles.yaml', <<-YAML + development: + filter: + foo: bar + YAML + write 'src/main/resources/foo', '${foo} ${baz}' + define('foo') do + resources.filter.mapping.clear + resources.filter.using 'baz' => 'qux' + end + project('foo').compile.invoke + file('target/resources/foo').should contain('${foo} qux') + end +end diff --git a/buildr/spec/core/doc_spec.rb b/buildr/spec/core/doc_spec.rb new file mode 100644 index 0000000..b87fcfe --- /dev/null +++ b/buildr/spec/core/doc_spec.rb @@ -0,0 +1,195 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Project, '#doc' do + def sources + @sources ||= (1..3).map { |i| "Test#{i}" }. + each { |name| write "src/main/java/foo/#{name}.java", "package foo; public class #{name}{}" }. + map { |name| "src/main/java/foo/#{name}.java" } + end + + it 'should return the project\'s Javadoc task' do + define('foo') { compile.using(:javac) } + project('foo').doc.name.should eql('foo:doc') + end + + it 'should return a DocTask' do + define('foo') { compile.using(:javac) } + project('foo').doc.should be_kind_of(Doc::DocTask) + end + + it 'should set target directory to target/doc' do + define 'foo' do + compile.using(:javac) + doc.target.to_s.should point_to_path('target/doc') + end + end + + it 'should create file task for target directory' do + define 'foo' do + compile.using(:javac) + doc.should_receive(:invoke_prerequisites) + end + project('foo').file('target/doc').invoke + end + + it 'should respond to into() and return self' do + define 'foo' do + compile.using(:javac) + doc.into('docs').should be(doc) + end + end + + it 'should respond to into() and change target directory' do + define 'foo' do + compile.using(:javac) + doc.into('docs') + doc.should_receive(:invoke_prerequisites) + end + file('docs').invoke + end + + it 'should respond to from() and return self' do + task = nil + define('foo') do + compile.using(:javac) + task = doc.from('srcs') + end + task.should be(project('foo').doc) + end + + it 'should respond to from() and add sources' do + define 'foo' do + compile.using(:javac) + doc.from('srcs').should be(doc) + end + end + + it 'should respond to from() and add file task' do + define 'foo' do + compile.using(:javac) + doc.from('srcs').should be(doc) + end + project('foo').doc.source_files.first.should point_to_path('srcs') + end + + it 'should respond to from() and add project\'s sources and dependencies' do + write 'bar/src/main/java/Test.java' + define 'foo' do + compile.using(:javac) + define('bar') { compile.using(:javac).with 'group:id:jar:1.0' } + doc.from project('foo:bar') + end + project('foo').doc.source_files.first.should point_to_path('bar/src/main/java/Test.java') + project('foo').doc.classpath.map(&:to_spec).should include('group:id:jar:1.0') + end + + it 'should generate docs from project' do + sources + define('foo') { compile.using(:javac) } + project('foo').doc.source_files.sort.should == sources.sort.map { |f| File.expand_path(f) } + end + + it 'should include compile dependencies' do + define('foo') { compile.using(:javac).with 'group:id:jar:1.0' } + project('foo').doc.classpath.map(&:to_spec).should include('group:id:jar:1.0') + end + + it 'should respond to include() and return self' do + define 'foo' do + compile.using(:javac) + doc.include('srcs').should be(doc) + end + end + + it 'should respond to include() and add files' do + included = sources.first + define 'foo' do + compile.using(:javac) + doc.include included + end + project('foo').doc.source_files.should include(included) + end + + it 'should respond to exclude() and return self' do + define 'foo' do + compile.using(:javac) + doc.exclude('srcs').should be(doc) + end + end + + it 'should respond to exclude() and ignore files' do + excluded = sources.first + define 'foo' do + compile.using(:javac) + doc.exclude excluded + end + sources + project('foo').doc.source_files.sort.should == sources[1..-1].map { |f| File.expand_path(f) } + end + + it 'should respond to using() and return self' do + define 'foo' do + compile.using(:javac) + doc.using(:foo=>'Fooing').should be(doc) + end + end + + it 'should respond to using() and accept options' do + define 'foo' do + compile.using(:javac) + doc.using :foo=>'Fooing' + end + project('foo').doc.options[:foo].should eql('Fooing') + end + + it 'should produce documentation' do + sources + define('foo') { compile.using(:javac) } + project('foo').doc.invoke + (1..3).map { |i| "target/doc/foo/Test#{i}.html" }.each { |f| file(f).should exist } + end + + it 'should fail on error' do + write 'Test.java', 'class Test {}' + define 'foo' do + compile.using(:javac) + doc.include 'Test.java' + end + lambda { project('foo').doc.invoke }.should raise_error(RuntimeError, /Failed to generate Javadocs/) + end + + it 'should be local task' do + define 'foo' do + define('bar') { compile.using(:javac) } + end + project('foo:bar').doc.should_receive(:invoke_prerequisites) + in_original_dir(project('foo:bar').base_dir) { task('doc').invoke } + end + + it 'should not recurse' do + define 'foo' do + compile.using(:javac) + define('bar') { compile.using(:javac) } + end + project('foo:bar').doc.should_not_receive(:invoke_prerequisites) + project('foo').doc.invoke + end +end + diff --git a/buildr/spec/core/extension_spec.rb b/buildr/spec/core/extension_spec.rb new file mode 100755 index 0000000..7f9978b --- /dev/null +++ b/buildr/spec/core/extension_spec.rb @@ -0,0 +1,201 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Extension do + + before do + @saved_modules = Project.class_eval { @extension_modules }.dup + @saved_callbacks = Project.class_eval { @global_callbacks }.dup + end + + after do + modules = @saved_modules + callbacks = @saved_callbacks + Project.class_eval do + @global_callbacks = callbacks + @extension_modules = modules + end + end + + it 'should call Extension.first_time during include' do + TestExtension.should_receive(:first_time_called).once + class Buildr::Project + include TestExtension + end + end + + it 'should call before_define and after_define in order when project is defined' do + begin + TestExtension.callback do |extension| + extension.should_receive(:before_define_called).once.ordered + extension.should_receive(:after_define_called).once.ordered + end + class Buildr::Project + include TestExtension + end + define('foo') + ensure + TestExtension.callback { |ignore| } + end + end + + it 'should call before_define and after_define for each project defined' do + begin + extensions = 0 + TestExtension.callback do |extension| + extensions += 1 + extension.should_receive(:before_define_called).once.ordered + extension.should_receive(:after_define_called).once.ordered + end + class Buildr::Project + include TestExtension + end + define('foo') + define('bar') + extensions.should equal(2) + ensure + TestExtension.callback { |ignore| } + end + end + + it 'should call before_define callbacks in dependency order' do + class Buildr::Project + include ExtensionOneTwo + include ExtensionThreeFour + end + define('foo') + project('foo').before_order.should == [ :one, :two, :three, :four ] + project('foo').after_order.should == [ :four, :three, :two, :one ] + end + + it 'should call before_define callbacks when extending project' do + define('foo') do + extend ExtensionOneTwo + extend ExtensionThreeFour + end + project('foo').before_order.should == [ :two, :one, :four, :three ] + project('foo').after_order.should == [ :four, :three, :two, :one ] + end + + it 'should raise error when including if callback dependencies cannot be satisfied' do + class Buildr::Project + include ExtensionOneTwo # missing ExtensionThreeFour + end + lambda { define('foo') }.should raise_error + end + + it 'should raise error when extending if callback dependencies cannot be satisfied' do + lambda { + define('foo') do |project| + extend ExtensionOneTwo # missing ExtensionThreeFour + end + }.should raise_error + end + + it 'should ignore dependencies when extending project' do + define('bar') do |project| + extend ExtensionThreeFour # missing ExtensionOneTwo + end + project('bar').before_order.should == [:four, :three] + project('bar').after_order.should == [:four, :three] + end +end + +module TestExtension + include Extension + + def initialize(*args) + super + # callback is used to obtain extension instance created by buildr + @@callback.call(self) if @@callback + end + + def self.callback(&block) + @@callback = block + end + + first_time do + self.first_time_called() + end + + before_define do |project| + project.before_define_called() + end + + after_define do |project| + project.after_define_called() + end + + def self.first_time_called() + end + +end + +module BeforeAfter + def before_order + @before_order ||= [] + end + + def after_order + @after_order ||= [] + end +end + +module ExtensionOneTwo + include Extension, BeforeAfter + + before_define(:two => :one) do |project| + project.before_order << :two + end + + before_define(:one) do |project| + project.before_order << :one + end + + after_define(:one => :two) do |project| + project.after_order << :one + end + + after_define(:two => :three) do |project| + project.after_order << :two + end +end + +module ExtensionThreeFour + include Extension, BeforeAfter + + before_define(:three => :two) + + before_define(:four => :three) do |project| + project.before_order << :four + end + + before_define(:three) do |project| + project.before_order << :three + end + + after_define(:three => :four) do |project| + project.after_order << :three + end + + after_define(:four) do |project| + project.after_order << :four + end +end + diff --git a/buildr/spec/core/generate_spec.rb b/buildr/spec/core/generate_spec.rb new file mode 100644 index 0000000..fc754cb --- /dev/null +++ b/buildr/spec/core/generate_spec.rb @@ -0,0 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Buildr::Generate do + + describe 'Generated buildfile' do + it 'should be a legal buildfile' do + File.open('buildfile', 'w') { |file| file.write Generate.from_directory(Dir.pwd).join("\n") } + lambda { Buildr.application.run }.should_not raise_error + end + + it 'should not contain NEXT_VERSION because it was removed in buildr 1.3.3' do + buildfile = Generate.from_directory(Dir.pwd) + buildfile.each { |line| line.should_not include('NEXT_VERSION')} + end + end +end diff --git a/buildr/spec/core/project_spec.rb b/buildr/spec/core/project_spec.rb new file mode 100644 index 0000000..ac0f2b0 --- /dev/null +++ b/buildr/spec/core/project_spec.rb @@ -0,0 +1,772 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Project do + it 'should be findable' do + foo = define('foo') + project('foo').should be(foo) + end + + it 'should not exist unless defined' do + lambda { project('foo') }.should raise_error(RuntimeError, /No such project/) + end + + it 'should fail to be defined if its name is already used for a task' do + lambda { define('test') }.should raise_error(RuntimeError, /Invalid project name/i) + define 'valid' do + lambda { define('build') }.should raise_error(RuntimeError, /Invalid project name/i) + end + end + + it 'should exist once defined' do + define 'foo' + lambda { project('foo') }.should_not raise_error + end + + it 'should always return same project for same name' do + foo, bar = define('foo'), define('bar') + foo.should_not be(bar) + foo.should be(project('foo')) + bar.should be(project('bar')) + end + + it 'should show up in projects list if defined' do + define('foo') + projects.map(&:name).should include('foo') + end + + it 'should not show up in projects list unless defined' do + projects.map(&:name).should_not include('foo') + end + + it 'should be findable from within a project' do + define('foo') + project('foo').project('foo').should be(project('foo')) + end + + it 'should cease to exist when project list cleared' do + define 'foo' + projects.map(&:name).should include('foo') + Project.clear + projects.map(&:name).should be_empty + end + + it 'should be defined only once' do + lambda { define 'foo' }.should_not raise_error + lambda { define 'foo' }.should raise_error + end + + it 'should be definable in any order' do + Buildr.define('baz') { define('bar') { project('foo:bar') } } + Buildr.define('foo') { define('bar') } + lambda { project('foo') }.should_not raise_error + end + + it 'should detect circular dependency' do + Buildr.define('baz') { define('bar') { project('foo:bar') } } + Buildr.define('foo') { define('bar') { project('baz:bar') } } + lambda { project('foo') }.should raise_error(RuntimeError, /Circular dependency/) + end +end + +describe Project, ' property' do + it 'should be set if passed as argument' do + define 'foo', 'version'=>'1.1' + project('foo').version.should eql('1.1') + end + + it 'should be set if assigned in body' do + define('foo') { self.version = '1.2' } + project('foo').version.should eql('1.2') + end + + it 'should take precedence when assigned in body' do + define('foo', 'version'=>'1.1') { self.version = '1.2' } + project('foo').version.should eql('1.2') + end + + it 'should inherit from parent (for some properties)' do + define('foo', 'version'=>'1.2', :group=>'foobar') { define 'bar' } + project('foo:bar').version.should eql('1.2') + project('foo:bar').group.should eql('foobar') + end + + it 'should have different value if set in sub-project' do + define 'foo', 'version'=>'1.2', :group=>'foobar' do + define 'bar', :version=>'1.3' do + self.group = 'barbaz' + end + end + project('foo:bar').version.should eql('1.3') + project('foo:bar').group.should eql('barbaz') + end +end + + +describe Project, ' block' do + it 'should execute once' do + define('foo') { self.name.should eql('foo') } + end + + it 'should execute in describe of project' do + define('foo') { self.version = '1.3' } + project('foo').version.should eql('1.3') + end + + it 'should execute by passing project' do + define('foo') { |project| project.version = '1.3' } + project('foo').version.should eql('1.3') + end + + it 'should execute in namespace of project' do + define('foo') { define('bar') { Buildr.application.current_scope.should eql(['foo', 'bar']) } } + end +end + + +describe Project, '#base_dir' do + it 'should be pwd if not specified' do + define('foo').base_dir.should eql(Dir.pwd) + end + + it 'should come from property, if specified' do + foo = define('foo', :base_dir=>'tmp') + foo.base_dir.should point_to_path('tmp') + end + + it 'should be expanded path' do + foo = define('foo', :base_dir=>'tmp') + foo.base_dir.should eql(File.expand_path('tmp')) + end + + it 'should be relative to parent project' do + define('foo') { define('bar') { define 'baz' } } + project('foo:bar:baz').base_dir.should point_to_path('bar/baz') + end + + it 'should be settable only if not read' do + lambda { define('foo', :base_dir=>'tmp') }.should_not raise_error + lambda { define('bar', :base_dir=>'tmp') { self.base_dir = 'bar' } }.should raise_error(Exception, /Cannot set/) + end +end + + +describe Layout do + before :each do + @layout = Layout.new + end + + it 'should expand empty to itself' do + @layout.expand.should eql('') + @layout.expand('').should eql('') + end + + it 'should expand array of symbols' do + @layout.expand(:foo, :bar).should eql('foo/bar') + end + + it 'should expand array of names' do + @layout.expand('foo', 'bar').should eql('foo/bar') + end + + it 'should map symbol to path' do + @layout[:foo] = 'baz' + @layout.expand(:foo, :bar).should eql('baz/bar') + end + + it 'should map symbols to path' do + @layout[:foo, :bar] = 'none' + @layout.expand(:foo, :bar).should eql('none') + end + + it 'should map strings to path' do + @layout[:foo, "bar"] = 'none' + @layout.expand(:foo, :bar).should eql('none') + @layout.expand(:foo, 'bar').should eql('none') + end + + it 'should ignore nil elements' do + @layout[:foo, :bar] = 'none' + @layout.expand(:foo, nil, :bar).should eql('none') + @layout.expand(nil, :foo).should eql('foo') + end + + it 'should return nil if path not mapped' do + @layout[:foo].should be_nil + end + + it 'should return path from symbol' do + @layout[:foo] = 'path' + @layout[:foo].should eql('path') + end + + it 'should return path from symbol' do + @layout[:foo, :bar] = 'path' + @layout[:foo, :bar].should eql('path') + end + + it 'should do eager mapping' do + @layout[:one] = 'none' + @layout[:one, :two] = '1..2' + @layout.expand(:one, :two, :three).should eql('1..2/three') + end + +end + + +describe Project, '#layout' do + before :each do + @layout = Layout.new + end + + it 'should exist by default' do + define('foo').layout.should respond_to(:expand) + end + + it 'should be clone of default layout' do + define 'foo' do + layout.should_not be(Layout.default) + layout.expand(:test, :main).should eql(Layout.default.expand(:test, :main)) + end + end + + it 'should come from property, if specified' do + foo = define('foo', :layout=>@layout) + foo.layout.should eql(@layout) + end + + it 'should inherit from parent project' do + define 'foo', :layout=>@layout do + layout[:foo] = 'foo' + define 'bar' + end + project('foo:bar').layout[:foo].should eql('foo') + end + + it 'should clone when inheriting from parent project' do + define 'foo', :layout=>@layout do + layout[:foo] = 'foo' + define 'bar' do + layout[:foo] = 'bar' + end + end + project('foo').layout[:foo].should eql('foo') + project('foo:bar').layout[:foo].should eql('bar') + end + + it 'should be settable only if not read' do + lambda { define('foo', :layout=>@layout) }.should_not raise_error + lambda { define('bar', :layout=>@layout) { self.layout = @layout.clone } }.should raise_error(Exception, /Cannot set/) + end + +end + + +describe Project, '#path_to' do + it 'should return absolute paths as is' do + define('foo').path_to('/tmp').should eql(File.expand_path('/tmp')) + end + + it 'should resolve empty path to project\'s base directory' do + define('foo').path_to.should eql(project('foo').base_dir) + end + + it 'should resolve relative paths' do + define('foo').path_to('tmp').should eql(File.expand_path('tmp')) + end + + it 'should accept multiple arguments' do + define('foo').path_to('foo', 'bar').should eql(File.expand_path('foo/bar')) + end + + it 'should handle relative paths' do + define('foo').path_to('..', 'bar').should eql(File.expand_path('../bar')) + end + + it 'should resolve symbols using layout' do + define('foo').layout[:foo] = 'bar' + project('foo').path_to(:foo).should eql(File.expand_path('bar')) + project('foo').path_to(:foo, 'tmp').should eql(File.expand_path('bar/tmp')) + end + + it 'should resolve path for sub-project' do + define('foo') { define 'bar' } + project('foo:bar').path_to('foo').should eql(File.expand_path('foo', project('foo:bar').base_dir)) + end + + it 'should be idempotent for relative paths' do + define 'foo' + path = project('foo').path_to('bar') + project('foo').path_to(path).should eql(path) + end +end + + +describe Project, '#on_define' do + it 'should be called when project is defined' do + names = [] + Project.on_define { |project| names << project.name } + define 'foo' ; define 'bar' + names.should eql(['foo', 'bar']) + end + + it 'should be called with project object' do + Project.on_define { |project| project.name.should eql('foo') } + define('foo') + end + + it 'should be called with project object and set properties' do + Project.on_define { |project| project.version.should eql('2.0') } + define('foo', :version=>'2.0') + end + + it 'should execute in namespace of project' do + scopes = [] + Project.on_define { |project| scopes << Buildr.application.current_scope } + define('foo') { define 'bar' } + scopes.should eql([['foo'], ['foo', 'bar']]) + end + + it 'should be called before project block' do + order = [] + Project.on_define { |project| order << 'on_define' } + define('foo') { order << 'define' } + order.should eql(['on_define', 'define']) + end + + it 'should accept enhancement and call it after project block' do + order = [] + Project.on_define { |project| project.enhance { order << 'enhance' } } + define('foo') { order << 'define' } + order.should eql(['define', 'enhance']) + end + + it 'should accept enhancement and call it with project' do + Project.on_define { |project| project.enhance { |project| project.name.should eql('foo') } } + define('foo') + end + + it 'should execute enhancement in namespace of project' do + scopes = [] + Project.on_define { |project| project.enhance { scopes << Buildr.application.current_scope } } + define('foo') { define 'bar' } + scopes.should eql([['foo'], ['foo', 'bar']]) + end + + it 'should be removed in version 1.5 since it was deprecated in version 1.3' do + Buildr::VERSION.should < '1.5' + end +end + + +describe Rake::Task, ' recursive' do + before do + @order = [] + Project.on_define do |project| # TODO on_define is deprecated + project.recursive_task('doda') { @order << project.name } + end + define('foo') { define('bar') { define('baz') } } + end + + it 'should invoke same task in child project' do + task('foo:doda').invoke + @order.should include('foo:bar:baz') + @order.should include('foo:bar') + @order.should include('foo') + end + + it 'should invoke in depth-first order' do + task('foo:doda').invoke + @order.should eql([ 'foo:bar:baz', 'foo:bar', 'foo' ]) + end + + it 'should not invoke task in parent project' do + task('foo:bar:baz:doda').invoke + @order.should eql([ 'foo:bar:baz' ]) + end +end + + +describe 'Sub-project' do + it 'should point at parent project' do + define('foo') { define 'bar' } + project('foo:bar').parent.should be(project('foo')) + end + + it 'should be defined only within parent project' do + lambda { define('foo:bar') }.should raise_error + end + + it 'should have unique name' do + lambda do + define 'foo' do + define 'bar' + define 'bar' + end + end.should raise_error + end + + it 'should be findable from root' do + define('foo') { define 'bar' } + projects.map(&:name).should include('foo:bar') + end + + it 'should be findable from parent project' do + define('foo') { define 'bar' } + project('foo').projects.map(&:name).should include('foo:bar') + end + + it 'should be findable during project definition' do + define 'foo' do + bar = define 'bar' do + baz = define 'baz' + project('baz').should eql(baz) + end + # Note: evaluating bar:baz first unearthed a bug that doesn't happen + # if we evaluate bar, then bar:baz. + project('bar:baz').should be(bar.project('baz')) + project('bar').should be(bar) + end + end + + it 'should be findable only if exists' do + define('foo') { define 'bar' } + lambda { project('foo').project('baz') }.should raise_error(RuntimeError, /No such project/) + end + + it 'should always execute its definition ' do + ordered = [] + define 'foo' do + ordered << self.name + define('bar') { ordered << self.name } + define('baz') { ordered << self.name } + end + ordered.should eql(['foo', 'foo:bar', 'foo:baz']) + end + + it 'should execute in order of dependency' do + ordered = [] + define 'foo' do + ordered << self.name + define('bar') { project('foo:baz') ; ordered << self.name } + define('baz') { ordered << self.name } + end + ordered.should eql(['foo', 'foo:baz', 'foo:bar']) + end + + it 'should warn of circular dependency' do + lambda do + define 'foo' do + define('bar') { project('foo:baz') } + define('baz') { project('foo:bar') } + end + end.should raise_error(RuntimeError, /Circular dependency/) + end +end + + +describe 'Top-level project' do + it 'should have no parent' do + define('foo') + project('foo').parent.should be_nil + end +end + + +describe Buildr, '#project' do + it 'should raise error if no such project' do + lambda { project('foo') }.should raise_error(RuntimeError, /No such project/) + end + + it 'should return a project if exists' do + foo = define('foo') + project('foo').should be(foo) + end + + it 'should define a project if a block is given' do + foo = project('foo') {} + project('foo').should be(foo) + end + + it 'should define a project if properties and a block are given' do + foo = project('foo', :version => '1.2') {} + project('foo').should be(foo) + end + + it 'should find a project by its full name' do + bar, baz = nil + define('foo') { bar = define('bar') { baz = define('baz') } } + project('foo:bar').should be(bar) + project('foo:bar:baz').should be(baz) + end + + it 'should find a project from any context' do + bar, baz = nil + define('foo') { bar = define('bar') { baz = define('baz') } } + project('foo:bar').project('foo:bar:baz').should be(baz) + project('foo:bar:baz').project('foo:bar').should be(bar) + end + + it 'should find a project from its parent or sibling project' do + define 'foo' do + define 'bar' + define 'baz' + end + project('foo').project('bar').should be(project('foo:bar')) + project('foo').project('baz').should be(project('foo:baz')) + project('foo:bar').project('baz').should be(project('foo:baz')) + end + + it 'should fine a project from its parent by proximity' do + define 'foo' do + define('bar') { define 'baz' } + define 'baz' + end + project('foo').project('baz').should be(project('foo:baz')) + project('foo:bar').project('baz').should be(project('foo:bar:baz')) + end + + it 'should invoke project before returning it' do + define('foo').should_receive(:invoke).once + project('foo') + end + + it 'should fail if called without a project name' do + lambda { project }.should raise_error(ArgumentError) + end + + it 'should return self if called on a project without a name' do + define('foo') { project.should be(self) } + end + + it 'should evaluate parent project before returning' do + # Note: gets around our define that also invokes the project. + Buildr.define('foo') { define('bar'); define('baz') } + project('foo:bar').should eql(projects[1]) + end +end + + +describe Buildr, '#projects' do + it 'should only return defined projects' do + projects.should eql([]) + define 'foo' + projects.should eql([project('foo')]) + end + + it 'should return all defined projects' do + define 'foo' + define('bar') { define 'baz' } + projects.should include(project('foo')) + projects.should include(project('bar')) + projects.should include(project('bar:baz')) + end + + it 'should return only named projects' do + define 'foo' ; define 'bar' ; define 'baz' + projects('foo', 'bar').should include(project('foo')) + projects('foo', 'bar').should include(project('bar')) + projects('foo', 'bar').should_not include(project('baz')) + end + + it 'should complain if named project does not exist' do + define 'foo' + projects('foo').should include(project('foo')) + lambda { projects('bar') }.should raise_error(RuntimeError, /No such project/) + end + + it 'should find a project from its parent or sibling project' do + define 'foo' do + define 'bar' + define 'baz' + end + project('foo').projects('bar').should eql(projects('foo:bar')) + project('foo').projects('baz').should eql(projects('foo:baz')) + project('foo:bar').projects('baz').should eql(projects('foo:baz')) + end + + it 'should fine a project from its parent by proximity' do + define 'foo' do + define('bar') { define 'baz' } + define 'baz' + end + project('foo').projects('baz').should eql(projects('foo:baz')) + project('foo:bar').projects('baz').should eql(projects('foo:bar:baz')) + end + + it 'should evaluate all projects before returning' do + # Note: gets around our define that also invokes the project. + Buildr.define('foo') { define('bar'); define('baz') } + projects.should eql(projects('foo', 'foo:bar', 'foo:baz')) + end +end + + +describe Rake::Task, ' local directory' do + before do + @task = Project.local_task(task(('doda'))) + Project.on_define { |project| task('doda') { |task| @task.from project.name } } + end + + it 'should execute project in local directory' do + define 'foo' + @task.should_receive(:from).with('foo') + @task.invoke + end + + it 'should execute sub-project in local directory' do + @task.should_receive(:from).with('foo:bar') + define('foo') { define 'bar' } + in_original_dir(project('foo:bar').base_dir) { @task.invoke } + end + + it 'should do nothing if no project in local directory' do + @task.should_not_receive(:from) + define('foo') { define 'bar' } + in_original_dir('../not_foo') { @task.invoke } + end + + it 'should find closest project that matches current directory' do + mkpath 'bar/src/main' + define('foo') { define 'bar' } + @task.should_receive(:from).with('foo:bar') + in_original_dir('bar/src/main') { @task.invoke } + end +end + + +describe Project, '#task' do + it 'should create a regular task' do + define('foo') { task('bar') } + Buildr.application.lookup('foo:bar').should_not be_nil + end + + it 'should return a task defined in the project' do + define('foo') { task('bar') } + project('foo').task('bar').should be_instance_of(Rake::Task) + end + + it 'should not create task outside project definition' do + define 'foo' + lambda { project('foo').task('bar') }.should raise_error(RuntimeError, /no task foo:bar/) + end + + it 'should include project name as prefix' do + define('foo') { task('bar') } + project('foo').task('bar').name.should eql('foo:bar') + end + + it 'should ignore namespace if starting with color' do + define 'foo' do + task(':bar').name.should == 'bar' + end + Rake::Task.task_defined?('bar').should be_true + end + + it 'should accept single dependency' do + define('foo') { task('bar'=>'baz') } + project('foo').task('bar').prerequisites.should include('baz') + end + + it 'should accept multiple dependencies' do + define('foo') { task('bar'=>['baz1', 'baz2']) } + project('foo').task('bar').prerequisites.should include('baz1') + project('foo').task('bar').prerequisites.should include('baz2') + end + + it 'should execute task exactly once' do + define('foo') do + task 'baz' + task 'bar'=>'baz' + end + lambda { project('foo').task('bar').invoke }.should run_tasks(['foo:baz', 'foo:bar']) + end + + it 'should create a file task' do + define('foo') { file('bar') } + Buildr.application.lookup(File.expand_path('bar')).should_not be_nil + end + + it 'should create file task with absolute path' do + define('foo') { file('/tmp') } + Buildr.application.lookup(File.expand_path('/tmp')).should_not be_nil + end + + it 'should create file task relative to project base directory' do + define('foo', :base_dir=>'tmp') { file('bar') } + Buildr.application.lookup(File.expand_path('tmp/bar')).should_not be_nil + end + + it 'should accept single dependency' do + define('foo') { file('bar'=>'baz') } + project('foo').file('bar').prerequisites.should include('baz') + end + + it 'should accept multiple dependencies' do + define('foo') { file('bar'=>['baz1', 'baz2']) } + project('foo').file('bar').prerequisites.should include('baz1') + project('foo').file('bar').prerequisites.should include('baz2') + end + + it 'should accept hash arguments' do + define('foo') do + task 'bar'=>'bar_dep' + file 'baz'=>'baz_dep' + end + project('foo').task('bar').prerequisites.should include('bar_dep') + project('foo').file('baz').prerequisites.should include('baz_dep') + end + + it 'should return a file task defined in the project' do + define('foo') { file('bar') } + project('foo').file('bar').should be_instance_of(Rake::FileTask) + end + + it 'should create file task relative to project definition' do + define('foo') { define 'bar' } + project('foo:bar').file('baz').name.should point_to_path('bar/baz') + end + + it 'should execute task exactly once' do + define('foo') do + task 'baz' + file 'bar'=>'baz' + end + lambda { project('foo').file('bar').invoke }.should run_tasks(['foo:baz', project('foo').path_to('bar')]) + end +end + + +=begin +describe Buildr::Generate do + it 'should be able to create buildfile from directory structure' do + write 'src/main/java/Foo.java', '' + write 'one/two/src/main/java/Foo.java', '' + write 'one/three/src/main/java/Foo.java', '' + write 'four/src/main/java/Foo.java', '' + script = Buildr::Generate.from_directory(Dir.pwd) + instance_eval(script.join("\n"), "generated buildfile") + # projects should have been defined + root = Dir.pwd.pathmap('%n') + names = [root, "#{root}:one:two", "#{root}:one:three", "#{root}:four"] + # the top level project has the directory name. + names.each { |name| lambda { project(name) }.should_not raise_error } + end +end +=end diff --git a/buildr/spec/core/run_spec.rb b/buildr/spec/core/run_spec.rb new file mode 100644 index 0000000..2a1510e --- /dev/null +++ b/buildr/spec/core/run_spec.rb @@ -0,0 +1,106 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe Project, :run do + + it 'should return the project\'s run task' do + define('foo') + project('foo').run.name.should eql('foo:run') + end + + it 'should return a RunTask' do + define('foo') + project('foo').run.should be_kind_of(Run::RunTask) + end + + it 'should include compile dependencies' do + define('foo') do + compile.using(:javac).with 'group:compile:jar:1.0' + test.compile.using(:javac).with 'group:test:jar:1.0' + end + project('foo').run.classpath.should include(artifact('group:compile:jar:1.0')) + end + + it 'should not include test dependencies' do + define('foo') do + compile.using(:javac).with 'group:compile:jar:1.0' + test.compile.using(:javac).with 'group:test:jar:1.0' + end + project('foo').run.classpath.should_not include(artifact('group:test:jar:1.0')) + end + + it 'should respond to using() and return self' do + define 'foo' do + run.using(:foo=>'Fooing').should be(run) + end + end + + it 'should respond to using() and accept options' do + define 'foo' do + run.using :foo=>'Fooing' + end + project('foo').run.options[:foo].should eql('Fooing') + end + + it 'should select runner using run.using' do + define 'foo' do + run.using :java + end + project('foo').run.runner.should be_a(Run::JavaRunner) + end + + it 'should select runner based on compile language' do + write 'src/main/java/Test.java', 'class Test {}' + define 'foo' do + # compile language detected as :java + end + project('foo').run.runner.should be_a(Run::JavaRunner) + end + + it "should run with the project resources" do + write 'src/main/java/Test.java', 'class Test {}' + write 'src/main/resources/test.properties', '' + define 'foo' + project('foo').run.classpath.should include project('foo').resources.target + end + + it 'should depend on project''s compile task' do + define 'foo' + project('foo').run.prerequisites.should include(project('foo').compile) + end + + it 'should be local task' do + define 'foo' do + define('bar') + end + project('foo:bar').run.should_receive(:invoke_prerequisites) + project('foo:bar').run.should_receive(:run) + in_original_dir(project('foo:bar').base_dir) { task('run').invoke } + end + + it 'should not recurse' do + define 'foo' do + define('bar') { run.using :java, :main => 'foo' } + end + project('foo:bar').run.should_not_receive(:invoke_prerequisites) + project('foo:bar').run.should_not_receive(:run) + project('foo').run.should_receive(:run) + project('foo').run.invoke + end + +end + diff --git a/buildr/spec/core/shell_spec.rb b/buildr/spec/core/shell_spec.rb new file mode 100644 index 0000000..20c6df3 --- /dev/null +++ b/buildr/spec/core/shell_spec.rb @@ -0,0 +1,146 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe Project, '.shell' do + + it 'should return the project\'s shell task' do + define('foo') + project('foo').shell.name.should eql('foo:shell') + end + + it 'should return a ShellTask' do + define('foo') + project('foo').shell.should be_kind_of(Shell::ShellTask) + end + + it 'should include compile and test.compile dependencies' do + define('foo') do + compile.using(:javac).with 'group:compile:jar:1.0' + test.compile.using(:javac).with 'group:test:jar:1.0' + end + project('foo').shell.classpath.should include(artifact('group:compile:jar:1.0')) + project('foo').shell.classpath.should include(artifact('group:test:jar:1.0')) + end + + it 'should respond to using() and return self' do + define 'foo' do + shell.using(:foo=>'Fooing').should be(shell) + end + end + + it 'should respond to using() and accept options' do + define 'foo' do + shell.using :foo=>'Fooing' + end + project('foo').shell.options[:foo].should eql('Fooing') + end + + it 'should select provider using shell.using' do + define 'foo' do + shell.using :bsh + end + project('foo').shell.provider.should be_a(Shell::BeanShell) + end + + it 'should select runner based on compile language' do + write 'src/main/java/Test.java', 'class Test {}' + define 'foo' do + # compile language detected as :java + end + project('foo').shell.provider.should be_a(Shell::BeanShell) + end + + it 'should depend on project''s compile task' do + define 'foo' + project('foo').shell.prerequisites.should include(project('foo').compile) + end + + it 'should be local task' do + define 'foo' do + define('bar') do + shell.using :bsh + end + end + task = project('foo:bar').shell + task.should_receive(:invoke_prerequisites) + task.should_receive(:run) + in_original_dir(project('foo:bar').base_dir) { task('shell').invoke } + end + + it 'should not recurse' do + define 'foo' do + shell.using :bsh + define('bar') { shell.using :bsh } + end + project('foo:bar').shell.should_not_receive(:invoke_prerequisites) + project('foo:bar').shell.should_not_receive(:run) + project('foo').shell.should_receive(:run) + project('foo').shell.invoke + end + + it 'should call shell provider with task configuration' do + define 'foo' do + shell.using :bsh + end + shell = project('foo').shell + shell.provider.should_receive(:launch).with(shell) + project('foo').shell.invoke + end +end + +shared_examples_for "shell provider" do + + it 'should have launch method accepting shell task' do + @instance.method(:launch).should_not be_nil + @instance.method(:launch).arity.should === 1 + end + +end + +Shell.providers.each do |provider| + describe provider do + before do + @provider = provider + @project = define('foo') {} + @instance = provider.new(@project) + @project.shell.using @provider.to_sym + end + + it_should_behave_like "shell provider" + + it 'should call Java::Commands.java with :java_args' do + @project.shell.using :java_args => ["-Xx"] + Java::Commands.should_receive(:java).with do |*args| + args.last.should be_a(Hash) + args.last.keys.should include(:java_args) + args.last[:java_args].should include('-Xx') + end + project('foo').shell.invoke + end + + it 'should call Java::Commands.java with :properties' do + @project.shell.using :properties => {:foo => "bar"} + Java::Commands.should_receive(:java).with do |*args| + args.last.should be_a(Hash) + args.last.keys.should include(:properties) + args.last[:properties][:foo].should == "bar" + end + project('foo').shell.invoke + end + end +end + diff --git a/buildr/spec/core/test_spec.rb b/buildr/spec/core/test_spec.rb new file mode 100644 index 0000000..7d66a59 --- /dev/null +++ b/buildr/spec/core/test_spec.rb @@ -0,0 +1,1320 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +module TestHelper + def touch_last_successful_test_run(test_task, timestamp = Time.now) + test_task.instance_eval do + record_successful_run + File.utime(timestamp, timestamp, last_successful_run_file) + end + end +end + + +describe Buildr::TestTask do + def test_task + @test_task ||= define('foo').test + end + + it 'should respond to :compile and return compile task' do + test_task.compile.should be_kind_of(Buildr::CompileTask) + end + + it 'should respond to :compile and add sources to compile' do + test_task.compile 'sources' + test_task.compile.sources.should include('sources') + end + + it 'should respond to :compile and add action for test:compile' do + write 'src/test/java/Test.java', 'class Test {}' + test_task.compile { task('action').invoke } + lambda { test_task.compile.invoke }.should run_tasks('action') + end + + it 'should execute compile tasks first' do + write 'src/main/java/Nothing.java', 'class Nothing {}' + write 'src/test/java/Test.java', 'class Test {}' + define 'foo' + lambda { project('foo').test.compile.invoke }.should run_tasks(['foo:compile', 'foo:test:compile']) + end + + it 'should respond to :resources and return resources task' do + test_task.resources.should be_kind_of(Buildr::ResourcesTask) + end + + it 'should respond to :resources and add prerequisites to test:resources' do + file('prereq').should_receive :invoke_prerequisites + test_task.resources 'prereq' + test_task.compile.invoke + end + + it 'should respond to :resources and add action for test:resources' do + task 'action' + test_task.resources { task('action').invoke } + lambda { test_task.resources.invoke }.should run_tasks('action') + end + + it 'should respond to :setup and return setup task' do + test_task.setup.name.should =~ /test:setup$/ + end + + it 'should respond to :setup and add prerequisites to test:setup' do + test_task.setup 'prereq' + test_task.setup.prerequisites.should include('prereq') + end + + it 'should respond to :setup and add action for test:setup' do + task 'action' + test_task.setup { task('action').invoke } + lambda { test_task.setup.invoke }.should run_tasks('action') + end + + it 'should respond to :teardown and return teardown task' do + test_task.teardown.name.should =~ /test:teardown$/ + end + + it 'should respond to :teardown and add prerequisites to test:teardown' do + test_task.teardown 'prereq' + test_task.teardown.prerequisites.should include('prereq') + end + + it 'should respond to :teardown and add action for test:teardown' do + task 'action' + test_task.teardown { task('action').invoke } + lambda { test_task.teardown.invoke }.should run_tasks('action') + end + + it 'should respond to :with and return self' do + test_task.with.should be(test_task) + end + + it 'should respond to :with and add artifacfs to compile task dependencies' do + test_task.with 'test.jar', 'acme:example:jar:1.0' + test_task.compile.dependencies.should include(File.expand_path('test.jar')) + test_task.compile.dependencies.should include(artifact('acme:example:jar:1.0')) + end + + it 'should respond to deprecated classpath' do + test_task.classpath = artifact('acme:example:jar:1.0') + test_task.classpath.should be(artifact('acme:example:jar:1.0')) + end + + it 'should respond to dependencies' do + test_task.dependencies = artifact('acme:example:jar:1.0') + test_task.dependencies.should be(artifact('acme:example:jar:1.0')) + end + + it 'should respond to :with and add artifacfs to task dependencies' do + test_task.with 'test.jar', 'acme:example:jar:1.0' + test_task.dependencies.should include(File.expand_path('test.jar')) + test_task.dependencies.should include(artifact('acme:example:jar:1.0')) + end + + it 'should response to :options and return test framework options' do + test_task.using :foo=>'bar' + test_task.options[:foo].should eql('bar') + end + + it 'should respond to :using and return self' do + test_task.using.should be(test_task) + end + + it 'should respond to :using and set value options' do + test_task.using('foo'=>'FOO', 'bar'=>'BAR') + test_task.options[:foo].should eql('FOO') + test_task.options[:bar].should eql('BAR') + end + + it 'should respond to :using with deprecated parameter style and set value options to true, up to version 1.5 since this usage was deprecated in version 1.3' do + Buildr::VERSION.should < '1.5' + test_task.using('foo', 'bar') + test_task.options[:foo].should eql(true) + test_task.options[:bar].should eql(true) + end + + it 'should start without pre-selected test framework' do + test_task.framework.should be_nil + end + + it 'should respond to :using and select test framework' do + test_task.using(:testng) + test_task.framework.should eql(:testng) + end + + it 'should infer test framework from compiled language' do + lambda { test_task.compile.using(:javac) }.should change { test_task.framework }.to(:junit) + end + + it 'should respond to :include and return self' do + test_task.include.should be(test_task) + end + + it 'should respond to :include and add inclusion patterns' do + test_task.include 'Foo', 'Bar' + test_task.send(:include?, 'Foo').should be_true + test_task.send(:include?, 'Bar').should be_true + end + + it 'should respond to :exclude and return self' do + test_task.exclude.should be(test_task) + end + + it 'should respond to :exclude and add exclusion patterns' do + test_task.exclude 'FooTest', 'BarTest' + test_task.send(:include?, 'FooTest').should be_false + test_task.send(:include?, 'BarTest').should be_false + test_task.send(:include?, 'BazTest').should be_true + end + + it 'should execute setup task before running tests' do + mock = mock('actions') + test_task.setup { mock.setup } + test_task.enhance { mock.tests } + mock.should_receive(:setup).ordered + mock.should_receive(:tests).ordered + test_task.invoke + end + + it 'should execute teardown task after running tests' do + mock = mock('actions') + test_task.teardown { mock.teardown } + test_task.enhance { mock.tests } + mock.should_receive(:tests).ordered + mock.should_receive(:teardown).ordered + test_task.invoke + end + + it 'should not execute teardown if setup failed' do + test_task.setup { fail } + lambda { test_task.invoke rescue nil }.should_not run_task(test_task.teardown) + end + + it 'should use the main compile dependencies' do + define('foo') { compile.using(:javac).with 'group:id:jar:1.0' } + project('foo').test.dependencies.should include(artifact('group:id:jar:1.0')) + end + + it 'should include the main compile target in its dependencies' do + define('foo') { compile.using(:javac) } + project('foo').test.dependencies.should include(project('foo').compile.target) + end + + it 'should include the main compile target in its dependencies, even when using non standard directories' do + write 'src/java/Nothing.java', 'class Nothing {}' + define('foo') { compile path_to('src/java') } + project('foo').test.dependencies.should include(project('foo').compile.target) + end + + it 'should include the main resources target in its dependencies' do + write 'src/main/resources/config.xml' + define('foo').test.dependencies.should include(project('foo').resources.target) + end + + it 'should use the test compile dependencies' do + define('foo') { test.compile.using(:javac).with 'group:id:jar:1.0' } + project('foo').test.dependencies.should include(artifact('group:id:jar:1.0')) + end + + it 'should include the test compile target in its dependencies' do + define('foo') { test.compile.using(:javac) } + project('foo').test.dependencies.should include(project('foo').test.compile.target) + end + + it 'should include the test compile target in its dependencies, even when using non standard directories' do + write 'src/test/Test.java', 'class Test {}' + define('foo') { test.compile path_to('src/test') } + project('foo').test.dependencies.should include(project('foo').test.compile.target) + end + + it 'should add test compile target ahead of regular compile target' do + write 'src/main/java/Code.java' + write 'src/test/java/Test.java' + define 'foo' + depends = project('foo').test.dependencies + depends.index(project('foo').test.compile.target).should < depends.index(project('foo').compile.target) + end + + it 'should include the test resources target in its dependencies' do + write 'src/test/resources/config.xml' + define('foo').test.dependencies.should include(project('foo').test.resources.target) + end + + it 'should add test resource target ahead of regular resource target' do + write 'src/main/resources/config.xml' + write 'src/test/resources/config.xml' + define 'foo' + depends = project('foo').test.dependencies + depends.index(project('foo').test.resources.target).should < depends.index(project('foo').resources.target) + end + + it 'should not have a last successful run timestamp before the tests are run' do + test_task.timestamp.should == Rake::EARLY + end + + it 'should clean after itself (test files)' do + define('foo') { test.compile.using(:javac) } + mkpath project('foo').test.compile.target.to_s + lambda { task('clean').invoke }.should change { File.exist?(project('foo').test.compile.target.to_s) }.to(false) + end + + it 'should clean after itself (reports)' do + define 'foo' + mkpath project('foo').test.report_to.to_s + lambda { task('clean').invoke }.should change { File.exist?(project('foo').test.report_to.to_s) }.to(false) + end + + it 'should only run tests explicitly specified if options.test is :only' do + Buildr.options.test = :only + write 'bar/src/main/java/Bar.java', 'public class Bar {}' + define('bar', :version=>'1.0', :base_dir=>'bar') { package :jar } + define('foo') { compile.with project('bar') } + lambda { task('foo:test').invoke rescue nil }.should_not run_tasks('bar:test') + end +end + + +describe Buildr::TestTask, 'with no tests' do + it 'should pass' do + lambda { define('foo').test.invoke }.should_not raise_error + end + + it 'should report no failed tests' do + lambda { verbose(true) { define('foo').test.invoke } }.should_not show_error(/fail/i) + end + + it 'should return no failed tests' do + define('foo') { test.using(:junit) } + project('foo').test.invoke + project('foo').test.failed_tests.should be_empty + end + + it 'should return no passing tests' do + define('foo') { test.using(:junit) } + project('foo').test.invoke + project('foo').test.passed_tests.should be_empty + end + + it 'should execute teardown task' do + lambda { define('foo').test.invoke }.should run_task('foo:test:teardown') + end +end + + +describe Buildr::TestTask, 'with passing tests' do + def test_task + @test_task ||= begin + define 'foo' do + test.using(:junit) + test.instance_eval do + @framework.stub!(:tests).and_return(['PassingTest1', 'PassingTest2']) + @framework.stub!(:run).and_return(['PassingTest1', 'PassingTest2']) + end + end + project('foo').test + end + end + + it 'should pass' do + lambda { test_task.invoke }.should_not raise_error + end + + it 'should report no failed tests' do + lambda { verbose(true) { test_task.invoke } }.should_not show_error(/fail/i) + end + + it 'should return passed tests' do + test_task.invoke + test_task.passed_tests.should == ['PassingTest1', 'PassingTest2'] + end + + it 'should return no failed tests' do + test_task.invoke + test_task.failed_tests.should be_empty + end + + it 'should execute teardown task' do + lambda { test_task.invoke }.should run_task('foo:test:teardown') + end + + it 'should update the last successful run timestamp' do + before = Time.now ; test_task.invoke ; after = Time.now + (before-1..after+1).should cover(test_task.timestamp) + end +end + + +describe Buildr::TestTask, 'with failed test' do + include TestHelper + + def test_task + @test_task ||= begin + define 'foo' do + test.using(:junit) + test.instance_eval do + @framework.stub!(:tests).and_return(['FailingTest', 'PassingTest']) + @framework.stub!(:run).and_return(['PassingTest']) + end + end + project('foo').test + end + end + + it 'should fail' do + lambda { test_task.invoke }.should raise_error(RuntimeError, /Tests failed/) + end + + it 'should report failed tests' do + lambda { verbose(true) { test_task.invoke rescue nil } }.should show_error(/FailingTest/) + end + + it 'should record failed tests' do + test_task.invoke rescue nil + File.read(project('foo').path_to('target', "#{test_task.framework}-failed")).should == 'FailingTest' + end + + it 'should return failed tests' do + test_task.invoke rescue nil + test_task.failed_tests.should == ['FailingTest'] + end + + it 'should return passing tests as well' do + test_task.invoke rescue nil + test_task.passed_tests.should == ['PassingTest'] + end + + it 'should know what tests failed last time' do + test_task.invoke rescue nil + project('foo').test.last_failures.should == ['FailingTest'] + end + + it 'should not fail if fail_on_failure is false' do + test_task.using(:fail_on_failure=>false).invoke + lambda { test_task.invoke }.should_not raise_error + end + + it 'should report failed tests even if fail_on_failure is false' do + test_task.using(:fail_on_failure=>false) + lambda { verbose(true) { test_task.invoke } }.should show_error(/FailingTest/) + end + + it 'should return failed tests even if fail_on_failure is false' do + test_task.using(:fail_on_failure=>false).invoke + test_task.failed_tests.should == ['FailingTest'] + end + + it 'should execute teardown task' do + lambda { test_task.invoke rescue nil }.should run_task('foo:test:teardown') + end + + it 'should not update the last successful run timestamp' do + a_second_ago = Time.now - 1 + touch_last_successful_test_run test_task, a_second_ago + test_task.invoke rescue nil + test_task.timestamp.should <= a_second_ago + end +end + + +describe Buildr::Project, '#test' do + it 'should return the project\'s test task' do + define('foo') { test.should be(task('test')) } + end + + it 'should accept prerequisites for task' do + define('foo') { test 'prereq' } + project('foo').test.prerequisites.should include('prereq') + end + + it 'should accept actions for task' do + task 'action' + define('foo') { test { task('action').invoke } } + lambda { project('foo').test.invoke }.should run_tasks('action') + end + + it 'should set fail_on_failure true by default' do + define('foo').test.options[:fail_on_failure].should be_true + end + + it 'should set fork mode by default' do + define('foo').test.options[:fork].should == :once + end + + it 'should set properties to empty hash by default' do + define('foo').test.options[:properties].should == {} + end + + it 'should set environment variables to empty hash by default' do + define('foo').test.options[:environment].should == {} + end + + it 'should inherit options from parent project' do + define 'foo' do + test.using :fail_on_failure=>false, :fork=>:each, :properties=>{ :foo=>'bar' }, :environment=>{ 'config'=>'config.yaml' } + define 'bar' do + test.using :junit + test.options[:fail_on_failure].should be_false + test.options[:fork].should == :each + test.options[:properties][:foo].should == 'bar' + test.options[:environment]['config'].should == 'config.yaml' + end + end + end + + it 'should clone options from parent project when using #using' do + define 'foo' do + define 'bar' do + test.using :fail_on_failure=>false, :fork=>:each, :properties=>{ :foo=>'bar' }, :environment=>{ 'config'=>'config.yaml' } + test.using :junit + end.invoke + test.options[:fail_on_failure].should be_true + test.options[:fork].should == :once + test.options[:properties].should == {} + test.options[:environment].should == {} + end + end + + it 'should clone options from parent project when using #options' do + define 'foo' do + define 'bar' do + test.options[:fail_on_failure] = false + test.options[:fork] = :each + test.options[:properties][:foo] = 'bar' + test.options[:environment]['config'] = 'config.yaml' + test.using :junit + end.invoke + test.options[:fail_on_failure].should be_true + test.options[:fork].should == :once + test.options[:properties].should == {} + test.options[:environment].should == {} + end + end + + it 'should accept to set a test property in the top project' do + define 'foo' do + test.options[:properties][:foo] = 'bar' + end + project('foo').test.options[:properties][:foo].should == 'bar' + end + + it 'should accept to set a test property in a subproject' do + define 'foo' do + define 'bar' do + test.options[:properties][:bar] = 'baz' + end + end + project('foo:bar').test.options[:properties][:bar].should == 'baz' + end + + it 'should not change options of unrelated projects when using #options' do + define 'foo' do + test.options[:properties][:foo] = 'bar' + end + define 'bar' do + test.options[:properties].should == {} + end + end + + it "should run from project's build task" do + write 'src/main/java/Foo.java' + write 'src/test/java/FooTest.java' + define('foo') + lambda { task('foo:build').invoke }.should run_task('foo:test') + end +end + + +describe Buildr::Project, '#test.compile' do + it 'should identify compiler from project' do + write 'src/test/java/com/example/Test.java' + define('foo') do + test.compile.compiler.should eql(:javac) + end + end + + it 'should include identified sources' do + write 'src/test/java/Test.java' + define('foo') do + test.compile.sources.should include(_('src/test/java')) + end + end + + it 'should compile to target/test/' do + define 'foo', :target=>'targeted' do + test.compile.using(:javac) + test.compile.target.should eql(file('targeted/test/classes')) + end + end + + it 'should use main compile dependencies' do + define 'foo' do + compile.using(:javac).with 'group:id:jar:1.0' + test.compile.using(:javac) + end + project('foo').test.compile.dependencies.should include(artifact('group:id:jar:1.0')) + end + + it 'should include the main compiled target in its dependencies' do + define 'foo' do + compile.using(:javac).into 'bytecode' + test.compile.using(:javac) + end + project('foo').test.compile.dependencies.should include(file('bytecode')) + end + + it 'should include the test framework dependencies' do + define 'foo' do + test.compile.using(:javac) + test.using(:junit) + end + project('foo').test.compile.dependencies.should include(*artifacts(JUnit.dependencies)) + end + + it 'should clean after itself' do + write 'src/test/java/Nothing.java', 'class Nothing {}' + define('foo') { test.compile.into 'bytecode' } + project('foo').test.compile.invoke + lambda { project('foo').clean.invoke }.should change { File.exist?('bytecode') }.to(false) + end +end + + +describe Buildr::Project, '#test.resources' do + it 'should ignore resources unless they exist' do + define('foo').test.resources.sources.should be_empty + project('foo').test.resources.target.should be_nil + end + + it 'should pick resources from src/test/resources if found' do + mkpath 'src/test/resources' + define('foo') { test.resources.sources.should include(file('src/test/resources')) } + end + + it 'should copy to the resources target directory' do + write 'src/test/resources/config.xml', '' + define('foo', :target=>'targeted').test.invoke + file('targeted/test/resources/config.xml').should contain('') + end + + it 'should create target directory even if no files to copy' do + define('foo') do + test.resources.filter.into('resources') + end + lambda { file(File.expand_path('resources')).invoke }.should change { File.exist?('resources') }.to(true) + end + + it 'should execute alongside compile task' do + task 'action' + define('foo') { test.resources { task('action').invoke } } + lambda { project('foo').test.compile.invoke }.should run_tasks('action') + end +end + + +describe Buildr::TestTask, '#invoke' do + include TestHelper + + def test_task + @test_task ||= define('foo') { + test.using(:junit) + test.instance_eval do + @framework.stub!(:tests).and_return(['PassingTest']) + @framework.stub!(:run).and_return(['PassingTest']) + end + }.test + end + + it 'should require dependencies to exist' do + lambda { test_task.with('no-such.jar').invoke }.should \ + raise_error(RuntimeError, /Don't know how to build/) + end + + it 'should run all dependencies as prerequisites' do + file(File.expand_path('no-such.jar')) { task('prereq').invoke } + lambda { test_task.with('no-such.jar').invoke }.should run_tasks(['prereq', 'foo:test']) + end + + it 'should run tests if they have never run' do + lambda { test_task.invoke }.should run_task('foo:test') + end + + it 'should not run tests if test option is off' do + Buildr.options.test = false + lambda { test_task.invoke }.should_not run_task('foo:test') + end + + describe 'when there was a successful test run already' do + before do + @a_second_ago = Time.now - 1 + src = ['main/java/Foo.java', 'main/resources/config.xml', 'test/java/FooTest.java', 'test/resources/config-test.xml'].map { |f| File.join('src', f) } + target = ['classes/Foo.class', 'resources/config.xml', 'test/classes/FooTest.class', 'test/resources/config-test.xml'].map { |f| File.join('target', f) } + files = ['buildfile'] + src + target + files.each { |file| write file } + dirs = (src + target).map { |file| file.pathmap('%d') } + (files + dirs ).each { |path| File.utime(@a_second_ago, @a_second_ago, path) } + touch_last_successful_test_run test_task, @a_second_ago + end + + it 'should not run tests if nothing changed' do + lambda { test_task.invoke }.should_not run_task('foo:test') + end + + it 'should run tests if options.test is :all' do + Buildr.options.test = :all + lambda { test_task.invoke }.should run_task('foo:test') + end + + it 'should run tests if main compile target changed' do + touch project('foo').compile.target.to_s + lambda { test_task.invoke }.should run_task('foo:test') + end + + it 'should run tests if test compile target changed' do + touch test_task.compile.target.to_s + lambda { test_task.invoke }.should run_task('foo:test') + end + + it 'should run tests if main resources changed' do + touch project('foo').resources.target.to_s + lambda { test_task.invoke }.should run_task('foo:test') + end + + it 'should run tests if test resources changed' do + touch test_task.resources.target.to_s + lambda { test_task.invoke }.should run_task('foo:test') + end + + it 'should run tests if compile-dependent project changed' do + write 'bar/src/main/java/Bar.java', 'public class Bar {}' + define('bar', :version=>'1.0', :base_dir=>'bar') { package :jar } + project('foo').compile.with project('bar') + lambda { test_task.invoke }.should run_task('foo:test') + end + + it 'should run tests if test-dependent project changed' do + write 'bar/src/main/java/Bar.java', 'public class Bar {}' + define('bar', :version=>'1.0', :base_dir=>'bar') { package :jar } + test_task.with project('bar') + lambda { test_task.invoke }.should run_task('foo:test') + end + + it 'should run tests if buildfile changed' do + touch 'buildfile' + test_task.should_receive(:run_tests) + lambda { test_task.invoke }.should run_task('foo:test') + end + + it 'should not run tests if buildfile changed but IGNORE_BUILDFILE is true' do + begin + ENV["IGNORE_BUILDFILE"] = "true" + test_task.should_not_receive(:run_tests) + test_task.invoke + ensure + ENV["IGNORE_BUILDFILE"] = nil + end + end + end +end + +describe Rake::Task, 'test' do + it 'should be recursive' do + define('foo') { define 'bar' } + lambda { task('test').invoke }.should run_tasks('foo:test', 'foo:bar:test') + end + + it 'should be local task' do + define('foo') { define 'bar' } + lambda do + in_original_dir project('foo:bar').base_dir do + task('test').invoke + end + end.should run_task('foo:bar:test').but_not('foo:test') + end + + it 'should stop at first failure' do + define('foo') { test { fail } } + define('bar') { test { fail } } + lambda { task('test').invoke rescue nil }.should run_tasks('foo:test').but_not('bar:test') + end + + it 'should ignore failure if options.test is :all' do + define('foo') { test { fail } } + define('bar') { test { fail } } + options.test = :all + lambda { task('test').invoke rescue nil }.should run_tasks('foo:test', 'bar:test') + end + + it 'should ignore failure in subprojects if options.test is :all' do + define('foo') { + define('p1') { test { fail } } + define('p2') { test { } } + define('p3') { test { fail } } + } + define('bar') { test { fail } } + options.test = :all + lambda { task('test').invoke rescue nil }.should run_tasks('foo:p1:test', 'foo:p2:test', 'foo:p3:test', 'bar:test') + end + + it 'should ignore failure in subprojects if environment variable test is \'all\'' do + define('foo') { + define('p1') { test { fail } } + define('p2') { test { } } + define('p3') { test { fail } } + } + define('bar') { test { fail } } + ENV['test'] = 'all' + lambda { task('test').invoke rescue nil }.should run_tasks('foo:p1:test', 'foo:p2:test', 'foo:p3:test', 'bar:test') + end + + it 'should ignore failure if options.test is :all and target is build task ' do + define('foo') { test { fail } } + define('bar') { test { fail } } + options.test = :all + lambda { task('build').invoke rescue nil }.should run_tasks('foo:test', 'bar:test') + end + + it 'should ignore failure if environment variable test is \'all\'' do + define('foo') { test { fail } } + define('bar') { test { fail } } + ENV['test'] = 'all' + lambda { task('test').invoke rescue nil }.should run_tasks('foo:test', 'bar:test') + end + + it 'should ignore failure if environment variable TEST is \'all\'' do + define('foo') { test { fail } } + define('bar') { test { fail } } + ENV['TEST'] = 'all' + lambda { task('test').invoke rescue nil }.should run_tasks('foo:test', 'bar:test') + end + + it 'should execute no tests if options.test is false' do + define('foo') { test { fail } } + define('bar') { test { fail } } + options.test = false + lambda { task('test').invoke rescue nil }.should_not run_tasks('foo:test', 'bar:test') + end + + it 'should execute no tests if environment variable test is \'no\'' do + define('foo') { test { fail } } + define('bar') { test { fail } } + ENV['test'] = 'no' + lambda { task('test').invoke rescue nil }.should_not run_tasks('foo:test', 'bar:test') + end + + it 'should execute no tests if environment variable TEST is \'no\'' do + define('foo') { test { fail } } + define('bar') { test { fail } } + ENV['TEST'] = 'no' + lambda { task('test').invoke rescue nil }.should_not run_tasks('foo:test', 'bar:test') + end + + it "should not compile tests if environment variable test is 'no'" do + write "src/test/java/HelloTest.java", "public class HelloTest { public void testTest() {}}" + define('foo') { test { fail } } + ENV['test'] = 'no' + lambda { task('test').invoke rescue nil }.should_not run_tasks('foo:test:compile') + end +end + +describe 'test rule' do + include TestHelper + + it 'should execute test task on local project' do + define('foo') { define 'bar' } + lambda { task('test:something').invoke }.should run_task('foo:test') + end + + it 'should reset tasks to specific pattern' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'nothing']) } + define 'bar' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'nothing']) } + end + end + task('test:something').invoke + ['foo', 'foo:bar'].map { |name| project(name) }.each do |project| + project.test.tests.should include('something') + project.test.tests.should_not include('nothing') + end + end + + it 'should apply *name* pattern' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['prefix-something-suffix']) } + end + task('test:something').invoke + project('foo').test.tests.should include('prefix-something-suffix') + end + + it 'should not apply *name* pattern if asterisks used' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['prefix-something', 'prefix-something-suffix']) } + end + task('test:*something').invoke + project('foo').test.tests.should include('prefix-something') + project('foo').test.tests.should_not include('prefix-something-suffix') + end + + it 'should accept multiple tasks separated by commas' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['foo', 'bar', 'baz']) } + end + task('test:foo,bar').invoke + project('foo').test.tests.should include('foo') + project('foo').test.tests.should include('bar') + project('foo').test.tests.should_not include('baz') + end + + it 'should execute only the named tests' do + write 'src/test/java/TestSomething.java', + 'public class TestSomething extends junit.framework.TestCase { public void testNothing() {} }' + write 'src/test/java/TestFails.java', + 'public class TestFails extends junit.framework.TestCase { public void testFailure() { fail(); } }' + define 'foo' + task('test:Something').invoke + end + + it 'should execute the named tests even if the test task is not needed' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'nothing']) } + end + touch_last_successful_test_run project('foo').test + task('test:something').invoke + project('foo').test.tests.should include('something') + end + + it 'should not execute excluded tests' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'nothing']) } + end + task('test:*,-nothing').invoke + project('foo').test.tests.should include('something') + project('foo').test.tests.should_not include('nothing') + end + + it 'should not execute tests in excluded package' do + write 'src/test/java/com/example/foo/TestSomething.java', + 'package com.example.foo; public class TestSomething extends junit.framework.TestCase { public void testNothing() {} }' + write 'src/test/java/com/example/bar/TestFails.java', + 'package com.example.bar; public class TestFails extends junit.framework.TestCase { public void testFailure() { fail(); } }' + define 'foo' do + test.using(:junit) + end + task('test:-com.example.bar').invoke + project('foo').test.tests.should include('com.example.foo.TestSomething') + project('foo').test.tests.should_not include('com.example.bar.TestFails') + end + + it 'should not execute excluded tests with wildcards' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'nothing']) } + end + task('test:something,-s*,-n*').invoke + project('foo').test.tests.should_not include('something') + project('foo').test.tests.should_not include('nothing') + end + + it 'should execute all tests except excluded tests' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'anything', 'nothing']) } + end + task('test:-nothing').invoke + project('foo').test.tests.should include('something', 'anything') + project('foo').test.tests.should_not include('nothing') + end + + it 'should ignore exclusions in buildfile' do + define 'foo' do + test.using(:junit) + test.exclude 'something' + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'anything', 'nothing']) } + end + task('test:-nothing').invoke + project('foo').test.tests.should include('something', 'anything') + project('foo').test.tests.should_not include('nothing') + end + + it 'should ignore inclusions in buildfile' do + define 'foo' do + test.using(:junit) + test.include 'something' + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'nothing']) } + end + task('test:nothing').invoke + project('foo').test.tests.should include('nothing') + project('foo').test.tests.should_not include('something') + end + + it 'should not execute a test if it''s both included and excluded' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['nothing']) } + end + task('test:nothing,-nothing').invoke + project('foo').test.tests.should_not include('nothing') + end + + it 'should not update the last successful test run timestamp' do + define 'foo' do + test.using(:junit) + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'nothing']) } + end + a_second_ago = Time.now - 1 + touch_last_successful_test_run project('foo').test, a_second_ago + task('test:something').invoke + project('foo').test.timestamp.should <= a_second_ago + end +end + +describe 'test failed' do + include TestHelper + + def test_task + @test_task ||= begin + define 'foo' do + test.using(:junit) + test.instance_eval do + @framework.stub!(:tests).and_return(['FailingTest', 'PassingTest']) + @framework.stub!(:run).and_return(['PassingTest']) + end + end + project('foo').test + end + end + + it 'should run the tests that failed the last time' do + define 'foo' do + test.using(:junit) + test.instance_eval do + @framework.stub!(:tests).and_return(['FailingTest', 'PassingTest']) + @framework.stub!(:run).and_return(['PassingTest']) + end + end + write project('foo').path_to(:target, "junit-failed"), "FailingTest" + task('test:failed').invoke rescue nil + project('foo').test.tests.should include('FailingTest') + project('foo').test.tests.should_not include('PassingTest') + end + + it 'should run failed tests, respecting excluded tests' do + define 'foo' do + test.using(:junit).exclude('ExcludedTest') + test.instance_eval do + @framework.stub!(:tests).and_return(['FailingTest', 'PassingTest', 'ExcludedTest']) + @framework.stub!(:run).and_return(['PassingTest']) + end + end + write project('foo').path_to(:target, "junit-failed"), "FailingTest\nExcludedTest" + task('test:failed').invoke rescue nil + project('foo').test.tests.should include('FailingTest') + project('foo').test.tests.should_not include('ExcludedTest') + end + + it 'should run only the tests that failed the last time, even when failed tests have dependencies' do + define 'parent' do + define 'foo' do + test.using(:junit) + test.instance_eval do + @framework.stub!(:tests).and_return(['PassingTest']) + @framework.stub!(:run).and_return(['PassingTest']) + end + end + define 'bar' do + test.using(:junit) + test.enhance ["parent:foo:test"] + test.instance_eval do + @framework.stub!(:tests).and_return(['FailingTest', 'PassingTest']) + @framework.stub!(:run).and_return(['PassingTest']) + end + end + end + write project('parent:bar').path_to(:target, "junit-failed"), "FailingTest" + task('test:failed').invoke rescue nil + project('parent:foo').test.tests.should_not include('PassingTest') + project('parent:bar').test.tests.should include('FailingTest') + project('parent:bar').test.tests.should_not include('PassingTest') + end + +end + + +describe Buildr::Options, 'test' do + it 'should be true by default' do + Buildr.options.test.should be_true + end + + ['skip', 'no', 'off', 'false'].each do |value| + it "should be false if test environment variable is '#{value}'" do + lambda { ENV['test'] = value }.should change { Buildr.options.test }.to(false) + end + end + + ['skip', 'no', 'off', 'false'].each do |value| + it "should be false if TEST environment variable is '#{value}'" do + lambda { ENV['TEST'] = value }.should change { Buildr.options.test }.to(false) + end + end + + it 'should be :all if test environment variable is all' do + lambda { ENV['test'] = 'all' }.should change { Buildr.options.test }.to(:all) + end + + it 'should be :all if TEST environment variable is all' do + lambda { ENV['TEST'] = 'all' }.should change { Buildr.options.test }.to(:all) + end + + it 'should be true and warn for any other value' do + ENV['TEST'] = 'funky' + lambda { Buildr.options.test.should be(true) }.should show_warning(/expecting the environment variable/i) + end +end + + +describe Buildr, 'integration' do + it 'should return the same task from all contexts' do + task = task('integration') + define 'foo' do + integration.should be(task) + define 'bar' do + integration.should be(task) + end + end + integration.should be(task) + end + + it 'should respond to :setup and return setup task' do + setup = integration.setup + define('foo') { integration.setup.should be(setup) } + end + + it 'should respond to :setup and add prerequisites to integration:setup' do + define('foo') { integration.setup 'prereq' } + integration.setup.prerequisites.should include('prereq') + end + + it 'should respond to :setup and add action for integration:setup' do + action = task('action') + define('foo') { integration.setup { action.invoke } } + lambda { integration.setup.invoke }.should run_tasks(action) + end + + it 'should respond to :teardown and return teardown task' do + teardown = integration.teardown + define('foo') { integration.teardown.should be(teardown) } + end + + it 'should respond to :teardown and add prerequisites to integration:teardown' do + define('foo') { integration.teardown 'prereq' } + integration.teardown.prerequisites.should include('prereq') + end + + it 'should respond to :teardown and add action for integration:teardown' do + action = task('action') + define('foo') { integration.teardown { action.invoke } } + lambda { integration.teardown.invoke }.should run_tasks(action) + end +end + + +describe Rake::Task, 'integration' do + it 'should be a local task' do + define('foo') { test.using :integration } + define('bar', :base_dir=>'other') { test.using :integration } + lambda { task('integration').invoke }.should run_task('foo:test').but_not('bar:test') + end + + it 'should be a recursive task' do + define 'foo' do + test.using :integration + define('bar') { test.using :integration } + end + lambda { task('integration').invoke }.should run_tasks('foo:test', 'foo:bar:test') + end + + it 'should find nested integration tests' do + define 'foo' do + define('bar') { test.using :integration } + end + lambda { task('integration').invoke }.should run_tasks('foo:bar:test').but_not('foo:test') + end + + it 'should ignore nested regular tasks' do + define 'foo' do + test.using :integration + define('bar') { test.using :integration=>false } + end + lambda { task('integration').invoke }.should run_tasks('foo:test').but_not('foo:bar:test') + end + + it 'should agree not to run the same tasks as test' do + define 'foo' do + define 'bar' do + test.using :integration + define('baz') { test.using :integration=>false } + end + end + lambda { task('test').invoke }.should run_tasks('foo:test', 'foo:bar:baz:test').but_not('foo:bar:test') + lambda { task('integration').invoke }.should run_tasks('foo:bar:test').but_not('foo:test', 'foo:bar:baz:test') + end + + it 'should run setup task before any project integration tests' do + define('foo') { test.using :integration } + define('bar') { test.using :integration } + lambda { task('integration').invoke }.should run_tasks([integration.setup, 'bar:test'], [integration.setup, 'foo:test']) + end + + it 'should run teardown task after all project integrations tests' do + define('foo') { test.using :integration } + define('bar') { test.using :integration } + lambda { task('integration').invoke }.should run_tasks(['bar:test', integration.teardown], ['foo:test', integration.teardown]) + end + + it 'should run test cases marked for integration' do + write 'src/test/java/FailingTest.java', + 'public class FailingTest extends junit.framework.TestCase { public void testNothing() { assertTrue(false); } }' + define('foo') { test.using :integration } + lambda { task('test').invoke }.should_not raise_error + lambda { task('integration').invoke }.should raise_error(RuntimeError, /tests failed/i) + end + + it 'should run setup and teardown tasks marked for integration' do + define('foo') { test.using :integration } + lambda { task('test').invoke }.should run_tasks().but_not('foo:test:setup', 'foo:test:teardown') + lambda { task('integration').invoke }.should run_tasks('foo:test:setup', 'foo:test:teardown') + end + + it 'should run test actions marked for integration' do + task 'action' + define 'foo' do + test.using :integration, :junit + end + lambda { task('test').invoke }.should_not change { project('foo').test.passed_tests } + lambda { task('integration').invoke }.should change { project('foo').test.passed_tests } + project('foo').test.passed_tests.should be_empty + end + + it 'should not fail if test=all' do + write 'src/test/java/FailingTest.java', + 'public class FailingTest extends junit.framework.TestCase { public void testNothing() { assertTrue(false); } }' + define('foo') { test.using :integration } + options.test = :all + lambda { task('integration').invoke }.should_not raise_error + end + + it 'should execute by local package task' do + define 'foo', :version=>'1.0' do + test.using :integration + package :jar + end + lambda { task('package').invoke }.should run_tasks(['foo:package', 'foo:test']) + end + + it 'should execute by local package task along with unit tests' do + define 'foo', :version=>'1.0' do + test.using :integration + package :jar + define('bar') { test.using :integration=>false } + end + lambda { task('package').invoke }.should run_tasks(['foo:package', 'foo:test'], + ['foo:bar:test', 'foo:bar:package']) + end + + it 'should not execute by local package task if test=no' do + define 'foo', :version=>'1.0' do + test.using :integration + package :jar + end + options.test = false + lambda { task('package').invoke }.should run_task('foo:package').but_not('foo:test') + end +end + + +describe 'integration rule' do + it 'should execute integration tests on local project' do + define 'foo' do + test.using :junit, :integration + define 'bar' + end + lambda { task('integration:something').invoke }.should run_task('foo:test') + end + + it 'should reset tasks to specific pattern' do + define 'foo' do + test.using :junit, :integration + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'nothing']) } + define 'bar' do + test.using :junit, :integration + test.instance_eval { @framework.stub!(:tests).and_return(['something', 'nothing']) } + end + end + task('integration:something').invoke + ['foo', 'foo:bar'].map { |name| project(name) }.each do |project| + project.test.tests.should include('something') + project.test.tests.should_not include('nothing') + end + end + + it 'should apply *name* pattern' do + define 'foo' do + test.using :junit, :integration + test.instance_eval { @framework.stub!(:tests).and_return(['prefix-something-suffix']) } + end + task('integration:something').invoke + project('foo').test.tests.should include('prefix-something-suffix') + end + + it 'should not apply *name* pattern if asterisks used' do + define 'foo' do + test.using :junit, :integration + test.instance_eval { @framework.stub!(:tests).and_return(['prefix-something', 'prefix-something-suffix']) } + end + task('integration:*something').invoke + project('foo').test.tests.should include('prefix-something') + project('foo').test.tests.should_not include('prefix-something-suffix') + end + + it 'should accept multiple tasks separated by commas' do + define 'foo' do + test.using :junit, :integration + test.instance_eval { @framework.stub!(:tests).and_return(['foo', 'bar', 'baz']) } + end + task('integration:foo,bar').invoke + project('foo').test.tests.should include('foo') + project('foo').test.tests.should include('bar') + project('foo').test.tests.should_not include('baz') + end + + it 'should execute only the named tests' do + write 'src/test/java/TestSomething.java', + 'public class TestSomething extends junit.framework.TestCase { public void testNothing() {} }' + write 'src/test/java/TestFails.java', + 'public class TestFails extends junit.framework.TestCase { public void testFailure() { fail(); } }' + define('foo') { test.using :junit, :integration } + task('integration:Something').invoke + end +end diff --git a/buildr/spec/core/transport_spec.rb b/buildr/spec/core/transport_spec.rb new file mode 100644 index 0000000..9526544 --- /dev/null +++ b/buildr/spec/core/transport_spec.rb @@ -0,0 +1,544 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe URI, '#download' do + before do + write @source = 'source', @content = 'Just a file' + @uri = URI(URI.escape("file://#{File.expand_path(@source)}")) + @target = 'target' + end + + it 'should download file if found' do + @uri.download @target + file(@target).should contain(@content) + end + + it 'should fail if file not found' do + lambda { (@uri + 'missing').download @target }.should raise_error(URI::NotFoundError) + file(@target).should_not exist + end + + it 'should work the same way from static method with URI' do + URI.download @uri, @target + file(@target).should contain(@content) + end + + it 'should work the same way from static method with String' do + URI.download @uri.to_s, @target + file(@target).should contain(@content) + end + + it 'should download to a task' do + @uri.download file(@target) + file(@target).should contain(@content) + end + + it 'should download to a file' do + File.open(@target, 'w') { |file| @uri.download file } + file(@target).should contain(@content) + end +end + + +describe URI, '#upload' do + before do + write @source = 'source', @content = 'Just a file' + @target = 'target' + @uri = URI(URI.escape("file://#{File.expand_path(@target)}")) + end + + it 'should preserve file permissions if uploading to a file' do + File.chmod(0666, @source) + s = File.stat(@source).mode + @uri.upload @source + File.stat(@target).mode.should eql(s) + end + + it 'should upload file if found' do + @uri.upload @source + file(@target).should contain(@content) + end + + it 'should fail if file not found' do + lambda { @uri.upload @source.ext('missing') }.should raise_error(URI::NotFoundError) + file(@target).should_not exist + end + + it 'should work the same way from static method with URI' do + URI.upload @uri, @source + file(@target).should contain(@content) + end + + it 'should work the same way from static method with String' do + URI.upload @uri.to_s, @source + file(@target).should contain(@content) + end + + it 'should upload from a task' do + @uri.upload file(@source) + file(@target).should contain(@content) + end + + it 'should create MD5 hash' do + @uri.upload file(@source) + file(@target.ext('.md5')).should contain(Digest::MD5.hexdigest(@content)) + end + + it 'should create SHA1 hash' do + @uri.upload file(@source) + file(@target.ext('.sha1')).should contain(Digest::SHA1.hexdigest(@content)) + end + + it 'should upload an entire directory' do + mkpath 'dir' ; write 'dir/test', 'in directory' + mkpath 'dir/nested' ; write 'dir/nested/test', 'in nested directory' + @uri.upload 'dir' + file(@target).should contain('test', 'nested/test') + file(@target + '/test').should contain('in directory') + file(@target + '/nested/test').should contain('in nested directory') + end +end + + +describe URI::FILE do + it 'should complain about file:' do + lambda { URI('file:') }.should raise_error(URI::InvalidURIError) + end + + it 'should accept file:something as file:///something' do + URI('file:something').should eql(URI('file:///something')) + end + + it 'should accept file:/ as file:///' do + URI('file:/').should eql(URI('file:///')) + end + + it 'should accept file:/something as file:///something' do + URI('file:/something').should eql(URI('file:///something')) + end + + it 'should complain about file://' do + lambda { URI('file://').should eql(URI('file:///')) }.should raise_error(URI::InvalidURIError) + end + + it 'should accept file://something as file://something/' do + URI('file://something').should eql(URI('file://something/')) + end + + it 'should accept file:///something' do + URI('file:///something').should be_kind_of(URI::FILE) + URI('file:///something').to_s.should eql('file:///something') + URI('file:///something').path.should eql('/something') + end + + it 'should treat host as path when host name is a Windows drive' do + URI('file://c:/something').should eql(URI('file:///c:/something')) + end +end + + +describe URI::FILE, '#read' do + before do + @filename = 'readme' + @uri = URI(URI.escape("file:///#{File.expand_path(@filename)}")) + @content = 'Readme. Please!' + write 'readme', @content + end + + it 'should not complain about excessive options' do + @uri.read :proxy=>[], :lovely=>true + end + + it 'should read the file' do + @uri.read.should eql(@content) + end + + it 'should read the file and yield to block' do + @uri.read { |content| content.should eql(@content) } + end + + it 'should raise NotFoundError if file doesn\'t exist' do + lambda { (@uri + 'notme').read }.should raise_error(URI::NotFoundError) + end + + it 'should raise NotFoundError if file is actually a directory' do + mkpath 'dir' + lambda { (@uri + 'dir').read }.should raise_error(URI::NotFoundError) + end +end + + +describe URI::FILE, '#write' do + before do + @filename = 'readme' + @uri = URI(URI.escape("file:///#{File.expand_path(@filename)}")) + @content = 'Readme. Please!' + end + + it 'should not complain about excessive options' do + @uri.write @content, :proxy=>[], :lovely=>true + end + + it 'should write the file from a string' do + @uri.write @content + read(@filename).should eql(@content) + end + + it 'should write the file from a reader' do + reader = Object.new + class << reader + def read(bytes) ; @array.pop ; end + end + reader.instance_variable_set :@array, [@content] + @uri.write reader + read(@filename).should eql(@content) + end + + it 'should write the file from a block' do + array = [@content] + @uri.write { array.pop } + read(@filename).should eql(@content) + end + + it 'should not create file if read fails' do + @uri.write { fail } rescue nil + file(@filename).should_not exist + end +end + + +describe URI::HTTP, '#read' do + before do + @proxy = 'http://john:smith@myproxy:8080' + @domain = 'domain' + @host_domain = "host.#{@domain}" + @path = "/foo/bar/baz" + @query = "?query" + @uri = URI("http://#{@host_domain}#{@path}#{@query}") + @no_proxy_args = [@host_domain, 80] + @proxy_args = @no_proxy_args + ['myproxy', 8080, 'john', 'smith'] + @http = mock('http') + @http.stub!(:request).and_yield(Net::HTTPNotModified.new(nil, nil, nil)) + end + + it 'should not use proxy unless proxy is set' do + Net::HTTP.should_receive(:new).with(*@no_proxy_args).and_return(@http) + @uri.read + end + + it 'should use HTTPS if applicable' do + Net::HTTP.should_receive(:new).with(@host_domain, 443).and_return(@http) + @http.should_receive(:use_ssl=).with(true) + URI(@uri.to_s.sub(/http/, 'https')).read + end + + it 'should use proxy from environment variable HTTP_PROXY when using http' do + ENV['HTTP_PROXY'] = @proxy + Net::HTTP.should_receive(:new).with(*@proxy_args).and_return(@http) + @uri.read + end + + it 'should use proxy from environment variable HTTPS_PROXY when using https' do + ENV['HTTPS_PROXY'] = @proxy + Net::HTTP.should_receive(:new).with(@host_domain, 443, 'myproxy', 8080, 'john', 'smith').and_return(@http) + @http.should_receive(:use_ssl=).with(true) + URI(@uri.to_s.sub(/http/, 'https')).read + end + + it 'should not use proxy for hosts from environment variable NO_PROXY' do + ENV['HTTP_PROXY'] = @proxy + ENV['NO_PROXY'] = @host_domain + Net::HTTP.should_receive(:new).with(*@no_proxy_args).and_return(@http) + @uri.read + end + + it 'should use proxy for hosts other than those specified by NO_PROXY' do + ENV['HTTP_PROXY'] = @proxy + ENV['NO_PROXY'] = 'whatever' + Net::HTTP.should_receive(:new).with(*@proxy_args).and_return(@http) + @uri.read + end + + it 'should support comma separated list in environment variable NO_PROXY' do + ENV['HTTP_PROXY'] = @proxy + ENV['NO_PROXY'] = 'optimus,prime' + Net::HTTP.should_receive(:new).with('optimus', 80).and_return(@http) + URI('http://optimus').read + Net::HTTP.should_receive(:new).with('prime', 80).and_return(@http) + URI('http://prime').read + Net::HTTP.should_receive(:new).with('bumblebee', *@proxy_args[1..-1]).and_return(@http) + URI('http://bumblebee').read + end + + it 'should support glob pattern in NO_PROXY' do + ENV['HTTP_PROXY'] = @proxy + ENV['NO_PROXY'] = "*.#{@domain}" + Net::HTTP.should_receive(:new).once.with(*@no_proxy_args).and_return(@http) + @uri.read + end + + it 'should support specific port in NO_PROXY' do + ENV['HTTP_PROXY'] = @proxy + ENV['NO_PROXY'] = "#{@host_domain}:80" + Net::HTTP.should_receive(:new).with(*@no_proxy_args).and_return(@http) + @uri.read + ENV['NO_PROXY'] = "#{@host_domain}:800" + Net::HTTP.should_receive(:new).with(*@proxy_args).and_return(@http) + @uri.read + end + + it 'should not die if content size is zero' do + ok = Net::HTTPOK.new(nil, nil, nil) + ok.stub!(:read_body) + @http.stub!(:request).and_yield(ok) + Net::HTTP.should_receive(:new).and_return(@http) + $stdout.should_receive(:isatty).and_return(false) + @uri.read :progress=>true + end + + it 'should use HTTP Basic authentication' do + Net::HTTP.should_receive(:new).and_return(@http) + request = mock('request') + Net::HTTP::Get.should_receive(:new).and_return(request) + request.should_receive(:basic_auth).with('john', 'secret') + URI("http://john:secret@#{@host_domain}").read + end + + it 'should preseve authentication information during a redirect' do + Net::HTTP.should_receive(:new).twice.and_return(@http) + + # The first request will produce a redirect + redirect = Net::HTTPRedirection.new(nil, nil, nil) + redirect['Location'] = "http://#{@host_domain}/asdf" + + request1 = mock('request1') + Net::HTTP::Get.should_receive(:new).once.with('/', nil).and_return(request1) + request1.should_receive(:basic_auth).with('john', 'secret') + @http.should_receive(:request).with(request1).and_yield(redirect) + + # The second request will be ok + ok = Net::HTTPOK.new(nil, nil, nil) + ok.stub!(:read_body) + + request2 = mock('request2') + Net::HTTP::Get.should_receive(:new).once.with("/asdf", nil).and_return(request2) + request2.should_receive(:basic_auth).with('john', 'secret') + @http.should_receive(:request).with(request2).and_yield(ok) + + URI("http://john:secret@#{@host_domain}").read + end + + it 'should include the query part when performing HTTP GET' do + # should this test be generalized or shared with any other URI subtypes? + Net::HTTP.stub!(:new).and_return(@http) + Net::HTTP::Get.should_receive(:new).with(/#{Regexp.escape(@query)}$/, nil) + @uri.read + end + +end + + +describe URI::HTTP, '#write' do + before do + @content = 'Readme. Please!' + @uri = URI('http://john:secret@host.domain/foo/bar/baz.jar') + @http = mock('Net::HTTP') + @http.stub!(:request).and_return(Net::HTTPOK.new(nil, nil, nil)) + Net::HTTP.stub!(:new).and_return(@http) + end + + it 'should open connection to HTTP server' do + Net::HTTP.should_receive(:new).with('host.domain', 80).and_return(@http) + @uri.write @content + end + + it 'should use HTTP basic authentication' do + @http.should_receive(:request) do |request| + request['authorization'].should == ('Basic ' + ['john:secret'].pack('m').delete("\r\n")) + Net::HTTPOK.new(nil, nil, nil) + end + @uri.write @content + end + + it 'should use HTTPS if applicable' do + Net::HTTP.should_receive(:new).with('host.domain', 443).and_return(@http) + @http.should_receive(:use_ssl=).with(true) + URI(@uri.to_s.sub(/http/, 'https')).write @content + end + + it 'should upload file with PUT request' do + @http.should_receive(:request) do |request| + request.should be_kind_of(Net::HTTP::Put) + Net::HTTPOK.new(nil, nil, nil) + end + @uri.write @content + end + + it 'should set Content-Length header' do + @http.should_receive(:request) do |request| + request.content_length.should == @content.size + Net::HTTPOK.new(nil, nil, nil) + end + @uri.write @content + end + + it 'should set Content-MD5 header' do + @http.should_receive(:request) do |request| + request['Content-MD5'].should == Digest::MD5.hexdigest(@content) + Net::HTTPOK.new(nil, nil, nil) + end + @uri.write @content + end + + it 'should send entire content' do + @http.should_receive(:request) do |request| + body_stream = request.body_stream + body_stream.read(1024).should == @content + body_stream.read(1024).should be_nil + Net::HTTPOK.new(nil, nil, nil) + end + @uri.write @content + end + + it 'should fail on 4xx response' do + @http.should_receive(:request).and_return(Net::HTTPBadRequest.new(nil, nil, nil)) + lambda { @uri.write @content }.should raise_error(RuntimeError, /failed to upload/i) + end + + it 'should fail on 5xx response' do + @http.should_receive(:request).and_return(Net::HTTPServiceUnavailable.new(nil, nil, nil)) + lambda { @uri.write @content }.should raise_error(RuntimeError, /failed to upload/i) + end + +end + + +describe URI::SFTP, '#read' do + before do + @uri = URI('sftp://john:secret@localhost/root/path/readme') + @content = 'Readme. Please!' + + @ssh_session = mock('Net::SSH::Session') + @sftp_session = mock('Net::SFTP::Session') + @file_factory = mock('Net::SFTP::Operations::FileFactory') + Net::SSH.stub!(:start).with('localhost', 'john', :password=>'secret', :port=>22).and_return(@ssh_session) do + Net::SFTP::Session.should_receive(:new).with(@ssh_session).and_yield(@sftp_session).and_return(@sftp_session) + @sftp_session.should_receive(:connect!).and_return(@sftp_session) + @sftp_session.should_receive(:loop) + @sftp_session.should_receive(:file).with.and_return(@file_factory) + @file_factory.stub!(:open) + @ssh_session.should_receive(:close) + @ssh_session + end + end + + it 'should open connection to SFTP server' do + @uri.read + end + + it 'should open file for reading' do + @file_factory.should_receive(:open).with('/root/path/readme', 'r') + @uri.read + end + + it 'should read contents of file and return it' do + file = mock('Net::SFTP::Operations::File') + file.should_receive(:read).with(URI::RW_CHUNK_SIZE).once.and_return(@content) + @file_factory.should_receive(:open).with('/root/path/readme', 'r').and_yield(file) + @uri.read.should eql(@content) + end + + it 'should read contents of file and pass it to block' do + file = mock('Net::SFTP::Operations::File') + file.should_receive(:read).with(URI::RW_CHUNK_SIZE).once.and_return(@content) + @file_factory.should_receive(:open).with('/root/path/readme', 'r').and_yield(file) + content = '' + @uri.read do |chunk| + content << chunk + end + content.should eql(@content) + end +end + + +describe URI::SFTP, '#write' do + before do + @uri = URI('sftp://john:secret@localhost/root/path/readme') + @content = 'Readme. Please!' + + @ssh_session = mock('Net::SSH::Session') + @sftp_session = mock('Net::SFTP::Session') + @file_factory = mock('Net::SFTP::Operations::FileFactory') + Net::SSH.stub!(:start).with('localhost', 'john', :password=>'secret', :port=>22).and_return(@ssh_session) do + Net::SFTP::Session.should_receive(:new).with(@ssh_session).and_yield(@sftp_session).and_return(@sftp_session) + @sftp_session.should_receive(:connect!).and_return(@sftp_session) + @sftp_session.should_receive(:loop) + @sftp_session.stub!(:opendir!).and_return { fail } + @sftp_session.stub!(:close) + @sftp_session.stub!(:mkdir!) + @sftp_session.should_receive(:file).with.and_return(@file_factory) + @file_factory.stub!(:open) + @ssh_session.should_receive(:close) + @ssh_session + end + end + + it 'should open connection to SFTP server' do + @uri.write @content + end + + it 'should check that path exists on server' do + paths = ['/root', '/root/path'] + @sftp_session.should_receive(:opendir!).with(anything()).twice { |path| paths.shift.should == path } + @uri.write @content + end + + it 'should close all opened directories' do + @sftp_session.should_receive(:opendir!).with(anything()).twice do |path| + @sftp_session.should_receive(:close).with(handle = Object.new) + handle + end + @uri.write @content + end + + it 'should create missing paths on server' do + @sftp_session.should_receive(:opendir!) { |path| fail unless path == '/root' } + @sftp_session.should_receive(:mkdir!).once.with('/root/path', {}) + @uri.write @content + end + + it 'should create missing directories recursively' do + paths = ['/root', '/root/path'] + @sftp_session.should_receive(:mkdir!).with(anything(), {}).twice { |path, options| paths.shift.should == path } + @uri.write @content + end + + it 'should open file for writing' do + @file_factory.should_receive(:open).with('/root/path/readme', 'w') + @uri.write @content + end + + it 'should write contents to file' do + file = mock('Net::SFTP::Operations::File') + file.should_receive(:write).with(@content) + @file_factory.should_receive(:open).with('/root/path/readme', 'w').and_yield(file) + @uri.write @content + end + +end diff --git a/buildr/spec/core/util_spec.rb b/buildr/spec/core/util_spec.rb new file mode 100644 index 0000000..a4c0b30 --- /dev/null +++ b/buildr/spec/core/util_spec.rb @@ -0,0 +1,141 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe Buildr do + describe "#replace_extension" do + it "should replace filename extensions" do + replace = lambda { |filename, ext| Util.replace_extension(filename, ext) } + + replace["foo.zip", "txt"].should eql("foo.txt") + replace["foo.", "txt"].should eql("foo.txt") + replace["foo", "txt"].should eql("foo.txt") + + replace["bar/foo.zip", "txt"].should eql("bar/foo.txt") + replace["bar/foo.", "txt"].should eql("bar/foo.txt") + replace["bar/foo", "txt"].should eql("bar/foo.txt") + end + end +end + +describe Hash do + describe "#only" do + it "should find value for one key" do + {:a => 1, :b => 2, :c => 3}.only(:a).should == {:a => 1} + end + + it "should find values for multiple keys" do + {:a => 1, :b => 2, :c => 3}.only(:b, :c).should == {:b => 2, :c => 3} + end + end +end + +describe OpenObject do + before do + @obj = OpenObject.new({:a => 1, :b => 2, :c => 3}) + end + + it "should be kind of Hash" do + Hash.should === @obj + end + + it "should accept block that supplies default value" do + obj = OpenObject.new { |hash, key| hash[key] = "New #{key}" } + obj[:foo].should == "New foo" + obj.keys.should == [:foo] + end + + it "should combine initial values from hash argument and from block" do + obj = OpenObject.new(:a => 6, :b => 2) { |h, k| h[k] = k.to_s * 2 } + obj[:a].should == 6 + obj[:c].should == 'cc' + end + + it "should allow reading a value by calling its name method" do + @obj.b.should == 2 + end + + it "should allow setting a value by calling its name= method" do + lambda { @obj.f = 32 }.should change { @obj.f }.to(32) + end + + it "should allow changing a value by calling its name= method" do + lambda { @obj.c = 17 }.should change { @obj.c }.to(17) + end + + it "should implement only method like a hash" do + @obj.only(:a).should == { :a => 1 } + end +end + +describe File do + # Quite a few of the other specs depend on File#utime working correctly. + # These specs validate that utime is working as expected. + describe "#utime" do + it "should update mtime of directories" do + mkpath 'tmp' + begin + creation_time = File.mtime('tmp') + + sleep 1 + File.utime(nil, nil, 'tmp') + + File.mtime('tmp').should > creation_time + ensure + Dir.rmdir 'tmp' + end + end + + it "should update mtime of files" do + FileUtils.touch('tmp') + begin + creation_time = File.mtime('tmp') + + sleep 1 + File.utime(nil, nil, 'tmp') + + File.mtime('tmp').should > creation_time + ensure + File.delete 'tmp' + end + end + + it "should be able to set mtime in the past" do + FileUtils.touch('tmp') + begin + time = Time.at((Time.now - 10).to_i) + File.utime(time, time, 'tmp') + + File.mtime('tmp').should == time + ensure + File.delete 'tmp' + end + end + + it "should be able to set mtime in the future" do + FileUtils.touch('tmp') + begin + time = Time.at((Time.now + 10).to_i) + File.utime(time, time, 'tmp') + + File.mtime('tmp').should == time + ensure + File.delete 'tmp' + end + end + end +end diff --git a/buildr/spec/groovy/bdd_spec.rb b/buildr/spec/groovy/bdd_spec.rb new file mode 100644 index 0000000..fd1b322 --- /dev/null +++ b/buildr/spec/groovy/bdd_spec.rb @@ -0,0 +1,80 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Buildr::Groovy::EasyB do + + def foo(*args, &prc) + define('foo', *args) do + test.using :easyb + if prc + instance_eval(&prc) + else + self + end + end + end + + it 'should apply to a project having EasyB sources' do + define('one', :base_dir => 'one') do + write _('src/spec/groovy/SomeSpecification.groovy'), 'true;' + Buildr::Groovy::EasyB.applies_to?(self).should be_true + end + define('two', :base_dir => 'two') do + write _('src/test/groovy/SomeSpecification.groovy'), 'true;' + Buildr::Groovy::EasyB.applies_to?(self).should be_false + end + define('three', :base_dir => 'three') do + write _('src/spec/groovy/SomeStory.groovy'), 'true;' + Buildr::Groovy::EasyB.applies_to?(self).should be_true + end + define('four', :base_dir => 'four') do + write _('src/test/groovy/SomeStory.groovy'), 'true;' + Buildr::Groovy::EasyB.applies_to?(self).should be_false + end + end + + it 'should be selected by :easyb name' do + foo { test.framework.should eql(:easyb) } + end + + it 'should select a java compiler if java sources are found' do + foo do + write _('src/spec/java/SomeSpecification.java'), 'public class SomeSpecification {}' + test.compile.language.should eql(:java) + end + end + + it 'should include src/spec/groovy/*Specification.groovy' do + foo do + spec = _('src/spec/groovy/SomeSpecification.groovy') + write spec, 'true' + test.invoke + test.tests.should include(spec) + end + end + + it 'should include src/spec/groovy/*Story.groovy' do + foo do + spec = _('src/spec/groovy/SomeStory.groovy') + write spec, 'true' + test.invoke + test.tests.should include(spec) + end + end + +end # EasyB diff --git a/buildr/spec/groovy/compiler_spec.rb b/buildr/spec/groovy/compiler_spec.rb new file mode 100644 index 0000000..e42bba0 --- /dev/null +++ b/buildr/spec/groovy/compiler_spec.rb @@ -0,0 +1,251 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe 'groovyc compiler' do + + it 'should identify itself from groovy source directories' do + write 'src/main/groovy/some/Hello.groovy', 'println "Hello Groovy"' + write 'src/test/groovy/some/Hello.groovy', 'println "Hello Groovy"' + define('foo') do + compile.compiler.should eql(:groovyc) + test.compile.compiler.should eql(:groovyc) + end + end + + it 'should identify if groovy sources are found on java directories' do + write 'src/main/java/some/Hello.groovy', 'println "Hello Groovy"' + write 'src/test/java/some/Hello.groovy', 'println "Hello Groovy"' + define('foo') do + compile.compiler.should eql(:groovyc) + test.compile.compiler.should eql(:groovyc) + end + end + + it 'should identify itself even if groovy and java sources are found' do + write 'src/main/java/some/Empty.java', 'package some; public interface Empty {}' + write 'src/main/groovy/some/Hello.groovy', 'println "Hello Groovy"' + write 'src/test/java/some/Empty.java', 'package some; public interface Empty {}' + write 'src/test/groovy/some/Hello.groovy', 'println "Hello Groovy"' + define('foo') do + compile.compiler.should eql(:groovyc) + test.compile.compiler.should eql(:groovyc) + end + end + + it 'should identify from custom layout' do + write 'groovy/Hello.groovy', 'println "Hello world"' + write 'testing/Hello.groovy', 'println "Hello world"' + custom = Layout.new + custom[:source, :main, :groovy] = 'groovy' + custom[:source, :test, :groovy] = 'testing' + define 'foo', :layout=>custom do + compile.compiler.should eql(:groovyc) + test.compile.compiler.should eql(:groovyc) + end + end + + it 'should identify from compile source directories' do + write 'src/com/example/Code.groovy', 'println "monkey code"' + write 'testing/com/example/Test.groovy', 'println "some test"' + define 'foo' do + lambda { compile.from 'src' }.should change { compile.compiler }.to(:groovyc) + lambda { test.compile.from 'testing' }.should change { test.compile.compiler }.to(:groovyc) + end + end + + it 'should report the multi-language as :groovy, :java' do + define('foo').compile.using(:groovyc).language.should == :groovy + end + + it 'should set the target directory to target/classes' do + define 'foo' do + lambda { compile.using(:groovyc) }.should change { compile.target.to_s }.to(File.expand_path('target/classes')) + end + end + + it 'should not override existing target directory' do + define 'foo' do + compile.into('classes') + lambda { compile.using(:groovyc) }.should_not change { compile.target } + end + end + + it 'should not change existing list of sources' do + define 'foo' do + compile.from('sources') + lambda { compile.using(:groovyc) }.should_not change { compile.sources } + end + end + + it 'should compile groovy sources' do + write 'src/main/groovy/some/Example.groovy', 'package some; class Example { static main(args) { println "Hello" } }' + define('foo').compile.invoke + file('target/classes/some/Example.class').should exist + end + + it 'should compile test groovy sources that rely on junit' do + write 'src/main/groovy/some/Example.groovy', 'package some; class Example { static main(args) { println "Hello" } }' + write 'src/test/groovy/some/ExampleTest.groovy', "package some\n import junit.framework.TestCase\n class ExampleTest extends TestCase { public testHello() { println \"Hello\" } }" + foo = define('foo') do + test.using :junit + end + foo.test.compile.invoke + file('target/classes/some/Example.class').should exist + file('target/test/classes/some/ExampleTest.class').should exist + end + + it 'should include as classpath dependency' do + write 'src/bar/groovy/some/Foo.groovy', 'package some; interface Foo {}' + write 'src/main/groovy/some/Example.groovy', 'package some; class Example implements Foo { }' + define('bar', :version => '1.0') do + compile.from('src/bar/groovy').into('target/bar') + package(:jar) + end + lambda { define('foo').compile.with(project('bar').package(:jar)).invoke }.should run_task('foo:compile') + file('target/classes/some/Example.class').should exist + end + + it 'should cross compile java sources' do + write 'src/main/java/some/Foo.java', 'package some; public interface Foo { public void hello(); }' + write 'src/main/java/some/Baz.java', 'package some; public class Baz extends Bar { }' + write 'src/main/groovy/some/Bar.groovy', 'package some; class Bar implements Foo { def void hello() { } }' + define('foo').compile.invoke + %w{Foo Bar Baz}.each { |f| file("target/classes/some/#{f}.class").should exist } + end + + it 'should cross compile test java sources' do + write 'src/test/java/some/Foo.java', 'package some; public interface Foo { public void hello(); }' + write 'src/test/java/some/Baz.java', 'package some; public class Baz extends Bar { }' + write 'src/test/groovy/some/Bar.groovy', 'package some; class Bar implements Foo { def void hello() { } }' + define('foo').test.compile.invoke + %w{Foo Bar Baz}.each { |f| file("target/test/classes/some/#{f}.class").should exist } + end + + it 'should package classes into a jar file' do + write 'src/main/groovy/some/Example.groovy', 'package some; class Example { }' + define('foo', :version => '1.0').package.invoke + file('target/foo-1.0.jar').should exist + Zip::ZipFile.open(project('foo').package(:jar).to_s) do |jar| + jar.file.exist?('some/Example.class').should be_true + end + end + +end + +describe 'groovyc compiler options' do + + def groovyc(&prc) + define('foo') do + compile.using(:groovyc) + @compiler = compile.instance_eval { @compiler } + class << @compiler + public :groovyc_options, :javac_options + end + if block_given? + instance_eval(&prc) + else + return compile + end + end + project('foo').compile + end + + it 'should set warning option to false by default' do + groovyc do + compile.options.warnings.should be_false + @compiler.javac_options[:nowarn].should be_true + end + end + + it 'should set warning option to true when running with --verbose option' do + verbose true + groovyc do + compile.options.warnings.should be_true + @compiler.javac_options[:nowarn].should be_false + end + end + + it 'should not set verbose option by default' do + groovyc.options.verbose.should be_false + end + + it 'should set verbose option when running with --trace=groovyc option' do + Buildr.application.options.trace_categories = [:groovyc] + groovyc.options.verbose.should be_true + end + + it 'should set debug option to false based on Buildr.options' do + Buildr.options.debug = false + groovyc.options.debug.should be_false + end + + it 'should set debug option to false based on debug environment variable' do + ENV['debug'] = 'no' + groovyc.options.debug.should be_false + end + + it 'should set debug option to false based on DEBUG environment variable' do + ENV['DEBUG'] = 'no' + groovyc.options.debug.should be_false + end + + it 'should set deprecation option to false by default' do + groovyc.options.deprecation.should be_false + end + + it 'should use deprecation argument when deprecation is true' do + groovyc do + compile.using(:deprecation=>true) + compile.options.deprecation.should be_true + @compiler.javac_options[:deprecation].should be_true + end + end + + it 'should not use deprecation argument when deprecation is false' do + groovyc do + compile.using(:deprecation=>false) + compile.options.deprecation.should be_false + @compiler.javac_options[:deprecation].should_not be_true + end + end + + it 'should set optimise option to false by default' do + groovyc.options.optimise.should be_false + end + + it 'should use optimize argument when deprecation is true' do + groovyc do + compile.using(:optimise=>true) + @compiler.javac_options[:optimize].should be_true + end + end + + it 'should not use optimize argument when deprecation is false' do + groovyc do + compile.using(:optimise=>false) + @compiler.javac_options[:optimize].should be_false + end + end + + after do + Buildr.options.debug = nil + ENV.delete "debug" + ENV.delete "DEBUG" + end + +end diff --git a/buildr/spec/groovy/doc_spec.rb b/buildr/spec/groovy/doc_spec.rb new file mode 100644 index 0000000..5647f92 --- /dev/null +++ b/buildr/spec/groovy/doc_spec.rb @@ -0,0 +1,65 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe "Groovydoc" do + + it 'should pick :windowtitle from project name by default' do + define 'foo' do + doc.using :groovydoc + + define 'bar' do + doc.using :groovydoc + end + end + + project('foo').doc.options[:windowtitle].should eql('foo') + project('foo:bar').doc.options[:windowtitle].should eql('foo:bar') + end + + it 'should pick :windowtitle from project description by default, if available' do + desc 'My App' + define 'foo' do + doc.using :groovydoc + end + project('foo').doc.options[:windowtitle].should eql('My App') + end + + it 'should not override explicit :windowtitle option' do + define 'foo' do + doc.using :groovydoc + doc.using :windowtitle => 'explicit' + end + project('foo').doc.options[:windowtitle].should eql('explicit') + end + + it 'should identify itself from groovy source directories' do + write 'src/main/groovy/some/A.java', 'package some; public class A {}' + write 'src/main/groovy/some/B.groovy', 'package some; public class B {}' + define('foo') do + doc.engine.should be_a(Buildr::Doc::Groovydoc) + end + end + + it 'should produce Groovydocs' do + write 'src/main/groovy/some/A.java', 'package some; public class A {}' + write 'src/main/groovy/some/B.groovy', 'package some; public class B {}' + define('foo') + project('foo').doc.invoke + file('target/doc/index.html').should exist + end +end diff --git a/buildr/spec/ide/eclipse_spec.rb b/buildr/spec/ide/eclipse_spec.rb new file mode 100644 index 0000000..d8334ac --- /dev/null +++ b/buildr/spec/ide/eclipse_spec.rb @@ -0,0 +1,739 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +JAVA_CONTAINER = Buildr::Eclipse::Java::CONTAINER +SCALA_CONTAINER = Buildr::Eclipse::Scala::CONTAINER +PLUGIN_CONTAINER = Buildr::Eclipse::Plugin::CONTAINER + +JAVA_NATURE = Buildr::Eclipse::Java::NATURE +SCALA_NATURE = Buildr::Eclipse::Scala::NATURE +PLUGIN_NATURE = Buildr::Eclipse::Plugin::NATURE + +JAVA_BUILDER = Buildr::Eclipse::Java::BUILDER +SCALA_BUILDER = Buildr::Eclipse::Scala::BUILDER +PLUGIN_BUILDERS = Buildr::Eclipse::Plugin::BUILDERS + + +module EclipseHelper + def classpath_xml_elements + task('eclipse').invoke + File.open('.classpath') { |f| REXML::Document.new(f).root.elements } + end + + def classpath_sources(attribute='path') + classpath_xml_elements.collect("classpathentry[@kind='src']") { |n| n.attributes[attribute] } + end + + # + def classpath_specific_output(path) + specific_output = classpath_xml_elements.collect("classpathentry[@path='#{path}']") { |n| n.attributes['output'] } + raise "expected: one output attribute for path '#{path}, got: #{specific_output.inspect}" if specific_output.length > 1 + specific_output[0] + end + + # + def classpath_default_output + default_output = classpath_xml_elements.collect("classpathentry[@kind='output']") { |n| n.attributes['path'] } + raise "expected: one path attribute for kind='output', got: #{default_output.inspect}" if default_output.length > 1 + default_output[0] + end + + # + def sourcepath_for_path(path) + classpath_xml_elements.collect("classpathentry[@kind='var',@path='#{path}']") do |n| + n.attributes['sourcepath'] || 'no source artifact' + end + end + + # + def javadocpath_for_path(path) + classpath_xml_elements.collect("classpathentry[@kind='var',@path='#{path}']") do |n| + n.attributes['javadocpath'] || 'no javadoc artifact' + end + end + + def project_xml_elements + task('eclipse').invoke + File.open('.project') { |f| REXML::Document.new(f).root.elements } + end + + def project_natures + project_xml_elements.collect("natures/nature") { |n| n.text } + end + + def build_commands + project_xml_elements.collect("buildSpec/buildCommand/name") { |n| n.text } + end + + def classpath_containers(attribute='path') + classpath_xml_elements.collect("classpathentry[@kind='con']") { |n| n.attributes[attribute] } + end +end + + +describe Buildr::Eclipse do + include EclipseHelper + + describe "eclipse's .project file" do + + describe 'default project' do + before do + write 'buildfile' + write 'src/main/nono/Main.nono' + end + + it 'should not have natures' do + define('foo') + project_natures.should be_empty + end + + it 'should not have build commands' do + define('foo') + build_commands.should be_empty + end + + it 'should generate a .project file' do + define('foo') + task('eclipse').invoke + File.open('.project') do |f| + REXML::Document.new(f).root. + elements.collect("name") { |e| e.text }.should == ['foo'] + end + end + + it 'should use eclipse project name if specified' do + define('foo') { eclipse.name = 'bar' } + task('eclipse').invoke + File.open('.project') do |f| + REXML::Document.new(f).root. + elements.collect("name") { |e| e.text }.should == ['bar'] + end + end + + it 'should not generate a .classpath file' do + define('foo') + task('eclipse').invoke + File.exists?('.classpath').should be_false + end + end + + describe 'parent project' do + before do + write 'buildfile' + mkpath 'bar' + end + + it 'should not generate a .project for the parent project' do + define('foo') do + define('bar') + end + task('eclipse').invoke + File.exists?('.project').should be_false + File.exists?(File.join('bar','.project')).should be_true + end + end + + describe 'java project' do + before do + write 'buildfile' + write 'src/main/java/Main.java' + end + + it 'should have Java nature' do + define('foo') + project_natures.should include(JAVA_NATURE) + end + + it 'should have Java build command' do + define('foo') + build_commands.should include(JAVA_BUILDER) + end + end + + describe 'nested java project' do + + it 'should have name corresponding to its project definition' do + mkdir 'foo' + define('myproject') { + project.version = '1.0' + define('foo') { compile.using(:javac); package :jar } + } + task('eclipse').invoke + File.open(File.join('foo', '.project')) do |f| + REXML::Document.new(f).root. + elements.collect("name") { |e| e.text }.should == ['myproject-foo'] + end + end + + it 'should use eclipse name for child project if set' do + mkdir 'foo' + define('myproject') { + project.version = '1.0' + define('foo') { eclipse.name = 'bar'; compile.using(:javac); package :jar } + } + task('eclipse').invoke + File.open(File.join('foo', '.project')) do |f| + REXML::Document.new(f).root. + elements.collect("name") { |e| e.text }.should == ['bar'] + end + end + + it 'should use short name for child project if eclipse.options.short_names = true' do + mkdir 'foo' + define('myproject') { + project.version = '1.0' + eclipse.options.short_names = true + define('foo') { compile.using(:javac); package :jar } + } + task('eclipse').invoke + File.open(File.join('foo', '.project')) do |f| + REXML::Document.new(f).root. + elements.collect("name") { |e| e.text }.should == ['foo'] + end + end + end + + describe 'scala project' do + + before do + define 'foo' do + eclipse.natures :scala + end + end + + it 'should have Scala nature before Java nature' do + project_natures.should include(SCALA_NATURE) + project_natures.should include(JAVA_NATURE) + project_natures.index(SCALA_NATURE).should < project_natures.index(JAVA_NATURE) + end + + it 'should have Scala build command and no Java build command' do + build_commands.should include(SCALA_BUILDER) + build_commands.should_not include(JAVA_BUILDER) + end + end + + describe 'standard scala project' do + + before do + write 'buildfile' + write 'src/main/scala/Main.scala' + define 'foo' + end + + it 'should have Scala nature before Java nature' do + project_natures.should include(SCALA_NATURE) + project_natures.should include(JAVA_NATURE) + project_natures.index(SCALA_NATURE).should < project_natures.index(JAVA_NATURE) + end + + it 'should have Scala build command and no Java build command' do + build_commands.should include(SCALA_BUILDER) + build_commands.should_not include(JAVA_BUILDER) + end + end + + describe 'non-standard scala project' do + + before do + write 'buildfile' + write 'src/main/foo/Main.scala' + define 'foo' do + eclipse.natures = :scala + end + end + + it 'should have Scala nature before Java nature' do + project_natures.should include(SCALA_NATURE) + project_natures.should include(JAVA_NATURE) + project_natures.index(SCALA_NATURE).should < project_natures.index(JAVA_NATURE) + end + + it 'should have Scala build command and no Java build command' do + build_commands.should include(SCALA_BUILDER) + build_commands.should_not include(JAVA_BUILDER) + end + end + + describe 'Plugin project' do + + before do + write 'buildfile' + write 'src/main/java/Activator.java' + write 'plugin.xml' + end + + it 'should have plugin nature before Java nature' do + define('foo') + project_natures.should include(PLUGIN_NATURE) + project_natures.should include(JAVA_NATURE) + project_natures.index(PLUGIN_NATURE).should < project_natures.index(JAVA_NATURE) + end + + it 'should have plugin build commands and the Java build command' do + define('foo') + build_commands.should include(PLUGIN_BUILDERS[0]) + build_commands.should include(PLUGIN_BUILDERS[1]) + build_commands.should include(JAVA_BUILDER) + end + end + + describe 'Plugin project' do + + before do + write 'buildfile' + write 'src/main/java/Activator.java' + write 'plugin.xml' + end + + it 'should have plugin nature before Java nature' do + define('foo') + project_natures.should include(PLUGIN_NATURE) + project_natures.should include(JAVA_NATURE) + project_natures.index(PLUGIN_NATURE).should < project_natures.index(JAVA_NATURE) + end + + it 'should have plugin build commands and the Java build command' do + define('foo') + build_commands.should include(PLUGIN_BUILDERS[0]) + build_commands.should include(PLUGIN_BUILDERS[1]) + build_commands.should include(JAVA_BUILDER) + end + end + + describe 'Plugin project with META-INF/MANIFEST.MF' do + + before do + write 'buildfile' + write 'src/main/java/Activator.java' + end + + it 'should have plugin nature by default if MANIFEST.MF contains "Bundle-SymbolicName:"' do + write 'META-INF/MANIFEST.MF', <<-MANIFEST +Manifest-Version: 1.0 +Name: example/ +Specification-Title: "Examples" +Specification-Version: "1.0" +Specification-Vendor: "Acme Corp.". +Implementation-Title: "example" +Implementation-Version: "build57" +Implementation-Vendor: "Acme Corp." +Bundle-SymbolicName: acme.plugin.example +MANIFEST + define('foo') + project_natures.should include(PLUGIN_NATURE) + end + + it 'should not have plugin nature if MANIFEST.MF exists but doesn\'t contain "Bundle-SymbolicName:"' do + write 'META-INF/MANIFEST.MF', <<-MANIFEST +Manifest-Version: 1.0 +Name: example/ +Specification-Title: "Examples" +Specification-Version: "1.0" +Specification-Vendor: "Acme Corp.". +Implementation-Title: "example" +Implementation-Version: "build57" +Implementation-Vendor: "Acme Corp." +MANIFEST + define('foo') + project_natures.should_not include(PLUGIN_NATURE) + end + end + end + + describe "eclipse's .classpath file" do + + describe 'scala project' do + + before do + write 'buildfile' + write 'src/main/scala/Main.scala' + end + + it 'should have SCALA_CONTAINER before JAVA_CONTAINER' do + define('foo') + classpath_containers.should include(SCALA_CONTAINER) + classpath_containers.should include(JAVA_CONTAINER) + classpath_containers.index(SCALA_CONTAINER).should < classpath_containers.index(JAVA_CONTAINER) + end + end + + describe 'source folders' do + + before do + write 'buildfile' + write 'src/main/java/Main.java' + write 'src/test/java/Test.java' + end + + shared_examples_for 'source' do + it 'should ignore CVS and SVN files' do + define('foo') + classpath_sources('excluding').each do |excluding_attribute| + excluding = excluding_attribute.split('|') + excluding.should include('**/.svn/') + excluding.should include('**/CVS/') + end + end + end + + describe 'main code' do + it_should_behave_like 'source' + + it 'should accept to come from the default directory' do + define('foo') + classpath_sources.should include('src/main/java') + end + + it 'should accept to come from a user-defined directory' do + define('foo') { compile path_to('src/java') } + classpath_sources.should include('src/java') + end + + it 'should accept a file task as a main source folder' do + define('foo') { compile apt } + classpath_sources.should include('target/generated/apt') + end + + it 'should go to the default target directory' do + define('foo') + classpath_specific_output('src/main/java').should be(nil) + classpath_default_output.should == 'target/classes' + end + end + + describe 'test code' do + it_should_behave_like 'source' + + it 'should accept to come from the default directory' do + define('foo') + classpath_sources.should include('src/test/java') + end + + it 'should accept to come from a user-defined directory' do + define('foo') { test.compile path_to('src/test') } + classpath_sources.should include('src/test') + end + + it 'should go to the default target directory' do + define('foo') + classpath_specific_output('src/test/java').should == 'target/test/classes' + end + + it 'should accept to be the only code in the project' do + rm 'src/main/java/Main.java' + define('foo') + classpath_sources.should include('src/test/java') + end + end + + describe 'main resources' do + it_should_behave_like 'source' + + before do + write 'src/main/resources/config.xml' + end + + it 'should accept to come from the default directory' do + define('foo') + classpath_sources.should include('src/main/resources') + end + + it 'should share a classpath entry if it comes from a directory with code' do + write 'src/main/java/config.properties' + define('foo') { resources.from('src/main/java').exclude('**/*.java') } + classpath_sources.select { |path| path == 'src/main/java'}.length.should == 1 + end + + it 'should go to the default target directory' do + define('foo') + classpath_specific_output('src/main/resources').should == 'target/resources' + end + end + + describe 'test resources' do + it_should_behave_like 'source' + + before do + write 'src/test/resources/config-test.xml' + end + + it 'should accept to come from the default directory' do + define('foo') + classpath_sources.should include('src/test/resources') + end + + it 'should share a classpath entry if it comes from a directory with code' do + write 'src/test/java/config-test.properties' + define('foo') { test.resources.from('src/test/java').exclude('**/*.java') } + classpath_sources.select { |path| path == 'src/test/java'}.length.should == 1 + end + + it 'should go to the default target directory' do + define('foo') + classpath_specific_output('src/test/resources').should == 'target/test/resources' + end + end + end + + describe 'project depending on another project' do + it 'should have the underlying project in its classpath' do + mkdir 'foo' + mkdir 'bar' + define('myproject') { + project.version = '1.0' + define('foo') { package :jar } + define('bar') { compile.using(:javac).with project('foo'); } + } + task('eclipse').invoke + File.open(File.join('bar', '.classpath')) do |f| + REXML::Document.new(f).root. + elements.collect("classpathentry[@kind='src']") { |n| n.attributes['path'] }.should include('/myproject-foo') + end + end + + it 'should use eclipse name in its classpath if set' do + mkdir 'foo' + mkdir 'bar' + define('myproject') { + project.version = '1.0' + define('foo') { eclipse.name = 'eclipsefoo'; package :jar } + define('bar') { eclipse.name = 'eclipsebar'; compile.using(:javac).with project('foo'); } + } + task('eclipse').invoke + File.open(File.join('bar', '.classpath')) do |f| + REXML::Document.new(f).root. + elements.collect("classpathentry[@kind='src']") { |n| n.attributes['path'] }.should include('/eclipsefoo') + end + end + end + end + + describe 'local dependency' do + before do + write 'lib/some-local.jar' + define('foo') { compile.using(:javac).with(_('lib/some-local.jar')) } + end + + it 'should have a lib artifact reference in the .classpath file' do + classpath_xml_elements.collect("classpathentry[@kind='lib']") { |n| n.attributes['path'] }. + should include('lib/some-local.jar') + end + end + + describe 'project .classpath' do + before do + mkdir_p '../libs' + write '../libs/some-local.jar' + define('foo') do + eclipse.classpath_variables :LIBS => '../libs', :LIBS2 => '../libs2' + compile.using(:javac).with(_('../libs/some-local.jar')) + end + end + + it 'supports generating library paths with classpath variables' do + classpath_xml_elements.collect("classpathentry[@kind='var']") { |n| n.attributes['path'] }. + should include('LIBS/some-local.jar') + end + end + + describe 'generated .classes' do + before do + write 'lib/some.class' + define('foo') { compile.using(:javac).with(_('lib')) } + end + + it 'should have src reference in the .classpath file' do + classpath_xml_elements.collect("classpathentry[@kind='src']") { |n| n.attributes['path'] }. + should include('lib') + end + end + + describe 'maven2 artifact dependency' do + before do + define('foo') { compile.using(:javac).with('com.example:library:jar:2.0') } + artifact('com.example:library:jar:2.0') { |task| write task.name } + task('eclipse').invoke + end + + it 'should have a reference in the .classpath file relative to the local M2 repo' do + classpath_xml_elements.collect("classpathentry[@kind='var']") { |n| n.attributes['path'] }. + should include('M2_REPO/com/example/library/2.0/library-2.0.jar') + end + + it 'should be downloaded' do + file(artifact('com.example:library:jar:2.0').name).should exist + end + + it 'should have a source artifact reference in the .classpath file' do + sourcepath_for_path('M2_REPO/com/example/library/2.0/library-2.0.jar'). + should == ['M2_REPO/com/example/library/2.0/library-2.0-sources.jar'] + end + + it 'should have a javadoc artifact reference in the .classpath file' do + javadocpath_for_path('M2_REPO/com/example/library/2.0/library-2.0.jar'). + should == ['M2_REPO/com/example/library/2.0/library-2.0-javadoc.jar'] + end + end + + describe 'maven2 repository variable' do + it 'should be configurable' do + define('foo') do + eclipse.options.m2_repo_var = 'PROJ_REPO' + compile.using(:javac).with('com.example:library:jar:2.0') + end + artifact('com.example:library:jar:2.0') { |task| write task.name } + + task('eclipse').invoke + classpath_xml_elements.collect("classpathentry[@kind='var']") { |n| n.attributes['path'] }. + should include('PROJ_REPO/com/example/library/2.0/library-2.0.jar') + end + + it 'should pick the parent value by default' do + define('foo') do + eclipse.options.m2_repo_var = 'FOO_REPO' + define('bar') + + define('bar2') do + eclipse.options.m2_repo_var = 'BAR2_REPO' + end + end + project('foo:bar').eclipse.options.m2_repo_var.should eql('FOO_REPO') + project('foo:bar2').eclipse.options.m2_repo_var.should eql('BAR2_REPO') + end + end + + describe 'natures variable' do + it 'should be configurable' do + define('foo') do + eclipse.natures = 'dummyNature' + compile.using(:javac).with('com.example:library:jar:2.0') + end + artifact('com.example:library:jar:2.0') { |task| write task.name } + project_natures.should include('dummyNature') + end + + it 'should pick the parent value by default' do + define('foo') do + eclipse.natures = 'foo_nature' + define('bar') + + define('bar2') do + eclipse.natures = 'bar2_nature' + end + end + project('foo:bar').eclipse.natures.should include('foo_nature') + project('foo:bar2').eclipse.natures.should include('bar2_nature') + end + + it 'should handle arrays correctly' do + define('foo') do + eclipse.natures ['foo_nature', 'bar_nature'] + end + project('foo').eclipse.natures.should == ['foo_nature', 'bar_nature'] + end + end + + describe 'builders variable' do + it 'should be configurable' do + define('foo') do + eclipse.builders 'dummyBuilder' + compile.using(:javac).with('com.example:library:jar:2.0') + end + artifact('com.example:library:jar:2.0') { |task| write task.name } + build_commands.should include('dummyBuilder') + end + + it 'should pick the parent value by default' do + define('foo') do + eclipse.builders = 'foo_builder' + define('bar') + + define('bar2') do + eclipse.builders = 'bar2_builder' + end + end + project('foo:bar').eclipse.builders.should include('foo_builder') + project('foo:bar2').eclipse.builders.should include('bar2_builder') + end + + it 'should handle arrays correctly' do + define('foo') do + eclipse.builders ['foo_builder', 'bar_builder'] + end + project('foo').eclipse.builders.should == ['foo_builder', 'bar_builder'] + end + end + + describe 'classpath_containers variable' do + it 'should be configurable' do + define('foo') do + eclipse.classpath_containers = 'myOlGoodContainer' + compile.using(:javac).with('com.example:library:jar:2.0') + end + artifact('com.example:library:jar:2.0') { |task| write task.name } + classpath_containers.should include('myOlGoodContainer') + end + + it 'should pick the parent value by default' do + define('foo') do + eclipse.classpath_containers = 'foo_classpath_containers' + define('bar') + + define('bar2') do + eclipse.classpath_containers = 'bar2_classpath_containers' + end + end + project('foo:bar').eclipse.classpath_containers.should include('foo_classpath_containers') + project('foo:bar2').eclipse.classpath_containers.should include('bar2_classpath_containers') + end + + it 'should handle arrays correctly' do + define('foo') do + eclipse.classpath_containers ['foo_cc', 'bar_cc'] + end + project('foo').eclipse.classpath_containers.should == ['foo_cc', 'bar_cc'] + end + end + + describe 'exclude_libs' do + it 'should support artifacts' do + define('foo') do + compile.using(:javac).with('com.example:library:jar:2.0') + eclipse.exclude_libs += [ artifact('com.example:library:jar:2.0') ] + end + artifact('com.example:library:jar:2.0') { |task| write task.name } + + task('eclipse').invoke + classpath_xml_elements.collect("classpathentry[@kind='var']") { |n| n.attributes['path'] }. + should_not include('M2_REPO/com/example/library/2.0/library-2.0.jar') + end + it 'should support string paths' do + define('foo') do + compile.using(:javac).with _('path/to/library.jar') + eclipse.exclude_libs += [ _('path/to/library.jar') ] + end + write project('foo').path_to('path/to/library.jar') + + task('eclipse').invoke + classpath_xml_elements.collect("classpathentry[@kind='lib']") { |n| n.attributes['path'] }. + should_not include('path/to/library.jar') + end + end +end diff --git a/buildr/spec/ide/idea_spec.rb b/buildr/spec/ide/idea_spec.rb new file mode 100644 index 0000000..ca057e2 --- /dev/null +++ b/buildr/spec/ide/idea_spec.rb @@ -0,0 +1,1196 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'xpath_matchers')) + +describe Buildr::IntellijIdea do + + def invoke_generate_task + task('idea').invoke + end + + def invoke_clean_task + task('idea:clean').invoke + end + + def root_project_filename(project) + project._("#{project.name}#{Buildr::IntellijIdea::IdeaFile::DEFAULT_SUFFIX}.ipr") + end + + def root_project_xml(project) + xml_document(root_project_filename(project)) + end + + def root_module_filename(project) + project._("#{project.name}#{Buildr::IntellijIdea::IdeaFile::DEFAULT_SUFFIX}.iml") + end + + def root_module_xml(project) + xml_document(root_module_filename(project)) + end + + def subproject_module_filename(project, sub_project_name) + project._("#{sub_project_name}/#{sub_project_name}#{Buildr::IntellijIdea::IdeaFile::DEFAULT_SUFFIX}.iml") + end + + def subproject_module_xml(project, sub_project_name) + xml_document(subproject_module_filename(project, sub_project_name)) + end + + def xml_document(filename) + File.should be_exist(filename) + REXML::Document.new(File.read(filename)) + end + + def xpath_to_module + "/project/component[@name='ProjectModuleManager']/modules/module" + end + + describe "idea:clean" do + before do + write "foo.ipr" + write "foo.iml" + write "other.ipr" + write "other.iml" + mkdir_p 'bar' + write "bar/bar.iml" + write "bar/other.ipr" + write "bar/other.iml" + + @foo = define "foo" do + define "bar" + end + invoke_clean_task + end + + it "should remove the ipr file" do + File.exists?("foo.ipr").should be_false + end + + it "should remove the project iml file" do + File.exists?("foo.iml").should be_false + end + + it "should remove the subproject iml file" do + File.exists?("foo.iml").should be_false + end + + it "should not remove other iml and ipr files" do + File.exists?("other.ipr").should be_true + File.exists?("other.iml").should be_true + File.exists?("bar/other.ipr").should be_true + File.exists?("bar/other.iml").should be_true + end + end + + describe "idea task" do + + def order_entry_xpath + "/module/component[@name='NewModuleRootManager']/orderEntry" + end + + describe "with a single dependency" do + describe "of type compile" do + before do + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + compile.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "generates one exported 'module-library' orderEntry in IML" do + root_module_xml(@foo).should have_nodes("#{order_entry_xpath}[@type='module-library', @exported='']/library/CLASSES/root", 1) + end + end + + describe "with iml.main_dependencies override" do + before do + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + iml.main_dependencies << 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "generates one exported 'module-library' orderEntry in IML" do + root_module_xml(@foo).should have_nodes("#{order_entry_xpath}[@type='module-library', @exported='']/library/CLASSES/root", 1) + end + end + + describe "of type test" do + before do + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + test.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "generates one non-exported test scope 'module-library' orderEntry in IML" do + root_module_xml(@foo).should have_nodes("#{order_entry_xpath}[@type='module-library' and @exported]/library/CLASSES/root", 0) + root_module_xml(@foo).should have_nodes("#{order_entry_xpath}[@type='module-library' and @scope='TEST']/library/CLASSES/root", 1) + end + end + + describe "with iml.test_dependencies override" do + before do + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + iml.test_dependencies << 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "generates one non-exported 'module-library' orderEntry in IML" do + root_module_xml(@foo).should have_nodes("#{order_entry_xpath}[@type='module-library' and @exported]/library/CLASSES/root", 0) + root_module_xml(@foo).should have_nodes("#{order_entry_xpath}[@type='module-library']/library/CLASSES/root", 1) + end + end + + describe "with sources artifact present" do + before do + artifact('group:id:jar:1.0') { |t| write t.to_s } + artifact('group:id:jar:sources:1.0') { |t| write t.to_s } + @foo = define "foo" do + compile.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "generates 'module-library' orderEntry in IML with SOURCES specified" do + root_module_xml(@foo).should have_nodes("#{order_entry_xpath}[@type='module-library', @exported='']/library/SOURCES/root", 1) + end + end + + describe "with local_repository_env_override set to nil" do + before do + Buildr.repositories.instance_eval do + @local = @remote = @release_to = nil + end + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + iml.local_repository_env_override = nil + compile.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "generates orderEntry with absolute path for classes jar" do + root_module_xml(@foo).should match_xpath("#{order_entry_xpath}/library/CLASSES/root/@url", + "jar://$MODULE_DIR$/home/.m2/repository/group/id/1.0/id-1.0.jar!/") + end + end + describe "with local_repository_env_override set to MAVEN_REPOSITORY" do + before do + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + iml.local_repository_env_override = 'MAVEN_REPOSITORY' + compile.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "generates orderEntry with absolute path for classes jar" do + root_module_xml(@foo).should match_xpath("#{order_entry_xpath}/library/CLASSES/root/@url", + "jar://$MAVEN_REPOSITORY$/group/id/1.0/id-1.0.jar!/") + end + end + end + + describe "with multiple dependencies" do + before do + artifact('group:id:jar:1.0') { |t| write t.to_s } + artifact('group:id2:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + compile.with 'group:id:jar:1.0', 'group:id2:jar:1.0' + end + invoke_generate_task + end + + it "generates multiple 'module-library' orderEntry in IML" do + root_module_xml(@foo).should have_nodes("#{order_entry_xpath}[@type='module-library']", 2) + end + end + + describe "with a single non artifact dependency" do + before do + @foo = define "foo" do + filename = _("foo-dep.jar") + File.open(filename, "wb") { |t| write "Hello" } + compile.with filename + end + invoke_generate_task + end + + it "generates one exported 'module-library' orderEntry in IML" do + root_module_xml(@foo).should match_xpath("#{order_entry_xpath}/library/CLASSES/root/@url", + "jar://$MODULE_DIR$/foo-dep.jar!/") + end + end + + describe "with extra_modules specified" do + before do + @foo = define "foo" do + ipr.extra_modules << 'other.iml' + ipr.extra_modules << 'other_other.iml' + end + invoke_generate_task + end + + it "generate an IPR with extra modules specified" do + doc = xml_document(@foo._("foo.ipr")) + doc.should have_nodes("#{xpath_to_module}", 3) + module_ref = "$PROJECT_DIR$/foo.iml" + doc.should have_xpath("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}']") + module_ref = "$PROJECT_DIR$/other.iml" + doc.should have_xpath("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}']") + module_ref = "$PROJECT_DIR$/other_other.iml" + doc.should have_xpath("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}']") + end + end + + describe "with web and webservice facet added to root project" do + before do + @foo = define "foo" do + iml.add_facet("Web", "web") do |facet| + facet.configuration do |conf| + conf.descriptors do |desc| + desc.deploymentDescriptor :name => 'web.xml', + :url => "file://$MODULE_DIR$/src/main/webapp/WEB-INF/web.xml", + :optional => "false", :version => "2.4" + end + conf.webroots do |webroots| + webroots.root :url => "file://$MODULE_DIR$/src/main/webapp", :relative => "/" + end + end + end + iml.add_facet("WebServices Client", "WebServicesClient") do |facet| + facet.configuration "ws.engine" => "Glassfish / JAX-WS 2.X RI / Metro 1.X / JWSDP 2.0" + end + define 'bar' + end + invoke_generate_task + end + + it "generates an IML for root project with a web and webservice facet" do + doc = xml_document(@foo._("foo.iml")) + facet_xpath = "/module/component[@name='FacetManager']/facet" + doc.should have_nodes(facet_xpath, 2) + doc.should have_xpath("#{facet_xpath}[@type='web', @name='Web']") + doc.should have_xpath("#{facet_xpath}[@type='WebServicesClient', @name='WebServices Client']") + end + end + + describe "with artifacts added to root project" do + before do + @foo = define "foo" do + ipr.add_artifact("MyFancy.jar", "jar") do |xml| + xml.tag!('output-path', project._(:artifacts, "MyFancy.jar")) + xml.element :id => "module-output", :name => "foo" + end + ipr.add_artifact("MyOtherFancy.jar", "jar") do |xml| + xml.tag!('output-path', project._(:artifacts, "MyOtherFancy.jar")) + xml.element :id => "module-output", :name => "foo" + end + end + invoke_generate_task + end + + it "generates an IPR with multiple jar artifacts" do + doc = xml_document(@foo._("foo.ipr")) + facet_xpath = "/project/component[@name='ArtifactManager']/artifact" + doc.should have_nodes(facet_xpath, 2) + doc.should have_xpath("#{facet_xpath}[@type='jar', @name='MyFancy.jar']") + doc.should have_xpath("#{facet_xpath}[@type='jar', @name='MyOtherFancy.jar']") + end + end + + describe "with configurations added to root project" do + before do + @foo = define "foo" do + ipr.add_configuration("Run Contacts.html", "GWT.ConfigurationType", "GWT Configuration") do |xml| + xml.module(:name => project.iml.id) + xml.option(:name => "RUN_PAGE", :value => "Contacts.html") + xml.option(:name => "compilerParameters", :value => "-draftCompile -localWorkers 2") + xml.option(:name => "compilerMaxHeapSize", :value => "512") + + xml.RunnerSettings(:RunnerId => "Run") + xml.ConfigurationWrapper(:RunnerId => "Run") + xml.tag! :method + end + ipr.add_configuration("Run Planner.html", "GWT.ConfigurationType", "GWT Configuration") do |xml| + xml.module(:name => project.iml.id) + xml.option(:name => "RUN_PAGE", :value => "Planner.html") + xml.option(:name => "compilerParameters", :value => "-draftCompile -localWorkers 2") + xml.option(:name => "compilerMaxHeapSize", :value => "512") + + xml.RunnerSettings(:RunnerId => "Run") + xml.ConfigurationWrapper(:RunnerId => "Run") + xml.tag! :method + end + end + invoke_generate_task + end + + it "generates an IPR with multiple configurations" do + doc = xml_document(@foo._("foo.ipr")) + facet_xpath = "/project/component[@name='ProjectRunConfigurationManager']/configuration" + doc.should have_nodes(facet_xpath, 2) + doc.should have_xpath("#{facet_xpath}[@type='GWT.ConfigurationType', @name='Run Contacts.html']") + doc.should have_xpath("#{facet_xpath}[@type='GWT.ConfigurationType', @name='Run Planner.html']") + end + end + + describe "with iml.group specified" do + before do + @foo = define "foo" do + iml.group = true + define 'bar' do + define 'baz' do + + end + end + define 'rab' do + iml.group = "MyGroup" + end + end + invoke_generate_task + end + + it "generate an IPR with correct group references" do + doc = xml_document(@foo._("foo.ipr")) + doc.should have_nodes("#{xpath_to_module}", 4) + module_ref = "$PROJECT_DIR$/foo.iml" + doc.should have_xpath("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}']") + module_ref = "$PROJECT_DIR$/rab/rab.iml" + doc.should have_xpath("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}' @group = 'MyGroup']") + module_ref = "$PROJECT_DIR$/bar/bar.iml" + doc.should have_xpath("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}' @group = 'foo']") + module_ref = "$PROJECT_DIR$/bar/baz/baz.iml" + doc.should have_xpath("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}' @group = 'foo/bar']") + end + end + + describe "with a single project definition" do + describe "and default naming" do + before do + @foo = define "foo" + invoke_generate_task + end + + it "generates a single IPR" do + Dir[@foo._("**/*.ipr")].should have(1).entry + end + + it "generate an IPR in the root directory" do + File.should be_exist(@foo._("foo.ipr")) + end + + it "generates a single IML" do + Dir[@foo._("**/*.iml")].should have(1).entry + end + + it "generates an IML in the root directory" do + File.should be_exist(@foo._("foo.iml")) + end + + it "generate an IPR with the reference to correct module file" do + File.should be_exist(@foo._("foo.ipr")) + doc = xml_document(@foo._("foo.ipr")) + module_ref = "$PROJECT_DIR$/foo.iml" + doc.should have_nodes("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}']", 1) + end + end + + describe "with no_iml generation disabled" do + before do + @foo = define "foo" do + project.no_iml + end + invoke_generate_task + end + + it "generates no IML" do + Dir[@foo._("**/*.iml")].should have(0).entry + end + + it "generate an IPR with no references" do + File.should be_exist(@foo._("foo.ipr")) + doc = xml_document(@foo._("foo.ipr")) + doc.should have_nodes("#{xpath_to_module}", 0) + end + end + + describe "with ipr generation disabled" do + before do + @foo = define "foo" do + project.no_ipr + end + invoke_generate_task + end + + it "generates a single IML" do + Dir[@foo._("**/*.iml")].should have(1).entry + end + + it "generate no IPR" do + File.should_not be_exist(@foo._("foo.ipr")) + end + end + + describe "and id overrides" do + before do + @foo = define "foo" do + ipr.id = 'fooble' + iml.id = 'feap' + define "bar" do + iml.id = "baz" + end + end + invoke_generate_task + end + + it "generate an IPR in the root directory" do + File.should be_exist(@foo._("fooble.ipr")) + end + + it "generates an IML in the root directory" do + File.should be_exist(@foo._("feap.iml")) + end + + it "generates an IML in the subproject directory" do + File.should be_exist(@foo._("bar/baz.iml")) + end + + it "generate an IPR with the reference to correct module file" do + File.should be_exist(@foo._("fooble.ipr")) + doc = xml_document(@foo._("fooble.ipr")) + module_ref = "$PROJECT_DIR$/feap.iml" + doc.should have_nodes("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}']", 1) + end + end + + describe "and a suffix defined" do + before do + @foo = define "foo" do + ipr.suffix = '-ipr-suffix' + iml.suffix = '-iml-suffix' + end + invoke_generate_task + end + + it "generate an IPR in the root directory" do + File.should be_exist(@foo._("foo-ipr-suffix.ipr")) + end + + it "generates an IML in the root directory" do + File.should be_exist(@foo._("foo-iml-suffix.iml")) + end + + it "generate an IPR with the reference to correct module file" do + File.should be_exist(@foo._("foo-ipr-suffix.ipr")) + doc = xml_document(@foo._("foo-ipr-suffix.ipr")) + doc.should have_nodes("#{xpath_to_module}", 1) + module_ref = "$PROJECT_DIR$/foo-iml-suffix.iml" + doc.should have_nodes("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}']", 1) + end + end + end + + describe "with a subproject" do + before do + @foo = define "foo" do + define 'bar' + end + invoke_generate_task + end + + it "creates the subproject directory" do + File.should be_exist(@foo._("bar")) + end + + it "generates an IML in the subproject directory" do + File.should be_exist(@foo._("bar/bar.iml")) + end + + it "generate an IPR with the reference to correct module file" do + File.should be_exist(@foo._("foo.ipr")) + doc = xml_document(@foo._("foo.ipr")) + doc.should have_nodes("#{xpath_to_module}", 2) + module_ref = "$PROJECT_DIR$/foo.iml" + doc.should have_nodes("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}']", 1) + module_ref = "$PROJECT_DIR$/bar/bar.iml" + doc.should have_nodes("#{xpath_to_module}[@fileurl='file://#{module_ref}', @filepath='#{module_ref}']", 1) + end + end + + describe "with base_dir specified" do + before do + @foo = define "foo" do + define('bar', :base_dir => 'fe') do + define('baz', :base_dir => 'fi') do + define('foe') + end + define('fum') + end + end + invoke_generate_task + end + + it "generates a subproject IML in the specified directory" do + File.should be_exist(@foo._("fe/bar.iml")) + end + + it "generates a sub-subproject IML in the specified directory" do + File.should be_exist(@foo._("fi/baz.iml")) + end + + it "generates a sub-sub-subproject IML that inherits the specified directory" do + File.should be_exist(@foo._("fi/foe/foe.iml")) + end + + it "generates a sub-subproject IML that inherits the specified directory" do + File.should be_exist(@foo._("fe/fum/fum.iml")) + end + + it "generate an IPR with the references to correct module files" do + doc = xml_document(@foo._("foo.ipr")) + doc.should have_nodes("#{xpath_to_module}", 5) + ["foo.iml", "fe/bar.iml", "fi/baz.iml", "fi/foe/foe.iml", "fe/fum/fum.iml"].each do |module_ref| + doc.should have_nodes("#{xpath_to_module}[@fileurl='file://$PROJECT_DIR$/#{module_ref}', @filepath='$PROJECT_DIR$/#{module_ref}']", 1) + end + end + end + + describe "with extensive intermodule dependencies" do + before do + mkdir_p 'foo/src/main/resources' + mkdir_p 'foo/src/main/java/foo' + touch 'foo/src/main/java/foo/Foo.java' # needed so that buildr will treat as a java project + artifact('group:id:jar:1.0') { |t| write t.to_s } + define "root" do + repositories.remote << 'http://mirrors.ibiblio.org/pub/mirrors/maven2/' + project.version = "2.5.2" + define 'foo' do + resources.from _(:source, :main, :resources) + compile.with 'group:id:jar:1.0' + test.using(:junit) + package(:jar) + end + + define 'bar' do + # internally transitive dependencies on foo, both runtime and test + compile.with project('root:foo'), project('root:foo').compile.dependencies + test.using(:junit).with [project('root:foo').test.compile.target, + project('root:foo').test.resources.target, + project('root:foo').test.compile.dependencies].compact + package(:jar) + end + end + + invoke_generate_task + @bar_iml = xml_document(project('root:bar')._('bar.iml')) + @bar_lib_urls = @bar_iml.get_elements("//orderEntry[@type='module-library']/library/CLASSES/root").collect do |root| + root.attribute('url').to_s + end + end + + it "depends on the associated module exactly once" do + @bar_iml.should have_nodes("//orderEntry[@type='module', @module-name='foo', @exported=""]", 1) + end + + it "does not depend on the other project's packaged JAR" do + @bar_lib_urls.grep(%r{#{project('root:foo').packages.first}}).should == [] + end + + it "does not depend on the the other project's target/classes directory" do + @bar_lib_urls.grep(%r{foo/target/classes}).should == [] + end + + it "does not depend on the the other project's target/resources directory" do + @bar_lib_urls.grep(%r{file://\$MODULE_DIR\$/../foo/target/resources}).size.should == 0 + end + end + + describe "with a single project definition" do + before do + @foo = define "foo" + end + + it "informs the user about generating IPR" do + lambda { invoke_generate_task }.should show_info(/Writing (.+)\/foo\.ipr/) + end + + it "informs the user about generating IML" do + lambda { invoke_generate_task }.should show_info(/Writing (.+)\/foo\.iml/) + end + end + describe "with a subproject" do + before do + @foo = define "foo" do + define 'bar' + end + end + + it "informs the user about generating subporoject IML" do + lambda { invoke_generate_task }.should show_info(/Writing (.+)\/bar\/bar\.iml/) + end + end + + describe "with compile.options.source = '1.6'" do + + before do + @foo = define "foo" do + compile.options.source = '1.5' + end + invoke_generate_task + end + + it "generate an ProjectRootManager with 1.5 jdk specified" do + #raise File.read(@foo._("foo.ipr")) + xml_document(@foo._("foo.ipr")). + should have_xpath("/project/component[@name='ProjectRootManager' and @project-jdk-name = '1.5' and @languageLevel = 'JDK_1_5']") + end + + it "generates a ProjectDetails component with the projectName option set" do + xml_document(@foo._("foo.ipr")). + should have_xpath("/project/component[@name='ProjectDetails']/option[@name = 'projectName' and @value = 'foo']") + end + end + + describe "with compile.options.source = '1.6'" do + before do + @foo = define "foo" do + compile.options.source = '1.6' + end + invoke_generate_task + end + + it "generate an ProjectRootManager with 1.6 jdk specified" do + xml_document(@foo._("foo.ipr")). + should have_xpath("/project/component[@name='ProjectRootManager' and @project-jdk-name = '1.6' and @languageLevel = 'JDK_1_6']") + end + end + + describe "with iml.skip_content! specified" do + before do + @foo = define "foo" do + iml.skip_content! + end + invoke_generate_task + end + + it "generate an IML with no content section" do + doc = xml_document(@foo._(root_module_filename(@foo))) + doc.should_not have_xpath("/module/component[@name='NewModuleRootManager']/content") + end + end + + describe "with iml.skip_content! not specified and standard layout" do + before do + @foo = define "foo" do + end + invoke_generate_task + end + + it "generate an IML with content section" do + root_module_xml(@foo).should have_xpath("/module/component[@name='NewModuleRootManager']/content") + end + + it "generate an exclude in content section for reports" do + root_module_xml(@foo).should have_xpath("/module/component[@name='NewModuleRootManager']/content/excludeFolder[@url='file://$MODULE_DIR$/reports']") + end + + it "generate an exclude in content section for target" do + root_module_xml(@foo).should have_xpath("/module/component[@name='NewModuleRootManager']/content/excludeFolder[@url='file://$MODULE_DIR$/target']") + end + end + + describe "with subprojects" do + before do + @foo = define "foo" do + define "bar" do + compile.from _(:source, :main, :bar) + end + end + invoke_generate_task + @bar_doc = xml_document(project('foo:bar')._('bar.iml')) + end + + it "generates the correct source directories" do + @bar_doc.should have_xpath("//content/sourceFolder[@url='file://$MODULE_DIR$/src/main/bar']") + end + + it "generates the correct exclude directories" do + @bar_doc.should have_xpath("//content/excludeFolder[@url='file://$MODULE_DIR$/target']") + end + end + + describe "with overrides" do + before do + @foo = define "foo" do + compile.from _(:source, :main, :bar) + iml.main_source_directories << _(:source, :main, :baz) + iml.test_source_directories << _(:source, :test, :foo) + end + invoke_generate_task + end + + it "generates the correct main source directories" do + root_module_xml(@foo).should have_xpath("//content/sourceFolder[@url='file://$MODULE_DIR$/src/main/baz' and @isTestSource='false']") + end + + it "generates the correct test source directories" do + root_module_xml(@foo).should have_xpath("//content/sourceFolder[@url='file://$MODULE_DIR$/src/test/foo' and @isTestSource='true']") + end + end + + describe "with report dir outside content" do + before do + layout = Layout::Default.new + layout[:reports] = "../reports" + + @foo = define "foo", :layout => layout do + end + invoke_generate_task + end + + it "generate an exclude in content section for target" do + root_module_xml(@foo).should have_xpath("/module/component[@name='NewModuleRootManager']/content/excludeFolder[@url='file://$MODULE_DIR$/target']") + end + + it "generates an content section without an exclude for report dir" do + root_module_xml(@foo).should have_nodes("/module/component[@name='NewModuleRootManager']/content/excludeFolder", 1) + end + end + + describe "with target dir outside content" do + before do + layout = Layout::Default.new + layout[:target] = "../target" + layout[:target, :main] = "../target" + + @foo = define "foo", :layout => layout do + end + invoke_generate_task + end + + it "generate an exclude in content section for reports" do + root_module_xml(@foo).should have_xpath("/module/component[@name='NewModuleRootManager']/content/excludeFolder[@url='file://$MODULE_DIR$/reports']") + end + + it "generates an content section without an exclude for target dir" do + root_module_xml(@foo).should have_nodes("/module/component[@name='NewModuleRootManager']/content/excludeFolder", 1) + end + end + + describe "templates" do + + def ipr_template + return < + + + + +PROJECT_XML + end + + def ipr_existing + return < + + + + + + + + + + + + +PROJECT_XML + end + + def ipr_from_template_xpath + "/project/component[@name='SvnBranchConfigurationManager']/option[@name = 'mySupportsUserInfoFilter' and @value = 'false']" + end + + def ipr_from_existing_xpath + "/project/component[@name='AntConfiguration']" + end + + def ipr_from_existing_shadowing_template_xpath + "/project/component[@name='SvnBranchConfigurationManager']/option[@name = 'mySupportsUserInfoFilter' and @value = 'true']" + end + + def ipr_from_existing_shadowing_generated_xpath + "/project/component[@name='ProjectModuleManager']/modules/module[@fileurl = 'file://$PROJECT_DIR$/existing.iml']" + end + + def ipr_from_generated_xpath + "/project/component[@name='ProjectModuleManager']/modules/module[@fileurl = 'file://$PROJECT_DIR$/foo.iml']" + end + + def iml_template + return < + + + + + + + + + +PROJECT_XML + end + + def iml_existing + return < + + + + + + + + + + + + + +PROJECT_XML + end + + def iml_from_template_xpath + "/module/component[@name='FacetManager']/facet[@type = 'JRUBY']" + end + + def iml_from_existing_xpath + "/module/component[@name='FunkyPlugin']" + end + + def iml_from_existing_shadowing_template_xpath + "/module/component[@name='FacetManager']/facet[@type = 'SCALA']" + end + + def iml_from_existing_shadowing_generated_xpath + "/module/component[@name='NewModuleRootManager']/orderEntry[@module-name = 'buildr-bnd']" + end + + def iml_from_generated_xpath + "/module/component[@name='NewModuleRootManager']/orderEntry[@type = 'module-library']" + end + + describe "with existing project files" do + before do + write "foo.ipr", ipr_existing + write "foo.iml", iml_existing + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + ipr.template = nil + iml.template = nil + compile.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "replaces ProjectModuleManager component in existing ipr file" do + xml_document(@foo._("foo.ipr")).should have_xpath(ipr_from_generated_xpath) + xml_document(@foo._("foo.ipr")).should_not have_xpath(ipr_from_existing_shadowing_generated_xpath) + end + + it "merges component in existing ipr file" do + xml_document(@foo._("foo.ipr")).should have_xpath(ipr_from_existing_xpath) + end + + def iml_from_generated_xpath + "/module/component[@name='NewModuleRootManager']/orderEntry[@type = 'module-library']" + end + + it "replaces NewModuleRootManager component in existing iml file" do + xml_document(@foo._("foo.iml")).should have_xpath(iml_from_generated_xpath) + xml_document(@foo._("foo.iml")).should_not have_xpath(iml_from_existing_shadowing_generated_xpath) + end + + it "merges component in existing iml file" do + xml_document(@foo._("foo.iml")).should have_xpath(iml_from_existing_xpath) + end + end + + describe "with an iml template" do + before do + write "module.template.iml", iml_template + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + ipr.template = nil + iml.template = "module.template.iml" + compile.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "replaces generated components" do + xml_document(@foo._("foo.iml")).should have_xpath(iml_from_generated_xpath) + end + + it "merges component in iml template" do + xml_document(@foo._("foo.iml")).should have_xpath(iml_from_template_xpath) + end + end + + describe "with an iml template and existing iml" do + before do + write "module.template.iml", iml_template + write "foo.iml", iml_existing + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + ipr.template = nil + iml.template = "module.template.iml" + compile.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "replaces generated components" do + xml_document(@foo._("foo.iml")).should have_xpath(iml_from_generated_xpath) + end + + it "merges component in iml template" do + xml_document(@foo._("foo.iml")).should have_xpath(iml_from_template_xpath) + end + + it "merges components not in iml template and not generated by task" do + xml_document(@foo._("foo.iml")).should have_xpath(iml_from_existing_xpath) + xml_document(@foo._("foo.iml")).should_not have_xpath(iml_from_existing_shadowing_template_xpath) + xml_document(@foo._("foo.iml")).should_not have_xpath(iml_from_existing_shadowing_generated_xpath) + end + end + + describe "with an ipr template" do + before do + write "project.template.iml", ipr_template + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + ipr.template = "project.template.iml" + iml.template = nil + compile.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "replaces generated component in ipr template" do + xml_document(@foo._("foo.ipr")).should have_xpath(ipr_from_generated_xpath) + end + + it "merges component in ipr template" do + xml_document(@foo._("foo.ipr")).should have_xpath(ipr_from_template_xpath) + end + end + + describe "with an ipr template and existing ipr" do + before do + write "project.template.iml", ipr_template + write "foo.ipr", ipr_existing + artifact('group:id:jar:1.0') { |t| write t.to_s } + @foo = define "foo" do + ipr.template = "project.template.iml" + iml.template = nil + compile.with 'group:id:jar:1.0' + end + invoke_generate_task + end + + it "replaces generated component in ipr template" do + xml_document(@foo._("foo.ipr")).should have_xpath(ipr_from_generated_xpath) + end + + it "merges component in ipr template" do + xml_document(@foo._("foo.ipr")).should have_xpath(ipr_from_template_xpath) + end + + it "merges components not in ipr template and not generated by task" do + xml_document(@foo._("foo.ipr")).should have_xpath(ipr_from_existing_xpath) + xml_document(@foo._("foo.ipr")).should_not have_xpath(ipr_from_existing_shadowing_generated_xpath) + xml_document(@foo._("foo.ipr")).should_not have_xpath(ipr_from_existing_shadowing_template_xpath) + end + end + end + end + + describe "Buildr::IntellijIdea::IdeaModule" do + + describe "with no settings" do + before do + @foo = define "foo" + end + + it "has correct default iml.type setting" do + @foo.iml.type.should == "JAVA_MODULE" + end + + it "has correct default iml.local_repository_env_override setting" do + @foo.iml.local_repository_env_override.should == "MAVEN_REPOSITORY" + end + end + + describe "settings inherited in subprojects" do + before do + mkdir_p 'bar' + @foo = define "foo" do + iml.type = "FOO_MODULE_TYPE" + define 'bar' + end + invoke_generate_task + end + + it "generates root IML with specified type" do + module_file = root_module_filename(@foo) + File.should be_exist(module_file) + File.read(module_file).should =~ /FOO_MODULE_TYPE/ + end + + it "generates subproject IML with inherited type" do + module_file = subproject_module_filename(@foo, "bar") + File.should be_exist(module_file) + File.read(module_file).should =~ /FOO_MODULE_TYPE/ + end + + end + + describe "with local_repository_env_override = nil" do + if Buildr::Util.win_os? + describe "base_directory on different drive on windows" do + before do + @foo = define "foo", :base_dir => "C:/bar" do + iml.local_repository_env_override = nil + end + end + + it "generates relative paths correctly" do + @foo.iml.send(:resolve_path, "E:/foo").should eql('E:/foo') + end + end + + describe "base_directory on same drive on windows" do + before do + @foo = define "foo", :base_dir => "C:/bar" do + iml.local_repository_env_override = nil + end + end + + it "generates relative paths correctly" do + @foo.iml.send(:resolve_path, "C:/foo").should eql('$MODULE_DIR$/../foo') + end + end + end + end + end + + describe "project extension" do + it "provides an 'idea' task" do + Rake::Task.tasks.detect { |task| task.to_s == "idea" }.should_not be_nil + end + + it "documents the 'idea' task" do + Rake::Task.tasks.detect { |task| task.to_s == "idea" }.comment.should_not be_nil + end + + it "provides an 'idea:clean' task" do + Rake::Task.tasks.detect { |task| task.to_s == "idea:clean" }.should_not be_nil + end + + it "documents the 'idea:clean' task" do + Rake::Task.tasks.detect { |task| task.to_s == "idea:clean" }.comment.should_not be_nil + end + + describe "#no_iml" do + it "makes #iml? false" do + @foo = define "foo" do + project.no_iml + end + @foo.iml?.should be_false + end + end + + describe "#iml" do + before do + define "foo" do + iml.suffix = "-top" + + define "bar" do + iml.suffix = "-mid" + + define "baz" do + end + end + end + end + + it "inherits the direct parent's IML settings" do + project('foo:bar:baz').iml.suffix.should == "-mid" + end + + it "does not modify the parent's IML settings" do + project('foo').iml.suffix.should == "-top" + end + + it "works even when the parent has no IML" do + lambda { + define "a" do + project.no_iml + define "b" do + iml.suffix = "-alone" + end + end + }.should_not raise_error + end + + it "inherits from the first ancestor which has an IML" do + define "a" do + iml.suffix = "-a" + define "b" do + iml.suffix = "-b" + define "c" do + project.no_iml + define "d" do + project.no_iml + define "e" do + project.no_iml + define "f" do + end + end + end + end + end + end + + project("a:b:c:d:e:f").iml.suffix.should == "-b" + end + end + end + +end diff --git a/buildr/spec/java/ant_spec.rb b/buildr/spec/java/ant_spec.rb new file mode 100644 index 0000000..e183f3c --- /dev/null +++ b/buildr/spec/java/ant_spec.rb @@ -0,0 +1,37 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Buildr::Ant do + + it 'should pick Ant version from ant build settings' do + begin + Buildr::Ant.instance_eval { @dependencies = nil } + write 'build.yaml', 'ant: 1.2.3' + Buildr::Ant.dependencies.should include("org.apache.ant:ant:jar:1.2.3") + ensure + Buildr::Ant.instance_eval { @dependencies = nil } + end + end + + it 'should have REQUIRES up to version 1.5 since it was deprecated in version 1.3.3' do + Buildr::VERSION.should < '1.5' + lambda { Ant::REQUIRES }.should_not raise_error + end + +end diff --git a/buildr/spec/java/bdd_spec.rb b/buildr/spec/java/bdd_spec.rb new file mode 100644 index 0000000..0e881a2 --- /dev/null +++ b/buildr/spec/java/bdd_spec.rb @@ -0,0 +1,164 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe Buildr::RSpec do + + before(:each) do + define('foo') do + test.using :rspec, :output => false + end + end + + it 'should be selected by :rspec name' do + project('foo').test.framework.should eql(:rspec) + end + + it 'should read passed specs from result yaml' do + # This test fails on the CI machine if the spec is run as part of a suite but not if run individually + # This seems to indicate that there is interaction with some other test but until that other test is + # identified the test has been marked as pending on the ci box + pending "Unable to determine why it fails on the CI machine so disabling" if `hostname` == "vesta.apache.org\n" + write('src/spec/ruby/success_spec.rb', 'describe("success") { it("is true") { nil.should be_nil } }') + + project('foo').test.invoke + project('foo').test.passed_tests.should eql([File.expand_path('src/spec/ruby/success_spec.rb')]) + end + + it 'should read result yaml to obtain the list of failed specs' do + # This test fails on the CI machine if the spec is run as part of a suite but not if run individually + # This seems to indicate that there is interaction with some other test but until that other test is + # identified the test has been marked as pending on the ci box + pending "Unable to determine why it fails on the CI machine so disabling" if `hostname` == "vesta.apache.org\n" + success = File.expand_path('src/spec/ruby/success_spec.rb') + write(success, 'describe("success") { it("is true") { nil.should be_nil } }') + failure = File.expand_path('src/spec/ruby/failure_spec.rb') + write(failure, 'describe("failure") { it("is false") { true.should == false } }') + error = File.expand_path('src/spec/ruby/error_spec.rb') + write(error, 'describe("error") { it("raises") { lambda; } }') + + lambda { project('foo').test.invoke }.should raise_error(/Tests failed/) + project('foo').test.tests.should include(success, failure, error) + project('foo').test.failed_tests.sort.should eql([failure, error].sort) + project('foo').test.passed_tests.should eql([success]) + + end + +end if RUBY_PLATFORM =~ /java/ || ENV['JRUBY_HOME'] # RSpec + +describe Buildr::JBehave do + def foo(*args, &prc) + define('foo', *args) do + test.using :jbehave + if prc + instance_eval(&prc) + else + self + end + end + end + + it 'should apply to projects having JBehave sources' do + define('one', :base_dir => 'one') do + write _('src/spec/java/SomeBehaviour.java'), 'public class SomeBehaviour {}' + JBehave.applies_to?(self).should be_true + end + define('two', :base_dir => 'two') do + write _('src/test/java/SomeBehaviour.java'), 'public class SomeBehaviour {}' + JBehave.applies_to?(self).should be_false + end + define('three', :base_dir => 'three') do + write _('src/spec/java/SomeBehavior.java'), 'public class SomeBehavior {}' + JBehave.applies_to?(self).should be_true + end + define('four', :base_dir => 'four') do + write _('src/test/java/SomeBehavior.java'), 'public class SomeBehavior {}' + JBehave.applies_to?(self).should be_false + end + end + + it 'should be selected by :jbehave name' do + foo { test.framework.should eql(:jbehave) } + end + + it 'should select a java compiler for its sources' do + write 'src/test/java/SomeBehavior.java', 'public class SomeBehavior {}' + foo do + test.compile.language.should eql(:java) + end + end + + it 'should include JBehave dependencies' do + foo do + test.compile.dependencies.should include(artifact("org.jbehave:jbehave:jar::#{JBehave.version}")) + test.dependencies.should include(artifact("org.jbehave:jbehave:jar::#{JBehave.version}")) + end + end + + it 'should include JMock dependencies' do + foo do + two_or_later = JMock.version[0,1].to_i >= 2 + group = two_or_later ? "org.jmock" : "jmock" + test.compile.dependencies.should include(artifact("#{group}:jmock:jar:#{JMock.version}")) + test.dependencies.should include(artifact("#{group}:jmock:jar:#{JMock.version}")) + end + end + + it 'should include classes whose name ends with Behavior' do + write 'src/spec/java/some/FooBehavior.java', <<-JAVA + package some; + public class FooBehavior { + public void shouldFoo() { assert true; } + } + JAVA + write 'src/spec/java/some/NotATest.java', <<-JAVA + package some; + public class NotATest { } + JAVA + foo.tap do |project| + project.test.invoke + project.test.tests.should include('some.FooBehavior') + end + end + + + it 'should include classes implementing Behaviours' do + write 'src/spec/java/some/MyBehaviours.java', <<-JAVA + package some; + public class MyBehaviours implements + org.jbehave.core.behaviour.Behaviours { + public Class[] getBehaviours() { + return new Class[] { some.FooBehave.class }; + } + } + JAVA + write 'src/spec/java/some/FooBehave.java', <<-JAVA + package some; + public class FooBehave { + public void shouldFoo() { assert true; } + } + JAVA + write 'src/spec/java/some/NotATest.java', <<-JAVA + package some; + public class NotATest { } + JAVA + foo.tap do |project| + project.test.invoke + project.test.tests.should include('some.MyBehaviours') + end + end + +end # JBehave diff --git a/buildr/spec/java/cobertura_spec.rb b/buildr/spec/java/cobertura_spec.rb new file mode 100644 index 0000000..106060d --- /dev/null +++ b/buildr/spec/java/cobertura_spec.rb @@ -0,0 +1,112 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), 'test_coverage_helper')) +Sandbox.require_optional_extension 'buildr/java/cobertura' +artifacts(Buildr::Cobertura::dependencies).map(&:invoke) + + +describe Buildr::Cobertura do + before do + # Reloading the extension because the sandbox removes all its actions + Buildr.module_eval { remove_const :Cobertura } + load File.expand_path('../lib/buildr/java/cobertura.rb') + @tool_module = Buildr::Cobertura + end + + it_should_behave_like 'test coverage tool' + + describe 'project-specific' do + + describe 'data file' do + it 'should have a default value' do + define('foo').cobertura.data_file.should point_to_path('reports/cobertura.ser') + end + + it 'should be overridable' do + define('foo') { cobertura.data_file = path_to('target/data.cobertura') } + project('foo').cobertura.data_file.should point_to_path('target/data.cobertura') + end + + it 'should be created during instrumentation' do + write 'src/main/java/Foo.java', 'public class Foo {}' + define('foo') + task('foo:cobertura:instrument').invoke + file(project('foo').cobertura.data_file).should exist + end + + it 'should not instrument projects which have no sources' do + write 'bar/src/main/java/Baz.java', 'public class Baz {}' + define('foo') { define('bar') } + task('foo:bar:cobertura:instrument').invoke + end + + it 'should not generate html if projects have no sources' do + define('foo') { define('bar') } + task('cobertura:html').invoke + end + end + + describe 'instrumentation' do + before do + ['Foo', 'Bar'].each { |cls| write File.join('src/main/java', "#{cls}.java"), "public class #{cls} {}" } + end + + it 'should instrument only included classes' do + define('foo') { cobertura.include 'Foo' } + task("foo:cobertura:instrument").invoke + Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should == ['Foo.class'] } + end + + it 'should not instrument excluded classes' do + define('foo') { cobertura.exclude 'Foo' } + task("foo:cobertura:instrument").invoke + Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should == ['Bar.class'] } + end + + it 'should instrument classes that are included but not excluded' do + write 'src/main/java/Baz.java', 'public class Baz {}' + define('foo') { cobertura.include('Ba').exclude('ar') } + task("foo:cobertura:instrument").invoke + Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should == ['Baz.class'] } + end + end + + describe 'check' do + before do + write 'src/main/java/Foo.java', 'public class Foo { public static boolean returnTrue() {return true;}}' + write 'src/test/java/FooTest.java', <<-JAVA +import static junit.framework.Assert.assertTrue; +import org.junit.Test; + +public class FooTest { + + @Test + public void testReturnTrue() { + assertTrue(Foo.returnTrue()); + } +} +JAVA + end + + it 'should not raise errors during execution' do + define('foo') { cobertura.include 'Foo' } + lambda {task("foo:cobertura:check").invoke}.should_not raise_error + end + + end + end +end diff --git a/buildr/spec/java/commands_spec.rb b/buildr/spec/java/commands_spec.rb new file mode 100644 index 0000000..1c18664 --- /dev/null +++ b/buildr/spec/java/commands_spec.rb @@ -0,0 +1,93 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Java::Commands do + + it "should not be verbose by default" do + write "build.xml", <<-BUILD + + + simple example build file + + + +BUILD + lambda { Java::Commands.java("org.apache.tools.ant.Main", :classpath => Buildr::Ant.dependencies) }.should_not show_info(/java/) + lambda { Java::Commands.java("org.apache.tools.ant.Main", :classpath => Buildr::Ant.dependencies, :verbose => true) }.should show_info(/java/) + end + + describe "Java::Commands.javac" do + + it "should compile java" do + write "Foo.java", "public class Foo {}" + lambda { Java::Commands.javac("Foo.java") }.should change {File.exist?("Foo.class")}.to(true) + end + + it 'should let the user specify an output directory' do + write "Foo.java", "public class Foo {}" + lambda { Java::Commands.javac("Foo.java", :output => "classes") }.should change {File.exist?("classes/Foo.class")}.to(true) + end + + it "should let the user specify a different name" do + write "Foo.java", "public class Foo {}" + lambda { Java::Commands.javac("Foo.java", :name => "bar") }.should show_info("Compiling 1 source files in bar") + end + + it "should let the user specify a source path" do + write "ext/org/Bar.java", "package org; public class Bar {}" + write "Foo.java", "import org.Bar;\n public class Foo {}" + lambda { Java::Commands.javac("Foo.java", :sourcepath => File.expand_path("ext")) }.should change {File.exist?("Foo.class")}.to(true) + end + + it "should let the user specify a classpath" do + write "ext/org/Bar.java", "package org; public class Bar {}" + Java::Commands.javac("ext/org/Bar.java", :output => "lib") + write "Foo.java", "import org.Bar;\n public class Foo {}" + lambda { Java::Commands.javac("Foo.java", :classpath => File.expand_path("lib")) }.should change {File.exist?("Foo.class")}.to(true) + end + end + + describe "Java::Commands.javadoc" do + + it "should fail if no output is defined" do + lambda { Java::Commands.javadoc("Foo.java") }.should raise_error(/No output defined for javadoc/) + end + + it "should create javadoc" do + write "Foo.java", "public class Foo {}" + lambda { Java::Commands.javadoc("Foo.java", :output => "doc") }.should change {File.exist?("doc/Foo.html")}.to(true) + end + + it "should accept file tasks as arguments" do + foo = file("Foo.java") do |file| + file.enhance do + write file.to_s, "public class Foo {}" + end + end + lambda { Java::Commands.javadoc(foo, :output => "doc") }.should change {File.exist?("doc/Foo.html")}.to(true) + end + + it "should accept projects as arguments" do + write "src/main/java/Foo.java", "public class Foo {}" + write "src/main/java/bar/Foobar.java", "package bar; public class Foobar {}" + define "foo" do + end + lambda { Java::Commands.javadoc(project("foo"), :output => "doc") }.should change {File.exist?("doc/Foo.html") && File.exist?("doc/bar/Foobar.html")}.to(true) + end + end +end \ No newline at end of file diff --git a/buildr/spec/java/compiler_spec.rb b/buildr/spec/java/compiler_spec.rb new file mode 100644 index 0000000..ac0927c --- /dev/null +++ b/buildr/spec/java/compiler_spec.rb @@ -0,0 +1,255 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe 'javac compiler' do + it 'should identify itself from source directories' do + write 'src/main/java/com/example/Test.java', 'package com.example; class Test {}' + define('foo').compile.compiler.should eql(:javac) + end + + it 'should identify from source directories using custom layout' do + write 'src/com/example/Code.java', 'package com.example; class Code {}' + write 'testing/com/example/Test.java', 'package com.example; class Test {}' + custom = Layout.new + custom[:source, :main, :java] = 'src' + custom[:source, :test, :java] = 'testing' + define 'foo', :layout=>custom do + compile.compiler.should eql(:javac) + test.compile.compiler.should eql(:javac) + end + end + + it 'should identify from compile source directories' do + write 'src/com/example/Code.java', 'package com.example; class Code {}' + write 'testing/com/example/Test.java', 'package com.example; class Test {}' + define 'foo' do + lambda { compile.from 'src' }.should change { compile.compiler }.to(:javac) + lambda { test.compile.from 'testing' }.should change { test.compile.compiler }.to(:javac) + end + end + + it 'should report the language as :java' do + define('foo').compile.using(:javac).language.should eql(:java) + end + + it 'should set the target directory to target/classes' do + define 'foo' do + lambda { compile.using(:javac) }.should change { compile.target.to_s }.to(File.expand_path('target/classes')) + end + end + + it 'should not override existing target directory' do + define 'foo' do + compile.into('classes') + lambda { compile.using(:javac) }.should_not change { compile.target } + end + end + + it 'should not change existing list of sources' do + define 'foo' do + compile.from('sources') + lambda { compile.using(:javac) }.should_not change { compile.sources } + end + end + + # Doesn't work under jdk1.5 - caused in one of the commits 1167678, 1170604, 1170605, 1180125 + if Java.java.lang.System.getProperty("java.runtime.version") >= "1.6" + it 'should include classpath dependencies' do + write 'src/dependency/Dependency.java', 'class Dependency {}' + define 'dependency', :version=>'1.0' do + compile.from('src/dependency').into('target/dependency') + package(:jar) + end + write 'src/test/DependencyTest.java', 'class DependencyTest { Dependency _var; }' + define('foo').compile.from('src/test').with(project('dependency')).invoke + file('target/classes/DependencyTest.class').should exist + end + end + + it 'should include tools.jar dependency' do + write 'src/main/java/UseApt.java', <<-JAVA + import com.sun.mirror.apt.AnnotationProcessor; + public class UseApt { } + JAVA + define('foo').compile.invoke + file('target/classes/UseApt.class').should exist + end +end + + +describe 'javac compiler options' do + def compile_task + @compile_task ||= define('foo').compile.using(:javac) + end + + def javac_args + compile_task.instance_eval { @compiler }.send(:javac_args) + end + + it 'should set warnings option to false by default' do + compile_task.options.warnings.should be_false + end + + it 'should set warnings option to true when running with --verbose option' do + verbose true + compile_task.options.warnings.should be_false + end + + it 'should use -nowarn argument when warnings is false' do + compile_task.using(:warnings=>false) + javac_args.should include('-nowarn') + end + + it 'should not use -nowarn argument when warnings is true' do + compile_task.using(:warnings=>true) + javac_args.should_not include('-nowarn') + end + + it 'should not use -verbose argument by default' do + javac_args.should_not include('-verbose') + end + + it 'should use -verbose argument when running with --trace=javac option' do + Buildr.application.options.trace_categories = [:javac] + javac_args.should include('-verbose') + end + + it 'should set debug option to true by default' do + compile_task.options.debug.should be_true + end + + it 'should set debug option to false based on Buildr.options' do + Buildr.options.debug = false + compile_task.options.debug.should be_false + end + + it 'should set debug option to false based on debug environment variable' do + ENV['debug'] = 'no' + compile_task.options.debug.should be_false + end + + it 'should set debug option to false based on DEBUG environment variable' do + ENV['DEBUG'] = 'no' + compile_task.options.debug.should be_false + end + + it 'should use -g argument when debug option is true' do + compile_task.using(:debug=>true) + javac_args.should include('-g') + end + + it 'should not use -g argument when debug option is false' do + compile_task.using(:debug=>false) + javac_args.should_not include('-g') + end + + it 'should set deprecation option to false by default' do + compile_task.options.deprecation.should be_false + end + + it 'should use -deprecation argument when deprecation is true' do + compile_task.using(:deprecation=>true) + javac_args.should include('-deprecation') + end + + it 'should not use -deprecation argument when deprecation is false' do + compile_task.using(:deprecation=>false) + javac_args.should_not include('-deprecation') + end + + it 'should not set source option by default' do + compile_task.options.source.should be_nil + javac_args.should_not include('-source') + end + + it 'should not set target option by default' do + compile_task.options.target.should be_nil + javac_args.should_not include('-target') + end + + it 'should use -source nn argument if source option set' do + compile_task.using(:source=>'1.5') + javac_args.should include('-source', '1.5') + end + + it 'should use -target nn argument if target option set' do + compile_task.using(:target=>'1.5') + javac_args.should include('-target', '1.5') + end + + it 'should set lint option to false by default' do + compile_task.options.lint.should be_false + end + + it 'should use -lint argument if lint option is true' do + compile_task.using(:lint=>true) + javac_args.should include('-Xlint') + end + + it 'should use -lint argument with value of option' do + compile_task.using(:lint=>'all') + javac_args.should include('-Xlint:all') + end + + it 'should use -lint argument with value of option as array' do + compile_task.using(:lint=>['path', 'serial']) + javac_args.should include('-Xlint:path,serial') + end + + it 'should not set other option by default' do + compile_task.options.other.should be_nil + end + + it 'should pass other argument if other option is string' do + compile_task.using(:other=>'-Xprint') + javac_args.should include('-Xprint') + end + + it 'should pass other argument if other option is array' do + compile_task.using(:other=>['-Xstdout', 'msgs']) + javac_args.should include('-Xstdout', 'msgs') + end + + it 'should complain about options it doesn\'t know' do + write 'source/Test.java', 'class Test {}' + compile_task.using(:unknown=>'option') + lambda { compile_task.from('source').invoke }.should raise_error(ArgumentError, /no such option/i) + end + + it 'should inherit options from parent' do + define 'foo' do + compile.using(:warnings=>true, :debug=>true, :deprecation=>true, :source=>'1.5', :target=>'1.4') + define 'bar' do + compile.using(:javac) + compile.options.warnings.should be_true + compile.options.debug.should be_true + compile.options.deprecation.should be_true + compile.options.source.should eql('1.5') + compile.options.target.should eql('1.4') + end + end + end + + after do + Buildr.options.debug = nil + ENV.delete "debug" + ENV.delete "DEBUG" + end +end + diff --git a/buildr/spec/java/doc_spec.rb b/buildr/spec/java/doc_spec.rb new file mode 100644 index 0000000..f8f0f66 --- /dev/null +++ b/buildr/spec/java/doc_spec.rb @@ -0,0 +1,56 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe 'Javadoc' do + def sources + @sources ||= (1..3).map { |i| "Test#{i}" }. + each { |name| write "src/main/java/foo/#{name}.java", "package foo; public class #{name}{}" }. + map { |name| "src/main/java/foo/#{name}.java" } + end + + it 'should pick -windowtitle from project name by default' do + define 'foo' do + compile.using(:javac) + + define 'bar' do + compile.using(:javac) + end + end + + project('foo').doc.options[:windowtitle].should eql('foo') + project('foo:bar').doc.options[:windowtitle].should eql('foo:bar') + end + + it 'should pick -windowtitle from project description by default, if available' do + desc 'My App' + define 'foo' do + compile.using(:javac) + end + project('foo').doc.options[:windowtitle].should eql('My App') + end + + it 'should not override explicit :windowtitle' do + define 'foo' do + compile.using(:javac) + doc.using :windowtitle => 'explicit' + end + project('foo').doc.options[:windowtitle].should eql('explicit') + end + +end + diff --git a/buildr/spec/java/ecj_spec.rb b/buildr/spec/java/ecj_spec.rb new file mode 100644 index 0000000..ab8d354 --- /dev/null +++ b/buildr/spec/java/ecj_spec.rb @@ -0,0 +1,115 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + + +describe Buildr::Compiler::Ecj do + + before(:all) do + #Make ecj appear as a compiler that applies: + class Buildr::Compiler::Ecj + class << self + + def applies_to?(project, task) + paths = task.sources + [sources].flatten.map { |src| Array(project.path_to(:source, task.usage, src.to_sym)) } + paths.flatten! + ext_glob = Array(source_ext).join(',') + + paths.each { |path| + Find.find(path) {|found| + if (!File.directory?(found)) && found.match(/.*\.#{Array(source_ext).join('|')}/) + return true + end + } if File.exist? path + } + false + end + end + end + end + + it "should be the default Java compiler once loaded" do + write 'src/main/java/Foo.java', 'public class Foo {}' + foo = define('foo') + foo.compile.compiler.should == :ecj + end + + describe "should compile a Java project just in the same way javac does" do + javac_spec = File.read(File.join(File.dirname(__FILE__), "compiler_spec.rb")) + javac_spec = javac_spec.match(Regexp.escape("require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers'))\n")).post_match + javac_spec.gsub!("javac", "ecj") + javac_spec.gsub!("nowarn", "warn:none") + eval(javac_spec) + end + + # Redirect the java error ouput, yielding so you can do something while it is + # and returning the content of the error buffer. + # + def redirect_java_err + pending "RJB doesn't support well instantiating a class that has several constructors" unless RUBY_PLATFORM =~ /java/ + err = Java.java.io.ByteArrayOutputStream.new + original_err = Java.java.lang.System.err + begin + printStream = Java.java.io.PrintStream + print = printStream.new(err) + Java.java.lang.System.setErr(print) + yield + ensure + Java.java.lang.System.setErr(original_err) + end + err.toString + end + + it "should not issue warnings for type casting when warnings are set to warn:none, by default" do + write "src/main/java/Main.java", "import java.util.List; public class Main {public List get() {return null;}}" + foo = define("foo") { + compile.options.source = "1.5" + compile.options.target = "1.5" + } + redirect_java_err { foo.compile.invoke }.should_not match(/WARNING/) + end + + it "should issue warnings for type casting when warnings are set" do + write "src/main/java/Main.java", "import java.util.List; public class Main {public List get() {return null;}}" + foo = define("foo") { + compile.options.source = "1.5" + compile.options.target = "1.5" + compile.options.warnings = true + } + redirect_java_err { foo.compile.invoke }.should match(/WARNING/) + end + + after(:all) do + #Make ecj appear as a compiler that doesn't apply: + module Buildr + module Compiler + + class Ecj + + class << self + + def applies_to?(project, task) + false + end + end + end + end + end + end +end + + diff --git a/buildr/spec/java/emma_spec.rb b/buildr/spec/java/emma_spec.rb new file mode 100644 index 0000000..8ce90c6 --- /dev/null +++ b/buildr/spec/java/emma_spec.rb @@ -0,0 +1,121 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), 'test_coverage_helper')) +Sandbox.require_optional_extension 'buildr/java/emma' +artifacts(Buildr::Emma::dependencies).map(&:invoke) + + +describe Buildr::Emma do + include TestCoverageHelper + + before do + # Reloading the extension because the sandbox removes all its actions + Buildr.module_eval { remove_const :Emma } + load File.expand_path('../lib/buildr/java/emma.rb') + @tool_module = Buildr::Emma + end + + it_should_behave_like 'test coverage tool' + + describe 'project-specific' do + describe 'metadata file' do + it 'should have a default value' do + define('foo').emma.metadata_file.should point_to_path('reports/emma/coverage.em') + end + + it 'should be overridable' do + define('foo') { emma.metadata_file = path_to('target/metadata.emma') } + project('foo').emma.metadata_file.should point_to_path('target/metadata.emma') + end + + it 'should be created during instrumentation' do + write 'src/main/java/Foo.java', 'public class Foo {}' + define('foo') + task('foo:emma:instrument').invoke + file(project('foo').emma.metadata_file).should exist + end + end + + describe 'coverage file' do + it 'should have a default value' do + define('foo').emma.coverage_file.should point_to_path('reports/emma/coverage.ec') + end + + it 'should be overridable' do + define('foo') { emma.coverage_file = path_to('target/coverage.emma') } + project('foo').emma.coverage_file.should point_to_path('target/coverage.emma') + end + + it 'should be created during test' do + write 'src/main/java/Foo.java', 'public class Foo {}' + write_test :for=>'Foo', :in=>'src/test/java' + define('foo') + task('foo:test').invoke + file(project('foo').emma.coverage_file).should exist + end + end + + describe 'instrumentation' do + before do + ['Foo', 'Bar'].each { |cls| write File.join('src/main/java', "#{cls}.java"), "public class #{cls} {}" } + end + + it 'should instrument only included classes' do + define('foo') { emma.include 'Foo' } + task("foo:emma:instrument").invoke + Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should == ['Foo.class'] } + end + + it 'should not instrument excluded classes' do + define('foo') { emma.exclude 'Foo' } + task("foo:emma:instrument").invoke + Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should == ['Bar.class'] } + end + + it 'should instrument classes that are included but not excluded' do + write 'src/main/java/Baz.java', 'public class Baz {}' + define('foo') { emma.include('Ba*').exclude('*ar') } + task("foo:emma:instrument").invoke + Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should == ['Baz.class'] } + end + end + + describe 'reports' do + before do + write 'src/main/java/Foo.java', 'public class Foo {}' + write_test :for=>'Foo', :in=>'src/test/java' + end + + describe 'in html' do + it 'should inform the user if no coverage data' do + rm 'src/test/java/FooTest.java' + define('foo') + lambda { task('foo:emma:html').invoke }. + should show_info(/No test coverage report for foo. Missing: #{project('foo').emma.coverage_file}/) + end + end + + describe 'in xml' do + it 'should have an xml file' do + define('foo') + task('foo:emma:xml').invoke + file(File.join(project('foo').emma.report_dir, 'coverage.xml')).should exist + end + end + end + end +end diff --git a/buildr/spec/java/external_spec.rb b/buildr/spec/java/external_spec.rb new file mode 100644 index 0000000..0f8cd49 --- /dev/null +++ b/buildr/spec/java/external_spec.rb @@ -0,0 +1,56 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +COMPILERS = Buildr::Compiler.compilers.dup +COMPILERS_WITHOUT_JAVAC = COMPILERS.dup +COMPILERS_WITHOUT_JAVAC.delete Buildr::Compiler::Javac + +describe Buildr::Compiler::ExternalJavac do + + before(:all) do + Buildr::Compiler.send :compilers=, COMPILERS_WITHOUT_JAVAC + end + + describe "should compile a Java project just in the same way javac does" do + javac_spec = File.read(File.join(File.dirname(__FILE__), "compiler_spec.rb")) + javac_spec = javac_spec.match(Regexp.escape("require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers'))\n")).post_match + javac_spec.gsub!("javac", "externaljavac") + javac_spec.gsub!("--trace=externaljavac", "--trace=javac") + javac_spec.gsub!("trace_categories = [:externaljavac]", "trace_categories = [:javac]") + eval(javac_spec) + end + + it "should accept a :jvm option as JAVA_HOME" do + write 'src/main/java/Foo.java', 'public class Foo {}' + define "foo" do + compile.using(:externaljavac).options.jvm = "somepath" + end + begin + trace true #We set it true to grab the trace statement with the jvm path in it! + lambda {lambda {project("foo").compile.invoke}.should raise_error(RuntimeError, /Failed to compile, see errors above/)}.should show(/somepath\/bin\/javac .*/) + end + trace false + end + + after :all do + Buildr::Compiler.send :compilers=, COMPILERS + end + +end + + diff --git a/buildr/spec/java/java_spec.rb b/buildr/spec/java/java_spec.rb new file mode 100644 index 0000000..b5441bc --- /dev/null +++ b/buildr/spec/java/java_spec.rb @@ -0,0 +1,132 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +unless RUBY_PLATFORM =~ /java/ + describe ENV, 'JAVA_HOME on OS X' do + before do + @old_home, ENV['JAVA_HOME'] = ENV['JAVA_HOME'], nil + @old_env_java = Object.module_eval { remove_const :ENV_JAVA } + Config::CONFIG.should_receive(:[]).with('host_os').and_return('darwin0.9') + end + + it 'should point to default JVM' do + load File.expand_path('../lib/buildr/java/rjb.rb') + ENV['JAVA_HOME'].should == '/System/Library/Frameworks/JavaVM.framework/Home' + end + + it 'should use value of environment variable if specified' do + ENV['JAVA_HOME'] = '/System/Library/Frameworks/JavaVM.specified' + load File.expand_path('../lib/buildr/java/rjb.rb') + ENV['JAVA_HOME'].should == '/System/Library/Frameworks/JavaVM.specified' + end + + after do + ENV['JAVA_HOME'] = @old_home + ENV_JAVA.replace @old_env_java + end + end +else + describe 'JRuby environment' do + it 'should enforce a minimum version of jruby' do + check =File.read(File.expand_path('../lib/buildr/java/jruby.rb')).match(/JRUBY_MIN_VERSION.*\n.*JRUBY_MIN_VERSION\n/).to_s + check.sub!('JRUBY_VERSION', "'0.0.0'") + lambda { eval(check) }.should raise_error(/JRuby must be at least at version /) + end + end +end + + +describe 'Java.tools_jar' do + before do + @old_home = ENV['JAVA_HOME'] + end + + describe 'when JAVA_HOME points to a JDK' do + before do + Java.instance_eval { @tools_jar = nil } + write 'jdk/lib/tools.jar' + ENV['JAVA_HOME'] = File.expand_path('jdk') + end + + it 'should return the path to tools.jar' do + Java.tools_jar.should point_to_path('jdk/lib/tools.jar') + end + end + + describe 'when JAVA_HOME points to a JRE inside a JDK' do + before do + Java.instance_eval { @tools_jar = nil } + write 'jdk/lib/tools.jar' + ENV['JAVA_HOME'] = File.expand_path('jdk/jre') + end + + it 'should return the path to tools.jar' do + Java.tools_jar.should point_to_path('jdk/lib/tools.jar') + end + end + + describe 'when there is no tools.jar' do + before do + Java.instance_eval { @tools_jar = nil } + ENV['JAVA_HOME'] = File.expand_path('jdk') + end + + it 'should return nil' do + Java.tools_jar.should be_nil + end + end + + after do + ENV['JAVA_HOME'] = @old_home + end +end + +describe 'Java#java' do + before do + @old_home = ENV['JAVA_HOME'] + end + + describe 'when JAVA_HOME points to an invalid JRE/JDK installation' do + before do + write 'jdk' + ENV['JAVA_HOME'] = File.expand_path('jdk') + end + + it 'should fail with an error message mentioning JAVA_HOME' do + begin + Java.java ['-version'] + fail 'Java.java did not fail with JAVA_HOME pointing to invalid JRE/JDK installation' + rescue => error + error.message.to_s.should match(/JAVA_HOME/) + end + end + end + + after do + ENV['JAVA_HOME'] = @old_home + end +end + + +describe Java::JavaWrapper do + it 'should be removed in version 1.5 since it was deprecated in version 1.3' do + Buildr::VERSION.should < '1.5' + lambda { Java::JavaWrapper }.should_not raise_error + end +end diff --git a/buildr/spec/java/packaging_spec.rb b/buildr/spec/java/packaging_spec.rb new file mode 100644 index 0000000..d528a79 --- /dev/null +++ b/buildr/spec/java/packaging_spec.rb @@ -0,0 +1,1266 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'packaging', 'packaging_helper')) + + +describe Project, '#manifest' do + it 'should include user name' do + ENV['USER'] = 'MysteriousJoe' + define('foo').manifest['Build-By'].should eql('MysteriousJoe') + end + + it 'should include JDK version' do + define('foo').manifest['Build-Jdk'].should =~ /^1\.\d+\.\w+$/ + end + + it 'should include project comment' do + desc 'My Project' + define('foo').manifest['Implementation-Title'].should eql('My Project') + end + + it 'should include project name if no comment' do + define('foo').manifest['Implementation-Title'].should eql('foo') + end + + it 'should include project version' do + define('foo', :version=>'2.1').manifest['Implementation-Version'].should eql('2.1') + end + + it 'should not include project version unless specified' do + define('foo').manifest['Implementation-Version'].should be_nil + end + + it 'should inherit from parent project' do + define('foo', :version=>'2.1') { define 'bar' } + project('foo:bar').manifest['Implementation-Version'].should eql('2.1') + end + +end + + +shared_examples_for 'package with manifest' do + before do + @long_line = 'No line may be longer than 72 bytes (not characters), in its UTF8-encoded form. If a value would make the initial line longer than this, it should be continued on extra lines (each starting with a single SPACE).' + end + + def package_with_manifest(manifest = nil) + packaging = @packaging + @project = define('foo', :version=>'1.2') do + package packaging + package(packaging).with(:manifest=>manifest) unless manifest.nil? + end + end + + def inspect_manifest(package = nil) + package ||= project('foo').package(@packaging) + package.invoke + yield Buildr::Packaging::Java::Manifest.from_zip(package) + end + + it 'should include default header when no options specified' do + ENV['USER'] = 'MysteriousJoe' + package_with_manifest # Nothing for default. + inspect_manifest do |manifest| + manifest.sections.size.should be(1) + manifest.main.should == { + 'Manifest-Version' =>'1.0', + 'Created-By' =>'Buildr', + 'Implementation-Title' =>@project.name, + 'Implementation-Version' =>'1.2', + 'Build-Jdk' =>ENV_JAVA['java.version'], + 'Build-By' =>'MysteriousJoe' + } + end + end + + it 'should not exist when manifest=false' do + package_with_manifest false + @project.package(@packaging).invoke + Zip::ZipFile.open(@project.package(@packaging).to_s) do |zip| + zip.file.exist?('META-INF/MANIFEST.MF').should be_false + end + end + + it 'should generate a new manifest for a file that does not have one' do + Zip::ZipOutputStream.open 'tmp.zip' do |zip| + zip.put_next_entry 'empty.txt' + end + begin + manifest = Buildr::Packaging::Java::Manifest.from_zip('tmp.zip') + manifest.each do |key, val| + Buildr::Packaging::Java::Manifest::STANDARD_HEADER.should include(key) + end + ensure + rm 'tmp.zip' + end + end + + it 'should map manifest from hash' do + package_with_manifest 'Foo'=>1, :bar=>'Bar' + inspect_manifest do |manifest| + manifest.sections.size.should be(1) + manifest.main['Manifest-Version'].should eql('1.0') + manifest.main['Created-By'].should eql('Buildr') + manifest.main['Foo'].should eql('1') + manifest.main['bar'].should eql('Bar') + end + end + + it 'should close the temporary file used for packaging the MANIFEST.MF file' do + package_with_manifest 'Foo'=>1, :bar=>'Bar' + package = project('foo').package(@packaging) + package.invoke + module AccessManifestTMP + attr_reader :manifest_tmp + end + (package.dup.extend(AccessManifestTMP).manifest_tmp.closed?).should be_true + end + + it 'should end hash manifest with EOL' do + package_with_manifest 'Foo'=>1, :bar=>'Bar' + package = project('foo').package(@packaging) + package.invoke + Zip::ZipFile.open(package.to_s) { |zip| zip.file.read('META-INF/MANIFEST.MF').should =~ /#{Buildr::Packaging::Java::Manifest::LINE_SEPARATOR}$/ } + end + + it 'should break hash manifest lines longer than 72 characters using continuations' do + package_with_manifest 'foo'=>@long_line + package = project('foo').package(@packaging) + inspect_manifest do |manifest| + manifest.main['foo'].should == @long_line + end + end + + it 'should map manifest from array' do + package_with_manifest [ { :foo=>'first' }, { :bar=>'second' } ] + inspect_manifest do |manifest| + manifest.sections.size.should be(2) + manifest.main['Manifest-Version'].should eql('1.0') + manifest.main['foo'].should eql('first') + manifest.sections.last['bar'].should eql('second') + end + end + + it 'should end array manifest with EOL' do + package_with_manifest [ { :foo=>'first' }, { :bar=>'second' } ] + package = project('foo').package(@packaging) + package.invoke + Zip::ZipFile.open(package.to_s) { |zip| zip.file.read('META-INF/MANIFEST.MF')[-1].should == ?\n } + end + + it 'should break array manifest lines longer than 72 characters using continuations' do + package_with_manifest ['foo'=>@long_line] + package = project('foo').package(@packaging) + inspect_manifest do |manifest| + manifest.main['foo'].should == @long_line + end + end + + it 'should put Name: at beginning of section' do + package_with_manifest [ {}, { 'Name'=>'first', :Foo=>'first', :bar=>'second' } ] + package = project('foo').package(@packaging) + package.invoke + inspect_manifest do |manifest| + manifest.sections[1]["Name"].should == "first" + end + end + + it 'should create manifest from proc' do + package_with_manifest lambda { 'Meta: data' } + inspect_manifest do |manifest| + manifest.sections.size.should be(1) + manifest.main['Manifest-Version'].should eql('1.0') + manifest.main['Meta'].should eql('data') + end + end + + it 'should create manifest from file' do + write 'MANIFEST.MF', 'Meta: data' + package_with_manifest 'MANIFEST.MF' + inspect_manifest do |manifest| + manifest.sections.size.should be(1) + manifest.main['Meta'].should eql('data') + end + end + + it 'should give 644 permissions to the manifest' do + package_with_manifest [ {}, { 'Name'=>'first', :Foo=>'first', :bar=>'second' } ] + package ||= project('foo').package(@packaging) + package.invoke + Zip::ZipFile.open(package.to_s) do |zip| + permissions = format("%o", zip.file.stat('META-INF/MANIFEST.MF').mode) + permissions.should match /644$/ + end + end + + it 'should not add manifest version twice' do + write 'MANIFEST.MF', 'Manifest-Version: 1.9' + package_with_manifest 'MANIFEST.MF' + package ||= project('foo').package(@packaging) + package.invoke + Zip::ZipFile.open(package.to_s) do |zip| + zip.read('META-INF/MANIFEST.MF').scan(/(Manifest-Version)/m).size.should == 1 + end + end + + it 'should give precedence to version specified in manifest file' do + write 'MANIFEST.MF', 'Manifest-Version: 1.9' + package_with_manifest 'MANIFEST.MF' + inspect_manifest do |manifest| + manifest.main['Manifest-Version'].should == '1.9' + end + end + + it 'should create manifest from task' do + file 'MANIFEST.MF' do |task| + write task.to_s, 'Meta: data' + end + package_with_manifest 'MANIFEST.MF' + inspect_manifest do |manifest| + manifest.sections.size.should be(1) + manifest.main['Manifest-Version'].should eql('1.0') + manifest.main['Meta'].should eql('data') + end + end + + it 'should respond to with() and accept manifest' do + write 'DISCLAIMER' + mkpath 'target/classes' + packaging = @packaging + define('foo', :version=>'1.0') { package(packaging).with :manifest=>{'Foo'=>'data'} } + inspect_manifest { |manifest| manifest.main['Foo'].should eql('data') } + end + + it 'should include META-INF directory' do + packaging = @packaging + package = define('foo', :version=>'1.0') { package(packaging) }.packages.first + package.invoke + Zip::ZipFile.open(package.to_s) do |zip| + zip.entries.map(&:to_s).should include('META-INF/') + end + end + + it 'should inherit manifest from parent project' do + packaging = @packaging + package = nil + define('foo', :version => '1.0') do + manifest['Foo'] = '1' + package(packaging) + define('bar', :version => '1.0') do + manifest['bar'] = 'Bar' + package(:jar) + package = packages.first + end + end + inspect_manifest(package) do |manifest| + manifest.sections.size.should be(1) + manifest.main['Manifest-Version'].should eql('1.0') + manifest.main['Created-By'].should eql('Buildr') + manifest.main['Foo'].should eql('1') + manifest.main['bar'].should eql('Bar') + end + end + + it 'should not modify manifest of parent project' do + packaging = @packaging + define('foo', :version => '1.0') do + manifest['Foo'] = '1' + package(packaging) + define('bar', :version => '1.0') do + manifest['bar'] = 'Bar' + package(:jar) + end + define('baz', :version => '1.0') do + manifest['baz'] = 'Baz' + package(:jar) + end + end + inspect_manifest(project('foo').packages.first) do |manifest| + manifest.sections.size.should be(1) + manifest.main['Manifest-Version'].should eql('1.0') + manifest.main['Created-By'].should eql('Buildr') + manifest.main['Foo'].should eql('1') + manifest.main['bar'].should be_nil + manifest.main['baz'].should be_nil + end + inspect_manifest(project('foo:bar').packages.first) do |manifest| + manifest.sections.size.should be(1) + manifest.main['Manifest-Version'].should eql('1.0') + manifest.main['Created-By'].should eql('Buildr') + manifest.main['Foo'].should eql('1') + manifest.main['bar'].should eql('Bar') + manifest.main['baz'].should be_nil + end + inspect_manifest(project('foo:baz').packages.first) do |manifest| + manifest.sections.size.should be(1) + manifest.main['Manifest-Version'].should eql('1.0') + manifest.main['Created-By'].should eql('Buildr') + manifest.main['Foo'].should eql('1') + manifest.main['baz'].should eql('Baz') + manifest.main['bar'].should be_nil + end + end +end + + +describe Project, '#meta_inf' do + it 'should by an array' do + define('foo').meta_inf.should be_kind_of(Array) + end + + it 'should include LICENSE file if found' do + write 'LICENSE' + define('foo').meta_inf.first.should point_to_path('LICENSE') + end + + it 'should be empty unless LICENSE exists' do + define('foo').meta_inf.should be_empty + end + + it 'should inherit from parent project' do + write 'LICENSE' + define('foo') { define 'bar' } + project('foo:bar').meta_inf.first.should point_to_path('LICENSE') + end + + it 'should expect LICENSE file parent project' do + write 'bar/LICENSE' + define('foo') { define 'bar' } + project('foo:bar').meta_inf.should be_empty + end +end + + +shared_examples_for 'package with meta_inf' do + + def package_with_meta_inf(meta_inf = nil) + packaging = @packaging + @project = Buildr.define('foo', :version=>'1.2') do + package packaging + package(packaging).with(:meta_inf=>meta_inf) if meta_inf + end + end + + def inspect_meta_inf + package = project('foo').package(@packaging) + package.invoke + assumed = Array(@meta_inf_ignore) + Zip::ZipFile.open(package.to_s) do |zip| + entries = zip.entries.map(&:name).select { |f| File.dirname(f) == 'META-INF' }.map { |f| File.basename(f) } + assumed.each { |f| entries.should include(f) } + yield entries - assumed if block_given? + end + end + + it 'should default to LICENSE file' do + write 'LICENSE' + package_with_meta_inf + inspect_meta_inf { |files| files.should eql(['LICENSE']) } + end + + it 'should be empty if no LICENSE file' do + package_with_meta_inf + inspect_meta_inf { |files| files.should be_empty } + end + + it 'should include file specified by :meta_inf option' do + write 'README' + package_with_meta_inf 'README' + inspect_meta_inf { |files| files.should eql(['README']) } + end + + it 'should include files specified by :meta_inf option' do + files = ['README', 'DISCLAIMER'].each { |file| write file } + package_with_meta_inf files + inspect_meta_inf { |files| files.should eql(files) } + end + + it 'should include file task specified by :meta_inf option' do + file('README') { |task| write task.to_s } + package_with_meta_inf file('README') + inspect_meta_inf { |files| files.should eql(['README']) } + end + + it 'should include file tasks specified by :meta_inf option' do + files = ['README', 'DISCLAIMER'].each { |file| file(file) { |task| write task.to_s } } + package_with_meta_inf files.map { |f| file(f) } + inspect_meta_inf { |files| files.should eql(files) } + end + + it 'should complain if cannot find file' do + package_with_meta_inf 'README' + lambda { inspect_meta_inf }.should raise_error(RuntimeError, /README/) + end + + it 'should complain if cannot build task' do + file('README') { fail 'Failed' } + package_with_meta_inf 'README' + lambda { inspect_meta_inf }.should raise_error(RuntimeError, /Failed/) + end + + it 'should respond to with() and accept manifest and meta_inf' do + write 'DISCLAIMER' + mkpath 'target/classes' + packaging = @packaging + define('foo', :version=>'1.0') { package(packaging).with :meta_inf=>'DISCLAIMER' } + inspect_meta_inf { |files| files.should eql(['DISCLAIMER']) } + end +end + + +describe Packaging, 'jar' do + it_should_behave_like 'packaging' + before { @packaging = :jar } + it_should_behave_like 'package with manifest' + it_should_behave_like 'package with meta_inf' + before { @meta_inf_ignore = 'MANIFEST.MF' } + + it 'should place the manifest as the first entry of the file' do + write 'src/main/java/Test.java', 'class Test {}' + define('foo', :version=>'1.0') { package(:jar) } + project('foo').package(:jar).invoke + Zip::ZipFile.open(project('foo').package(:jar).to_s) do |jar| + entries_to_s = jar.entries.map(&:to_s).delete_if {|entry| entry[-1,1] == "/"} + # Sometimes META-INF/ is counted as first entry, which is fair game. + (entries_to_s.first == 'META-INF/MANIFEST.MF' || entries_to_s[1] == 'META-INF/MANIFEST.MF').should be_true + end + end + + it 'should use files from compile directory if nothing included' do + write 'src/main/java/Test.java', 'class Test {}' + define('foo', :version=>'1.0') { package(:jar) } + project('foo').package(:jar).invoke + Zip::ZipFile.open(project('foo').package(:jar).to_s) do |jar| + jar.entries.map(&:to_s).sort.should include('META-INF/MANIFEST.MF', 'Test.class') + end + end + + it 'should use files from resources directory if nothing included' do + write 'src/main/resources/test/important.properties' + define('foo', :version=>'1.0') { package(:jar) } + project('foo').package(:jar).invoke + Zip::ZipFile.open(project('foo').package(:jar).to_s) do |jar| + jar.entries.map(&:to_s).sort.should include('test/important.properties') + end + end + + it 'should include class directories' do + write 'src/main/java/code/Test.java', 'package code ; class Test {}' + define('foo', :version=>'1.0') { package(:jar) } + project('foo').package(:jar).invoke + Zip::ZipFile.open(project('foo').package(:jar).to_s) do |jar| + jar.entries.map(&:to_s).sort.should include('code/') + end + end + + it 'should include resource files starting with dot' do + write 'src/main/resources/test/.config' + define('foo', :version=>'1.0') { package(:jar) } + project('foo').package(:jar).invoke + Zip::ZipFile.open(project('foo').package(:jar).to_s) do |jar| + jar.entries.map(&:to_s).sort.should include('test/.config') + end + end + + it 'should include empty resource directories' do + mkpath 'src/main/resources/empty' + define('foo', :version=>'1.0') { package(:jar) } + project('foo').package(:jar).invoke + Zip::ZipFile.open(project('foo').package(:jar).to_s) do |jar| + jar.entries.map(&:to_s).sort.should include('empty/') + end + end + + it 'should raise error when calling with() with nil value' do + lambda { + define('foo', :version=>'1.0') { package(:jar).with(nil) } + }.should raise_error + end + + it 'should exclude resources when ordered to do so' do + write 'src/main/resources/foo.xml', '' + foo = define('foo', :version => '1.0') { package(:jar).exclude('foo.xml')} + foo.package(:jar).invoke + Zip::ZipFile.open(foo.package(:jar).to_s) do |jar| + jar.entries.map(&:to_s).sort.should_not include('foo.xml') + end + end + +end + + +describe Packaging, 'war' do + it_should_behave_like 'packaging' + before { @packaging = :war } + it_should_behave_like 'package with manifest' + it_should_behave_like 'package with meta_inf' + before { @meta_inf_ignore = 'MANIFEST.MF' } + + def make_jars + artifact('group:id:jar:1.0') { |t| write t.to_s } + artifact('group:id:jar:2.0') { |t| write t.to_s } + end + + def inspect_war + project('foo').package(:war).invoke + Zip::ZipFile.open(project('foo').package(:war).to_s) do |war| + yield war.entries.map(&:to_s).sort + end + end + + it 'should use files from webapp directory if nothing included' do + write 'src/main/webapp/test.html' + define('foo', :version=>'1.0') { package(:war) } + inspect_war { |files| files.should include('test.html') } + end + + it 'should accept files from :classes option' do + write 'src/main/java/Test.java', 'class Test {}' + write 'classes/test' + define('foo', :version=>'1.0') { package(:war).with(:classes=>'classes') } + inspect_war { |files| files.should include('WEB-INF/classes/test') } + end + + it 'should use files from compile directory if nothing included' do + write 'src/main/java/Test.java', 'class Test {}' + define('foo', :version=>'1.0') { package(:war) } + inspect_war { |files| files.should include('WEB-INF/classes/Test.class') } + end + + it 'should ignore compile directory if no source files to compile' do + define('foo', :version=>'1.0') { package(:war) } + inspect_war { |files| files.should_not include('target/classes') } + end + + it 'should include only specified classes directories' do + write 'src/main/java' + define('foo', :version=>'1.0') { package(:war).with :classes=>_('additional') } + project('foo').package(:war).classes.should_not include(project('foo').file('target/classes')) + project('foo').package(:war).classes.should include(project('foo').file('additional')) + end + + it 'should use files from resources directory if nothing included' do + write 'src/main/resources/test/important.properties' + define('foo', :version=>'1.0') { package(:war) } + inspect_war { |files| files.should include('WEB-INF/classes/test/important.properties') } + end + + it 'should include empty resource directories' do + mkpath 'src/main/resources/empty' + define('foo', :version=>'1.0') { package(:war) } + inspect_war { |files| files.should include('WEB-INF/classes/empty/') } + end + + it 'should accept file from :libs option' do + write 'lib/foo.jar' + define('foo', :version=>'1.0') { package(:war).libs << 'lib/foo.jar' } + inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/foo.jar') } + end + + + it 'should accept artifacts from :libs option' do + make_jars + define('foo', :version=>'1.0') { package(:war).with(:libs=>'group:id:jar:1.0') } + inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') } + end + + it 'should accept artifacts from :libs option' do + make_jars + define('foo', :version=>'1.0') { package(:war).with(:libs=>['group:id:jar:1.0', 'group:id:jar:2.0']) } + inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar', 'WEB-INF/lib/id-2.0.jar') } + end + + it 'should use artifacts from compile classpath if no libs specified' do + make_jars + define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war) } + inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar', 'WEB-INF/lib/id-2.0.jar') } + end + + it 'should use artifacts from compile classpath if no libs specified, leaving the user specify which to exclude as files' do + make_jars + define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war).path('WEB-INF/lib').exclude('id-2.0.jar') } + inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') } + end + + it 'should use artifacts from compile classpath if no libs specified, leaving the user specify which to exclude as files with glob expressions' do + make_jars + define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war).path('WEB-INF/lib').exclude('**/id-2.0.jar') } + inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') } + end + + it 'should exclude files regardless of the path where they are included, using wildcards' do + make_jars + define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war).exclude('**/id-2.0.jar') } + inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') } + end + + it 'should exclude files regardless of the path where they are included, specifying target path entirely' do + make_jars + define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war).exclude('WEB-INF/lib/id-2.0.jar') } + inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') } + end + + it 'should exclude files regardless of the path where they are included for war files' do + write 'src/main/java/com/example/included/Test.java', 'package com.example.included; class Test {}' + write 'src/main/java/com/example/excluded/Test.java', 'package com.example.excluded; class Test {}' + define('foo', :version=>'1.0') do + package(:war).enhance do |war| + war.exclude('WEB-INF/classes/com/example/excluded/**.class') + end + end + inspect_war do |files| + files.should include('WEB-INF/classes/com/example/included/Test.class') + files.should_not include('WEB-INF/classes/com/example/excluded/Test.class') + end + end + + it 'should include only specified libraries' do + define 'foo', :version=>'1.0' do + compile.with 'group:id:jar:1.0' + package(:war).with :libs=>'additional:id:jar:1.0' + end + project('foo').package(:war).libs.should_not include(artifact('group:id:jar:1.0')) + project('foo').package(:war).libs.should include(artifact('additional:id:jar:1.0')) + end + +end + + +describe Packaging, 'aar' do + it_should_behave_like 'packaging' + before { @packaging = :aar } + it_should_behave_like 'package with manifest' + it_should_behave_like 'package with meta_inf' + before do + write 'src/main/axis2/services.xml' + @meta_inf_ignore = ['MANIFEST.MF', 'services.xml'] + end + + def make_jars + artifact('group:id:jar:1.0') { |t| write t.to_s } + artifact('group:id:jar:2.0') { |t| write t.to_s } + end + + def inspect_aar + project('foo').package(:aar).invoke + Zip::ZipFile.open(project('foo').package(:aar).to_s) do |aar| + yield aar.entries.map(&:to_s).sort + end + end + + it 'should automatically include services.xml and any *.wsdl files under src/main/axis2' do + write 'src/main/axis2/my-service.wsdl' + define('foo', :version=>'1.0') { package(:aar) } + inspect_aar { |files| files.should include('META-INF/MANIFEST.MF', 'META-INF/services.xml', 'META-INF/my-service.wsdl') } + end + + it 'should accept files from :include option' do + write 'test' + define('foo', :version=>'1.0') { package(:aar).include 'test' } + inspect_aar { |files| files.should include('META-INF/MANIFEST.MF', 'test') } + end + + it 'should use files from compile directory if nothing included' do + write 'src/main/java/Test.java', 'class Test {}' + define('foo', :version=>'1.0') { package(:aar) } + inspect_aar { |files| files.should include('Test.class') } + end + + it 'should use files from resources directory if nothing included' do + write 'src/main/resources/test/important.properties' + define('foo', :version=>'1.0') { package(:aar) } + inspect_aar { |files| files.should include('test/important.properties') } + end + + it 'should include empty resource directories' do + mkpath 'src/main/resources/empty' + define('foo', :version=>'1.0') { package(:aar) } + inspect_aar { |files| files.should include('empty/') } + end + + it 'should accept file from :libs option' do + make_jars + define('foo', :version=>'1.0') { package(:aar).with :libs=>'group:id:jar:1.0' } + inspect_aar { |files| files.should include('META-INF/MANIFEST.MF', 'lib/id-1.0.jar') } + end + + it 'should accept file from :libs option' do + make_jars + define('foo', :version=>'1.0') { package(:aar).with :libs=>['group:id:jar:1.0', 'group:id:jar:2.0'] } + inspect_aar { |files| files.should include('META-INF/MANIFEST.MF', 'lib/id-1.0.jar', 'lib/id-2.0.jar') } + end + + it 'should NOT use artifacts from compile classpath if no libs specified' do + make_jars + define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:aar) } + inspect_aar { |files| files.should include('META-INF/MANIFEST.MF') } + end + + it 'should return all libraries from libs attribute' do + define 'foo', :version=>'1.0' do + compile.with 'group:id:jar:1.0' + package(:aar).with :libs=>'additional:id:jar:1.0' + end + project('foo').package(:aar).libs.should_not include(artifact('group:id:jar:1.0')) + project('foo').package(:aar).libs.should include(artifact('additional:id:jar:1.0')) + end + +end + + +describe Packaging, 'ear' do + it_should_behave_like 'packaging' + before { @packaging = :ear } + it_should_behave_like 'package with manifest' + it_should_behave_like 'package with meta_inf' + before { @meta_inf_ignore = ['MANIFEST.MF', 'application.xml'] } + + def inspect_ear + project('foo').package(:ear).invoke + Zip::ZipFile.open(project('foo').package(:ear).to_s) do |ear| + yield ear.entries.map(&:to_s).sort + end + end + + def inspect_application_xml + project('foo').package(:ear).invoke + Zip::ZipFile.open(project('foo').package(:ear).to_s) do |ear| + yield REXML::Document.new(ear.read('META-INF/application.xml')).root + end + end + + def inspect_classpath(package) + project('foo').package(:ear).invoke + Zip::ZipFile.open(project('foo').package(:ear).to_s) do |ear| + File.open('tmp.zip', 'wb') do |tmp| + tmp.write ear.file.read(package) + end + manifest = Buildr::Packaging::Java::Manifest.from_zip('tmp.zip') + yield manifest.main['Class-Path'].split(' ') + end + end + + it 'should set display name from project id' do + define 'foo', :version=>'1.0' do + package(:ear).display_name.should eql('foo') + define 'bar' do + package(:ear).display_name.should eql('foo-bar') + end + end + end + + it 'should set display name in application.xml' do + define 'foo', :version=>'1.0' do + package(:ear) + end + inspect_application_xml { |xml| xml.get_text('/application/display-name').should == 'foo' } + end + + it 'should accept different display name' do + define 'foo', :version=>'1.0' do + package(:ear).display_name = 'bar' + end + inspect_application_xml { |xml| xml.get_text('/application/display-name').should == 'bar' } + end + + it 'should set description in application.xml to project comment if not specified' do + desc "MyDescription" + define 'foo', :version=>'1.0' do + package(:ear) + end + inspect_application_xml { |xml| xml.get_text('/application/description').should == 'MyDescription' } + end + + it 'should not set description in application.xml if not specified and no project comment' do + define 'foo', :version=>'1.0' do + package(:ear) + end + inspect_application_xml { |xml| xml.get_text('/application/description').should be_nil } + end + + it 'should set description in application.xml if specified' do + define 'foo', :version=>'1.0' do + package(:ear).description = "MyDescription" + end + inspect_application_xml { |xml| xml.get_text('/application/description').should == 'MyDescription' } + end + + it 'should add security-roles to application.xml if given' do + define 'foo', :version=>'1.0' do + package(:ear).security_roles << {:id=>'sr1', + :description=>'System Administrator', :name=>'systemadministrator'} + end + inspect_application_xml do |xml| + xml.get_text("/application/security-role[@id='sr1']/description").to_s.should eql('System Administrator') + xml.get_text("/application/security-role[@id='sr1']/role-name").to_s.should eql('systemadministrator') + end + end + + it 'should map WARs to /war directory' do + define 'foo', :version=>'1.0' do + package(:ear) << package(:war) + end + inspect_ear { |files| files.should include('war/foo-1.0.war') } + end + + it 'should map EJBs to /ejb directory' do + define 'foo', :version=>'1.0' do + package(:ear).add :ejb=>package(:jar) + end + inspect_ear { |files| files.should include('ejb/foo-1.0.jar') } + end + + it 'should not modify original artifact for its components' do + define 'one', :version => '1.0' do + write 'src/main/resources/one.txt', '1' + package(:jar) + end + + define 'two', :version => '1.0' do + write 'src/main/resources/two.txt', '2' + package(:jar) + end + + define 'foo', :version => '1.0' do + package(:ear).add project(:one).package(:jar) + package(:ear).add :ejb => project(:two).package(:jar) + end + + inspect_ear { |files| files.should include('lib/one-1.0.jar', 'ejb/two-1.0.jar') } + + Buildr::Packaging::Java::Manifest.from_zip(project('one').package(:jar)).main['Class-Path'].should be_nil + Buildr::Packaging::Java::Manifest.from_zip(project('two').package(:jar)).main['Class-Path'].should be_nil + + inspect_classpath 'ejb/two-1.0.jar' do |classpath| + classpath.should include('../lib/one-1.0.jar') + end + end + + it 'should map JARs to /lib directory' do + define 'foo', :version=>'1.0' do + package(:ear) << package(:jar) + end + inspect_ear { |files| files.should include('lib/foo-1.0.jar') } + end + + it 'should accept component type with :type option' do + define 'foo', :version=>'1.0' do + package(:ear).add package(:jar), :type=>:ejb + end + inspect_ear { |files| files.should include('ejb/foo-1.0.jar') } + end + + it 'should accept component and its type as type=>artifact' do + define 'foo', :version=>'1.0' do + package(:ear).add :ejb=>package(:jar) + end + inspect_ear { |files| files.should include('ejb/foo-1.0.jar') } + end + + it 'should map typed JARs to /jar directory' do + define 'foo', :version=>'1.0' do + package(:ear).add :jar=>package(:jar) + end + inspect_ear { |files| files.should include('jar/foo-1.0.jar') } + end + + it 'should add multiple components at a time using the type=>component style' do + define 'bar', :version => '1.5' do + package(:war) # must be added as a webapp + package(:jar) # must be added as a shared lib + package(:zip) # this one should be excluded + end + define 'baz', :version => '1.5' do + package(:jar, :id => 'one') + package(:jar, :id => 'two') + end + define 'foo', :version => '1.0' do + package(:ear).add :lib => project('baz'), + :war => project('bar').package(:war), + :ejb => project('bar').package(:jar) + end + inspect_ear do |files| + files.should include(*%w{ lib/one-1.5.jar lib/two-1.5.jar war/bar-1.5.war ejb/bar-1.5.jar }) + files.should_not satisfy { files.any? { |f| f =~ /\.zip$/ } } + end + end + + it 'should add all EAR supported packages when given a project argument' do + define 'bar', :version => '1.5' do + package(:war) # must be added as a webapp + package(:jar) # must be added as a shared lib + package(:zip) # this one should be excluded + end + define 'baz', :version => '1.5' do + package(:war) + package(:jar) + end + define 'foo', :version => '1.0' do + package(:ear).add projects(:bar, :baz) + end + inspect_ear do |files| + files.should include('war/bar-1.5.war', 'lib/bar-1.5.jar', 'lib/baz-1.5.jar', 'war/baz-1.5.war') + files.should_not satisfy { files.any? { |f| f =~ /\.zip$/ } } + end + end + + it 'should complain about unknown component type' do + define 'foo', :version=>'1.0' do + lambda { package(:ear).add package(:zip) }.should raise_error(RuntimeError, /ear component/i) + end + end + + it 'should allow unknown component types with explicit type' do + define 'foo', :version=>'1.0' do + package(:ear).add :lib=>package(:zip) + end + inspect_ear { |files| files.should include('lib/foo-1.0.zip') } + end + + it 'should accept alternative directory name' do + define 'foo', :version=>'1.0' do + package(:ear).add package(:jar), :path=>'trash' + end + inspect_ear { |files| files.should include('trash/foo-1.0.jar') } + end + + it 'should accept customization of directory map' do + define 'foo', :version=>'1.0' do + package(:ear).dirs[:jar] = 'jarred' + package(:ear).add :jar=>package(:jar) + end + inspect_ear { |files| files.should include('jarred/foo-1.0.jar') } + end + + it 'should accept customization of directory map with nil paths in application.xml' do + define 'foo', :version=>'1.0' do + package(:ear).dirs[:war] = nil + package(:ear).add :war=>package(:war) + package(:ear).add package(:jar) + end + inspect_ear { |files| files.should include('foo-1.0.war') } + inspect_application_xml do |xml| + xml.get_text("/application/module[@id='foo']/web/web-uri").to_s.should eql('foo-1.0.war') + end + end + + it 'should accept customization of directory map with nil paths in the classpath' do + define 'foo', :version=>'1.0' do + package(:ear).dirs[:lib] = nil + package(:ear).add :war=>package(:war) + package(:ear) << package(:jar) + end + inspect_classpath 'war/foo-1.0.war' do |classpath| + classpath.should include('../foo-1.0.jar') + end + end + + it 'should list WAR components in application.xml' do + define 'foo', :version=>'1.0' do + package(:ear) << package(:war) << package(:war, :id=>'bar') + end + inspect_application_xml do |xml| + xml.get_elements("/application/module[@id='foo'][web]").should_not be_empty + xml.get_elements("/application/module[@id='bar'][web]").should_not be_empty + end + end + + it 'should specify web-uri for WAR components in application.xml' do + define 'foo', :version=>'1.0' do + package(:ear) << package(:war) + package(:ear).add package(:war, :id=>'bar'), :path=>'ws' + end + inspect_application_xml do |xml| + xml.get_text("/application/module[@id='foo']/web/web-uri").to_s.should eql('war/foo-1.0.war') + xml.get_text("/application/module[@id='bar']/web/web-uri").to_s.should eql('ws/bar-1.0.war') + end + end + + it 'should specify context-root for WAR components in application.xml' do + define 'foo', :version=>'1.0' do + package(:ear) << package(:war) + package(:ear).add package(:war, :id=>'bar') + end + inspect_application_xml do |xml| + xml.get_text("/application/module[@id='foo']/web/context-root").to_s.should eql('/foo') + xml.get_text("/application/module[@id='bar']/web/context-root").to_s.should eql('/bar') + end + end + + it 'should accept context-root for WAR components in application.xml' do + define 'foo', :version=>'1.0' do + package(:ear).add package(:war), :context_root=>'rooted' + end + inspect_application_xml do |xml| + xml.get_text("/application/module[@id='foo']/web/context-root").to_s.should eql('/rooted') + end + end + + it 'should allow disabling the context root' do + define 'foo', :version=>'1.0' do + package(:ear).add package(:war), :context_root=>false + end + inspect_application_xml do |xml| + xml.get_elements("/application/module[@id='foo']/web/context-root").should be_empty + end + end + + it 'should list EJB components in application.xml' do + define 'foo', :version=>'1.0' do + package(:ear).add :ejb=>package(:jar) + package(:ear).add :ejb=>package(:jar, :id=>'bar') + end + inspect_application_xml do |xml| + xml.get_text("/application/module[@id='foo']/ejb").to_s.should eql('ejb/foo-1.0.jar') + xml.get_text("/application/module[@id='bar']/ejb").to_s.should eql('ejb/bar-1.0.jar') + end + end + + it 'should list JAR components in application.xml' do + define 'foo', :version=>'1.0' do + package(:ear) << { :jar=>package(:jar) } << { :jar=>package(:jar, :id=>'bar') } + end + inspect_application_xml do |xml| + jars = xml.get_elements('/application/jar').map(&:texts).map(&:join) + jars.should include('jar/foo-1.0.jar', 'jar/bar-1.0.jar') + end + end + + it 'should update WAR component classpath to include libraries' do + define 'foo', :version=>'1.0' do + package(:ear) << package(:jar, :id=>'lib1') << package(:jar, :id=>'lib2') + package(:ear).add package(:war) + end + inspect_classpath 'war/foo-1.0.war' do |classpath| + classpath.should include('../lib/lib1-1.0.jar', '../lib/lib2-1.0.jar') + end + end + + it 'should update WAR component classpath but skip internal libraries' do + define 'foo', :version=>'1.0' do + package(:ear) << package(:jar, :id=>'lib1') << package(:jar, :id=>'lib2') + package(:war).with(:libs=>package(:jar, :id=>'lib1')) + package(:ear).add package(:war) + end + inspect_classpath 'war/foo-1.0.war' do |classpath| + classpath.should_not include('../lib/lib1-1.0.jar') + classpath.should include('../lib/lib2-1.0.jar') + end + end + + it 'should update EJB component classpath to include libraries' do + define 'foo', :version=>'1.0' do + package(:ear) << package(:jar, :id=>'lib1') << package(:jar, :id=>'lib2') + package(:ear).add :ejb=>package(:jar, :id=>'foo') + end + inspect_classpath 'ejb/foo-1.0.jar' do |classpath| + classpath.should include('../lib/lib1-1.0.jar', '../lib/lib2-1.0.jar') + end + end + + it 'should update JAR component classpath to include libraries' do + define 'foo', :version=>'1.0' do + package(:ear) << package(:jar, :id=>'lib1') << package(:jar, :id=>'lib2') + package(:ear).add :jar=>package(:jar, :id=>'foo') + end + inspect_classpath 'jar/foo-1.0.jar' do |classpath| + classpath.should include('../lib/lib1-1.0.jar', '../lib/lib2-1.0.jar') + end + end + + it 'should deal with very long classpaths' do + define 'foo', :version=>'1.0' do + 20.times { |i| package(:ear) << package(:jar, :id=>"lib#{i}") } + package(:ear).add :jar=>package(:jar, :id=>'foo') + end + inspect_classpath 'jar/foo-1.0.jar' do |classpath| + classpath.should include('../lib/lib1-1.0.jar', '../lib/lib2-1.0.jar') + end + end + + + it 'should generate relative classpaths for top level EJB' do + define 'foo', :version => '1.0' do + package(:ear).add package(:jar, :id => 'one'), :path => '.' + package(:ear).add package(:jar, :id => 'two'), :path => 'dos' + package(:ear).add package(:jar, :id => 'three'), :path => 'tres' + package(:ear).add :ejb => package(:jar, :id => 'ejb1'), :path => '.' + end + inspect_classpath 'ejb1-1.0.jar' do |classpath| + classpath.should include(*%w{ one-1.0.jar dos/two-1.0.jar tres/three-1.0.jar }) + end + end + + it 'should generate relative classpaths for second level EJB' do + define 'foo', :version => '1.0' do + package(:ear).add package(:jar, :id => 'one'), :path => '.' + package(:ear).add package(:jar, :id => 'two'), :path => 'dos' + package(:ear).add package(:jar, :id => 'three'), :path => 'tres' + package(:ear).add :ejb => package(:jar, :id => 'ejb2'), :path => 'dos' + end + inspect_classpath 'dos/ejb2-1.0.jar' do |classpath| + classpath.should include(*%w{ ../one-1.0.jar two-1.0.jar ../tres/three-1.0.jar }) + end + end + + it 'should generate relative classpaths for nested EJB' do + define 'foo', :version => '1.0' do + package(:ear).add package(:jar, :id => 'one'), :path => '.' + package(:ear).add package(:jar, :id => 'two'), :path => 'dos' + package(:ear).add package(:jar, :id => 'three'), :path => 'dos/tres' + package(:ear).add package(:jar, :id => 'four'), :path => 'dos/cuatro' + package(:ear).add :ejb => package(:jar, :id => 'ejb4'), :path => 'dos/cuatro' + end + inspect_classpath 'dos/cuatro/ejb4-1.0.jar' do |classpath| + classpath.should include(*%w{ ../../one-1.0.jar ../two-1.0.jar ../tres/three-1.0.jar four-1.0.jar }) + end + end + +end + + +describe Packaging, 'sources' do + it_should_behave_like 'packaging' + before { @packaging, @package_type = :sources, :jar } + + it 'should create package of type :jar and classifier \'sources\'' do + define 'foo', :version=>'1.0' do + package(:sources).type.should eql(:jar) + package(:sources).classifier.should eql('sources') + package(:sources).name.should match(/foo-1.0-sources.jar$/) + end + end + + it 'should contain source and resource files' do + write 'src/main/java/Source.java' + write 'src/main/resources/foo.properties', 'foo=bar' + define('foo', :version=>'1.0') { package(:sources) } + project('foo').task('package').invoke + project('foo').packages.first.should contain('Source.java') + project('foo').packages.first.should contain('foo.properties') + end + + it 'should create sources jar if resources exists (but not sources)' do + write 'src/main/resources/foo.properties', 'foo=bar' + define('foo', :version=>'1.0') { package(:sources) } + project('foo').package(:sources).invoke + project('foo').packages.first.should contain('foo.properties') + end + + it 'should be a ZipTask' do + define 'foo', :version=>'1.0' do + package(:sources).should be_kind_of(ZipTask) + end + end +end + +describe Packaging, 'javadoc' do + it_should_behave_like 'packaging' + before { @packaging, @package_type = :javadoc, :jar } + + it 'should create package of type :zip and classifier \'javadoc\'' do + define 'foo', :version=>'1.0' do + package(:javadoc).type.should eql(:jar) + package(:javadoc).classifier.should eql('javadoc') + package(:javadoc).name.pathmap('%f').should eql('foo-1.0-javadoc.jar') + end + end + + it 'should contain Javadocs' do + write 'src/main/java/Source.java', 'public class Source {}' + define('foo', :version=>'1.0') { package(:javadoc) } + project('foo').task('package').invoke + project('foo').packages.first.should contain('Source.html', 'index.html') + end + + it 'should use project description in window title' do + write 'src/main/java/Source.java', 'public class Source {}' + desc 'My Project' + define('foo', :version=>'1.0') { package(:javadoc) } + project('foo').task('package').invoke + project('foo').packages.first.entry('index.html').should contain('My Project') + end + + it 'should be a ZipTask' do + define 'foo', :version=>'1.0' do + package(:javadoc).should be_kind_of(ZipTask) + end + end +end + +shared_examples_for 'package_with_' do + + def prepare(options = {}) + packager = "package_with_#{@packaging}" + write 'src/main/java/Source.java' + write 'baz/src/main/java/Source.java' + define 'foo', :version=>'1.0' do + send packager, options + define 'bar' ; define 'baz' + end + end + + def applied_to + projects.select { |project| project.packages.first }.map(&:name) + end + + it 'should create package of the right packaging with classifier' do + prepare + project('foo').packages.first.to_s.should =~ /foo-1.0-#{@packaging}.#{@ext}/ + end + + it 'should create package for projects that have source files' do + prepare + applied_to.should include('foo', 'foo:baz') + end + + it 'should not create package for projects that have no source files' do + prepare + applied_to.should_not include('foo:bar') + end + + it 'should limit to projects specified by :only' do + prepare :only=>'baz' + applied_to.should eql(['foo:baz']) + end + + it 'should limit to projects specified by :only array' do + prepare :only=>['baz'] + applied_to.should eql(['foo:baz']) + end + + it 'should ignore project specified by :except' do + prepare :except=>'baz' + applied_to.should eql(['foo']) + end + + it 'should ignore projects specified by :except array' do + prepare :except=>['baz'] + applied_to.should eql(['foo']) + end +end + +describe 'package_with_sources' do + it_should_behave_like 'package_with_' + before { @packaging, @ext = :sources, 'jar' } +end + +describe 'package_with_javadoc' do + it_should_behave_like 'package_with_' + before { @packaging, @ext = :javadoc, 'jar' } +end diff --git a/buildr/spec/java/pom_spec.rb b/buildr/spec/java/pom_spec.rb new file mode 100644 index 0000000..bb6fbe3 --- /dev/null +++ b/buildr/spec/java/pom_spec.rb @@ -0,0 +1,70 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) +require 'fileutils' + +describe Buildr::POM do + before do + repositories.remote = 'http://example.com' + @app = 'group:pomapp:jar:1.0' + write artifact(@app).pom.to_s, <<-XML + + pomapp + group + + + library + org.example + 1.1 + runtime + + + javax.mail + mail + + + + + +XML + @library = 'org.example:library:jar:1.1' + write artifact(@library).pom.to_s, <<-XML + + pomapp + group + + + mail + javax.mail + 1.0 + + + foo + org.example + 2.0 + + + +XML + end + + it 'should respect exclusions when computing transitive dependencies' do + pom = POM.load(artifact(@app).pom) + specs = [ 'org.example:library:jar:1.1', 'org.example:foo:jar:2.0' ] + pom.dependencies.should eql(specs) + end +end + diff --git a/buildr/spec/java/run_spec.rb b/buildr/spec/java/run_spec.rb new file mode 100644 index 0000000..9c0a238 --- /dev/null +++ b/buildr/spec/java/run_spec.rb @@ -0,0 +1,78 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Run::JavaRunner do + + it 'should fail on error' do + define 'foo' do + run.using :java, :main => 'org.example.NonExistentMain' # class doesn't exist + end + lambda { project('foo').run.invoke }.should raise_error(RuntimeError, /Failed to execute java/) + end + + it 'should execute main class' do + write 'src/main/java/org/example/Main.java', <<-JAVA + package org.example; + public class Main { + public static void main(String[] args) { + System.out.println("Hello, world!"); + } + } + JAVA + define 'foo' do + run.using :main => 'org.example.Main' + end + project('foo').run.prerequisites.should include(task("foo:compile")) + end + + it 'should accept :main option as an array including parameters for the main class' do + define 'foo' do + run.using :java, :main => ['org.example.Main', '-t', 'input.txt'] + end + Java::Commands.should_receive(:java).once.with do |*args| + args[0].should == ['org.example.Main', '-t', 'input.txt'] + end + project('foo').run.invoke + end + + it 'should accept :java_args and pass them to java' do + define 'foo' do + run.using :java, :main => 'foo', :java_args => ['-server'] + end + Java::Commands.should_receive(:java).once.with do |*args| + args[0].should == 'foo' + args[1][:java_args].should include('-server') + end + project('foo').run.invoke + end + + it 'should accept :properties and pass them as -Dproperty=value to java' do + define 'foo' do + run.using :java, :main => 'foo', :properties => { :foo => 'one', :bar => 'two' } + end + Java::Commands.should_receive(:java).once.with do |*args| + args[0].should == 'foo' + args[1][:properties][:foo].should == 'one' + args[1][:properties][:bar].should == 'two' + end + project('foo').run.invoke + end + +end + diff --git a/buildr/spec/java/test_coverage_helper.rb b/buildr/spec/java/test_coverage_helper.rb new file mode 100644 index 0000000..3b555d1 --- /dev/null +++ b/buildr/spec/java/test_coverage_helper.rb @@ -0,0 +1,257 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +module TestCoverageHelper + def write_test options + write File.join(options[:in], "#{options[:for]}Test.java"), + "public class #{options[:for]}Test extends junit.framework.TestCase { public void test#{options[:for]}() { new #{options[:for]}(); } }" + end + + # Rspec matcher using file glob patterns. + class FileNamePatternMatcher + def initialize(pattern) + @expected_pattern = pattern + @pattern_matcher = lambda { |filename| File.fnmatch? pattern, filename } + end + + def matches?(directory) + @actual_filenames = Dir[File.join(directory,'*')] + @actual_filenames.any? &@pattern_matcher + end + + def failure_message + "Expected to find at least one element matching '#{@expected_pattern}' among #{@actual_filenames.inspect}, but found none" + end + + def negative_failure_message + "Expected to find no element matching '#{@expected_pattern}' among #{@actual_filenames.inspect}, but found matching element(s) #{@actual_filenames.select(&@pattern_matcher).inspect}" + end + end + + # Test if a directory contains at least one file matching a given glob pattern. + # + # For example, to check that a directory contains at least one HTML file: + # '/path/to/some/directory'.should have_files_matching('*.html') + def have_files_matching pattern + FileNamePatternMatcher.new pattern + end +end + +shared_examples_for 'test coverage tool' do + include TestCoverageHelper + + def toolname + @tool_module.name.split('::').last.downcase + end + + def test_coverage_config + project('foo').send(toolname) + end + + describe 'project-specific' do + + before do + write 'src/main/java/Foo.java', 'public class Foo {}' + write_test :for=>'Foo', :in=>'src/test/java' + end + + describe 'clean' do + before { define('foo') } + + it 'should remove the instrumented directory' do + mkdir_p test_coverage_config.instrumented_dir.to_s + task('foo:clean').invoke + file(test_coverage_config.instrumented_dir).should_not exist + end + + it 'should remove the reporting directory' do + mkdir_p test_coverage_config.report_dir + task('foo:clean').invoke + file(test_coverage_config.report_dir).should_not exist + end + end + + describe 'instrumented directory' do + it 'should have a default value' do + define('foo') + test_coverage_config.instrumented_dir.should point_to_path('target/instrumented/classes') + end + + it 'should be overridable' do + toolname = toolname() + define('foo') { send(toolname).instrumented_dir = path_to('target/coverage/classes') } + test_coverage_config.instrumented_dir.should point_to_path('target/coverage/classes') + end + + it 'should be created during instrumentation' do + define('foo') + task("foo:#{toolname}:instrument").invoke + file(test_coverage_config.instrumented_dir).should exist + end + end + + describe 'instrumentation' do + def instrumented_dir + file(test_coverage_config.instrumented_dir) + end + + it 'should happen after compile' do + define('foo') + lambda { task("foo:#{toolname}:instrument").invoke }.should run_task('foo:compile') + end + + it 'should put classes from compile.target in the instrumented directory' do + define('foo') + task("foo:#{toolname}:instrument").invoke + Dir.entries(instrumented_dir.to_s).should == Dir.entries(project('foo').compile.target.to_s) + end + + it 'should touch instrumented directory if anything instrumented' do + a_long_time_ago = Time.now - 10 + define('foo') + mkpath instrumented_dir.to_s + File.utime(a_long_time_ago, a_long_time_ago, instrumented_dir.to_s) + task("foo:#{toolname}:instrument").invoke + instrumented_dir.timestamp.should be_close(Time.now, 2) + end + + it 'should not touch instrumented directory if nothing instrumented' do + a_long_time_ago = Time.now - 10 + define('foo').compile.invoke + mkpath instrumented_dir.to_s + [project('foo').compile.target, instrumented_dir].map(&:to_s).each { |dir| File.utime(a_long_time_ago, a_long_time_ago, dir) } + task("foo:#{toolname}:instrument").invoke + instrumented_dir.timestamp.should be_close(a_long_time_ago, 2) + end + end + + describe 'testing classpath' do + it 'should give priority to instrumented classes over non-instrumented ones' do + define('foo') + depends = project('foo').test.dependencies + depends.index(test_coverage_config.instrumented_dir).should < depends.index(project('foo').compile.target) + end + + it 'should have the test coverage tools artifacts' do + define('foo') + artifacts(@tool_module.dependencies).each { |artifact| project('foo').test.dependencies.should include(artifact) } + end + end + + describe 'html report' do + it 'should have html files' do + define('foo') + task("foo:#{toolname}:html").invoke + test_coverage_config.report_to(:html).should have_files_matching('*.html') + end + + it 'should contain full source code, including comments' do + write 'src/main/java/Foo.java', + 'public class Foo { /* This comment is a TOKEN to check that test coverage reports include the source code */ }' + define('foo') + task("foo:#{toolname}:html").invoke + html_report_contents = Dir[File.join(test_coverage_config.report_dir, '**/*.html')].map{|path|File.open(path).read}.join + html_report_contents.force_encoding('ascii-8bit') if RUBY_VERSION >= '1.9' + html_report_contents.should =~ /TOKEN/ + end + end + end + + describe 'cross-project' do + describe 'reporting' do + before do + write 'src/main/java/Foo.java', 'public class Foo {}' + write 'bar/src/main/java/Bar.java', 'public class Bar {}' + write_test :for=>'Bar', :in=>'bar/src/test/java' + define('foo') { define('bar') } + end + + it 'should have a default target' do + @tool_module.report_to.should point_to_path(File.join('reports', toolname)) + end + + describe 'in html' do + it 'should be a defined task' do + Rake::Task.task_defined?("#{toolname}:html").should be(true) + end + + it 'should happen after project instrumentation and testing' do + lambda { task("#{toolname}:html").invoke }.should run_tasks(["foo:#{toolname}:instrument", 'foo:bar:test']) + end + + it 'should have html files' do + task("#{toolname}:html").invoke + @tool_module.report_to(:html).should have_files_matching('*.html') + end + + it 'should contain full source code, including comments' do + write 'bar/src/main/java/Bar.java', + 'public class Bar { /* This comment is a TOKEN to check that test coverage reports include the source code */ }' + task("#{toolname}:html").invoke + html_report_contents = Dir[File.join(@tool_module.report_to(:html), '**/*.html')].map{|path|File.read(path)}.join + html_report_contents.force_encoding('ascii-8bit') if RUBY_VERSION >= '1.9' + html_report_contents.should =~ /TOKEN/ + end + + it 'should handle gracefully a project with no source' do + define 'baz', :base_dir=>'baz' + task("#{toolname}:html").invoke + lambda { task("#{toolname}:html").invoke }.should_not raise_error + end + end + end + + describe 'clean' do + it 'should remove the report directory' do + define('foo') + mkdir_p @tool_module.report_to + task("#{toolname}:clean").invoke + file(@tool_module.report_to).should_not exist + end + + it 'should be called when calling global clean' do + define('foo') + lambda { task('clean').invoke }.should run_task("#{toolname}:clean") + end + end + end + + describe 'project with no source' do + it 'should not define an html report task' do + define 'foo' + Rake::Task.task_defined?("foo:#{toolname}:html").should be(false) + end + + it 'should not raise an error when instrumenting' do + define('foo') + lambda { task("foo:#{toolname}:instrument").invoke }.should_not raise_error + end + + it 'should not add the instrumented directory to the testing classpath' do + define 'foo' + depends = project('foo').test.dependencies + depends.should_not include(test_coverage_config.instrumented_dir) + end + + it 'should not add the test coverage tools artifacts to the testing classpath' do + define('foo') + @tool_module.dependencies.each { |artifact| project('foo').test.dependencies.should_not include(artifact) } + end + end +end diff --git a/buildr/spec/java/tests_spec.rb b/buildr/spec/java/tests_spec.rb new file mode 100644 index 0000000..0d09d9f --- /dev/null +++ b/buildr/spec/java/tests_spec.rb @@ -0,0 +1,682 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +describe Buildr::JUnit do + it 'should be the default test framework when test cases are in java' do + write 'src/test/java/com/exampe/FirstTest.java', <<-JAVA + package com.example; + public class FirstTest extends junit.framework.TestCase { } + JAVA + define 'foo' + project('foo').test.framework.should eql(:junit) + end + + it 'should be picked if the test language is Java' do + define 'foo' do + test.compile.using(:javac) + test.framework.should eql(:junit) + end + end + + it 'should include JUnit dependencies' do + define('foo') { test.using(:junit) } + project('foo').test.compile.dependencies.should include(artifact("junit:junit:jar:#{JUnit.version}")) + project('foo').test.dependencies.should include(artifact("junit:junit:jar:#{JUnit.version}")) + end + + it 'should have REQUIRES up to version 1.5 since it was deprecated in 1.3.3' do + Buildr::VERSION.should < '1.5' + lambda { JUnit::REQUIRES }.should_not raise_error + end + + it 'should pick JUnit version from junit build settings' do + Buildr::JUnit.instance_eval { @dependencies = nil } + write 'build.yaml', 'junit: 1.2.3' + define('foo') { test.using(:junit) } + project('foo').test.compile.dependencies.should include(artifact("junit:junit:jar:1.2.3")) + end + + it 'should include JMock dependencies' do + define('foo') { test.using(:junit) } + two_or_later = JMock.version[0,1].to_i >= 2 + group = two_or_later ? "org.jmock" : "jmock" + project('foo').test.compile.dependencies.should include(artifact("#{group}:jmock:jar:#{JMock.version}")) + project('foo').test.dependencies.should include(artifact("#{group}:jmock:jar:#{JMock.version}")) + end + + it 'should pick JUnit version from junit build settings' do + Buildr::JUnit.instance_eval { @dependencies = nil } # JUnit caches JMock dependencies + Buildr::JMock.instance_eval { @dependencies = nil } + write 'build.yaml', 'jmock: 1.2.3' + define('foo') { test.using(:junit) } + project('foo').test.compile.dependencies.should include(artifact("jmock:jmock:jar:1.2.3")) + end + + it 'should include public classes extending junit.framework.TestCase' do + write 'src/test/java/com/example/FirstTest.java', <<-JAVA + package com.example; + public class FirstTest extends junit.framework.TestCase { + public void testNothing() { } + } + JAVA + write 'src/test/java/com/example/AnotherOne.java', <<-JAVA + package com.example; + public class AnotherOne extends junit.framework.TestCase { + public void testNothing() { } + } + JAVA + define('foo').test.invoke + project('foo').test.tests.should include('com.example.FirstTest', 'com.example.AnotherOne') + end + + it 'should include public classes with annotated test cases' do + write 'src/test/java/com/example/FirstTest.java', <<-JAVA + package com.example; + import org.junit.Test; + public class FirstTest { + public void utilityMethod() { } + @Test + public void annotated() { } + } + JAVA + define('foo').test.invoke + project('foo').test.tests.should include('com.example.FirstTest') + end + + it 'should include public classes with RunWith annotation' do + write 'src/test/java/com/example/TestSuite.java', <<-JAVA + package com.example; + import org.junit.Test; + public class TestSuite { + @Test + public void annotated() { } + } + JAVA + write 'src/test/java/com/example/RunSuite.java', <<-JAVA + package com.example; + import org.junit.runner.RunWith; + import org.junit.runners.Suite; + @RunWith(Suite.class) + @Suite.SuiteClasses({TestSuite.class}) + public class RunSuite { + } + JAVA + define('foo').test.invoke + project('foo').test.tests.should include('com.example.RunSuite') + end + + it 'should ignore classes not extending junit.framework.TestCase' do + write 'src/test/java/NotATest.java', <<-JAVA + public class NotATest { } + JAVA + define('foo').test.invoke + project('foo').test.tests.should be_empty + end + + it 'should ignore inner classes' do + write 'src/test/java/InnerClassTest.java', <<-JAVA + public class InnerClassTest extends junit.framework.TestCase { + public void testNothing() { } + + public class InnerTest extends junit.framework.TestCase { + public void testNothing() { } + } + } + JAVA + define('foo').test.invoke + project('foo').test.tests.should eql(['InnerClassTest']) + end + + it 'should ignore abstract classes' do + write 'src/test/java/AbstractClassTest.java', <<-JAVA + public abstract class AbstractClassTest extends junit.framework.TestCase { + public void testNothing() { } + } + JAVA + define('foo').test.invoke + project('foo').test.tests.should be_empty + end + + it 'should ignore classes with no tests in them' do + write 'src/test/java/NoTests.java', <<-JAVA + public class NoTests { + } + JAVA + define('foo').test.invoke + project('foo').test.tests.should be_empty + end + + it 'should pass when JUnit test case passes' do + write 'src/test/java/PassingTest.java', <<-JAVA + public class PassingTest extends junit.framework.TestCase { + public void testNothing() {} + } + JAVA + lambda { define('foo').test.invoke }.should_not raise_error + end + + it 'should fail when JUnit test case fails' do + write 'src/test/java/FailingTest.java', <<-JAVA + public class FailingTest extends junit.framework.TestCase { + public void testFailure() { + assertTrue(false); + } + } + JAVA + lambda { define('foo').test.invoke }.should raise_error(RuntimeError, /Tests failed/) rescue nil + end + + it 'should report failed test names' do + write 'src/test/java/FailingTest.java', <<-JAVA + public class FailingTest extends junit.framework.TestCase { + public void testFailure() { + assertTrue(false); + } + } + JAVA + define('foo').test.invoke rescue + project('foo').test.failed_tests.should include('FailingTest') + end + + it 'should report to reports/junit' do + write 'src/test/java/PassingTest.java', <<-JAVA + public class PassingTest extends junit.framework.TestCase { + public void testNothing() {} + } + JAVA + define 'foo' do + test.report_to.should be(file('reports/junit')) + end + project('foo').test.invoke + project('foo').file('reports/junit/TEST-PassingTest.txt').should exist + project('foo').file('reports/junit/TEST-PassingTest.xml').should exist + end + + it 'should pass properties to JVM' do + write 'src/test/java/PropertyTest.java', <<-JAVA + public class PropertyTest extends junit.framework.TestCase { + public void testProperty() { + assertEquals("value", System.getProperty("name")); + } + } + JAVA + define('foo').test.using :properties=>{ 'name'=>'value' } + project('foo').test.invoke + project('foo').test.options[:properties]["baseDir"].should eql(project("foo").test.compile.target.to_s) + end + + it 'should pass environment to JVM' do + write 'src/test/java/EnvironmentTest.java', <<-JAVA + public class EnvironmentTest extends junit.framework.TestCase { + public void testEnvironment() { + assertEquals("value", System.getenv("NAME")); + } + } + JAVA + define('foo').test.using :environment=>{ 'NAME'=>'value' } + project('foo').test.invoke + end + + it 'should set current directory' do + mkpath 'baz' + expected = File.expand_path('baz') + expected.gsub!('/', '\\') if expected =~ /^[A-Z]:/ # Java returns back slashed paths for windows + write 'baz/src/test/java/CurrentDirectoryTest.java', <<-JAVA + public class CurrentDirectoryTest extends junit.framework.TestCase { + public void testCurrentDirectory() throws Exception { + assertEquals(#{expected.inspect}, new java.io.File(".").getCanonicalPath()); + } + } + JAVA + define 'bar' do + define 'baz' do + test.include 'CurrentDirectoryTest' + end + end + project('bar:baz').test.invoke + end + + def fork_tests(mode) + write 'src/test/java/Shared.java', <<-JAVA + public class Shared { + public static boolean flag = false; + } + JAVA + write 'src/test/java/TestCase1.java', <<-JAVA + public class TestCase1 extends junit.framework.TestCase { + public void testSameVM() { + assertFalse(Shared.flag); + Shared.flag = true; + } + } + JAVA + write 'src/test/java/TestCase2.java', <<-JAVA + public class TestCase2 extends junit.framework.TestCase { + public void testSameVM() { + assertFalse(Shared.flag); + Shared.flag = true; + } + } + JAVA + define 'foo' do + test.using :fork=>mode, :fail_on_failure=>false + end + project('foo').test.invoke + end + + it 'should run all test cases in same VM if fork is once' do + fork_tests :once + project('foo').test.failed_tests.size.should eql(1) + end + + it 'should run each test case in separate same VM if fork is each' do + fork_tests :each + project('foo').test.failed_tests.should be_empty + end + + after do + # Yes, this is ugly. Better solution? + Buildr::JUnit.instance_eval { @dependencies = nil } + Buildr::JMock.instance_eval { @dependencies = nil } + end +end + + +describe Buildr::JUnit, 'report' do + it 'should default to the target directory reports/junit' do + JUnit.report.target.should eql('reports/junit') + end + + it 'should generate report into the target directory' do + JUnit.report.target = 'test-report' + lambda { task('junit:report').invoke }.should change { File.exist?(JUnit.report.target) }.to(true) + end + + it 'should clean after itself' do + mkpath JUnit.report.target + lambda { task('clean').invoke }.should change { File.exist?(JUnit.report.target) }.to(false) + end + + it 'should generate a consolidated XML report' do + lambda { task('junit:report').invoke }.should change { File.exist?('reports/junit/TESTS-TestSuites.xml') }.to(true) + end + + it 'should default to generating a report with frames' do + JUnit.report.frames.should be_true + end + + it 'should generate single page when frames is false' do + JUnit.report.frames = false + task('junit:report').invoke + file('reports/junit/html/junit-noframes.html').should exist + end + + it 'should generate frame page when frames is false' do + JUnit.report.frames = true + task('junit:report').invoke + file('reports/junit/html/index.html').should exist + end + + it 'should generate reports from all projects that ran test cases' do + write 'src/test/java/TestSomething.java', <<-JAVA + public class TestSomething extends junit.framework.TestCase { + public void testNothing() {} + } + JAVA + define 'foo' + project('foo').test.invoke + task('junit:report').invoke + FileList['reports/junit/html/*TestSomething.html'].size.should be(1) + end + + after do + JUnit.instance_eval { @report = nil } + end +end + + +describe Buildr::TestNG do + it 'should be selectable in project' do + define 'foo' do + test.using(:testng) + test.framework.should eql(:testng) + end + end + + it 'should be selectable in parent project' do + write 'bar/src/test/java/TestCase.java' + define 'foo' do + test.using(:testng) + define 'bar' + end + project('foo:bar').test.framework.should eql(:testng) + end + + it 'should include TestNG dependencies' do + define('foo') { test.using :testng } + project('foo').test.compile.dependencies.should include(artifact("org.testng:testng:jar:jdk15:#{TestNG.version}")) + project('foo').test.dependencies.should include(artifact("org.testng:testng:jar:jdk15:#{TestNG.version}")) + end + + it 'should include TestNG dependencies' do + define('foo') { test.using :testng } + two_or_later = JMock.version[0,1].to_i >= 2 + group = two_or_later ? "org.jmock" : "jmock" + project('foo').test.compile.dependencies.should include(artifact("#{group}:jmock:jar:#{JMock.version}")) + project('foo').test.dependencies.should include(artifact("#{group}:jmock:jar:#{JMock.version}")) + end + + it 'should include classes using TestNG annotations' do + write 'src/test/java/com/example/AnnotatedClass.java', <<-JAVA + package com.example; + @org.testng.annotations.Test + public class AnnotatedClass { } + JAVA + write 'src/test/java/com/example/AnnotatedMethod.java', <<-JAVA + package com.example; + public class AnnotatedMethod { + @org.testng.annotations.Test + public void annotated() { } + } + JAVA + define('foo') { test.using(:testng) } + project('foo').test.invoke + project('foo').test.tests.should include('com.example.AnnotatedClass', 'com.example.AnnotatedMethod') + end + + it 'should ignore classes not using TestNG annotations' do + write 'src/test/java/NotATestClass.java', 'public class NotATestClass {}' + define('foo') { test.using(:testng) } + project('foo').test.invoke + project('foo').test.tests.should be_empty + end + + it 'should ignore inner classes' do + write 'src/test/java/InnerClassTest.java', <<-JAVA + @org.testng.annotations.Test + public class InnerClassTest { + public class InnerTest { + } + } + JAVA + define('foo') { test.using(:testng) } + project('foo').test.invoke + project('foo').test.tests.should eql(['InnerClassTest']) + end + + it 'should pass when TestNG test case passes' do + write 'src/test/java/PassingTest.java', <<-JAVA + public class PassingTest { + @org.testng.annotations.Test + public void testNothing() {} + } + JAVA + define('foo') { test.using(:testng) } + lambda { project('foo').test.invoke }.should_not raise_error + end + + it 'should fail when TestNG test case fails' do + write 'src/test/java/FailingTest.java', <<-JAVA + public class FailingTest { + @org.testng.annotations.Test + public void testNothing() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + define('foo') { test.using(:testng) } + lambda { project('foo').test.invoke }.should raise_error(RuntimeError, /Tests failed/) + end + + it 'should fail when multiple TestNG test case fail' do + write 'src/test/java/FailingTest1.java', <<-JAVA + public class FailingTest1 { + @org.testng.annotations.Test + public void testNothing() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + write 'src/test/java/FailingTest2.java', <<-JAVA + public class FailingTest2 { + @org.testng.annotations.Test + public void testNothing() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + define('foo') { test.using(:testng) } + lambda { project('foo').test.invoke }.should raise_error(RuntimeError, /Tests failed/) + end + + it 'should report failed test names' do + write 'src/test/java/FailingTest.java', <<-JAVA + public class FailingTest { + @org.testng.annotations.Test + public void testNothing() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + define('foo') { test.using(:testng) } + project('foo').test.invoke rescue nil + project('foo').test.failed_tests.should include('FailingTest') + end + + it 'should report to reports/testng' do + define('foo') { test.using(:testng) } + project('foo').test.report_to.should be(project('foo').file('reports/testng')) + end + + it 'should generate reports' do + write 'src/test/java/PassingTest.java', <<-JAVA + public class PassingTest { + @org.testng.annotations.Test + public void testNothing() {} + } + JAVA + define('foo') { test.using(:testng) } + lambda { project('foo').test.invoke }.should change { File.exist?('reports/testng/foo/index.html') }.to(true) + end + + it 'should include classes using TestNG annotations marked with a specific group' do + write 'src/test/java/com/example/AnnotatedClass.java', <<-JAVA + package com.example; + @org.testng.annotations.Test(groups={"included"}) + public class AnnotatedClass { } + JAVA + write 'src/test/java/com/example/AnnotatedMethod.java', <<-JAVA + package com.example; + public class AnnotatedMethod { + @org.testng.annotations.Test + public void annotated() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + define('foo').test.using :testng, :groups=>['included'] + lambda { project('foo').test.invoke }.should_not raise_error + end + + it 'should exclude classes using TestNG annotations marked with a specific group' do + write 'src/test/java/com/example/AnnotatedClass.java', <<-JAVA + package com.example; + @org.testng.annotations.Test(groups={"excluded"}) + public class AnnotatedClass { + public void annotated() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + write 'src/test/java/com/example/AnnotatedMethod.java', <<-JAVA + package com.example; + public class AnnotatedMethod { + @org.testng.annotations.Test(groups={"included"}) + public void annotated() {} + } + JAVA + define('foo').test.using :testng, :excludegroups=>['excluded'] + lambda { project('foo').test.invoke }.should_not raise_error + end +end + +describe Buildr::MultiTest do + it 'should be selectable in project' do + define 'foo' do + test.using(:multitest, :frameworks => []) + test.framework.should eql(:multitest) + end + end + + it 'should include dependencies of whichever test framework(s) are selected' do + define('foo') { test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ] } + project('foo').test.compile.dependencies.should include(artifact("junit:junit:jar:#{JUnit.version}")) + project('foo').test.compile.dependencies.should include(artifact("org.testng:testng:jar:jdk15:#{TestNG.version}")) + project('foo').test.dependencies.should include(artifact("junit:junit:jar:#{JUnit.version}")) + project('foo').test.dependencies.should include(artifact("org.testng:testng:jar:jdk15:#{TestNG.version}")) + end + + it 'should include classes of given test framework(s)' do + write 'src/test/java/com/example/JUnitTest.java', <<-JAVA + package com.example; + public class JUnitTest extends junit.framework.TestCase { + public void testNothing() { } + } + JAVA + write 'src/test/java/com/example/TestNGTest.java', <<-JAVA + package com.example; + @org.testng.annotations.Test + public class TestNGTest { } + JAVA + define('foo') { test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ] } + project('foo').test.invoke + project('foo').test.tests.should include('com.example.JUnitTest', 'com.example.TestNGTest') + end + + it 'should pass when test case passes' do + write 'src/test/java/PassingTest.java', <<-JAVA + public class PassingTest extends junit.framework.TestCase { + public void testNothing() {} + } + JAVA + define('foo') { test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ] } + lambda { project('foo').test.invoke }.should_not raise_error + end + + it 'should fail when test case fails' do + write 'src/test/java/FailingTest.java', <<-JAVA + public class FailingTest { + @org.testng.annotations.Test + public void testNothing() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + define('foo') { test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ] } + lambda { project('foo').test.invoke }.should raise_error(RuntimeError, /Tests failed/) + end + + it 'should fail when multiple test case fail' do + write 'src/test/java/FailingTest1.java', <<-JAVA + public class FailingTest1 { + @org.testng.annotations.Test + public void testNothing() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + write 'src/test/java/FailingTest2.java', <<-JAVA + public class FailingTest2 { + @org.testng.annotations.Test + public void testNothing() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + define('foo') { test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ] } + lambda { project('foo').test.invoke }.should raise_error(RuntimeError, /Tests failed/) + end + + it 'should report failed test names' do + write 'src/test/java/FailingTest.java', <<-JAVA + public class FailingTest { + @org.testng.annotations.Test + public void testNothing() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + define('foo') { test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ] } + project('foo').test.invoke rescue nil + project('foo').test.failed_tests.should include('FailingTest') + end + + it 'should generate reports' do + write 'src/test/java/PassingTest.java', <<-JAVA + public class PassingTest { + @org.testng.annotations.Test + public void testNothing() {} + } + JAVA + define('foo') { test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ] } + lambda { project('foo').test.invoke }.should change { + p Dir['./**/*'].inspect + File.exist?('reports/multitest/foo/index.html') }.to(true) + end + + it 'should include classes using TestNG annotations marked with a specific group' do + write 'src/test/java/com/example/AnnotatedClass.java', <<-JAVA + package com.example; + @org.testng.annotations.Test(groups={"included"}) + public class AnnotatedClass { } + JAVA + write 'src/test/java/com/example/AnnotatedMethod.java', <<-JAVA + package com.example; + public class AnnotatedMethod { + @org.testng.annotations.Test + public void annotated() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + define('foo') { test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ], :groups=>['included'] } + lambda { project('foo').test.invoke }.should_not raise_error + end + + it 'should exclude classes using TestNG annotations marked with a specific group' do + write 'src/test/java/com/example/AnnotatedClass.java', <<-JAVA + package com.example; + @org.testng.annotations.Test(groups={"excluded"}) + public class AnnotatedClass { + public void annotated() { + org.testng.AssertJUnit.assertTrue(false); + } + } + JAVA + write 'src/test/java/com/example/AnnotatedMethod.java', <<-JAVA + package com.example; + public class AnnotatedMethod { + @org.testng.annotations.Test(groups={"included"}) + public void annotated() {} + } + JAVA + define('foo') { test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ], :excludegroups=>['excluded'] } + lambda { project('foo').test.invoke }.should_not raise_error + end +end diff --git a/buildr/spec/packaging/archive_spec.rb b/buildr/spec/packaging/archive_spec.rb new file mode 100644 index 0000000..ffab753 --- /dev/null +++ b/buildr/spec/packaging/archive_spec.rb @@ -0,0 +1,775 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + + +module ArchiveTaskHelpers + # Not too smart, we just create some content based on file name to make sure you read what you write. + def content_for(file) + "Content for #{File.basename(file)}" + end + + # Qualify a filename + # + # e.g. qualify("file.zip", "src") => "file-src.zip" + def qualify(filename, qualifier) + ext = (filename =~ /\.$/) ? "." : File.extname(filename) + base = filename[0..0-ext.size-1] + base + "-" + qualifier + ext + end + + # Create an archive not using the archive task, this way we do have a file in existence, but we don't + # have an already invoked task. Yield an archive task to the block which can use it to include files, + # set options, etc. + def create_without_task + archive(qualify(@archive, "tmp")).tap do |task| + yield task if block_given? + task.invoke + mv task.name, @archive + end + end + + def create_for_merge + zip(qualify(@archive, "src")).include(@files).tap do |task| + yield task + end + end + + def init_dir + unless @dir + @dir = File.expand_path('test') + @files = %w{Test1.txt Text2.html}.map { |file| File.expand_path(file, @dir) }. + each { |file| write file, content_for(file) } + @empty_dirs = %w{EmptyDir1 EmptyDir2}.map { |file| File.expand_path(file, @dir) }. + each { |file| mkdir file } + end + end +end + +shared_examples_for 'ArchiveTask' do + include ArchiveTaskHelpers + + before(:each) do + init_dir + end + + it 'should point to archive file' do + archive(@archive).name.should eql(@archive) + end + + it 'should create file' do + lambda { archive(@archive).invoke }.should change { File.exist?(@archive) }.to(true) + end + + it 'should create empty archive if no files included' do + archive(@archive).invoke + inspect_archive { |archive| archive.should be_empty } + end + + it 'should raise error when include() is called with nil values' do + lambda { archive(@archive).include(nil) }.should raise_error + lambda { archive(@archive).include([nil]) }.should raise_error + end + + it 'should create empty archive if called #clean method' do + archive(@archive).include(@files).clean.invoke + inspect_archive { |archive| archive.should be_empty } + end + + it 'should archive all included files' do + archive(@archive).include(@files).invoke + inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } } + inspect_archive.size.should eql(@files.size) + end + + it 'should archive file tasks' do + tasks = @files.map { |fn| file(fn) } + archive(@archive).include(tasks).invoke + inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } } + inspect_archive.size.should eql(@files.size) + end + + it 'should invoke and archive file tasks' do + file = file('included') { write 'included' } + lambda { archive(@archive).include(file).invoke }.should change { File.exist?(file.to_s) }.to(true) + inspect_archive.keys.should include('included') + end + + it 'should archive artifacts' do + write 'library-1.0.txt', 'library-1.0' + artifact("org.example:library:txt:1.0").from 'library-1.0.txt' + archive(@archive).include("org.example:library:txt:1.0").invoke + inspect_archive.keys.should include('library-1.0.txt') + end + + it 'should archive project artifacts' do + define 'p1' do + project.version = '1.0' + package(:zip) + end + archive(@archive).include(project('p1')).invoke + inspect_archive.keys.should include('p1-1.0.zip') + end + + it 'should include entry for directory' do + archive(@archive).include(@dir).invoke + inspect_archive { |archive| @files.each { |f| archive['test/' + File.basename(f)].should eql(content_for(f)) } } + end + + it 'should not archive any excluded files' do + archive(@archive).include(@files).exclude(@files.last).invoke + inspect_archive do |archive| + archive.keys.should include(File.basename(@files.first)) + archive.keys.should_not include(File.basename(@files.last)) + end + end + + it 'should not archive any excluded files in included directories' do + archive(@archive).include(@dir).exclude(@files.last).invoke + inspect_archive do |archive| + archive.keys.should include('test/' + File.basename(@files.first)) + archive.keys.should_not include('test/' + File.basename(@files.last)) + end + end + + it 'should not archive any excluded files when using :from/:as' do + archive(@archive).include(:from=>@dir).exclude(@files.last).invoke + inspect_archive do |archive| + archive.keys.should include(File.basename(@files.first)) + archive.keys.should_not include(File.basename(@files.last)) + end + end + + it 'should raise error when using :from with nil value' do + lambda { + archive(@archive).include(:from=>nil) + }.should raise_error + end + + it 'should exclude entire directory and all its children' do + mkpath "#{@dir}/sub" + write "#{@dir}/sub/test" + archive(@archive).include(@dir).exclude("#{@dir}/sub").invoke + inspect_archive do |archive| + archive.keys.select { |file| file =~ /sub/ }.should be_empty + end + end + + it 'should not archive any excluded files when pattern is *.ext' do + write "test/file.txt" + write "test/file.swf" + archive(@archive).include(@dir).exclude('**/*.swf').invoke + inspect_archive do |archive| + archive.keys.should include('test/file.txt') + archive.keys.should_not include('test/file.swf') + end + end + + it 'should archive files into specified path' do + archive(@archive).include(@files, :path=>'code').invoke + inspect_archive { |archive| @files.each { |f| archive['code/' + File.basename(f)].should eql(content_for(f)) } } + end + + it 'should include entry for directory' do + archive(@archive).include(@dir).invoke + inspect_archive { |archive| @files.each { |f| archive['test/' + File.basename(f)].should eql(content_for(f)) } } + end + + it 'should archive files into specified path' do + archive(@archive).include(@files, :path=>'code').invoke + inspect_archive { |archive| @files.each { |f| archive['code/' + File.basename(f)].should eql(content_for(f)) } } + end + + it 'should archive directories into specified path' do + archive(@archive).include(@dir, :path=>'code').invoke + inspect_archive { |archive| @files.each { |f| archive['code/test/' + File.basename(f)].should eql(content_for(f)) } } + end + + it 'should understand . in path' do + archive(@archive).path('.').should == archive(@archive).path('') + archive(@archive).path('foo').path('.').should == archive(@archive).path('foo') + end + + it 'should understand .. in path' do + archive(@archive).path('..').should == archive(@archive).path('') + archive(@archive).path('foo').path('..').should == archive(@archive).path('') + archive(@archive).path('foo/bar').path('..').should == archive(@archive).path('foo') + end + + it 'should understand leading / in path' do + archive(@archive).path('/').should == archive(@archive).path('') + archive(@archive).path('foo/bar').path('/').should == archive(@archive).path('') + end + + it 'should archive file into specified name' do + archive(@archive).include(@files.first, :as=>'test/sample').invoke + inspect_archive { |archive| @files.each { |f| archive['test/sample'].should eql(content_for(@files.first)) } } + end + + it 'should archive directory into specified alias, without using "."' do + archive(@archive).include(@dir, :as=>'.').invoke + inspect_archive { |archive| archive.keys.should_not include(".") } + end + + it 'should archive directories into specified alias, even if it has the same name' do + archive(@archive).include(@dir, :as=>File.basename(@dir)).invoke + inspect_archive { |archive| + archive.keys.should_not include "#{File.basename(@dir)}" + } + end + + it 'should archive file into specified name/path' do + archive(@archive).include(@files.first, :as=>'test/sample', :path=>'path').invoke + inspect_archive { |archive| @files.each { |f| archive['path/test/sample'].should eql(content_for(@files.first)) } } + end + + it 'should archive files starting with dot' do + write 'test/.config', '# configuration' + archive(@archive).include('test').invoke + inspect_archive { |archive| @files.each { |f| archive['test/.config'].should eql('# configuration') } } + end + + it 'should archive directory into specified name' do + archive(@archive).include(@dir, :as=>'code').invoke + inspect_archive { |archive| @files.each { |f| archive['code/' + File.basename(f)].should eql(content_for(f)) } } + end + + it 'should archive directory into specified name/path' do + archive(@archive).include(@dir, :as=>'code', :path=>'path').invoke + inspect_archive { |archive| @files.each { |f| archive['path/code/' + File.basename(f)].should eql(content_for(f)) } } + end + + it 'should archive directory contents' do + archive(@archive).include(@dir, :as=>'.').invoke + inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } } + end + + it 'should archive directory contents into specified path' do + archive(@archive).include(@dir, :as=>'.', :path=>'path').invoke + inspect_archive { |archive| @files.each { |f| archive['path/' + File.basename(f)].should eql(content_for(f)) } } + end + + it 'should not allow two files with the :as argument' do + lambda { archive(@archive).include(@files.first, @files.last, :as=>'test/sample') }.should raise_error(RuntimeError, /one file/) + end + + it 'should expand another archive file' do + create_for_merge do |src| + archive(@archive).merge(src) + archive(@archive).invoke + inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } } + end + end + + it 'should expand another archive file with include pattern' do + create_for_merge do |src| + archive(@archive).merge(src).include(File.basename(@files.first)) + archive(@archive).invoke + inspect_archive do |archive| + archive[File.basename(@files.first)].should eql(content_for(@files.first)) + archive[File.basename(@files.last)].should be_nil + end + end + end + + it 'should expand another archive file with exclude pattern' do + create_for_merge do |src| + archive(@archive).merge(src).exclude(File.basename(@files.first)) + archive(@archive).invoke + inspect_archive do |archive| + @files[1..-1].each { |f| archive[File.basename(f)].should eql(content_for(f)) } + archive[File.basename(@files.first)].should be_nil + end + end + end + + it 'should expand another archive file with nested exclude pattern' do + @files = %w{Test1.txt Text2.html}.map { |file| File.join(@dir, "foo", file) }. + each { |file| write file, content_for(file) } + zip(qualify(@archive, "src")).include(@dir).tap do |task| + archive(@archive).merge(task).exclude('test/*') + archive(@archive).invoke + inspect_archive.should be_empty + end + end + + it 'should expand another archive file into path' do + create_for_merge do |src| + archive(@archive).path('test').merge(src) + archive(@archive).invoke + inspect_archive { |archive| @files.each { |f| archive['test/' + File.basename(f)].should eql(content_for(f)) } } + end + end + + it 'should expand another archive file into path with :path option' do + create_for_merge do |src| + archive(@archive).merge(src, :path=>'test') + archive(@archive).invoke + inspect_archive { |archive| @files.each { |f| archive['test/' + File.basename(f)].should eql(content_for(f)) } } + end + end + + it "should expand another archive file into path with :path=>'/'" do + create_for_merge do |src| + archive(@archive).merge(src, :path=>'/') + archive(@archive).invoke + inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } } + end + end + + it 'should expand another archive file into path with merge option' do + create_for_merge do |src| + archive(@archive).include(src, :merge=>true) + archive(@archive).invoke + inspect_archive { |archive| @files.each { |f| archive[File.basename(f)].should eql(content_for(f)) } } + end + end + + it 'should update if one of the files is recent' do + create_without_task { |archive| archive.include(@files) } + # Touch archive file to some point in the past. This effectively makes + # all included files newer. + File.utime Time.now - 100, Time.now - 100, @archive + archive(@archive).include(@files).invoke + File.stat(@archive).mtime.should be_close(Time.now, 10) + end + + it 'should update if a file in a subdir is more recent' do + subdir = File.expand_path("subdir", @dir) + test3 = File.expand_path("test3.css", subdir) + + mkdir_p subdir + write test3, '/* Original */' + + create_without_task { |archive| archive.include(:from => @dir) } + inspect_archive { |archive| archive["subdir/test3.css"].should eql('/* Original */') } + + write test3, '/* Refreshed */' + File.utime(Time.now + 100, Time.now + 100, test3) + archive(@archive).include(:from => @dir).invoke + inspect_archive { |archive| archive["subdir/test3.css"].should eql('/* Refreshed */') } + end + + it 'should do nothing if all files are uptodate' do + create_without_task { |archive| archive.include(@files) } + # By touching all files in the past, there's nothing new to update. + (@files + [@archive]).each { |f| File.utime Time.now - 100, Time.now - 100, f } + archive(@archive).include(@files).invoke + File.stat(@archive).mtime.should be_close(Time.now - 100, 10) + end + + it 'should update if one of the files is recent' do + create_without_task { |archive| archive.include(@files) } + # Change files, we expect to see new content. + write @files.first, '/* Refreshed */' + File.utime(Time.now - 100, Time.now - 100, @archive) # Touch archive file to some point in the past. + archive(@archive).include(@files).invoke + inspect_archive { |archive| archive[File.basename(@files.first)].should eql('/* Refreshed */') } + end + + it 'should create new archive when updating' do + create_without_task { |archive| archive.include(@files) } + File.utime(Time.now - 100, Time.now - 100, @archive) # Touch archive file to some point in the past. + archive(@archive).include(@files[1..-1]).invoke + inspect_archive.size.should be(@files.size - 1) + end + + it 'should not accept invalid options' do + archive(@archive).include(@files) + lambda { archive(@archive).with :option=>true }.should raise_error + end +end + +describe TarTask do + it_should_behave_like 'ArchiveTask' + + before(:each) do + @archive = File.expand_path('test.tar') + end + + define_method(:archive) { |file| tar(file) } + + def inspect_archive + entries = {} + Archive::Tar::Minitar.open @archive, 'r' do |reader| + reader.each { |entry| entries[entry.directory ? "#{entry.name}/" : entry.name] = entry.read } + end + yield entries if block_given? + entries + end +end + + +describe TarTask, ' gzipped' do + it_should_behave_like 'ArchiveTask' + + before(:each) do + @archive = File.expand_path('test.tgz') + end + + define_method(:archive) { |file| tar(file) } + + def inspect_archive + entries = {} + Zlib::GzipReader.open @archive do |gzip| + Archive::Tar::Minitar.open gzip, 'r' do |reader| + reader.each { |entry| entries[entry.directory ? "#{entry.name}/" : entry.name] = entry.read } + end + end + yield entries if block_given? + entries + end +end + +describe "ZipTask" do + include ArchiveTaskHelpers + + it_should_behave_like 'ArchiveTask' + + before(:each) do + init_dir + @archive = File.expand_path('test.zip') + end + + define_method(:archive) { |file| zip(file) } + + after(:each) do + checkZip(@archive) + end + + # Check for possible corruption using Java's ZipInputStream and Java's "jar" command since + # they are stricter than rubyzip + def checkZip(file) + return unless File.exist?(file) + zip = Java.java.util.zip.ZipInputStream.new(Java.java.io.FileInputStream.new(file)) + zip_entry_count = 0 + while entry = zip.getNextEntry do + # just iterate over all entries + zip_entry_count = zip_entry_count + 1 + end + zip.close() + + # jar tool fails with "ZipException: error in opening zip file" if empty + if zip_entry_count > 0 + sh "#{File.join(ENV['JAVA_HOME'], 'bin', 'jar')} tvf #{file}" + end + end + + def inspect_archive + entries = {} + Zip::ZipFile.open @archive do |zip| + zip.entries.each do |entry| + # Ignore the / directory created for empty ZIPs when using java.util.zip. + entries[entry.to_s] = zip.read(entry) unless entry.to_s == '/' + end + end + yield entries if block_given? + entries + end + + it 'should include empty dirs' do + archive(@archive).include(@dir) + archive(@archive).invoke + inspect_archive do |archive| + archive.keys.should include('test/EmptyDir1/') + end + end + + it 'should include empty dirs from Dir' do + archive(@archive).include(Dir["#{@dir}/*"]) + archive(@archive).invoke + inspect_archive do |archive| + archive.keys.should include('EmptyDir1/') + end + end + + it 'should work with path object' do + archive(@archive).path('code').include(@files) + archive(@archive).invoke + inspect_archive { |archive| archive.keys.should include('code/') } + end + + it 'should have path object that includes empty dirs' do + archive(@archive).path('code').include(Dir["#{@dir}/*"]) + archive(@archive).invoke + inspect_archive do |archive| + archive.keys.should include('code/EmptyDir1/') + end + end + + # chmod is not reliable on Windows + unless Buildr::Util.win_os? + it 'should preserve file permissions' do + # with JRuby it's important to use absolute paths with File.chmod() + # http://jira.codehaus.org/browse/JRUBY-3300 + hello = File.expand_path('src/main/bin/hello') + write hello, 'echo hi' + File.chmod(0777, hello) + fail("Failed to set permission on #{hello}") unless (File.stat(hello).mode & 0777) == 0777 + + zip('foo.zip').include('src/main/bin/*').invoke + unzip('target' => 'foo.zip').extract + (File.stat('target/hello').mode & 0777).should == 0777 + end + end + +end + +describe Unzip do + before(:each) do + @zip = File.expand_path('test.zip') + @dir = File.expand_path('test') + @files = %w{Test1.txt Text2.html}.map { |file| File.join(@dir, file) }. + each { |file| write file, content_for(file) } + @target = File.expand_path('target') + @targz = File.expand_path('test.tar.gz') + @targz2 = File.expand_path('test.tgz') + end + + # Not too smart, we just create some content based on file name to + # make sure you read what you write. + def content_for(file) + "Content for #{File.basename(file)}" + end + + def with_tar(*args) + tar(@targz).include(*args.empty? ? @files : args).invoke + yield + end + + def with_tar_too(*args) + tar(@targz2).include(*args.empty? ? @files : args).invoke + yield + end + + def with_zip(*args) + zip(@zip).include(*args.empty? ? @files : args).invoke + yield + end + + it 'should touch target directory' do + with_zip do + mkdir @target + File.utime(Time.now - 10, Time.now - 10, @target) + unzip(@target=>@zip).target.invoke + end + File.stat(@target).mtime.should be_close(Time.now, 2) + end + + it 'should expand files' do + with_zip do + unzip(@target=>@zip).target.invoke + @files.each { |f| File.read(File.join(@target, File.basename(f))).should eql(content_for(f)) } + end + end + + it 'should expand files from a tar.gz file' do + with_tar do + unzip(@target=>@targz).target.invoke + @files.each { |f| File.read(File.join(@target, File.basename(f))).should eql(content_for(f)) } + end + end + + it 'should expand files from a .tgz file' do + with_tar_too do + unzip(@target=>@targz2).target.invoke + @files.each { |f| File.read(File.join(@target, File.basename(f))).should eql(content_for(f)) } + end + end + + it 'should expand all files' do + with_zip do + unzip(@target=>@zip).target.invoke + FileList[File.join(@target, '*')].size.should be(@files.size) + end + end + + it 'should expand all files from a .tar.gz file' do + with_tar do + unzip(@target=>@targz).target.invoke + FileList[File.join(@target, '*')].size.should be(@files.size) + end + end + + it 'should expand only included files' do + with_zip do + only = File.basename(@files.first) + unzip(@target=>@zip).include(only).target.invoke + FileList[File.join(@target, '*')].should include(File.expand_path(only, @target)) + FileList[File.join(@target, '*')].size.should be(1) + end + end + + it 'should expand only included files from a .tar.gz file' do + with_tar do + only = File.basename(@files.first) + unzip(@target=>@targz).include(only).target.invoke + FileList[File.join(@target, '*')].should include(File.expand_path(only, @target)) + FileList[File.join(@target, '*')].size.should be(1) + end + end + + it 'should expand all but excluded files' do + with_zip do + except = File.basename(@files.first) + unzip(@target=>@zip).exclude(except).target.invoke + FileList[File.join(@target, '*')].should_not include(File.expand_path(except, @target)) + FileList[File.join(@target, '*')].size.should be(@files.size - 1) + end + end + + it 'should expand all but excluded files with a .tar.gz file' do + with_tar do + except = File.basename(@files.first) + unzip(@target=>@targz).exclude(except).target.invoke + FileList[File.join(@target, '*')].should_not include(File.expand_path(except, @target)) + FileList[File.join(@target, '*')].size.should be(@files.size - 1) + end + end + + it 'should include with nested path patterns' do + with_zip @files, :path=>'test/path' do + only = File.basename(@files.first) + unzip(@target=>@zip).include(only).target.invoke + FileList[File.join(@target, '*')].should be_empty + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@zip).include('test/path/' + only).target.invoke + FileList[File.join(@target, 'test/path/*')].size.should be(1) + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@zip).include('test/**/*').target.invoke + FileList[File.join(@target, 'test/path/*')].size.should be(2) + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@zip).include('test/*').target.invoke + FileList[File.join(@target, 'test/path/*')].size.should be(2) + end + end + + it 'should include with nested path patterns with a .tar.gz file' do + with_tar @files, :path=>'test/path' do + only = File.basename(@files.first) + unzip(@target=>@targz).include(only).target.invoke + FileList[File.join(@target, '*')].should be_empty + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@targz).include('test/path/' + only).target.invoke + FileList[File.join(@target, 'test/path/*')].size.should be(1) + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@targz).include('test/**/*').target.invoke + FileList[File.join(@target, 'test/path/*')].size.should be(2) + end + end + + it 'should include with relative path' do + with_zip @files, :path=>'test/path' do + only = File.basename(@files.first) + unzip(@target=>@zip).tap { |unzip| unzip.from_path('test').include(only) }.target.invoke + FileList[File.join(@target, '*')].should be_empty + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@zip).tap { |unzip| unzip.from_path('test').include('test/*') }.target.invoke + FileList[File.join(@target, 'path/*')].should be_empty + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@zip).tap { |unzip| unzip.from_path('test').include('path/*' + only) }.target.invoke + FileList[File.join(@target, 'path/*')].size.should be(1) + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@zip).tap { |unzip| unzip.from_path('test').include('path/*') }.target.invoke + FileList[File.join(@target, 'path/*')].size.should be(2) + end + end + + it 'should include with relative path with a .tar.gz file' do + with_tar @files, :path=>'test/path' do + only = File.basename(@files.first) + unzip(@target=>@targz).tap { |unzip| unzip.from_path('test').include(only) }.target.invoke + FileList[File.join(@target, '*')].should be_empty + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@targz).tap { |unzip| unzip.from_path('test').include('test/*') }.target.invoke + FileList[File.join(@target, 'path/*')].should be_empty + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@targz).tap { |unzip| unzip.from_path('test').include('path/*' + only) }.target.invoke + FileList[File.join(@target, 'path/*')].size.should be(1) + + Rake::Task.clear ; rm_rf @target + unzip(@target=>@targz).tap { |unzip| unzip.from_path('test').include('path/*') }.target.invoke + FileList[File.join(@target, 'path/*')].size.should be(2) + end + end + + it 'should exclude with relative path' do + with_zip @files, :path=>'test' do + except = File.basename(@files.first) + unzip(@target=>@zip).tap { |unzip| unzip.from_path('test').exclude(except) }.target.invoke + FileList[File.join(@target, '*')].should include(File.join(@target, File.basename(@files[1]))) + FileList[File.join(@target, '*')].size.should be(@files.size - 1) + end + end + + it 'should exclude with relative path on a tar.gz file' do + with_tar @files, :path=>'test' do + except = File.basename(@files.first) + unzip(@target=>@targz).tap { |unzip| unzip.from_path('test').exclude(except) }.target.invoke + FileList[File.join(@target, '*')].should include(File.join(@target, File.basename(@files[1]))) + FileList[File.join(@target, '*')].size.should be(@files.size - 1) + end + end + + it "should handle relative paths without any includes or excludes" do + lib_files = %w{Test3.so Test4.rb}. + map { |file| File.join(@dir, file) }. + each { |file| write file, content_for(file) } + zip(@zip).include(@files, :path => 'src').include(lib_files, :path => 'lib').invoke + + unzip(@target=>@zip).tap { |unzip| unzip.from_path('lib') }.target.invoke + FileList[File.join(@target, '**/*')].should have(2).files + end + + it "should handle relative paths without any includes or excludes with a tar.gz file" do + lib_files = %w{Test3.so Test4.rb}. + map { |file| File.join(@dir, file) }. + each { |file| write file, content_for(file) } + tar(@targz).include(@files, :path => 'src').include(lib_files, :path => 'lib').invoke + + unzip(@target=>@targz).tap { |unzip| unzip.from_path('lib') }.target.invoke + FileList[File.join(@target, '**/*')].should have(2).files + end + + it 'should return itself from root method' do + task = unzip(@target=>@zip) + task.root.should be(task) + task.from_path('foo').root.should be(task) + end + + it 'should return target task from target method' do + task = unzip(@target=>@zip) + task.target.should be(file(@target)) + task.from_path('foo').target.should be(file(@target)) + end + + it 'should alias from_path as path' do + task = unzip(@target=>@zip) + task.from_path('foo').should be(task.path('foo')) + end + +end diff --git a/buildr/spec/packaging/artifact_namespace_spec.rb b/buildr/spec/packaging/artifact_namespace_spec.rb new file mode 100644 index 0000000..d072485 --- /dev/null +++ b/buildr/spec/packaging/artifact_namespace_spec.rb @@ -0,0 +1,758 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe Buildr::ArtifactNamespace do + + before(:each) { Buildr::ArtifactNamespace.clear } + + def abc_module + Object.module_eval 'module A; module B; module C; end; end; end' + yield + ensure + Object.send :remove_const, :A + end + + describe '.root' do + it 'should return the top level namespace' do + Buildr::ArtifactNamespace.root.should be_root + end + + it 'should yield the namespace if a block is given' do + flag = false + Buildr::ArtifactNamespace.root { |ns| flag = true; ns.should be_root } + flag.should == true + end + + it 'should return the root when used outside of a project definition' do + artifact_ns.should be_root + end + + it 'should yield to a block when used outside of a project definition' do + flag = false + artifact_ns {|ns| flag = true; ns.should be_root} + flag.should == true + end + end + + describe '.instance' do + it 'should return the top level namespace when invoked outside a project definition' do + artifact_ns.should be_root + end + + it 'should return the namespace for the receiving project' do + define('foo') { } + project('foo').artifact_ns.name.should == 'foo' + end + + it 'should return the current project namespace when invoked inside a project' do + define 'foo' do + artifact_ns.should_not be_root + artifact_ns.name.should == 'foo' + task :doit do + artifact_ns.should_not be_root + artifact_ns.name.should == 'foo' + end.invoke + end + end + + it 'should return the root namespace if given :root' do + artifact_ns(:root).should be_root + end + + it 'should return the namespace for the given name' do + artifact_ns(:foo).name.should == 'foo' + artifact_ns('foo:bar').name.should == 'foo:bar' + artifact_ns(['foo', 'bar', 'baz']).name.should == 'foo:bar:baz' + abc_module do + artifact_ns(A::B::C).name.should == 'A::B::C' + end + artifact_ns(:root).should be_root + artifact_ns(:current).should be_root + define 'foo' do + artifact_ns(:current).name.should == 'foo' + define 'baz' do + artifact_ns(:current).name.should == 'foo:baz' + end + end + end + end + + describe '#parent' do + it 'should be nil for root namespace' do + artifact_ns(:root).parent.should be_nil + end + + it 'should be the parent namespace for nested modules' do + abc_module do + artifact_ns(A::B::C).parent.should == artifact_ns(A::B) + artifact_ns(A::B).parent.should == artifact_ns(A) + artifact_ns(A).parent.should == artifact_ns(:root) + end + end + + it 'should be the parent namespace for nested projects' do + define 'a' do + define 'b' do + define 'c' do + artifact_ns.parent.should == artifact_ns(parent) + end + artifact_ns.parent.should == artifact_ns(parent) + end + artifact_ns.parent.should == artifact_ns(:root) + end + end + end + + describe '#parent=' do + it 'should reject to set parent for root namespace' do + lambda { artifact_ns(:root).parent = :foo }.should raise_error(Exception, /cannot set parent/i) + end + + it 'should allow to set parent' do + artifact_ns(:bar).parent = :foo + artifact_ns(:bar).parent.should == artifact_ns(:foo) + artifact_ns(:bar).parent = artifact_ns(:baz) + artifact_ns(:bar).parent.should == artifact_ns(:baz) + end + + it 'should allow to set parent to :current' do + abc_module do + mod = A::B + artifact_ns(mod).parent = :current + def mod.stuff + Buildr::artifact_ns(self) + end + define 'a' do + define 'b' do + mod.stuff.parent.should == artifact_ns + end + mod.stuff.parent.should == artifact_ns + end + end + end + end + + describe '#need' do + it 'should accept an artifact spec' do + define 'one' do + artifact_ns.need 'a:b:c:1' + # referenced by spec + artifact_ns['a:b:c'].should_not be_selected + + # referenced by name + artifact_ns[:b].should_not be_selected + artifact_ns[:b].should be_satisfied_by('a:b:c:1') + artifact_ns[:b].should_not be_satisfied_by('a:b:c:2') + artifact_ns[:b].should_not be_satisfied_by('d:b:c:1') + artifact_ns[:b].version.should == '1' + end + end + + it 'should accept an artifact spec with classifier' do + define 'one' do + artifact_ns.need 'a:b:c:d:1' + # referenced by spec + artifact_ns['a:b:c:d:'].should_not be_selected + + # referenced by name + artifact_ns[:b].should_not be_selected + artifact_ns[:b].should be_satisfied_by('a:b:c:d:1') + artifact_ns[:b].should_not be_satisfied_by('a:b:c:d:2') + artifact_ns[:b].should_not be_satisfied_by('d:b:c:d:1') + artifact_ns[:b].version.should == '1' + end + end + + it 'should accept a requirement_spec' do + define 'one' do + artifact_ns.need 'thing -> a:b:c:2.1 -> ~>2.0' + # referenced by spec + artifact_ns['a:b:c'].should_not be_selected + + # referenced by name + artifact_ns.key?(:b).should be_false + artifact_ns[:thing].should_not be_selected + artifact_ns[:thing].should be_satisfied_by('a:b:c:2.5') + artifact_ns[:thing].should_not be_satisfied_by('a:b:c:3') + artifact_ns[:thing].version.should == '2.1' + end + end + + it 'should accept a hash :name -> requirement_spec' do + define 'one' do + artifact_ns.need :thing => 'a:b:c:2.1 -> ~>2.0' + artifact_ns[:thing].should be_satisfied_by('a:b:c:2.5') + artifact_ns[:thing].should_not be_satisfied_by('a:b:c:3') + artifact_ns[:thing].version.should == '2.1' + end + + define 'two' do + artifact_ns.need :thing => 'a:b:c:(~>2.0 | 2.1)' + artifact_ns[:thing].should be_satisfied_by('a:b:c:2.5') + artifact_ns[:thing].should_not be_satisfied_by('a:b:c:3') + artifact_ns[:thing].version.should == '2.1' + end + end + + it 'should take a hash :name -> specs_array' do + define 'one' do + artifact_ns.need :things => ['foo:bar:jar:1.0', + 'foo:baz:jar:2.0',] + artifact_ns['foo:bar:jar'].should_not be_selected + artifact_ns['foo:baz:jar'].should_not be_selected + artifact_ns[:bar, :baz].should == [nil, nil] + artifact_ns[:things].map(&:unversioned_spec).should include('foo:bar:jar', 'foo:baz:jar') + artifact_ns.alias :baz, 'foo:baz:jar' + artifact_ns[:baz].should == artifact_ns['foo:baz:jar'] + end + end + + it 'should select best matching version if defined' do + define 'one' do + artifact_ns.use :a => 'foo:bar:jar:1.5' + artifact_ns.use :b => 'foo:baz:jar:2.0' + define 'two' do + artifact_ns[:a].requirement.should be_nil + artifact_ns[:a].should be_selected + + artifact_ns.need :c => 'foo:bat:jar:3.0' + artifact_ns['foo:bat:jar'].should_not be_selected + artifact_ns[:c].should_not be_selected + + artifact_ns.need :one => 'foo:bar:jar:>=1.0' + artifact_ns[:one].version.should == '1.5' + artifact_ns[:one].should be_selected + artifact_ns[:a].requirement.should be_nil + + artifact_ns.need :two => 'foo:baz:jar:>2' + artifact_ns[:two].version.should be_nil + artifact_ns[:two].should_not be_selected + artifact_ns[:b].requirement.should be_nil + end + end + end + end + + describe '#use' do + it 'should register the artifact on namespace' do + define 'one' do + artifact_ns.use :thing => 'a:b:c:1' + artifact_ns[:thing].requirement.should be_nil + artifact_ns[:thing].version.should == '1' + artifact_ns[:thing].id.should == 'b' + define 'one' do + artifact_ns.use :thing => 'a:d:c:2' + artifact_ns[:thing].requirement.should be_nil + artifact_ns[:thing].version.should == '2' + artifact_ns[:thing].id.should == 'd' + + artifact_ns.use :copied => artifact_ns.parent[:thing] + artifact_ns[:copied].should_not == artifact_ns.parent[:thing] + artifact_ns[:copied].requirement.should be_nil + artifact_ns[:copied].version.should == '1' + artifact_ns[:copied].id.should == 'b' + + artifact_ns.use :aliased => :copied + artifact_ns[:aliased].should == artifact_ns[:copied] + + lambda { artifact_ns.use :invalid => :unknown }.should raise_error(NameError, /undefined/i) + end + artifact_ns[:copied].should be_nil + end + end + + it 'should register two artifacts with different version on namespace' do + define 'one' do + artifact_ns.use :foo => 'a:b:c:1' + artifact_ns.use :bar => 'a:b:c:2' + artifact_ns[:foo].version.should == '1' + artifact_ns[:bar].version.should == '2' + # unversioned references the last version set. + artifact_ns['a:b:c'].version.should == '2' + end + end + + it 'should complain if namespace requirement is not satisfied' do + define 'one' do + artifact_ns.need :bar => 'foo:bar:baz:~>1.5' + lambda { artifact_ns.use :bar => '1.4' }.should raise_error(Exception, /unsatisfied/i) + end + end + + it 'should be able to register a group' do + specs = ['its:me:here:1', 'its:you:there:2'] + artifact_ns.use :them => specs + artifact_ns[:them].map(&:to_spec).should == specs + artifact_ns['its:me:here'].should_not be_nil + artifact_ns[:you].should be_nil + end + + it 'should be able to assign sub namespaces' do + artifact_ns(:foo).bar = "foo:bar:baz:0" + artifact_ns(:moo).foo = artifact_ns(:foo) + artifact_ns(:moo).foo.should == artifact_ns(:foo) + artifact_ns(:moo).foo_bar.should == artifact_ns(:foo).bar + end + + it 'should handle symbols with dashes and periods' do + [:'a-b', :'a.b'].each do |symbol| + artifact_ns.use symbol => 'a:b:c:1' + artifact_ns[symbol].version.should == '1' + artifact_ns[symbol].id.should == 'b' + end + end + + it 'should handle version string' do + foo = artifact_ns do |ns| + ns.bar = 'a:b:c:1' + end + foo.use :bar => '2.0' + foo.bar.version.should == '2.0' + end + end + + describe '#values' do + it 'returns the artifacts defined on namespace' do + define 'foo' do + artifact_ns.use 'foo:one:baz:1.0' + define 'bar' do + artifact_ns.use 'foo:two:baz:1.0' + + specs = artifact_ns.values.map(&:to_spec) + specs.should include('foo:two:baz:1.0') + specs.should_not include('foo:one:baz:1.0') + + specs = artifact_ns.values(true).map(&:to_spec) + specs.should include('foo:two:baz:1.0', 'foo:one:baz:1.0') + end + end + end + end + + describe '#values_at' do + it 'returns the named artifacts' do + define 'foo' do + artifact_ns.use 'foo:one:baz:1.0' + define 'bar' do + artifact_ns.use :foo_baz => 'foo:two:baz:1.0' + + specs = artifact_ns.values_at('one').map(&:to_spec) + specs.should include('foo:one:baz:1.0') + specs.should_not include('foo:two:baz:1.0') + + specs = artifact_ns.values_at('foo_baz').map(&:to_spec) + specs.should include('foo:two:baz:1.0') + specs.should_not include('foo:one:baz:1.0') + end + end + end + + it 'returns first artifacts by their unversioned spec' do + define 'foo' do + artifact_ns.use 'foo:one:baz:2.0' + define 'bar' do + artifact_ns.use :older => 'foo:one:baz:1.0' + + specs = artifact_ns.values_at('foo:one:baz').map(&:to_spec) + specs.should include('foo:one:baz:1.0') + specs.should_not include('foo:one:baz:2.0') + end + specs = artifact_ns.values_at('foo:one:baz').map(&:to_spec) + specs.should include('foo:one:baz:2.0') + specs.should_not include('foo:one:baz:1.0') + end + end + + it 'return first artifact satisfying a dependency' do + define 'foo' do + artifact_ns.use 'foo:one:baz:2.0' + define 'bar' do + artifact_ns.use :older => 'foo:one:baz:1.0' + + specs = artifact_ns.values_at('foo:one:baz:>1.0').map(&:to_spec) + specs.should include('foo:one:baz:2.0') + specs.should_not include('foo:one:baz:1.0') + end + end + end + end + + describe '#artifacts' do + it 'returns artifacts in namespace' do + define 'one' do + artifact_ns[:foo] = 'group:foo:jar:1' + artifact_ns[:bar] = 'group:bar:jar:1' + artifact_ns.artifacts.map{|a| a.to_spec}.should include('group:foo:jar:1', 'group:bar:jar:1') + end + end + end + + describe '#keys' do + it 'returns names in namespace' do + define 'one' do + artifact_ns[:foo] = 'group:foo:jar:1' + artifact_ns[:bar] = 'group:bar:jar:1' + artifact_ns.keys.should include('foo', 'bar') + end + end + end + + describe '#delete' do + it 'deletes corresponding artifact requirement' do + define 'one' do + artifact_ns[:foo] = 'group:foo:jar:1' + artifact_ns[:bar] = 'group:bar:jar:1' + artifact_ns.delete :bar + artifact_ns.artifacts.map{|a| a.to_spec}.should include('group:foo:jar:1') + artifact_ns[:foo].to_spec.should eql('group:foo:jar:1') + end + end + end + + describe '#clear' do + it 'clears all artifact requirements in namespace' do + define 'one' do + artifact_ns[:foo] = 'group:foo:jar:1' + artifact_ns[:bar] = 'group:bar:jar:1' + artifact_ns.clear + artifact_ns.artifacts.should be_empty + end + end + end + + describe '#method_missing' do + it 'should use cool_aid! to create a requirement' do + define 'foo' do + artifact_ns.cool_aid!('cool:aid:jar:2').should be_kind_of(ArtifactNamespace::ArtifactRequirement) + artifact_ns[:cool_aid].version.should == '2' + artifact_ns[:cool_aid].should_not be_selected + define 'bar' do + artifact_ns.cool_aid! 'cool:aid:man:3', '>2' + artifact_ns[:cool_aid].version.should == '3' + artifact_ns[:cool_aid].requirement.should be_satisfied_by('2.5') + artifact_ns[:cool_aid].should_not be_selected + end + end + end + + it 'should use cool_aid= as shorhand for [:cool_aid]=' do + artifact_ns.cool_aid = 'cool:aid:jar:1' + artifact_ns[:cool_aid].should be_selected + end + + it 'should use cool_aid as shorthand for [:cool_aid]' do + artifact_ns.need :cool_aid => 'cool:aid:jar:1' + artifact_ns.cool_aid.should_not be_selected + end + + it 'should use cool_aid? to test if artifact has been defined and selected' do + artifact_ns.need :cool_aid => 'cool:aid:jar:>1' + artifact_ns.should_not have_cool_aid + artifact_ns.should_not have_unknown + artifact_ns.cool_aid = '2' + artifact_ns.should have_cool_aid + end + end + + describe '#ns' do + it 'should create a sub namespace' do + artifact_ns.ns :foo + artifact_ns[:foo].should be_kind_of(ArtifactNamespace) + artifact_ns(:foo).should_not === artifact_ns.foo + artifact_ns.foo.parent.should == artifact_ns + end + + it 'should take any use arguments' do + artifact_ns.ns :foo, :bar => 'foo:bar:jar:0', :baz => 'foo:baz:jar:0' + artifact_ns.foo.bar.should be_selected + artifact_ns.foo[:baz].should be_selected + end + + it 'should access sub artifacts using with foo_bar like syntax' do + artifact_ns.ns :foo, :bar => 'foo:bar:jar:0', :baz => 'foo:baz:jar:0' + artifact_ns[:foo_baz].should be_selected + artifact_ns.foo_bar.should be_selected + + artifact_ns.foo.ns :bat, 'bat:man:jar:>1' + batman = artifact_ns.foo.bat.man + batman.should be_selected + artifact_ns[:foo_bat_man] = '3' + artifact_ns[:foo_bat_man].should == batman + artifact_ns[:foo_bat_man].version.should == '3' + end + + it 'should include sub artifacts when calling #values' do + artifact_ns.ns :bat, 'bat:man:jar:>1' + artifact_ns.values.should_not be_empty + artifact_ns.values.first.unversioned_spec.should == 'bat:man:jar' + end + + it 'should reopen a sub-namespace' do + artifact_ns.ns :bat, 'bat:man:jar:>1' + bat = artifact_ns[:bat] + bat.should == artifact_ns.ns(:bat) + end + + it 'should fail reopening if not a sub-namespace' do + artifact_ns.foo = 'foo:bar:baz:0' + lambda { artifact_ns.ns(:foo) }.should raise_error(TypeError, /not a sub/i) + end + + it 'should clone artifacts when assigned' do + artifact_ns(:foo).bar = "foo:bar:jar:0" + artifact_ns(:moo).ns :muu, :miu => artifact_ns(:foo).bar + artifact_ns(:moo).muu.miu.should_not == artifact_ns(:foo).bar + artifact_ns(:moo).muu.miu.to_spec.should == artifact_ns(:foo).bar.to_spec + end + + it 'should clone parent artifacts by name' do + define 'foo' do + artifact_ns.bar = "foo:bar:jar:0" + define 'moo' do + artifact_ns.ns(:muu).use :bar + artifact_ns.muu_bar.should be_selected + artifact_ns.muu.bar.should_not == artifact_ns.bar + end + end + end + end + + it 'should be an Enumerable' do + artifact_ns.should be_kind_of(Enumerable) + artifact_ns.use 'foo:bar:baz:1.0' + artifact_ns.map(&:artifact).should include(artifact('foo:bar:baz:1.0')) + end + +end # ArtifactNamespace + +describe Buildr::ArtifactNamespace::ArtifactRequirement do + before(:each) { Buildr::ArtifactNamespace.clear } + it 'should be created from artifact_ns' do + foo = artifact_ns do |ns| + ns.bar = 'a:b:c:1.0' + end + foo.bar.should be_kind_of(ArtifactNamespace::ArtifactRequirement) + end + + it 'should handle version as string' do + foo = artifact_ns do |ns| + ns.bar = 'a:b:c:1.0' + end + foo.bar.version = '2.0' + foo.bar.version.should == '2.0' + end + + it 'should handle version string directly' do + foo = artifact_ns do |ns| + ns.bar = 'a:b:c:1.0' + end + foo.bar = '2.0' + foo.bar.version.should == '2.0' + end + +end # ArtifactRequirement + +describe Buildr do + before(:each) { Buildr::ArtifactNamespace.clear } + + describe '.artifacts' do + it 'should take ruby symbols and ask the current namespace for them' do + define 'foo' do + artifact_ns.cool = 'cool:aid:jar:1.0' + artifact_ns.use 'some:other:jar:1.0' + artifact_ns.use 'bat:man:jar:1.0' + compile.with :cool, :other, :'bat:man:jar' + compile.dependencies.map(&:to_spec).should include('cool:aid:jar:1.0', 'some:other:jar:1.0', 'bat:man:jar:1.0') + end + end + + it 'should take a namespace' do + artifact_ns(:moo).muu = 'moo:muu:jar:1.0' + define 'foo' do + compile.with artifact_ns(:moo) + compile.dependencies.map(&:to_spec).should include('moo:muu:jar:1.0') + end + end + end + + describe '.artifact' do + it 'should search current namespace if given a symbol' do + define 'foo' do + artifact_ns.use :cool => 'cool:aid:jar:1.0' + define 'bar' do + artifact(:cool).should == artifact_ns[:cool].artifact + end + end + end + + it 'should search current namespace if given a symbol spec' do + define 'foo' do + artifact_ns.use 'cool:aid:jar:1.0' + define 'bar' do + artifact(:'cool:aid:jar').should == artifact_ns[:aid].artifact + end + end + end + + it 'should fail when no artifact by that name is found' do + define 'foo' do + artifact_ns.use 'cool:aid:jar:1.0' + define 'bar' do + lambda { artifact(:cool) }.should raise_error(IndexError, /artifact/) + end + end + end + end +end + +describe "Extension using ArtifactNamespace" do + before(:each) { Buildr::ArtifactNamespace.clear } + + def abc_module + Object.module_eval 'module A; module B; module C; end; end; end' + yield + ensure + Object.send :remove_const, :A + end + + it 'can register namespace listeners' do + abc_module do + # An example extension to illustrate namespace listeners and method forwarding + class A::Example + + module Ext + include Buildr::Extension + def example; @example ||= A::Example.new; end + before_define do |p| + Rake::Task.define_task('example') { p.example.doit } + end + end + + REQUIRES = ArtifactNamespace.for(self) do |ns| + ns.xmlbeans! 'org.apache.xmlbeans:xmlbeans:jar:2.3.0', '>2' + ns.stax_api! 'stax:stax-api:jar:>=1.0.1' + end + + attr_reader :options, :requires + + def initialize + # We could actually use the REQUIRES namespace, but to make things + # a bit more interesting, suppose each Example instance can have its + # own artifact requirements in adition to those specified on REQUIRES. + # To achieve this we create an anonymous namespace. + @requires = ArtifactNamespace.new # a namespace per instance + REQUIRES.each { |requirement| @requires.need requirement } + + # For user convenience, we make the options object respond to + # :xmlbeans, :xmlbeans=, :xmlbeans? + # forwarding them to the namespace. + @options = OpenObject.new.extend(@requires.accessor(:xmlbeans, :stax_api)) + # Register callbacks so we can perform some logic when an artifact + # is selected by the user. + options.xmlbeans.add_listener &method(:selected_xmlbeans) + options.stax_api.add_listener do |stax| + # Now using a proc + stax.should be_selected + stax.version.should == '1.6180' + options[:math] = :golden # customize our options for this version + # In this example we set the stax version when running outside + # a project definition. This means we have no access to the project + # namespace unless we had a reference to the project or knew it's name + Buildr.artifact_ns(:current).name.should == 'root' + end + end + + include RSpec::Matchers # for assertions + + # Called with the ArtifactRequirement that has just been selected + # by a user. This allows extension author to selectively perform + # some action by inspecting the requirement state. + def selected_xmlbeans(xmlbeans) + xmlbeans.should be_selected + xmlbeans.version.should == '3.1415' + options[:math] = :pi + # This example just sets xmlbeans for foo:bar project + # So the currently running namespace should have the foo:bar name + Buildr.artifact_ns(:current).name.should == 'foo:bar' + end + + # Suppose we invoke an ant task here or something else. + def doit + # Now call ant task with our selected artifact and options + classpath = requires.map(&:artifact).map(&:to_s).join(File::PATH_SEPARATOR) + lambda { ant('thing') { |ant| ant.classpath classpath, :math => options[:math] } } + + # We are not a Project instance, hence we have no artifact_ns + lambda { artifact_ns }.should raise_error(NameError) + + # Extension authors may NOT rely project's namespaces. + # However the ruby-way gives you power and at the same time + # makes you dangerous, (think open-modules, monkey-patching) + # Given that buildr is pure ruby, consider it a sharp-edged sword. + # Having said that, you may actually inspect a project's + # namespace, but don't write on it without letting your users + # know you will. + # This example obtains the current project namespace to make + # some assertions. + + # To obtain a project's namespace we need either + # 1) a reference to the project, and call artifact_ns on it + # project.artifact_ns # the namespace for project + # 2) know the project name + # Buildr.artifact_ns('the:project') + # 3) Use :current to reference the currently running project + # Buildr.artifact_ns(:current) + name = Buildr.artifact_ns(:current).name + case name + when 'foo:bar' + options[:math].should == :pi + requires.xmlbeans.version.should == '3.1415' + requires.stax_api.version.should == '1.0.1' + when 'foo:baz' + options[:math].should == :golden + requires.xmlbeans.version.should == '2.3.0' + requires.stax_api.version.should == '1.6180' + else + fail "This example expects foo:bar or foo:baz projects not #{name.inspect}" + end + end + end + + define 'foo' do + define 'bar' do + extend A::Example::Ext + task('setup') do + example.options.xmlbeans = '3.1415' + end + task('run' => [:setup, :example]) + end + define 'baz' do + extend A::Example::Ext + end + end + + project('foo:bar').example.requires.should_not == project('foo:baz').example.requires + project('foo:bar').example.requires.xmlbeans.should_not == project('foo:baz').example.requires.xmlbeans + + # current namespace outside a project is :root, see the stax callback + project('foo:baz').example.options.stax_api = '1.6180' + # we call the task outside the project, see #doit + lambda { task('foo:bar:run').invoke }.should run_task('foo:bar:example') + lambda { task('foo:baz:example').invoke }.should run_task('foo:baz:example') + end + end +end diff --git a/buildr/spec/packaging/artifact_spec.rb b/buildr/spec/packaging/artifact_spec.rb new file mode 100644 index 0000000..cfcbc01 --- /dev/null +++ b/buildr/spec/packaging/artifact_spec.rb @@ -0,0 +1,1142 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) +require 'fileutils' + +describe Artifact do + before do + Buildr.repositories.instance_eval do + @local = @remote = @release_to = nil + end + @spec = { :group=>'com.example', :id=>'library', :type=>:jar, :version=>'2.0' } + @artifact = artifact(@spec) + @classified = artifact(@spec.merge(:classifier=>'all')) + @snapshot = artifact(@spec.merge({ :version=>'2.1-SNAPSHOT' })) + end + + + it 'should act as one' do + @artifact.should respond_to(:to_spec) + end + + it 'should have an artifact identifier' do + @artifact.id.should eql('library') + end + + it 'should have a group identifier' do + @artifact.group.should eql('com.example') + end + + it 'should have a version number' do + @artifact.version.should eql('2.0') + end + + it 'should know if it is a snapshot' do + @artifact.should_not be_snapshot + @classified.should_not be_snapshot + @snapshot.should be_snapshot + end + + it 'should have a file type' do + @artifact.type.should eql(:jar) + end + + it 'should understand classifier' do + @artifact.classifier.should be_nil + @classified.classifier.should eql('all') + end + + it 'should return hash specification' do + @artifact.to_hash.should == @spec + @artifact.to_spec_hash.should == @spec + @classified.to_hash.should == @spec.merge(:classifier=>'all') + end + + it 'should return string specification' do + @artifact.to_spec.should eql('com.example:library:jar:2.0') + @classified.to_spec.should eql('com.example:library:jar:all:2.0') + end + + it 'should have associated POM artifact' do + @artifact.pom.to_hash.should == @artifact.to_hash.merge(:type=>:pom) + end + + it 'should have one POM artifact for all classifiers' do + @classified.pom.to_hash.should == @classified.to_hash.merge(:type=>:pom).except(:classifier) + end + + it 'should have associated sources artifact' do + @artifact.sources_artifact.to_hash.should == @artifact.to_hash.merge(:classifier=>'sources') + end + + it 'should have associated javadoc artifact' do + @artifact.javadoc_artifact.to_hash.should == @artifact.to_hash.merge(:classifier=>'javadoc') + end + + it 'should download file if file does not exist' do + lambda { @artifact.invoke }.should raise_error(Exception, /No remote repositories/) + lambda { @classified.invoke }.should raise_error(Exception, /No remote repositories/) + end + + it 'should not download file if file exists' do + write repositories.locate(@artifact) + lambda { @artifact.invoke }.should_not raise_error + write repositories.locate(@classified) + lambda { @classified.invoke }.should_not raise_error + end + + it 'should handle lack of POM gracefully' do + repositories.remote = 'http://example.com' + URI.should_receive(:download).twice { |uri, target, options| raise URI::NotFoundError if uri.to_s.ends_with('.pom') } + lambda { @artifact.invoke }.should_not raise_error + end + + it 'should pass if POM provided' do + repositories.remote = 'http://example.com' + @artifact.pom.enhance { |task| write task.name, @artifact.pom_xml } + write repositories.locate(@artifact) + lambda { @artifact.invoke }.should_not raise_error + end + + it 'should pass if POM not required' do + repositories.remote = 'http://example.com' + class << @artifact ; def pom() ; end ; end + write repositories.locate(@artifact) + lambda { @artifact.invoke }.should_not raise_error + end + + it 'should not download file if dry-run' do + dryrun do + lambda { @artifact.invoke }.should_not raise_error + lambda { @classified.invoke }.should_not raise_error + end + end + + it 'should resolve to path in local repository' do + @artifact.to_s.should == File.join(repositories.local, 'com/example/library/2.0/library-2.0.jar') + @classified.to_s.should == File.join(repositories.local, 'com/example/library/2.0/library-2.0-all.jar') + end + + it 'should return a list of all registered artifact specifications' do + define('foo', :version=>'1.0') { package :jar } + Artifact.list.should include(@artifact.to_spec) + Artifact.list.should include(@classified.to_spec) + Artifact.list.should include('foo:foo:jar:1.0') + end + + it 'should accept user-defined string content' do + a = artifact(@spec) + a.content 'foo' + install a + lambda { install.invoke }.should change { File.exist?(a.to_s) && File.exist?(repositories.locate(a)) }.to(true) + read(repositories.locate(a)).should eql('foo') + end +end + + +describe Repositories, 'local' do + before do + Buildr.repositories.instance_eval do + @local = @remote = @release_to = nil + end + end + + it 'should default to .m2 path' do + # For convenience, sandbox actually sets the local repository to a temp directory + repositories.local = nil + repositories.local.should eql(File.expand_path('.m2/repository', ENV['HOME'])) + end + + it 'should be settable' do + repositories.local = '.m2/local' + repositories.local.should eql(File.expand_path('.m2/local')) + end + + it 'should reset to default' do + repositories.local = '.m2/local' + repositories.local = nil + repositories.local.should eql(File.expand_path('~/.m2/repository')) + end + + it 'should locate file from string specification' do + repositories.local = nil + repositories.locate('com.example:library:jar:2.0').should eql( + File.expand_path('~/.m2/repository/com/example/library/2.0/library-2.0.jar')) + end + + it 'should locate file from hash specification' do + repositories.local = nil + repositories.locate(:group=>'com.example', :id=>'library', :version=>'2.0').should eql( + File.expand_path('~/.m2/repository/com/example/library/2.0/library-2.0.jar')) + end + + it 'should load path from settings file' do + write 'home/.buildr/settings.yaml', <<-YAML + repositories: + local: my_repo + YAML + repositories.local.should eql(File.expand_path('my_repo')) + end + + it 'should not override custom install methods defined when extending an object' do + class MyOwnInstallTask + + attr_accessor :result + + def install + result = true + end + + end + task = MyOwnInstallTask.new + task.result = "maybe" + task.extend ActsAsArtifact + task.install + task.result.should be_true + end +end + + +describe Repositories, 'remote' do + before do + Buildr.repositories.instance_eval do + @local = @remote = @release_to = nil + end + + @repos = [ 'http://www.ibiblio.org/maven2', 'http://repo1.maven.org/maven2' ] + end + + it 'should be empty initially' do + repositories.remote.should be_empty + end + + it 'should be settable' do + repositories.remote = @repos.first + repositories.remote.should eql([@repos.first]) + end + + it 'should be settable from array' do + repositories.remote = @repos + repositories.remote.should eql(@repos) + end + + it 'should add and return repositories in order' do + @repos.each { |url| repositories.remote << url } + repositories.remote.should eql(@repos) + end + + it 'should be used to download artifact' do + repositories.remote = 'http://example.com' + URI.should_receive(:download).twice.and_return { |uri, target, options| write target } + lambda { artifact('com.example:library:jar:2.0').invoke }. + should change { File.exist?(File.join(repositories.local, 'com/example/library/2.0/library-2.0.jar')) }.to(true) + end + + it 'should lookup in array order' do + repositories.remote = [ 'http://example.com', 'http://example.org' ] + order = ['com', 'org'] + URI.should_receive(:download).any_number_of_times do |uri, target, options| + order.shift if order.first && uri.to_s[order.first] + fail URI::NotFoundError unless order.empty? + write target + end + lambda { artifact('com.example:library:jar:2.0').invoke }.should change { order.empty? } + end + + it 'should fail if artifact not found' do + repositories.remote = 'http://example.com' + URI.should_receive(:download).once.ordered.and_return { fail URI::NotFoundError } + lambda { artifact('com.example:library:jar:2.0').invoke }.should raise_error(RuntimeError, /Failed to download/) + File.exist?(File.join(repositories.local, 'com/example/library/2.0/library-2.0.jar')).should be_false + end + + it 'should support artifact classifier' do + repositories.remote = 'http://example.com' + URI.should_receive(:download).once.and_return { |uri, target, options| write target } + lambda { artifact('com.example:library:jar:all:2.0').invoke }. + should change { File.exist?(File.join(repositories.local, 'com/example/library/2.0/library-2.0-all.jar')) }.to(true) + end + + it 'should deal well with repositories URL that lack the last slash' do + repositories.remote = 'http://example.com/base' + uri = nil + URI.should_receive(:download).twice.and_return { |_uri, args| uri = _uri } + artifact('group:id:jar:1.0').invoke + uri.to_s.should eql('http://example.com/base/group/id/1.0/id-1.0.pom') + end + + it 'should deal well with repositories URL that have the last slash' do + repositories.remote = 'http://example.com/base/' + uri = nil + URI.should_receive(:download).twice.and_return { |_uri, args| uri = _uri } + artifact('group:id:jar:1.0').invoke + uri.to_s.should eql('http://example.com/base/group/id/1.0/id-1.0.pom') + end + + it 'should resolve m2-style deployed snapshots' do + metadata = <<-XML + + + com.example + library + 2.1-SNAPSHOT + + + 20071012.190008 + 8 + + 20071012190008 + + + XML + repositories.remote = 'http://example.com' + URI.should_receive(:download).twice.with(uri(/2.1-SNAPSHOT\/library-2.1-SNAPSHOT.(jar|pom)$/), anything()). + and_return { fail URI::NotFoundError } + URI.should_receive(:download).twice.with(uri(/2.1-SNAPSHOT\/maven-metadata.xml$/), duck_type(:write)). + and_return { |uri, target, options| target.write(metadata) } + URI.should_receive(:download).twice.with(uri(/2.1-SNAPSHOT\/library-2.1-20071012.190008-8.(jar|pom)$/), /2.1-SNAPSHOT\/library-2.1-SNAPSHOT.(jar|pom).(\d){1,}$/). + and_return { |uri, target, options| write target } + lambda { artifact('com.example:library:jar:2.1-SNAPSHOT').invoke }. + should change { File.exist?(File.join(repositories.local, 'com/example/library/2.1-SNAPSHOT/library-2.1-SNAPSHOT.jar')) }.to(true) + end + + it 'should resolve m2-style deployed snapshots with classifiers' do + metadata = <<-XML + + + com.example + library + 2.1-SNAPSHOT + + + 20071012.190008 + 8 + + 20071012190008 + + + XML + repositories.remote = 'http://example.com' + URI.should_receive(:download).once.with(uri(/2.1-SNAPSHOT\/library-2.1-20071012.190008-8-classifier.jar$/), anything()). + and_return { |uri, target, options| write target } + URI.should_receive(:download).once.with(uri(/2.1-SNAPSHOT\/maven-metadata.xml$/), duck_type(:write)). + and_return { |uri, target, options| target.write(metadata) } + puts repositories.local + lambda { artifact('com.example:library:jar:classifier:2.1-SNAPSHOT').invoke}. + should change {File.exists?(File.join(repositories.local, 'com/example/library/2.1-SNAPSHOT/library-2.1-SNAPSHOT-classifier.jar')) }.to(true) + end + + it 'should fail resolving m2-style deployed snapshots if a timestamp is missing' do + metadata = <<-XML + + + com.example + library + 2.1-SNAPSHOT + + + 8 + + 20071012190008 + + + XML + repositories.remote = 'http://example.com' + URI.should_receive(:download).once.with(uri(/2.1-SNAPSHOT\/library-2.1-SNAPSHOT.(jar|pom)$/), anything()). + and_return { fail URI::NotFoundError } + URI.should_receive(:download).once.with(uri(/2.1-SNAPSHOT\/maven-metadata.xml$/), duck_type(:write)). + and_return { |uri, target, options| target.write(metadata) } + lambda { + lambda { artifact('com.example:library:jar:2.1-SNAPSHOT').invoke }.should raise_error(RuntimeError, /Failed to download/) + }.should show_error "No timestamp provided for the snapshot com.example:library:jar:2.1-SNAPSHOT" + File.exist?(File.join(repositories.local, 'com/example/library/2.1-SNAPSHOT/library-2.1-SNAPSHOT.jar')).should be_false + end + + it 'should fail resolving m2-style deployed snapshots if a build number is missing' do + metadata = <<-XML + + + com.example + library + 2.1-SNAPSHOT + + + 20071012.190008 + + 20071012190008 + + + XML + repositories.remote = 'http://example.com' + URI.should_receive(:download).once.with(uri(/2.1-SNAPSHOT\/library-2.1-SNAPSHOT.(jar|pom)$/), anything()). + and_return { fail URI::NotFoundError } + URI.should_receive(:download).once.with(uri(/2.1-SNAPSHOT\/maven-metadata.xml$/), duck_type(:write)). + and_return { |uri, target, options| target.write(metadata) } + lambda { + lambda { artifact('com.example:library:jar:2.1-SNAPSHOT').invoke }.should raise_error(RuntimeError, /Failed to download/) + }.should show_error "No build number provided for the snapshot com.example:library:jar:2.1-SNAPSHOT" + File.exist?(File.join(repositories.local, 'com/example/library/2.1-SNAPSHOT/library-2.1-SNAPSHOT.jar')).should be_false + end + + it 'should handle missing maven metadata by reporting the artifact unavailable' do + repositories.remote = 'http://example.com' + URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/library-2.1-SNAPSHOT.jar$/), anything()). + and_return { fail URI::NotFoundError } + URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/maven-metadata.xml$/), duck_type(:write)). + and_return { fail URI::NotFoundError } + lambda { artifact('com.example:library:jar:2.1-SNAPSHOT').invoke }.should raise_error(RuntimeError, /Failed to download/) + File.exist?(File.join(repositories.local, 'com/example/library/2.1-SNAPSHOT/library-2.1-SNAPSHOT.jar')).should be_false + end + + it 'should handle missing m2 snapshots by reporting the artifact unavailable' do + metadata = <<-XML + + + com.example + library + 2.1-SNAPSHOT + + + 20071012.190008 + 8 + + 20071012190008 + + + XML + repositories.remote = 'http://example.com' + URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/library-2.1-SNAPSHOT.jar$/), anything()). + and_return { fail URI::NotFoundError } + URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/maven-metadata.xml$/), duck_type(:write)). + and_return { |uri, target, options| target.write(metadata) } + URI.should_receive(:download).with(uri(/2.1-SNAPSHOT\/library-2.1-20071012.190008-8.jar$/), anything()). + and_return { fail URI::NotFoundError } + lambda { artifact('com.example:library:jar:2.1-SNAPSHOT').invoke }.should raise_error(RuntimeError, /Failed to download/) + File.exist?(File.join(repositories.local, 'com/example/library/2.1-SNAPSHOT/library-2.1-SNAPSHOT.jar')).should be_false + end + + it 'should load with all repositories specified in settings file' do + write 'home/.buildr/settings.yaml', <<-YAML + repositories: + remote: + - http://example.com + - http://example.org + YAML + repositories.remote.should include('http://example.com', 'http://example.org') + end + + it 'should load with all repositories specified in build.yaml file' do + write 'build.yaml', <<-YAML + repositories: + remote: + - http://example.com + - http://example.org + YAML + repositories.remote.should include('http://example.com', 'http://example.org') + end + + it 'should load with all repositories specified in settings and build.yaml files' do + write 'home/.buildr/settings.yaml', <<-YAML + repositories: + remote: + - http://example.com + YAML + write 'build.yaml', <<-YAML + repositories: + remote: + - http://example.org + YAML + repositories.remote.should include('http://example.com', 'http://example.org') + end +end + + +describe Repositories, 'release_to' do + it 'should accept URL as first argument' do + repositories.release_to = 'http://example.com' + repositories.release_to.should == { :url=>'http://example.com' } + end + + it 'should accept hash with options' do + repositories.release_to = { :url=>'http://example.com', :username=>'john' } + repositories.release_to.should == { :url=>'http://example.com', :username=>'john' } + end + + it 'should allow the hash to be manipulated' do + repositories.release_to = 'http://example.com' + repositories.release_to.should == { :url=>'http://example.com' } + repositories.release_to[:username] = 'john' + repositories.release_to.should == { :url=>'http://example.com', :username=>'john' } + end + + it 'should load URL from settings file' do + write 'home/.buildr/settings.yaml', <<-YAML + repositories: + release_to: http://john:secret@example.com + YAML + repositories.release_to.should == { :url=>'http://john:secret@example.com' } + end + + it 'should load URL from build settings file' do + write 'build.yaml', <<-YAML + repositories: + release_to: http://john:secret@example.com + YAML + repositories.release_to.should == { :url=>'http://john:secret@example.com' } + end + + it 'should load URL, username and password from settings file' do + write 'home/.buildr/settings.yaml', <<-YAML + repositories: + release_to: + url: http://example.com + username: john + password: secret + YAML + repositories.release_to.should == { :url=>'http://example.com', :username=>'john', :password=>'secret' } + end +end + + +describe Buildr, '#artifact' do + before do + @spec = { :group=>'com.example', :id=>'library', :type=>'jar', :version=>'2.0' } + @snapshot_spec = 'group:id:jar:1.0-SNAPSHOT' + write @file = 'testartifact.jar' + end + + it 'should accept hash specification' do + artifact(:group=>'com.example', :id=>'library', :type=>'jar', :version=>'2.0').should respond_to(:invoke) + end + + it 'should reject partial hash specifier' do + lambda { artifact(@spec.merge(:group=>nil)) }.should raise_error + lambda { artifact(@spec.merge(:id=>nil)) }.should raise_error + lambda { artifact(@spec.merge(:version=>nil)) }.should raise_error + end + + it 'should complain about invalid key' do + lambda { artifact(@spec.merge(:error=>true)) }.should raise_error(ArgumentError, /no such option/i) + end + + it 'should use JAR type by default' do + artifact(@spec.merge(:type=>nil)).should respond_to(:invoke) + end + + it 'should accept string specification' do + artifact('com.example:library:jar:2.0').should respond_to(:invoke) + end + + it 'should reject partial string specifier' do + artifact('com.example:library::2.0') + lambda { artifact('com.example:library:jar') }.should raise_error + lambda { artifact('com.example:library:jar:') }.should raise_error + lambda { artifact('com.example:library::2.0') }.should_not raise_error + lambda { artifact('com.example::jar:2.0') }.should raise_error + lambda { artifact(':library:jar:2.0') }.should raise_error + end + + it 'should create a task naming the artifact in the local repository' do + file = File.join(repositories.local, 'com', 'example', 'library', '2.0', 'library-2.0.jar') + Rake::Task.task_defined?(file).should be_false + artifact('com.example:library:jar:2.0').name.should eql(file) + end + + it 'should use from method to install artifact from existing file' do + write 'test.jar' + artifact = artifact('group:id:jar:1.0').from('test.jar') + lambda { artifact.invoke }.should change { File.exist?(artifact.to_s) }.to(true) + end + + it 'should use from method to install artifact from a file task' do + test_jar = file('test.jar') + test_jar.enhance do + #nothing... + end + write 'test.jar' + artifact = artifact('group:id:jar:1.0').from(test_jar) + lambda { artifact.invoke }.should change { File.exist?(artifact.to_s) }.to(true) + end + + it 'should invoke the artifact associated file task if the file doesnt exist' do + test_jar = file('test.jar') + called = false + test_jar.enhance do + write 'test.jar' + called = true + end + artifact = artifact('group:id:jar:1.0').from(test_jar) + artifact.invoke + unless called + raise "The file task was not called." + end + end + + it 'should not invoke the artifact associated file task if the file already exists' do + test_jar = file('test.jar') + test_jar.enhance do + raise 'the test.jar file is created again!' + end + write 'test.jar' + artifact = artifact('group:id:jar:1.0').from(test_jar) + artifact.invoke + end + + it 'should reference artifacts defined on build.yaml by using ruby symbols' do + write 'build.yaml', <<-YAML + artifacts: + j2ee: geronimo-spec:geronimo-spec-j2ee:jar:1.4-rc4 + YAML + Buildr.application.send(:load_artifact_ns) + artifact(:j2ee).to_s.pathmap('%f').should == 'geronimo-spec-j2ee-1.4-rc4.jar' + end + + it 'should try to download snapshot artifact' do + run_with_repo + snapshot = artifact(@snapshot_spec) + + URI.should_receive(:download).at_least(:twice).and_return { |uri, target, options| write target } + FileUtils.should_receive(:mv).at_least(:twice) + snapshot.invoke + end + + it 'should not try to update snapshot in offline mode if it exists' do + run_with_repo + snapshot = artifact(@snapshot_spec) + write snapshot.to_s + Buildr.application.options.work_offline = true + URI.should_receive(:download).exactly(0).times + snapshot.invoke + end + + it 'should download snapshot even in offline mode if it doesn''t exist' do + run_with_repo + snapshot = artifact(@snapshot_spec) + Buildr.application.options.work_offline = true + URI.should_receive(:download).exactly(2).times + snapshot.invoke + end + + it 'should update snapshots if --update-snapshots' do + run_with_repo + snapshot = artifact(@snapshot_spec) + write snapshot.to_s + Buildr.application.options.update_snapshots = true + + URI.should_receive(:download).at_least(:twice).and_return { |uri, target, options| write target } + FileUtils.should_receive(:mv).at_least(:twice) + snapshot.invoke + end + + it 'should update snapshot if it''s older than 24 hours' do + run_with_repo + snapshot = artifact(@snapshot_spec) + write snapshot.to_s + time = Time.at((Time.now - (60 * 60 * 24) - 10 ).to_i) + File.utime(time, time, snapshot.to_s) + URI.should_receive(:download).at_least(:once).and_return { |uri, target, options| write target } + snapshot.invoke + end + + def run_with_repo + repositories.remote = 'http://example.com' + end + +end + + +describe Buildr, '#artifacts' do + it 'should return a list of artifacts from all its arguments' do + specs = [ 'saxon:saxon:jar:8.4', 'saxon:saxon-dom:jar:8.4', 'saxon:saxon-xpath:jar:8.4' ] + artifacts(*specs).should eql(specs.map { |spec| artifact(spec) }) + end + + it 'should accept nested arrays' do + specs = [ 'saxon:saxon:jar:8.4', 'saxon:saxon-dom:jar:8.4', 'saxon:saxon-xpath:jar:8.4' ] + artifacts([[specs[0]]], [[specs[1]], specs[2]]).should eql(specs.map { |spec| artifact(spec) }) + end + + it 'should accept struct' do + specs = struct(:main=>'saxon:saxon:jar:8.4', :dom=>'saxon:saxon-dom:jar:8.4', :xpath=>'saxon:saxon-xpath:jar:8.4') + artifacts(specs).should eql(specs.values.map { |spec| artifact(spec) }) + end + + it 'should ignore duplicates' do + artifacts('saxon:saxon:jar:8.4', 'saxon:saxon:jar:8.4').size.should be(1) + end + + it 'should accept and return existing tasks' do + artifacts(task('foo'), task('bar')).should eql([task('foo'), task('bar')]) + end + + it 'should accept filenames and expand them' do + artifacts('test').map(&:to_s).should eql([File.expand_path('test')]) + end + + it 'should accept filenames and return filenames' do + artifacts('c:test').first.should be_kind_of(String) + end + + it 'should accept any object responding to :to_spec' do + obj = Object.new + class << obj + def to_spec; "org.example:artifact:jar:1.1"; end + end + artifacts(obj).size.should be(1) + end + + it 'should accept project and return all its packaging tasks' do + define 'foobar', :group=>'group', :version=>'1.0' do + package :jar, :id=>'code' + package :war, :id=>'webapp' + end + foobar = project('foobar') + artifacts(foobar).should eql([ + task(foobar.path_to('target/code-1.0.jar')), + task(foobar.path_to('target/webapp-1.0.war')) + ]) + end + + it 'should complain about an invalid specification' do + lambda { artifacts(5) }.should raise_error + lambda { artifacts('group:no:version:') }.should raise_error + end +end + + +describe Buildr, '#group' do + it 'should accept list of artifact identifiers' do + list = group('saxon', 'saxon-dom', 'saxon-xpath', :under=>'saxon', :version=>'8.4') + list.should include(artifact('saxon:saxon:jar:8.4')) + list.should include(artifact('saxon:saxon-dom:jar:8.4')) + list.should include(artifact('saxon:saxon-xpath:jar:8.4')) + list.size.should be(3) + end + + it 'should accept array with artifact identifiers' do + list = group(%w{saxon saxon-dom saxon-xpath}, :under=>'saxon', :version=>'8.4') + list.should include(artifact('saxon:saxon:jar:8.4')) + list.should include(artifact('saxon:saxon-dom:jar:8.4')) + list.should include(artifact('saxon:saxon-xpath:jar:8.4')) + list.size.should be(3) + end + + it 'should accept a type' do + list = group('struts-bean', 'struts-html', :under=>'struts', :type=>'tld', :version=>'1.1') + list.should include(artifact('struts:struts-bean:tld:1.1')) + list.should include(artifact('struts:struts-html:tld:1.1')) + list.size.should be(2) + end + + it 'should accept a classifier' do + list = group('camel-core', :under=>'org.apache.camel', :version=>'2.2.0', :classifier=>'spring3') + list.should include(artifact('org.apache.camel:camel-core:jar:spring3:2.2.0')) + list.size.should be(1) + end + +end + +describe Buildr, '#install' do + before do + @spec = 'group:id:jar:1.0' + write @file = 'test.jar' + @snapshot_spec = 'group:id:jar:1.0-SNAPSHOT' + end + + it 'should return the install task' do + install.should be(task('install')) + end + + it 'should accept artifacts to install' do + install artifact(@spec) + lambda { install @file }.should raise_error(ArgumentError) + end + + it 'should install artifact when install task is run' do + write @file + install artifact(@spec).from(@file) + lambda { install.invoke }.should change { File.exist?(artifact(@spec).to_s) }.to(true) + end + + it 'should re-install artifact when "from" is newer' do + install artifact(@spec).from(@file) + write artifact(@spec).to_s # install a version of the artifact + old_mtime = File.mtime(artifact(@spec).to_s) + sleep 1; write @file # make sure the "from" file has newer modification time + lambda { install.invoke }.should change { modified?(old_mtime, @spec) }.to(true) + end + + it 'should re-install snapshot artifact when "from" is newer' do + install artifact(@snapshot_spec).from(@file) + write artifact(@snapshot_spec).to_s # install a version of the artifact + old_mtime = File.mtime(artifact(@snapshot_spec).to_s) + sleep 1; write @file # make sure the "from" file has newer modification time + lambda { install.invoke }.should change { modified?(old_mtime, @snapshot_spec) }.to(true) + end + + it 'should download snapshot to temporary location' do + repositories.remote = 'http://example.com' + snapshot = artifact(@snapshot_spec) + same_time = Time.new + download_file = "#{Dir.tmpdir}/#{File.basename(snapshot.name)}#{same_time.to_i}" + + Time.should_receive(:new).twice.and_return(same_time) + URI.should_receive(:download).at_least(:twice).and_return { |uri, target, options| write target } + FileUtils.should_receive(:mv).at_least(:twice) + snapshot.invoke + end + + it 'should install POM alongside artifact (if artifact has no classifier)' do + pom = artifact(@spec).pom + write @file + install artifact(@spec).from(@file) + lambda { install.invoke }.should change { File.exist?(repositories.locate(pom)) }.to(true) + end + + it 'should not install POM alongside artifact if artifact has classifier' do + @spec = 'group:id:jar:all:1.0' + pom = artifact(@spec).pom + write @file + p method(:install) + install artifact(@spec).from(@file) + lambda { install.invoke }.should_not change { File.exist?(repositories.locate(pom)) }.to(true) + end + + it 'should reinstall POM alongside artifact' do + pom = artifact(@spec).pom + write @file + write repositories.locate(pom) + sleep 1 + + install artifact(@spec).from(@file) + lambda { install.invoke }.should change { File.mtime(repositories.locate(pom)) } + end +end + + +describe Buildr, '#upload' do + before do + @spec = 'group:id:jar:1.0' + write @file = 'test.jar' + repositories.release_to = 'sftp://example.com/base' + end + + it 'should return the upload task' do + upload.should be(task('upload')) + end + + it 'should accept artifacts to upload' do + upload artifact(@spec) + lambda { upload @file }.should raise_error(ArgumentError) + end + + it 'should upload artifact when upload task is run' do + write @file + upload artifact(@spec).from(@file) + URI.should_receive(:upload).once. + with(URI.parse('sftp://example.com/base/group/id/1.0/id-1.0.jar'), artifact(@spec).to_s, anything) + URI.should_receive(:upload).once. + with(URI.parse('sftp://example.com/base/group/id/1.0/id-1.0.pom'), artifact(@spec).pom.to_s, anything) + upload.invoke + end +end + + +describe ActsAsArtifact, '#upload' do + it 'should be used to upload artifact' do + artifact = artifact('com.example:library:jar:2.0') + # Prevent artifact from downloading anything. + write repositories.locate(artifact) + write repositories.locate(artifact.pom) + URI.should_receive(:upload).once. + with(URI.parse('sftp://example.com/base/com/example/library/2.0/library-2.0.pom'), artifact.pom.to_s, anything) + URI.should_receive(:upload).once. + with(URI.parse('sftp://example.com/base/com/example/library/2.0/library-2.0.jar'), artifact.to_s, anything) + verbose(false) { artifact.upload(:url=>'sftp://example.com/base') } + end + + it 'should support artifact classifier and should not upload pom if artifact has classifier' do + artifact = artifact('com.example:library:jar:all:2.0') + # Prevent artifact from downloading anything. + write repositories.locate(artifact) + URI.should_receive(:upload).exactly(:once). + with(URI.parse('sftp://example.com/base/com/example/library/2.0/library-2.0-all.jar'), artifact.to_s, anything) + verbose(false) { artifact.upload(:url=>'sftp://example.com/base') } + end + + it 'should complain without any repository configuration' do + artifact = artifact('com.example:library:jar:2.0') + # Prevent artifact from downloading anything. + write repositories.locate(artifact) + write repositories.locate(artifact.pom) + lambda { artifact.upload }.should raise_error(Exception, /where to upload/) + end + + it 'should accept repositories.upload setting' do + artifact = artifact('com.example:library:jar:2.0') + # Prevent artifact from downloading anything. + write repositories.locate(artifact) + write repositories.locate(artifact.pom) + URI.should_receive(:upload).at_least(:once) + repositories.release_to = 'sftp://example.com/base' + artifact.upload + lambda { artifact.upload }.should_not raise_error + end + +end + + +describe Rake::Task, ' artifacts' do + before do + Buildr.repositories.instance_eval do + @local = @remote = @release_to = nil + end + end + + it 'should download all specified artifacts' do + artifact 'group:id:jar:1.0' + repositories.remote = 'http://example.com' + URI.should_receive(:download).twice.and_return { |uri, target, options| write target } + task('artifacts').invoke + end + + it 'should fail if failed to download an artifact' do + artifact 'group:id:jar:1.0' + lambda { task('artifacts').invoke }.should raise_error(RuntimeError, /No remote repositories/) + end + + it 'should succeed if artifact already exists' do + write repositories.locate(artifact('group:id:jar:1.0')) + suppress_stdout do + lambda { task('artifacts').invoke }.should_not raise_error + end + end +end + + +describe Rake::Task, ' artifacts:sources' do + + before do + Buildr.repositories.instance_eval do + @local = @remote = @release_to = nil + end + task('artifacts:sources').clear + repositories.remote = 'http://example.com' + end + + it 'should download sources for all specified artifacts' do + artifact 'group:id:jar:1.0' + URI.should_receive(:download).any_number_of_times.and_return { |uri, target| write target } + lambda { task('artifacts:sources').invoke }.should change { File.exist?('home/.m2/repository/group/id/1.0/id-1.0-sources.jar') }.to(true) + end + + it "should not try to download sources for the project's artifacts" do + define('foo', :version=>'1.0') { package(:jar) } + URI.should_not_receive(:download) + task('artifacts:sources').invoke + end + + describe 'when the source artifact does not exist' do + + before do + artifact 'group:id:jar:1.0' + URI.should_receive(:download).any_number_of_times.and_raise(URI::NotFoundError) + end + + it 'should not fail' do + lambda { task('artifacts:sources').invoke }.should_not raise_error + end + + it 'should inform the user' do + lambda { task('artifacts:sources').invoke }.should show_info('Failed to download group:id:jar:sources:1.0. Skipping it.') + end + end +end + +describe Rake::Task, ' artifacts:javadoc' do + + before do + Buildr.repositories.instance_eval do + @local = @remote = @release_to = nil + end + task('artifacts:javadoc').clear + repositories.remote = 'http://example.com' + end + + it 'should download javadoc for all specified artifacts' do + artifact 'group:id:jar:1.0' + URI.should_receive(:download).any_number_of_times.and_return { |uri, target| write target } + lambda { task('artifacts:javadoc').invoke }.should change { File.exist?('home/.m2/repository/group/id/1.0/id-1.0-javadoc.jar') }.to(true) + end + + it "should not try to download javadoc for the project's artifacts" do + define('foo', :version=>'1.0') { package(:jar) } + URI.should_not_receive(:download) + task('artifacts:javadoc').invoke + end + + describe 'when the javadoc artifact does not exist' do + + before do + artifact 'group:id:jar:1.0' + URI.should_receive(:download).any_number_of_times.and_raise(URI::NotFoundError) + end + + it 'should not fail' do + lambda { task('artifacts:javadoc').invoke }.should_not raise_error + end + + it 'should inform the user' do + lambda { task('artifacts:javadoc').invoke }.should show_info('Failed to download group:id:jar:javadoc:1.0. Skipping it.') + end + end +end + +describe Buildr, '#transitive' do + before do + repositories.remote = 'http://example.com' + @simple = [ 'saxon:saxon:jar:8.4', 'saxon:saxon-dom:jar:8.4', 'saxon:saxon-xpath:jar:8.4' ] + @simple.map { |spec| artifact(spec).pom }.each { |task| write task.name, task.pom_xml } + @provided = @simple.first + @complex = 'group:app:jar:1.0' + write artifact(@complex).pom.to_s, <<-XML + + app + group + + + saxon + saxon + 8.4 + provided + + + saxon-dom + saxon + 8.4 + runtime + + + saxon-xpath + saxon + 8.4 + + + saxon-nosuch + saxon + 8.4 + test + + + jlib-optional + jlib + 1.4 + runtime + true + + + +XML + @transitive = 'master:app:war:1.0' + write artifact(@transitive).pom.to_s, <<-XML + + app + group + + + app + group + 1.0 + + + +XML + end + + it 'should return a list of artifacts from all its arguments' do + specs = [ 'saxon:saxon:jar:8.4', 'saxon:saxon-dom:jar:8.4', 'saxon:saxon-xpath:jar:8.4' ] + transitive(*specs).should eql(specs.map { |spec| artifact(spec) }) + end + + it 'should accept nested arrays' do + specs = [ 'saxon:saxon:jar:8.4', 'saxon:saxon-dom:jar:8.4', 'saxon:saxon-xpath:jar:8.4' ] + transitive([[specs[0]]], [[specs[1]], specs[2]]).should eql(specs.map { |spec| artifact(spec) }) + end + + it 'should accept struct' do + specs = struct(:main=>'saxon:saxon:jar:8.4', :dom=>'saxon:saxon-dom:jar:8.4', :xpath=>'saxon:saxon-xpath:jar:8.4') + transitive(specs).should eql(specs.values.map { |spec| artifact(spec) }) + end + + it 'should ignore duplicates' do + transitive('saxon:saxon:jar:8.4', 'saxon:saxon:jar:8.4').size.should be(1) + end + + it 'should accept and return existing tasks' do + transitive(task('foo'), task('bar')).should eql([task('foo'), task('bar')]) + end + + it 'should accept filenames and expand them' do + transitive('test').map(&:to_s).should eql([File.expand_path('test')]) + end + + it 'should accept filenames and return file task' do + transitive('c:test').first.should be_kind_of(Rake::FileTask) + end + + it 'should accept project and return all its packaging tasks' do + define 'foobar', :group=>'group', :version=>'1.0' do + package :jar, :id=>'code' + package :war, :id=>'webapp' + end + foobar = project('foobar') + transitive(foobar).should eql([ + task(foobar.path_to('target/code-1.0.jar')), + task(foobar.path_to('target/webapp-1.0.war')) + ]) + end + + it 'should complain about an invalid specification' do + lambda { transitive(5) }.should raise_error + lambda { transitive('group:no:version:') }.should raise_error + end + + it 'should bring artifact and its dependencies' do + transitive(@complex).should eql(artifacts(@complex, @simple)) + end + + it 'should bring dependencies of POM without artifact itself' do + transitive(@complex.sub(/jar/, 'pom')).should eql(artifacts(@simple)) + end + + it 'should bring artifact and transitive depenencies' do + transitive(@transitive).should eql(artifacts(@transitive, @complex, @simple - [@provided])) + end + + it 'should filter dependencies based on :scopes argument' do + specs = [@complex, 'saxon:saxon-dom:jar:8.4'] + transitive(@complex, :scopes => [:runtime]).should eql(specs.map { |spec| artifact(spec) }) + end + + it 'should filter dependencies based on :optional argument' do + specs = [@complex, 'saxon:saxon-dom:jar:8.4', 'jlib:jlib-optional:jar:1.4'] + transitive(@complex, :scopes => [:runtime], :optional => true).should eql(specs.map { |spec| artifact(spec) }) + end +end + +def modified?(old_mtime, spec) + File.exist?(artifact(spec).to_s) && old_mtime < File.mtime(artifact(spec).to_s) +end diff --git a/buildr/spec/packaging/packaging_helper.rb b/buildr/spec/packaging/packaging_helper.rb new file mode 100644 index 0000000..d555d03 --- /dev/null +++ b/buildr/spec/packaging/packaging_helper.rb @@ -0,0 +1,63 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +shared_examples_for 'packaging' do + it 'should create artifact of proper type' do + packaging = @packaging + package_type = @package_type || @packaging + define 'foo', :version=>'1.0' do + package(packaging).type.should eql(package_type) rescue exit! + end + end + + it 'should create file with proper extension' do + packaging = @packaging + package_type = @package_type || @packaging + define 'foo', :version=>'1.0' do + package(packaging).to_s.should match(/.#{package_type}$/) + end + end + + it 'should always return same task for the same package' do + packaging = @packaging + define 'foo', :version=>'1.0' do + package(packaging) + package(packaging, :id=>'other') + end + project('foo').packages.uniq.size.should eql(2) + end + + it 'should complain if option not known' do + packaging = @packaging + define 'foo', :version=>'1.0' do + lambda { package(packaging, :unknown_option=>true) }.should raise_error(ArgumentError, /no such option/) + end + end + + it 'should respond to with() and return self' do + packaging = @packaging + define 'foo', :version=>'1.0' do + package(packaging).with({}).should be(package(packaging)) + end + end + + it 'should respond to with() and complain if unknown option' do + packaging = @packaging + define 'foo', :version=>'1.0' do + lambda { package(packaging).with(:unknown_option=>true) }.should raise_error(ArgumentError, /does not support the option/) + end + end +end diff --git a/buildr/spec/packaging/packaging_spec.rb b/buildr/spec/packaging/packaging_spec.rb new file mode 100644 index 0000000..ab1966e --- /dev/null +++ b/buildr/spec/packaging/packaging_spec.rb @@ -0,0 +1,719 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) +require File.expand_path(File.join(File.dirname(__FILE__), 'packaging_helper')) + + +describe Project, '#group' do + it 'should default to project name' do + desc 'My Project' + define('foo').group.should eql('foo') + end + + it 'should be settable' do + define('foo', :group=>'bar').group.should eql('bar') + end + + it 'should inherit from parent project' do + define('foo', :group=>'groupie') { define 'bar' } + project('foo:bar').group.should eql('groupie') + end +end + + +describe Project, '#version' do + it 'should default to nil' do + define('foo').version.should be_nil + end + + it 'should be settable' do + define('foo', :version=>'2.1').version.should eql('2.1') + end + + it 'should inherit from parent project' do + define('foo', :version=>'2.1') { define 'bar' } + project('foo:bar').version.should eql('2.1') + end + +end + + +describe Project, '#id' do + it 'should be same as project name' do + define('foo').id.should eql('foo') + end + + it 'should replace colons with dashes' do + define('foo', :version=>'2.1') { define 'bar' } + project('foo:bar').id.should eql('foo-bar') + end + + it 'should not be settable' do + lambda { define 'foo', :id=>'bar' }.should raise_error(NoMethodError) + end +end + + +describe Project, '#package' do + it 'should default to id from project' do + define('foo', :version=>'1.0') do + package(:jar).id.should eql('foo') + end + end + + it 'should default to composed id for nested projects' do + define('foo', :version=>'1.0') do + define 'bar' do + package(:jar).id.should eql('foo-bar') + end + end + end + + it 'should take id from option if specified' do + define 'foo', :version=>'1.0' do + package(:jar, :id=>'bar').id.should eql('bar') + define 'bar' do + package(:jar, :id=>'baz').id.should eql('baz') + end + end + end + + it 'should default to group from project' do + define 'foo', :version=>'1.0' do + package(:jar).group.should eql('foo') + define 'bar' do + package(:jar).group.should eql('foo') + end + end + end + + it 'should take group from option if specified' do + define 'foo', :version=>'1.0' do + package(:jar, :group=>'foos').group.should eql('foos') + define 'bar' do + package(:jar, :group=>'bars').group.should eql('bars') + end + end + end + + it 'should default to version from project' do + define 'foo', :version=>'1.0' do + package(:jar).version.should eql('1.0') + define 'bar' do + package(:jar).version.should eql('1.0') + end + end + end + + it 'should take version from option if specified' do + define 'foo', :version=>'1.0' do + package(:jar, :version=>'1.1').version.should eql('1.1') + define 'bar' do + package(:jar, :version=>'1.2').version.should eql('1.2') + end + end + end + + it 'should accept package type as first argument' do + define 'foo', :version=>'1.0' do + package(:war).type.should eql(:war) + define 'bar' do + package(:jar).type.should eql(:jar) + end + end + end + + it 'should support optional type' do + define 'foo', :version=>'1.0' do + package.type.should eql(:zip) + package(:classifier=>'srcs').type.should eql(:zip) + end + define 'bar', :version=>'1.0' do + compile.using :javac + package(:classifier=>'srcs').type.should eql(:jar) + end + end + + it 'should assume :zip package type unless specified' do + define 'foo', :version=>'1.0' do + package.type.should eql(:zip) + define 'bar' do + package.type.should eql(:zip) + end + end + end + + it 'should infer packaging type from compiler' do + define 'foo', :version=>'1.0' do + compile.using :javac + package.type.should eql(:jar) + end + end + + it 'should fail if packaging not supported' do + lambda { define('foo') { package(:weirdo) } }.should raise_error(RuntimeError, /Don't know how to create a package/) + end + + it 'should call package_as_foo when using package(:foo)' do + class Buildr::Project + def package_as_foo(file_name) + file(file_name) do |t| + mkdir_p File.dirname(t.to_s) + File.open(t.to_s, 'w') {|f| f.write('foo') } + end + end + end + define('foo', :version => '1.0') do |project| + package(:foo).invoke + package(:foo).should exist + package(:foo).should contain('foo') + end + end + + it 'should allow to respec package(:sources) using package_as_sources_spec()' do + class Buildr::Project + def package_as_sources_spec(spec) + spec.merge({ :type=>:jar, :classifier=>'sources' }) + end + end + define('foo', :version => '1.0') do + package(:sources).type.should eql(:jar) + package(:sources).classifier.should eql('sources') + end + end + + it 'should produce different packages for different specs' do + class Buildr::Project + def package_as_foo(file_name) + file(file_name) + end + + def package_as_foo_spec(spec) + spec.merge(:type => :zip) + end + + def package_as_bar(file_name) + file(file_name) + end + + def package_as_bar_spec(spec) + spec.merge(:type => :zip, :classifier => "foobar") + end + + end + define('foo', :version => '1.0') do + package(:foo).type.should eql(:zip) + package(:foo).classifier.should be_nil + package(:bar).type.should eql(:zip) + package(:bar).classifier.should eql('foobar') + package(:foo).equal?(package(:bar)).should be_false + end + end + + it 'should default to no classifier' do + define 'foo', :version=>'1.0' do + package.classifier.should be_nil + define 'bar' do + package.classifier.should be_nil + end + end + end + + it 'should accept classifier from option' do + define 'foo', :version=>'1.0' do + package(:classifier=>'srcs').classifier.should eql('srcs') + define 'bar' do + package(:classifier=>'docs').classifier.should eql('docs') + end + end + end + + it 'should return a file task' do + define('foo', :version=>'1.0') { package(:jar) } + project('foo').package(:jar).should be_kind_of(Rake::FileTask) + end + + it 'should return a task that acts as artifact' do + define('foo', :version=>'1.0') { package(:jar) } + project('foo').package(:jar).should respond_to(:to_spec) + project('foo').package(:jar).to_spec.should eql('foo:foo:jar:1.0') + end + + it 'should create different tasks for each spec' do + define 'foo', :version=>'1.0' do + package(:jar) + package(:war) + package(:jar, :id=>'bar') + package(:jar, :classifier=>'srcs') + package(:jar, :classifier=>'doc') + end + project('foo').packages.uniq.size.should be(5) + end + + it 'should create different tasks for package with different ids' do + define 'foo', :version=>'1.0' do + package(:jar, :id=>'bar') + package(:jar) + end + project('foo').packages.uniq.size.should be(2) + end + + it 'should create different tasks for package with classifier' do + define 'foo', :version=>'1.0' do + package(:jar) + package(:jar, :classifier=>'foo') + end + project('foo').packages.uniq.size.should be(2) + end + + it 'should not create multiple packages for the same spec' do + define 'foo', :version=>'1.0' do + package(:war) + package(:war) + package(:jar, :id=>'bar') + package(:jar, :id=>'bar') + package(:jar, :id=>'baz') + end + project('foo').packages.uniq.size.should be(3) + end + + it 'should create different tasks for specs with matching type' do + define 'foo', :version=>'1.0' do + javadoc("foo").into( "foo" ) + package(:javadoc) + package(:zip) + end + project('foo').packages.uniq.size.should be(2) + end + + it 'should return the same task for subsequent calls' do + define 'foo', :version=>'1.0' do + package.should eql(package) + package(:jar, :classifier=>'resources').should be(package(:jar, :classifier=>'resources')) + end + end + + it 'should return a packaging task even if file already exists' do + write 'target/foo-1.0.zip', '' + define 'foo', :version=>'1.0' do + package.should be_kind_of(ZipTask) + end + end + + it 'should register task as artifact' do + define 'foo', :version=>'1.0' do + package(:jar, :id=>'bar') + package(:war) + end + project('foo').packages.should eql(artifacts('foo:bar:jar:1.0', 'foo:foo:war:1.0')) + end + + it 'should create in target path' do + define 'foo', :version=>'1.0' do + package(:war).should point_to_path('target/foo-1.0.war') + package(:jar, :id=>'bar').should point_to_path('target/bar-1.0.jar') + package(:zip, :classifier=>'srcs').should point_to_path('target/foo-1.0-srcs.zip') + end + end + + it 'should create prerequisite for package task' do + define 'foo', :version=>'1.0' do + package(:war) + package(:jar, :id=>'bar') + package(:jar, :classifier=>'srcs') + end + project('foo').task('package').prerequisites.should include(*project('foo').packages) + end + + it 'should create task requiring a build' do + define 'foo', :version=>'1.0' do + package(:war).prerequisites.should include(build) + package(:jar, :id=>'bar').prerequisites.should include(build) + package(:jar, :classifier=>'srcs').prerequisites.should include(build) + end + end + + it 'should create a POM artifact in target directory' do + define 'foo', :version=>'1.0' do + package.pom.should be(artifact('foo:foo:pom:1.0')) + package.pom.to_s.should point_to_path('target/foo-1.0.pom') + end + end + + it 'should create POM artifact ignoring classifier' do + define 'foo', :version=>'1.0' do + package(:jar, :classifier=>'srcs').pom.should be(artifact('foo:foo:pom:1.0')) + end + end + + it 'should create POM artifact that creates its own POM' do + define('foo', :group=>'bar', :version=>'1.0') { package(:jar, :classifier=>'srcs') } + pom = project('foo').packages.first.pom + pom.invoke + read(pom.to_s).should eql(<<-POM + + + 4.0.0 + bar + foo + 1.0 + +POM + ) + end + + it 'should not require downloading artifact or POM' do + #task('artifacts').instance_eval { @actions.clear } + define('foo', :group=>'bar', :version=>'1.0') { package(:jar) } + lambda { task('artifacts').invoke }.should_not raise_error + end + + describe "existing package access" do + it "should return the same instance for identical optionless invocations" do + define 'foo', :version => '1.0' do + package(:zip).should equal(package(:zip)) + end + project('foo').packages.size.should == 1 + end + + it "should return the exactly matching package identical invocations with options" do + define 'foo', :version => '1.0' do + package(:zip, :id => 'src') + package(:zip, :id => 'bin') + end + project('foo').package(:zip, :id => 'src').should equal(project('foo').packages.first) + project('foo').package(:zip, :id => 'bin').should equal(project('foo').packages.last) + project('foo').packages.size.should == 2 + end + + it "should return the first of the same type for subsequent optionless invocations" do + define 'foo', :version => '1.0' do + package(:zip, :file => 'override.zip') + package(:jar, :file => 'another.jar') + end + project('foo').package(:zip).name.should == 'override.zip' + project('foo').package(:jar).name.should == 'another.jar' + project('foo').packages.size.should == 2 + end + end + +end + + + + + +describe Project, '#package file' do + it 'should be a file task' do + define 'foo' do + package(:zip, :file=>'foo.zip').should be_kind_of(Rake::FileTask) + end + end + + it 'should not require id, project or version' do + define 'foo', :group=>nil do + lambda { package(:zip, :file=>'foo.zip') }.should_not raise_error + lambda { package(:zip, :file=>'bar.zip', :id=>'error') }.should raise_error + lambda { package(:zip, :file=>'bar.zip', :group=>'error') }.should raise_error + lambda { package(:zip, :file=>'bar.zip', :version=>'error') }.should raise_error + end + end + + it 'should not provide project or version' do + define 'foo' do + package(:zip, :file=>'foo.zip').tap do |pkg| + pkg.should_not respond_to(:group) + pkg.should_not respond_to(:version) + end + end + end + + it 'should provide packaging type' do + define 'foo', :version=>'1.0' do + zip = package(:zip, :file=>'foo.zip') + jar = package(:jar, :file=>'bar.jar') + zip.type.should eql(:zip) + jar.type.should eql(:jar) + end + end + + it 'should assume packaging type from extension if unspecified' do + define 'foo', :version=>'1.0' do + package(:file=>'foo.zip').class.should be(Buildr::ZipTask) + define 'bar' do + package(:file=>'bar.jar').class.should be(Buildr::Packaging::Java::JarTask) + end + end + end + + it 'should support different packaging types' do + define 'foo', :version=>'1.0' do + package(:jar, :file=>'foo.jar').class.should be(Buildr::Packaging::Java::JarTask) + end + define 'bar' do + package(:type=>:war, :file=>'bar.war').class.should be(Buildr::Packaging::Java::WarTask) + end + end + + it 'should fail if packaging not supported' do + lambda { define('foo') { package(:weirdo, :file=>'foo.zip') } }.should raise_error(RuntimeError, /Don't know how to create a package/) + end + + it 'should create different tasks for each file' do + define 'foo', :version=>'1.0' do + package(:zip, :file=>'foo.zip') + package(:jar, :file=>'foo.jar') + end + project('foo').packages.uniq.size.should be(2) + end + + it 'should return the same task for subsequent calls' do + define 'foo', :version=>'1.0' do + package(:zip, :file=>'foo.zip').should eql(package(:file=>'foo.zip')) + end + end + + it 'should point to specified file' do + define 'foo', :version=>'1.0' do + package(:zip, :file=>'foo.zip').should point_to_path('foo.zip') + package(:zip, :file=>'target/foo-1.0.zip').should point_to_path('target/foo-1.0.zip') + end + end + + it 'should create prerequisite for package task' do + define 'foo', :version=>'1.0' do + package(:zip, :file=>'foo.zip') + end + project('foo').task('package').prerequisites.should include(*project('foo').packages) + end + + it 'should create task requiring a build' do + define 'foo', :version=>'1.0' do + package(:zip, :file=>'foo.zip').prerequisites.should include(build) + end + end + + it 'should create specified file during build' do + define 'foo', :version=>'1.0' do + package(:zip, :file=>'foo.zip') + end + lambda { project('foo').task('package').invoke }.should change { File.exist?('foo.zip') }.to(true) + end + + it 'should do nothing for installation/upload' do + define 'foo', :version=>'1.0' do + package(:zip, :file=>'foo.zip') + end + lambda do + task('install').invoke + task('upload').invoke + task('uninstall').invoke + end.should_not raise_error + end + +end + + + + + + + +describe Rake::Task, ' package' do + it 'should be local task' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + in_original_dir project('foo:bar').base_dir do + task('package').invoke + project('foo').package.should_not exist + project('foo:bar').package.should exist + end + end + + it 'should be recursive task' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + task('package').invoke + project('foo').package.should exist + project('foo:bar').package.should exist + end + + it 'should create package in target directory' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + task('package').invoke + FileList['**/target/*.zip'].map.sort.should == ['bar/target/foo-bar-1.0.zip', 'target/foo-1.0.zip'] + end +end + + +describe Rake::Task, ' install' do + it 'should be local task' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + in_original_dir project('foo:bar').base_dir do + task('install').invoke + artifacts('foo:foo:zip:1.0', 'foo:foo:pom:1.0').each { |t| t.should_not exist } + artifacts('foo:foo-bar:zip:1.0', 'foo:foo-bar:pom:1.0').each { |t| t.should exist } + end + end + + it 'should be recursive task' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + task('install').invoke + artifacts('foo:foo:zip:1.0', 'foo:foo:pom:1.0', 'foo:foo-bar:zip:1.0', 'foo:foo-bar:pom:1.0').each { |t| t.should exist } + end + + it 'should create package in local repository' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + task('install').invoke + FileList[repositories.local + '/**/*'].reject { |f| File.directory?(f) }.sort.should == [ + File.expand_path('foo/foo/1.0/foo-1.0.zip', repositories.local), + File.expand_path('foo/foo/1.0/foo-1.0.pom', repositories.local), + File.expand_path('foo/foo-bar/1.0/foo-bar-1.0.zip', repositories.local), + File.expand_path('foo/foo-bar/1.0/foo-bar-1.0.pom', repositories.local)].sort + end +end + + +describe Rake::Task, ' uninstall' do + it 'should be local task' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + task('install').invoke + in_original_dir project('foo:bar').base_dir do + task('uninstall').invoke + FileList[repositories.local + '/**/*'].reject { |f| File.directory?(f) }.sort.should == [ + File.expand_path('foo/foo/1.0/foo-1.0.zip', repositories.local), + File.expand_path('foo/foo/1.0/foo-1.0.pom', repositories.local)].sort + end + end + + it 'should be recursive task' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + task('install').invoke + task('uninstall').invoke + FileList[repositories.local + '/**/*'].reject { |f| File.directory?(f) }.sort.should be_empty + end +end + + +describe Rake::Task, ' upload' do + before do + repositories.release_to = URI.escape("file://#{File.expand_path('remote')}") + end + + it 'should be local task' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + in_original_dir project('foo:bar').base_dir do + lambda { task('upload').invoke }.should run_task('foo:bar:upload').but_not('foo:upload') + end + end + + it 'should be recursive task' do + define 'foo', :version=>'1.0' do + package + define('bar') { package } + end + lambda { task('upload').invoke }.should run_tasks('foo:upload', 'foo:bar:upload') + end + + it 'should upload artifact and POM' do + define('foo', :version=>'1.0') { package :jar } + task('upload').invoke + { 'remote/foo/foo/1.0/foo-1.0.jar'=>project('foo').package(:jar), + 'remote/foo/foo/1.0/foo-1.0.pom'=>project('foo').package(:jar).pom }.each do |upload, package| + read(upload).should eql(read(package)) + end + end + + it 'should not upload twice the pom when artifacts are uploaded from a project' do + write 'src/main/java/Foo.java', 'public class Foo {}' + repositories.release_to = 'sftp://example.com/base' + define 'foo' do + project.group = "attached" + project.version = "1.0" + package(:jar) + package(:sources) + end + URI.should_receive(:upload).exactly(:once). + with(URI.parse('sftp://example.com/base/attached/foo/1.0/foo-1.0-sources.jar'), project("foo").package(:sources).to_s, anything) + URI.should_receive(:upload).exactly(:once). + with(URI.parse('sftp://example.com/base/attached/foo/1.0/foo-1.0.jar'), project("foo").package(:jar).to_s, anything) + URI.should_receive(:upload).exactly(:once). + with(URI.parse('sftp://example.com/base/attached/foo/1.0/foo-1.0.pom'), project("foo").package(:jar).pom.to_s, anything) + verbose(false) { project("foo").upload.invoke } + end + + it 'should upload signatures for artifact and POM' do + define('foo', :version=>'1.0') { package :jar } + task('upload').invoke + { 'remote/foo/foo/1.0/foo-1.0.jar'=>project('foo').package(:jar), + 'remote/foo/foo/1.0/foo-1.0.pom'=>project('foo').package(:jar).pom }.each do |upload, package| + read("#{upload}.md5").split.first.should eql(Digest::MD5.hexdigest(read(package, "rb"))) + read("#{upload}.sha1").split.first.should eql(Digest::SHA1.hexdigest(read(package, "rb"))) + end + end +end + + +describe Packaging, 'zip' do + it_should_behave_like 'packaging' + before { @packaging = :zip } + + it 'should not include META-INF directory' do + define('foo', :version=>'1.0') { package(:zip) } + project('foo').package(:zip).invoke + Zip::ZipFile.open(project('foo').package(:zip).to_s) do |zip| + zip.entries.map(&:to_s).should_not include('META-INF/') + end + end +end + + +describe Packaging, ' tar' do + before { @packaging = :tar } + it_should_behave_like 'packaging' +end + + +describe Packaging, ' tgz' do + before { @packaging = :tgz } + it_should_behave_like 'packaging' +end diff --git a/buildr/spec/sandbox.rb b/buildr/spec/sandbox.rb new file mode 100644 index 0000000..0c0d538 --- /dev/null +++ b/buildr/spec/sandbox.rb @@ -0,0 +1,203 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +# The local repository we use for testing is void of any artifacts, which will break given +# that the code requires several artifacts. So we establish them first using the real local +# repository and cache these across test cases. +Buildr.application.instance_eval { @rakefile = File.expand_path('buildfile') } +repositories.remote << 'http://repo1.maven.org/maven2' +repositories.remote << 'http://scala-tools.org/repo-releases' + +# Force Scala version for specs; don't want to rely on SCALA_HOME +module Buildr::Scala + SCALA_VERSION_FOR_SPECS = ENV["SCALA_VERSION"] || "2.8.1" +end +Buildr.settings.build['scala.version'] = Buildr::Scala::SCALA_VERSION_FOR_SPECS + +# Add a 'require' here only for optional extensions, not for extensions that should be loaded by default. +require 'buildr/clojure' +require 'buildr/groovy' +require 'buildr/scala' +require 'buildr/bnd' +require 'buildr/jaxb_xjc' + +Java.load # Anything added to the classpath. +artifacts( + TestFramework.frameworks.map(&:dependencies).flatten, + JUnit.ant_taskdef, + Buildr::Groovy.dependencies, + Buildr::JaxbXjc.dependencies, + Buildr::Bnd.dependencies, + Buildr::Scala::Scalac.dependencies, + Buildr::Shell::BeanShell.artifact, + Buildr::Clojure.dependencies +).each do |path| + file(path).invoke +end + +ENV['HOME'] = File.expand_path(File.join(File.dirname(__FILE__), '..', 'tmp', 'home')) +mkpath ENV['HOME'] + +# Make Scala.version resilient to sandbox reset +module Buildr::Scala + + remove_const(:DEFAULT_VERSION) + + DEFAULT_VERSION = SCALA_VERSION_FOR_SPECS + + class << self + def version + SCALA_VERSION_FOR_SPECS + end + end + + class Scalac + class << self + def use_installed? + false + end + end + end +end + +# We need to run all tests inside a _sandbox, tacking a snapshot of Buildr before the test, +# and restoring everything to its previous state after the test. Damn state changes. +module Sandbox + + class << self + attr_reader :tasks, :rules + + def included(spec) + spec.before(:each) { sandbox } + spec.after(:each) { reset } + end + + # Require an optional extension without letting its callbacks pollute the Project class. + def require_optional_extension(extension_require_path) + project_callbacks_without_extension = Project.class_eval { @global_callbacks }.dup + begin + require extension_require_path + ensure + Project.class_eval { @global_callbacks = project_callbacks_without_extension } + end + end + end + + @tasks = Buildr.application.tasks.collect do |original| + prerequisites = original.send(:prerequisites).map(&:to_s) + actions = original.instance_eval { @actions }.clone + lambda do + original.class.send(:define_task, original.name=>prerequisites).tap do |task| + task.comment = original.comment + actions.each { |action| task.enhance &action } + end + end + end + @rules = Buildr.application.instance_variable_get(:@rules) + + def sandbox + @_sandbox = {} + + # Create a temporary directory where we can create files, e.g, + # for projects, compilation. We need a place that does not depend + # on the current directory. + @_sandbox[:original_dir] = Dir.pwd + @temp = File.join(File.dirname(__FILE__), '../tmp') + FileUtils.mkpath @temp + Dir.chdir @temp + + ARGV.clear + Buildr.application = Buildr::Application.new + Sandbox.tasks.each { |block| block.call } + Buildr.application.instance_variable_set :@rules, Sandbox.rules.clone + Buildr.application.instance_eval { @rakefile = File.expand_path('buildfile') } + + @_sandbox[:load_path] = $LOAD_PATH.clone + + # clear RUBYOPT since bundler hooks into it + # e.g. RUBYOPT=-I/usr/lib/ruby/gems/1.8/gems/bundler-1.0.15/lib -rbundler/setup + # and so Buildr's own Gemfile configuration taints e.g., JRuby's environment + @_sandbox[:ruby_opt] = ENV["RUBYOPT"] + ENV["RUBYOPT"] = nil + + #@_sandbox[:loaded_features] = $LOADED_FEATURES.clone + + # Later on we'll want to lose all the on_define created during the test. + @_sandbox[:on_define] = Project.class_eval { (@on_define || []).dup } + @_sandbox[:extension_modules] = Project.class_eval { (@extension_modules || []).dup } + @_sandbox[:global_callbacks] = Project.class_eval { (@global_callbacks || []).dup } + @_sandbox[:layout] = Layout.default.clone + + # Create a local repository we can play with. However, our local repository will be void + # of some essential artifacts (e.g. JUnit artifacts required by build task), so we create + # these first (see above) and keep them across test cases. + @_sandbox[:artifacts] = Artifact.class_eval { @artifacts }.clone + @_sandbox[:local_repository] = Buildr.repositories.local + ENV['HOME'] = File.expand_path('home') + ENV['BUILDR_ENV'] = 'development' + + @_sandbox[:env_keys] = ENV.keys + ['DEBUG', 'TEST', 'HTTP_PROXY', 'HTTPS_PROXY', 'USER'].each { |k| ENV.delete(k) ; ENV.delete(k.downcase) } + + # By default, remote repository is user's own local M2 repository + # since we don't want to remotely download artifacts into the sandbox over and over + Buildr.repositories.instance_eval do + @remote = ["file://" + @local] + @local = @release_to = nil + end + Buildr.options.proxy.http = nil + + # Don't output crap to the console. + trace false + verbose false + end + + # Call this from teardown. + def reset + # Get rid of all the projects and the on_define blocks we used. + Project.clear + + on_define = @_sandbox[:on_define] + extension_modules = @_sandbox[:extension_modules] + global_callbacks = @_sandbox[:global_callbacks] + + Project.class_eval do + @on_define = on_define + @global_callbacks = global_callbacks + @extension_modules = extension_modules + end + + Layout.default = @_sandbox[:layout].clone + + $LOAD_PATH.replace @_sandbox[:load_path] + ENV["RUBYOPT"] = @_sandbox[:ruby_opt] + + FileUtils.rm_rf @temp + mkpath ENV['HOME'] + + # Get rid of all artifacts. + @_sandbox[:artifacts].tap { |artifacts| Artifact.class_eval { @artifacts = artifacts } } + + Buildr.repositories.local = @_sandbox[:local_repository] + + # Restore options. + Buildr.options.test = nil + (ENV.keys - @_sandbox[:env_keys]).each { |key| ENV.delete key } + + Dir.chdir @_sandbox[:original_dir] + end + +end diff --git a/buildr/spec/scala/bdd_spec.rb b/buildr/spec/scala/bdd_spec.rb new file mode 100644 index 0000000..fb946da --- /dev/null +++ b/buildr/spec/scala/bdd_spec.rb @@ -0,0 +1,222 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +if Java.java.lang.System.getProperty("java.runtime.version") >= "1.6" + +describe Buildr::Scala::Specs do + + it 'should be the default when tests in src/spec/scala reference "org.specs"' do + write 'src/spec/scala/com/example/MySpecs.scala', <<-SCALA + package com.example + object MySpecs extends org.specs.Specification { + "it" should { + "add" in { + val sum = 1 + 1 + sum mustEqual 2 + } + } + } + SCALA + define 'foo' + project('foo').test.framework.should eql(:specs) + end + + it 'should include Specs dependencies' do + define('foo') { test.using(:specs) } + project('foo').test.compile.dependencies.should include(*artifacts(Scala::Specs.dependencies)) + project('foo').test.dependencies.should include(*artifacts(Scala::Specs.dependencies)) + end + + it 'should include ScalaCheck dependencies' do + define('foo') { test.using(:specs) } + project('foo').test.compile.dependencies.should include(*artifacts(Scala::Check.dependencies)) + project('foo').test.dependencies.should include(*artifacts(Scala::Check.dependencies)) + end + + it 'should include public objects extending org.specs.Specification' do + write 'src/spec/scala/com/example/MySpecs.scala', <<-SCALA + package com.example + object MySpecs extends org.specs.Specification { + "it" should { + "add" in { + val sum = 1 + 1 + sum mustEqual 2 + } + } + } + SCALA + define('foo').test.invoke + project('foo').test.tests.should include('com.example.MySpecs') + end + + it 'should include public objects extending org.specs.Specification even with companion classes' do + write 'src/spec/scala/com/example/MySpecs.scala', <<-SCALA + package com.example + object MySpecs extends org.specs.Specification { + "it" should { + "add" in { + val sum = 1 + 1 + sum mustEqual 2 + } + } + } + class MySpecs extends org.specs.runner.JUnit4(MySpecs) + SCALA + define('foo').test.invoke + project('foo').test.tests.should include('com.example.MySpecs') + end + + it 'should pass when spec passes' do + write 'src/spec/scala/PassingSpecs.scala', <<-SCALA + object PassingSpecs extends org.specs.Specification { + "it" should { + "add" in { + val sum = 1 + 1 + sum mustEqual 2 + } + } + } + SCALA + lambda { define('foo').test.invoke }.should_not raise_error + end + + it 'should fail when spec fails' do + write 'src/spec/scala/StringSpecs.scala', <<-SCALA + import org.specs._ + import org.specs.runner._ + + object StringSpecs extends Specification { + "empty string" should { + "have a zero length" in { + ("".length) mustEqual(1) + } + } + } + SCALA + define('foo') + project('foo').test.invoke rescue + project('foo').test.failed_tests.should include('StringSpecs') + end +end + +describe Buildr::Scala::Specs2 do + + it 'should be the default when tests in src/spec/scala reference "org.specs2"' do + write 'src/spec/scala/com/example/MySpecs.scala', <<-SCALA + package com.example + object MySpecs extends org.specs2.Specification { + "it" should { + "add" in { + val sum = 1 + 1 + sum mustEqual 2 + } + } + } + SCALA + define 'foo' + project('foo').test.framework.should eql(:specs2) + end + + it 'should include Specs2 dependencies' do + define('foo') { test.using(:specs2) } + project('foo').test.compile.dependencies.should include(*artifacts(Scala::Specs2.dependencies)) + project('foo').test.dependencies.should include(*artifacts(Scala::Specs2.dependencies)) + end + + it 'should include ScalaCheck dependencies' do + define('foo') { test.using(:specs2) } + project('foo').test.compile.dependencies.should include(*artifacts(Scala::Check.dependencies)) + project('foo').test.dependencies.should include(*artifacts(Scala::Check.dependencies)) + end + + it 'should include public objects extending org.specs2.mutable.Specification' do + write 'src/spec/scala/com/example/MySpecs.scala', <<-SCALA + package com.example + object MySpecs extends org.specs2.mutable.Specification { + "it" should { + "add" in { + val sum = 1 + 1 + sum mustEqual 2 + } + } + } + SCALA + define('foo').test.framework.should eql(:specs2) + project('foo').test.invoke + project('foo').test.tests.should include('com.example.MySpecs$') + end + + it 'should include classes extending org.specs2.SpecificationWithJUnit' do + write 'src/spec/scala/com/example/MySpecs.scala', <<-SCALA + package com.example + import org.specs2.mutable._ + class MySpecs extends org.specs2.SpecificationWithJUnit { + def is = { + "The 'Hello world' string should" ^ + "contain 11 characters" ! { + "Hello world" must have size(11) + }^ + "start with 'Hello'" ! { + "Hello world" must startWith("Hello") + }^ + "end with 'world'" ! { + "Hello world" must endWith("world") + } + } + } + SCALA + define('foo') + project('foo').test.framework.should eql(:specs2) + project('foo').test.invoke + project('foo').test.tests.should include('com.example.MySpecs') + end + + it 'should pass when spec passes' do + write 'src/spec/scala/PassingSpecs.scala', <<-SCALA + object PassingSpecs extends org.specs2.mutable.Specification { + "it" should { + "add" in { + val sum = 1 + 1 + sum mustEqual 2 + } + } + } + SCALA + lambda { define('foo').test.invoke }.should_not raise_error + end + + it 'should fail when spec fails' do + write 'src/spec/scala/StringSpecs.scala', <<-SCALA + import org.specs2.mutable._ + + object StringSpecs extends Specification { + "empty string" should { + "have a zero length" in { + ("".length) mustEqual(1) + } + } + } + SCALA + define('foo') + project('foo').test.invoke rescue + project('foo').test.failed_tests.should include('StringSpecs$') + end +end + +elsif Buildr::VERSION >= '1.5' + raise "JVM version guard in #{__FILE__} should be removed since it is assumed that Java 1.5 is no longer supported." +end diff --git a/buildr/spec/scala/compiler_spec.rb b/buildr/spec/scala/compiler_spec.rb new file mode 100644 index 0000000..67fbac7 --- /dev/null +++ b/buildr/spec/scala/compiler_spec.rb @@ -0,0 +1,321 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +# need to test both with and without SCALA_HOME +share_as :ScalacCompiler do + + it 'should identify itself from source directories' do + write 'src/main/scala/com/example/Test.scala', 'package com.example; class Test { val i = 1 }' + define('foo').compile.compiler.should eql(:scalac) + end + + it 'should report the language as :scala' do + define('foo').compile.using(:scalac).language.should eql(:scala) + end + + it 'should set the target directory to target/classes' do + define 'foo' do + lambda { compile.using(:scalac) }.should change { compile.target.to_s }.to(File.expand_path('target/classes')) + end + end + + it 'should not override existing target directory' do + define 'foo' do + compile.into('classes') + lambda { compile.using(:scalac) }.should_not change { compile.target } + end + end + + it 'should not change existing list of sources' do + define 'foo' do + compile.from('sources') + lambda { compile.using(:scalac) }.should_not change { compile.sources } + end + end + + it 'should include as classpath dependency' do + write 'src/dependency/Dependency.scala', 'class Dependency {}' + define 'dependency', :version=>'1.0' do + compile.from('src/dependency').into('target/dependency') + package(:jar) + end + write 'src/test/DependencyTest.scala', 'class DependencyTest { var d: Dependency = _ }' + lambda { define('foo').compile.from('src/test').with(project('dependency')).invoke }.should run_task('foo:compile') + file('target/classes/DependencyTest.class').should exist + end + + def define_test1_project + write 'src/main/scala/com/example/Test1.scala', 'package com.example; class Test1 { val i = 1 }' + define 'test1', :version=>'1.0' do + package(:jar) + end + end + + it 'should compile a simple .scala file into a .class file' do + define_test1_project + task('test1:compile').invoke + file('target/classes/com/example/Test1.class').should exist + end + + it 'should package .class into a .jar file' do + define_test1_project + task('test1:package').invoke + file('target/test1-1.0.jar').should exist + Zip::ZipFile.open(project('test1').package(:jar).to_s) do |zip| + zip.file.exist?('com/example/Test1.class').should be_true + end + end + + it 'should compile scala class depending on java class in same project' do + write 'src/main/java/com/example/Foo.java', 'package com.example; public class Foo {}' + write 'src/main/scala/com/example/Bar.scala', 'package com.example; class Bar extends Foo' + define 'test1', :version=>'1.0' do + package(:jar) + end + task('test1:package').invoke + file('target/test1-1.0.jar').should exist + Zip::ZipFile.open(project('test1').package(:jar).to_s) do |zip| + zip.file.exist?('com/example/Foo.class').should be_true + zip.file.exist?('com/example/Bar.class').should be_true + end + end + + it 'should compile java class depending on scala class in same project' do + write 'src/main/scala/com/example/Foo.scala', 'package com.example; class Foo' + write 'src/main/java/com/example/Bar.java', 'package com.example; public class Bar extends Foo {}' + define 'test1', :version=>'1.0' do + package(:jar) + end + task('test1:package').invoke + file('target/test1-1.0.jar').should exist + Zip::ZipFile.open(project('test1').package(:jar).to_s) do |zip| + zip.file.exist?('com/example/Foo.class').should be_true + zip.file.exist?('com/example/Bar.class').should be_true + end + end +end + + +describe 'scala compiler (installed in SCALA_HOME)' do + it 'requires present SCALA_HOME' do + ENV['SCALA_HOME'].should_not be_nil + end + + it_should_behave_like ScalacCompiler +end + + +describe 'scala compiler (downloaded from repository)' do + old_home = ENV['SCALA_HOME'] + + before :all do + ENV['SCALA_HOME'] = nil + end + + it 'requires absent SCALA_HOME' do + ENV['SCALA_HOME'].should be_nil + end + + it_should_behave_like ScalacCompiler + + after :all do + ENV['SCALA_HOME'] = old_home + end +end + +share_as :ScalacCompiler_CommonOptions do + + it 'should set warnings option to false by default' do + compile_task.options.warnings.should be_false + end + + it 'should set warnings option to true when running with --verbose option' do + verbose true + compile_task.options.warnings.should be_true + end + + it 'should use -nowarn argument when warnings is false' do + compile_task.using(:warnings=>false) + scalac_args.should include('-nowarn') + end + + it 'should not use -nowarn argument when warnings is true' do + compile_task.using(:warnings=>true) + scalac_args.should_not include('-nowarn') + end + + it 'should not use -verbose argument by default' do + scalac_args.should_not include('-verbose') + end + + it 'should use -verbose argument when running with --trace=scalac option' do + Buildr.application.options.trace_categories = [:scalac] + scalac_args.should include('-verbose') + end + + it 'should set debug option to true by default' do + compile_task.options.debug.should be_true + end + + it 'should set debug option to false based on Buildr.options' do + Buildr.options.debug = false + compile_task.options.debug.should be_false + end + + it 'should set debug option to false based on debug environment variable' do + ENV['debug'] = 'no' + compile_task.options.debug.should be_false + end + + it 'should set debug option to false based on DEBUG environment variable' do + ENV['DEBUG'] = 'no' + compile_task.options.debug.should be_false + end + + it 'should set deprecation option to false by default' do + compile_task.options.deprecation.should be_false + end + + it 'should use -deprecation argument when deprecation is true' do + compile_task.using(:deprecation=>true) + scalac_args.should include('-deprecation') + end + + it 'should not use -deprecation argument when deprecation is false' do + compile_task.using(:deprecation=>false) + scalac_args.should_not include('-deprecation') + end + + it 'should set optimise option to false by default' do + compile_task.options.optimise.should be_false + end + + it 'should use -optimise argument when deprecation is true' do + compile_task.using(:optimise=>true) + scalac_args.should include('-optimise') + end + + it 'should not use -optimise argument when deprecation is false' do + compile_task.using(:optimise=>false) + scalac_args.should_not include('-optimise') + end + + it 'should not set target option by default' do + compile_task.options.target.should be_nil + scalac_args.should_not include('-target') + end + + it 'should use -target:xxx argument if target option set' do + compile_task.using(:target=>'1.5') + scalac_args.should include('-target:jvm-1.5') + end + + it 'should not set other option by default' do + compile_task.options.other.should be_nil + end + + it 'should pass other argument if other option is string' do + compile_task.using(:other=>'-unchecked') + scalac_args.should include('-unchecked') + end + + it 'should pass other argument if other option is array' do + compile_task.using(:other=>['-unchecked', '-Xprint-types']) + scalac_args.should include('-unchecked', '-Xprint-types') + end + + it 'should complain about options it doesn\'t know' do + write 'source/Test.scala', 'class Test {}' + compile_task.using(:unknown=>'option') + lambda { compile_task.from('source').invoke }.should raise_error(ArgumentError, /no such option/i) + end + + it 'should inherit options from parent' do + define 'foo' do + compile.using(:warnings=>true, :debug=>true, :deprecation=>true, :target=>'1.4') + define 'bar' do + compile.using(:scalac) + compile.options.warnings.should be_true + compile.options.debug.should be_true + compile.options.deprecation.should be_true + compile.options.target.should eql('1.4') + end + end + end + + after do + Buildr.options.debug = nil + ENV.delete "debug" + ENV.delete "DEBUG" + end +end + + +describe 'scala compiler 2.8 options' do + + it_should_behave_like ScalacCompiler_CommonOptions + + def compile_task + @compile_task ||= define('foo').compile.using(:scalac) + end + + def scalac_args + compile_task.instance_eval { @compiler }.send(:scalac_args) + end + + it 'should use -g argument when debug option is true' do + compile_task.using(:debug=>true) + scalac_args.should include('-g') + end + + it 'should not use -g argument when debug option is false' do + compile_task.using(:debug=>false) + scalac_args.should_not include('-g') + end +end if Buildr::Scala.version?(2.8) + +describe 'scala compiler 2.9 options' do + + it_should_behave_like ScalacCompiler_CommonOptions + + def compile_task + @compile_task ||= define('foo').compile.using(:scalac) + end + + def scalac_args + compile_task.instance_eval { @compiler }.send(:scalac_args) + end + + # these specs fail. Somehow the compiler is still in version 2.8 + it 'should use -g:vars argument when debug option is true' do + compile_task.using(:debug=>true) + scalac_args.should include('-g:vars') + end + + it 'should use -g:whatever argument when debug option is \'whatever\'' do + compile_task.using(:debug=>:whatever) + scalac_args.should include('-g:whatever') + end + + it 'should not use -g argument when debug option is false' do + compile_task.using(:debug=>false) + scalac_args.should_not include('-g') + end + +end if Buildr::Scala.version?(2.9) + diff --git a/buildr/spec/scala/doc_spec.rb b/buildr/spec/scala/doc_spec.rb new file mode 100644 index 0000000..91705c8 --- /dev/null +++ b/buildr/spec/scala/doc_spec.rb @@ -0,0 +1,83 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe "Scaladoc" do + + it 'should pick -doc-title from project name by default' do + define 'foo' do + compile.using(:scalac) + + define 'bar' do + compile.using(:scalac) + end + end + + project('foo').doc.options[:"doc-title"].should eql('foo') + project('foo:bar').doc.options[:"doc-title"].should eql('foo:bar') + end + + it 'should pick -doc-title from project description by default, if available' do + desc 'My App' + define 'foo' do + compile.using(:scalac) + end + project('foo').doc.options[:"doc-title"].should eql('My App') + end + + it 'should not override explicit "doc-title" option' do + define 'foo' do + compile.using(:scalac) + doc.using "doc-title" => 'explicit' + end + project('foo').doc.options[:"doc-title"].should eql('explicit') + end + + it 'should convert :windowtitle to -doc-title for Scala 2.8.1 and later' do + write 'src/main/scala/com/example/Test.scala', 'package com.example; class Test { val i = 1 }' + define('foo') do + doc.using :windowtitle => "foo" + end + Java.scala.tools.nsc.ScalaDoc.should_receive(:process) do |args| + # Convert Java Strings to Ruby Strings, if needed. + args.map { |a| a.is_a?(String) ? a : a.toString }.should include("-doc-title") + 0 # normal return + end + project('foo').doc.invoke + end unless Buildr::Scala.version?(2.7, "2.8.0") +end + +describe "package(:scaladoc)" do + it "should generate target/project-version-scaladoc.jar" do + write 'src/main/scala/Foo.scala', 'class Foo' + define 'foo', :version=>'1.0' do + package(:scaladoc) + end + + scaladoc = project('foo').package(:scaladoc) + scaladoc.should point_to_path('target/foo-1.0-scaladoc.jar') + + lambda { + project('foo').task('package').invoke + }.should change { File.exist?('target/foo-1.0-scaladoc.jar') }.to(true) + + scaladoc.should exist + scaladoc.should contain('index.html') + scaladoc.should contain('Foo.html') + end +end + diff --git a/buildr/spec/scala/scala.rb b/buildr/spec/scala/scala.rb new file mode 100644 index 0000000..9e3e115 --- /dev/null +++ b/buildr/spec/scala/scala.rb @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +describe 'scala' do + + it 'should automatically add the remote scala-tools.org repository' do + # NOTE: the sandbox environment clears "repositories.remote" so we can't + # test for this spec right now. + # + # repositories.remote.should include('http://scala-tools.org/repo-releases') + end + + it "should provide the Scala version string" do + Scala.version_str.should eql(Buildr::Scala::SCALA_VERSION_FOR_SPECS) + end +end diff --git a/buildr/spec/scala/tests_spec.rb b/buildr/spec/scala/tests_spec.rb new file mode 100644 index 0000000..82e088b --- /dev/null +++ b/buildr/spec/scala/tests_spec.rb @@ -0,0 +1,295 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) + +if Java.java.lang.System.getProperty("java.runtime.version") >= "1.6" + +# TODO's +# -test passing System props +# -test passing ENV variables +# -test exclude group +# -test include Suite's +# -test exclude Suite's +# +describe "scalatest version" do + + case + when Buildr::Scala.version?(2.8) + it 'should be 1.3 for scala 2.8' do + Scala::ScalaTest.dependencies.should include("org.scalatest:scalatest:jar:1.3") + end + when Buildr::Scala.version?(2.9) + it 'should be 1.6.1 for scala 2.9' do + Scala::ScalaTest.dependencies.should include("org.scalatest:scalatest_2.9.0:jar:1.6.1") + end + end +end + +describe Buildr::Scala::ScalaTest do + + it 'should be the default test framework when test cases are in Scala' do + write 'src/test/scala/com/example/MySuite.scala', <<-SCALA + package com.example + import org.scalatest.FunSuite + class MySuite extends FunSuite { + test("addition") { + val sum = 1 + 1 + assert(sum === 2) + } + } + SCALA + define 'foo' + project('foo').test.framework.should eql(:scalatest) + end + + it 'should include Scalatest dependencies' do + define('foo') { test.using(:scalatest) } + project('foo').test.compile.dependencies.should include(*artifacts(Scala::ScalaTest.dependencies)) + project('foo').test.dependencies.should include(*artifacts(Scala::ScalaTest.dependencies)) + end + + it 'should include JMock dependencies' do + define('foo') { test.using(:scalatest) } + project('foo').test.compile.dependencies.should include(*artifacts(JMock.dependencies)) + project('foo').test.dependencies.should include(*artifacts(JMock.dependencies)) + end + + it 'should include ScalaCheck dependencies' do + define('foo') { test.using(:scalatest) } + project('foo').test.compile.dependencies.should include(*artifacts(Scala::Check.dependencies)) + project('foo').test.dependencies.should include(*artifacts(Scala::Check.dependencies)) + end + + it 'should set current directory' do + mkpath 'baz' + expected = File.expand_path('baz') + expected.gsub!('/', '\\') if expected =~ /^[A-Z]:/ # Java returns back slashed paths for windows + write 'baz/src/test/scala/CurrentDirectoryTestSuite.scala', <<-SCALA + class CurrentDirectoryTestSuite extends org.scalatest.FunSuite { + test("testCurrentDirectory") { + assert("value" === System.getenv("NAME")) + assert(#{expected.inspect} === new java.io.File(".").getCanonicalPath()) + } + } + SCALA + define 'bar' do + define 'baz' do + test.include 'CurrentDirectoryTest' + end + end + project('bar:baz').test.invoke + end + + it 'should include public classes extending org.scalatest.FunSuite' do + write 'src/test/scala/com/example/MySuite.scala', <<-SCALA + package com.example + import org.scalatest.FunSuite + class MySuite extends FunSuite { + test("addition") { + val sum = 1 + 1 + assert(sum === 2) + } + } + SCALA + define('foo').test.invoke + project('foo').test.tests.should include('com.example.MySuite') + end + + it 'should ignore classes not extending org.scalatest.FunSuite' do + write 'src/test/scala/com/example/NotASuite.scala', <<-SCALA + package com.example + class Another { + } + SCALA + define('foo').test.invoke + project('foo').test.tests.should be_empty + end + + it 'should ignore inner classes' do + write 'src/test/scala/com/example/InnerClassTest.scala', <<-SCALA + package com.example + import org.scalatest.FunSuite + class InnerClassTest extends FunSuite { + test("addition") { + val sum = 1 + 1 + assert(sum === 2) + } + + class InnerSuite extends FunSuite { + test("addition") { + val sum = 1 + 1 + assert(sum === 2) + } + } + } + SCALA + define('foo').test.invoke + project('foo').test.tests.should eql(['com.example.InnerClassTest']) + end + + it 'should pass when ScalaTest test case passes' do + write 'src/test/scala/PassingSuite.scala', <<-SCALA + class PassingSuite extends org.scalatest.FunSuite { + test("addition") { + val sum = 1 + 1 + assert(sum === 2) + } + } + SCALA + lambda { define('foo').test.invoke }.should_not raise_error + end + + it 'should fail when ScalaTest test case fails' do + write 'src/test/scala/FailingSuite.scala', <<-SCALA + class FailingSuite extends org.scalatest.FunSuite { + test("failing") { + assert(false) + } + } + SCALA + lambda { define('foo').test.invoke }.should raise_error(RuntimeError, /Tests failed/) rescue nil + end + + it 'should report failed test names' do + write 'src/test/scala/FailingSuite.scala', <<-SCALA + class FailingSuite extends org.scalatest.FunSuite { + test("failing") { + assert(false) + } + } + SCALA + define('foo').test.invoke rescue + project('foo').test.failed_tests.should include('FailingSuite') + end + + it 'should report to reports/scalatest/TEST-TestSuiteName.xml' do + write 'src/test/scala/PassingSuite.scala', <<-SCALA + class PassingSuite extends org.scalatest.FunSuite { + test("passing") { + assert(true) + } + } + SCALA + define 'foo' do + test.report_to.should be(file('reports/scalatest')) + end + project('foo').test.invoke + project('foo').file('reports/scalatest/TEST-PassingSuite.xml').should exist + end + + it 'should report to reports/scalatest/TEST-TestSuiteName.txt' do + write 'src/test/scala/PassingSuite.scala', <<-SCALA + class PassingSuite extends org.scalatest.FunSuite { + test("passing") { + assert(true) + } + } + SCALA + define 'foo' do + test.report_to.should be(file('reports/scalatest')) + end + project('foo').test.invoke + project('foo').file('reports/scalatest/TEST-PassingSuite.txt').should exist + end + + it 'should pass properties to Suite' do + write 'src/test/scala/PropertyTestSuite.scala', <<-SCALA + import org.scalatest._ + class PropertyTestSuite extends FunSuite { + var configMap = Map[String, Any]() + test("testProperty") { + assert(configMap("name") === "value") + } + + protected override def runTests(testName: Option[String], reporter: Reporter, stopper: Stopper, + filter: Filter, configMap: Map[String, Any], + distributor: Option[Distributor], tracker: Tracker) { + this.configMap = configMap + super.runTests(testName, reporter, stopper, filter, configMap, distributor, tracker) + } + } + SCALA + define('foo').test.using :properties=>{ 'name'=>'value' } + project('foo').test.invoke + end + + it 'should run with ScalaCheck automatic test case generation' do + write 'src/test/scala/MySuite.scala', <<-SCALA + import org.scalatest.FunSuite + import org.scalatest.prop.Checkers + import org.scalacheck.Arbitrary._ + import org.scalacheck.Prop._ + + class MySuite extends FunSuite with Checkers { + + test("list concatenation") { + val x = List(1, 2, 3) + val y = List(4, 5, 6) + assert(x ::: y === List(1, 2, 3, 4, 5, 6)) + check((a: List[Int], b: List[Int]) => a.size + b.size == (a ::: b).size) + } + + test("list concatenation using a test method") { + check((a: List[Int], b: List[Int]) => a.size + b.size == (a ::: b).size) + } + } + SCALA + define('foo') + project('foo').test.invoke + project('foo').test.passed_tests.should include('MySuite') + end + + it 'should fail if ScalaCheck test case fails' do + write 'src/test/scala/StringSuite.scala', <<-SCALA + import org.scalatest.FunSuite + import org.scalatest.prop.Checkers + import org.scalacheck.Arbitrary._ + import org.scalacheck.Prop._ + + class StringSuite extends FunSuite with Checkers { + test("startsWith") { + check( (a: String, b: String) => (a+b).startsWith(a) ) + } + + test("endsWith") { + check( (a: String, b: String) => (a+b).endsWith(b) ) + } + + // Is this really always true? + test("concat") { + check( (a: String, b: String) => (a+b).length > a.length && (a+b).length > b.length ) + } + + test("substring2") { + check( (a: String, b: String) => (a+b).substring(a.length) == b ) + } + + test("substring3") { + check( (a: String, b: String, c: String) => + (a+b+c).substring(a.length, a.length+b.length) == b ) + } + } + SCALA + define('foo') + project('foo').test.invoke rescue + project('foo').test.failed_tests.should include('StringSuite') + end + +end + +elsif Buildr::VERSION >= '1.5' + raise "JVM version guard in #{__FILE__} should be removed since it is assumed that Java 1.5 is no longer supported." +end diff --git a/buildr/spec/spec_helpers.rb b/buildr/spec/spec_helpers.rb new file mode 100644 index 0000000..f8b9429 --- /dev/null +++ b/buildr/spec/spec_helpers.rb @@ -0,0 +1,369 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +# This file gets loaded twice when running 'spec spec/*' and not with pleasant results, +# so ignore the second attempt to load it. +unless defined?(SpecHelpers) + + require 'rubygems' + + # For testing we use the gem requirements specified on the buildr.gemspec + spec = Gem::Specification.load(File.expand_path('../buildr.gemspec', File.dirname(__FILE__))) + # Dependency.version_requirements deprecated in rubygems 1.3.6 + spec.dependencies.select {|dep| dep.type == :runtime }.each { |dep| gem dep.name, (dep.respond_to?(:requirement) ? dep.requirement.to_s : dep.version_requirements.to_s) } + + # Make sure to load from these paths first, we don't want to load any + # code from Gem library. + $LOAD_PATH.unshift File.expand_path('../lib', File.dirname(__FILE__)), + File.expand_path('../addon', File.dirname(__FILE__)) + + # Buildr uses autoload extensively, but autoload when running specs creates + # a problem -- we sandbox $LOADED_FEATURES, so we end up autoloading the same + # module twice. This turns autoload into a require, which is not the right + # thing, but will do for now. + def autoload(symbol, path) + require path + end + require 'buildr' + # load ecj + require 'buildr/java/ecj' + #Make ecj appear as a compiler that doesn't apply: + class Buildr::Compiler::Ecj + class << self + def applies_to?(project, task) + false + end + end + end + + # Give a chance for plugins to do a few things before requiring the sandbox. + include SandboxHook if defined?(SandboxHook) + + require File.expand_path('sandbox', File.dirname(__FILE__)) + + module SpecHelpers + + include Checks::Matchers + + [:info, :warn, :error, :puts].each do |severity| + ::Object.class_eval do + define_method severity do |*args| + $messages ||= {} + $messages[severity] ||= [] + $messages[severity].push(*args) + end + end + end + + class << Buildr.application + alias :deprecated_without_capture :deprecated + def deprecated(message) + verbose(true) { deprecated_without_capture message } + end + end + + class MessageWithSeverityMatcher + def initialize(severity, message) + @severity = severity + @expect = message + end + + def matches?(target) + $messages = {@severity => []} + target.call + return Regexp === @expect ? $messages[@severity].join('\n') =~ @expect : $messages[@severity].include?(@expect.to_s) + end + + def failure_message + "Expected #{@severity} #{@expect.inspect}, " + + ($messages[@severity].empty? ? "no #{@severity} issued" : "found #{$messages[@severity].inspect}") + end + + def negative_failure_message + "Found unexpected #{$messages[@severity].inspect}" + end + end + + # Test if an info message was shown. You can use a string or regular expression. + # + # For example: + # lambda { info 'ze test' }.should show_info(/ze test/) + def show_info(message) + MessageWithSeverityMatcher.new :info, message + end + + # Test if a warning was shown. You can use a string or regular expression. + # + # For example: + # lambda { warn 'ze test' }.should show_warning(/ze test/) + def show_warning(message) + MessageWithSeverityMatcher.new :warn, message + end + + # Test if an error message was shown. You can use a string or regular expression. + # + # For example: + # lambda { error 'ze test' }.should show_error(/ze test/) + def show_error(message) + MessageWithSeverityMatcher.new :error, message + end + + # Test if any message was shown (puts). You can use a string or regular expression. + # + # For example: + # lambda { puts 'ze test' }.should show(/ze test/) + def show(message) + MessageWithSeverityMatcher.new :puts, message + end + + # Yields a block that should try exiting the application. + # Accepts + # + # For example: + # test_exit(1) { puts "Hello" ; exit(1) }.should show("Hello") + # + def test_exit(status = nil) + return lambda { + begin + yield + raise "Exit was not called!" + rescue SystemExit => e + raise "Exit status incorrect! Expected: #{status}, got #{e.status}" if status && (e.status != status) + end + } + end + + class ::Rake::Task + alias :execute_without_a_record :execute + def execute(args) + $executed ||= [] + $executed << name + execute_without_a_record args + end + end + + class InvokeMatcher + def initialize(*tasks) + @expecting = tasks.map { |task| [task].flatten.map(&:to_s) } + end + + def matches?(target) + $executed = [] + target.call + return false unless all_ran? + return !@but_not.any_ran? if @but_not + return true + end + + def failure_message + return @but_not.negative_failure_message if all_ran? && @but_not + "Expected the tasks #{expected} to run, but #{remaining} did not run, or not in the order we expected them to." + + " Tasks that ran: #{$executed.inspect}" + end + + def negative_failure_message + if all_ran? + "Expected the tasks #{expected} to not run, but they all ran." + else + "Expected the tasks #{expected} to not run, and all but #{remaining} ran." + end + end + + def but_not(*tasks) + @but_not = InvokeMatcher.new(*tasks) + self + end + + protected + + def expected + @expecting.map { |tests| tests.join('=>') }.join(', ') + end + + def remaining + @remaining.map { |tests| tests.join('=>') }.join(', ') + end + + def all_ran? + @remaining ||= $executed.inject(@expecting) do |expecting, executed| + expecting.map { |tasks| tasks.first == executed ? tasks[1..-1] : tasks }.reject(&:empty?) + end + @remaining.empty? + end + + def any_ran? + all_ran? + @remaining.size < @expecting.size + end + + end + + # Tests that all the tasks ran, in the order specified. Can also be used to test that some + # tasks and not others ran. + # + # Takes a list of arguments. Each argument can be a task name, matching only if that task ran. + # Each argument can be an array of task names, matching only if all these tasks ran in that order. + # So run_tasks('foo', 'bar') expects foo and bar to run in any order, but run_task(['foo', 'bar']) + # expects foo to run before bar. + # + # You can call but_not on the matchers to specify that certain tasks must not execute. + # + # For example: + # # Either task + # lambda { task('compile').invoke }.should run_tasks('compile', 'resources') + # # In that order + # lambda { task('build').invoke }.should run_tasks(['compile', 'test']) + # # With exclusion + # lambda { task('build').invoke }.should run_tasks('compile').but_not('install') + def run_tasks(*tasks) + InvokeMatcher.new *tasks + end + + # Tests that a task ran. Similar to run_tasks, but accepts a single task name. + # + # For example: + # lambda { task('build').invoke }.should run_task('test') + def run_task(task) + InvokeMatcher.new [task] + end + + class UriPathMatcher + def initialize(re) + @expression = re + end + + def matches?(uri) + @uri = uri + uri.path =~ @expression + end + + def description + "URI with path matching #{@expression}" + end + end + + # Matches a parsed URI's path against the given regular expression + def uri(re) + UriPathMatcher.new(re) + end + + + class AbsolutePathMatcher + def initialize(path) + @expected = File.expand_path(path.to_s) + end + + def matches?(path) + @provided = File.expand_path(path.to_s) + @provided == @expected + end + + def failure_message + "Expected path #{@expected}, but found path #{@provided}" + end + + def negative_failure_message + "Expected a path other than #{@expected}" + end + end + + def point_to_path(path) + AbsolutePathMatcher.new(path) + end + + + # Value covered by range. For example: + # (1..5).should cover(3) + RSpec::Matchers.define :cover do |actual| + match do |range| + actual >= range.min && actual <= range.max + end + end + + + def suppress_stdout + stdout = $stdout + $stdout = StringIO.new + begin + yield + ensure + $stdout = stdout + end + end + + def dryrun + Buildr.application.options.dryrun = true + begin + suppress_stdout { yield } + ensure + Buildr.application.options.dryrun = false + end + end + + # We run tests with tracing off. Then things break. And we need to figure out what went wrong. + # So just use trace() as you would use verbose() to find and squash the bug. + def trace(value = nil) + old_value = Buildr.application.options.trace + Buildr.application.options.trace = value unless value.nil? + if block_given? + begin + yield + ensure + Buildr.application.options.trace = old_value + end + end + Buildr.application.options.trace + end + + # Change the Buildr original directory, faking invocation from a different directory. + def in_original_dir(dir) + begin + original_dir = Buildr.application.original_dir + Buildr.application.instance_eval { @original_dir = File.expand_path(dir) } + yield + ensure + Buildr.application.instance_eval { @original_dir = original_dir } + end + end + + + # Buildr's define method creates a project definition but does not evaluate it + # (that happens once the buildfile is loaded), and we include Buildr's define in + # the test context so we can use it without prefixing with Buildr. This just patches + # define to evaluate the project definition before returning it. + def define(name, properties = nil, &block) #:yields:project + Project.define(name, properties, &block).tap { |project| project.invoke } + end + + end + + + # Allow using matchers within the project definition. + class Buildr::Project + include ::RSpec::Matchers, SpecHelpers + end + + + ::RSpec.configure do |config| + # Make all Buildr methods accessible from test cases, and add various helper methods. + config.include Buildr + config.include SpecHelpers + + # Sandbox Buildr for each test. + config.include Sandbox + end + +end diff --git a/buildr/spec/version_requirement_spec.rb b/buildr/spec/version_requirement_spec.rb new file mode 100644 index 0000000..f3ed736 --- /dev/null +++ b/buildr/spec/version_requirement_spec.rb @@ -0,0 +1,145 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + + +require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helpers')) + +describe Buildr::VersionRequirement, '.create' do + def create(str) + Buildr::VersionRequirement.create(str) + end + + it 'should complain on invalid input' do + lambda { create }.should raise_error(Exception) + lambda { create('%') }.should raise_error(Exception, /invalid character/) + lambda { create('1#{0}') }.should raise_error(Exception, /invalid character/) + lambda { create('1.0rc`exit`') }.should raise_error(Exception, /invalid character/) + lambda { create(1.0) }.should raise_error(Exception) + lambda { create('1.0') }.should_not raise_error(Exception) + lambda { create('1.0rc3') }.should_not raise_error(Exception) + end + + it 'should allow versions using hyphen' do + lambda { create('1.0-rc3') }.should_not raise_error(Exception) + end + + it 'should create a single version requirement' do + create('1.0').should_not be_composed + end + + it 'should create a composed version requirement' do + create('1.0 | 2.1').should be_composed + end +end + +=begin +# TODO: Fix this. +# 1. Can't use should_satisfy, this breaks under RSpec 1.2 +# 2. These should_satisfy calls are not proper specs since the subject is +# the satistifed_by? method. satisfied_by should satisfy??? +describe Buildr::VersionRequirement, '#satisfied_by?' do + def should_satisfy(str, valids = [], invalids = []) + req = Buildr::VersionRequirement.create(str) + valids.each { |v| req.should be_satisfied_by(v) } + invalids.each { |v| req.should_not be_satisfied_by(v) } + end + + it 'should accept Gem version operators' do + should_satisfy '1.0', %w(1 1.0), %w(1.1 0.1) + should_satisfy '=1.0', %w(1 1.0), %w(1.1 0.1) + should_satisfy '= 1.0', %w(1 1.0), %w(1.1 0.1) + should_satisfy '!= 1.0', %w(0.9 1.1 2), %w(1 1.0 1.0.0) + + should_satisfy '>1.0', %w(1.0.1), %w(1 1.0 0.1) + should_satisfy '>=1.0', %w(1.0.1 1 1.0), %w(0.9) + + should_satisfy '<1.0', %w(0.9 0.9.9), %w(1 1.0 1.1 2) + should_satisfy '<=1.0', %w(0.9 0.9.9 1 1.0), %w(1.1 2) + + should_satisfy '~> 1.2.3', %w(1.2.3 1.2.3.4 1.2.4), %w(1.2.1 0.9 1.4 2) + end + + it 'should accept logic not operator' do + should_satisfy 'not 0.5', %w(0 1), %w(0.5) + should_satisfy '! 0.5', %w(0 1), %w(0.5) + should_satisfy '!= 0.5', %w(0 1), %w(0.5) + should_satisfy '!<= 0.5', %w(0.5.1 2), %w(0.5) + end + + it 'should accept logic or operator' do + should_satisfy '0.5 or 2.0', %w(0.5 2.0), %w(1.0 0.5.1 2.0.9) + should_satisfy '0.5 | 2.0', %w(0.5 2.0), %w(1.0 0.5.1 2.0.9) + end + + it 'should accept logic and operator' do + should_satisfy '>1.5 and <2.0', %w(1.6 1.9), %w(1.5 2 2.0) + should_satisfy '>1.5 & <2.0', %w(1.6 1.9), %w(1.5 2 2.0) + end + + it 'should assume logic and if missing operator between expressions' do + should_satisfy '>1.5 <2.0', %w(1.6 1.9), %w(1.5 2 2.0) + end + + it 'should allow combining logic operators' do + should_satisfy '>1.0 | <2.0 | =3.0', %w(1.5 3.0 1 2 4) + should_satisfy '>1.0 & <2.0 | =3.0', %w(1.3 3.0), %w(1 2) + should_satisfy '=1.0 | <2.0 & =0.5', %w(0.5 1.0), %w(1.1 0.1 2) + should_satisfy '~>1.1 | ~>1.3 | ~>1.5 | 2.0', %w(2 1.5.6 1.1.2 1.1.3), %w(1.0.9 0.5 2.2.1) + should_satisfy 'not(2) | 1', %w(1 3), %w(2) + end + + it 'should allow using parens to group logic expressions' do + should_satisfy '(1.0)', %w(1 1.0), %w(0.9 1.1) + should_satisfy '!( !(1.0) )', %w(1 1.0), %w(0.9 1.1) + should_satisfy '1 | !(2 | 3)', %w(1), %w(2 3) + should_satisfy '!(2 | 3) | 1', %w(1), %w(2 3) + end +end +=end + +describe Buildr::VersionRequirement, '#default' do + it 'should return nil if missing default requirement' do + Buildr::VersionRequirement.create('>1').default.should be_nil + Buildr::VersionRequirement.create('<1').default.should be_nil + Buildr::VersionRequirement.create('!1').default.should be_nil + Buildr::VersionRequirement.create('!<=1').default.should be_nil + end + + it 'should return the last version with a = requirement' do + Buildr::VersionRequirement.create('1').default.should == '1' + Buildr::VersionRequirement.create('=1').default.should == '1' + Buildr::VersionRequirement.create('<=1').default.should == '1' + Buildr::VersionRequirement.create('>=1').default.should == '1' + Buildr::VersionRequirement.create('1 | 2 | 3').default.should == '3' + Buildr::VersionRequirement.create('1 2 | 3').default.should == '3' + Buildr::VersionRequirement.create('1 & 2 | 3').default.should == '3' + end +end + +describe Buildr::VersionRequirement, '#version?' do + it 'should identify valid versions' do + Buildr::VersionRequirement.version?('1').should be_true + Buildr::VersionRequirement.version?('1a').should be_true + Buildr::VersionRequirement.version?('1.0').should be_true + Buildr::VersionRequirement.version?('11.0').should be_true + Buildr::VersionRequirement.version?(' 11.0 ').should be_true + Buildr::VersionRequirement.version?('11.0-alpha').should be_true + Buildr::VersionRequirement.version?('r09').should be_true # BUILDR-615: com.google.guava:guava:jar:r09 + + Buildr::VersionRequirement.version?('a').should be_false + Buildr::VersionRequirement.version?('a1').should be_false + Buildr::VersionRequirement.version?('r').should be_false + end +end diff --git a/buildr/spec/xpath_matchers.rb b/buildr/spec/xpath_matchers.rb new file mode 100644 index 0000000..88c53ed --- /dev/null +++ b/buildr/spec/xpath_matchers.rb @@ -0,0 +1,121 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +require 'rexml/document' +require 'rexml/element' + +module RSpec + module Matchers + + # check if the xpath exists one or more times + class HaveXpath + def initialize(xpath) + @xpath = xpath + end + + def matches?(response) + @response = response + doc = response.is_a?(REXML::Document) ? response : REXML::Document.new(@response) + match = REXML::XPath.match(doc, @xpath) + not match.empty? + end + + def failure_message + "Did not find expected xpath #{@xpath}" + end + + def negative_failure_message + "Did find unexpected xpath #{@xpath}" + end + + def description + "match the xpath expression #{@xpath}" + end + end + + def have_xpath(xpath) + HaveXpath.new(xpath) + end + + # check if the xpath has the specified value + # value is a string and there must be a single result to match its + # equality against + class MatchXpath + def initialize(xpath, val) + @xpath = xpath + @val= val + end + + def matches?(response) + @response = response + doc = response.is_a?(REXML::Document) ? response : REXML::Document.new(@response) + ok = true + REXML::XPath.each(doc, @xpath) do |e| + @actual_val = case e + when REXML::Attribute + e.to_s + when REXML::Element + e.text + else + e.to_s + end + return false unless @val == @actual_val + end + return ok + end + + def failure_message + "The xpath #{@xpath} did not have the value '#{@val}' It was '#{@actual_val}'" + end + + def description + "match the xpath expression #{@xpath} with #{@val}" + end + end + + def match_xpath(xpath, val) + MatchXpath.new(xpath, val) + end + + # checks if the given xpath occurs num times + class HaveNodes #:nodoc: + def initialize(xpath, num) + @xpath= xpath + @num = num + end + + def matches?(response) + @response = response + doc = response.is_a?(REXML::Document) ? response : REXML::Document.new(@response) + match = REXML::XPath.match(doc, @xpath) + @num_found= match.size + @num_found == @num + end + + def failure_message + "Did not find expected number of nodes #{@num} in xpath #{@xpath} Found #{@num_found}" + end + + def description + "match the number of nodes #{@num}" + end + end + + def have_nodes(xpath, num) + HaveNodes.new(xpath, num) + end + + end +end \ No newline at end of file diff --git a/buildr/tests/BUILDR-320/Buildfile b/buildr/tests/BUILDR-320/Buildfile new file mode 100644 index 0000000..60a60e6 --- /dev/null +++ b/buildr/tests/BUILDR-320/Buildfile @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +define "whole" do + define 'sub' do + define 'C' do + compile.with project('sub:B') + end + + define 'A' do + end + + define 'B' do + compile.with project('sub:A') + end + end +end \ No newline at end of file diff --git a/buildr/tests/JavaSystemProperty/Buildfile b/buildr/tests/JavaSystemProperty/Buildfile new file mode 100644 index 0000000..6fbfed5 --- /dev/null +++ b/buildr/tests/JavaSystemProperty/Buildfile @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +define "proj" do + ::Java.java.lang.System.setProperty("foo", "bar") +end \ No newline at end of file diff --git a/buildr/tests/JavaSystemProperty/src/test/java/FooTest.java b/buildr/tests/JavaSystemProperty/src/test/java/FooTest.java new file mode 100644 index 0000000..4b4324b --- /dev/null +++ b/buildr/tests/JavaSystemProperty/src/test/java/FooTest.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + +import org.junit.Test; + +public class FooTest { + + @Test + public void testSomething() {} +} \ No newline at end of file diff --git a/buildr/tests/compile_with_parent/Buildfile b/buildr/tests/compile_with_parent/Buildfile new file mode 100644 index 0000000..db3814f --- /dev/null +++ b/buildr/tests/compile_with_parent/Buildfile @@ -0,0 +1,18 @@ + +repositories.remote << "http://repo1.maven.org/maven2" + +LOG4J = artifact("log4j:log4j:jar:1.2.16") +unless File.exist? File.join("lib", File.basename(LOG4J.to_s)) + LOG4J.invoke + cp LOG4J.to_s, "lib" +end + +define "parent" do + + define "child" do + + compile.with project.parent.path_to(File.join("lib", File.basename(LOG4J.to_s))) + + end +end + \ No newline at end of file diff --git a/buildr/tests/compile_with_parent/child/src/main/java/Foo.java b/buildr/tests/compile_with_parent/child/src/main/java/Foo.java new file mode 100644 index 0000000..e85da18 --- /dev/null +++ b/buildr/tests/compile_with_parent/child/src/main/java/Foo.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + +import org.apache.log4j.Logger; + +public class Foo { + + private static final Logger _logger = Logger.getLogger(Foo.class); +} \ No newline at end of file diff --git a/buildr/tests/helloWorld/Buildfile b/buildr/tests/helloWorld/Buildfile new file mode 100644 index 0000000..8a26a34 --- /dev/null +++ b/buildr/tests/helloWorld/Buildfile @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +define "helloWorld", :version => "1.0", :group => "org.buildr" do + + package(:jar) + +end \ No newline at end of file diff --git a/buildr/tests/helloWorld/src/main/java/HelloWorld.java b/buildr/tests/helloWorld/src/main/java/HelloWorld.java new file mode 100644 index 0000000..a819dd0 --- /dev/null +++ b/buildr/tests/helloWorld/src/main/java/HelloWorld.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + +public class HelloWorld { + + public void foo() { + System.err.println("bar"); + } +} \ No newline at end of file diff --git a/buildr/tests/include_as/Buildfile b/buildr/tests/include_as/Buildfile new file mode 100644 index 0000000..6a43272 --- /dev/null +++ b/buildr/tests/include_as/Buildfile @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +repositories.remote = ["http://repo1.maven.org/maven2"] +SLF4J_API = "org.slf4j:slf4j-api:jar:1.6.1" + +define "proj" do + project.version = "1.0" + project.group = "org.example" + package(:zip).include(_("doc"), :as => "docu") + package(:zip).include(artifact(SLF4J_API), :as => "lib/logging.jar") +end \ No newline at end of file diff --git a/buildr/tests/include_as/doc/index.html b/buildr/tests/include_as/doc/index.html new file mode 100644 index 0000000..f8a3e0b --- /dev/null +++ b/buildr/tests/include_as/doc/index.html @@ -0,0 +1,8 @@ + + + Index + + + Hello world + + \ No newline at end of file diff --git a/buildr/tests/include_path/Buildfile b/buildr/tests/include_path/Buildfile new file mode 100644 index 0000000..98ebe52 --- /dev/null +++ b/buildr/tests/include_path/Buildfile @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +repositories.remote = ["http://repo1.maven.org/maven2"] +SLF4J_API = "org.slf4j:slf4j-api:jar:1.6.1" + +define "proj" do + project.version = "1.0" + project.group = "org.example" + package(:zip).include(_("doc"), :path => "distrib") + package(:zip).include(artifact(SLF4J_API), :path => "distrib/lib") +end \ No newline at end of file diff --git a/buildr/tests/include_path/doc/index.html b/buildr/tests/include_path/doc/index.html new file mode 100644 index 0000000..f8a3e0b --- /dev/null +++ b/buildr/tests/include_path/doc/index.html @@ -0,0 +1,8 @@ + + + Index + + + Hello world + + \ No newline at end of file diff --git a/buildr/tests/integration_testing.rb b/buildr/tests/integration_testing.rb new file mode 100644 index 0000000..4214ece --- /dev/null +++ b/buildr/tests/integration_testing.rb @@ -0,0 +1,81 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you 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. + +BUILDR = ENV['BUILDR'] || File.expand_path("../_buildr", File.dirname(__FILE__)) + +require 'test/unit' +require 'zip/zip' + +module Buildr + module IntegrationTests + + def self.test(folder, cmd, after_block = nil) + + eval <<-TEST + class #{folder.sub("-", "").capitalize} < Test::Unit::TestCase + + def test_#{folder.sub("-", "")} + begin + result = %x[cd #{File.expand_path("#{folder}", File.dirname(__FILE__))} ; #{BUILDR} #{cmd}] + assert($?.success?) + #{ after_block || "" } + ensure + %x[cd #{File.expand_path("#{folder}", File.dirname(__FILE__))} ; #{BUILDR} clean] + end + + end + + end + TEST + + end + + #BUILDR-320 still not resolved. + #test "BUILDR-320", "--trace -P" + + test "JavaSystemProperty", "test" + + test "helloWorld", "package" + + test "compile_with_parent", "compile" + + test "junit3", "test" + + test "include_path", "package", <<-CHECK +path = File.expand_path("include_path/target/proj-1.0.zip", File.dirname(__FILE__)) +assert(File.exist? path) +Zip::ZipFile.open(path) {|zip| +assert(!zip.get_entry("distrib/doc/index.html").nil?) +assert(!zip.get_entry("distrib/lib/slf4j-api-1.6.1.jar").nil?) +} + CHECK + + test "include_as", "package", <<-CHECK +path = File.expand_path("include_as/target/proj-1.0.zip", File.dirname(__FILE__)) +assert(File.exist? path) +Zip::ZipFile.open(path) {|zip| +assert(!zip.get_entry("docu/index.html").nil?) +assert(!zip.get_entry("lib/logging.jar").nil?) +} + CHECK + + test "package_war_as_jar", "package", <<-CHECK + assert(File.exist? File.join(File.expand_path(File.dirname(__FILE__)), "package_war_as_jar", "target", "webapp-1.0.jar")) + %x[cd #{File.expand_path("package_war_as_jar", File.dirname(__FILE__))} ; #{BUILDR} clean] + assert($?.success?) + CHECK + + end +end \ No newline at end of file diff --git a/buildr/tests/junit3/Buildfile b/buildr/tests/junit3/Buildfile new file mode 100644 index 0000000..a9a40ad --- /dev/null +++ b/buildr/tests/junit3/Buildfile @@ -0,0 +1,9 @@ + +repositories.remote << "http://repo1.maven.org/maven2" +Buildr.settings.build['junit'] = '3.8.1' + +define "foo" do + compile + test + +end \ No newline at end of file diff --git a/buildr/tests/junit3/src/main/java/Foo.java b/buildr/tests/junit3/src/main/java/Foo.java new file mode 100644 index 0000000..3044b2a --- /dev/null +++ b/buildr/tests/junit3/src/main/java/Foo.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + +public class Foo { + + public String bar() { + return "bar"; + } +} \ No newline at end of file diff --git a/buildr/tests/junit3/src/test/java/FooTest.java b/buildr/tests/junit3/src/test/java/FooTest.java new file mode 100644 index 0000000..a3341be --- /dev/null +++ b/buildr/tests/junit3/src/test/java/FooTest.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + +import junit.framework.TestCase; +import static junit.framework.Assert.assertEquals; + +public class FooTest extends TestCase { + + public void testFoo() { + Foo foo = new Foo(); + assertEquals("bar", foo.bar()); + } + +} \ No newline at end of file diff --git a/buildr/tests/package_war_as_jar/Buildfile b/buildr/tests/package_war_as_jar/Buildfile new file mode 100644 index 0000000..91e024a --- /dev/null +++ b/buildr/tests/package_war_as_jar/Buildfile @@ -0,0 +1,15 @@ + +module PackageWarAsJar + + def package_as_war_spec(spec) + spec.merge(:type => "jar") + end +end + +define "webapp", :version => "1.0", :group => "org.group" do + project.extend PackageWarAsJar + + package(:war) + +end + \ No newline at end of file diff --git a/buildr/tests/package_war_as_jar/src/main/java/Foo.java b/buildr/tests/package_war_as_jar/src/main/java/Foo.java new file mode 100644 index 0000000..325cc6b --- /dev/null +++ b/buildr/tests/package_war_as_jar/src/main/java/Foo.java @@ -0,0 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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. + */ + +public class Foo { +} \ No newline at end of file
  • ktX)4X&EYvE MOMtbU8=kkQq;`)eq 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