浏览代码

Switch build system to Gradle

See #13930
Ryan Ernst 10 年之前
父节点
当前提交
c86100f636
共有 80 个文件被更改,包括 4191 次插入90 次删除
  1. 1 1
      .gitignore
  2. 4 6
      CONTRIBUTING.md
  3. 2 3
      README.textile
  4. 45 53
      TESTING.asciidoc
  5. 165 0
      build.gradle
  6. 61 0
      buildSrc/build.gradle
  7. 53 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/BalancersConfiguration.groovy
  8. 25 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/ListenersConfiguration.groovy
  9. 64 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/LoggingOutputStream.groovy
  10. 47 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/RandomizedTestingPlugin.groovy
  11. 248 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/RandomizedTestingTask.groovy
  12. 14 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/SlowTestsConfiguration.groovy
  13. 14 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/StackTraceFiltersConfiguration.groovy
  14. 16 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/TestLoggingConfiguration.groovy
  15. 69 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/TestProgressLogger.groovy
  16. 373 0
      buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/TestReportLogger.groovy
  17. 135 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy
  18. 35 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/ElasticsearchProperties.groovy
  19. 45 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/MavenFilteringHack.groovy
  20. 109 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginBuildPlugin.groovy
  21. 56 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginPropertiesExtension.groovy
  22. 95 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginPropertiesTask.groovy
  23. 189 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/DependencyLicensesTask.groovy
  24. 108 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/ForbiddenPatternsTask.groovy
  25. 93 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy
  26. 63 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy
  27. 199 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy
  28. 90 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/test/RestIntegTestTask.groovy
  29. 75 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/test/RestSpecHack.groovy
  30. 59 0
      buildSrc/src/main/groovy/org/elasticsearch/gradle/test/RestTestPlugin.groovy
  31. 1 0
      buildSrc/src/main/resources/META-INF/gradle-plugins/carrotsearch.randomizedtesting.properties
  32. 1 0
      buildSrc/src/main/resources/META-INF/gradle-plugins/elasticsearch.build.properties
  33. 1 0
      buildSrc/src/main/resources/META-INF/gradle-plugins/elasticsearch.esplugin.properties
  34. 1 0
      buildSrc/src/main/resources/META-INF/gradle-plugins/elasticsearch.rest-test.properties
  35. 1 0
      buildSrc/src/main/resources/elasticsearch.properties
  36. 92 0
      buildSrc/src/main/resources/forbidden/all-signatures.txt
  37. 85 0
      buildSrc/src/main/resources/forbidden/core-signatures.txt
  38. 23 0
      buildSrc/src/main/resources/forbidden/test-signatures.txt
  39. 66 0
      buildSrc/src/main/resources/forbidden/third-party-signatures.txt
  40. 76 0
      buildSrc/src/main/resources/plugin-descriptor.properties
  41. 133 0
      core/build.gradle
  42. 0 1
      core/src/test/java/org/elasticsearch/index/analysis/AnalysisModuleTests.java
  43. 10 25
      core/src/test/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java
  44. 1 1
      core/src/test/java/org/elasticsearch/test/rest/support/FileUtils.java
  45. 9 0
      core/src/test/resources/log4j.properties
  46. 12 0
      dev-tools/build.gradle
  47. 201 0
      distribution/build.gradle
  48. 4 0
      distribution/deb/build.gradle
  49. 4 0
      distribution/rpm/build.gradle
  50. 10 0
      distribution/tar/build.gradle
  51. 11 0
      distribution/zip/build.gradle
  52. 2 0
      distribution/ziphack/build.gradle
  53. 2 0
      gradle.properties
  54. 34 0
      plugins/analysis-icu/build.gradle
  55. 32 0
      plugins/analysis-kuromoji/build.gradle
  56. 34 0
      plugins/analysis-phonetic/build.gradle
  57. 32 0
      plugins/analysis-smartcn/build.gradle
  58. 31 0
      plugins/analysis-stempel/build.gradle
  59. 32 0
      plugins/build.gradle
  60. 24 0
      plugins/delete-by-query/build.gradle
  61. 48 0
      plugins/discovery-azure/build.gradle
  62. 40 0
      plugins/discovery-ec2/build.gradle
  63. 23 0
      plugins/discovery-gce/build.gradle
  64. 25 0
      plugins/discovery-multicast/build.gradle
  65. 29 0
      plugins/jvm-example/build.gradle
  66. 35 0
      plugins/lang-expression/build.gradle
  67. 37 0
      plugins/lang-groovy/build.gradle
  68. 38 0
      plugins/lang-javascript/build.gradle
  69. 38 0
      plugins/lang-python/build.gradle
  70. 25 0
      plugins/mapper-murmur3/build.gradle
  71. 24 0
      plugins/mapper-size/build.gradle
  72. 40 0
      plugins/repository-azure/build.gradle
  73. 40 0
      plugins/repository-s3/build.gradle
  74. 27 0
      plugins/site-example/build.gradle
  75. 24 0
      plugins/store-smb/build.gradle
  76. 22 0
      qa/smoke-test-client/build.gradle
  77. 47 0
      qa/smoke-test-plugins/build.gradle
  78. 1 0
      rest-api-spec/build.gradle
  79. 42 0
      settings.gradle
  80. 69 0
      test-framework/build.gradle

+ 1 - 1
.gitignore

@@ -9,7 +9,7 @@ logs/
 .DS_Store
 .DS_Store
 build/
 build/
 target/
 target/
-*-execution-hints.log
+**/.local*
 docs/html/
 docs/html/
 docs/build.log
 docs/build.log
 /tmp/
 /tmp/

+ 4 - 6
CONTRIBUTING.md

