Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions buildSrc/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,20 @@ dependencyResolutionManagement {

/*
The code, we have put on a classpath, exposes a class `SpineVersionCatalog`,
which can execute code upon `VersionCatalogBuilder`.
which can execute actions upon `VersionCatalogBuilder`.

It is so because we want to preserve a possibility of overwrite.
Currently, Gradle does not provide a clear way to perform overwrite for
already created catalogs. When a library is added to a catalog, it can
not be overwritten. The subsequent attempts to add the same library lead
to a silent nothing.
already created catalogs. Thus, it is a responsibility of end-users
to create catalogs. `SpineVersionCatalog` just operates upon the given builder.

Thus, to overwrite a library or version you should declare it first.
All subsequent declaration of that library or version will just be ignored.
And this is the reason, why we don't have a plugin at all. We just
don't need it.
In order to override a version, shipped by the catalog, one should
declare it BEFORE applying the catalog to the builder. Versions,
declared first, always win.

In contrast, to override a library, plugin or bundle, one should
declare it AFTER applying the catalog to the builder. Those items,
declared last, always win.

See the issue: https://github.com/gradle/gradle/issues/20836
*/
Expand Down
213 changes: 127 additions & 86 deletions version-catalog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,83 +46,141 @@ In order to add a new dependency to this catalog, perform the following steps:
2. Open `io.spine.internal.catalog.entry` package.
3. Create a new file there, which contains an object declaration, named after
a dependency, that is being added.
4. Make an object inherit from one of the following entries, depending on what
a dependency represents:
1. `VersionEntry` for a bare version.
2. `LibraryEntry` for a single library.
3. `PluginEntry` for a single Gradle plugin.
4. `DependnecyEntry` for complex dependencies, which may contain several modules,
plugins or bundles.
5. Publish a new version of the catalog.

Take a look on an example, which showcases usage of all entries in a single place.
Pay attention to how entries are nested one into another. And how it reflects in
their resulting accessors.
4. Make an object inherit from `CatalogEntry`.
5. Perform all necessary declarations within this object.
6. Publish a new version of the catalog.

Take a look on an example, which showcases usage of `CatalogEntry` API. Pay attention
to how entries are nested one into another. And how it reflects in their resulting accessors.

Source code of `Dummy` dependency:

```kotlin
internal object Dummy : DependencyEntry() {

private const val group = "org.dummy.company"
override val module = "$group:dummy-lib" // libs.dummy
override val version = "1.0.0" // libs.versions.dummy

val core by lib("$group:dummy-core") // libs.dummy.core
val runner by lib("$group:dummy-runner") // libs.dummy.runner
val api by lib("$group:dummy-api") // libs.dummy.api

// In bundles, you can reference already declared libs,
// or create them in-place.

override val bundle = setOf( // libs.bundles.dummy
core, runner, api,
lib("params", "$group:dummy-params"), // libs.dummy.params
lib("types", "$group:dummy-types"), // libs.dummy.types
)

// "GradlePlugin" - is a special entry name for `PluginEntry`.
// For plugin entries with this name, the facade will not put "gradlePlugin"
// suffix for a plugin's ID. Note, that we have this suffix for the version
// and module, and does not have for ID.

object GradlePlugin : PluginEntry() {
override val version = "0.0.8" // libs.versions.dummy.gradlePlugin
override val module = "$group:my-dummy-plugin" // libs.dummy.gradlePlugin
override val id = "my-dummy-plugin" // libs.plugins.dummy
}
internal object Dummy : CatalogEntry() {

private const val group = "org.dummy.company"
override val module = "$group:dummy-lib" // libs.dummy
override val version = "1.0.0" // libs.versions.dummy

val core by lib("$group:dummy-core") // libs.dummy.core
val runner by lib("$group:dummy-runner") // libs.dummy.runner
val api by lib("$group:dummy-api") // libs.dummy.api

// In bundles, you can reference entries (which declare module), extra
// libraries or declare them in-place.

override val bundle = setOf( // libs.bundles.dummy
this,
core, runner, api,
lib("params", "$group:dummy-params"), // libs.dummy.params
lib("types", "$group:dummy-types"), // libs.dummy.types
)

// "GradlePlugin" - is a special entry name. "gradlePlugin" suffix will not
// be put for a final plugin alias. Note, that in an example below, we have
// this suffix for the version and module, and does not have for ID.

object GradlePlugin : CatalogEntry() {
override val version = "0.0.8" // libs.versions.dummy.gradlePlugin
override val module = "$group:my-dummy-plugin" // libs.dummy.gradlePlugin
override val id = "my-dummy-plugin" // libs.plugins.dummy
}

object Runtime : CatalogEntry() {

// When an entry does not override the version, it will try to fetch it
// from the closest parental entry, which has one. For example, in this case,
// all libraries within "Runtime" entry will have version = "1.0.0".

val win by lib("$group:runtime-win") // libs.dummy.runtime.win
val mac by lib("$group:runtime-mac") // libs.dummy.runtime.mac
val linux by lib("$group:runtime-linux") // libs.dummy.runtime.linux

object Bom : CatalogEntry() {
override val version = "2.0.0" // libs.versions.dummy.runtime.bom
override val module = "$group:dummy-bom" // libs.dummy.runtime.bom
}
}

// It's also possible to declare an extra bundle by a property delegate.
// Just like with extra modules.

val runtime by bundle( // libs.bundles.dummy.runtime
Runtime.Bom,
Runtime.win,
Runtime.mac,
Runtime.linux,
)
}
```


## Overriding of items shipped by `SpineVersionCatalog`

Sometimes, it happens that a projects needs to override items, shipped by the catalog.
In most cases, it is needed to override one or more versions.

object Runtime : DependencyEntry() {
Currently, Gradle does not [provide](https://github.com/gradle/gradle/issues/20836)
a clear way to perform overwrite for already created catalogs. Thus, we use approach
with creating a catalog directly in settings files. It preserves a possibility
of a local override.

// When an entry does not override the version, it is taken from
// the outer entry. For example, in this case, all libs within "Runtime"
// entry will have "1.0.0".
Below are examples on how to override the items of `Dummy` dependency. Instead of
applying `SpineVersionCatalog`, `DummyVersionCatalog` is used, as we are going to
override `Dummy`'s items.

val win by lib("$group:runtime-win") // libs.dummy.runtime.win
val mac by lib("$group:runtime-mac") // libs.dummy.runtime.mac
val linux by lib("$group:runtime-linux") // libs.dummy.runtime.linux
### Overriding of versions

object BOM : LibraryEntry() {
override val version = "2.0.0" // libs.versions.dummy.runtime.bom
override val module = "$group:dummy-bom" // libs.dummy.runtime.bom
In order to override a version you should declare it *before* applying the catalog.
A version, declared first always wins. All subsequent declarations of the same version
will be ignored by the builder.

In total, `Dummy` declares three versions.

Let's override them all:

```kotlin
dependencyResolutionManagement {
versionCatalogs {
create("libs") {

version("dummy", "2.0.0") // Dummy.version
version("dummy-gradlePlugin", "0.0.9") // Dummy.GradlePlugin.version
version("dummy-runtime-bom", "3.0.0") // Dummy.Runtime.Bom.version

DummyVersionCatalog.useIn(this)
}
}
}
```

// A library that is declared as `object SomeLib : LibraryEntry()` can be
// referenced as well as the one declared by `lib()` delegate.
### Overriding of libraries, plugins and bundles

val runtime by bundle( // libs.bundles.dummy.runtime
Runtime.BOM,
Runtime.win,
Runtime.mac,
Runtime.linux,
)
In order to override a library, plugin or bundle, one should declare it *after*
applying the catalog. This is the opposite of what is done with versions.

// It is also possible to declare just a bare version.
When overriding libraries and plugins, a version should be specified in-place.

object Tools : VersionEntry() {
override val version = "3.0.0" // libs.versions.dummy.tools
}
For example:

```kotlin
dependencyResolutionManagement {
versionCatalogs {
create("libs") {

DummyVersionCatalog.useIn(this)

library("dummy", "org.dummy.company:dummy-lib-patched:3.41-patched") // Dummy.module + version
library("dummy-gradlePlugin", "org.dummy.company:another-plugin:3.41-patched") // Dummy.GradlePlugin.module + version
library("dummy-runtime-mac", "org.dummy.company:runtime-linux:3.41-patched") // Dummy.Runtime.mac + version

plugin("dummy", "my-dummy-plugin-patched").version("1.0.0-patched") // Dummy.GradlePlugin.id + version

// In bundles, the passed list contains aliases of libraries.
bundle("dummy", listOf("dummy-runtime-bom", "dummy-runtime-win")) // Dummy.bundle
bundle("dummy-runtime", listOf("dummy", "dummy-core")) // Dummy.runtime
}
}
}
```

Expand All @@ -133,20 +191,17 @@ Within this PR, `spine-version-catalog` is a resident of `time` repository, but
it is a standalone project. Meaning, it has its own Gradle's `settings` file,
and doesn't relate anyhow to `time`.

`spine-version-catalog` consists of several modules:

1. `api` – represents a facade upon Gradle's provided `VersionCatalogBuilder`.
This module hides an imperative nature of the builder, and, instead, provides
a declarative API to declare dependencies using Kotlin objects.
`spine-version-catalog` consists of two modules:

2. `catalog` – contains all dependencies, declared using the declarative `api`.
The module publishes `SpineVersionCatalog`.
1. `catalog` – provides a facade upon Gradle's provided `VersionCatalogBuilder`
and assembles `SpineVersionCatalog`, using that facade.

3. `func-test` – performs testing of `api` with a real `VersionCatalogBuilder`.
2. `func-test` – performs testing of the facade with a real instance of `VersionCatalogBuilder`.
To do that, the module does the following:

1. Assembles a `dummy-catalog` with a single `Dummy` dependency and publishes
it to Maven local.
it to Maven local. In order to declare `Dummy`, the module depends on `:catalog`,
which exposes the declarative facade.
2. Makes `dummy-project` use `dummy-catalog` from Maven local.
3. Builds `dummy-project`. It has assertions in its build file. Those assertions verify
the generated type-safe accessors to `Dummy` dependency. When any of assertions
Expand All @@ -155,20 +210,6 @@ To do that, the module does the following:

## Details about Functional Testing

`func-test` module sets the next dependencies for `test` task:

```kotlin
test {
dependsOn(
":api:publishToMavenLocal",
":func-test:dummy-catalog:publishToMavenLocal"
)
}
```

It is so, because `dummy-project` (which the test builds), fetches `dummy-catalog`
from Maven local. Which, in turn, depends on `api` module. Thus, we need them both in Maven local.

We have to do a true functional testing here, because Gradle does not provide
a test fixture for `Settings`, as it does for `Project`. For this reason, we test
it on a real Gradle project, with assertions right in a build file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ import org.reflections.Reflections
import org.reflections.util.ConfigurationBuilder

/**
* This catalog contains dependencies, which are used in Spine-related projects.
* Contains dependencies, which are used in Spine-related projects.
*
* In order to use this catalog, one should perform the following:
*
* 1. Obtain this class on a classpath of settings file.
* 1. Obtain this class on a classpath of `settings.gradle.kts` file.
* 2. Create a version catalog. `libs` is a conventional name to go with.
* 3. Call [useIn] on a newly created catalog.
*
Expand Down Expand Up @@ -66,8 +66,9 @@ import org.reflections.util.ConfigurationBuilder
* ```
*
* In order to add a new dependency to this catalog, create an object declaration
* in [io.spine.internal.catalog.entry] package. Take a look on a special `Dummy`
* dependency in README file to quickly grasp API of a dependency declaration.
* that inherits from [CatalogEntry] in [io.spine.internal.catalog.entry] package.
* Take a look on a special `Dummy` dependency in README file to quickly grasp API
* of a dependency declaration.
*/
@Suppress("unused")
class SpineVersionCatalog {
Expand Down Expand Up @@ -95,8 +96,7 @@ class SpineVersionCatalog {
* the following criteria:
*
* 1. Be an object declaration. Only objects can serve as concrete entries.
* 2. Be a top-level declared. Only root entries should be asked for records.
* Then, they will ask their nested entries accordingly.
* 2. Be top-level declared. Only root entries can be asked for records.
*/
private fun findEntries(): Set<CatalogEntry> {
val builder = ConfigurationBuilder().forPackage(pkg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@

package io.spine.internal.catalog.entry

import io.spine.internal.catalog.model.LibraryEntry
import io.spine.internal.catalog.model.CatalogEntry

/**
* [AnimalSniffer](https://www.mojohaus.org/animal-sniffer/animal-sniffer-maven-plugin/)
*/
@Suppress("unused")
internal object AnimalSniffer : LibraryEntry() {
internal object AnimalSniffer : CatalogEntry() {
override val version = "1.21"
override val module = "org.codehaus.mojo:animal-sniffer-annotations"
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,30 @@
package io.spine.internal.catalog.entry

import io.spine.internal.catalog.model.CatalogEntry
import io.spine.internal.catalog.model.LibraryEntry

@Suppress("unused")
internal object ApacheCommons : CatalogEntry() {

/**
* [CommonsCli](https://commons.apache.org/proper/commons-cli/)
*/
object Cli : LibraryEntry() {
object Cli : CatalogEntry() {
override val version = "1.5.0"
override val module = "commons-cli:commons-cli"
}

/**
* [CommonsCodec](https://commons.apache.org/proper/commons-codec/changes-report.html)
*/
object Codec : LibraryEntry() {
object Codec : CatalogEntry() {
override val version = "1.15"
override val module = "commons-codec:commons-codec"
}

/**
* [CommonsLogging](https://commons.apache.org/proper/commons-logging/)
*/
object Logging : LibraryEntry() {
object Logging : CatalogEntry() {
override val version = "1.2"
override val module = "commons-logging:commons-logging"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@

package io.spine.internal.catalog.entry

import io.spine.internal.catalog.model.DependencyEntry
import io.spine.internal.catalog.model.CatalogEntry

/**
* [ApacheHttp](https://hc.apache.org/downloads.cgi)
*/
@Suppress("unused")
internal object ApacheHttp : DependencyEntry() {
internal object ApacheHttp : CatalogEntry() {
override val version = "4.4.14"
val core by lib("org.apache.httpcomponents:httpcore")
}
Loading