You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dw...@apache.org on 2019/12/02 14:35:47 UTC

[lucene-solr] branch gradle-master created (now d4a9842)

This is an automated email from the ASF dual-hosted git repository.

dweiss pushed a change to branch gradle-master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git.


      at d4a9842  Initial gradle build layer.

This branch includes the following new commits:

     new d4a9842  Initial gradle build layer.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[lucene-solr] 01/01: Initial gradle build layer.

Posted by dw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dweiss pushed a commit to branch gradle-master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit d4a9842375080c1a78404d9353455543d808a60b
Author: Dawid Weiss <dw...@apache.org>
AuthorDate: Mon Dec 2 15:34:57 2019 +0100

    Initial gradle build layer.
---
 .gitattributes                                     |   2 +
 .gitignore                                         |   3 +
 .travis.yml                                        |  17 ++
 build.gradle                                       |  49 +++
 gradle.TODO                                        |  52 ++++
 gradle.properties                                  |   4 +
 gradle/ant-compat/artifact-naming.gradle           |  15 +
 gradle/ant-compat/folder-layout.gradle             |  26 ++
 gradle/ant-compat/misc.gradle                      |  42 +++
 gradle/ant-compat/post-jar.gradle                  |  34 +++
 gradle/ant-compat/resolve.gradle                   | 210 +++++++++++++
 gradle/ant-compat/test-classes-cross-deps.gradle   |  53 ++++
 gradle/buildscan.gradle                            |   5 +
 gradle/defaults-idea.gradle                        |  12 +
 gradle/defaults-java.gradle                        |  11 +
 gradle/defaults-maven.gradle                       | 121 ++++++++
 gradle/defaults.gradle                             |  18 ++
 gradle/help.gradle                                 |  30 ++
 gradle/maven-local.gradle                          |  42 +++
 gradle/testing/defaults-tests-solr.gradle          |  13 +
 gradle/testing/defaults-tests.gradle               |  51 ++++
 gradle/testing/per-project-summary.gradle          |  20 ++
 gradle/testing/randomization.gradle                |  72 +++++
 gradle/testing/slowest-tests-at-end.gradle         |  31 ++
 gradle/travis.gradle                               |  21 ++
 gradle/wrapper/gradle-wrapper.jar                  | Bin 0 -> 56177 bytes
 gradle/wrapper/gradle-wrapper.properties           |   5 +
 gradlew                                            | 172 +++++++++++
 gradlew.bat                                        |  84 ++++++
 help/ant.txt                                       |  49 +++
 help/tests.txt                                     |  83 ++++++
 help/workflow.txt                                  |  33 +++
 lucene/analysis/common/build.gradle                |   7 +
 lucene/analysis/icu/build.gradle                   |  10 +
 lucene/analysis/kuromoji/build.gradle              |   8 +
 lucene/analysis/morfologik/build.gradle            |  13 +
 lucene/analysis/nori/build.gradle                  |   8 +
 lucene/analysis/opennlp/build.gradle               |   9 +
 lucene/analysis/phonetic/build.gradle              |  11 +
 lucene/analysis/smartcn/build.gradle               |   8 +
 lucene/analysis/stempel/build.gradle               |   8 +
 lucene/backward-codecs/build.gradle                |   7 +
 lucene/benchmark/build.gradle                      |  22 ++
 lucene/build.gradle                                |   3 +
 lucene/classification/build.gradle                 |  12 +
 lucene/codecs/build.gradle                         |   6 +
 lucene/core/build.gradle                           |   7 +
 lucene/demo/build.gradle                           |  12 +
 lucene/expressions/build.gradle                    |  20 ++
 lucene/facet/build.gradle                          |  12 +
 lucene/grouping/build.gradle                       |  10 +
 lucene/highlighter/build.gradle                    |  12 +
 lucene/join/build.gradle                           |   6 +
 lucene/luke/build.gradle                           |  16 +
 lucene/memory/build.gradle                         |   9 +
 lucene/misc/build.gradle                           |   6 +
 lucene/monitor/build.gradle                        |  11 +
 lucene/queries/build.gradle                        |   8 +
 lucene/queryparser/build.gradle                    |   9 +
 lucene/replicator/build.gradle                     |  19 ++
 lucene/sandbox/build.gradle                        |   6 +
 lucene/spatial-extras/build.gradle                 |  14 +
 lucene/spatial/build.gradle                        |   6 +
 lucene/spatial3d/build.gradle                      |   6 +
 lucene/suggest/build.gradle                        |   8 +
 lucene/test-framework/build.gradle                 |  11 +
 settings.gradle                                    |  55 ++++
 solr/build.gradle                                  |   3 +
 solr/contrib/analysis-extras/build.gradle          |  16 +
 solr/contrib/analytics/build.gradle                |   6 +
 solr/contrib/clustering/build.gradle               |  12 +
 solr/contrib/dataimporthandler-extras/build.gradle |  14 +
 solr/contrib/dataimporthandler/build.gradle        |  15 +
 solr/contrib/extraction/build.gradle               |  55 ++++
 .../contrib/jaegertracer-configurator/build.gradle |  12 +
 solr/contrib/langid/build.gradle                   |  15 +
 solr/contrib/ltr/build.gradle                      |  14 +
 solr/contrib/prometheus-exporter/build.gradle      |  28 ++
 solr/contrib/velocity/build.gradle                 |  14 +
 solr/core/build.gradle                             | 129 ++++++++
 solr/example/build.gradle                          |  46 +++
 solr/packaging/build.gradle                        |  95 ++++++
 solr/server/build.gradle                           | 107 +++++++
 solr/solr-ref-guide/build.gradle                   | 328 +++++++++++++++++++++
 solr/solrj/build.gradle                            |  55 ++++
 solr/test-framework/build.gradle                   |  15 +
 solr/webapp/build.gradle                           |  37 +++
 versions.lock                                      | 230 +++++++++++++++
 versions.props                                     | 111 +++++++
 89 files changed, 3132 insertions(+)

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..e37d866
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Ignore all differences in line endings for the lock file.
+versions.lock text eol=lf
diff --git a/.gitignore b/.gitignore
index d0f0ade..023c827 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,6 @@ __pycache__
 /dev-tools/scripts/scripts.iml
 .DS_Store
 