@@ -76,9 +76,7 @@ Contributing to the Elasticsearch codebase
 
 
 **Repository:** [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
 **Repository:** [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
 
 
-Make sure you have [Maven](http://maven.apache.org) installed, as Elasticsearch uses it as its build system. Integration with IntelliJ and Eclipse should work out of the box. Eclipse users can automatically configure their IDE by running `mvn eclipse:eclipse` and then importing the project into their workspace: `File > Import > Existing project into workspace` and make sure to select `Search for nested projects...` option as Elasticsearch is a multi-module maven project. Additionally you will want to ensure that Eclipse is using 2048m of heap by modifying `eclipse.ini` accordingly to avoid GC overhead errors. Please make sure the [m2e-connector](http://marketplace.eclipse.org/content/m2e-connector-maven-dependency-plugin) is not installed in your Eclipse distribution as it will interfere with setup performed by `mvn eclipse:eclipse`.
-
-Elasticsearch also works perfectly with Eclipse's [m2e](http://www.eclipse.org/m2e/).  Once you've installed m2e you can import Elasticsearch as an `Existing Maven Project`.
+Make sure you have [Gradle](http://gradle.org) installed, as Elasticsearch uses it as its build system. Integration with IntelliJ and Eclipse should work out of the box. Eclipse users can automatically configure their IDE by running `gradle eclipse` and then importing the project into their workspace: `File > Import > Existing project into workspace` and make sure to select `Search for nested projects...` option as Elasticsearch is a multi-module maven project. Additionally you will want to ensure that Eclipse is using 2048m of heap by modifying `eclipse.ini` accordingly to avoid GC overhead errors.
 
 
 Please follow these formatting guidelines:
 Please follow these formatting guidelines:
 
 
@@ -92,15 +90,15 @@ To create a distribution from the source, simply run:
 
 
 ```sh
 ```sh
 cd elasticsearch/
 cd elasticsearch/
-mvn clean package -DskipTests
+gradle assemble
 ```
 ```
 
 
-You will find the newly built packages under: `./target/releases/`.
+You will find the newly built packages under: `./distribution/build/distributions/`.
 
 
 Before submitting your changes, run the test suite to make sure that nothing is broken, with:
 Before submitting your changes, run the test suite to make sure that nothing is broken, with:
 
 
 ```sh
 ```sh
-mvn clean test -Dtests.slow=true
+gradle check
 ```
 ```
 
 
 Source: [Contributing to elasticsearch](https://www.elastic.co/contributing-to-elasticsearch/)
 Source: [Contributing to elasticsearch](https://www.elastic.co/contributing-to-elasticsearch/)

+ 2 - 3
README.textile

@@ -200,10 +200,9 @@ We have just covered a very small portion of what Elasticsearch is all about. Fo
 
 
 h3. Building from Source
 h3. Building from Source
 
 
-Elasticsearch uses "Maven":http://maven.apache.org for its build system.
+Elasticsearch uses "Gradle":http://gradle.org for its build system. You'll need to have a modern version of Gradle installed - 2.6 should do.
 
 
-In order to create a distribution, simply run the @mvn clean package
--DskipTests@ command in the cloned directory.
+In order to create a distribution, simply run the @gradle build@ command in the cloned directory.
 
 
 The distribution for each project will be created under the @target/releases@ directory in that project.
 The distribution for each project will be created under the @target/releases@ directory in that project.
 
 

+ 45 - 53
TESTING.asciidoc

@@ -13,7 +13,7 @@ To create a distribution without running the tests, simply run the
 following:
 following:
 
 
 -----------------------------
 -----------------------------
-mvn clean package -DskipTests
+gradle assemble
 -----------------------------
 -----------------------------
 
 
 == Other test options
 == Other test options
@@ -35,7 +35,7 @@ Use local transport (default since 1.3):
 Alternatively, you can set the `ES_TEST_LOCAL` environment variable:
 Alternatively, you can set the `ES_TEST_LOCAL` environment variable:
 
 
 -------------------------------------
 -------------------------------------
-export ES_TEST_LOCAL=true && mvn test
+export ES_TEST_LOCAL=true && gradle test
 -------------------------------------
 -------------------------------------
 
 
 === Running Elasticsearch from a checkout
 === Running Elasticsearch from a checkout
@@ -55,20 +55,20 @@ run it using Maven:
 Run a single test case (variants)
 Run a single test case (variants)
 
 
 ----------------------------------------------------------
 ----------------------------------------------------------
-mvn test -Dtests.class=org.elasticsearch.package.ClassName
-mvn test "-Dtests.class=*.ClassName"
+gradle test -Dtests.class=org.elasticsearch.package.ClassName
+gradle test "-Dtests.class=*.ClassName"
 ----------------------------------------------------------
 ----------------------------------------------------------
 
 
 Run all tests in a package and sub-packages
 Run all tests in a package and sub-packages
 
 
 ----------------------------------------------------
 ----------------------------------------------------
-mvn test "-Dtests.class=org.elasticsearch.package.*"
+gradle test "-Dtests.class=org.elasticsearch.package.*"
 ----------------------------------------------------
 ----------------------------------------------------
 
 
 Run any test methods that contain 'esi' (like: ...r*esi*ze...).
 Run any test methods that contain 'esi' (like: ...r*esi*ze...).
 
 
 -------------------------------
 -------------------------------
-mvn test "-Dtests.method=*esi*"
+gradle test "-Dtests.method=*esi*"
 -------------------------------
 -------------------------------
 
 
 You can also filter tests by certain annotations ie:
 You can also filter tests by certain annotations ie:
@@ -81,7 +81,7 @@ You can also filter tests by certain annotations ie:
 Those annotation names can be combined into a filter expression like:
 Those annotation names can be combined into a filter expression like:
 
 
 ------------------------------------------------
 ------------------------------------------------
-mvn test -Dtests.filter="@nightly and not @backwards"
+gradle test -Dtests.filter="@nightly and not @backwards"
 ------------------------------------------------
 ------------------------------------------------
 
 
 to run all nightly test but not the ones that are backwards tests. `tests.filter` supports
 to run all nightly test but not the ones that are backwards tests. `tests.filter` supports
@@ -89,7 +89,7 @@ the boolean operators `and, or, not` and grouping ie:
 
 
 
 
 ---------------------------------------------------------------
 ---------------------------------------------------------------
-mvn test -Dtests.filter="@nightly and not(@badapple or @backwards)"
+gradle test -Dtests.filter="@nightly and not(@badapple or @backwards)"
 ---------------------------------------------------------------
 ---------------------------------------------------------------
 
 
 === Seed and repetitions.
 === Seed and repetitions.
@@ -97,7 +97,7 @@ mvn test -Dtests.filter="@nightly and not(@badapple or @backwards)"
 Run with a given seed (seed is a hex-encoded long).
 Run with a given seed (seed is a hex-encoded long).
 
 
 ------------------------------
 ------------------------------
-mvn test -Dtests.seed=DEADBEEF
+gradle test -Dtests.seed=DEADBEEF
 ------------------------------
 ------------------------------
 
 
 === Repeats _all_ tests of ClassName N times.
 === Repeats _all_ tests of ClassName N times.
@@ -106,7 +106,7 @@ Every test repetition will have a different method seed
 (derived from a single random master seed).
 (derived from a single random master seed).
 
 
 --------------------------------------------------
 --------------------------------------------------
-mvn test -Dtests.iters=N -Dtests.class=*.ClassName
+gradle test -Dtests.iters=N -Dtests.class=*.ClassName
 --------------------------------------------------
 --------------------------------------------------
 
 
 === Repeats _all_ tests of ClassName N times.
 === Repeats _all_ tests of ClassName N times.
@@ -115,7 +115,7 @@ Every test repetition will have exactly the same master (0xdead) and
 method-level (0xbeef) seed.
 method-level (0xbeef) seed.
 
 
 ------------------------------------------------------------------------
 ------------------------------------------------------------------------
-mvn test -Dtests.iters=N -Dtests.class=*.ClassName -Dtests.seed=DEAD:BEEF
+gradle test -Dtests.iters=N -Dtests.class=*.ClassName -Dtests.seed=DEAD:BEEF
 ------------------------------------------------------------------------
 ------------------------------------------------------------------------
 
 
 === Repeats a given test N times
 === Repeats a given test N times
@@ -125,14 +125,14 @@ ie: testFoo[0], testFoo[1], etc... so using testmethod or tests.method
 ending in a glob is necessary to ensure iterations are run).
 ending in a glob is necessary to ensure iterations are run).
 
 
 -------------------------------------------------------------------------
 -------------------------------------------------------------------------
-mvn test -Dtests.iters=N -Dtests.class=*.ClassName -Dtests.method=mytest*
+gradle test -Dtests.iters=N -Dtests.class=*.ClassName -Dtests.method=mytest*
 -------------------------------------------------------------------------
 -------------------------------------------------------------------------
 
 
 Repeats N times but skips any tests after the first failure or M initial failures.
 Repeats N times but skips any tests after the first failure or M initial failures.
 
 
 -------------------------------------------------------------
 -------------------------------------------------------------
-mvn test -Dtests.iters=N -Dtests.failfast=true -Dtestcase=...
-mvn test -Dtests.iters=N -Dtests.maxfailures=M -Dtestcase=...
+gradle test -Dtests.iters=N -Dtests.failfast=true -Dtestcase=...
+gradle test -Dtests.iters=N -Dtests.maxfailures=M -Dtestcase=...
 -------------------------------------------------------------
 -------------------------------------------------------------
 
 
 === Test groups.
 === Test groups.
@@ -142,9 +142,9 @@ Test groups can be enabled or disabled (true/false).
 Default value provided below in [brackets].
 Default value provided below in [brackets].
 
 
 ------------------------------------------------------------------
 ------------------------------------------------------------------
-mvn test -Dtests.nightly=[false]   - nightly test group (@Nightly)
-mvn test -Dtests.weekly=[false]    - weekly tests (@Weekly)
-mvn test -Dtests.awaitsfix=[false] - known issue (@AwaitsFix)
+gradle test -Dtests.nightly=[false]   - nightly test group (@Nightly)
+gradle test -Dtests.weekly=[false]    - weekly tests (@Weekly)
+gradle test -Dtests.awaitsfix=[false] - known issue (@AwaitsFix)
 ------------------------------------------------------------------
 ------------------------------------------------------------------
 
 
 === Load balancing and caches.
 === Load balancing and caches.
@@ -154,7 +154,7 @@ By default, the tests run sequentially on a single forked JVM.
 To run with more forked JVMs than the default use:
 To run with more forked JVMs than the default use:
 
 
 ----------------------------
 ----------------------------
-mvn test -Dtests.jvms=8 test
+gradle test -Dtests.jvms=8
 ----------------------------
 ----------------------------
 
 
 Don't count hypercores for CPU-intense tests and leave some slack
 Don't count hypercores for CPU-intense tests and leave some slack
@@ -167,7 +167,7 @@ It is possible to provide a version that allows to adapt the tests behaviour
 to older features or bugs that have been changed or fixed in the meantime.
 to older features or bugs that have been changed or fixed in the meantime.
 
 
 -----------------------------------------
 -----------------------------------------
-mvn test -Dtests.compatibility=1.0.0
+gradle test -Dtests.compatibility=1.0.0
 -----------------------------------------
 -----------------------------------------
 
 
 
 
@@ -176,50 +176,50 @@ mvn test -Dtests.compatibility=1.0.0
 Run all tests without stopping on errors (inspect log files).
 Run all tests without stopping on errors (inspect log files).
 
 
 -----------------------------------------
 -----------------------------------------
-mvn test -Dtests.haltonfailure=false test
+gradle test -Dtests.haltonfailure=false
 -----------------------------------------
 -----------------------------------------
 
 
 Run more verbose output (slave JVM parameters, etc.).
 Run more verbose output (slave JVM parameters, etc.).
 
 
 ----------------------
 ----------------------
-mvn test -verbose test
+gradle test -verbose
 ----------------------
 ----------------------
 
 
 Change the default suite timeout to 5 seconds for all
 Change the default suite timeout to 5 seconds for all
 tests (note the exclamation mark).
 tests (note the exclamation mark).
 
 
 ---------------------------------------
 ---------------------------------------
-mvn test -Dtests.timeoutSuite=5000! ...
+gradle test -Dtests.timeoutSuite=5000! ...
 ---------------------------------------
 ---------------------------------------
 
 
-Change the logging level of ES (not mvn)
+Change the logging level of ES (not gradle)
 
 
 --------------------------------
 --------------------------------
-mvn test -Des.logger.level=DEBUG
+gradle test -Des.logger.level=DEBUG
 --------------------------------
 --------------------------------
 
 
 Print all the logging output from the test runs to the commandline
 Print all the logging output from the test runs to the commandline
 even if tests are passing.
 even if tests are passing.
 
 
 ------------------------------
 ------------------------------
-mvn test -Dtests.output=always
+gradle test -Dtests.output=always
 ------------------------------
 ------------------------------
 
 
 Configure the heap size.
 Configure the heap size.
 
 
 ------------------------------
 ------------------------------
-mvn test -Dtests.heap.size=512m
+gradle test -Dtests.heap.size=512m
 ------------------------------
 ------------------------------
 
 
 Pass arbitrary jvm arguments.
 Pass arbitrary jvm arguments.
 
 
 ------------------------------
 ------------------------------
 # specify heap dump path
 # specify heap dump path
-mvn test -Dtests.jvm.argline="-XX:HeapDumpPath=/path/to/heapdumps"
+gradle test -Dtests.jvm.argline="-XX:HeapDumpPath=/path/to/heapdumps"
 # enable gc logging
 # enable gc logging
-mvn test -Dtests.jvm.argline="-verbose:gc"
+gradle test -Dtests.jvm.argline="-verbose:gc"
 # enable security debugging
 # enable security debugging
-mvn test -Dtests.jvm.argline="-Djava.security.debug=access,failure"
+gradle test -Dtests.jvm.argline="-Djava.security.debug=access,failure"
 ------------------------------
 ------------------------------
 
 
 == Backwards Compatibility Tests
 == Backwards Compatibility Tests
@@ -230,7 +230,7 @@ To run backwards compatibilty tests untar or unzip a release and run the tests
 with the following command:
 with the following command:
 
 
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
-mvn test -Dtests.filter="@backwards" -Dtests.bwc.version=x.y.z -Dtests.bwc.path=/path/to/elasticsearch -Dtests.security.manager=false
+gradle test -Dtests.filter="@backwards" -Dtests.bwc.version=x.y.z -Dtests.bwc.path=/path/to/elasticsearch -Dtests.security.manager=false
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 
 
 Note that backwards tests must be run with security manager disabled.
 Note that backwards tests must be run with security manager disabled.
@@ -238,7 +238,7 @@ If the elasticsearch release is placed under `./backwards/elasticsearch-x.y.z` t
 can be omitted:
 can be omitted:
 
 
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
-mvn test -Dtests.filter="@backwards" -Dtests.bwc.version=x.y.z -Dtests.security.manager=false
+gradle test -Dtests.filter="@backwards" -Dtests.bwc.version=x.y.z -Dtests.security.manager=false
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 
 
 To setup the bwc test environment execute the following steps (provided you are
 To setup the bwc test environment execute the following steps (provided you are
@@ -250,19 +250,25 @@ $ curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elastic
 $ tar -xzf elasticsearch-1.2.1.tar.gz
 $ tar -xzf elasticsearch-1.2.1.tar.gz
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 
 
-== Running integration tests
+== Running verification tasks
 
 
-To run the integration tests:
+To run all verification tasks, including static checks, unit tests, and integration tests:
 
 
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
-mvn verify
+gradle check
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 
 
-Note that this will also run the unit tests first. If you want to just
-run the integration tests only (because you are debugging them):
+Note that this will also run the unit tests and precommit tasks first. If you want to just
+run the integration tests (because you are debugging them):
 
 
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
-mvn verify -Dskip.unit.tests
+gradle integTest
+---------------------------------------------------------------------------
+
+If you want to just run the precommit checks:
+
+---------------------------------------------------------------------------
+gradle precommit
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 
 
 == Testing the REST layer
 == Testing the REST layer
@@ -278,7 +284,7 @@ The REST tests are run automatically when executing the maven test command. To r
 REST tests use the following command:
 REST tests use the following command:
 
 
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
-mvn verify -Dtests.filter="@Rest" -Dskip.unit.tests=true
+gradle integTest -Dtests.filter="@Rest"
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 
 
 `RestNIT` are the executable test classes that runs all the
 `RestNIT` are the executable test classes that runs all the
@@ -303,20 +309,6 @@ comma separated list of nodes to connect to (e.g. localhost:9300). A transport c
 be created based on that and used for all the before|after test operations, and to extract
 be created based on that and used for all the before|after test operations, and to extract
 the http addresses of the nodes so that REST requests can be sent to them.
 the http addresses of the nodes so that REST requests can be sent to them.
 
 
-== Skip validate
-
-To disable validation step (forbidden API or `// NOCOMMIT`) use
-
----------------------------------------------------------------------------
-mvn test -Dvalidate.skip=true
----------------------------------------------------------------------------
-
-You can also skip this by using the "dev" profile:
-
----------------------------------------------------------------------------
-mvn test -Pdev
----------------------------------------------------------------------------
-
 == Testing scripts
 == Testing scripts
 
 
 The simplest way to test scripts and the packaged distributions is to use
 The simplest way to test scripts and the packaged distributions is to use
@@ -334,7 +326,7 @@ vagrant plugin install vagrant-cachier
 . Validate your installed dependencies:
 . Validate your installed dependencies:
 
 
 -------------------------------------
 -------------------------------------
-mvn -Dtests.vagrant -pl qa/vagrant validate
+gradle :qa:vagrant:validate
 -------------------------------------
 -------------------------------------
 
 
 . Download the VMs. Since Maven or ant or something eats the progress reports
 . Download the VMs. Since Maven or ant or something eats the progress reports

+ 165 - 0
build.gradle

@@ -0,0 +1,165 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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 com.bmuschko.gradle.nexus.NexusPlugin
+
+buildscript {
+  repositories {
+    mavenCentral()
+  }
+  dependencies {
+    classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1'
+  }
+}
+
+// common maven publishing configuration
+subprojects {
+  plugins.withType(NexusPlugin).whenPluginAdded {
+    modifyPom {
+      project {
+        url 'https://github.com/elastic/elasticsearch'
+        inceptionYear '2009'
+
+        scm {
+          url 'https://github.com/elastic/elasticsearch'
+          connection 'scm:https://elastic@github.com/elastic/elasticsearch'
+          developerConnection 'scm:git://github.com/elastic/elasticsearch.git'
+        }
+
+        licenses {
+          license {
+            name 'The Apache Software License, Version 2.0'
+            url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+            distribution 'repo'
+          }
+        }
+      }
+    }  
+    extraArchive {
+      javadoc = false
+      tests = false
+    }
+    // we have our own username/password prompts so that they only happen once
+    // TODO: add gpg signing prompts
+    project.gradle.taskGraph.whenReady { taskGraph ->
+      if (taskGraph.allTasks.any { it.name == 'uploadArchives' }) {
+        Console console = System.console()
+        if (project.hasProperty('nexusUsername') == false) {
+          String nexusUsername = console.readLine('\nNexus username: ')
+          project.rootProject.allprojects.each {
+            it.ext.nexusUsername = nexusUsername
+          }
+        }
+        if (project.hasProperty('nexusPassword') == false) {
+          String nexusPassword = new String(console.readPassword('\nNexus password: '))
+          project.rootProject.allprojects.each {
+            it.ext.nexusPassword = nexusPassword
+          }
+        }
+      }
+    }
+  }
+}
+
+if (hasProperty('projectsPrefix') == false) {
+  allprojects {
+    project.ext['projectsPrefix'] = ''
+  }  
+}
+
+allprojects {
+  // injecting groovy property variables into all projects
+  project.ext {
+    // minimum java 8
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = sourceCompatibility
+
+    luceneSnapshotRevision = '1710880'
+    // dependency versions that are used in more than one place
+    versions = [
+      lucene:             "5.4.0-snapshot-${luceneSnapshotRevision}",
+      randomizedrunner:   '2.2.0',
+      httpclient:         '4.3.6'
+    ]
+  }
+}
+
+subprojects {
+  repositories {
+    mavenCentral()
+    maven {
+      name 'sonatype-snapshots'
+      url 'http://oss.sonatype.org/content/repositories/snapshots/'
+    }
+    maven {
+      name 'lucene-snapshots'
+      url "http://s3.amazonaws.com/download.elasticsearch.org/lucenesnapshots/${luceneSnapshotRevision}"
+    }
+  }
+
+  // include license and notice in jars
+  gradle.projectsEvaluated {
+    tasks.withType(Jar) {
+      into('META-INF') {
+        from project.rootProject.rootDir
+        include 'LICENSE.txt'   
+        include 'NOTICE.txt'   
+      }
+    }
+  }
+
+  configurations {
+    all {
+      resolutionStrategy {
+        //failOnVersionConflict()
+
+        dependencySubstitution {
+          substitute module("org.elasticsearch:rest-api-spec:${version}") with project("${projectsPrefix}:rest-api-spec")
+          substitute module("org.elasticsearch:elasticsearch:${version}") with project("${projectsPrefix}:core")
+          substitute module("org.elasticsearch:test-framework:${version}") with project("${projectsPrefix}:test-framework")
+          substitute module("org.elasticsearch.distribution.zip:elasticsearch:${version}") with project("${projectsPrefix}:distribution:zip")
+        }
+      }
+    }
+  }
+}
+
+// IDE configuration
+allprojects {
+  apply plugin: 'idea'
+  apply plugin: 'eclipse'
+
+  // TODO: similar for intellij
+  eclipse {
+    classpath {
+      defaultOutputDir = new File(project.buildDir, 'eclipse')
+    }
+  }
+}
+
+idea {
+  if (project != null) {
+    // could be null, if this project is attached to another...
+    project {
+      languageLevel = sourceCompatibility
+      vcs = 'Git'
+    }
+  }
+}
+

+ 61 - 0
buildSrc/build.gradle

@@ -0,0 +1,61 @@
+import org.apache.tools.ant.filters.ReplaceTokens
+
+plugins {
+  id 'groovy'
+  id 'com.bmuschko.nexus' version '2.3.1'
+}
+// TODO: move common IDE configuration to a common file to include
+apply plugin: 'idea'
+apply plugin: 'eclipse'
+
+/*idea {
+  project {
+    languageLevel = '1.8'
+    vcs = 'Git'
+  }
+}*/
+
+group = 'org.elasticsearch.gradle'
+archivesBaseName = 'build-tools'
+
+repositories {
+  mavenCentral()
+  maven {
+    name 'sonatype-snapshots'
+    url "https://oss.sonatype.org/content/repositories/snapshots/"
+  }
+}
+
+dependencies {
+  compile gradleApi()
+  compile localGroovy()
+  compile 'com.carrotsearch.randomizedtesting:junit4-ant:2.2.0'
+  compile('junit:junit:4.11') {
+    transitive = false
+  }
+  compile 'com.netflix.nebula:gradle-extra-configurations-plugin:3.0.3'
+  compile 'de.thetaphi:forbiddenapis:2.0'
+}
+
+Properties props = new Properties()
+props.load(project.file('../gradle.properties').newDataInputStream())
+version = props.getProperty('version')
+
+processResources {
+    inputs.file('../gradle.properties')
+    filter ReplaceTokens, tokens: [
+        'version': props.getProperty('version')
+    ]
+}
+
+extraArchive {
+  javadoc = false
+  tests = false
+}
+
+eclipse {
+  classpath {
+    defaultOutputDir = new File(file('build'), 'eclipse')
+  }
+}
+

+ 53 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/BalancersConfiguration.groovy

@@ -0,0 +1,53 @@
+package com.carrotsearch.gradle.randomizedtesting
+
+import com.carrotsearch.ant.tasks.junit4.SuiteBalancer
+import com.carrotsearch.ant.tasks.junit4.balancers.ExecutionTimeBalancer
+import com.carrotsearch.ant.tasks.junit4.listeners.ExecutionTimesReport
+import org.apache.tools.ant.types.FileSet
+
+class BalancersConfiguration {
+    // parent task, so executionTime can register an additional listener
+    RandomizedTestingTask task
+    List<SuiteBalancer> balancers = new ArrayList<>()
+
+    void executionTime(Map<String,Object> properties) {
+        ExecutionTimeBalancer balancer = new ExecutionTimeBalancer()
+
+        FileSet fileSet = new FileSet()
+        Object filename = properties.remove('cacheFilename')
+        if (filename == null) {
+            throw new IllegalArgumentException('cacheFilename is required for executionTime balancer')
+        }
+        fileSet.setIncludes(filename.toString())
+
+        File cacheDir = task.project.projectDir
+        Object dir = properties.remove('cacheDir')
+        if (dir != null) {
+            cacheDir = new File(dir.toString())
+        }
+        fileSet.setDir(cacheDir)
+        balancer.add(fileSet)
+
+        int historySize = 10
+        Object size = properties.remove('historySize')
+        if (size instanceof Integer) {
+            historySize = (Integer)size
+        } else if (size != null) {
+            throw new IllegalArgumentException('historySize must be an integer')
+        }
+        ExecutionTimesReport listener = new ExecutionTimesReport()
+        listener.setFile(new File(cacheDir, filename.toString()))
+        listener.setHistoryLength(historySize)
+
+        if (properties.isEmpty() == false) {
+            throw new IllegalArgumentException('Unknown properties for executionTime balancer: ' + properties.keySet())
+        }
+
+        task.listenersConfig.listeners.add(listener)
+        balancers.add(balancer)
+    }
+
+    void custom(SuiteBalancer balancer) {
+        balancers.add(balancer)
+    }
+}

+ 25 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/ListenersConfiguration.groovy

@@ -0,0 +1,25 @@
+package com.carrotsearch.gradle.randomizedtesting
+
+import com.carrotsearch.ant.tasks.junit4.listeners.AggregatedEventListener
+import com.carrotsearch.ant.tasks.junit4.listeners.antxml.AntXmlReport
+
+
+class ListenersConfiguration {
+    RandomizedTestingTask task
+    List<AggregatedEventListener> listeners = new ArrayList<>()
+
+    void junitReport(Map<String, Object> props) {
+        AntXmlReport reportListener = new AntXmlReport()
+        Object dir = props == null ? null : props.get('dir')
+        if (dir != null) {
+            reportListener.setDir(task.project.file(dir))
+        } else {
+            reportListener.setDir(new File(task.project.buildDir, 'reports' + File.separator + "${task.name}Junit"))
+        }
+        listeners.add(reportListener)
+    }
+
+    void custom(AggregatedEventListener listener) {
+        listeners.add(listener)
+    }
+}

+ 64 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/LoggingOutputStream.groovy

@@ -0,0 +1,64 @@
+package com.carrotsearch.gradle.randomizedtesting
+
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.Logger
+
+/**
+ * Writes data passed to this stream as log messages.
+ *
+ * The stream will be flushed whenever a newline is detected.
+ * Allows setting an optional prefix before each line of output.
+ */
+public class LoggingOutputStream extends OutputStream {
+
+    /** The starting length of the buffer */
+    static final int DEFAULT_BUFFER_LENGTH = 4096
+
+    /** The buffer of bytes sent to the stream */
+    byte[] buffer = new byte[DEFAULT_BUFFER_LENGTH]
+
+    /** Offset of the start of unwritten data in the buffer */
+    int start = 0
+
+    /** Offset of the end (semi-open) of unwritten data in the buffer */
+    int end = 0
+
+    /** Logger to write stream data to */
+    Logger logger
+
+    /** Prefix to add before each line of output */
+    String prefix = ""
+
+    /** Log level to write log messages to */
+    LogLevel level
+
+    void write(final int b) throws IOException {
+        if (b == 0) return;
+        if (b == (int)'\n' as char) {
+            // always flush with newlines instead of adding to the buffer
+            flush()
+            return
+        }
+
+        if (end == buffer.length) {
+            if (start != 0) {
+                // first try shifting the used buffer back to the beginning to make space
+                System.arraycopy(buffer, start, buffer, 0, end - start)
+            } else {
+                // need more space, extend the buffer
+            }
+            final int newBufferLength = buffer.length + DEFAULT_BUFFER_LENGTH;
+            final byte[] newBuffer = new byte[newBufferLength];
+            System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
+            buffer = newBuffer;
+        }
+
+        buffer[end++] = (byte) b;
+    }
+
+    void flush() {
+        if (end == start) return
+        logger.log(level, prefix + new String(buffer, start, end - start));
+        start = end
+    }
+}

+ 47 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/RandomizedTestingPlugin.groovy

@@ -0,0 +1,47 @@
+package com.carrotsearch.gradle.randomizedtesting
+
+import com.carrotsearch.ant.tasks.junit4.JUnit4
+import org.gradle.api.AntBuilder
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.TaskContainer
+import org.gradle.api.tasks.testing.Test
+
+class RandomizedTestingPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        replaceTestTask(project.tasks)
+        configureAnt(project.ant)
+    }
+
+    static void replaceTestTask(TaskContainer tasks) {
+        Test oldTestTask = tasks.findByPath('test')
+        if (oldTestTask == null) {
+            // no test task, ok, user will use testing task on their own
+            return
+        }
+        tasks.remove(oldTestTask)
+
+        Map properties = [
+            name: 'test',
+            type: RandomizedTestingTask,
+            dependsOn: oldTestTask.dependsOn,
+            group: JavaBasePlugin.VERIFICATION_GROUP,
+            description: 'Runs unit tests with the randomized testing framework'
+        ]
+        RandomizedTestingTask newTestTask = tasks.create(properties)
+        newTestTask.classpath = oldTestTask.classpath
+        newTestTask.testClassesDir = oldTestTask.testClassesDir
+
+        // hack so check task depends on custom test
+        Task checkTask = tasks.findByPath('check')
+        checkTask.dependsOn.remove(oldTestTask)
+        checkTask.dependsOn.add(newTestTask)
+    }
+
+    static void configureAnt(AntBuilder ant) {
+        ant.project.addTaskDefinition('junit4:junit4', JUnit4.class)
+    }
+}

+ 248 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/RandomizedTestingTask.groovy

@@ -0,0 +1,248 @@
+package com.carrotsearch.gradle.randomizedtesting
+
+import com.carrotsearch.ant.tasks.junit4.ListenersList
+import com.carrotsearch.ant.tasks.junit4.listeners.AggregatedEventListener
+import groovy.xml.NamespaceBuilder
+import org.apache.tools.ant.RuntimeConfigurable
+import org.apache.tools.ant.UnknownElement
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.FileTreeElement
+import org.gradle.api.internal.tasks.options.Option
+import org.gradle.api.specs.Spec
+import org.gradle.api.tasks.*
+import org.gradle.api.tasks.util.PatternFilterable
+import org.gradle.api.tasks.util.PatternSet
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.util.ConfigureUtil
+
+import javax.inject.Inject
+
+class RandomizedTestingTask extends DefaultTask {
+
+    PatternFilterable patternSet = new PatternSet()
+
+    // TODO: change to "executable" to match gradle test params?
+    @Optional
+    @Input
+    String jvm = 'java'
+
+    @Optional
+    @Input
+    File workingDir = new File(project.buildDir, 'testrun' + File.separator + name)
+
+    @Optional
+    @Input
+    FileCollection classpath
+
+    @Input
+    String parallelism = '1'
+
+    @InputDirectory
+    File testClassesDir
+
+    @Optional
+    @Input
+    boolean haltOnFailure = true
+
+    @Optional
+    @Input
+    boolean shuffleOnSlave = true
+
+    @Optional
+    @Input
+    boolean enableAssertions = true
+
+    @Optional
+    @Input
+    boolean enableSystemAssertions = true
+
+    TestLoggingConfiguration testLoggingConfig = new TestLoggingConfiguration()
+
+    BalancersConfiguration balancersConfig = new BalancersConfiguration(task: this)
+    ListenersConfiguration listenersConfig = new ListenersConfiguration(task: this)
+
+    List<String> jvmArgs = new ArrayList<>()
+    Map<String, String> systemProperties = new HashMap<>()
+
+    RandomizedTestingTask() {
+        outputs.upToDateWhen {false} // randomized tests are never up to date
+        listenersConfig.listeners.add(new TestProgressLogger(factory: getProgressLoggerFactory()))
+        listenersConfig.listeners.add(new TestReportLogger(logger: logger, config: testLoggingConfig))
+    }
+
+    @Inject
+    ProgressLoggerFactory getProgressLoggerFactory() {
+        throw new UnsupportedOperationException();
+    }
+
+    void jvmArgs(Iterable<String> arguments) {
+        jvmArgs.addAll(arguments)
+    }
+
+    void jvmArg(String argument) {
+        jvmArgs.add(argument)
+    }
+
+    void systemProperty(String property, String value) {
+        systemProperties.put(property, value)
+    }
+
+    void include(String... includes) {
+        this.patternSet.include(includes);
+    }
+
+    void include(Iterable<String> includes) {
+        this.patternSet.include(includes);
+    }
+
+    void include(Spec<FileTreeElement> includeSpec) {
+        this.patternSet.include(includeSpec);
+    }
+
+    void include(Closure includeSpec) {
+        this.patternSet.include(includeSpec);
+    }
+
+    void exclude(String... excludes) {
+        this.patternSet.exclude(excludes);
+    }
+
+    void exclude(Iterable<String> excludes) {
+        this.patternSet.exclude(excludes);
+    }
+
+    void exclude(Spec<FileTreeElement> excludeSpec) {
+        this.patternSet.exclude(excludeSpec);
+    }
+
+    void exclude(Closure excludeSpec) {
+        this.patternSet.exclude(excludeSpec);
+    }
+
+    @Input
+    void testLogging(Closure closure) {
+        ConfigureUtil.configure(closure, testLoggingConfig)
+    }
+
+    @Input
+    void balancers(Closure closure) {
+        ConfigureUtil.configure(closure, balancersConfig)
+    }
+
+    @Input
+    void listeners(Closure closure) {
+        ConfigureUtil.configure(closure, listenersConfig)
+    }
+
+    @Option(
+            option = "tests",
+            description = "Sets test class or method name to be included. This is for IDEs. Use -Dtests.class and -Dtests.method"
+    )
+    void setTestNameIncludePattern(String testNamePattern) {
+        // This is only implemented to give support for IDEs running tests. There are 3 patterns expected:
+        // * An exact test class and method
+        // * An exact test class
+        // * A package name prefix, ending with .*
+        // There is no way to distinguish the first two without looking at classes, so we use the rule
+        // that class names start with an uppercase letter...
+        // TODO: this doesn't work yet, but not sure why...intellij says it is using --tests, and this work from the command line...
+        String[] parts = testNamePattern.split('\\.')
+        String lastPart = parts[parts.length - 1]
+        String classname
+        String methodname = null
+        if (lastPart.equals('*') || lastPart.charAt(0).isUpperCase()) {
+            // package name or class name, just pass through
+            classname = testNamePattern
+        } else {
+            // method name, need to separate
+            methodname = lastPart
+            classname = testNamePattern.substring(0, testNamePattern.length() - lastPart.length() - 1)
+        }
+        ant.setProperty('tests.class', classname)
+        if (methodname != null) {
+            ant.setProperty('tests.method', methodname)
+        }
+    }
+
+    // TODO: add leaveTemporary
+    // TODO: add jvmOutputAction?
+    // TODO: add ifNoTests!
+
+    @TaskAction
+    void executeTests() {
+        Map attributes = [
+            jvm: jvm,
+            parallelism: parallelism,
+            heartbeat: testLoggingConfig.slowTests.heartbeat,
+            dir: workingDir,
+            tempdir: new File(workingDir, 'temp'),
+            haltOnFailure: haltOnFailure,
+            shuffleOnSlave: shuffleOnSlave
+        ]
+
+        def junit4 = NamespaceBuilder.newInstance(ant, 'junit4')
+        junit4.junit4(attributes) {
+            classpath {
+                pathElement(path: classpath.asPath)
+            }
+            if (enableAssertions) {
+                jvmarg(value: '-ea')
+            }
+            if (enableSystemAssertions) {
+                jvmarg(value: '-esa')
+            }
+            for (String arg : jvmArgs) {
+                jvmarg(value: arg)
+            }
+            fileset(dir: testClassesDir) {
+                for (String includePattern : patternSet.getIncludes()) {
+                    include(name: includePattern)
+                }
+                for (String excludePattern : patternSet.getExcludes()) {
+                    exclude(name: excludePattern)
+                }
+            }
+            for (Map.Entry<String, String> prop : systemProperties) {
+                sysproperty key: prop.getKey(), value: prop.getValue()
+            }
+            makeListeners()
+        }
+    }
+
+    static class ListenersElement extends UnknownElement {
+        AggregatedEventListener[] listeners
+
+        ListenersElement() {
+            super('listeners')
+            setNamespace('junit4')
+            setQName('listeners')
+        }
+
+        public void handleChildren(Object realThing, RuntimeConfigurable wrapper) {
+            assert realThing instanceof ListenersList
+            ListenersList list = (ListenersList)realThing
+
+            for (AggregatedEventListener listener : listeners) {
+                list.addConfigured(listener)
+            }
+        }
+    }
+
+    /**
+     * Makes an ant xml element for 'listeners' just as AntBuilder would, except configuring
+     * the element adds the already created children.
+     */
+    def makeListeners() {
+        def context = ant.getAntXmlContext()
+        def parentWrapper = context.currentWrapper()
+        def parent = parentWrapper.getProxy()
+        UnknownElement element = new ListenersElement(listeners: listenersConfig.listeners)
+        element.setProject(context.getProject())
+        element.setRealThing(logger)
+        ((UnknownElement)parent).addChild(element)
+        RuntimeConfigurable wrapper = new RuntimeConfigurable(element, element.getQName())
+        parentWrapper.addChild(wrapper)
+        return wrapper.getProxy()
+    }
+}

+ 14 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/SlowTestsConfiguration.groovy

@@ -0,0 +1,14 @@
+package com.carrotsearch.gradle.randomizedtesting
+
+class SlowTestsConfiguration {
+    int heartbeat = 0
+    int summarySize = 0
+
+    void heartbeat(int heartbeat) {
+        this.heartbeat = heartbeat
+    }
+
+    void summarySize(int summarySize) {
+        this.summarySize = summarySize
+    }
+}

+ 14 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/StackTraceFiltersConfiguration.groovy

@@ -0,0 +1,14 @@
+package com.carrotsearch.gradle.randomizedtesting
+
+class StackTraceFiltersConfiguration {
+    List<String> patterns = new ArrayList<>()
+    List<String> contains = new ArrayList<>()
+
+    void regex(String pattern) {
+        patterns.add(pattern)
+    }
+
+    void contains(String contain) {
+        contains.add(contain)
+    }
+}

+ 16 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/TestLoggingConfiguration.groovy

@@ -0,0 +1,16 @@
+package com.carrotsearch.gradle.randomizedtesting
+
+import org.gradle.util.ConfigureUtil
+
+class TestLoggingConfiguration {
+    SlowTestsConfiguration slowTests = new SlowTestsConfiguration()
+    StackTraceFiltersConfiguration stackTraceFilters = new StackTraceFiltersConfiguration()
+
+    void slowTests(Closure closure) {
+        ConfigureUtil.configure(closure, slowTests)
+    }
+
+    void stackTraceFilters(Closure closure) {
+        ConfigureUtil.configure(closure, stackTraceFilters)
+    }
+}

+ 69 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/TestProgressLogger.groovy

@@ -0,0 +1,69 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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 com.carrotsearch.gradle.randomizedtesting
+
+import com.carrotsearch.ant.tasks.junit4.JUnit4
+import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.eventbus.Subscribe
+import com.carrotsearch.ant.tasks.junit4.events.aggregated.AggregatedStartEvent
+import com.carrotsearch.ant.tasks.junit4.events.aggregated.AggregatedSuiteResultEvent
+import com.carrotsearch.ant.tasks.junit4.listeners.AggregatedEventListener
+import org.gradle.logging.ProgressLogger
+import org.gradle.logging.ProgressLoggerFactory
+import org.junit.runner.Description
+
+import java.util.concurrent.atomic.AtomicInteger
+
+import static com.carrotsearch.ant.tasks.junit4.FormattingUtils.formatDurationInSeconds
+
+class TestProgressLogger implements AggregatedEventListener {
+
+    /** Factory to build a progress logger when testing starts */
+    ProgressLoggerFactory factory
+    ProgressLogger progressLogger
+    int totalSuites;
+    AtomicInteger suitesCompleted = new AtomicInteger()
+    AtomicInteger testsCompleted = new AtomicInteger()
+    AtomicInteger testsFailed = new AtomicInteger()
+    AtomicInteger testsIgnored = new AtomicInteger()
+
+    @Subscribe
+    void onStart(AggregatedStartEvent e) throws IOException {
+        totalSuites = e.getSuiteCount();
+        progressLogger = factory.newOperation(TestProgressLogger)
+        progressLogger.setDescription('Randomized test runner')
+        progressLogger.started()
+        progressLogger.progress('Starting JUnit4 with ' + e.getSlaveCount() + ' jvms')
+    }
+
+    @Subscribe
+    void onSuiteResult(AggregatedSuiteResultEvent e) throws IOException {
+        final int suitesCompleted = suitesCompleted.incrementAndGet();
+        final int testsCompleted = testsCompleted.addAndGet(e.getDescription().testCount())
+        final int testsFailed = testsFailed.addAndGet(e.getErrorCount() + e.getFailureCount())
+        final int testsIgnored = testsIgnored.addAndGet(e.getIgnoredCount())
+        Description description = e.getDescription()
+        String suiteName = description.getDisplayName();
+        suiteName = suiteName.substring(suiteName.lastIndexOf('.') + 1);
+        progressLogger.progress('Suites [' + suitesCompleted + '/' + totalSuites + '], Tests [' + testsCompleted + '|' + testsFailed + '|' + testsIgnored + '], ' + suiteName + ' on J' + e.getSlave().id + ' in ' + formatDurationInSeconds(e.getExecutionTime()))
+    }
+
+    @Override
+    void setOuter(JUnit4 junit) {}
+}

+ 373 - 0
buildSrc/src/main/groovy/com/carrotsearch/gradle/randomizedtesting/TestReportLogger.groovy

@@ -0,0 +1,373 @@
+package com.carrotsearch.gradle.randomizedtesting
+
+import com.carrotsearch.ant.tasks.junit4.JUnit4
+import com.carrotsearch.ant.tasks.junit4.Pluralize
+import com.carrotsearch.ant.tasks.junit4.TestsSummaryEventListener
+import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Strings
+import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.eventbus.Subscribe
+import com.carrotsearch.ant.tasks.junit4.events.*
+import com.carrotsearch.ant.tasks.junit4.events.aggregated.*
+import com.carrotsearch.ant.tasks.junit4.events.mirrors.FailureMirror
+import com.carrotsearch.ant.tasks.junit4.listeners.AggregatedEventListener
+import com.carrotsearch.ant.tasks.junit4.listeners.StackTraceFilter
+import org.apache.tools.ant.filters.TokenFilter
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.Logger
+import org.junit.runner.Description
+
+import java.util.concurrent.atomic.AtomicInteger
+
+import static com.carrotsearch.ant.tasks.junit4.FormattingUtils.*
+
+class TestReportLogger extends TestsSummaryEventListener implements AggregatedEventListener {
+
+    static final String FAILURE_MARKER = " <<< FAILURES!"
+
+    /** Status names column. */
+    static EnumMap<TestStatus, String> statusNames;
+    static {
+        statusNames = new EnumMap<>(TestStatus.class);
+        for (TestStatus s : TestStatus.values()) {
+            statusNames.put(s,
+                    s == TestStatus.IGNORED_ASSUMPTION
+                            ? "IGNOR/A" : s.toString());
+        }
+    }
+
+    JUnit4 owner
+
+    /** Logger to write the report to */
+    Logger logger
+
+    TestLoggingConfiguration config
+
+    /** Forked concurrent JVM count. */
+    int forkedJvmCount
+
+    /** Format line for JVM ID string. */
+    String jvmIdFormat
+
+    /** Summarize the first N failures at the end. */
+    int showNumFailuresAtEnd = 3
+
+    /** Output stream that logs messages to the given logger */
+    LoggingOutputStream outStream
+    LoggingOutputStream errStream
+
+    /** Display mode for output streams. */
+    static enum OutputMode {
+        /** Always display the output emitted from tests. */
+        ALWAYS,
+        /**
+         * Display the output only if a test/ suite failed. This requires internal buffering
+         * so the output will be shown only after a test completes.
+         */
+        ONERROR,
+        /** Don't display the output, even on test failures. */
+        NEVER
+    }
+    OutputMode outputMode = OutputMode.ONERROR
+
+    /** A list of failed tests, if to be displayed at the end. */
+    List<Description> failedTests = new ArrayList<>()
+
+    /** Stack trace filters. */
+    StackTraceFilter stackFilter = new StackTraceFilter()
+
+    Map<String, Long> suiteTimes = new HashMap<>()
+    boolean slowTestsFound = false
+
+    int totalSuites
+    AtomicInteger suitesCompleted = new AtomicInteger()
+
+    @Subscribe
+    void onStart(AggregatedStartEvent e) throws IOException {
+        this.totalSuites = e.getSuiteCount();
+        StringBuilder info = new StringBuilder('==> Test Info: ')
+        info.append('seed=' + owner.getSeed() + '; ')
+        info.append(Pluralize.pluralize(e.getSlaveCount(), 'jvm') + '=' + e.getSlaveCount() + '; ')
+        info.append(Pluralize.pluralize(e.getSuiteCount(), 'suite') + '=' + e.getSuiteCount())
+        logger.lifecycle(info.toString())
+
+        forkedJvmCount = e.getSlaveCount();
+        jvmIdFormat = " J%-" + (1 + (int) Math.floor(Math.log10(forkedJvmCount))) + "d";
+
+        outStream = new LoggingOutputStream(logger: logger, level: LogLevel.ERROR, prefix: "  1> ")
+        errStream = new LoggingOutputStream(logger: logger, level: LogLevel.ERROR, prefix: "  2> ")
+
+        for (String contains : config.stackTraceFilters.contains) {
+            TokenFilter.ContainsString containsFilter = new TokenFilter.ContainsString()
+            containsFilter.setContains(contains)
+            stackFilter.addContainsString(containsFilter)
+        }
+        for (String pattern : config.stackTraceFilters.patterns) {
+            TokenFilter.ContainsRegex regexFilter = new TokenFilter.ContainsRegex()
+            regexFilter.setPattern(pattern)
+            stackFilter.addContainsRegex(regexFilter)
+        }
+    }
+
+    @Subscribe
+    void onChildBootstrap(ChildBootstrap e) throws IOException {
+        logger.info("Started J" + e.getSlave().id + " PID(" + e.getSlave().getPidString() + ").");
+    }
+
+    @Subscribe
+    void onHeartbeat(HeartBeatEvent e) throws IOException {
+        logger.warn("HEARTBEAT J" + e.getSlave().id + " PID(" + e.getSlave().getPidString() + "): " +
+                formatTime(e.getCurrentTime()) + ", stalled for " +
+                formatDurationInSeconds(e.getNoEventDuration()) + " at: " +
+                (e.getDescription() == null ? "<unknown>" : formatDescription(e.getDescription())))
+        slowTestsFound = true
+    }
+
+    @Subscribe
+    void onQuit(AggregatedQuitEvent e) throws IOException {
+        if (showNumFailuresAtEnd > 0 && !failedTests.isEmpty()) {
+            List<Description> sublist = this.failedTests
+            StringBuilder b = new StringBuilder()
+            b.append('Tests with failures')
+            if (sublist.size() > showNumFailuresAtEnd) {
+                sublist = sublist.subList(0, showNumFailuresAtEnd)
+                b.append(" (first " + showNumFailuresAtEnd + " out of " + failedTests.size() + ")")
+            }
+            b.append(':\n')
+            for (Description description : sublist) {
+                b.append("  - ").append(formatDescription(description, true)).append('\n')
+            }
+            logger.warn(b.toString())
+        }
+        if (config.slowTests.summarySize > 0) {
+            List<Map.Entry<String, Long>> sortedSuiteTimes = new ArrayList<>(suiteTimes.entrySet())
+            Collections.sort(sortedSuiteTimes, new Comparator<Map.Entry<String, Long>>() {
+                @Override
+                int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> o2) {
+                    return o2.value - o1.value // sort descending
+                }
+            })
+            LogLevel level = slowTestsFound ? LogLevel.WARN : LogLevel.INFO
+            int numToLog = Math.min(config.slowTests.summarySize, sortedSuiteTimes.size())
+            logger.log(level, 'Slow Tests Summary:')
+            for (int i = 0; i < numToLog; ++i) {
+                logger.log(level, String.format(Locale.ENGLISH, '%6.2fs | %s',
+                        sortedSuiteTimes.get(i).value / 1000.0,
+                        sortedSuiteTimes.get(i).key));
+            }
+            logger.log(level, '') // extra vertical separation
+        }
+        if (failedTests.isEmpty()) {
+            // summary is already printed for failures
+            logger.lifecycle('==> Test Summary: ' + getResult().toString())
+        }
+    }
+
+    @Subscribe
+    void onSuiteStart(AggregatedSuiteStartedEvent e) throws IOException {
+        if (isPassthrough()) {
+            SuiteStartedEvent evt = e.getSuiteStartedEvent();
+            emitSuiteStart(LogLevel.INFO, evt.getDescription());
+        }
+    }
+
+    @Subscribe
+    void onOutput(PartialOutputEvent e) throws IOException {
+        if (isPassthrough() && logger.isInfoEnabled()) {
+            // We only allow passthrough output if there is one JVM.
+            switch (e.getEvent().getType()) {
+                case EventType.APPEND_STDERR:
+                    ((IStreamEvent) e.getEvent()).copyTo(errStream);
+                    break;
+                case EventType.APPEND_STDOUT:
+                    ((IStreamEvent) e.getEvent()).copyTo(outStream);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    @Subscribe
+    void onTestResult(AggregatedTestResultEvent e) throws IOException {
+        if (isPassthrough() && e.getStatus() != TestStatus.OK) {
+            flushOutput();
+            emitStatusLine(LogLevel.ERROR, e, e.getStatus(), e.getExecutionTime());
+        }
+
+        if (!e.isSuccessful()) {
+            failedTests.add(e.getDescription());
+        }
+    }
+
+    @Subscribe
+    void onSuiteResult(AggregatedSuiteResultEvent e) throws IOException {
+        try {
+        final int completed = suitesCompleted.incrementAndGet();
+
+        if (e.isSuccessful() && e.getTests().isEmpty()) {
+            return;
+        }
+        if (config.slowTests.summarySize > 0) {
+            suiteTimes.put(e.getDescription().getDisplayName(), e.getExecutionTime())
+        }
+
+        LogLevel level = e.isSuccessful() ? LogLevel.INFO : LogLevel.ERROR
+        // We must emit buffered test and stream events (in case of failures).
+        if (!isPassthrough()) {
+            emitSuiteStart(level, e.getDescription())
+            emitBufferedEvents(level, e)
+        }
+
+        // Emit a synthetic failure for suite-level errors, if any.
+        if (!e.getFailures().isEmpty()) {
+            emitStatusLine(level, e, TestStatus.ERROR, 0)
+        }
+
+        if (!e.getFailures().isEmpty()) {
+            failedTests.add(e.getDescription())
+        }
+
+        emitSuiteEnd(level, e, completed)
+    } catch (Exception exc) {
+            logger.lifecycle('EXCEPTION: ', exc)
+        }
+    }
+
+    /** Suite prologue. */
+    void emitSuiteStart(LogLevel level, Description description) throws IOException {
+        logger.log(level, 'Suite: ' + description.getDisplayName());
+    }
+
+    void emitBufferedEvents(LogLevel level, AggregatedSuiteResultEvent e) throws IOException {
+        if (outputMode == OutputMode.NEVER) {
+            return
+        }
+
+        final IdentityHashMap<TestFinishedEvent,AggregatedTestResultEvent> eventMap = new IdentityHashMap<>();
+        for (AggregatedTestResultEvent tre : e.getTests()) {
+            eventMap.put(tre.getTestFinishedEvent(), tre)
+        }
+
+        final boolean emitOutput = outputMode == OutputMode.ALWAYS && isPassthrough() == false ||
+                                   outputMode == OutputMode.ONERROR && e.isSuccessful() == false
+
+        for (IEvent event : e.getEventStream()) {
+            switch (event.getType()) {
+                case EventType.APPEND_STDOUT:
+                    if (emitOutput) ((IStreamEvent) event).copyTo(outStream);
+                    break;
+
+                case EventType.APPEND_STDERR:
+                    if (emitOutput) ((IStreamEvent) event).copyTo(errStream);
+                    break;
+
+                case EventType.TEST_FINISHED:
+                    assert eventMap.containsKey(event)
+                    final AggregatedTestResultEvent aggregated = eventMap.get(event);
+                    if (aggregated.getStatus() != TestStatus.OK) {
+                        flushOutput();
+                        emitStatusLine(level, aggregated, aggregated.getStatus(), aggregated.getExecutionTime());
+                    }
+
+                default:
+                    break;
+            }
+        }
+
+        if (emitOutput) {
+            flushOutput()
+        }
+    }
+
+    void emitSuiteEnd(LogLevel level, AggregatedSuiteResultEvent e, int suitesCompleted) throws IOException {
+
+        final StringBuilder b = new StringBuilder();
+        b.append(String.format(Locale.ENGLISH, 'Completed [%d/%d]%s in %.2fs, ',
+                suitesCompleted,
+                totalSuites,
+                e.getSlave().slaves > 1 ? ' on J' + e.getSlave().id : '',
+                e.getExecutionTime() / 1000.0d));
+        b.append(e.getTests().size()).append(Pluralize.pluralize(e.getTests().size(), ' test'));
+
+        int failures = e.getFailureCount();
+        if (failures > 0) {
+            b.append(', ').append(failures).append(Pluralize.pluralize(failures, ' failure'));
+        }
+
+        int errors = e.getErrorCount();
+        if (errors > 0) {
+            b.append(', ').append(errors).append(Pluralize.pluralize(errors, ' error'));
+        }
+
+        int ignored = e.getIgnoredCount();
+        if (ignored > 0) {
+            b.append(', ').append(ignored).append(' skipped');
+        }
+
+        if (!e.isSuccessful()) {
+            b.append(' <<< FAILURES!');
+        }
+
+        b.append('\n')
+        logger.log(level, b.toString());
+    }
+
+    /** Emit status line for an aggregated event. */
+    void emitStatusLine(LogLevel level, AggregatedResultEvent result, TestStatus status, long timeMillis) throws IOException {
+        final StringBuilder line = new StringBuilder();
+
+        line.append(Strings.padEnd(statusNames.get(status), 8, ' ' as char))
+        line.append(formatDurationInSeconds(timeMillis))
+        if (forkedJvmCount > 1) {
+            line.append(String.format(Locale.ENGLISH, jvmIdFormat, result.getSlave().id))
+        }
+        line.append(' | ')
+
+        line.append(formatDescription(result.getDescription()))
+        if (!result.isSuccessful()) {
+            line.append(FAILURE_MARKER)
+        }
+        logger.log(level, line.toString())
+
+        PrintWriter writer = new PrintWriter(new LoggingOutputStream(logger: logger, level: level, prefix: '   > '))
+
+        if (status == TestStatus.IGNORED && result instanceof AggregatedTestResultEvent) {
+            writer.write('Cause: ')
+            writer.write(((AggregatedTestResultEvent) result).getCauseForIgnored())
+            writer.flush()
+        }
+
+        final List<FailureMirror> failures = result.getFailures();
+        if (!failures.isEmpty()) {
+            int count = 0;
+            for (FailureMirror fm : failures) {
+                count++;
+                if (fm.isAssumptionViolation()) {
+                    writer.write(String.format(Locale.ENGLISH,
+                            'Assumption #%d: %s',
+                            count, fm.getMessage() == null ? '(no message)' : fm.getMessage()));
+                } else {
+                    writer.write(String.format(Locale.ENGLISH,
+                            'Throwable #%d: %s',
+                            count,
+                            stackFilter.apply(fm.getTrace())));
+                }
+            }
+            writer.flush()
+        }
+    }
+
+    void flushOutput() throws IOException {
+        outStream.flush()
+        errStream.flush()
+    }
+
+    /** Returns true if output should be logged immediately. Only relevant when running with INFO log level. */
+    boolean isPassthrough() {
+        return forkedJvmCount == 1 && outputMode == OutputMode.ALWAYS && logger.isInfoEnabled()
+    }
+
+    @Override
+    void setOuter(JUnit4 task) {
+        owner = task
+    }
+}

+ 135 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy

@@ -0,0 +1,135 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle
+
+import org.elasticsearch.gradle.precommit.PrecommitTasks
+import org.gradle.api.JavaVersion
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.tasks.compile.JavaCompile
+
+/**
+ * Encapsulates build configuration for elasticsearch projects.
+ */
+class BuildPlugin implements Plugin<Project> {
+
+    @Override
+    void apply(Project project) {
+        project.pluginManager.apply('java')
+        project.pluginManager.apply('carrotsearch.randomizedtesting')
+
+        configureCompile(project)
+        configureTest(project)
+        PrecommitTasks.configure(project)
+    }
+
+    /** Adds compiler settings to the project */
+    static void configureCompile(Project project) {
+        project.afterEvaluate {
+            // fail on all javac warnings
+            project.tasks.withType(JavaCompile) {
+                options.compilerArgs << '-Werror' << '-Xlint:all' << '-Xdoclint:all/private' << '-Xdoclint:-missing'
+                options.encoding = 'UTF-8'
+            }
+        }
+    }
+
+    /** Returns a closure of common configuration shared by unit and integration tests. */
+    static Closure commonTestConfig(Project project) {
+        return {
+            jvm System.getProperty("java.home") + File.separator + 'bin' + File.separator + 'java'
+            parallelism System.getProperty('tests.jvms', 'auto')
+
+            // TODO: why are we not passing maxmemory to junit4?
+            jvmArg '-Xmx' + System.getProperty('tests.heap.size', '512m')
+            jvmArg '-Xms' + System.getProperty('tests.heap.size', '512m')
+            if (JavaVersion.current().isJava7()) {
+                // some tests need a large permgen, but that only exists on java 7
+                jvmArg '-XX:MaxPermSize=128m'
+            }
+            jvmArg '-XX:MaxDirectMemorySize=512m'
+            jvmArg '-XX:+HeapDumpOnOutOfMemoryError'
+            File heapdumpDir = new File(project.buildDir, 'heapdump')
+            heapdumpDir.mkdirs()
+            jvmArg '-XX:HeapDumpPath=' + heapdumpDir
+
+            // we use './temp' since this is per JVM and tests are forbidden from writing to CWD
+            systemProperty 'java.io.tmpdir', './temp'
+            systemProperty 'java.awt.headless', 'true'
+            systemProperty 'tests.maven', 'true' // TODO: rename this once we've switched to gradle!
+            systemProperty 'tests.artifact', project.name
+            systemProperty 'tests.task', path
+            systemProperty 'tests.security.manager', 'true'
+            // default test sysprop values
+            systemProperty 'tests.ifNoTests', 'fail'
+            systemProperty 'es.logger.level', 'WARN'
+            for (Map.Entry<String, String> property : System.properties.entrySet()) {
+                if (property.getKey().startsWith('tests.') ||
+                    property.getKey().startsWith('es.')) {
+                    systemProperty property.getKey(), property.getValue()
+                }
+            }
+
+            // System assertions (-esa) are disabled for now because of what looks like a
+            // JDK bug triggered by Groovy on JDK7. We should look at re-enabling system
+            // assertions when we upgrade to a new version of Groovy (currently 2.4.4) or
+            // require JDK8. See https://issues.apache.org/jira/browse/GROOVY-7528.
+            enableSystemAssertions false
+
+            testLogging {
+                slowTests {
+                    heartbeat 10
+                    summarySize 5
+                }
+                stackTraceFilters {
+                    // custom filters: we carefully only omit test infra noise here
+                    contains '.SlaveMain.'
+                    regex(/^(\s+at )(org\.junit\.)/)
+                    // also includes anonymous classes inside these two:
+                    regex(/^(\s+at )(com\.carrotsearch\.randomizedtesting\.RandomizedRunner)/)
+                    regex(/^(\s+at )(com\.carrotsearch\.randomizedtesting\.ThreadLeakControl)/)
+                    regex(/^(\s+at )(com\.carrotsearch\.randomizedtesting\.rules\.)/)
+                    regex(/^(\s+at )(org\.apache\.lucene\.util\.TestRule)/)
+                    regex(/^(\s+at )(org\.apache\.lucene\.util\.AbstractBeforeAfterRule)/)
+                }
+            }
+
+            balancers {
+                executionTime cacheFilename: ".local-${project.version}-${name}-execution-times.log"
+            }
+
+            listeners {
+                junitReport()
+            }
+
+            exclude '**/*$*.class'
+        }
+    }
+
+    /** Configures the test task */
+    static Task configureTest(Project project) {
+        Task test = project.tasks.getByName('test')
+        test.configure(commonTestConfig(project))
+        test.configure {
+            include '**/*Tests.class'
+        }
+        return test
+    }
+}

+ 35 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/ElasticsearchProperties.groovy

@@ -0,0 +1,35 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle
+
+/**
+ * Accessor for properties about the version of elasticsearch this was built with.
+ */
+class ElasticsearchProperties {
+    static final String version
+    static {
+        Properties props = new Properties()
+        InputStream propsStream = ElasticsearchProperties.class.getResourceAsStream('/elasticsearch.properties')
+        if (propsStream == null) {
+            throw new RuntimeException('/elasticsearch.properties resource missing')
+        }
+        props.load(propsStream)
+        version = props.getProperty('version')
+    }
+}

+ 45 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/MavenFilteringHack.groovy

@@ -0,0 +1,45 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle
+
+import org.apache.tools.ant.filters.ReplaceTokens
+import org.gradle.api.file.CopySpec
+
+/**
+ * Gradle provides "expansion" functionality using groovy's SimpleTemplatingEngine (TODO: check name).
+ * However, it allows substitutions of the form {@code $foo} (no curlies). Rest tests provide
+ * some substitution from the test runner, which this form is used for.
+ *
+ * This class provides a helper to do maven filtering, where only the form {@code $\{foo\}} is supported.
+ *
+ * TODO: we should get rid of this hack, and make the rest tests use some other identifier
+ * for builtin vars
+ */
+class MavenFilteringHack {
+    /**
+     * Adds a filter to the given copy spec that will substitute maven variables.
+     * @param CopySpec
+     */
+    static void filter(CopySpec copySpec, Map substitutions) {
+        Map mavenSubstitutions = substitutions.collectEntries() {
+            key, value -> ["{${key}".toString(), value.toString()]
+        }
+        copySpec.filter(ReplaceTokens, tokens: mavenSubstitutions, beginToken: '$', endToken: '}')
+    }
+}

+ 109 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginBuildPlugin.groovy

@@ -0,0 +1,109 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.plugin
+
+import nebula.plugin.extraconfigurations.ProvidedBasePlugin
+import org.elasticsearch.gradle.BuildPlugin
+import org.elasticsearch.gradle.ElasticsearchProperties
+import org.elasticsearch.gradle.test.RestIntegTestTask
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.tasks.bundling.Zip
+
+/**
+ * Encapsulates build configuration for an Elasticsearch plugin.
+ */
+class PluginBuildPlugin extends BuildPlugin {
+
+    @Override
+    void apply(Project project) {
+        super.apply(project)
+        project.pluginManager.apply(ProvidedBasePlugin)
+        // TODO: add target compatibility (java version) to elasticsearch properties and set for the project
+        configureDependencies(project)
+        // this afterEvaluate must happen before the afterEvaluate added by integTest configure,
+        // so that the file name resolution for installing the plugin will be setup
+        project.afterEvaluate {
+            project.jar.configure {
+                baseName project.pluginProperties.extension.name
+            }
+            project.bundlePlugin.configure {
+                baseName project.pluginProperties.extension.name
+            }
+            project.integTest.configure {
+                dependsOn project.bundlePlugin
+                cluster {
+                    plugin 'installPlugin', project.bundlePlugin.outputs.files
+                }
+            }
+        }
+        Task bundle = configureBundleTask(project)
+        RestIntegTestTask.configure(project)
+        project.configurations.archives.artifacts.removeAll { it.archiveTask.is project.jar }
+        project.configurations.getByName('default').extendsFrom = []
+        project.artifacts {
+            archives bundle
+            'default' bundle
+        }
+    }
+
+    static void configureDependencies(Project project) {
+        String elasticsearchVersion = ElasticsearchProperties.version
+        project.dependencies {
+            provided "org.elasticsearch:elasticsearch:${elasticsearchVersion}"
+            testCompile "org.elasticsearch:test-framework:${elasticsearchVersion}"
+            // we "upgrade" these optional deps to provided for plugins, since they will run
+            // with a full elasticsearch server that includes optional deps
+            // TODO: remove duplication of version here with core...
+            provided 'com.spatial4j:spatial4j:0.4.1'
+            provided 'com.vividsolutions:jts:1.13'
+            provided 'com.github.spullara.mustache.java:compiler:0.9.1'
+            provided "log4j:log4j:1.2.17"
+            provided "log4j:apache-log4j-extras:1.2.17"
+            provided "org.slf4j:slf4j-api:1.6.2"
+            provided 'net.java.dev.jna:jna:4.1.0'
+        }
+    }
+
+    static Task configureBundleTask(Project project) {
+        PluginPropertiesTask buildProperties = project.tasks.create(name: 'pluginProperties', type: PluginPropertiesTask)
+        File pluginMetadata = project.file("src/main/plugin-metadata")
+        project.processTestResources {
+            from buildProperties
+            from pluginMetadata
+        }
+        Task bundle = project.tasks.create(name: 'bundlePlugin', type: Zip, dependsOn: [project.jar, buildProperties])
+        bundle.configure {
+            from buildProperties
+            from pluginMetadata
+            from project.jar
+            from bundle.project.configurations.runtime - bundle.project.configurations.provided
+            from('src/main/packaging') // TODO: move all config/bin/_size/etc into packaging
+            from('src/main') {
+                include 'config/**'
+                include 'bin/**'
+            }
+            from('src/site') {
+                include '_site/**'
+            }
+        }
+        project.assemble.dependsOn(bundle)
+        return bundle
+    }
+}

+ 56 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginPropertiesExtension.groovy

@@ -0,0 +1,56 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.plugin
+
+import org.gradle.api.Project
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+
+/**
+ * A container for plugin properties that will be written to the plugin descriptor, for easy
+ * manipulation in the gradle DSL.
+ */
+class PluginPropertiesExtension {
+
+    @Input
+    String name
+
+    @Input
+    String version
+
+    @Input
+    String description
+
+    @Input
+    boolean jvm = true
+
+    @Input
+    String classname
+
+    @Input
+    boolean site = false
+
+    @Input
+    boolean isolated = true
+
+    PluginPropertiesExtension(Project project) {
+        name = project.name
+        version = project.version
+    }
+}

+ 95 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginPropertiesTask.groovy

@@ -0,0 +1,95 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.plugin
+
+import org.elasticsearch.gradle.ElasticsearchProperties
+import org.gradle.api.DefaultTask
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Creates a plugin descriptor.
+ *
+ * TODO: copy the example properties file to plugin documentation
+ */
+class PluginPropertiesTask extends DefaultTask {
+
+    PluginPropertiesExtension extension
+    Map<String, String> properties = new HashMap<>()
+
+    PluginPropertiesTask() {
+        extension = project.extensions.create('esplugin', PluginPropertiesExtension, project)
+        project.afterEvaluate {
+            if (extension.description == null) {
+                throw new InvalidUserDataException('description is a required setting for esplugin')
+            }
+            if (extension.jvm && extension.classname == null) {
+                throw new InvalidUserDataException('classname is a required setting for esplugin with jvm=true')
+            }
+            if (extension.jvm) {
+                dependsOn(project.classes) // so we can check for the classname
+            }
+            fillProperties()
+            configure {
+                inputs.properties(properties)
+            }
+        }
+    }
+
+    @OutputFile
+    File propertiesFile = new File(project.buildDir, "plugin" + File.separator + "plugin-descriptor.properties")
+
+    void fillProperties() {
+        // TODO: need to copy the templated plugin-descriptor with a dependent task, since copy requires a file (not uri)
+        properties = [
+            'name': extension.name,
+            'description': extension.description,
+            'version': extension.version,
+            'elasticsearch.version': ElasticsearchProperties.version,
+            'jvm': extension.jvm as String,
+            'site': extension.site as String
+        ]
+        if (extension.jvm) {
+            properties['classname'] = extension.classname
+            properties['isolated'] = extension.isolated as String
+            properties['java.version'] = project.targetCompatibility as String
+        }
+    }
+
+    @TaskAction
+    void buildProperties() {
+        if (extension.jvm) {
+            File classesDir = project.sourceSets.main.output.classesDir
+            File classFile = new File(classesDir, extension.classname.replace('.', File.separator) + '.class')
+            if (classFile.exists() == false) {
+                throw new InvalidUserDataException('classname ' + extension.classname + ' does not exist')
+            }
+            if (extension.isolated == false) {
+                logger.warn('Disabling isolation is deprecated and will be removed in the future')
+            }
+        }
+
+        Properties props = new Properties()
+        for (Map.Entry<String, String> prop : properties) {
+            props.put(prop.getKey(), prop.getValue())
+        }
+        props.store(propertiesFile.newWriter(), null)
+    }
+}

+ 189 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/DependencyLicensesTask.groovy

@@ -0,0 +1,189 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.precommit
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.StopActionException
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.VerificationTask
+
+import java.nio.file.Files
+import java.security.MessageDigest
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+class DependencyLicensesTask extends DefaultTask {
+    static final String SHA_EXTENSION = '.sha1'
+
+    static Task configure(Project project, Closure closure) {
+        DependencyLicensesTask task = project.tasks.create(type: DependencyLicensesTask, name: 'dependencyLicenses')
+        UpdateShasTask update = project.tasks.create(type: UpdateShasTask, name: 'updateShas')
+        update.parentTask = task
+        task.configure(closure)
+        project.check.dependsOn(task)
+        return task
+    }
+
+    @InputFiles
+    FileCollection dependencies
+
+    @InputDirectory
+    File licensesDir = new File(project.projectDir, 'licenses')
+
+    LinkedHashMap<String, String> mappings = new LinkedHashMap<>()
+
+    @Input
+    void mapping(Map<String, String> props) {
+        String from = props.get('from')
+        if (from == null) {
+            throw new InvalidUserDataException('Missing "from" setting for license name mapping')
+        }
+        String to = props.get('to')
+        if (to == null) {
+            throw new InvalidUserDataException('Missing "to" setting for license name mapping')
+        }
+        mappings.put(from, to)
+    }
+
+    @TaskAction
+    void checkDependencies() {
+        // TODO: empty license dir (or error when dir exists and no deps)
+        if (licensesDir.exists() == false && dependencies.isEmpty() == false) {
+            throw new GradleException("Licences dir ${licensesDir} does not exist, but there are dependencies")
+        }
+
+        // order is the same for keys and values iteration since we use a linked hashmap
+        List<String> mapped = new ArrayList<>(mappings.values())
+        Pattern mappingsPattern = Pattern.compile('(' + mappings.keySet().join(')|(') + ')')
+        Map<String, Integer> licenses = new HashMap<>()
+        Map<String, Integer> notices = new HashMap<>()
+        Set<File> shaFiles = new HashSet<File>()
+
+        licensesDir.eachFile {
+            String name = it.getName()
+            if (name.endsWith(SHA_EXTENSION)) {
+                shaFiles.add(it)
+            } else if (name.endsWith('-LICENSE') || name.endsWith('-LICENSE.txt')) {
+                // TODO: why do we support suffix of LICENSE *and* LICENSE.txt??
+                licenses.put(name, 0)
+            } else if (name.contains('-NOTICE') || name.contains('-NOTICE.txt')) {
+                notices.put(name, 0)
+            }
+        }
+
+        for (File dependency : dependencies) {
+            String jarName = dependency.getName()
+            logger.info("Checking license/notice/sha for " + jarName)
+            checkSha(dependency, jarName, shaFiles)
+
+            String name = jarName - ~/\-\d+.*/
+            Matcher match = mappingsPattern.matcher(name)
+            if (match.matches()) {
+                int i = 0
+                while (i < match.groupCount() && match.group(i + 1) == null) ++i;
+                logger.info("Mapped dependency name ${name} to ${mapped.get(i)} for license check")
+                name = mapped.get(i)
+            }
+            checkFile(name, jarName, licenses, 'LICENSE')
+            checkFile(name, jarName, notices, 'NOTICE')
+        }
+
+        licenses.each { license, count ->
+            if (count == 0) {
+                throw new GradleException("Unused license ${license}")
+            }
+        }
+        notices.each { notice, count ->
+            if (count == 0) {
+                throw new GradleException("Unused notice ${notice}")
+            }
+        }
+        if (shaFiles.isEmpty() == false) {
+            throw new GradleException("Unused sha files found: \n${shaFiles.join('\n')}")
+        }
+    }
+
+    void checkSha(File jar, String jarName, Set<File> shaFiles) {
+        File shaFile = new File(licensesDir, jarName + SHA_EXTENSION)
+        if (shaFile.exists() == false) {
+            throw new GradleException("Missing SHA for ${jarName}. Run 'gradle updateSHAs' to create")
+        }
+        // TODO: shouldn't have to trim, sha files should not have trailing newline
+        String expectedSha = shaFile.getText('UTF-8').trim()
+        String sha = MessageDigest.getInstance("SHA-1").digest(jar.getBytes()).encodeHex().toString()
+        if (expectedSha.equals(sha) == false) {
+            throw new GradleException("SHA has changed! Expected ${expectedSha} for ${jarName} but got ${sha}. " +
+                                      "\nThis usually indicates a corrupt dependency cache or artifacts changed upstream." +
+                                      "\nEither wipe your cache, fix the upstream artifact, or delete ${shaFile} and run updateShas")
+        }
+        shaFiles.remove(shaFile)
+    }
+
+    void checkFile(String name, String jarName, Map<String, Integer> counters, String type) {
+        String fileName = "${name}-${type}"
+        Integer count = counters.get(fileName)
+        if (count == null) {
+            // try the other suffix...TODO: get rid of this, just support ending in .txt
+            fileName = "${fileName}.txt"
+            counters.get(fileName)
+        }
+        count = counters.get(fileName)
+        if (count == null) {
+            throw new GradleException("Missing ${type} for ${jarName}, expected in ${fileName}")
+        }
+        counters.put(fileName, count + 1)
+    }
+
+    static class UpdateShasTask extends DefaultTask {
+        DependencyLicensesTask parentTask
+        @TaskAction
+        void updateShas() {
+            Set<File> shaFiles = new HashSet<File>()
+            parentTask.licensesDir.eachFile {
+                String name = it.getName()
+                if (name.endsWith(SHA_EXTENSION)) {
+                    shaFiles.add(it)
+                }
+            }
+            for (File dependency : parentTask.dependencies) {
+                String jarName = dependency.getName()
+                File shaFile = new File(parentTask.licensesDir, jarName + SHA_EXTENSION)
+                if (shaFile.exists() == false) {
+                    logger.lifecycle("Adding sha for ${jarName}")
+                    String sha = MessageDigest.getInstance("SHA-1").digest(dependency.getBytes()).encodeHex().toString()
+                    shaFile.setText(sha, 'UTF-8')
+                } else {
+                    shaFiles.remove(shaFile)
+                }
+            }
+            shaFiles.each { shaFile ->
+                logger.lifecycle("Removing unused sha ${shaFile.getName()}")
+                Files.delete(shaFile.toPath())
+            }
+        }
+    }
+}

+ 108 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/ForbiddenPatternsTask.groovy

@@ -0,0 +1,108 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.precommit
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputFiles
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.util.PatternFilterable
+import org.gradle.api.tasks.util.PatternSet
+
+import java.util.regex.Pattern
+
+/**
+ * Checks for patterns in source files for the project which are forbidden.
+ */
+class ForbiddenPatternsTask extends DefaultTask {
+    Map<String,String> patterns = new LinkedHashMap<>()
+    PatternFilterable filesFilter = new PatternSet()
+
+    ForbiddenPatternsTask() {
+        // we always include all source files, and exclude what should not be checked
+        filesFilter.include('**')
+        // exclude known binary extensions
+        filesFilter.exclude('**/*.gz')
+        filesFilter.exclude('**/*.ico')
+        filesFilter.exclude('**/*.jar')
+        filesFilter.exclude('**/*.zip')
+        filesFilter.exclude('**/*.jks')
+        filesFilter.exclude('**/*.crt')
+        filesFilter.exclude('**/*.png')
+
+        // TODO: add compile and test compile outputs as this tasks outputs, so we don't rerun when source files haven't changed
+    }
+
+    /** Adds a file glob pattern to be excluded */
+    void exclude(String... excludes) {
+        this.filesFilter.exclude(excludes)
+    }
+
+    /** Adds pattern to forbid */
+    void rule(Map<String,String> props) {
+        String name = props.get('name')
+        if (name == null) {
+            throw new IllegalArgumentException('Missing [name] for invalid pattern rule')
+        }
+        String pattern = props.get('pattern')
+        if (pattern == null) {
+            throw new IllegalArgumentException('Missing [pattern] for invalid pattern rule')
+        }
+        // TODO: fail if pattern contains a newline, it won't work (currently)
+        patterns.put(name, pattern)
+    }
+
+    /** Returns the files this task will check */
+    @InputFiles
+    FileCollection files() {
+        List<FileCollection> collections = new ArrayList<>()
+        for (SourceSet sourceSet : project.sourceSets) {
+            collections.add(sourceSet.allSource.matching(filesFilter))
+        }
+        return project.files(collections.toArray())
+    }
+
+    @TaskAction
+    void checkInvalidPatterns() {
+        Pattern allPatterns = Pattern.compile('(' + patterns.values().join(')|(') + ')')
+        List<String> failures = new ArrayList<>()
+        for (File f : files()) {
+            f.eachLine('UTF-8') { line, lineNumber ->
+                if (allPatterns.matcher(line).find()) {
+                    addErrorMessages(failures, f, (String)line, (int)lineNumber)
+                }
+            }
+        }
+        if (failures.isEmpty() == false) {
+            throw new IllegalArgumentException('Found invalid patterns:\n' + failures.join('\n'))
+        }
+    }
+
+    // iterate through patterns to find the right ones for nice error messages
+    void addErrorMessages(List<String> failures, File f, String line, int lineNumber) {
+        String path = project.getRootProject().projectDir.toURI().relativize(f.toURI()).toString()
+        for (Map.Entry<String,String> pattern : patterns.entrySet()) {
+            if (Pattern.compile(pattern.value).matcher(line).find()) {
+                failures.add('- ' + pattern.key + ' on line ' + lineNumber + ' of ' + path)
+            }
+        }
+    }
+}

+ 93 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy

@@ -0,0 +1,93 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.precommit
+
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.TaskContainer
+
+/**
+ * Validation tasks which should be run before committing. These run before tests.
+ */
+class PrecommitTasks {
+
+    /** Adds a precommit task, which depends on non-test verification tasks. */
+    static void configure(Project project) {
+        List precommitTasks = [
+                configureForbiddenApis(project),
+                configureForbiddenPatterns(project.tasks)]
+
+        Map precommitOptions = [
+                name: 'precommit',
+                group: JavaBasePlugin.VERIFICATION_GROUP,
+                description: 'Runs all non-test checks.',
+                dependsOn: precommitTasks
+        ]
+        Task precommit = project.tasks.create(precommitOptions)
+        project.check.dependsOn(precommit)
+
+        // delay ordering relative to test tasks, since they may not be setup yet
+        project.afterEvaluate {
+            Task test = project.tasks.findByName('test')
+            if (test != null) {
+                test.mustRunAfter(precommit)
+            }
+            Task integTest = project.tasks.findByName('integTest')
+            if (integTest != null) {
+                integTest.mustRunAfter(precommit)
+            }
+        }
+    }
+
+    static Task configureForbiddenApis(Project project) {
+        project.pluginManager.apply('de.thetaphi.forbiddenapis')
+        project.forbiddenApis {
+            internalRuntimeForbidden = true
+            failOnUnsupportedJava = false
+            bundledSignatures = ['jdk-unsafe', 'jdk-deprecated']
+            signaturesURLs = [getClass().getResource('/forbidden/all-signatures.txt')]
+            suppressAnnotations = ['**.SuppressForbidden']
+        }
+        project.tasks.findByName('forbiddenApisMain').configure {
+            bundledSignatures += ['jdk-system-out']
+            signaturesURLs += [
+                    getClass().getResource('/forbidden/core-signatures.txt'),
+                    getClass().getResource('/forbidden/third-party-signatures.txt')]
+        }
+        project.tasks.findByName('forbiddenApisTest').configure {
+            signaturesURLs += [getClass().getResource('/forbidden/test-signatures.txt')]
+        }
+        Task forbiddenApis = project.tasks.findByName('forbiddenApis')
+        forbiddenApis.group = "" // clear group, so this does not show up under verification tasks
+        return forbiddenApis
+    }
+
+    static Task configureForbiddenPatterns(TaskContainer tasks) {
+        Map options = [
+                name: 'forbiddenPatterns',
+                type: ForbiddenPatternsTask,
+                description: 'Checks source files for invalid patterns like nocommits or tabs',
+        ]
+        return tasks.create(options) {
+            rule name: 'nocommit', pattern: /nocommit/
+            rule name: 'tab', pattern: /\t/
+        }
+    }
+}

+ 63 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy

@@ -0,0 +1,63 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.test
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.Input
+
+/** Configuration for an elasticsearch cluster, used for integration tests. */
+class ClusterConfiguration {
+
+    @Input
+    int numNodes = 1
+
+    @Input
+    int httpPort = 9400
+
+    @Input
+    int transportPort = 9500
+
+    Map<String, String> systemProperties = new HashMap<>()
+
+    @Input
+    void systemProperty(String property, String value) {
+        systemProperties.put(property, value)
+    }
+
+    LinkedHashMap<String, Object[]> setupCommands = new LinkedHashMap<>()
+
+    @Input
+    void plugin(String name, FileCollection file) {
+        setupCommands.put(name, ['bin/plugin', 'install', new LazyFileUri(file: file)])
+    }
+
+    static class LazyFileUri {
+        FileCollection file
+        @Override
+        String toString() {
+            return file.singleFile.toURI().toURL().toString();
+        }
+
+    }
+
+    @Input
+    void setupCommand(String name, Object... args) {
+        setupCommands.put(name, args)
+    }
+}

+ 199 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy

@@ -0,0 +1,199 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.test
+
+import org.apache.tools.ant.taskdefs.condition.Os
+import org.elasticsearch.gradle.ElasticsearchProperties
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.Delete
+import org.gradle.api.tasks.Exec
+
+/**
+ * A helper for creating tasks to build a cluster that is used by a task, and tear down the cluster when the task is finished.
+ */
+class ClusterFormationTasks {
+
+    /**
+     * Adds dependent tasks to the given task to start a cluster with the given configuration.
+     * Also adds a finalize task to stop the cluster.
+     */
+    static void setup(Project project, Task task, ClusterConfiguration config) {
+        if (task.getEnabled() == false) {
+            // no need to cluster formation if the task won't run!
+            return
+        }
+        addZipConfiguration(project)
+        File clusterDir = new File(project.buildDir, 'cluster' + File.separator + task.name)
+        if (config.numNodes == 1) {
+            addNodeStartupTasks(project, task, config, clusterDir)
+            addNodeStopTask(project, task, clusterDir)
+        } else {
+            for (int i = 0; i < config.numNodes; ++i) {
+                File nodeDir = new File(clusterDir, "node${i}")
+                addNodeStartupTasks(project, task, config, nodeDir)
+                addNodeStopTask(project, task, nodeDir)
+            }
+        }
+    }
+
+    static void addNodeStartupTasks(Project project, Task task, ClusterConfiguration config, File baseDir) {
+        String clusterName = "${task.path.replace(':', '_').substring(1)}"
+        File home = new File(baseDir, "elasticsearch-${ElasticsearchProperties.version}")
+        List setupDependsOn = [project.configurations.elasticsearchZip]
+        setupDependsOn.addAll(task.dependsOn)
+        Task setup = project.tasks.create(name: task.name + '#setup', type: Copy, dependsOn: setupDependsOn) {
+            from { project.zipTree(project.configurations.elasticsearchZip.singleFile) }
+            into baseDir
+        }
+        // chain setup tasks to maintain their order
+        setup = project.tasks.create(name: "${task.name}#clean", type: Delete, dependsOn: setup) {
+            delete new File(home, 'plugins'), new File(home, 'data'), new File(home, 'logs')
+        }
+        setup = project.tasks.create(name: "${task.name}#configure", type: DefaultTask, dependsOn: setup) << {
+            File configFile = new File(home, 'config' + File.separator + 'elasticsearch.yml')
+            logger.info("Configuring ${configFile}")
+            configFile.setText("cluster.name: ${clusterName}", 'UTF-8')
+        }
+        for (Map.Entry<String, String> command : config.setupCommands.entrySet()) {
+            Task nextSetup = project.tasks.create(name: "${task.name}#${command.getKey()}", type: Exec, dependsOn: setup) {
+                workingDir home
+                if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+                    executable 'cmd'
+                    args '/C', 'call'
+                } else {
+                    executable 'sh'
+                }
+                args command.getValue()
+                // only show output on failure, when not in info or debug mode
+                if (logger.isInfoEnabled() == false) {
+                    standardOutput = new ByteArrayOutputStream()
+                    errorOutput = standardOutput
+                    ignoreExitValue = true
+                    doLast {
+                        if (execResult.exitValue != 0) {
+                            logger.error(standardOutput.toString())
+                            throw new GradleException("Process '${command.getValue().join(' ')}' finished with non-zero exit value ${execResult.exitValue}")
+                        }
+                    }
+                }
+            }
+            setup = nextSetup
+        }
+
+        File pidFile = pidFile(baseDir)
+        List esArgs = [
+            "-Des.http.port=${config.httpPort}",
+            "-Des.transport.tcp.port=${config.transportPort}",
+            "-Des.pidfile=${pidFile}",
+            "-Des.path.repo=${home}/repo",
+            "-Des.path.shared_data=${home}/../",
+        ]
+        esArgs.addAll(config.systemProperties.collect {key, value -> "-D${key}=${value}"})
+        Closure esPostStartActions = { ant, logger ->
+            ant.waitfor(maxwait: '30', maxwaitunit: 'second', checkevery: '500', checkeveryunit: 'millisecond', timeoutproperty: "failed${task.name}#start") {
+                and {
+                    resourceexists {
+                        file file: pidFile.toString()
+                    }
+                    http(url: "http://localhost:${config.httpPort}")
+                }
+            }
+            if (ant.properties.containsKey("failed${task.name}#start".toString())) {
+                new File(home, 'logs' + File.separator + clusterName + '.log').eachLine {
+                    line -> logger.error(line)
+                }
+                throw new GradleException('Failed to start elasticsearch')
+            }
+        }
+        Task start;
+        if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+            // elasticsearch.bat is spawned as it has no daemon mode
+            start = project.tasks.create(name: "${task.name}#start", type: DefaultTask, dependsOn: setup) << {
+                // Fall back to Ant exec task as Gradle Exec task does not support spawning yet
+                ant.exec(executable: 'cmd', spawn: true, dir: home) {
+                    (['/C', 'call', 'bin/elasticsearch'] + esArgs).each { arg(value: it) }
+                }
+                esPostStartActions(ant, logger)
+            }
+        } else {
+            start = project.tasks.create(name: "${task.name}#start", type: Exec, dependsOn: setup) {
+                workingDir home
+                executable 'sh'
+                args 'bin/elasticsearch', '-d' // daemonize!
+                args esArgs
+                errorOutput = new ByteArrayOutputStream()
+                doLast {
+                    if (errorOutput.toString().isEmpty() == false) {
+                        logger.error(errorOutput.toString())
+                        new File(home, 'logs' + File.separator + clusterName + '.log').eachLine {
+                            line -> logger.error(line)
+                        }
+                        throw new GradleException('Failed to start elasticsearch')
+                    }
+                    esPostStartActions(ant, logger)
+                }
+            }
+        }
+        task.dependsOn(start)
+    }
+
+    static void addNodeStopTask(Project project, Task task, File baseDir) {
+        LazyPidReader pidFile = new LazyPidReader(pidFile: pidFile(baseDir))
+        Task stop = project.tasks.create(name: task.name + '#stop', type: Exec) {
+            if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+                executable 'Taskkill'
+                args '/PID', pidFile, '/F'
+            } else {
+                executable 'kill'
+                args '-9', pidFile
+            }
+            doLast {
+                // TODO: wait for pid to close, or kill -9 and fail
+            }
+        }
+        task.finalizedBy(stop)
+    }
+
+    /** Delays reading a pid file until needing to use the pid */
+    static class LazyPidReader {
+        File pidFile
+        @Override
+        String toString() {
+            return pidFile.text.stripMargin()
+        }
+    }
+
+    static File pidFile(File dir) {
+        return new File(dir, 'es.pid')
+    }
+
+    static void addZipConfiguration(Project project) {
+        String elasticsearchVersion = ElasticsearchProperties.version
+        project.configurations {
+            elasticsearchZip
+        }
+        project.dependencies {
+            elasticsearchZip "org.elasticsearch.distribution.zip:elasticsearch:${elasticsearchVersion}@zip"
+        }
+    }
+}

+ 90 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/test/RestIntegTestTask.groovy

@@ -0,0 +1,90 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.test
+
+import com.carrotsearch.gradle.randomizedtesting.RandomizedTestingTask
+import org.elasticsearch.gradle.BuildPlugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.Input
+import org.gradle.util.ConfigureUtil
+
+/**
+ * Runs integration tests, but first starts an ES cluster,
+ * and passes the ES cluster info as parameters to the tests.
+ */
+class RestIntegTestTask extends RandomizedTestingTask {
+
+    ClusterConfiguration clusterConfig = new ClusterConfiguration()
+
+    @Input
+    boolean includePackaged = false
+
+    static RestIntegTestTask configure(Project project) {
+        Map integTestOptions = [
+            name: 'integTest',
+            type: RestIntegTestTask,
+            dependsOn: 'testClasses',
+            group: JavaBasePlugin.VERIFICATION_GROUP,
+            description: 'Runs rest tests against an elasticsearch cluster.'
+        ]
+        RestIntegTestTask integTest = project.tasks.create(integTestOptions)
+        integTest.configure(BuildPlugin.commonTestConfig(project))
+        integTest.configure {
+            include '**/*IT.class'
+            systemProperty 'tests.rest.load_packaged', 'false'
+        }
+        RandomizedTestingTask test = project.tasks.findByName('test')
+        if (test != null) {
+            integTest.classpath = test.classpath
+            integTest.testClassesDir = test.testClassesDir
+            integTest.mustRunAfter(test)
+        }
+        project.check.dependsOn(integTest)
+        RestSpecHack.configureDependencies(project)
+        project.afterEvaluate {
+            integTest.dependsOn(RestSpecHack.configureTask(project, integTest.includePackaged))
+        }
+        return integTest
+    }
+
+    RestIntegTestTask() {
+        project.afterEvaluate {
+            Task test = project.tasks.findByName('test')
+            if (test != null) {
+                mustRunAfter(test)
+            }
+            ClusterFormationTasks.setup(project, this, clusterConfig)
+            configure {
+                parallelism '1'
+                systemProperty 'tests.cluster', "localhost:${clusterConfig.transportPort}"
+            }
+        }
+    }
+
+    @Input
+    void cluster(Closure closure) {
+        ConfigureUtil.configure(closure, clusterConfig)
+    }
+
+    ClusterConfiguration getCluster() {
+        return clusterConfig
+    }
+}

+ 75 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/test/RestSpecHack.groovy

@@ -0,0 +1,75 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.test
+
+import org.elasticsearch.gradle.ElasticsearchProperties
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.tasks.Copy
+
+/**
+ * The rest-api-spec tests are loaded from the classpath. However, they
+ * currently must be available on the local filesystem. This class encapsulates
+ * setting up tasks to copy the rest spec api to test resources.
+ */
+class RestSpecHack {
+    /**
+     * Sets dependencies needed to copy the rest spec.
+     * @param project The project to add rest spec dependency to
+     */
+    static void configureDependencies(Project project) {
+        project.configurations {
+            restSpec
+        }
+        project.dependencies {
+            restSpec "org.elasticsearch:rest-api-spec:${ElasticsearchProperties.version}"
+        }
+    }
+
+    /**
+     * Creates a task to copy the rest spec files.
+     *
+     * @param project The project to add the copy task to
+     * @param includePackagedTests true if the packaged tests should be copied, false otherwise
+     */
+    static Task configureTask(Project project, boolean includePackagedTests) {
+        Map copyRestSpecProps = [
+                name     : 'copyRestSpec',
+                type     : Copy,
+                dependsOn: [project.configurations.restSpec, 'processTestResources']
+        ]
+        Task copyRestSpec = project.tasks.create(copyRestSpecProps) {
+            from { project.zipTree(project.configurations.restSpec.singleFile) }
+            include 'rest-api-spec/api/**'
+            if (includePackagedTests) {
+                include 'rest-api-spec/test/**'
+            }
+            into project.sourceSets.test.output.resourcesDir
+        }
+        project.idea {
+            module {
+                if (scopes.TEST != null) {
+                    // TODO: need to add the TEST scope somehow for rest test plugin...
+                    scopes.TEST.plus.add(project.configurations.restSpec)
+                }
+            }
+        }
+        return copyRestSpec
+    }
+}

+ 59 - 0
buildSrc/src/main/groovy/org/elasticsearch/gradle/test/RestTestPlugin.groovy

@@ -0,0 +1,59 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.test
+
+import com.carrotsearch.gradle.randomizedtesting.RandomizedTestingTask
+import org.elasticsearch.gradle.ElasticsearchProperties
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/** Configures the build to have a rest integration test.  */
+class RestTestPlugin implements Plugin<Project> {
+
+    @Override
+    void apply(Project project) {
+        project.pluginManager.apply('java-base')
+        project.pluginManager.apply('carrotsearch.randomizedtesting')
+        project.pluginManager.apply('idea')
+
+        // remove some unnecessary tasks for a qa test
+        project.tasks.removeAll { it.name in ['assemble', 'buildDependents'] }
+
+        // only setup tests to build
+        project.sourceSets {
+            test
+        }
+        project.dependencies {
+            testCompile "org.elasticsearch:test-framework:${ElasticsearchProperties.version}"
+        }
+
+        RandomizedTestingTask integTest = RestIntegTestTask.configure(project)
+        RestSpecHack.configureDependencies(project)
+        integTest.configure {
+            classpath = project.sourceSets.test.runtimeClasspath
+            testClassesDir project.sourceSets.test.output.classesDir
+        }
+
+        project.eclipse {
+            classpath {
+                sourceSets = [project.sourceSets.test]
+            }
+        }
+    }
+}

+ 1 - 0
buildSrc/src/main/resources/META-INF/gradle-plugins/carrotsearch.randomizedtesting.properties

@@ -0,0 +1 @@
+implementation-class=com.carrotsearch.gradle.randomizedtesting.RandomizedTestingPlugin

+ 1 - 0
buildSrc/src/main/resources/META-INF/gradle-plugins/elasticsearch.build.properties

@@ -0,0 +1 @@
+implementation-class=org.elasticsearch.gradle.BuildPlugin

+ 1 - 0
buildSrc/src/main/resources/META-INF/gradle-plugins/elasticsearch.esplugin.properties

@@ -0,0 +1 @@
+implementation-class=org.elasticsearch.gradle.plugin.PluginBuildPlugin

+ 1 - 0
buildSrc/src/main/resources/META-INF/gradle-plugins/elasticsearch.rest-test.properties

@@ -0,0 +1 @@
+implementation-class=org.elasticsearch.gradle.test.RestTestPlugin

+ 1 - 0
buildSrc/src/main/resources/elasticsearch.properties

@@ -0,0 +1 @@
+version=@version@

+ 92 - 0
buildSrc/src/main/resources/forbidden/all-signatures.txt

@@ -0,0 +1,92 @@
+# Licensed to Elasticsearch under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Elasticsearch 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.
+
+@defaultMessage Convert to URI
+java.net.URL#getPath()
+java.net.URL#getFile()
+
+@defaultMessage Usage of getLocalHost is discouraged
+java.net.InetAddress#getLocalHost()
+
+@defaultMessage Use java.nio.file instead of java.io.File API
+java.util.jar.JarFile
+java.util.zip.ZipFile
+java.io.File
+java.io.FileInputStream
+java.io.FileOutputStream
+java.io.PrintStream#<init>(java.lang.String,java.lang.String)
+java.io.PrintWriter#<init>(java.lang.String,java.lang.String)
+java.util.Formatter#<init>(java.lang.String,java.lang.String,java.util.Locale)
+java.io.RandomAccessFile
+java.nio.file.Path#toFile()
+
+@defaultMessage Don't use deprecated lucene apis
+org.apache.lucene.index.DocsEnum
+org.apache.lucene.index.DocsAndPositionsEnum
+org.apache.lucene.queries.TermFilter
+org.apache.lucene.queries.TermsFilter
+org.apache.lucene.search.TermRangeFilter
+org.apache.lucene.search.NumericRangeFilter
+org.apache.lucene.search.PrefixFilter
+
+java.nio.file.Paths @ Use org.elasticsearch.common.io.PathUtils.get() instead.
+java.nio.file.FileSystems#getDefault() @ use org.elasticsearch.common.io.PathUtils.getDefaultFileSystem() instead.
+
+@defaultMessage Specify a location for the temp file/directory instead.
+java.nio.file.Files#createTempDirectory(java.lang.String,java.nio.file.attribute.FileAttribute[])
+java.nio.file.Files#createTempFile(java.lang.String,java.lang.String,java.nio.file.attribute.FileAttribute[])
+
+@defaultMessage Don't use java serialization - this can break BWC without noticing it
+java.io.ObjectOutputStream
+java.io.ObjectOutput
+java.io.ObjectInputStream
+java.io.ObjectInput
+
+java.nio.file.Files#isHidden(java.nio.file.Path) @ Dependent on the operating system, use FileSystemUtils.isHidden instead
+
+java.nio.file.Files#getFileStore(java.nio.file.Path) @ Use org.elasticsearch.env.Environment.getFileStore() instead, impacted by JDK-8034057
+java.nio.file.Files#isWritable(java.nio.file.Path) @ Use org.elasticsearch.env.Environment.isWritable() instead, impacted by JDK-8034057
+
+@defaultMessage Resolve hosts explicitly to the address(es) you want with InetAddress.
+java.net.InetSocketAddress#<init>(java.lang.String,int)
+java.net.Socket#<init>(java.lang.String,int)
+java.net.Socket#<init>(java.lang.String,int,java.net.InetAddress,int)
+
+@defaultMessage Don't bind to wildcard addresses. Be specific.
+java.net.DatagramSocket#<init>()
+java.net.DatagramSocket#<init>(int)
+java.net.InetSocketAddress#<init>(int)
+java.net.MulticastSocket#<init>()
+java.net.MulticastSocket#<init>(int)
+java.net.ServerSocket#<init>(int)
+java.net.ServerSocket#<init>(int,int)
+
+@defaultMessage use NetworkAddress format/formatAddress to print IP or IP+ports
+java.net.InetAddress#toString()
+java.net.InetAddress#getHostAddress()
+java.net.Inet4Address#getHostAddress()
+java.net.Inet6Address#getHostAddress()
+java.net.InetSocketAddress#toString()
+
+@defaultMessage avoid DNS lookups by accident: if you have a valid reason, then @SuppressWarnings with that reason so its completely clear
+java.net.InetAddress#getHostName()
+java.net.InetAddress#getCanonicalHostName()
+
+java.net.InetSocketAddress#getHostName() @ Use getHostString() instead, which avoids a DNS lookup
+
+@defaultMessage Do not violate java's access system
+java.lang.reflect.AccessibleObject#setAccessible(boolean)
+java.lang.reflect.AccessibleObject#setAccessible(java.lang.reflect.AccessibleObject[], boolean)

+ 85 - 0
buildSrc/src/main/resources/forbidden/core-signatures.txt

@@ -0,0 +1,85 @@
+# Licensed to Elasticsearch under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Elasticsearch 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.
+
+# For third-party dependencies, please put signatures in third-party.txt instead of here.
+
+@defaultMessage spawns threads with vague names; use a custom thread factory and name threads so that you can tell (by its name) which executor it is associated with
+
+java.util.concurrent.Executors#newFixedThreadPool(int)
+java.util.concurrent.Executors#newSingleThreadExecutor()
+java.util.concurrent.Executors#newCachedThreadPool()
+java.util.concurrent.Executors#newSingleThreadScheduledExecutor()
+java.util.concurrent.Executors#newScheduledThreadPool(int)
+java.util.concurrent.Executors#defaultThreadFactory()
+java.util.concurrent.Executors#privilegedThreadFactory()
+
+java.lang.Character#codePointBefore(char[],int) @ Implicit start offset is error-prone when the char[] is a buffer and the first chars are random chars
+java.lang.Character#codePointAt(char[],int) @ Implicit end offset is error-prone when the char[] is a buffer and the last chars are random chars
+
+java.io.StringReader#<init>(java.lang.String) @ Use FastStringReader instead
+
+@defaultMessage Reference management is tricky, leave it to SearcherManager
+org.apache.lucene.index.IndexReader#decRef()
+org.apache.lucene.index.IndexReader#incRef()
+org.apache.lucene.index.IndexReader#tryIncRef()
+
+@defaultMessage Pass the precision step from the mappings explicitly instead
+org.apache.lucene.search.NumericRangeQuery#newDoubleRange(java.lang.String,java.lang.Double,java.lang.Double,boolean,boolean)
+org.apache.lucene.search.NumericRangeQuery#newFloatRange(java.lang.String,java.lang.Float,java.lang.Float,boolean,boolean)
+org.apache.lucene.search.NumericRangeQuery#newIntRange(java.lang.String,java.lang.Integer,java.lang.Integer,boolean,boolean)
+org.apache.lucene.search.NumericRangeQuery#newLongRange(java.lang.String,java.lang.Long,java.lang.Long,boolean,boolean)
+org.apache.lucene.search.NumericRangeFilter#newDoubleRange(java.lang.String,java.lang.Double,java.lang.Double,boolean,boolean)
+org.apache.lucene.search.NumericRangeFilter#newFloatRange(java.lang.String,java.lang.Float,java.lang.Float,boolean,boolean)
+org.apache.lucene.search.NumericRangeFilter#newIntRange(java.lang.String,java.lang.Integer,java.lang.Integer,boolean,boolean)
+org.apache.lucene.search.NumericRangeFilter#newLongRange(java.lang.String,java.lang.Long,java.lang.Long,boolean,boolean)
+
+@defaultMessage Only use wait / notify when really needed try to use concurrency primitives, latches or callbacks instead.
+java.lang.Object#wait()
+java.lang.Object#wait(long)
+java.lang.Object#wait(long,int)
+java.lang.Object#notify()
+java.lang.Object#notifyAll()
+
+@defaultMessage Beware of the behavior of this method on MIN_VALUE
+java.lang.Math#abs(int)
+java.lang.Math#abs(long)
+
+@defaultMessage Please do not try to stop the world
+java.lang.System#gc()
+
+@defaultMessage Use Channels.* methods to write to channels. Do not write directly.
+java.nio.channels.WritableByteChannel#write(java.nio.ByteBuffer)
+java.nio.channels.FileChannel#write(java.nio.ByteBuffer, long)
+java.nio.channels.GatheringByteChannel#write(java.nio.ByteBuffer[], int, int)
+java.nio.channels.GatheringByteChannel#write(java.nio.ByteBuffer[])
+java.nio.channels.ReadableByteChannel#read(java.nio.ByteBuffer)
+java.nio.channels.ScatteringByteChannel#read(java.nio.ByteBuffer[])
+java.nio.channels.ScatteringByteChannel#read(java.nio.ByteBuffer[], int, int)
+java.nio.channels.FileChannel#read(java.nio.ByteBuffer, long)
+
+@defaultMessage Use Lucene.parseLenient instead it strips off minor version
+org.apache.lucene.util.Version#parseLeniently(java.lang.String)
+
+@defaultMessage Spawns a new thread which is solely under lucenes control use ThreadPool#estimatedTimeInMillisCounter instead
+org.apache.lucene.search.TimeLimitingCollector#getGlobalTimerThread()
+org.apache.lucene.search.TimeLimitingCollector#getGlobalCounter()
+
+@defaultMessage Don't interrupt threads use FutureUtils#cancel(Future<T>) instead
+java.util.concurrent.Future#cancel(boolean)
+
+@defaultMessage Don't try reading from paths that are not configured in Environment, resolve from Environment instead
+org.elasticsearch.common.io.PathUtils#get(java.lang.String, java.lang.String[])
+org.elasticsearch.common.io.PathUtils#get(java.net.URI)

+ 23 - 0
buildSrc/src/main/resources/forbidden/test-signatures.txt

@@ -0,0 +1,23 @@
+# Licensed to Elasticsearch under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Elasticsearch 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.
+
+com.carrotsearch.randomizedtesting.RandomizedTest#globalTempDir() @ Use newTempDirPath() instead
+com.carrotsearch.randomizedtesting.annotations.Seed @ Don't commit hardcoded seeds
+com.carrotsearch.randomizedtesting.annotations.Repeat @ Don't commit hardcoded repeats
+
+org.apache.lucene.codecs.Codec#setDefault(org.apache.lucene.codecs.Codec) @ Use the SuppressCodecs("*") annotation instead
+org.apache.lucene.util.LuceneTestCase$Slow @ Don't write slow tests
+org.junit.Ignore @ Use AwaitsFix instead

+ 66 - 0
buildSrc/src/main/resources/forbidden/third-party-signatures.txt

@@ -0,0 +1,66 @@
+# Licensed to Elasticsearch under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Elasticsearch 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.
+
+@defaultMessage unsafe encoders/decoders have problems in the lzf compress library.  Use variants of encode/decode functions which take Encoder/Decoder.
+com.ning.compress.lzf.impl.UnsafeChunkEncoders#createEncoder(int)
+com.ning.compress.lzf.impl.UnsafeChunkEncoders#createNonAllocatingEncoder(int)
+com.ning.compress.lzf.impl.UnsafeChunkEncoders#createEncoder(int, com.ning.compress.BufferRecycler)
+com.ning.compress.lzf.impl.UnsafeChunkEncoders#createNonAllocatingEncoder(int, com.ning.compress.BufferRecycler)
+com.ning.compress.lzf.impl.UnsafeChunkDecoder#<init>()
+com.ning.compress.lzf.parallel.CompressTask
+com.ning.compress.lzf.util.ChunkEncoderFactory#optimalInstance()
+com.ning.compress.lzf.util.ChunkEncoderFactory#optimalInstance(int)
+com.ning.compress.lzf.util.ChunkEncoderFactory#optimalNonAllocatingInstance(int)
+com.ning.compress.lzf.util.ChunkEncoderFactory#optimalInstance(com.ning.compress.BufferRecycler)
+com.ning.compress.lzf.util.ChunkEncoderFactory#optimalInstance(int, com.ning.compress.BufferRecycler)
+com.ning.compress.lzf.util.ChunkEncoderFactory#optimalNonAllocatingInstance(int, com.ning.compress.BufferRecycler)
+com.ning.compress.lzf.util.ChunkDecoderFactory#optimalInstance()
+com.ning.compress.lzf.util.LZFFileInputStream#<init>(java.io.File)
+com.ning.compress.lzf.util.LZFFileInputStream#<init>(java.io.FileDescriptor)
+com.ning.compress.lzf.util.LZFFileInputStream#<init>(java.lang.String)
+com.ning.compress.lzf.util.LZFFileOutputStream#<init>(java.io.File)
+com.ning.compress.lzf.util.LZFFileOutputStream#<init>(java.io.File, boolean)
+com.ning.compress.lzf.util.LZFFileOutputStream#<init>(java.io.FileDescriptor)
+com.ning.compress.lzf.util.LZFFileOutputStream#<init>(java.lang.String)
+com.ning.compress.lzf.util.LZFFileOutputStream#<init>(java.lang.String, boolean)
+com.ning.compress.lzf.LZFEncoder#encode(byte[])
+com.ning.compress.lzf.LZFEncoder#encode(byte[], int, int)
+com.ning.compress.lzf.LZFEncoder#encode(byte[], int, int, com.ning.compress.BufferRecycler)
+com.ning.compress.lzf.LZFEncoder#appendEncoded(byte[], int, int, byte[], int)
+com.ning.compress.lzf.LZFEncoder#appendEncoded(byte[], int, int, byte[], int, com.ning.compress.BufferRecycler)
+com.ning.compress.lzf.LZFCompressingInputStream#<init>(java.io.InputStream)
+com.ning.compress.lzf.LZFDecoder#fastDecoder()
+com.ning.compress.lzf.LZFDecoder#decode(byte[])
+com.ning.compress.lzf.LZFDecoder#decode(byte[], int, int)
+com.ning.compress.lzf.LZFDecoder#decode(byte[], byte[])
+com.ning.compress.lzf.LZFDecoder#decode(byte[], int, int, byte[])
+com.ning.compress.lzf.LZFInputStream#<init>(java.io.InputStream)
+com.ning.compress.lzf.LZFInputStream#<init>(java.io.InputStream, boolean)
+com.ning.compress.lzf.LZFInputStream#<init>(java.io.InputStream, com.ning.compress.BufferRecycler)
+com.ning.compress.lzf.LZFInputStream#<init>(java.io.InputStream, com.ning.compress.BufferRecycler, boolean)
+com.ning.compress.lzf.LZFOutputStream#<init>(java.io.OutputStream)
+com.ning.compress.lzf.LZFOutputStream#<init>(java.io.OutputStream, com.ning.compress.BufferRecycler)
+com.ning.compress.lzf.LZFUncompressor#<init>(com.ning.compress.DataHandler)
+com.ning.compress.lzf.LZFUncompressor#<init>(com.ning.compress.DataHandler, com.ning.compress.BufferRecycler)
+
+@defaultMessage Constructing a DateTime without a time zone is dangerous
+org.joda.time.DateTime#<init>()
+org.joda.time.DateTime#<init>(long)
+org.joda.time.DateTime#<init>(int, int, int, int, int)
+org.joda.time.DateTime#<init>(int, int, int, int, int, int)
+org.joda.time.DateTime#<init>(int, int, int, int, int, int, int)
+org.joda.time.DateTime#now()
+org.joda.time.DateTimeZone#getDefault()

+ 76 - 0
buildSrc/src/main/resources/plugin-descriptor.properties

@@ -0,0 +1,76 @@
+# Elasticsearch plugin descriptor file
+# This file must exist as 'plugin-descriptor.properties' at
+# the root directory of all plugins.
+#
+# A plugin can be 'site', 'jvm', or both.
+#
+### example site plugin for "foo":
+#
+# foo.zip <-- zip file for the plugin, with this structure:
+#   _site/ <-- the contents that will be served
+#   plugin-descriptor.properties <-- example contents below:
+#
+# site=true
+# description=My cool plugin
+# version=1.0
+#
+### example jvm plugin for "foo"
+#
+# foo.zip <-- zip file for the plugin, with this structure:
+#   <arbitrary name1>.jar <-- classes, resources, dependencies
+#   <arbitrary nameN>.jar <-- any number of jars
+#   plugin-descriptor.properties <-- example contents below:
+#
+# jvm=true
+# classname=foo.bar.BazPlugin
+# description=My cool plugin
+# version=2.0
+# elasticsearch.version=2.0
+# java.version=1.7
+#
+### mandatory elements for all plugins:
+#
+# 'description': simple summary of the plugin
+description=${project.description}
+#
+# 'version': plugin's version
+version=${project.version}
+#
+# 'name': the plugin name
+name=${elasticsearch.plugin.name}
+
+### mandatory elements for site plugins:
+#
+# 'site': set to true to indicate contents of the _site/
+#  directory in the root of the plugin should be served.
+site=${elasticsearch.plugin.site}
+#
+### mandatory elements for jvm plugins :
+#
+# 'jvm': true if the 'classname' class should be loaded
+#  from jar files in the root directory of the plugin.
+#  Note that only jar files in the root directory are
+#  added to the classpath for the plugin! If you need
+#  other resources, package them into a resources jar.
+jvm=${elasticsearch.plugin.jvm}
+#
+# 'classname': the name of the class to load, fully-qualified.
+classname=${elasticsearch.plugin.classname}
+#
+# 'java.version' version of java the code is built against
+# use the system property java.specification.version
+# version string must be a sequence of nonnegative decimal integers
+# separated by "."'s and may have leading zeros
+java.version=${java.target.version}
+#
+# 'elasticsearch.version' version of elasticsearch compiled against
+elasticsearch.version=${elasticsearch.version}
+#
+### deprecated elements for jvm plugins :
+#
+# 'isolated': true if the plugin should have its own classloader.
+# passing false is deprecated, and only intended to support plugins
+# that have hard dependencies against each other. If this is
+# not specified, then the plugin is isolated by default.
+isolated=${elasticsearch.plugin.isolated}
+#

+ 133 - 0
core/build.gradle

@@ -0,0 +1,133 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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 com.carrotsearch.gradle.randomizedtesting.RandomizedTestingTask
+import org.elasticsearch.gradle.BuildPlugin
+import org.elasticsearch.gradle.test.RestSpecHack
+
+apply plugin: 'elasticsearch.build'
+apply plugin: 'com.bmuschko.nexus'
+apply plugin: 'nebula.optional-base'
+
+archivesBaseName = 'elasticsearch'
+
+versions << [
+  jackson:  '2.6.2',
+  log4j:    '1.2.17',
+  slf4j:    '1.6.2'
+]
+
+dependencies {
+
+  // lucene
+  compile "org.apache.lucene:lucene-core:${versions.lucene}"
+  compile "org.apache.lucene:lucene-backward-codecs:${versions.lucene}"
+  compile "org.apache.lucene:lucene-analyzers-common:${versions.lucene}"
+  compile "org.apache.lucene:lucene-queries:${versions.lucene}"
+  compile "org.apache.lucene:lucene-memory:${versions.lucene}"
+  compile "org.apache.lucene:lucene-highlighter:${versions.lucene}"
+  compile "org.apache.lucene:lucene-queryparser:${versions.lucene}"
+  compile "org.apache.lucene:lucene-suggest:${versions.lucene}"
+  compile "org.apache.lucene:lucene-join:${versions.lucene}"
+  compile "org.apache.lucene:lucene-spatial:${versions.lucene}"
+
+  compile 'org.elasticsearch:securesm:1.0'
+
+  // utilities
+  compile 'commons-cli:commons-cli:1.3.1'
+  compile 'com.carrotsearch:hppc:0.7.1'
+
+  // time handling, remove with java 8 time
+  compile 'joda-time:joda-time:2.8.2'
+  // joda 2.0 moved to using volatile fields for datetime
+  // When updating to a new version, make sure to update our copy of BaseDateTime
+  compile 'org.joda:joda-convert:1.2'
+
+  // json and yaml
+  compile "com.fasterxml.jackson.core:jackson-core:${versions.jackson}"
+  compile "com.fasterxml.jackson.dataformat:jackson-dataformat-smile:${versions.jackson}"
+  compile(group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: versions.jackson) {
+    exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
+  }
+  compile "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:${versions.jackson}"
+  // network stack
+  compile 'io.netty:netty:3.10.5.Final'
+  // compression of transport protocol
+  compile 'com.ning:compress-lzf:1.0.2'
+  // percentiles aggregation
+  compile 'com.tdunning:t-digest:3.0'
+  // precentil ranks aggregation
+  compile 'org.hdrhistogram:HdrHistogram:2.1.6'
+
+  // lucene spatial
+  compile 'com.spatial4j:spatial4j:0.5', optional
+  compile 'com.vividsolutions:jts:1.13', optional
+
+  // templating
+  compile 'com.github.spullara.mustache.java:compiler:0.9.1', optional
+
+  // logging
+  compile "log4j:log4j:${versions.log4j}", optional
+  compile "log4j:apache-log4j-extras:${versions.log4j}", optional
+  compile "org.slf4j:slf4j-api:${versions.slf4j}", optional
+
+  compile 'net.java.dev.jna:jna:4.1.0', optional
+
+  // TODO: remove these test deps and just depend on test-framework
+  testCompile(group: 'junit', name: 'junit', version: '4.11') {
+    transitive = false
+  }
+  testCompile "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}"
+
+  testCompile("org.apache.lucene:lucene-test-framework:${versions.lucene}") {
+    exclude group: 'com.carrotsearch.randomizedtesting', module: 'junit4-ant'
+  }
+  testCompile(group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3') {
+    exclude group: 'org.hamcrest', module: 'hamcrest-core'
+  }
+  testCompile 'com.google.jimfs:jimfs:1.0'
+  testCompile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
+}
+
+compileJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-fallthrough,-overrides,-rawtypes,-serial,-try,-unchecked"
+compileTestJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-fallthrough,-overrides,-rawtypes,-serial,-try,-unchecked"
+
+forbiddenPatterns {
+  exclude '**/*.json'
+  exclude '**/*.jmx'
+  exclude '**/org/elasticsearch/cluster/routing/shard_routes.txt'
+}
+
+task integTest(type: RandomizedTestingTask,
+               group: JavaBasePlugin.VERIFICATION_GROUP,
+               description: 'Multi-node tests',
+               dependsOn: test.dependsOn) {
+  configure(BuildPlugin.commonTestConfig(project))
+  classpath = project.test.classpath
+  testClassesDir = project.test.testClassesDir
+  include '**/*IT.class'
+}
+check.dependsOn integTest
+integTest.mustRunAfter test
+
+RestSpecHack.configureDependencies(project)
+Task copyRestSpec = RestSpecHack.configureTask(project, true)
+integTest.dependsOn copyRestSpec
+test.dependsOn copyRestSpec
+

+ 0 - 1
core/src/test/java/org/elasticsearch/index/analysis/AnalysisModuleTests.java

@@ -107,7 +107,6 @@ public class AnalysisModuleTests extends ESTestCase {
                 .put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_0_90_0)
                 .put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_0_90_0)
                 .build();
                 .build();
         AnalysisService analysisService2 = getAnalysisService(settings2);
         AnalysisService analysisService2 = getAnalysisService(settings2);
-
         // indicesanalysisservice always has the current version
         // indicesanalysisservice always has the current version
         IndicesAnalysisService indicesAnalysisService2 = injector.getInstance(IndicesAnalysisService.class);
         IndicesAnalysisService indicesAnalysisService2 = injector.getInstance(IndicesAnalysisService.class);
         assertThat(indicesAnalysisService2.analyzer("default"), is(instanceOf(NamedAnalyzer.class)));
         assertThat(indicesAnalysisService2.analyzer("default"), is(instanceOf(NamedAnalyzer.class)));

+ 10 - 25
core/src/test/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java

@@ -76,40 +76,25 @@ public class ReproduceInfoPrinter extends RunListener {
             return;
             return;
         }
         }
 
 
-        final StringBuilder b = new StringBuilder();
-        if (inVerifyPhase()) {
-            b.append("REPRODUCE WITH: mvn verify -Pdev -Dskip.unit.tests" );
-        } else {
-            b.append("REPRODUCE WITH: mvn test -Pdev");
-        }
-        String project = System.getProperty("tests.project");
-        if (project != null) {
-            b.append(" -pl " + project);
-        }
-        MavenMessageBuilder mavenMessageBuilder = new MavenMessageBuilder(b);
-        mavenMessageBuilder.appendAllOpts(failure.getDescription());
+        final StringBuilder b = new StringBuilder("REPRODUCE WITH: gradle ");
+        String task = System.getProperty("tests.task");
+        // TODO: enforce (intellij still runs the runner?) or use default "test" but that wont' work for integ
+        b.append(task);
+
+        GradleMessageBuilder gradleMessageBuilder = new GradleMessageBuilder(b);
+        gradleMessageBuilder.appendAllOpts(failure.getDescription());
 
 
         //Rest tests are a special case as they allow for additional parameters
         //Rest tests are a special case as they allow for additional parameters
         if (failure.getDescription().getTestClass().isAnnotationPresent(Rest.class)) {
         if (failure.getDescription().getTestClass().isAnnotationPresent(Rest.class)) {
-            mavenMessageBuilder.appendRestTestsProperties();
+            gradleMessageBuilder.appendRestTestsProperties();
         }
         }
 
 
         System.err.println(b.toString());
         System.err.println(b.toString());
     }
     }
 
 
