Dependencies
============

Each gradle project can have multiple (named) "configurations"
and each configuration can have dependencies attached to it.

There are some standard conventions so, for example, the Java plugin
adds standard configurations such as "api", "implementation",
"testImplementation" and others. These configurations can also inherit
from each other; more about this typic can be found here:

https://docs.gradle.org/current/userguide/dependency_management_for_java_projects.html#dependency_management_for_java_projects
https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_separation
https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_plugin_and_dependency_management

Lucene typically uses three configurations and attach project 
dependencies to them:

api  - makes a dependency available for main classes, tests and any
  other modules importing the project (exportable dependency),

implementation - makes a dependency available for main classes, tests
  but will *not* export the dependency for other modules (so their
  compilation classpath won't contain it).

testImplementation - makes a dependency only available for test  classes.


Adding a library dependency
---------------------------

Let's say we wish to add a dependency on library "foo.bar:baz" in
version 1.2 to :lucene:core. Let's assume this library is only
used internally by the project. The :lucene:core project is configured
by lucene/core/build.gradle and we would add (or modify) the dependency
block as follows:

dependencies {
  implementation "foo.bar:baz"
}

The "implementation" here is a named configuration; we don't need to declare
it because it is declared for us by the java-library plugin.

In "normal" gradle the version of the dependency would be present
directly inside the declaration but we use a plugin
(palantir-consistent-versions) to manage all dependency versions
from the top-level (so that conflicts can be resolved globally).

If this is the first time "foo.bar:baz" is added to the project, we'd have
to add its version to "versions.props" file at the top level of the
checkout:

foo.bar:baz=1.2

and then regenerate the "versions.lock" file using the following
command:

gradlew --write-locks

IMPORTANT: The versions.lock file will contain the actual version
of the dependency picked based on other project dependencies and
their transitive dependencies. This selected version may be
different from what each of these actually requires (the highest
version number will be typically selected). To see which dependencies
require which version of the library use:

gradlew why --hash=...

where the hash code comes from versions.lock file. For example, at
the time of writing, jackson-databind has the following entry:

com.fasterxml.jackson.core:jackson-databind:2.10.0 (3 constraints: 931a7796)

and "gradlew why --hash=931a7796" prints:

com.fasterxml.jackson.core:jackson-databind:2.10.0
        projects -> 2.10.0
        net.thisptr:jackson-jq -> 2.7.0
        org.carrot2:carrot2-mini -> 2.9.9.3

Once the dependency is added it always makes sense to see the
tree of all module dependencies and maybe exclude transitive
dependencies of foo.bar:baz that we won't need.


Inspecting current dependencies
-------------------------------

The tree of dependencies of a project (in all configurations) can
be dumped by the following command (example):

gradlew -p lucene\analysis\icu dependencies

But this can be a bit overwhelming; we will most likely be interested
in just the "publicly visible" and "classpath-visible" configurations.

The publicly visible project dependencies (classes shared by other
modules importing our module) can be displayed with:

gradlew -p lucene\analysis\icu dependencies --configuration api

And the "private" set of dependencies (real classpath) can be dumped
with:

gradlew -p lucene\analysis\icu dependencies --configuration runtimeClasspath


Excluding a transitive dependency
---------------------------------

Let's say "foo.bar:baz" has a transitive dependency on project
"foo.bar:irrelevant" and we know the transitive dependency is not
crucial for the functioning of "foo.bar:baz". We can exclude it
by adding an exclusion block to the original declaration:

dependencies {
  implementation("foo.bar:baz", {
    exclude group: "foo.bar", module: "irrelevant"
  })
}

Note the brackets - they are important and prevent accidental
mistakes of applying the exclusion to the wrong scope.


Updating dependency checksum and licenses
-----------------------------------------

The last step is to make sure the licenses, notice files and checksums
are in place for any new dependencies. This command will print what's
missing and where:

gradlew licenses

To update JAR checksums for licenses use:

gradlew updateLicenses
