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 topic 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 uses the following configurations and attach project dependencies
to them:

moduleApi - makes the dependency available to main classes, tests and any
  other modules importing the project (exportable dependency),

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

moduleTestImplementation - makes the dependency available for test classes only.

The "module" prefix is used to distinguish configurations which apply
to modular builds, compared to the regular classpath-configurations defined
by gradle's java module. Some Lucene modules may define regular classpath
entries to bypass the limitations of the module system (or gradle's).


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

Lucene dependencies and their versions are managed globally using version
catalogs (in versions.toml) [https://docs.gradle.org/current/userguide/platforms.html].

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, so we add (or modify) the dependency
block as follows:

dependencies {
  moduleImplementation deps.baz
}

The "moduleImplementation" here is a named configuration explained in the
section above. The "deps.baz" refers to the version catalog named "deps",
in which the dependency "baz" should be declared. If this is the first
reference to this library, then we have to add it to "versions.toml" catalog:
the version goes under the "versions" and module coordinates
under the "libraries" section:

[versions]
baz = "1.2"
...
[libraries]
baz = { module = "foo.bar:baz", version.ref = "baz" }

The version defined in the "versions" section is the preferred version of the library
we wish to use. Finally, run tidy to sort all entries in versions.toml:

gradlew tidy

Gradle will try to consolidate different versions across different
configurations to make sure they're compatible and may complain if it encounters
conflicting versions in the dependency tree. We want all dependencies to be consistent,
so we use an additional build plugin to ensure no accidental version changes
occur. Whenever we add or remove dependencies, we have to follow-up with lock file
regeneration:

gradlew writeLocks
git diff versions.*

IMPORTANT: The versions.lock file will contain a list of actual library versions
and configurations they occurred in.

Once a new dependency is added it always makes sense to regenerate the lock file
and look at which dependencies have changed (and why).


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 moduleApi

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

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


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(deps.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