-    protected TraceFormatting traces() {
-        TraceFormatting traces = new TraceFormatting();
-        try {
-            traces = RandomizedContext.current().getRunner().getTraceFormatting();
-        } catch (IllegalStateException e) {
-            // Ignore if no context.
-        }
-        return traces;
-    }
-
-    protected static class MavenMessageBuilder extends ReproduceErrorMessageBuilder {
+    protected static class GradleMessageBuilder extends ReproduceErrorMessageBuilder {
 
 
-        public MavenMessageBuilder(StringBuilder b) {
+        public GradleMessageBuilder(StringBuilder b) {
             super(b);
             super(b);
         }
         }
 
 

+ 1 - 1
core/src/test/java/org/elasticsearch/test/rest/support/FileUtils.java

@@ -94,7 +94,7 @@ public final class FileUtils {
                 String newPath = optionalPathPrefix + "/" + path;
                 String newPath = optionalPathPrefix + "/" + path;
                 file = findFile(fileSystem, newPath, optionalFileSuffix);
                 file = findFile(fileSystem, newPath, optionalFileSuffix);
                 if (!lenientExists(file)) {
                 if (!lenientExists(file)) {
-                    throw new NoSuchFileException(path);
+                    throw new NoSuchFileException("path prefix: " + optionalPathPrefix + ", path: " + path + ", file suffix: " + optionalFileSuffix);
                 }
                 }
             }
             }
             return file;
             return file;

+ 9 - 0
core/src/test/resources/log4j.properties

@@ -0,0 +1,9 @@
+es.logger.level=INFO
+log4j.rootLogger=${es.logger.level}, out
+
+log4j.logger.org.apache.http=INFO, out
+log4j.additivity.org.apache.http=false
+
+log4j.appender.out=org.apache.log4j.ConsoleAppender
+log4j.appender.out.layout=org.apache.log4j.PatternLayout
+log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n

+ 12 - 0
dev-tools/build.gradle

@@ -0,0 +1,12 @@
+apply plugin: 'groovy'
+
+repositories {
+  mavenCentral()
+}
+
+dependencies {
+  compile gradleApi()
+  compile localGroovy()
+  //compile group: 'com.carrotsearch.randomizedtesting', name: 'junit4-ant', version: '2.1.16'
+}
+

+ 201 - 0
distribution/build.gradle

@@ -0,0 +1,201 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.tools.ant.filters.FixCrLfFilter
+import org.elasticsearch.gradle.precommit.DependencyLicensesTask
+import org.elasticsearch.gradle.MavenFilteringHack
+
+// for deb/rpm
+buildscript {
+  repositories {
+    maven {
+      url "https://plugins.gradle.org/m2/"
+    }
+  }
+  dependencies {
+    classpath 'com.netflix.nebula:gradle-ospackage-plugin:3.1.0'
+  }
+}
+
+allprojects {
+  project.ext {
+    // this is common configuration for distributions, but we also add it here for the license check to use
+    deps = project("${projectsPrefix}:core").configurations.runtime.copyRecursive().exclude(module: 'slf4j-api')
+  }
+}
+
+subprojects {
+  /*****************************************************************************
+   *                              Maven config                                 *
+   *****************************************************************************/
+  // note: the group must be correct before applying the nexus plugin, or it will capture the wrong value...
+  project.group = "org.elasticsearch.distribution.${project.name}"
+  apply plugin: 'com.bmuschko.nexus'
+  // we must create our own install task, because it is only added when the java plugin is added
+  task install(type: Upload, description: "Installs the 'archives' artifacts into the local Maven repository.", group: 'Upload') {
+    configuration = configurations.archives
+    MavenRepositoryHandlerConvention repositoriesHandler = (MavenRepositoryHandlerConvention)getRepositories().getConvention().getPlugin(MavenRepositoryHandlerConvention);
+    repositoriesHandler.mavenInstaller();
+  }
+
+  // TODO: the map needs to be an input of the tasks, so that when it changes, the task will re-run...
+  /*****************************************************************************
+   *             Properties to expand when copying packaging files             *
+   *****************************************************************************/
+  project.ext {
+    expansions = [
+      'project.version': version,
+      'project.parent.artifactId': 'distributions',
+      // Default values for min/max heap memory allocated to elasticsearch java process
+      'packaging.elasticsearch.heap.min': '256m',
+      'packaging.elasticsearch.heap.max': '1g',
+      'project.build.finalName': "elasticsearch-${version}",
+      // Default configuration directory and file to use in bin/plugin script
+      'packaging.plugin.default.config.dir': '$ES_HOME/config',
+      'packaging.plugin.default.config.file': '$ES_HOME/config/elasticsearch.yml',
+      'packaging.env.file': '',
+      // TODO: do we really need this marker? the tgz and zip are exactly the same,
+      // we should not need to specify twice just to change this
+      'packaging.type': 'tar.gz',
+    ]
+
+    /*****************************************************************************
+     *                   Common files in all distributions                       *
+     *****************************************************************************/
+    libFiles = copySpec {
+      into 'lib'
+      from project("${projectsPrefix}:core").jar
+      from deps
+    }
+
+    configFiles = copySpec {
+      from '../src/main/resources/config'
+    }
+
+    commonFiles = copySpec {
+      // everything except windows files, and config is separate
+      from '../src/main/resources'
+      exclude 'bin/*.bat'
+      exclude 'bin/*.exe'
+      exclude 'config/**'
+      filesMatching('bin/*') { it.setMode(0755) }
+    }
+  }
+}
+
+/*****************************************************************************
+ *                         Zip and tgz configuration                         *
+ *****************************************************************************/
+configure(subprojects.findAll { it.name == 'zip' || it.name == 'tar' }) {
+  project.ext.archivesFiles = copySpec {
+    into("elasticsearch-${version}") {
+      with libFiles
+      into('config') {
+        with configFiles
+      }
+      with copySpec {
+        with commonFiles
+        from('../src/main/resources') {
+          include 'bin/*.bat' 
+          filter(FixCrLfFilter, eol: FixCrLfFilter.CrLf.newInstance('crlf'))
+        }
+        MavenFilteringHack.filter(it, expansions)
+      }
+      from('../src/main/resources') {
+        include 'bin/*.exe' 
+      } 
+    }
+  }
+}
+
+/*****************************************************************************
+ *                         Deb and rpm configuration                         *
+ *****************************************************************************/
+// ospackage supports adding empty dirs with directory() to rpm, but not deb...yet
+// https://github.com/nebula-plugins/gradle-ospackage-plugin/issues/115
+// however, even adding just for rpm doesn't seem to work...
+// gradle may also get native support https://issues.gradle.org/browse/GRADLE-1671
+// in the meantime, we hack this by copying an empty dir
+// TODO: HACK DOES NOT WORK
+/*ext.emptyDir = new File(project.buildDir, 'empty') 
+Closure emptyDirSpec() {
+  return {
+    from emptyDir
+    addParentDirs false
+    createDirectoryEntry true
+  }
+}
+task createEmptyDir << {
+  emptyDir.mkdirs()
+}
+buildRpm.dependsOn createEmptyDir
+buildDeb.dependsOn createEmptyDir
+*/
+
+/*****************************************************************************
+ *                         Deb and rpm configuration                         *
+ *****************************************************************************/
+configure(subprojects.findAll { it.name == 'zip' || it.name == 'tar' }) {
+  apply plugin: 'nebula.ospackage-base'
+  ospackage {
+    packageName = 'elasticsearch'
+    // TODO: '-' is an illegal character in rpm version...redline croaks
+    version = '3.0.0'
+    into '/usr/share/elasticsearch'
+    user 'root'
+    permissionGroup 'root'
+    with libFiles
+    with copySpec {
+      with commonFiles
+      // TODO: omit LICENSE.txt file on deb??
+    }
+    into('/etc/elasticsearch') {
+      with configFiles
+      //into('scripts', emptyDirSpec())
+      createDirectoryEntry = true
+      includeEmptyDirs = true
+    }
+    directory('/etc/elasticsearch/scripts')
+  }
+  if (project.name == 'deb') {
+    task buildDeb(type: Deb) {
+      dependsOn deps
+    }
+    artifacts {
+      archives buildDeb
+    }
+  } else if (project.name == 'rpm') {
+    task buildRpm(type: Rpm) {
+      dependsOn deps
+    }
+    artifacts {
+      archives buildRpm
+    }
+  }
+}
+
+// TODO: dependency checks should really be when building the jar itself, which would remove the need
+// for this hackery and instead we can do this inside the BuildPlugin
+task check(group: 'Verification', description: 'Runs all checks.') {} // dummy task!
+DependencyLicensesTask.configure(project) {
+  dependsOn = [deps]
+  dependencies = deps
+  mapping from: /lucene-.*/, to: 'lucene'
+  mapping from: /jackson-.*/, to: 'jackson'
+}

+ 4 - 0
distribution/deb/build.gradle

@@ -0,0 +1,4 @@
+
+/*task buildDeb(type: Deb) {
+  dependsOn deps
+}*/

+ 4 - 0
distribution/rpm/build.gradle

@@ -0,0 +1,4 @@
+
+/*task buildRpm(type: Rpm) {
+  dependsOn deps
+}*/

+ 10 - 0
distribution/tar/build.gradle

@@ -0,0 +1,10 @@
+
+task buildTar(type: Tar, dependsOn: deps) {
+  baseName = 'elasticsearch'
+  with archivesFiles
+  compression = Compression.GZIP
+}
+
+artifacts {
+  archives buildTar
+}

+ 11 - 0
distribution/zip/build.gradle

@@ -0,0 +1,11 @@
+
+task buildZip(type: Zip, dependsOn: deps) {
+  baseName = 'elasticsearch'
+  with archivesFiles
+}
+
+artifacts {
+  'default' buildZip
+  archives buildZip
+}
+

+ 2 - 0
distribution/ziphack/build.gradle

@@ -0,0 +1,2 @@
+
+confi

+ 2 - 0
gradle.properties

@@ -0,0 +1,2 @@
+group=org.elasticsearch
+version=3.0.0-SNAPSHOT

+ 34 - 0
plugins/analysis-icu/build.gradle

@@ -0,0 +1,34 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The ICU Analysis plugin integrates Lucene ICU module into elasticsearch, adding ICU relates analysis components.'
+  classname 'org.elasticsearch.plugin.analysis.icu.AnalysisICUPlugin'
+}
+
+dependencies {
+  compile "org.apache.lucene:lucene-analyzers-icu:${versions.lucene}"
+}
+
+dependencyLicenses {
+  mapping from: /lucene-.*/, to: 'lucene'
+}
+
+compileJava.options.compilerArgs << "-Xlint:-deprecation"
+