+build/
+.gradle/
+.idea/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6cab06f
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,17 @@
+language: java
+
+jdk:
+  - openjdk11
+
+before_cache:
+  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock
+  - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
+
+cache:
+  directories:
+    - $HOME/.gradle/caches/
+    - $HOME/.gradle/wrapper/
+
+script:
+  - ./gradlew assemble --scan --stacktrace -Ptravis
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..04c83cf
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,49 @@
+plugins {
+  id "base"
+  id "com.palantir.consistent-versions" version "1.12.4"
+  id "com.gradle.build-scan" version "3.0"
+}
+
+// Project version and main properties. Applies to all projects.
+allprojects {
+  version = "9.0.0-SNAPSHOT"
+}
+
+// Include smaller chunks configuring dedicated build areas.
+// Some of these intersect or add additional functionality.
+// The order of inclusion of these files shouldn't matter (but may
+// if the build file is incorrectly written and evaluates something
+// eagerly).
+
+// CI systems.
+apply from: file('gradle/buildscan.gradle')
+apply from: file('gradle/travis.gradle')
+
+// Set up defaults and configure aspects for certain modules or functionality
+// (java, tests)
+apply from: file('gradle/defaults.gradle')
+apply from: file('gradle/defaults-java.gradle')
+apply from: file('gradle/testing/defaults-tests.gradle')
+apply from: file('gradle/testing/defaults-tests-solr.gradle')
+apply from: file('gradle/testing/randomization.gradle')
+apply from: file('gradle/defaults-maven.gradle')
+
+// IDE settings and specials.
+apply from: file('gradle/defaults-idea.gradle')
+
+// Additional development aids.
+apply from: file('gradle/maven-local.gradle')
+apply from: file('gradle/testing/per-project-summary.gradle')
+apply from: file('gradle/testing/slowest-tests-at-end.gradle')
+apply from: file('gradle/help.gradle')
+
+// Ant-compatibility layer. ALL OF THESE SHOULD BE GONE at some point. They are
+// here so that we can coexist with current ant build but they are indicative
+// of potential problems with the build conventions, dependencies, etc.
+apply from: file('gradle/ant-compat/folder-layout.gradle')
+apply from: file('gradle/ant-compat/misc.gradle')
+apply from: file('gradle/ant-compat/resolve.gradle')
+apply from: file('gradle/ant-compat/post-jar.gradle')
+apply from: file('gradle/ant-compat/test-classes-cross-deps.gradle')
+apply from: file('gradle/ant-compat/artifact-naming.gradle')
+
diff --git a/gradle.TODO b/gradle.TODO
new file mode 100644
index 0000000..4dffe42
--- /dev/null
+++ b/gradle.TODO
@@ -0,0 +1,52 @@
+
+The gradle build is currently missing or could use some love in the following areas:
+
+- Apply forbiddenAPIs
+
+- configure security policy/ sandboxing for tests (!).
+
+- add test 'beasting' (rerunning the same suite multiple times). I'm afraid it'll be difficult
+  to run it sensibly because gradle doesn't offer cwd separation for the forked test runners (?)
+
+- jar checksums, jar checksum computation and validation.
+  this should be done without intermediate folders (directly
+  on dependency sets).
+
+- add a :helpDeps explanation to how the dependency system works (palantir plugin, lockfile) and
+  how to retrieve structured information about current dependencies of a given module 
+  (in a tree-like output).
+
+- identify and list precommit tasks so that they can be ported one by one.
+
+- identify and port any other "check" utilities that may be called from ant.
+
+- identify and port various "regenerate" tasks from ant builds (javacc, precompiled automata, etc.)
+
+- add rendering of javadocs (gradlew javadoc) and attaching them to maven publications.
+
+- fill in POM details in gradle/defaults-maven.gradle so that they reflect the previous content better
+  (dependencies aside).
+
+- Add any IDE integration layers that should be added (I use IntelliJ and it imports the project
+  out of the box, without the need for any special tuning).
+
+- Clean up dependencies, especially for Solr: any { transitive = false } should just explicitly
+  exclude whatever they don't need (and their dependencies currently declared explicitly
+  should be folded). Figure out which scope to import a dependency to.
+
+- add Solr packaging for docs/* (see TODO in packaging/build.gradle; currently XSLT...)
+
+- I didn't bother adding Solr dist/test-framework to packaging (who'd use it from a binary
+  distribution?)
+
+Intentional differences:
+
+- the back-compatibility target 'resolve' is added to gradle but it's really for informational purposes
+  and debugging. Packaging should be done from subcomponent configurations and dependencies,
+  not from source folders... "gradlew -p packaging assemble" puts together the entire Solr distribution
+  under packaging/build where it doesn't interfere with sources.
+
+  'resolve' for Lucene  also does *not* copy test dependencies under lib/ (like ant version does).
+
+- transitive export of certain core libraries from solr-core/ solrj (guava, etc.).
+
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..928cfe4
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,4 @@
+systemProp.file.encoding=UTF-8
+
+org.gradle.parallel=true
+org.gradle.priority=low
diff --git a/gradle/ant-compat/artifact-naming.gradle b/gradle/ant-compat/artifact-naming.gradle
new file mode 100644
index 0000000..9fe4f39
--- /dev/null
+++ b/gradle/ant-compat/artifact-naming.gradle
@@ -0,0 +1,15 @@
+// Stick to previous artifact names (not identical to path/ folders).
+configure(subprojects.findAll { it.path.contains(':solr:contrib:') }) {
+  project.archivesBaseName = project.archivesBaseName.replace("-contrib-", "-")
+}
+
+// This project has a different artifact name (solr-contrib-cell). Don't know why.
+configure(project(":solr:contrib:extraction")) {
+  archivesBaseName = "solr-cell"
+}
+
+configure(subprojects.findAll { it.path.contains(':lucene:analysis:') }) {
+  project.archivesBaseName = project.archivesBaseName.replace("-analysis-", "-analyzers-")
+}
+
+
diff --git a/gradle/ant-compat/folder-layout.gradle b/gradle/ant-compat/folder-layout.gradle
new file mode 100644
index 0000000..d6410a9
--- /dev/null
+++ b/gradle/ant-compat/folder-layout.gradle
@@ -0,0 +1,26 @@
+// Adapt to custom folder convention.
+allprojects {
+  plugins.withType(JavaPlugin) {
+    sourceSets {
+      main.java.srcDirs = ['src/java']
+      main.resources.srcDirs = ['src/resources']
+      test.java.srcDirs = ['src/test']
+      test.resources.srcDirs = ['src/test-files']
+    }
+
+    task copyTestResources(type: Copy) {
+      from('src/test') {
+        exclude '**/*.java'
+      }
+      into sourceSets.test.java.outputDir
+    }
+    processTestResources.dependsOn copyTestResources
+  }
+}
+
+// Adapt to custom 'web' folder location.
+configure(project(":solr:webapp")) {
+  plugins.withType(WarPlugin) {
+    webAppDirName = "web"
+  }
+}
diff --git a/gradle/ant-compat/misc.gradle b/gradle/ant-compat/misc.gradle
new file mode 100644
index 0000000..7ffadb2
--- /dev/null
+++ b/gradle/ant-compat/misc.gradle
@@ -0,0 +1,42 @@
+
+// Exclude inner classes from testing.
+allprojects {
+  tasks.withType(Test) { task ->
+    exclude '**/*$*'
+  }
+}
+
+// Exclude test classes that are not actually stand-alone tests (they're executed from other stuff).
+configure(project(":lucene:replicator")) {
+  plugins.withType(JavaPlugin) {
+    test {
+      exclude "**/SimpleServer*"
+    }
+  }
+}
+
+
+// Resources from top-level project folder are looked up via getClass(). Strange.
+configure(project(":lucene:benchmark")) {
+  plugins.withType(JavaPlugin) {
+    task syncConf(type: Sync) {
+      from('conf')
+      into file("${sourceSets.test.java.outputDir}/conf")
+    }
+    processTestResources.dependsOn syncConf
+  }
+}
+
+// lucene:replicator has httpclient dependency with transitive commons-logging:1.2 but currently
+// requires 1.1.2. This commons-logging should be removed entirely and replaced with slf4j-to-*
+// redirector.
+configure(project(":lucene:replicator")) {
+  plugins.withType(JavaPlugin) {
+    configurations.all {
+      resolutionStrategy {
+        force 'commons-logging:commons-logging:1.1.3'
+      }
+    }
+  }
+}
+
diff --git a/gradle/ant-compat/post-jar.gradle b/gradle/ant-compat/post-jar.gradle
new file mode 100644
index 0000000..991a979
--- /dev/null
+++ b/gradle/ant-compat/post-jar.gradle
@@ -0,0 +1,34 @@
+// This adds a configuration and artifact to solr-core which exports "post.jar" tool.
+// this should be a separate project instead (it is self-contained and classes are reused
+// in many places).
+
+configure(project(":solr:core")) {
+  plugins.withType(JavaPlugin) {
+    configurations {
+      postJar
+    }
+
+    task assemblePostJar(type: Jar) {
+      dependsOn classes
+
+      archiveFileName = "post.jar"
+      destinationDirectory = file("${buildDir}/postJar")
+
+      from(sourceSets.main.output, {
+        include "org/apache/solr/util/CLIO.class"
+        include "org/apache/solr/util/SimplePostTool*.class"
+        include "org/apache/solr/util/RTimer*.class"
+      })
+
+      manifest {
+        attributes("Main-Class": "org.apache.solr.util.SimplePostTool")
+      }
+    }
+
+    artifacts {
+      postJar assemblePostJar
+    }
+
+    assemble.dependsOn assemblePostJar
+  }
+}
\ No newline at end of file
diff --git a/gradle/ant-compat/resolve.gradle b/gradle/ant-compat/resolve.gradle
new file mode 100644
index 0000000..051bdff
--- /dev/null
+++ b/gradle/ant-compat/resolve.gradle
@@ -0,0 +1,210 @@
+
+// For Lucene, a 'resolve' task that copies any (non-project) dependencies
+// under lib/ folder.
+configure(allprojects.findAll {project -> project.path.startsWith(":lucene") }) {
+  plugins.withType(JavaPlugin) {
+    configurations {
+      runtimeLibs {
+        extendsFrom runtimeElements
+        extendsFrom testRuntimeClasspath
+      }
+    }
+
+    task resolve(type: Sync) {
+      from({
+        return configurations.runtimeLibs.copyRecursive { dep ->
+          !(dep instanceof org.gradle.api.artifacts.ProjectDependency)
+        }
+      })
+
+      into 'lib'
+    }
+  }
+}
+
+// For Solr, a 'resolve' task is much more complex. There are three folders:
+// lib/
+// test-lib/
+// lucene-libs/
+//
+// There doesn't seem to be one ideal set of rules on how these should be created, but
+// I tried to imitate the current (master) logic present in ivy and ant files in this way:
+//
+// The "solr platform" set of dependencies is a union of all deps for (core, solrj, server).
+//
+// Then:
+// lib - these are module's "own" dependencies, excluding Lucene's that are not present in the
+//       solr platform.
+// lucene-libs - these are lucene modules declared as module's dependencies and not
+//       present in solr platform.
+// test-lib/ - libs not present in solr platform and not included in solr:test-framework.
+//
+// None of these are really needed with gradle... they should be collected just in the distribution
+// package, not at each project's level.
+//
+// Unfortunately this "resolution" process is also related to how the final Solr packaging is assembled.
+// I don't know how to untie these two cleanly.
+//
+
+configure(allprojects.findAll {project -> project.path.startsWith(":solr:contrib") }) {
+  plugins.withType(JavaPlugin) {
+    ext {
+      packagingDir = file("${buildDir}/packaging")
+      deps = file("${packagingDir}/${project.name}")
+    }
+
+    configurations {
+      solrPlatformLibs
+      solrTestPlatformLibs
+      runtimeLibs {
+        extendsFrom runtimeElements
+      }
+      packaging
+    }
+
+    dependencies {
+      solrPlatformLibs project(":solr:core")
+      solrPlatformLibs project(":solr:solrj")
+      solrPlatformLibs project(":solr:server")
+
+      solrTestPlatformLibs project(":solr:test-framework")
+    }
+
+    // An aggregate that configures lib, lucene-libs and test-lib in a temporary location.
+    task assemblePackaging(type: Sync) {
+      from "README.txt"
+
+      from ({
+        def externalLibs = configurations.runtimeLibs.copyRecursive { dep ->
+          if (dep instanceof org.gradle.api.artifacts.ProjectDependency) {
+            return !dep.dependencyProject.path.startsWith(":solr")
+          } else {
+            return true
+          }
+        }
+        return externalLibs - configurations.solrPlatformLibs
+      }, {
+        exclude "lucene-*"
+        into "lib"
+      })
+
+      from ({
+        def projectLibs = configurations.runtimeLibs.copyRecursive { dep ->
+          (dep instanceof org.gradle.api.artifacts.ProjectDependency)
+        }
+        return projectLibs - configurations.solrPlatformLibs
+      }, {
+        include "lucene-*"
+        into "lucene-libs"
+      })
+
+      into deps
+    }
+
+    task syncLib(type: Sync) {
+      dependsOn assemblePackaging
+
+      from(file("${deps}/lib"), {
+        include "**"
+      })
+      into file("${projectDir}/lib")
+    }
+
+    task syncTestLib(type: Sync) {
+      // From test runtime classpath exclude:
+      // 1) project dependencies (and their dependencies)
+      // 2) runtime dependencies
+      // What remains is this module's "own" test dependency.
+      from({
+        def testRuntimeLibs = configurations.testRuntimeClasspath.copyRecursive { dep ->
+          !(dep instanceof org.gradle.api.artifacts.ProjectDependency)
+        }
+
+        return testRuntimeLibs - configurations.runtimeLibs - configurations.solrTestPlatformLibs
+      })
+
+      into file("${projectDir}/test-lib")
+    }
+
+    task resolve() {
+      dependsOn syncLib, syncTestLib
+    }
+
+    // Contrib packaging currently depends on internal resolve.
+    artifacts {
+      packaging packagingDir, {
+        builtBy assemblePackaging
+      }
+    }
+  }
+}
+
+configure(project(":solr:example")) {
+  evaluationDependsOn(":solr:example") // explicitly wait for other configs to be applied
+
+  task resolve(type: Copy) {
+    from(configurations.postJar, {
+      into "exampledocs/"
+    })
+
+    from(configurations.dih, {
+      into "example-DIH/solr/db/lib"
+    })
+
+    into projectDir
+  }
+}
+
+configure(project(":solr:server")) {
+  evaluationDependsOn(":solr:server")
+
+  task resolve(type: Copy) {
+    dependsOn assemblePackaging
+
+    from({ packagingDir }, {
+      include "**/*.jar"
+      include "solr-webapp/webapp/**"
+      includeEmptyDirs false
+    })
+
+    into projectDir
+  }
+}
+
+configure(project(":solr:core")) {
+  evaluationDependsOn(":solr:core")
+
+  configurations {
+    runtimeLibs {
+      extendsFrom runtimeElements
+    }
+  }
+
+  task resolve(type: Sync) {
+    from({
+      def ownDeps = configurations.runtimeLibs.copyRecursive { dep ->
+        if (dep instanceof org.gradle.api.artifacts.ProjectDependency) {
+          return !dep.dependencyProject.path.startsWith(":solr")
+        } else {
+          return true
+        }
+      }
+      return ownDeps
+    }, {
+      exclude "lucene-*"
+    })
+
+    into "lib"
+  }
+}
+
+configure(project(":solr:solrj")) {
+  evaluationDependsOn(":solr:solrj")
+
+  task resolve(type: Sync) {
+    from({ configurations.runtimeClasspath }, {
+    })
+
+    into "lib"
+  }
+}
\ No newline at end of file
diff --git a/gradle/ant-compat/test-classes-cross-deps.gradle b/gradle/ant-compat/test-classes-cross-deps.gradle
new file mode 100644
index 0000000..02057ac
--- /dev/null
+++ b/gradle/ant-compat/test-classes-cross-deps.gradle
@@ -0,0 +1,53 @@
+// Set up cross-project dependency on test classes. This should be resolved by pulling reused classes into
+// a separate regular module. Exporting test classes is sort of weird.
+configure([project(":lucene:spatial3d"),
+           project(":lucene:analysis:common"),
+           project(":lucene:backward-codecs"),
+           project(":lucene:queryparser"),
+           project(":solr:contrib:dataimporthandler")]) {
+  plugins.withType(JavaPlugin) {
+    configurations {
+      testClassesExported
+    }
+
+    artifacts {
+      testClassesExported sourceSets.test.java.outputDir, {
+        builtBy testClasses
+      }
+    }
+  }
+}
+
+configure(project(":lucene:spatial-extras")) {
+  plugins.withType(JavaPlugin) {
+    dependencies {
+      testImplementation project(path: ':lucene:spatial3d', configuration: 'testClassesExported')
+    }
+  }
+}
+
+configure(project(":solr:core")) {
+  plugins.withType(JavaPlugin) {
+    dependencies {
+      testImplementation project(path: ':lucene:backward-codecs', configuration: 'testClassesExported')
+      testImplementation project(path: ':lucene:queryparser', configuration: 'testClassesExported')
+    }
+  }
+}
+
+configure(project(":solr:contrib:analysis-extras")) {
+  plugins.withType(JavaPlugin) {
+    dependencies {
+      testImplementation project(path: ':lucene:analysis:common', configuration: 'testClassesExported')
+      testImplementation project(path: ':solr:contrib:dataimporthandler', configuration: 'testClassesExported')
+    }
+  }
+}
+
+configure(project(":solr:contrib:dataimporthandler-extras")) {
+  plugins.withType(JavaPlugin) {
+    dependencies {
+      testImplementation project(path: ':solr:contrib:dataimporthandler', configuration: 'testClassesExported')
+    }
+  }
+}
diff --git a/gradle/buildscan.gradle b/gradle/buildscan.gradle
new file mode 100644
index 0000000..83d0b71
--- /dev/null
+++ b/gradle/buildscan.gradle
@@ -0,0 +1,5 @@
+
+buildScan {
+    termsOfServiceUrl = "https://gradle.com/terms-of-service"
+    termsOfServiceAgree = "yes"
+}
diff --git a/gradle/defaults-idea.gradle b/gradle/defaults-idea.gradle
new file mode 100644
index 0000000..b832324
--- /dev/null
+++ b/gradle/defaults-idea.gradle
@@ -0,0 +1,12 @@
+allprojects {
+  apply plugin: 'idea'
+
+  idea {
+    module {
+      outputDir file('build/idea/classes/main')
+      testOutputDir file('build/idea/classes/test')
+      downloadSources = true
+    }
+  }
+}
+
diff --git a/gradle/defaults-java.gradle b/gradle/defaults-java.gradle
new file mode 100644
index 0000000..896b8bb
--- /dev/null
+++ b/gradle/defaults-java.gradle
@@ -0,0 +1,11 @@
+// Configure Java project defaults.
+
+allprojects {
+  plugins.withType(JavaPlugin) {
+    sourceCompatibility = "11"
+    targetCompatibility = "11"
+
+    compileJava.options.encoding = "UTF-8"
+    compileTestJava.options.encoding = "UTF-8"
+  }
+}
diff --git a/gradle/defaults-maven.gradle b/gradle/defaults-maven.gradle
new file mode 100644
index 0000000..35dcab3
--- /dev/null
+++ b/gradle/defaults-maven.gradle
@@ -0,0 +1,121 @@
+
+// Maven publications and configuration.
+//
+// the 'published' list contains an explicit list of all projects
+// which should be published to Maven repositories.
+
+configure(rootProject) {
+  ext {
+    published = [
+        ":lucene:analysis:common",
+        ":lucene:analysis:icu",
+        ":lucene:analysis:kuromoji",
+        ":lucene:analysis:morfologik",
+        ":lucene:analysis:nori",
+        ":lucene:analysis:opennlp",
+        ":lucene:analysis:phonetic",
+        ":lucene:analysis:smartcn",
+        ":lucene:analysis:stempel",
+        ":lucene:backward-codecs",
+        ":lucene:benchmark",
+        ":lucene:classification",
+        ":lucene:codecs",
+        ":lucene:core",
+        ":lucene:demo",
+        ":lucene:expressions",
+        ":lucene:facet",
+        ":lucene:grouping",
+        ":lucene:highlighter",
+        ":lucene:join",
+        ":lucene:luke",
+        ":lucene:memory",
+        ":lucene:misc",
+        ":lucene:monitor",
+        ":lucene:queries",
+        ":lucene:queryparser",
+        ":lucene:replicator",
+        ":lucene:sandbox",
+        ":lucene:spatial",
+        ":lucene:spatial-extras",
+        ":lucene:spatial3d",
+        ":lucene:suggest",
+        ":lucene:test-framework",
+
+        ":solr:core",
+        ":solr:solrj",
+        ":solr:contrib:analysis-extras",
+        ":solr:contrib:dataimporthandler",
+        ":solr:contrib:dataimporthandler-extras",
+        ":solr:contrib:analytics",
+        ":solr:contrib:clustering",
+        ":solr:contrib:extraction",
+        ":solr:contrib:langid",
+        ":solr:contrib:jaegertracer-configurator",
+        ":solr:contrib:prometheus-exporter",
+        ":solr:contrib:velocity",
+        ":solr:test-framework",
+    ]
+  }
+
+  configure(subprojects.findAll { it.path in rootProject.published }) {
+    apply plugin: 'maven-publish'
+    apply plugin: 'signing'
+
+    publishing {
+      // TODO: Add publishing repository details.
+    }
+
+    plugins.withType(JavaPlugin) {
+      task sourcesJar(type: Jar, dependsOn: classes) {
+        archiveClassifier = 'sources'
+        from sourceSets.main.allJava
+      }
+
+      task javadocJar(type: Jar, dependsOn: javadoc) {
+        archiveClassifier = 'javadoc'
+        from javadoc.destinationDir
+      }
+
+      publishing {
+        def configurePom = {
+          name = "Apache Solr/Lucene (${project.name})"
+          licenses {
+            license {
+              name = 'Apache 2'
+              url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+            }
+          }
+        }
+
+        publications {
+          // JARS and sources, no javadocs (for local inspection only).
+          jars(MavenPublication) {
+            from components.java
+            groupId = project.group
+            artifactId = project.archivesBaseName
+
+            artifact sourcesJar
+
+            pom(configurePom)
+          }
+
+          // Full set of signed artifacts.
+          signed(MavenPublication) {
+            from components.java
+            groupId = project.group
+            artifactId = project.archivesBaseName
+
+            artifact sourcesJar
+            artifact javadocJar
+
+            pom(configurePom)
+          }
+        }
+      }
+
+      signing {
+        sign publishing.publications.signed
+      }
+    }
+  }
+}
diff --git a/gradle/defaults.gradle b/gradle/defaults.gradle
new file mode 100644
index 0000000..474826e
--- /dev/null
+++ b/gradle/defaults.gradle
@@ -0,0 +1,18 @@
+allprojects {
+  apply plugin: 'base'
+
+  group "org.apache"
+
+  // Repositories to fetch dependencies from.
+  repositories {
+    mavenCentral()
+
+    maven {
+      url "https://maven.restlet.com"
+    }
+  }
+
+  // Artifacts will have names after full gradle project path
+  // so :solr:core will have solr-core.jar, etc.
+  project.archivesBaseName = project.path.replaceAll("^:", "").replace(':', '-')
+}
diff --git a/gradle/help.gradle b/gradle/help.gradle
new file mode 100644
index 0000000..01fce34
--- /dev/null
+++ b/gradle/help.gradle
@@ -0,0 +1,30 @@
+// Add "help" tasks which display plain text files under 'help' folder.
+
+configure(rootProject) {
+  def helpFiles = [
+      ["Workflow", "help/workflow.txt", "Typical workflow commands."],
+      ["Ant", "help/ant-gradle.txt", "Ant-gradle migration help."],
+      ["Tests", "help/tests.txt", "Tests, filtering, beasting, etc."],
+  ]
+
+  helpFiles.each { section, path, sectionInfo ->
+    task "help${section}" {
+      group = 'Help (developer guides and hints)'
+      description = sectionInfo
+      doFirst {
+        println "\n" + rootProject.file(path).getText("UTF-8")
+      }
+    }
+  }
+
+  help {
+    doLast {
+      println ""
+      println "This is an experimental Lucene/Solr gradle build. See some"
+      println "guidelines, ant-equivalent commands etc. under help/*; or type:"
+      helpFiles.each { entry ->
+        println "  gradlew :help${entry[0]}  # ${entry[2]}"
+      }
+    }
+  }
+}
diff --git a/gradle/maven-local.gradle b/gradle/maven-local.gradle
new file mode 100644
index 0000000..25a4746
--- /dev/null
+++ b/gradle/maven-local.gradle
@@ -0,0 +1,42 @@
+
+// This adds a root project task to install all artifacts to a build-local
+// Maven repository (so that pom files can be manually reviewed).
+
+configure(rootProject) {
+  ext {
+    mavenLocalDir = file("${buildDir}/maven-local")
+  }
+
+  task mavenLocal() {
+    group "Publishing"
+    description "Publish Maven JARs and POMs locally to " + mavenLocalDir
+
+    doLast {
+      logger.lifecycle "Local maven artifacts (poms, jars) created at: ${mavenLocalDir}"
+    }
+  }
+
+  task mavenLocalClean(type: Delete) {
+    delete mavenLocalDir
+  }
+
+  configure(subprojects.findAll { it.path in rootProject.published }) {
+    plugins.withType(PublishingPlugin) {
+      publishing {
+        repositories {
+          maven {
+            name = 'build'
+            url = mavenLocalDir
+          }
+        }
+      }
+
+      tasks.matching { it.name == "publishJarsPublicationToBuildRepository" }.all { task ->
+        // Clean prior to republishing to local build repository.
+        task.dependsOn mavenLocalClean
+        // Attach to root project's mavenLocal task.
+        mavenLocal.dependsOn task
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/gradle/testing/defaults-tests-solr.gradle b/gradle/testing/defaults-tests-solr.gradle
new file mode 100644
index 0000000..be84a59
--- /dev/null
+++ b/gradle/testing/defaults-tests-solr.gradle
@@ -0,0 +1,13 @@
+import org.apache.tools.ant.taskdefs.condition.Os
+
+// Solr-specific test configs.
+
+configure(allprojects.findAll {project -> project.path.startsWith(":solr") }) {
+  plugins.withType(JavaPlugin) {
+    test {
+      systemProperty 'tests.disableHdfs', Os.isFamily(Os.FAMILY_WINDOWS) ? 'true' : 'false'
+      systemProperty 'jetty.testMode', '1'
+      systemProperty 'jetty.insecurerandom', '1'
+    }
+  }
+}
diff --git a/gradle/testing/defaults-tests.gradle b/gradle/testing/defaults-tests.gradle
new file mode 100644
index 0000000..668cb84
--- /dev/null
+++ b/gradle/testing/defaults-tests.gradle
@@ -0,0 +1,51 @@
+import org.apache.tools.ant.taskdefs.condition.Os
+import org.gradle.api.tasks.testing.logging.*
+
+allprojects {
+  plugins.withType(JavaPlugin) {
+    project.ext {
+      testsWorkDir = file("${buildDir}/tmp/tests-cwd")
+      testsTmpDir = file("${buildDir}/tmp/tests-tmp")
+    }
+
+    test {
+      workingDir testsWorkDir
+
+      useJUnit()
+
+      // Set up default parallel execution limits.
+      maxParallelForks = (int) Math.max(1, Math.min(Runtime.runtime.availableProcessors() / 2.0, 3.0))
+
+      minHeapSize = "256m"
+      maxHeapSize = "512m"
+
+      systemProperty 'java.awt.headless', 'true'
+      systemProperty 'jdk.map.althashing.threshold', '0'
+
+      if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
+        systemProperty 'java.security.egd', 'file:/dev/./urandom'
+      }
+
+      // Set up cwd and temp locations.
+      systemProperty("java.io.tmpdir", testsTmpDir)
+      doFirst {
+        testsWorkDir.mkdirs()
+        testsTmpDir.mkdirs()
+      }
+
+      // Set up logging.
+      testLogging {
+        events TestLogEvent.FAILED
+        exceptionFormat TestExceptionFormat.FULL
+        showExceptions true
+        showCauses true
+        showStackTraces true
+      }
+
+      doFirst {
+        // Print some diagnostics about locations used.
+        logger.info("Test folders for {}: cwd={}, tmp={}", project.path, testsWorkDir, testsTmpDir)
+      }
+    }
+  }
+}
diff --git a/gradle/testing/per-project-summary.gradle b/gradle/testing/per-project-summary.gradle
new file mode 100644
index 0000000..ec5281e
--- /dev/null
+++ b/gradle/testing/per-project-summary.gradle
@@ -0,0 +1,20 @@
+
+// Per-project test summary.
+
+allprojects {
+  tasks.withType(Test) { task ->
+    afterSuite { desc, result ->
+      if (!desc.parent) {
+        if (result.testCount > 0) {
+          def components = [
+              "test(s)"   : result.testCount,
+              "failure(s)": result.failedTestCount,
+              "skipped"   : result.skippedTestCount
+          ].findAll { k, v -> v > 0 }.collect { k, v -> "$v $k" }.join(", ")
+
+          logger.lifecycle("${task.path} (${result.resultType}): ${components}")
+        }
+      }
+    }
+  }
+}
diff --git a/gradle/testing/randomization.gradle b/gradle/testing/randomization.gradle
new file mode 100644
index 0000000..8024264
--- /dev/null
+++ b/gradle/testing/randomization.gradle
@@ -0,0 +1,72 @@
+
+// Configure test randomization seeds and derived test properties.
+
+allprojects {
+  ext {
+    // Support passing overrides via -P or -D.
+    propertyOrDefault = { propName, defValue ->
+      def result
+      if (project.hasProperty(propName)) {
+        result = project.getProperty(propName)
+      } else if (System.properties.containsKey(propName)) {
+        result = System.properties.get(propName)
+      } else {
+        result = defValue
+      }
+      return result
+    }
+  }
+}
+
+// Pick the "root" seed from which everything else is derived.
+configure(rootProject) {
+  ext {
+    rootSeed = propertyOrDefault('tests.seed', String.format("%08X", new Random().nextLong()))
+  }
+
+  task randomizationInfo() {
+    doFirst {
+      logger.lifecycle("Running tests with randomization seed: tests.seed=${rootSeed}")
+    }
+  }
+}
+
+// Any test task will trigger display of randomization settings.
+allprojects {
+  tasks.withType(Test) { task ->
+    task.dependsOn rootProject.randomizationInfo
+  }
+}
+
+// Append randomization properties to tests, allow overriding these properties with -Pkey=value.
+allprojects {
+  tasks.withType(Test) { task ->
+    [
+        'tests.seed': rootSeed,
+        'tests.multiplier': '1',
+        'tests.codec': 'random',
+        'tests.postingsformat': 'random',
+        'tests.docvaluesformat': 'random',
+        'tests.locale': 'random',
+        'tests.timezone': 'random',
+        'tests.directory': 'random',
+        'tests.nightly': 'false',
+        'tests.weekly': 'false',
+        'tests.monster': 'false',
+        'tests.slow': 'true',
+        'tests.verbose': 'false',
+        'tests.filterstacks': 'true',
+        'tests.asserts': 'true',
+        'tests.iters': null,
+        'tests.filter': null,
+        'tests.linedocsfile': 'europarl.lines.txt.gz',
+        'tests.cleanthreads.sysprop': 'perMethod'
+    ].each { propName, defValue ->
+      def value = propertyOrDefault(propName, defValue)
+      if (value != null) {
+        systemProperty propName, value
+      }
+    }
+  }
+}
+
diff --git a/gradle/testing/slowest-tests-at-end.gradle b/gradle/testing/slowest-tests-at-end.gradle
new file mode 100644
index 0000000..2e33917
--- /dev/null
+++ b/gradle/testing/slowest-tests-at-end.gradle
@@ -0,0 +1,31 @@
+// Add test duration summary at the end of the build.
+
+def allTests = []
+
+allprojects {
+  tasks.withType(Test) { task ->
+    afterTest { desc, result ->
+      def duration = (result.getEndTime() - result.getStartTime())
+
+      allTests << [
+        name    : "${desc.className.replaceAll('.+\\.', "")}.${desc.name} (${project.name})",
+        duration: duration
+      ]
+    }
+  }
+}
+
+gradle.buildFinished {
+  if (allTests) {
+    def slowest = allTests
+      .sort { a, b -> b.duration.compareTo(a.duration) }
+      .take(10)
+      .findAll { e -> e.duration >= 500 }
+      .collect { e -> String.format(Locale.ROOT, "%5.2fs %s", e.duration / 1000d, e.name) }
+
+    if (slowest) {
+      logger.lifecycle("The slowest tests (exceeding 500 ms) during this run:\n  " +
+        slowest.join("\n  "))
+    }
+  }
+}
diff --git a/gradle/travis.gradle b/gradle/travis.gradle
new file mode 100644
index 0000000..45d3bb2
--- /dev/null
+++ b/gradle/travis.gradle
@@ -0,0 +1,21 @@
+// Emit more periodic info for travis builds (otherwise they timeout due to
+// no input on the console).
+
+import org.gradle.api.tasks.testing.logging.*
+
+if (hasProperty("travis")) {
+  allprojects {
+    tasks.withType(Test) { task ->
+      maxParallelForks = 2
+
+      afterSuite { desc, result ->
+        if (desc.className) {
+          def tc = result.testCount
+          def tcs = (tc == 1 ? 'test' : 'tests')
+
+          logger.lifecycle("Completed: ${desc.className} (${tc} ${tcs})")
+        }
+      }
+    }
+  }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..29953ea
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..51b873d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https://services.gradle.org/distributions/gradle-5.6.2-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/help/ant.txt b/help/ant.txt
new file mode 100644
index 0000000..b201c7e
--- /dev/null
+++ b/help/ant.txt
@@ -0,0 +1,49 @@
+Gradle for Ant users
+====================
+
+This shows some common ant targets and their equivalent Gradle commands.
+Examples below assume cwd is at the top of the checkout (gradlew
+script available from ./). Quoted [string] gives a better or more
+conventional and commonly used task alternative.
+
+Gradle tasks apply to all modules that contain a given task name. Use
+"-p" switch with a directory or a colon-notation to specify a particular
+task or module. For example these two are equivalent:
+
+gradlew -p lucene/core check
+gradlew :lucene:core:check
+
+List of common dev task equivalents
+-----------------------------------
+
+ant clean           => gradlew clean
+ant jar             => gradlew jar          [better: gradlew assemble]
+
+ant compile         => gradlew classes      [better: gradlew assemble]
+                       gradlew testClasses  [better: gradlew assemble]
+
+ant validate        => gradlew check
+ant test            => gradlew test
+
+ant jar-checksums   => TODO
+ant clean-jars      => NO REPLACEMENT
+
+ant precommit       => gradlew check -x test
+
+ant get-maven-poms  => gradlew mavenLocal
+
+Solr-specific targets
+---------------------
+
+Assemble Solr distribution at solr/packaging/build/...
+
+ant create-package  => gradlew -p solr/packaging assemble
+
+"Resolve" libraries by copying them to lib/ and other source
+locations. This task should *not* be used anymore. It is added
+for backward compatibility with ant (and for debugging)
+but it mixes sources with output locations and this workflow
+should be discouraged. Instead run assemble on packaging (above).
+
+ant resolve         => gradlew -p solr resolve
+
diff --git a/help/tests.txt b/help/tests.txt
new file mode 100644
index 0000000..6f5938a
--- /dev/null
+++ b/help/tests.txt
@@ -0,0 +1,83 @@
+Testing
+=======
+
+Examples below assume cwd at the gradlew script in the top directory of
+the project's checkout.
+
+
+Generic test/ checkup commands
+------------------------------
+
+Run all unit tests:
+
+gradlew test
+
+Run all verification tasks, including tests:
+
+gradlew check
+
+Run all verification tasks, excluding tests (-x is gradle's generic task
+exclusion mechanism):
+
+gradlew check -x test
+
+Run verification for a selected module only:
+
+gradlew :lucene:core:check     # By full gradle project path
+gradlew -p lucene/core check   # By folder designation + task
+
+
+Randomization
+-------------
+
+Run tests with the given starting seed:
+
+gradlew :lucene:misc:test -Ptests.seed=DEADBEEF
+
+
+Filtering
+---------
+
+Run tests of lucene-core module:
+
+gradlew -p lucene/core test
+
+Run a single test case (from a single module). Uses gradle's built-in filtering
+(https://docs.gradle.org/current/userguide/java_testing.html#test_filtering):
+
+gradlew -p lucene/core test --tests TestDemo
+
+Run all tests in a package:
+
+gradlew -p lucene/core test --tests "org.apache.lucene.document.*"
+
+Run all test classes/ methods that match this pattern:
+
+gradlew -p lucene/core test --tests "*testFeatureMissing*"
+
+
+Test groups
+-----------
+
+Tests can be filtered by an annotation they're marked with.
+Some test group annotations include: @AwaitsFix, @Nightly, @Slow
+
+This uses filtering infrastructure on the *runner* (randomizedtesting), 
+not gradle's built-in mechanisms (but it can be combined with "--tests").
+For example, run all lucene-core tests annotated as @Slow:
+
+gradlew -p lucene/core test -Ptests.filter=@Slow
+
+Test group filters can be combined into Boolean expressions:
+
+gradlew -p lucene/core test "default and not(@awaitsfix or @slow)"
+
+
+Reiteration ("beasting")
+------------------------
+
+Multiply each test case N times (this works by repeating the same test
+within the same JVM; it also works in IDEs):
+
+gradlew -p lucene/core test --tests TestDemo -Ptests.iters=5
+
diff --git a/help/workflow.txt b/help/workflow.txt
new file mode 100644
index 0000000..f929a52
--- /dev/null
+++ b/help/workflow.txt
@@ -0,0 +1,33 @@
+Typical workflow and tasks
+==========================
+
+This shows some typical workflow gradle commands.
+
+Run tests on a module:
+gradlew -p lucene/core test
+
+Run test of a single-class (run "gradlew :helpTests" for more):
+gradlew -p lucene/core test --tests "*Demo*"
+
+Run all tests and validation checks on a module:
+gradlew -p lucene/core check
+
+Run all tests and validation checks on everything:
+gradlew check
+
+Run all validation checks but skip all tests:
+gradlew check -x test
+
+Assemble a single module's JAR (here for lucene-core):
+gradlew -p lucene/core assemble
+ls lucene/core/build/libs
+
+Create all distributable packages, POMs, etc. and create a
+local maven repository for inspection:
+gradlew mavenLocal
+ls -R build/maven-local/
+
+Put together Solr distribution:
+gradlew -p solr/packaging assemble
+ls solr/packaging/build/solr-*
+
diff --git a/lucene/analysis/common/build.gradle b/lucene/analysis/common/build.gradle
new file mode 100644
index 0000000..2c68c0d
--- /dev/null
+++ b/lucene/analysis/common/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  testImplementation project(':lucene:test-framework')
+}
+
diff --git a/lucene/analysis/icu/build.gradle b/lucene/analysis/icu/build.gradle
new file mode 100644
index 0000000..d114f52
--- /dev/null
+++ b/lucene/analysis/icu/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+
+  api 'com.ibm.icu:icu4j'
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/analysis/kuromoji/build.gradle b/lucene/analysis/kuromoji/build.gradle
new file mode 100644
index 0000000..1e0a9e3
--- /dev/null
+++ b/lucene/analysis/kuromoji/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/analysis/morfologik/build.gradle b/lucene/analysis/morfologik/build.gradle
new file mode 100644
index 0000000..ddbef7a
--- /dev/null
+++ b/lucene/analysis/morfologik/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+
+  api 'org.carrot2:morfologik-stemming'
+
+  implementation 'org.carrot2:morfologik-polish'
+  implementation 'ua.net.nlp:morfologik-ukrainian-search'
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/analysis/nori/build.gradle b/lucene/analysis/nori/build.gradle
new file mode 100644
index 0000000..f31a53e
--- /dev/null
+++ b/lucene/analysis/nori/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+  
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/analysis/opennlp/build.gradle b/lucene/analysis/opennlp/build.gradle
new file mode 100644
index 0000000..65e45ca
--- /dev/null
+++ b/lucene/analysis/opennlp/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+  api 'org.apache.opennlp:opennlp-tools'
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/analysis/phonetic/build.gradle b/lucene/analysis/phonetic/build.gradle
new file mode 100644
index 0000000..d0d08bd
--- /dev/null
+++ b/lucene/analysis/phonetic/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+
+  implementation 'commons-codec:commons-codec'
+
+  testImplementation project(':lucene:test-framework')
+} 
+
diff --git a/lucene/analysis/smartcn/build.gradle b/lucene/analysis/smartcn/build.gradle
new file mode 100644
index 0000000..d4d34df
--- /dev/null
+++ b/lucene/analysis/smartcn/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+
+  testImplementation project(':lucene:test-framework')
+} 
diff --git a/lucene/analysis/stempel/build.gradle b/lucene/analysis/stempel/build.gradle
new file mode 100644
index 0000000..1e0a9e3
--- /dev/null
+++ b/lucene/analysis/stempel/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/backward-codecs/build.gradle b/lucene/backward-codecs/build.gradle
new file mode 100644
index 0000000..b92dea0
--- /dev/null
+++ b/lucene/backward-codecs/build.gradle
@@ -0,0 +1,7 @@
+
+apply plugin: 'java-library'
+
+dependencies { 
+  api project(':lucene:core')
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/benchmark/build.gradle b/lucene/benchmark/build.gradle
new file mode 100644
index 0000000..32daeb7
--- /dev/null
+++ b/lucene/benchmark/build.gradle
@@ -0,0 +1,22 @@
+
+apply plugin: 'java-library'
+
+dependencies {  
+  api project(':lucene:core')
+
+  implementation project(':lucene:analysis:common')
+  implementation project(':lucene:facet')
+  implementation project(':lucene:highlighter')
+  implementation project(':lucene:queries')
+  implementation project(':lucene:spatial-extras')
+  implementation project(':lucene:queryparser')
+
+  implementation "org.apache.commons:commons-compress"
+  implementation "com.ibm.icu:icu4j"
+  implementation "org.locationtech.spatial4j:spatial4j"
+  implementation("net.sourceforge.nekohtml:nekohtml", {
+    exclude module: "xml-apis"
+  })
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/build.gradle b/lucene/build.gradle
new file mode 100644
index 0000000..26082a2
--- /dev/null
+++ b/lucene/build.gradle
@@ -0,0 +1,3 @@
+subprojects {
+  group "org.apache.lucene"
+}
\ No newline at end of file
diff --git a/lucene/classification/build.gradle b/lucene/classification/build.gradle
new file mode 100644
index 0000000..835cd33
--- /dev/null
+++ b/lucene/classification/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  implementation project(':lucene:queries')
+  implementation project(':lucene:grouping')
+
+  testImplementation project(':lucene:test-framework')
+  testImplementation project(':lucene:analysis:common')
+  testImplementation project(':lucene:codecs')
+}
diff --git a/lucene/codecs/build.gradle b/lucene/codecs/build.gradle
new file mode 100644
index 0000000..50a833a
--- /dev/null
+++ b/lucene/codecs/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'java-library'
+
+dependencies {
+    implementation project(':lucene:core')
+    testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/core/build.gradle b/lucene/core/build.gradle
new file mode 100644
index 0000000..8be3b6e
--- /dev/null
+++ b/lucene/core/build.gradle
@@ -0,0 +1,7 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  testImplementation project(':lucene:codecs')
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/demo/build.gradle b/lucene/demo/build.gradle
new file mode 100644
index 0000000..015f4c1
--- /dev/null
+++ b/lucene/demo/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':lucene:core')
+  implementation project(':lucene:facet')
+  implementation project(':lucene:queries')
+  implementation project(':lucene:analysis:common')
+  implementation project(':lucene:queryparser')
+  implementation project(':lucene:expressions')
+  
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/expressions/build.gradle b/lucene/expressions/build.gradle
new file mode 100644
index 0000000..9222bf0
--- /dev/null
+++ b/lucene/expressions/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  implementation project(':lucene:codecs')
+
+  implementation 'org.antlr:antlr4-runtime'
+
+  // It is awkward that we force-omit the intermediate dependency here...
+  // The dependency chain is:
+  //   asm-commons -> asm-tree -> asm
+  // Should we really go through these hoops?
+  implementation 'org.ow2.asm:asm'
+  implementation('org.ow2.asm:asm-commons', {
+    exclude group: "org.ow2.asm", module: "asm-tree"
+  })
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/facet/build.gradle b/lucene/facet/build.gradle
new file mode 100644
index 0000000..246371e
--- /dev/null
+++ b/lucene/facet/build.gradle
@@ -0,0 +1,12 @@
+
+apply plugin: 'java-library'
+
+
+dependencies { 
+  api project(':lucene:core')
+
+  implementation 'com.carrotsearch:hppc'
+
+  testImplementation project(':lucene:test-framework')
+  testImplementation project(':lucene:queries')
+}
diff --git a/lucene/grouping/build.gradle b/lucene/grouping/build.gradle
new file mode 100644
index 0000000..ab0e891
--- /dev/null
+++ b/lucene/grouping/build.gradle
@@ -0,0 +1,10 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  implementation project(':lucene:queries')
+
+  testImplementation project(':lucene:test-framework')
+}
\ No newline at end of file
diff --git a/lucene/highlighter/build.gradle b/lucene/highlighter/build.gradle
new file mode 100644
index 0000000..c46636d
--- /dev/null
+++ b/lucene/highlighter/build.gradle
@@ -0,0 +1,12 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  implementation project(':lucene:queries')
+  implementation project(':lucene:memory')
+
+  testImplementation project(':lucene:test-framework')
+  testImplementation project(':lucene:analysis:common')
+}
diff --git a/lucene/join/build.gradle b/lucene/join/build.gradle
new file mode 100644
index 0000000..fa8aca9
--- /dev/null
+++ b/lucene/join/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  testImplementation project(':lucene:test-framework')
+}
\ No newline at end of file
diff --git a/lucene/luke/build.gradle b/lucene/luke/build.gradle
new file mode 100644
index 0000000..4e9fc42
--- /dev/null
+++ b/lucene/luke/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  implementation project(':lucene:codecs')
+  implementation project(':lucene:backward-codecs')
+  implementation project(':lucene:analysis:common')
+  implementation project(':lucene:queries')
+  implementation project(':lucene:queryparser')
+  implementation project(':lucene:misc')
+
+  implementation 'org.apache.logging.log4j:log4j-core'
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/memory/build.gradle b/lucene/memory/build.gradle
new file mode 100644
index 0000000..92b4c5b
--- /dev/null
+++ b/lucene/memory/build.gradle
@@ -0,0 +1,9 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  testImplementation project(':lucene:test-framework')
+  testImplementation project(':lucene:queryparser')
+}
\ No newline at end of file
diff --git a/lucene/misc/build.gradle b/lucene/misc/build.gradle
new file mode 100644
index 0000000..fa8aca9
--- /dev/null
+++ b/lucene/misc/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  testImplementation project(':lucene:test-framework')
+}
\ No newline at end of file
diff --git a/lucene/monitor/build.gradle b/lucene/monitor/build.gradle
new file mode 100644
index 0000000..fc9583a
--- /dev/null
+++ b/lucene/monitor/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  implementation project(':lucene:memory')
+  implementation project(':lucene:analysis:common')
+
+  testImplementation project(':lucene:queryparser')
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/queries/build.gradle b/lucene/queries/build.gradle
new file mode 100644
index 0000000..6212520
--- /dev/null
+++ b/lucene/queries/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  testImplementation project(':lucene:test-framework')
+  testImplementation project(':lucene:expressions')
+}
\ No newline at end of file
diff --git a/lucene/queryparser/build.gradle b/lucene/queryparser/build.gradle
new file mode 100644
index 0000000..1a562c4
--- /dev/null
+++ b/lucene/queryparser/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:queries')
+  api project(':lucene:sandbox')
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/replicator/build.gradle b/lucene/replicator/build.gradle
new file mode 100644
index 0000000..f924533
--- /dev/null
+++ b/lucene/replicator/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  implementation project(':lucene:facet')
+
+  implementation('org.apache.httpcomponents:httpclient', {
+    exclude group: "commons-codec", module: "commons-codec"
+  })
+
+  implementation 'org.eclipse.jetty:jetty-server'
+  implementation('org.eclipse.jetty:jetty-servlet', {
+    exclude group: "org.eclipse.jetty", module: "jetty-security"
+  })
+  implementation 'org.eclipse.jetty:jetty-continuation'
+
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/sandbox/build.gradle b/lucene/sandbox/build.gradle
new file mode 100644
index 0000000..f59fd2c
--- /dev/null
+++ b/lucene/sandbox/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/spatial-extras/build.gradle b/lucene/spatial-extras/build.gradle
new file mode 100644
index 0000000..444148f
--- /dev/null
+++ b/lucene/spatial-extras/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:spatial3d')
+
+  api 'org.locationtech.spatial4j:spatial4j'
+  api 'io.sgr:s2-geometry-library-java'
+
+  testImplementation project(':lucene:test-framework')
+
+  testImplementation 'org.locationtech.jts:jts-core'
+  testImplementation 'org.locationtech.spatial4j:spatial4j::tests'
+}
diff --git a/lucene/spatial/build.gradle b/lucene/spatial/build.gradle
new file mode 100644
index 0000000..fa8aca9
--- /dev/null
+++ b/lucene/spatial/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  testImplementation project(':lucene:test-framework')
+}
\ No newline at end of file
diff --git a/lucene/spatial3d/build.gradle b/lucene/spatial3d/build.gradle
new file mode 100644
index 0000000..fa8aca9
--- /dev/null
+++ b/lucene/spatial3d/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  testImplementation project(':lucene:test-framework')
+}
\ No newline at end of file
diff --git a/lucene/suggest/build.gradle b/lucene/suggest/build.gradle
new file mode 100644
index 0000000..f31a53e
--- /dev/null
+++ b/lucene/suggest/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+  
+  testImplementation project(':lucene:test-framework')
+}
diff --git a/lucene/test-framework/build.gradle b/lucene/test-framework/build.gradle
new file mode 100644
index 0000000..c0b7615
--- /dev/null
+++ b/lucene/test-framework/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+
+  api ("com.carrotsearch.randomizedtesting:randomizedtesting-runner")
+  api ('org.hamcrest:hamcrest-core')
+  api ("junit:junit")
+
+  implementation project(':lucene:codecs')
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..a04620d
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,55 @@
+
+include "lucene:analysis:common"
+include "lucene:analysis:icu"
+include "lucene:analysis:kuromoji"
+include "lucene:analysis:morfologik"
+include "lucene:analysis:nori"
+include "lucene:analysis:opennlp"
+include "lucene:analysis:phonetic"
+include "lucene:analysis:smartcn"
+include "lucene:analysis:stempel"
+include "lucene:backward-codecs"
+include "lucene:benchmark"
+include "lucene:classification"
+include "lucene:codecs"
+include "lucene:core"
+include "lucene:demo"
+include "lucene:expressions"
+include "lucene:facet"
+include "lucene:grouping"
+include "lucene:highlighter"
+include "lucene:join"
+include "lucene:luke"
+include "lucene:memory"
+include "lucene:misc"
+include "lucene:monitor"
+include "lucene:queries"
+include "lucene:queryparser"
+include "lucene:replicator"
+include "lucene:sandbox"
+include "lucene:spatial"
+include "lucene:spatial-extras"
+include "lucene:spatial3d"
+include "lucene:suggest"
+include "lucene:test-framework"
+
+include "solr:solrj"
+include "solr:core"
+include "solr:server"
+include "solr:contrib:analysis-extras"
+include "solr:contrib:dataimporthandler"
+include "solr:contrib:dataimporthandler-extras"
+include "solr:contrib:analytics"
+include "solr:contrib:clustering"
+include "solr:contrib:extraction"
+include "solr:contrib:langid"
+include "solr:contrib:jaegertracer-configurator"
+include "solr:contrib:prometheus-exporter"
+include "solr:contrib:velocity"
+include "solr:contrib:ltr"
+include "solr:webapp"
+include "solr:test-framework"
+include "solr:solr-ref-guide"
+include "solr:example"
+
+include "solr:packaging"
diff --git a/solr/build.gradle b/solr/build.gradle
new file mode 100644
index 0000000..e48b30f
--- /dev/null
+++ b/solr/build.gradle
@@ -0,0 +1,3 @@
+subprojects {
+  group "org.apache.solr"
+}
\ No newline at end of file
diff --git a/solr/contrib/analysis-extras/build.gradle b/solr/contrib/analysis-extras/build.gradle
new file mode 100644
index 0000000..8e6ab06
--- /dev/null
+++ b/solr/contrib/analysis-extras/build.gradle
@@ -0,0 +1,16 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':solr:core')
+
+  implementation project(':lucene:analysis:icu')
+  implementation project(':lucene:analysis:smartcn')
+  implementation project(':lucene:analysis:morfologik')
+  implementation project(':lucene:analysis:opennlp')
+  implementation project(':lucene:analysis:smartcn')
+  implementation project(':lucene:analysis:stempel')
+
+  testImplementation project(':solr:test-framework')
+}
+
diff --git a/solr/contrib/analytics/build.gradle b/solr/contrib/analytics/build.gradle
new file mode 100644
index 0000000..de21ff6
--- /dev/null
+++ b/solr/contrib/analytics/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+  testImplementation project(':solr:test-framework')
+}
diff --git a/solr/contrib/clustering/build.gradle b/solr/contrib/clustering/build.gradle
new file mode 100644
index 0000000..860a645
--- /dev/null
+++ b/solr/contrib/clustering/build.gradle
@@ -0,0 +1,12 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+  implementation project(':lucene:analysis:common')
+  implementation('org.carrot2:carrot2-mini', {
+    exclude group: "org.simpleframework", module: "simple-xml"
+  })
+
+  testImplementation project(':solr:test-framework')
+}
diff --git a/solr/contrib/dataimporthandler-extras/build.gradle b/solr/contrib/dataimporthandler-extras/build.gradle
new file mode 100644
index 0000000..bbf4c69
--- /dev/null
+++ b/solr/contrib/dataimporthandler-extras/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+
+  implementation project(':solr:contrib:dataimporthandler')
+  implementation project(':solr:contrib:extraction')
+
+  implementation ('javax.activation:activation')
+  implementation ('com.sun.mail:javax.mail')
+  implementation ('com.sun.mail:gimap')
+
+  testImplementation project(':solr:test-framework')
+}
diff --git a/solr/contrib/dataimporthandler/build.gradle b/solr/contrib/dataimporthandler/build.gradle
new file mode 100644
index 0000000..d34b9dd
--- /dev/null
+++ b/solr/contrib/dataimporthandler/build.gradle
@@ -0,0 +1,15 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+
+  testImplementation project(':solr:test-framework')
+
+  testImplementation('org.mockito:mockito-core', {
+    exclude group: "net.bytebuddy", module: "byte-buddy-agent"
+  })
+  testImplementation ('org.hsqldb:hsqldb')
+  testImplementation ('org.apache.derby:derby')
+  testImplementation ('org.objenesis:objenesis')
+}
diff --git a/solr/contrib/extraction/build.gradle b/solr/contrib/extraction/build.gradle
new file mode 100644
index 0000000..7875efc
--- /dev/null
+++ b/solr/contrib/extraction/build.gradle
@@ -0,0 +1,55 @@
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+
+  // We export tika because other contribs depend on it (and its submodules)
+  // and we don't want to duplicate the dependency across different contribs.
+  api ('org.apache.tika:tika-core') { transitive = false }
+  api ('org.apache.tika:tika-parsers') { transitive = false }
+
+  runtimeOnly ('org.apache.james:apache-mime4j-core') { transitive = false }
+  runtimeOnly ('org.apache.james:apache-mime4j-dom') { transitive = false }
+  runtimeOnly ('org.apache.tika:tika-java7') { transitive = false }
+  runtimeOnly ('org.apache.tika:tika-xmp') { transitive = false }
+
+  implementation ('com.healthmarketscience.jackcess:jackcess') { transitive = false }
+  implementation ('com.healthmarketscience.jackcess:jackcess-encrypt') { transitive = false }
+  implementation ('org.gagravarr:vorbis-java-tika') { transitive = false }
+  implementation ('org.gagravarr:vorbis-java-core') { transitive = false }
+  implementation ('org.apache.commons:commons-compress') { transitive = false }
+  implementation ('org.apache.pdfbox:pdfbox') { transitive = false }
+  implementation ('org.apache.pdfbox:pdfbox-tools') { transitive = false }
+  implementation ('org.apache.pdfbox:fontbox') { transitive = false }
+  implementation ('org.apache.pdfbox:jempbox') { transitive = false }
+  implementation ('org.bouncycastle:bcmail-jdk15on') { transitive = false }
+  implementation ('org.bouncycastle:bcpkix-jdk15on') { transitive = false }
+  implementation ('org.bouncycastle:bcprov-jdk15on') { transitive = false }
+  implementation ('org.apache.poi:poi') { transitive = false }
+  implementation ('org.apache.poi:poi-scratchpad') { transitive = false }
+  implementation ('org.apache.poi:poi-ooxml') { transitive = false }
+  implementation ('org.apache.poi:poi-ooxml-schemas') { transitive = false }
+  implementation ('org.apache.xmlbeans:xmlbeans') { transitive = false }
+  implementation ('org.apache.commons:commons-collections4') { transitive = false }
+  implementation ('com.github.virtuald:curvesapi') { transitive = false }
+  implementation ('org.ccil.cowan.tagsoup:tagsoup') { transitive = false }
+  implementation ('com.googlecode.mp4parser:isoparser') { transitive = false }
+  implementation ('org.aspectj:aspectjrt') { transitive = false }
+  implementation ('com.drewnoakes:metadata-extractor') { transitive = false }
+  implementation ('de.l3s.boilerpipe:boilerpipe') { transitive = false }
+  implementation ('com.rometools:rome') { transitive = false }
+  implementation ('com.rometools:rome-utils') { transitive = false }
+  implementation ('org.jdom:jdom2') { transitive = false }
+  implementation ('com.googlecode.juniversalchardet:juniversalchardet') { transitive = false }
+  implementation ('org.tukaani:xz') { transitive = false }
+  implementation ('com.adobe.xmp:xmpcore') { transitive = false }
+  implementation ('com.pff:java-libpst') { transitive = false }
+  implementation ('org.tallison:jmatio') { transitive = false }
+  implementation ('com.epam:parso') { transitive = false }
+  implementation ('org.brotli:dec') { transitive = false }
+  implementation ('xerces:xercesImpl') { transitive = false }
+
+  implementation ('com.ibm.icu:icu4j')
+
+  testImplementation project(':solr:test-framework')
+}
diff --git a/solr/contrib/jaegertracer-configurator/build.gradle b/solr/contrib/jaegertracer-configurator/build.gradle
new file mode 100644
index 0000000..f8e13a8
--- /dev/null
+++ b/solr/contrib/jaegertracer-configurator/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+
+  implementation ("io.jaegertracing:jaeger-thrift", {
+    exclude group: "com.squareup.okhttp3", module: "okhttp"
+    exclude group: "com.google.code.gson", module: "gson"
+  })
+
+  testImplementation project(':solr:test-framework')
+}
\ No newline at end of file
diff --git a/solr/contrib/langid/build.gradle b/solr/contrib/langid/build.gradle
new file mode 100644
index 0000000..6621dd0
--- /dev/null
+++ b/solr/contrib/langid/build.gradle
@@ -0,0 +1,15 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+
+  implementation ('org.apache.tika:tika-core') { transitive = false }
+
+  implementation 'com.cybozu.labs:langdetect'
+  implementation 'net.arnx:jsonic'
+  implementation 'org.apache.opennlp:opennlp-tools'
+
+
+  testImplementation project(':solr:test-framework')
+}
diff --git a/solr/contrib/ltr/build.gradle b/solr/contrib/ltr/build.gradle
new file mode 100644
index 0000000..638ca38
--- /dev/null
+++ b/solr/contrib/ltr/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+  implementation project(':lucene:analysis:common')
+
+  testImplementation('org.mockito:mockito-core', {
+    exclude group: "net.bytebuddy", module: "byte-buddy-agent"
+  })
+  testImplementation ('org.objenesis:objenesis')
+  testImplementation ('org.restlet.jee:org.restlet.ext.servlet')
+
+  testImplementation project(':solr:test-framework')
+}
diff --git a/solr/contrib/prometheus-exporter/build.gradle b/solr/contrib/prometheus-exporter/build.gradle
new file mode 100644
index 0000000..e01b42b
--- /dev/null
+++ b/solr/contrib/prometheus-exporter/build.gradle
@@ -0,0 +1,28 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+  implementation project(':lucene:analysis:common')
+
+  implementation ('io.prometheus:simpleclient')
+  implementation ('io.prometheus:simpleclient_common')
+  implementation ('io.prometheus:simpleclient_httpserver')
+  implementation ('net.thisptr:jackson-jq', {
+    exclude group: "org.jruby.joni", module: "joni"
+  })
+  implementation ('net.sourceforge.argparse4j:argparse4j')
+
+  testImplementation ('org.apache.httpcomponents:httpcore')
+  testImplementation ('org.eclipse.jetty:jetty-servlet')
+
+  testImplementation project(':solr:test-framework')
+}
+
+// Add two folders to default packaging.
+assemblePackaging {
+  from(projectDir, {
+    include "bin/**"
+    include "conf/**"
+  })
+}
\ No newline at end of file
diff --git a/solr/contrib/velocity/build.gradle b/solr/contrib/velocity/build.gradle
new file mode 100644
index 0000000..2163614
--- /dev/null
+++ b/solr/contrib/velocity/build.gradle
@@ -0,0 +1,14 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  implementation project(':solr:core')
+
+  implementation('org.apache.velocity.tools:velocity-tools-view-jsp', {
+    exclude group: "commons-beanutils", module: "commons-beanutils"
+    exclude group: "org.apache.commons", module: "commons-digester3"
+    exclude group: "com.github.cliftonlabs", module: "json-simple"
+  })
+
+  testImplementation project(':solr:test-framework')
+}
diff --git a/solr/core/build.gradle b/solr/core/build.gradle
new file mode 100644
index 0000000..0f0a4d0
--- /dev/null
+++ b/solr/core/build.gradle
@@ -0,0 +1,129 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':lucene:core')
+  api project(':lucene:analysis:common')
+  api project(':lucene:analysis:kuromoji')
+  api project(':lucene:analysis:nori')
+  api project(':lucene:analysis:phonetic')
+  api project(':lucene:backward-codecs')
+  api project(':lucene:classification')
+  api project(':lucene:codecs')
+  api project(':lucene:expressions')
+  api project(':lucene:grouping')
+  api project(':lucene:highlighter')
+  api project(':lucene:join')
+  api project(':lucene:misc')
+  api project(':lucene:queries')
+  api project(':lucene:queryparser')
+  api project(':lucene:sandbox')
+  api project(':lucene:spatial-extras')
+  api project(':lucene:suggest')
+
+  // Export these dependencies so that they're imported transitively by
+  // other modules.
+  api ('com.google.guava:guava', {
+    exclude group: "org.codehaus.mojo", module: "animal-sniffer-annotations"
+    exclude group: "com.google.j2objc", module: "j2objc-annotations"
+    exclude group: "com.google.errorprone", module: "error_prone_annotations"
+    exclude group: "org.checkerframework", module: "checker-qual"
+    exclude group: "com.google.code.findbugs", module: "jsr305"
+  })
+
+  api project(':solr:solrj')
+  api project(':solr:server')
+
+  api 'org.apache.commons:commons-lang3'
+  api 'com.carrotsearch:hppc'
+  api 'com.fasterxml.jackson.core:jackson-databind'
+  api 'commons-cli:commons-cli'
+  api 'commons-codec:commons-codec'
+  api 'commons-collections:commons-collections'
+
+  implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-smile'
+
+  implementation('com.github.ben-manes.caffeine:caffeine', {
+    exclude group: "org.checkerframework", module: "checker-qual"
+    exclude group: "com.google.errorprone", module: "error_prone_annotations"
+  })
+
+  implementation 'com.github.zafarkhaja:java-semver'
+  implementation 'com.google.re2j:re2j'
+
+  implementation('com.jayway.jsonpath:json-path', {
+    exclude group: "net.minidev", module: "json-smart"
+  })
+
+  implementation 'com.tdunning:t-digest'
+  implementation 'commons-fileupload:commons-fileupload'
+
+  implementation 'io.opentracing:opentracing-api'
+  implementation 'io.opentracing:opentracing-noop'
+  implementation 'io.opentracing:opentracing-util'
+  implementation 'org.apache.commons:commons-exec'
+  implementation 'org.apache.commons:commons-text'
+  implementation("org.apache.commons:commons-configuration2", {
+    exclude group: "commons-logging", module: "commons-logging"
+  })
+  implementation 'org.apache.htrace:htrace-core4'
+
+  implementation 'org.apache.logging.log4j:log4j-api'
+  implementation 'org.apache.logging.log4j:log4j-core'
+  implementation 'org.apache.logging.log4j:log4j-slf4j-impl'
+
+  api 'org.bitbucket.b_c:jose4j'
+  implementation 'org.codehaus.janino:commons-compiler'
+  implementation 'org.codehaus.janino:janino'
+
+  api 'org.restlet.jee:org.restlet'
+  implementation 'org.rrd4j:rrd4j'
+  implementation 'org.restlet.jee:org.restlet.ext.servlet'
+
+  implementation ('org.apache.calcite.avatica:avatica-core') { transitive = false }
+  implementation ('org.apache.calcite:calcite-core') { transitive = false }
+  implementation ('org.apache.calcite:calcite-linq4j') { transitive = false }
+  implementation ('org.apache.curator:curator-client') { transitive = false }
+  implementation ('org.apache.curator:curator-framework') { transitive = false }
+  implementation ('org.apache.hadoop:hadoop-annotations') { transitive = false }
+  implementation ('org.apache.hadoop:hadoop-auth') { transitive = false }
+  implementation ('org.apache.hadoop:hadoop-common') { transitive = false }
+  implementation ('org.apache.hadoop:hadoop-hdfs-client') { transitive = false }
+
+  implementation ('net.hydromatic:eigenbase-properties') { transitive = false }
+
+  runtimeOnly ('org.apache.curator:curator-recipes') { transitive = false }
+  runtimeOnly ('org.apache.kerby:kerb-core')
+  runtimeOnly ('org.apache.kerby:kerb-util')
+  runtimeOnly ('org.apache.kerby:kerby-asn1')
+  runtimeOnly ('org.apache.kerby:kerby-pkix')
+  runtimeOnly ('com.google.protobuf:protobuf-java')
+
+  testImplementation project(':lucene:analysis:icu')
+  testImplementation project(':solr:contrib:analysis-extras')
+  testImplementation project(':solr:test-framework')
+
+  testImplementation ('org.apache.hadoop:hadoop-common::tests') { transitive = false }
+  testImplementation ('org.apache.hadoop:hadoop-hdfs') { transitive = false }
+  testImplementation ('org.apache.hadoop:hadoop-hdfs::tests') { transitive = false }
+  testImplementation ('org.apache.hadoop:hadoop-minikdc') { transitive = false }
+
+  testImplementation ('org.apache.kerby:kerb-client') { transitive = false }
+  testImplementation ('org.apache.kerby:kerb-common') { transitive = false }
+  testImplementation ('org.apache.kerby:kerb-identity') { transitive = false }
+  testImplementation ('org.apache.kerby:kerb-server') { transitive = false }
+  testImplementation ('org.apache.kerby:kerb-simplekdc') { transitive = false }
+  testImplementation ('org.apache.kerby:kerb-admin') { transitive = false }
+  testImplementation ('org.apache.kerby:kerby-kdc') { transitive = false }
+
+  testImplementation ('com.sun.jersey:jersey-servlet:1.19.4') { transitive = false }
+
+  testImplementation 'com.google.protobuf:protobuf-java'
+  testImplementation 'commons-logging:commons-logging'
+  testImplementation('org.mockito:mockito-core', {
+    exclude group: "net.bytebuddy", module: "byte-buddy-agent"
+  })
+
+  testRuntimeOnly 'org.slf4j:log4j-over-slf4j'
+}
+
diff --git a/solr/example/build.gradle b/solr/example/build.gradle
new file mode 100644
index 0000000..e1a5314
--- /dev/null
+++ b/solr/example/build.gradle
@@ -0,0 +1,46 @@
+
+// I am not convinced packaging of examples should be a separate project... Seems more logical to
+// move it to just the packaging project (?). Let's leave it for now though.
+
+configurations {
+  packaging
+  postJar
+  dih
+}
+
+dependencies {
+  postJar project(path: ":solr:core", configuration: "postJar")
+  dih 'org.hsqldb:hsqldb'
+  dih 'org.apache.derby:derby'
+}
+
+ext {
+  packagingDir = file("${buildDir}/packaging")
+}
+
+task assemblePackaging(type: Sync) {
+  from(projectDir, {
+    include "example-DIH/**"
+    include "exampledocs/**"
+    include "files/**"
+    include "films/**"
+    include "README.txt"
+    exclude "**/*.jar"
+  })
+
+  from(configurations.postJar, {
+    into "exampledocs/"
+  })
+
+  from(configurations.dih, {
+    into "example-DIH/solr/db/lib"
+  })
+
+  into packagingDir
+}
+
+artifacts {
+  packaging packagingDir, {
+    builtBy assemblePackaging
+  }
+}
diff --git a/solr/packaging/build.gradle b/solr/packaging/build.gradle
new file mode 100644
index 0000000..57b1572
--- /dev/null
+++ b/solr/packaging/build.gradle
@@ -0,0 +1,95 @@
+
+// This project puts together a "distribution", assembling dependencies from
+// various other projects.
+
+plugins {
+  id 'base'
+}
+
+ext {
+  distDir = file("$buildDir/solr-${version}")
+}
+
+configurations {
+  distSolr {
+    transitive = false
+  }
+  distSolrj
+  contrib
+  example
+  server
+}
+
+dependencies {
+  distSolrj project(":solr:solrj")
+
+  [":solr:contrib:analysis-extras",
+   ":solr:contrib:analytics",
+   ":solr:contrib:extraction",
+   ":solr:contrib:clustering",
+   ":solr:contrib:dataimporthandler",
+   ":solr:contrib:dataimporthandler-extras",
+   ":solr:contrib:jaegertracer-configurator",
+   ":solr:contrib:langid",
+   ":solr:contrib:ltr",
+   ":solr:contrib:prometheus-exporter",
+   ":solr:contrib:velocity",
+  ].each { contribName ->
+    distSolr project(contribName)
+    contrib  project(path: contribName, configuration: "packaging")
+  }
+
+  distSolr project(":solr:core")
+  distSolr project(":solr:solrj")
+  distSolr project(":solr:test-framework")
+
+  example project(path: ":solr:example", configuration: "packaging")
+  server project(path: ":solr:server", configuration: "packaging")
+}
+
+task toDir(type: Sync) {
+  from(project(":solr").projectDir, {
+    include "bin/**"
+    include "licenses/**"
+    include "CHANGES.txt"
+    include "LICENSE.txt"
+    include "NOTICE.txt"
+    include "README.txt"
+  })
+
+  from(project(":lucene").projectDir, {
+    include "CHANGES.txt"
+    rename { file -> 'LUCENE_CHANGES.txt' }
+  })
+
+  from(configurations.contrib, {
+    into "contrib"
+  })
+
+  from(configurations.distSolr, {
+    into "dist"
+  })
+
+  from(configurations.distSolrj - configurations.distSolr, {
+    into "dist/solrj-lib"
+  })
+
+  from(configurations.example, {
+    into "example"
+  })
+
+  from(configurations.server, {
+    into "server"
+  })
+
+  // docs/   - TODO: this is assembled via XSLT... leaving out for now.
+
+  into distDir
+
+  doLast {
+    logger.lifecycle "Solr distribution assembled under: ${distDir}"
+  }
+}
+
+assemble.dependsOn toDir
+
diff --git a/solr/server/build.gradle b/solr/server/build.gradle
new file mode 100644
index 0000000..fd0e880
--- /dev/null
+++ b/solr/server/build.gradle
@@ -0,0 +1,107 @@
+apply plugin: 'java-library'
+
+configurations {
+  api {
+    exclude group: "org.slf4j"
+  }
+  startJar
+  libExt
+  webapp
+  packaging
+}
+
+dependencies {
+  api('org.eclipse.jetty:jetty-alpn-java-server', {
+    exclude group: "org.eclipse.jetty.alpn", module: "alpn-api"
+  })
+
+  api('io.dropwizard.metrics:metrics-core', {
+    exclude group: "com.rabbitmq", module: "amqp-client"
+  })
+  api('io.dropwizard.metrics:metrics-graphite', {
+    exclude group: "com.rabbitmq", module: "amqp-client"
+  })
+  api 'io.dropwizard.metrics:metrics-jetty9'
+  api 'io.dropwizard.metrics:metrics-jvm'
+  api 'io.dropwizard.metrics:metrics-jmx'
+
+  api 'org.eclipse.jetty:jetty-continuation'
+  api 'org.eclipse.jetty:jetty-deploy'
+  api 'org.eclipse.jetty:jetty-http'
+  api 'org.eclipse.jetty:jetty-io'
+  api 'org.eclipse.jetty:jetty-jmx'
+  api 'org.eclipse.jetty:jetty-rewrite'
+  api 'org.eclipse.jetty:jetty-security'
+  api 'org.eclipse.jetty:jetty-server'
+  api 'org.eclipse.jetty:jetty-servlet'
+  api 'org.eclipse.jetty:jetty-servlets'
+  api 'org.eclipse.jetty:jetty-util'
+  api 'org.eclipse.jetty:jetty-webapp'
+  api 'org.eclipse.jetty:jetty-xml'
+  api 'org.eclipse.jetty:jetty-alpn-server'
+
+  api 'org.eclipse.jetty.http2:http2-server'
+  api 'org.eclipse.jetty.http2:http2-common'
+  api 'org.eclipse.jetty.http2:http2-hpack'
+
+  api 'javax.servlet:javax.servlet-api'
+
+  libExt 'com.lmax:disruptor'
+  libExt 'org.slf4j:jcl-over-slf4j'
+  libExt 'org.slf4j:jul-to-slf4j'
+  libExt 'org.slf4j:slf4j-api'
+  libExt 'org.apache.logging.log4j:log4j-1.2-api'
+  libExt 'org.apache.logging.log4j:log4j-api'
+  libExt 'org.apache.logging.log4j:log4j-core'
+  libExt 'org.apache.logging.log4j:log4j-slf4j-impl'
+  libExt 'org.apache.logging.log4j:log4j-web'
+
+  webapp project(path: ":solr:webapp", configuration: "war")
+
+  startJar('org.eclipse.jetty:jetty-start::shaded', {
+    transitive false
+  })
+}
+
+ext {
+  packagingDir = file("${buildDir}/packaging")
+}
+
+task assemblePackaging(type: Sync) {
+  from(projectDir, {
+    include "contexts/**"
+    include "etc/**"
+    include "modules/**"
+    include "resources/**"
+    include "scripts/**"
+    include "solr/**"
+    include "README.txt"
+  })
+
+  from(configurations.compileClasspath, {
+    into "lib/"
+  })
+
+  from(configurations.libExt, {
+    into "lib/ext"
+  })
+
+  from { project.configurations.startJar.singleFile } {
+    rename { file -> 'start.jar' }
+  }
+
+  dependsOn configurations.webapp
+  from( { zipTree(configurations.webapp.asPath) }, {
+    into "solr-webapp/webapp"
+  })
+
+  into packagingDir
+}
+
+artifacts {
+  packaging packagingDir, {
+    builtBy assemblePackaging
+  }
+}
+
+assemble.dependsOn assemblePackaging
diff --git a/solr/solr-ref-guide/build.gradle b/solr/solr-ref-guide/build.gradle
new file mode 100644
index 0000000..74f3d07
--- /dev/null
+++ b/solr/solr-ref-guide/build.gradle
@@ -0,0 +1,328 @@
+// TODO 1: the separation of sources between tools and refGuide is awkward; it'd be
+// better to separate the refGuideTools as a plain Java module and then depend on
+// it as a project dependency. This would enable this module to *not* be a java module at all
+// and inherit from base, adding just refGuide-related tasks.
+// OR (better) one could rewrite those tools in Groovy (or Kotlin) and use them directly, without
+// an additional compilation phase.
+
+// TODO 2: property expansion via ant properties is awkward in gradle. We can do cleaner than going
+// through ant -- we could use gradle's expand when copying or at least use some more humane
+// property names.
+
+// TODO 3: task names currently roughly correspond to ant tasks. Simpler aliases (such as 'html')
+// would be much clearer.
+
+// TODO 4: currently buildscript dependencies are hardcoded (asciidoctor) because they can't be resolved
+// using Palantir's plugin. This is another reason to switch to gradle-based tools -- then
+// only the build script dependencies would be needed and asciidoctorj would be removed from version
+// properties entirely (it is only used locally in this build file).
+
+import java.time.*
+import java.time.format.*
+import java.nio.file.*
+import org.asciidoctor.*
+
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath "org.asciidoctor:asciidoctorj:1.6.2"
+    }
+}
+
+plugins {
+    id 'java'
+    id 'com.github.jruby-gradle.base' version '1.6.0'
+}
+
+configurations {
+    depVer
+    refGuide
+}
+
+dependencies {
+    // Dependencies to compile internal tools.
+    implementation('org.asciidoctor:asciidoctorj')
+    implementation('com.vaadin.external.google:android-json')
+    implementation('org.jsoup:jsoup')
+    implementation('org.slf4j:jcl-over-slf4j')
+    implementation('org.slf4j:slf4j-simple')
+    implementation('org.apache.logging.log4j:log4j-core')
+    implementation('com.google.guava:guava')
+    implementation('commons-codec:commons-codec')
+
+    // Dependencies referenced in the guide.
+    depVer('commons-codec:commons-codec')
+    depVer('io.dropwizard.metrics:metrics-core')
+    depVer('org.apache.logging.log4j:log4j-core')
+    depVer('org.apache.opennlp:opennlp-tools')
+    depVer('org.apache.tika:tika-core')
+    depVer('org.apache.velocity.tools:velocity-tools-generic')
+    depVer('org.apache.zookeeper:zookeeper')
+
+    // jekyll dependencies
+    jrubyExec 'rubygems:jekyll:3.5.2'
+    jrubyExec 'rubygems:jekyll-asciidoc:3.0.0'
+
+    // don't know why we have to explicitly add these deps but it doesn't resolve them
+    // automatically.
+    jrubyExec 'rubygems:tilt:2.0.10'
+    jrubyExec 'rubygems:slim:4.0.1'
+    jrubyExec 'rubygems:concurrent-ruby:1.0.5'
+}
+
+sourceSets {
+    refGuide {
+        java {
+            srcDirs = ['src']
+        }
+    }
+
+    main {
+        java {
+            srcDirs = ['tools']
+            exclude "**/CustomizedAsciidoctorAntTask.java"
+            exclude "**/asciidoctor-antlib.xml"
+        }
+    }
+}
+
+ext {
+    buildContentDir = file("${buildDir}/content")
+    mainPage = "index"
+
+    solrDocsVersion = "${version}".replaceAll(/^(\d+\.\d+)(|\..*)$/, "\$1")
+    solrDocsVersionPath = "${solrDocsVersion}".replaceAll(/^(\d+)\.(\d+)$/, "\$1_\$2_0")
+
+    if (!project.hasProperty("solrGuideVersion")) {
+        solrGuideVersion = "${solrDocsVersion}-DRAFT"
+    }
+
+    solrRootPath = '../../../../solr/'
+    solrGuideDraftStatus = solrGuideVersion.matches(/^\d+\.\d+(|\.\d+)$/) ? "" : "DRAFT"
+    solrGuideVersionPath = solrGuideVersion.replaceAll(/^(\d+)\.(\d+)(-DRAFT)?.*/, "\$1_\$2\$3")
+
+    javadocLink = "https://docs.oracle.com/en/java/javase/11/docs/api/"
+
+    if (project.hasProperty("local.javadocs")) {
+        htmlSolrJavadocs = "link:../../docs/"
+        htmlLuceneJavadocs = "link:../../../../lucene/build/docs/"
+    } else {
+        htmlSolrJavadocs = "https://lucene.apache.org/solr/${solrDocsVersionPath}/"
+        htmlLuceneJavadocs = "https://lucene.apache.org/core/${solrDocsVersionPath}/"
+    }
+
+    bareBonesDir = file("${buildDir}/bare-bones-html")
+
+    def tstamp = ZonedDateTime.now()
+    buildDate = DateTimeFormatter.ofPattern("yyyy-MM-dd").format(tstamp)
+    buildTime = DateTimeFormatter.ofPattern("HH:mm:ss").format(tstamp)
+    buildYear = DateTimeFormatter.ofPattern("yyyy").format(tstamp)
+
+    // Some additional version properties are lazily computed
+    // in setupProps (because they need to be computed after evalution is complete).
+    props = [
+            "solr-root-path"         : solrRootPath,
+            "solr-guide-draft-status": solrGuideDraftStatus,
+            "solr-guide-version"     : solrGuideVersion,
+            "solr-guide-version-path": solrGuideVersionPath,
+            "solr-docs-version"      : solrDocsVersion,
+            "javadoc.link"           : javadocLink,
+            "java-javadocs"          : javadocLink,
+            "solr-javadocs"          : htmlSolrJavadocs,
+            "html-solr-javadocs"     : htmlSolrJavadocs,
+            "lucene-javadocs"        : htmlLuceneJavadocs,
+            "html-lucene-javadocs"   : htmlLuceneJavadocs,
+            "build-date"             : buildDate,
+            "DSTAMP"                 : buildDate,
+            "build-year"             : buildYear,
+            "current.year"           : buildYear
+    ]
+
+    asciiDocAttrs = [
+            'common': [
+                    'attribute-missing' : 'warn',
+                    'section-toc'       : '',
+                    'icons'             : 'font',
+                    'icon-set'          : 'fa',
+                    'figure-caption!'   : '',
+                    'idprefix'          : '',
+                    'idseparator'       : '-',
+                    'source-highlighter': 'coderay',
+                    'solr-root-path'    : solrRootPath,
+            ],
+            'html'  : [
+                    'imagesdir': buildContentDir.toString()
+            ]
+    ]
+}
+
+// Tasks modeled after ant file until we have a working build.
+
+task setupProps {
+    doFirst {
+        // These properties have to be resolved after configuration phase (palantir's constraint)
+        // so we can't use them as input for caches.
+        [
+                ["ivyversions./commons-codec/commons-codec", "commons-codec", "commons-codec"],
+                ["ivyversions.io.dropwizard.metrics.version", "io.dropwizard.metrics", "metrics-core"],
+                ["ivyversions.org.apache.logging.log4j.version", "org.apache.logging.log4j", "log4j-core"],
+                ["ivyversions./org.apache.opennlp/opennlp-tools", "org.apache.opennlp", "opennlp-tools"],
+                ["ivyversions.org.apache.tika.version", "org.apache.tika", "tika-core"],
+                ["ivyversions.org.apache.velocity.tools.version", "org.apache.velocity.tools", "velocity-tools-generic"],
+                ["ivyversions./org.apache.zookeeper/zookeeper", "org.apache.zookeeper", "zookeeper"],
+
+                ["ivy-zookeeper-version", "org.apache.zookeeper", "zookeeper"],
+                ["ivy-log4j-version", "org.apache.logging.log4j", "log4j-core"],
+                ["ivy-tika-version", "org.apache.tika", "tika-core"],
+                ["ivy-opennlp-version", "org.apache.opennlp", "opennlp-tools"],
+                ["ivy-commons-codec-version", "commons-codec", "commons-codec"],
+                ["ivy-velocity-tools-version", "org.apache.velocity.tools", "velocity-tools-generic"],
+                ["ivy-dropwizard-version", "io.dropwizard.metrics", "metrics-core"]
+        ].each { antProp, depGroup, depId ->
+            props[antProp] = getVersion(depGroup, depId, configurations.depVer)
+        }
+
+        // Emit info about properties for clarity.
+        logger.warn("Building ref guide with:\n" + props.collect({ k, v -> "  ${k} -> ${v}" }).join('\n'))
+    }
+}
+
+task buildInit(type: Sync) {
+    dependsOn setupProps
+
+    def dummyAntProject = new org.apache.tools.ant.Project()
+
+    // If replaceable properties change, we have to rerun.
+    inputs.properties props
+
+    from(file("src"), {
+        exclude '**/*.template'
+    })
+
+    from(file("src"), {
+        include '**/*.template'
+        rename '(.+)\\.template', '$1'
+        filteringCharset = 'UTF-8'
+        filter(org.apache.tools.ant.filters.ExpandProperties, project: dummyAntProject)
+    })
+
+    doFirst {
+        props.each { k, v ->
+          dummyAntProject.setProperty(k, v)
+        }
+    }
+
+    into buildContentDir
+}
+
+task buildNavDataFiles(type: JavaExec) {
+    dependsOn buildInit, classes
+    classpath = sourceSets.main.runtimeClasspath
+
+    main = 'BuildNavDataFiles'
+    workingDir = buildContentDir
+
+    args([
+            "${buildContentDir}",
+            "${mainPage}"
+    ])
+
+    doFirst {
+        // Remove previously generated files first.
+        [
+                "scrollnav.json",
+                "sidebar.json"
+        ].each { name ->
+            project.delete(file("${buildContentDir}/_data/${name}"))
+        }
+    }
+}
+
+task bareBonesAsciiDoctor {
+    dependsOn buildNavDataFiles
+
+    doLast {
+        // Regenerate fully.
+        project.delete(bareBonesDir)
+        bareBonesDir.mkdirs()
+
+        // Convert each file separately so that folders are preserved.
+        Path source = buildContentDir.toPath().toAbsolutePath().normalize()
+        Path target = bareBonesDir.toPath().toAbsolutePath().normalize()
+
+        Asciidoctor adoc = Asciidoctor.Factory.create()
+        fileTree(source, {
+            include "**/*.adoc"
+            exclude "**/_*"
+        }).each { file ->
+            Path relative = source.relativize(file.toPath())
+            Path targetDir = target.resolve(relative).getParent()
+
+            def opts = OptionsBuilder.options()
+                    .backend('html5')
+                    .docType("book")
+                    .headerFooter(false)
+                    .safe(SafeMode.UNSAFE)
+                    .baseDir(source.toFile())
+                    .toDir(targetDir.toFile())
+                    .destinationDir(targetDir.toFile())
+                    .mkDirs(true)
+                    .attributes(asciiDocAttrs.common + asciiDocAttrs.html + props)
+
+            adoc.convertFile(file, opts)
+        }
+    }
+}
+
+task bareBonesHtmlValidation(type: JavaExec) {
+    dependsOn bareBonesAsciiDoctor
+    description("Builds (w/o Jekyll) a very simple html version of the guide and runs link/anchor validation on it")
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'CheckLinksAndAnchors'
+    workingDir = buildContentDir
+
+    args([
+            "${bareBonesDir}",
+            "-bare-bones"
+    ])
+
+    if (project.hasProperty("local.javadocs")) {
+        args += "-check-all-relative-links"
+    }
+}
+
+task buildSiteJekyll(type: com.github.jrubygradle.JRubyExec) {
+    dependsOn buildNavDataFiles
+
+    inputs.dir buildContentDir
+    outputs.dir file("${buildDir}/html-site")
+
+    script 'jekyll'
+    scriptArgs 'build' //, '--verbose'
+    workingDir buildContentDir
+}
+
+task buildSite(type: JavaExec) {
+    group "Documentation"
+    description "Builds an HTML Site w/Jekyll and verifies the anchors+links are valid"
+
+    dependsOn buildSiteJekyll
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'CheckLinksAndAnchors'
+    workingDir = buildContentDir
+
+    args([
+            file("${buildDir}/html-site"),
+    ])
+
+    if (project.hasProperty("local.javadocs")) {
+        args += "-check-all-relative-links"
+    }
+}
+
+check.dependsOn bareBonesHtmlValidation
diff --git a/solr/solrj/build.gradle b/solr/solrj/build.gradle
new file mode 100644
index 0000000..c77d2dc
--- /dev/null
+++ b/solr/solrj/build.gradle
@@ -0,0 +1,55 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  api 'org.slf4j:slf4j-api'
+  implementation 'org.slf4j:jcl-over-slf4j'
+
+  api 'commons-io:commons-io'
+  api 'org.apache.commons:commons-math3'
+
+  api 'org.eclipse.jetty.http2:http2-client'
+  api 'org.eclipse.jetty.http2:http2-http-client-transport'
+  api 'org.eclipse.jetty:jetty-util'
+  api 'org.eclipse.jetty:jetty-http'
+  api 'org.eclipse.jetty:jetty-alpn-java-client'
+  api 'org.eclipse.jetty:jetty-alpn-client'
+
+  api('org.apache.httpcomponents:httpmime', {
+    exclude group: "commons-codec", module: "commons-codec"
+    exclude group: "commons-logging", module: "commons-logging"
+  })
+
+  api 'io.netty:netty-buffer'
+  api 'io.netty:netty-codec'
+  api 'io.netty:netty-common'
+  api 'io.netty:netty-handler'
+  api 'io.netty:netty-resolver'
+  api 'io.netty:netty-transport'
+  api 'io.netty:netty-transport-native-epoll'
+  api 'io.netty:netty-transport-native-unix-common'
+
+  api('org.apache.zookeeper:zookeeper', {
+    exclude group: "org.apache.yetus", module: "audience-annotations"
+    exclude group: "io.netty", module: "netty-all"
+    exclude group: "log4j", module: "log4j"
+    exclude group: "org.slf4j", module: "slf4j-log4j12"
+  })
+
+  api('org.codehaus.woodstox:woodstox-core-asl', {
+    exclude group: "javax.xml.stream", module: "stax-api"
+  })
+
+  testImplementation project(':solr:test-framework')
+  testImplementation 'org.eclipse.jetty:jetty-webapp'
+  testImplementation 'org.eclipse.jetty:jetty-alpn-java-server'
+  testImplementation 'org.restlet.jee:org.restlet.ext.servlet'
+  testImplementation 'org.objenesis:objenesis'
+  testImplementation('org.mockito:mockito-core', {
+    exclude group: "net.bytebuddy", module: "byte-buddy-agent"
+  })
+  testImplementation("org.apache.logging.log4j:log4j-slf4j-impl", {
+    exclude group: "org.apache.logging.log4j", module: "log4j-api"
+  })
+  testImplementation "org.hsqldb:hsqldb"
+}
diff --git a/solr/test-framework/build.gradle b/solr/test-framework/build.gradle
new file mode 100644
index 0000000..6b6eca9
--- /dev/null
+++ b/solr/test-framework/build.gradle
@@ -0,0 +1,15 @@
+
+apply plugin: 'java-library'
+
+dependencies {
+  api project(':solr:core')
+  api project(':solr:solrj')
+  api project(':lucene:test-framework')
+  api project(':lucene:analysis:common')
+
+  api 'org.apache.logging.log4j:log4j-core'
+  api 'io.opentracing:opentracing-mock'
+
+  implementation 'io.dropwizard.metrics:metrics-jetty9'
+  implementation 'com.lmax:disruptor'
+}
diff --git a/solr/webapp/build.gradle b/solr/webapp/build.gradle
new file mode 100644
index 0000000..f915c93
--- /dev/null
+++ b/solr/webapp/build.gradle
@@ -0,0 +1,37 @@
+plugins {
+  id 'java'
+  id 'war'
+}
+
+configurations {
+  war {}
+}
+
+dependencies {
+  implementation(project(":solr:core"), {
+    exclude module: "server"
+
+    // Exclude additional deps from core and logging deps to sync up with ant.
+    // This is suspicious that we have to do it though.
+    exclude group: "org.apache.kerby", module: "kerby-config"
+    exclude group: "org.apache.kerby", module: "kerby-util"
+    exclude group: "org.apache.kerby", module: "kerb-crypto"
+    exclude group: "org.slf4j"
+    exclude group: "org.apache.logging.log4j"
+  })
+}
+
+war {
+  // Why are they in the source code at all if they're excluded from the distribution?
+  exclude "libs/angular-cookies.js"
+  exclude "libs/angular-route.js"
+  exclude "libs/angular-sanitize.js"
+  exclude "libs/angular-utf8-base.js"
+  exclude "libs/angular.js"
+  exclude "libs/chosen.jquery.js"
+}
+
+// Expose 'war' archive as an artifact so that it can be packaged in the distribution.
+artifacts {
+  war tasks.war
+}
\ No newline at end of file
diff --git a/versions.lock b/versions.lock
new file mode 100644
index 0000000..d02ff02
--- /dev/null
+++ b/versions.lock
@@ -0,0 +1,230 @@
+# Run ./gradlew --write-locks to regenerate this file
+com.adobe.xmp:xmpcore:5.1.3 (1 constraints: 0b050a36)
+com.beust:jcommander:1.35 (1 constraints: b50c1901)
+com.carrotsearch:hppc:0.8.1 (2 constraints: af0fd8a6)
+com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.7.2 (1 constraints: 0d050c36)
+com.carrotsearch.thirdparty:simple-xml-safe:2.7.1 (1 constraints: a60a82ca)
+com.cybozu.labs:langdetect:1.1-20120112 (1 constraints: 5c066d5e)
+com.drewnoakes:metadata-extractor:2.11.0 (1 constraints: 3605323b)
+com.epam:parso:2.0.9 (1 constraints: 0d05fe35)
+com.fasterxml.jackson.core:jackson-annotations:2.9.9 (2 constraints: 0a1d1637)
+com.fasterxml.jackson.core:jackson-core:2.9.9 (3 constraints: 23350c6a)
+com.fasterxml.jackson.core:jackson-databind:2.9.9.3 (3 constraints: 741a6389)
+com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.9.9 (1 constraints: 16051936)
+com.github.ben-manes.caffeine:caffeine:2.8.0 (1 constraints: 0c050d36)
+com.github.jnr:jffi:1.2.18 (1 constraints: b20902ab)
+com.github.jnr:jnr-constants:0.9.12 (4 constraints: ed2c9d5d)
+com.github.jnr:jnr-enxio:0.19 (2 constraints: 2a167d08)
+com.github.jnr:jnr-netdb:1.1.6 (1 constraints: 7e0952a1)
+com.github.jnr:jnr-posix:3.0.49 (2 constraints: f0161b5b)
+com.github.jnr:jnr-unixsocket:0.20 (1 constraints: 4a09d497)
+com.github.virtuald:curvesapi:1.04 (1 constraints: d904f330)
+com.github.zafarkhaja:java-semver:0.9.0 (1 constraints: 0b050636)
+com.google.code.findbugs:jsr305:3.0.2 (1 constraints: 170aecb4)
+com.google.errorprone:error_prone_annotations:2.1.3 (1 constraints: 180aebb4)
+com.google.guava:guava:25.1-jre (1 constraints: 4a06b047)
+com.google.j2objc:j2objc-annotations:1.1 (1 constraints: b609eba0)
+com.google.protobuf:protobuf-java:3.7.1 (1 constraints: 0d051036)
+com.google.re2j:re2j:1.2 (1 constraints: a7041c2c)
+com.googlecode.juniversalchardet:juniversalchardet:1.0.3 (1 constraints: 0605f335)
+com.googlecode.mp4parser:isoparser:1.1.22 (1 constraints: 38052d3b)
+com.headius:backport9:1.1 (1 constraints: 1a098c8e)
+com.headius:invokebinder:1.11 (1 constraints: 4b09d797)
+com.headius:modulator:1.0 (1 constraints: 19098b8e)
+com.headius:options:1.4 (1 constraints: 1d098f8e)
+com.healthmarketscience.jackcess:jackcess:2.1.12 (1 constraints: 3805313b)
+com.healthmarketscience.jackcess:jackcess-encrypt:2.1.4 (1 constraints: 0905fc35)
+com.ibm.icu:icu4j:62.1 (1 constraints: dd040c31)
+com.jayway.jsonpath:json-path:2.4.0 (1 constraints: 08050136)
+com.jcraft:jzlib:1.1.3 (1 constraints: 7b094fa1)
+com.lmax:disruptor:3.4.2 (1 constraints: 0b050836)
+com.martiansoftware:nailgun-server:0.9.1 (1 constraints: 800960a1)
+com.pff:java-libpst:0.8.1 (1 constraints: 0b050436)
+com.rometools:rome:1.5.1 (1 constraints: 09050036)
+com.rometools:rome-utils:1.5.1 (1 constraints: 09050036)
+com.sun.mail:gimap:1.5.1 (1 constraints: 09050036)
+com.sun.mail:javax.mail:1.5.1 (2 constraints: 830d2844)
+com.tdunning:t-digest:3.1 (1 constraints: a804212c)
+com.vaadin.external.google:android-json:0.0.20131108.vaadin1 (1 constraints: 34092a9e)
+commons-cli:commons-cli:1.2 (1 constraints: a7041c2c)
+commons-codec:commons-codec:1.11 (1 constraints: d704f230)
+commons-collections:commons-collections:3.2.2 (1 constraints: 09050236)
+commons-fileupload:commons-fileupload:1.3.3 (1 constraints: 0905fc35)
+commons-io:commons-io:2.5 (2 constraints: be142680)
+commons-logging:commons-logging:1.2 (2 constraints: c8149e7f)
+de.l3s.boilerpipe:boilerpipe:1.1.0 (1 constraints: 0405f335)
+io.dropwizard.metrics:metrics-core:4.0.5 (5 constraints: 204326bf)
+io.dropwizard.metrics:metrics-graphite:4.0.5 (1 constraints: 0b050436)
+io.dropwizard.metrics:metrics-jetty9:4.0.5 (1 constraints: 0b050436)
+io.dropwizard.metrics:metrics-jmx:4.0.5 (1 constraints: 0b050436)
+io.dropwizard.metrics:metrics-jvm:4.0.5 (1 constraints: 0b050436)
+io.jaegertracing:jaeger-core:0.35.5 (1 constraints: 970d1034)
+io.jaegertracing:jaeger-thrift:0.35.5 (1 constraints: 3f053f3b)
+io.netty:netty-buffer:4.1.29.Final (4 constraints: 5b3421e8)
+io.netty:netty-codec:4.1.29.Final (2 constraints: fc135782)
+io.netty:netty-common:4.1.29.Final (5 constraints: 88482df2)
+io.netty:netty-handler:4.1.29.Final (1 constraints: 5a076061)
+io.netty:netty-resolver:4.1.29.Final (2 constraints: 0b15d4b5)
+io.netty:netty-transport:4.1.29.Final (5 constraints: 7847682a)
+io.netty:netty-transport-native-epoll:4.1.29.Final (1 constraints: 5a076061)
+io.netty:netty-transport-native-unix-common:4.1.29.Final (2 constraints: 081ace05)
+io.opentracing:opentracing-api:0.33.0 (5 constraints: 4c3c8052)
+io.opentracing:opentracing-mock:0.33.0 (1 constraints: 3805343b)
+io.opentracing:opentracing-noop:0.33.0 (3 constraints: 7c2142bd)
+io.opentracing:opentracing-util:0.33.0 (3 constraints: f61f583b)
+io.prometheus:simpleclient:0.2.0 (3 constraints: fe242db8)
+io.prometheus:simpleclient_common:0.2.0 (2 constraints: e8159ecb)
+io.prometheus:simpleclient_httpserver:0.2.0 (1 constraints: 0405f135)
+io.sgr:s2-geometry-library-java:1.0.0 (1 constraints: 0305f035)
+javax.activation:activation:1.1.1 (3 constraints: 1017445c)
+javax.servlet:javax.servlet-api:3.1.0 (3 constraints: 75209943)
+joda-time:joda-time:2.9.9 (1 constraints: 8a0972a1)
+junit:junit:4.12 (2 constraints: 3e1e6104)
+net.arnx:jsonic:1.2.7 (2 constraints: db10d4d1)
+net.hydromatic:eigenbase-properties:1.1.5 (1 constraints: 0905f835)
+net.sourceforge.argparse4j:argparse4j:0.8.1 (1 constraints: 0b050436)
+net.sourceforge.nekohtml:nekohtml:1.9.17 (1 constraints: 4405503b)
+net.thisptr:jackson-jq:0.0.8 (1 constraints: 0a05f335)
+org.antlr:antlr4-runtime:4.5.1-1 (1 constraints: 6a05b240)
+org.apache.calcite:calcite-core:1.18.0 (1 constraints: 3c05413b)
+org.apache.calcite:calcite-linq4j:1.18.0 (1 constraints: 3c05413b)
+org.apache.calcite.avatica:avatica-core:1.13.0 (1 constraints: 3705323b)
+org.apache.commons:commons-collections4:4.2 (1 constraints: aa04252c)
+org.apache.commons:commons-compress:1.18 (1 constraints: de04f930)
+org.apache.commons:commons-configuration2:2.1.1 (1 constraints: 0605f935)
+org.apache.commons:commons-exec:1.3 (1 constraints: a8041d2c)
+org.apache.commons:commons-lang3:3.8.1 (7 constraints: 33673f05)
+org.apache.commons:commons-math3:3.6.1 (1 constraints: 0c050d36)
+org.apache.commons:commons-text:1.6 (1 constraints: ab04202c)
+org.apache.curator:curator-client:2.13.0 (1 constraints: 3805383b)
+org.apache.curator:curator-framework:2.13.0 (1 constraints: 3805383b)
+org.apache.curator:curator-recipes:2.13.0 (1 constraints: 3805383b)
+org.apache.hadoop:hadoop-annotations:3.2.0 (1 constraints: 07050036)
+org.apache.hadoop:hadoop-auth:3.2.0 (1 constraints: 07050036)
+org.apache.hadoop:hadoop-common:3.2.0 (1 constraints: 07050036)
+org.apache.hadoop:hadoop-hdfs-client:3.2.0 (1 constraints: 07050036)
+org.apache.htrace:htrace-core4:4.1.0-incubating (1 constraints: 58090086)
+org.apache.httpcomponents:httpclient:4.5.6 (2 constraints: 6514987d)
+org.apache.httpcomponents:httpcore:4.4.10 (2 constraints: 9015d5cd)
+org.apache.httpcomponents:httpmime:4.5.6 (1 constraints: 11051436)
+org.apache.james:apache-mime4j-core:0.8.2 (1 constraints: 0c050536)
+org.apache.james:apache-mime4j-dom:0.8.2 (1 constraints: 0c050536)
+org.apache.kerby:kerb-core:1.0.1 (3 constraints: f11c583a)
+org.apache.kerby:kerb-crypto:1.0.1 (1 constraints: 860b05e6)
+org.apache.kerby:kerb-util:1.0.1 (1 constraints: 0405f135)
+org.apache.kerby:kerby-asn1:1.0.1 (2 constraints: 001155df)
+org.apache.kerby:kerby-config:1.0.1 (1 constraints: 860b05e6)
+org.apache.kerby:kerby-pkix:1.0.1 (2 constraints: 741065ca)
+org.apache.kerby:kerby-util:1.0.1 (2 constraints: 6518bdb6)
+org.apache.logging.log4j:log4j-api:2.11.2 (3 constraints: c124e473)
+org.apache.logging.log4j:log4j-core:2.11.2 (2 constraints: 09164024)
+org.apache.logging.log4j:log4j-slf4j-impl:2.11.2 (1 constraints: 3805343b)
+org.apache.opennlp:opennlp-tools:1.9.1 (1 constraints: 0d050c36)
+org.apache.pdfbox:fontbox:2.0.12 (1 constraints: 37052d3b)
+org.apache.pdfbox:jempbox:1.8.16 (1 constraints: 42054b3b)
+org.apache.pdfbox:pdfbox:2.0.12 (1 constraints: 37052d3b)
+org.apache.pdfbox:pdfbox-tools:2.0.12 (1 constraints: 37052d3b)
+org.apache.poi:poi:4.0.0 (1 constraints: 0605ff35)
+org.apache.poi:poi-ooxml:4.0.0 (1 constraints: 0605ff35)
+org.apache.poi:poi-ooxml-schemas:4.0.0 (1 constraints: 0605ff35)
+org.apache.poi:poi-scratchpad:4.0.0 (1 constraints: 0605ff35)
+org.apache.thrift:libthrift:0.12.0 (1 constraints: 8d0dfa33)
+org.apache.tika:tika-core:1.19.1 (1 constraints: 3e05453b)
+org.apache.tika:tika-java7:1.19.1 (1 constraints: 3e05453b)
+org.apache.tika:tika-parsers:1.19.1 (1 constraints: 3e05453b)
+org.apache.tika:tika-xmp:1.19.1 (1 constraints: 3e05453b)
+org.apache.velocity:velocity-engine-core:2.0 (3 constraints: 973bcd79)
+org.apache.velocity.tools:velocity-tools-generic:3.0 (1 constraints: 00136415)
+org.apache.velocity.tools:velocity-tools-view:3.0 (1 constraints: 7a14126a)
+org.apache.velocity.tools:velocity-tools-view-jsp:3.0 (1 constraints: a704202c)
+org.apache.xmlbeans:xmlbeans:3.0.1 (1 constraints: 0605fb35)
+org.apache.zookeeper:zookeeper:3.5.5 (1 constraints: 0f050e36)
+org.apache.zookeeper:zookeeper-jute:3.5.5 (1 constraints: 8d0d3928)
+org.asciidoctor:asciidoctorj:1.6.2 (1 constraints: 0b050436)
+org.asciidoctor:asciidoctorj-api:1.6.2 (1 constraints: e30cfb0d)
+org.aspectj:aspectjrt:1.8.0 (1 constraints: 0b050836)
+org.bitbucket.b_c:jose4j:0.6.5 (1 constraints: 0d050236)
+org.bouncycastle:bcmail-jdk15on:1.60 (1 constraints: db04fb30)
+org.bouncycastle:bcpkix-jdk15on:1.60 (1 constraints: db04fb30)
+org.bouncycastle:bcprov-jdk15on:1.60 (1 constraints: db04fb30)
+org.brotli:dec:0.1.2 (1 constraints: 0505f035)
+org.carrot2:carrot2-mini:3.16.2 (1 constraints: 3e05493b)
+org.carrot2:morfologik-fsa:2.1.5 (1 constraints: d70d9836)
+org.carrot2:morfologik-polish:2.1.5 (1 constraints: 0a05fd35)
+org.carrot2:morfologik-stemming:2.1.5 (2 constraints: 0b12640c)
+org.carrot2.attributes:attributes-binder:1.3.3 (1 constraints: a30a73ca)
+org.carrot2.shaded:carrot2-guava:18.0 (2 constraints: b31b3b7b)
+org.ccil.cowan.tagsoup:tagsoup:1.2.1 (1 constraints: 0605f735)
+org.checkerframework:checker-qual:2.0.0 (1 constraints: 140ae5b4)
+org.codehaus.janino:commons-compiler:3.0.9 (2 constraints: d910f7d1)
+org.codehaus.janino:janino:3.0.9 (1 constraints: 0e050336)
+org.codehaus.mojo:animal-sniffer-annotations:1.14 (1 constraints: ea09d5aa)
+org.codehaus.woodstox:stax2-api:3.1.4 (2 constraints: 241635f1)
+org.codehaus.woodstox:woodstox-core-asl:4.4.1 (1 constraints: 0b050c36)
+org.eclipse.jetty:jetty-alpn-client:9.4.19.v20190610 (3 constraints: d12c8400)
+org.eclipse.jetty:jetty-alpn-java-client:9.4.19.v20190610 (1 constraints: 8007517d)
+org.eclipse.jetty:jetty-alpn-java-server:9.4.19.v20190610 (1 constraints: 8007517d)
+org.eclipse.jetty:jetty-alpn-server:9.4.19.v20190610 (2 constraints: 231beedd)
+org.eclipse.jetty:jetty-client:9.4.19.v20190610 (1 constraints: ce1741ae)
+org.eclipse.jetty:jetty-continuation:9.4.19.v20190610 (2 constraints: 5d186efb)
+org.eclipse.jetty:jetty-deploy:9.4.19.v20190610 (1 constraints: 8007517d)
+org.eclipse.jetty:jetty-http:9.4.19.v20190610 (5 constraints: 8b497e4f)
+org.eclipse.jetty:jetty-io:9.4.19.v20190610 (8 constraints: 0f7eb132)
+org.eclipse.jetty:jetty-jmx:9.4.19.v20190610 (1 constraints: 8007517d)
+org.eclipse.jetty:jetty-rewrite:9.4.19.v20190610 (1 constraints: 8007517d)
+org.eclipse.jetty:jetty-security:9.4.19.v20190610 (2 constraints: ea172ade)
+org.eclipse.jetty:jetty-server:9.4.19.v20190610 (6 constraints: bd5e38f5)
+org.eclipse.jetty:jetty-servlet:9.4.19.v20190610 (2 constraints: 641789bf)
+org.eclipse.jetty:jetty-servlets:9.4.19.v20190610 (1 constraints: 8007517d)
+org.eclipse.jetty:jetty-util:9.4.19.v20190610 (7 constraints: 77648009)
+org.eclipse.jetty:jetty-webapp:9.4.19.v20190610 (2 constraints: 72178fc0)
+org.eclipse.jetty:jetty-xml:9.4.19.v20190610 (3 constraints: 56274720)
+org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 (1 constraints: 6513848a)
+org.eclipse.jetty.http2:http2-client:9.4.19.v20190610 (2 constraints: 4d1ff83f)
+org.eclipse.jetty.http2:http2-common:9.4.19.v20190610 (3 constraints: 242bc31f)
+org.eclipse.jetty.http2:http2-hpack:9.4.19.v20190610 (2 constraints: 50198f5c)
+org.eclipse.jetty.http2:http2-http-client-transport:9.4.19.v20190610 (1 constraints: 8007517d)
+org.eclipse.jetty.http2:http2-server:9.4.19.v20190610 (1 constraints: 8007517d)
+org.gagravarr:vorbis-java-core:0.8 (1 constraints: ac041f2c)
+org.gagravarr:vorbis-java-tika:0.8 (1 constraints: ac041f2c)
+org.hamcrest:hamcrest-core:1.3 (2 constraints: 730ad9bf)
+org.jdom:jdom2:2.0.6 (1 constraints: 0a05fb35)
+org.jruby:dirgra:0.3 (1 constraints: 1b098b8e)
+org.jruby:jruby:9.2.6.0 (1 constraints: 490d7d28)
+org.jruby:jruby-core:9.2.6.0 (1 constraints: 0f08b57d)
+org.jruby:jruby-stdlib:9.2.6.0 (1 constraints: 0f08b57d)
+org.jruby.jcodings:jcodings:1.0.41 (2 constraints: e3124361)
+org.jruby.joni:joni:2.1.25 (1 constraints: b00903ab)
+org.jsoup:jsoup:1.11.3 (1 constraints: 38052f3b)
+org.locationtech.spatial4j:spatial4j:0.7 (1 constraints: ab041e2c)
+org.ow2.asm:asm:5.1 (1 constraints: aa04272c)
+org.ow2.asm:asm-commons:5.1 (1 constraints: aa04272c)
+org.restlet.jee:org.restlet:2.3.0 (2 constraints: e3159ee6)
+org.restlet.jee:org.restlet.ext.servlet:2.3.0 (1 constraints: 0705fe35)
+org.rrd4j:rrd4j:3.5 (1 constraints: ac04252c)
+org.slf4j:jcl-over-slf4j:1.7.25 (1 constraints: 4005473b)
+org.slf4j:slf4j-api:1.7.25 (19 constraints: aaff8c21)
+org.slf4j:slf4j-simple:1.7.25 (1 constraints: 4005473b)
+org.tallison:jmatio:1.5 (1 constraints: aa041f2c)
+org.tukaani:xz:1.8 (1 constraints: ad04222c)
+ua.net.nlp:morfologik-ukrainian-search:3.9.0 (1 constraints: 0e051536)
+xerces:xercesImpl:2.9.1 (2 constraints: f613d869)
+
+[Test dependencies]
+com.sun.jersey:jersey-servlet:1.19.4 (1 constraints: 4105483b)
+net.bytebuddy:byte-buddy:1.9.3 (2 constraints: 2510faaf)
+org.apache.derby:derby:10.9.1.0 (1 constraints: 9b054946)
+org.apache.hadoop:hadoop-hdfs:3.2.0 (1 constraints: 07050036)
+org.apache.hadoop:hadoop-minikdc:3.2.0 (1 constraints: 07050036)
+org.apache.kerby:kerb-admin:1.0.1 (1 constraints: 0405f135)
+org.apache.kerby:kerb-client:1.0.1 (1 constraints: 0405f135)
+org.apache.kerby:kerb-common:1.0.1 (1 constraints: 0405f135)
+org.apache.kerby:kerb-identity:1.0.1 (1 constraints: 0405f135)
+org.apache.kerby:kerb-server:1.0.1 (1 constraints: 0405f135)
+org.apache.kerby:kerb-simplekdc:1.0.1 (1 constraints: 0405f135)
+org.apache.kerby:kerby-kdc:1.0.1 (1 constraints: 0405f135)
+org.hsqldb:hsqldb:2.4.0 (1 constraints: 08050136)
+org.locationtech.jts:jts-core:1.15.0 (1 constraints: 3905383b)
+org.mockito:mockito-core:2.23.4 (1 constraints: 3d05403b)
+org.objenesis:objenesis:2.6 (2 constraints: 5f0ffb79)
+org.slf4j:log4j-over-slf4j:1.7.25 (1 constraints: 4005473b)
diff --git a/versions.props b/versions.props
new file mode 100644
index 0000000..bbca163
--- /dev/null
+++ b/versions.props
@@ -0,0 +1,111 @@
+com.adobe.xmp:xmpcore=5.1.3
+com.carrotsearch.randomizedtesting:*=2.7.2
+com.carrotsearch:hppc=0.8.1
+com.cybozu.labs:langdetect=1.1-20120112
+com.drewnoakes:metadata-extractor=2.11.0
+com.epam:parso=2.0.9
+com.fasterxml.jackson*:*=2.9.9
+com.fasterxml.woodstox:*=4.4.1
+com.github.ben-manes.caffeine:caffeine=2.8.0
+com.github.virtuald:curvesapi=1.04
+com.github.zafarkhaja:java-semver=0.9.0
+com.google.guava:guava=25.1-jre
+com.google.protobuf:protobuf-java=3.7.1
+com.google.re2j:re2j=1.2
+com.googlecode.juniversalchardet:juniversalchardet=1.0.3
+com.googlecode.mp4parser:isoparser=1.1.22
+com.healthmarketscience.jackcess:jackcess-encrypt=2.1.4
+com.healthmarketscience.jackcess:jackcess=2.1.12
+com.ibm.icu:icu4j=62.1
+com.jayway.jsonpath:json-path=2.4.0
+com.lmax:disruptor=3.4.2
+com.pff:java-libpst=0.8.1
+com.rometools:*=1.5.1
+com.sun.jersey:*=1.19
+com.sun.jersey:jersey-json=1.19.4
+com.sun.mail:*=1.5.1
+com.tdunning:t-digest=3.1
+com.vaadin.external.google:android-json=0.0.20131108.vaadin1
+commons-beanutils:commons-beanutils=1.9.3
+commons-cli:commons-cli=1.2
+commons-codec:commons-codec=1.11
+commons-collections:commons-collections=3.2.2
+commons-fileupload:commons-fileupload=1.3.3
+commons-io:commons-io=2.5
+commons-logging:commons-logging=1.1.3
+de.l3s.boilerpipe:boilerpipe=1.1.0
+info.ganglia.gmetric4j:gmetric4j=1.0.7
+io.dropwizard.metrics:*=4.0.5
+io.netty:*=4.1.29.Final
+io.opentracing:*=0.33.0
+io.prometheus:*=0.2.0
+io.sgr:s2-geometry-library-java=1.0.0
+javax.activation:activation=1.1.1
+javax.servlet:javax.servlet-api=3.1.0
+junit:junit=4.12
+net.arnx:jsonic=1.2.7
+net.bytebuddy:byte-buddy=1.9.3
+net.hydromatic:eigenbase-properties=1.1.5
+net.sourceforge.argparse4j:argparse4j=0.8.1
+net.sourceforge.nekohtml:nekohtml=1.9.17
+net.thisptr:jackson-jq=0.0.8
+org.antlr:antlr4-runtime=4.5.1-1
+org.apache.calcite.avatica:avatica-core=1.13.0
+org.apache.calcite:*=1.18.0
+org.apache.commons:commons-collections4:4.2
+org.apache.commons:commons-compress=1.18
+org.apache.commons:commons-configuration2=2.1.1
+org.apache.commons:commons-exec=1.3
+org.apache.commons:commons-lang3=3.6
+org.apache.commons:commons-math3=3.6.1
+org.apache.commons:commons-text=1.6
+org.apache.curator:*=2.13.0
+org.apache.derby:derby=10.9.1.0
+org.apache.hadoop:*=3.2.0
+org.apache.htrace:htrace-core4=4.1.0-incubating
+org.apache.httpcomponents:httpclient=4.5.6
+org.apache.httpcomponents:httpcore=4.4.10
+org.apache.httpcomponents:httpmime=4.5.6
+org.apache.james:*=0.8.2
+org.apache.kerby:*=1.0.1
+org.apache.logging.log4j:*=2.11.2
+org.apache.opennlp:opennlp-tools=1.9.1
+org.apache.pdfbox:*=2.0.12
+org.apache.pdfbox:jbig2-imageio=3.0.2
+org.apache.pdfbox:jempbox=1.8.16
+org.apache.poi:*=4.0.0
+org.apache.tika:*=1.19.1
+org.apache.velocity.tools:*=3.0
+org.apache.xmlbeans:xmlbeans=3.0.1
+org.apache.zookeeper:*=3.5.5
+org.asciidoctor:asciidoctorj=1.6.2
+org.aspectj:aspectjrt=1.8.0
+org.bitbucket.b_c:jose4j=0.6.5
+org.bouncycastle:*=1.60
+org.brotli:dec=0.1.2
+org.carrot2:carrot2-mini=3.16.2
+org.carrot2:morfologik-*=2.1.5
+org.ccil.cowan.tagsoup:tagsoup=1.2.1
+org.codehaus.janino:*=3.0.9
+org.codehaus.woodstox:stax2-api=3.1.4
+org.codehaus.woodstox:woodstox-core-asl=4.4.1
+org.eclipse.jetty.http2:*=9.4.19.v20190610
+org.eclipse.jetty:*=9.4.19.v20190610
+org.gagravarr:*=0.8
+org.hamcrest:*=1.3
+org.hsqldb:hsqldb=2.4.0
+org.jdom:jdom2=2.0.6
+org.jsoup:jsoup=1.11.3
+org.locationtech.jts:jts-core=1.15.0
+org.locationtech.spatial4j:*=0.7
+org.mockito:mockito-core=2.23.4
+org.objenesis:objenesis=2.6
+org.ow2.asm:*=5.1
+org.restlet.jee:*=2.3.0
+org.rrd4j:rrd4j=3.5
+org.slf4j:*=1.7.24
+org.tallison:jmatio=1.5
+org.tukaani:xz=1.8
+ua.net.nlp:morfologik-ukrainian-search=3.9.0
+xerces:xercesImpl=2.9.1
+io.jaegertracing:*=0.35.5
\ No newline at end of file