+ 32 - 0
plugins/analysis-kuromoji/build.gradle

@@ -0,0 +1,32 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Japanese (kuromoji) Analysis plugin integrates Lucene kuromoji analysis module into elasticsearch.'
+  classname 'org.elasticsearch.plugin.analysis.kuromoji.AnalysisKuromojiPlugin'
+}
+
+dependencies {
+  compile "org.apache.lucene:lucene-analyzers-kuromoji:${versions.lucene}"
+}
+
+dependencyLicenses {
+  mapping from: /lucene-.*/, to: 'lucene'
+}
+

+ 34 - 0
plugins/analysis-phonetic/build.gradle

@@ -0,0 +1,34 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Phonetic Analysis plugin integrates phonetic token filter analysis with elasticsearch.'
+  classname 'org.elasticsearch.plugin.analysis.AnalysisPhoneticPlugin'
+}
+
+dependencies {
+  compile "org.apache.lucene:lucene-analyzers-phonetic:${versions.lucene}"
+}
+
+dependencyLicenses {
+  mapping from: /lucene-.*/, to: 'lucene'
+}
+
+compileJava.options.compilerArgs << "-Xlint:-rawtypes,-unchecked"
+

+ 32 - 0
plugins/analysis-smartcn/build.gradle

@@ -0,0 +1,32 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'Smart Chinese Analysis plugin integrates Lucene Smart Chinese analysis module into elasticsearch.'
+  classname 'org.elasticsearch.plugin.analysis.smartcn.AnalysisSmartChinesePlugin'
+}
+
+dependencies {
+  compile "org.apache.lucene:lucene-analyzers-smartcn:${versions.lucene}"
+}
+
+dependencyLicenses {
+  mapping from: /lucene-.*/, to: 'lucene'
+}
+

+ 31 - 0
plugins/analysis-stempel/build.gradle

@@ -0,0 +1,31 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Stempel (Polish) Analysis plugin integrates Lucene stempel (polish) analysis module into elasticsearch.'
+  classname 'org.elasticsearch.plugin.analysis.stempel.AnalysisStempelPlugin'
+}
+
+dependencies {
+  compile "org.apache.lucene:lucene-analyzers-stempel:${versions.lucene}"
+}
+
+dependencyLicenses {
+  mapping from: /lucene-.*/, to: 'lucene'
+}

+ 32 - 0
plugins/build.gradle

@@ -0,0 +1,32 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.precommit.DependencyLicensesTask
+
+subprojects {
+  group = 'org.elasticsearch.plugin'
+
+  apply plugin: 'elasticsearch.esplugin'
+  apply plugin: 'com.bmuschko.nexus'
+
+  Task dependencyLicensesTask = DependencyLicensesTask.configure(project) {
+    dependencies = project.configurations.runtime - project.configurations.provided
+  }
+  project.precommit.dependsOn(dependencyLicensesTask)
+}

+ 24 - 0
plugins/delete-by-query/build.gradle

@@ -0,0 +1,24 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Delete By Query plugin allows to delete documents in Elasticsearch with a single query.'
+  classname 'org.elasticsearch.plugin.deletebyquery.DeleteByQueryPlugin'
+}
+

+ 48 - 0
plugins/discovery-azure/build.gradle

@@ -0,0 +1,48 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Azure Discovery plugin allows to use Azure API for the unicast discovery mechanism.'
+  classname 'org.elasticsearch.plugin.discovery.azure.AzureDiscoveryPlugin'
+}
+
+dependencies {
+  compile('com.microsoft.azure:azure-management-compute:0.7.0') {
+    exclude group: 'stax', module: 'stax-api'
+  }
+  compile('com.microsoft.azure:azure-management:0.7.0') {
+    exclude group: 'stax', module: 'stax-api'
+  }
+  compile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
+}
+
+dependencyLicenses {
+  mapping from: /azure-.*/, to: 'azure'
+  mapping from: /jackson-.*/, to: 'jackson'
+  mapping from: /jersey-.*/, to: 'jersey'
+  mapping from: /jaxb-.*/, to: 'jaxb'
+  mapping from: /stax-.*/, to: 'stax'
+}
+
+compileJava.options.compilerArgs << '-Xlint:-path,-serial,-static,-unchecked'
+// TODO: why is deprecation needed here but not in maven....?
+compileJava.options.compilerArgs << '-Xlint:-deprecation'
+// TODO: and why does this static not show up in maven...
+compileTestJava.options.compilerArgs << '-Xlint:-static'
+

+ 40 - 0
plugins/discovery-ec2/build.gradle

@@ -0,0 +1,40 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The EC2 discovery plugin allows to use AWS API for the unicast discovery mechanism.'
+  classname 'org.elasticsearch.plugin.discovery.ec2.Ec2DiscoveryPlugin'
+}
+
+dependencies {
+  compile 'com.amazonaws:aws-java-sdk-ec2:1.10.19'
+  compile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
+}
+
+dependencyLicenses {
+  mapping from: /aws-java-sdk-.*/, to: 'aws-java-sdk'
+  mapping from: /jackson-.*/, to: 'jackson'
+}
+
+compileJava.options.compilerArgs << '-Xlint:-rawtypes'
+
+test {
+  // this is needed for insecure plugins, remove if possible!
+  systemProperty 'tests.artifact', project.name 
+}

+ 23 - 0
plugins/discovery-gce/build.gradle

@@ -0,0 +1,23 @@
+
+esplugin {
+  description 'The Google Compute Engine (GCE) Discovery plugin allows to use GCE API for the unicast discovery mechanism.'
+  classname 'org.elasticsearch.plugin.discovery.gce.GceDiscoveryPlugin'
+}
+
+dependencies {
+  compile('com.google.apis:google-api-services-compute:v1-rev71-1.20.0') {
+    exclude group: 'com.google.guava', module: 'guava-jdk5'
+  }
+  compile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
+}
+
+dependencyLicenses {
+  mapping from: /google-.*/, to: 'google'
+}
+
+compileJava.options.compilerArgs << '-Xlint:-rawtypes,-unchecked'
+
+test {
+  // this is needed for insecure plugins, remove if possible!
+  systemProperty 'tests.artifact', project.name 
+}

+ 25 - 0
plugins/discovery-multicast/build.gradle

@@ -0,0 +1,25 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Multicast Discovery plugin allows discovery other nodes using multicast requests'
+  classname 'org.elasticsearch.plugin.discovery.multicast.MulticastDiscoveryPlugin'
+}
+
+compileJava.options.compilerArgs << "-Xlint:-deprecation"

+ 29 - 0
plugins/jvm-example/build.gradle

@@ -0,0 +1,29 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'Demonstrates all the pluggable Java entry points in Elasticsearch'
+  classname 'org.elasticsearch.plugin.example.JvmExamplePlugin'
+}
+
+// no unit tests
+test.enabled = false
+
+compileJava.options.compilerArgs << "-Xlint:-rawtypes"
+

+ 35 - 0
plugins/lang-expression/build.gradle

@@ -0,0 +1,35 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'Lucene expressions integration for Elasticsearch'
+  classname 'org.elasticsearch.script.expression.ExpressionPlugin'
+}
+
+dependencies {
+  compile "org.apache.lucene:lucene-expressions:${versions.lucene}"
+}
+
+dependencyLicenses {
+  mapping from: /lucene-.*/, to: 'lucene'
+}
+
+compileJava.options.compilerArgs << '-Xlint:-rawtypes'
+compileTestJava.options.compilerArgs << '-Xlint:-rawtypes'
+

+ 37 - 0
plugins/lang-groovy/build.gradle

@@ -0,0 +1,37 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'Groovy scripting integration for Elasticsearch'
+  classname 'org.elasticsearch.script.groovy.GroovyPlugin'
+}
+
+dependencies {
+  compile 'org.codehaus.groovy:groovy-all:2.4.4:indy'
+}
+
+compileJava.options.compilerArgs << '-Xlint:-rawtypes,-unchecked,-cast,-deprecation'
+compileTestJava.options.compilerArgs << '-Xlint:-rawtypes,-unchecked,-cast,-deprecation'
+
+integTest {
+  cluster {
+    systemProperty 'es.script.inline', 'on'
+    systemProperty 'es.script.indexed', 'on'
+  }
+}

+ 38 - 0
plugins/lang-javascript/build.gradle

@@ -0,0 +1,38 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The JavaScript language plugin allows to have javascript as the language of scripts to execute.'
+  classname 'org.elasticsearch.plugin.javascript.JavaScriptPlugin'
+}
+
+dependencies {
+  compile 'org.mozilla:rhino:1.7R4'
+}
+
+compileJava.options.compilerArgs << "-Xlint:-rawtypes,-unchecked"
+compileTestJava.options.compilerArgs << "-Xlint:-rawtypes,-unchecked"
+
+integTest {
+  cluster {
+    systemProperty 'es.script.inline', 'on'
+    systemProperty 'es.script.indexed', 'on'
+  }
+}
+

+ 38 - 0
plugins/lang-python/build.gradle

@@ -0,0 +1,38 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Python language plugin allows to have python as the language of scripts to execute.'
+  classname 'org.elasticsearch.plugin.python.PythonPlugin'
+}
+
+dependencies {
+  compile 'org.python:jython-standalone:2.7.0'
+}
+
+compileJava.options.compilerArgs << "-Xlint:-unchecked"
+compileTestJava.options.compilerArgs << "-Xlint:-unchecked"
+
+integTest {
+  cluster {
+    systemProperty 'es.script.inline', 'on'
+    systemProperty 'es.script.indexed', 'on'
+  }
+}
+

+ 25 - 0
plugins/mapper-murmur3/build.gradle

@@ -0,0 +1,25 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Mapper Murmur3 plugin allows to compute hashes of a field\'s values at index-time and to store them in the index.'
+  classname 'org.elasticsearch.plugin.mapper.MapperMurmur3Plugin'
+}
+
+compileJava.options.compilerArgs << "-Xlint:-rawtypes"

+ 24 - 0
plugins/mapper-size/build.gradle

@@ -0,0 +1,24 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Mapper Size plugin allows document to record their uncompressed size at index time.'
+  classname 'org.elasticsearch.plugin.mapper.MapperSizePlugin'
+}
+

+ 40 - 0
plugins/repository-azure/build.gradle

@@ -0,0 +1,40 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Azure Repository plugin adds support for Azure storage repositories.'
+  classname 'org.elasticsearch.plugin.repository.azure.AzureRepositoryPlugin'
+}
+
+dependencies {
+  compile('com.microsoft.azure:azure-storage:2.0.0') {
+    exclude group: 'org.slf4j', module: 'slf4j-api'
+  }
+}
+
+dependencyLicenses {
+  mapping from: /azure-.*/, to: 'azure'
+  mapping from: /jackson-.*/, to: 'jackson'
+  mapping from: /jersey-.*/, to: 'jersey'
+  mapping from: /jaxb-.*/, to: 'jaxb'
+  mapping from: /stax-.*/, to: 'stax'
+}
+
+compileJava.options.compilerArgs << '-Xlint:-deprecation,-serial'
+

+ 40 - 0
plugins/repository-s3/build.gradle

@@ -0,0 +1,40 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The S3 repository plugin adds S3 repositories.'
+  classname 'org.elasticsearch.plugin.repository.s3.S3RepositoryPlugin'
+}
+
+dependencies {
+  compile 'com.amazonaws:aws-java-sdk-s3:1.10.19'
+  compile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
+}
+
+dependencyLicenses {
+  mapping from: /aws-java-sdk-.*/, to: 'aws-java-sdk'
+  mapping from: /jackson-.*/, to: 'jackson'
+}
+
+compileJava.options.compilerArgs << '-Xlint:-deprecation,-rawtypes'
+
+test {
+  // this is needed for insecure plugins, remove if possible!
+  systemProperty 'tests.artifact', project.name 
+}

+ 27 - 0
plugins/site-example/build.gradle

@@ -0,0 +1,27 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'Demonstrates how to serve resources via elasticsearch.'
+  jvm false
+  site true
+}
+
+// no unit tests
+test.enabled = false

+ 24 - 0
plugins/store-smb/build.gradle

@@ -0,0 +1,24 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+esplugin {
+  description 'The Store SMB plugin adds support for SMB stores.'
+  classname 'org.elasticsearch.plugin.store.smb.SMBStorePlugin'
+}
+

+ 22 - 0
qa/smoke-test-client/build.gradle

@@ -0,0 +1,22 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.
+ */
+
+apply plugin: 'elasticsearch.rest-test'
+
+// TODO: this test works, but it isn't really a rest test...should we have another plugin for "non rest test that just needs N clusters?"

+ 47 - 0
qa/smoke-test-plugins/build.gradle

@@ -0,0 +1,47 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.gradle.MavenFilteringHack
+
+apply plugin: 'elasticsearch.rest-test'
+
+ext.pluginCount = 0
+for (Project subproj : project.rootProject.subprojects) {
+  if (subproj.path.startsWith(':plugins:')) {
+    integTest {
+      def bundlePlugin = subproj.tasks.findByName('bundlePlugin')
+      String camelName = subproj.name.replaceAll(/-(\w)/) { _, c -> c.toUpperCase() }
+      dependsOn bundlePlugin
+      cluster {
+        plugin "install${camelName.capitalize()}", bundlePlugin.outputs.files
+      }
+    }
+    pluginCount += 1
+  }
+} 
+
+ext.expansions = [
+  'expected.plugin.count': pluginCount
+]
+
+processTestResources {
+  inputs.properties(expansions)
+  MavenFilteringHack.filter(it, expansions)
+}
+

+ 1 - 0
rest-api-spec/build.gradle

@@ -0,0 +1 @@
+apply plugin: 'java'

+ 42 - 0
settings.gradle

@@ -0,0 +1,42 @@
+rootProject.name = 'elasticsearch'
+
+String[] projects = [
+  'rest-api-spec',
+  'core',
+  'distribution:zip',
+  'distribution:tar',
+  'distribution:deb',
+  'distribution:rpm',
+  'test-framework',
+  'plugins:analysis-icu',
+  'plugins:analysis-kuromoji',
+  'plugins:analysis-phonetic',
+  'plugins:analysis-smartcn',
+  'plugins:analysis-stempel',
+  'plugins:delete-by-query',
+  'plugins:discovery-azure',
+  'plugins:discovery-ec2',
+  'plugins:discovery-gce',
+  'plugins:discovery-multicast',
+  'plugins:lang-expression',
+  'plugins:lang-groovy',
+  'plugins:lang-javascript',
+  'plugins:lang-python',
+  'plugins:mapper-murmur3',
+  'plugins:mapper-size',
+  'plugins:repository-azure',
+  'plugins:repository-s3',
+  'plugins:jvm-example',
+  'plugins:site-example',
+  'plugins:store-smb',
+  'qa:smoke-test-client',
+  'qa:smoke-test-plugins'
+]
+
+if (hasProperty('elasticsearch.projectsPrefix')) {
+  String prefix = getProperty('elasticsearch.projectsPrefix')
+  projects = projects.collect { "${prefix}:${it}" }
+}
+
+include projects
+

+ 69 - 0
test-framework/build.gradle

@@ -0,0 +1,69 @@
+
+apply plugin: 'java'
+apply plugin: 'com.bmuschko.nexus'
+
+dependencies {
+  // TODO: change to elasticsearch core jar dep, and use dependnecy subs to point at core project
+  compile "org.elasticsearch:elasticsearch:${version}"
+
+  compile(group: 'junit', name: 'junit', version: '4.11') {
+    exclude group: 'org.hamcrest', module: 'hamcrest-core'
+  }
+  compile "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}"
+
+  compile("org.apache.lucene:lucene-test-framework:${versions.lucene}") {
+    exclude group: 'com.carrotsearch.randomizedtesting', module: 'junit4-ant'
+  }
+  compile(group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3') {
+    exclude group: 'org.hamcrest', module: 'hamcrest-core'
+  }
+  compile "com.google.jimfs:jimfs:1.0"
+  compile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
+}
+
+// HACK: this is temporary until we have moved to gradle, at which
+// point we can physically move the test framework files to this project
+project.ext {
+  srcDir = new File(project.buildDir, 'src')
+  coreDir = new File(project("${projectsPrefix}:core").projectDir, 'src' + File.separator + 'test')
+}
+sourceSets.main.java.srcDir(new File(srcDir, "java"))
+sourceSets.main.resources.srcDir(new File(srcDir, "resources"))
+task copySourceFiles(type: Sync) {
+ from(coreDir) {
+   include 'resources/log4j.properties'
+   include 'java/org/elasticsearch/test/**'
+   include 'java/org/elasticsearch/bootstrap/BootstrapForTesting.java'
+   include 'java/org/elasticsearch/bootstrap/MockPluginPolicy.java'
+   include 'java/org/elasticsearch/common/cli/CliToolTestCase.java'
+   include 'java/org/elasticsearch/cluster/MockInternalClusterInfoService.java'
+   include 'java/org/elasticsearch/cluster/routing/TestShardRouting.java'
+   include 'java/org/elasticsearch/index/MockEngineFactoryPlugin.java'
+   include 'java/org/elasticsearch/search/MockSearchService.java'
+   include 'java/org/elasticsearch/search/aggregations/bucket/AbstractTermsTestCase.java'
+   include 'java/org/elasticsearch/search/aggregations/bucket/script/NativeSignificanceScoreScriptNoParams.java'
+   include 'java/org/elasticsearch/search/aggregations/bucket/script/NativeSignificanceScoreScriptWithParams.java'
+   include 'java/org/elasticsearch/search/aggregations/bucket/script/TestScript.java'
+   include 'java/org/elasticsearch/search/aggregations/metrics/AbstractNumericTestCase.java'
+   include 'java/org/elasticsearch/percolator/PercolatorTestUtil.java'
+   include 'java/org/elasticsearch/cache/recycler/MockPageCacheRecycler.java'
+   include 'java/org/elasticsearch/common/util/MockBigArrays.java'
+   include 'java/org/elasticsearch/node/NodeMocksPlugin.java'
+   include 'java/org/elasticsearch/node/MockNode.java'
+   include 'java/org/elasticsearch/common/io/PathUtilsForTesting.java'
+   // unit tests for yaml suite parser & rest spec parser need to be excluded
+   exclude 'java/org/elasticsearch/test/rest/test/**'
+   // unit tests for test framework classes
+   exclude 'java/org/elasticsearch/test/test/**'
+
+   // no geo (requires optional deps)
+   exclude 'java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java'
+   exclude 'java/org/elasticsearch/test/geo/RandomShapeGenerator.java'
+   // this mock is just for a single logging test
+   exclude 'java/org/elasticsearch/test/MockLogAppender.java'
+ }
+ into srcDir
+}
+compileJava.dependsOn copySourceFiles
+
+compileJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-fallthrough,-overrides,-rawtypes,-serial,-try,-unchecked"