Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype migration of dependencies to a published Version Catalog #145

Draft
wants to merge 145 commits into
base: master
Choose a base branch
from

Conversation

yevhenii-nadtochii
Copy link
Contributor

@yevhenii-nadtochii yevhenii-nadtochii commented May 12, 2022

This PR is a prototype. It is not going to be merged.


It addresses #359.

Information on this matter:

  1. Documentation on Version Catalog.
  2. Miro board.

The PR prototypes migration of dependencies into a published version catalog. Initially, it was supposed to be a plugin for Settings, that should have declared a libs catalog on its own. But due to the problems with override, it took the form of just class which can operate upon Gradle-provided VersionCatalogBuilder.

In this PR, the catalog is represented by a separate version-catalog project that is published to Maven local in order to simplify prototyping. Also, it resides in time project, they are independent. time modules use this catalog, fetching it from Maven local in settings.gradle.kts file.

version-catalog has a facade upon VersionCatalogBuilder, which provides a declarative API for dependencies. The main goal was to hide the imperative nature of the builder. The spirit of the resulted API for dependencies declaration is very similar to the way we declare dependencies now. As a result, we get all the same Kotlin objects, only with a more strict, unified structure.

What have been done:

  1. All our dependency objects have been moved out of buildSrc/io/spine/internal/dependency to the catalog and translated with the new API. For some of them, GitHub set an incorrect match in its Files changed section, but it is still possible to compare most of them.
  2. Configured publishing of the catalog into Maven local for the local run. The same is done for GitHub workflow.
  3. buildSrc and time modules have been adopted to use Version Catalog, which the module provides.
  4. Added prons and cons section for this approach to the Miro board.

A showcase of the new API is a Dummy dependency. It covers all the available API in a single place. The comments show how a particular statements will be reflected in the generated type-safe accessors. Details on a local overriding of items, shipped by the catalog, can be found in README file.

The code of Dummy dependency, which showcases the API:

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,
    )
}

The issues below are relevant to this prototype:

1. Using of Version Catalog corrupts applying of standalone scripts.
2. Make version catalogs accessible from precompiled script plugins.
3. Typesafe accessors to version catalog do not work in the subprojects block.
4. Provide a way to use plugin definition from version catalog without version.
5. Accept plugin declarations from version catalog also as libraries.
6. False-positive "can't be called in this context by implicit receiver" with plugins in Gradle version catalogs as a TOML file.
7. Add possibility to overwrite items of already created version catalogs.
8. Provide means for testing Settings plugins.


Off-topic

Having to use output from buildSrc a lot for development, I've tried to get rid of the warnings it emits:

  1. 'compileJava' task (current target is 11) and 'compileKotlin' task (current target is 1.8) jvm target compatibility should be set to the same Java version. - nothing can be done. It's a bug in Gradle. They state, it is fixed in v7.5, which is the current Release Candidate.
  2. Inconsistency with different Kotlin versions. I've forced the version in buildSrc to the one, used by Gradle itself. I've left a note about this in buildSrc/settings.gradle.kts file.
  3. Dokka depends on libraries, which are indeed fat jars with Kotlin runtime in it. One more Kotlin version. I've excluded those libraries to get rid of annoying warnings. Interesting case, needs some investigation. I've commented this moment as well.

@yevhenii-nadtochii yevhenii-nadtochii self-assigned this May 12, 2022
@yevhenii-nadtochii yevhenii-nadtochii marked this pull request as draft May 13, 2022 14:55
@codecov
Copy link

codecov bot commented May 13, 2022

Codecov Report

Merging #145 (f5199ae) into master (edd9f99) will increase coverage by 0.11%.
The diff coverage is n/a.

@@             Coverage Diff              @@
##             master     #145      +/-   ##
============================================
+ Coverage     94.61%   94.73%   +0.11%     
- Complexity      341      342       +1     
============================================
  Files            52       52              
  Lines           892      892              
  Branches         18       18              
============================================
+ Hits            844      845       +1     
  Misses           43       43              
+ Partials          5        4       -1     

@yevhenii-nadtochii
Copy link
Contributor Author

@armiol @alexander-yevsyukov Please, take a look.

Copy link
Contributor

@alexander-yevsyukov alexander-yevsyukov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got only one comment. But I did only a cursory review. In general it's LGTM. Unfortunately, I cannot put much efforts in this PR — busy with other urgent things now. So I rely on review from @armiol for a formal approval.

version-catalog/README.md Outdated Show resolved Hide resolved
Copy link
Contributor

@armiol armiol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yevhenii-nadtochii please see my comments. We need to discuss my comments vocally.

* Dokka Releases</a>
*/
val dokkaVersion = "1.6.20"
// Let's get rid of warnings about different Kotlin version on the classpath.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading it again, I am thinking now, to which piece of code this comment relates? Perhaps, it is misplaced?

implementation(libs.protobuf.gradlePlugin)

/*
These guys below use a fat jar with Kotlin runtime inside. One more
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be more polite and format here. They aren't "these guys".


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

1. `api` – represents a facade upon Gradle's provided `VersionCatalogBuilder`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have discussed this previously. I would not have this module as a standalone entity.

package io.spine.internal.catalog

/**
* Defines what information is necessary to create one or another version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean this?

"Defines the information necessary to create a versioned catalog-compatible item."

* Defines what information is necessary to create one or another version
* catalog-compatible item.
*
* Notations are citizens of a declaration site. They form DSL and say what is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to discuss these notations vocally. So far, I have very little understanding of what they are (especially in this DSL part), and why there's still some parallel hierarchies with "Catalog-Version-Library-..." terms.

*
* See direct implementations: [PluginEntry], [DependencyEntry].
*/
open class VersionInheritingEntry : CatalogEntry(), VersionNotation {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss this vocally. From our last round, you said CatalogEntry is a counter-example for VersionInheritingEntry, as CatalogEntry takes the properties from its children, while VersionInheritingEntry descendants — at the moment — just take their versions from their parent.

Two points:

  1. We might need to inherit more than just a version. Hence the name is still questionable.
  2. CatalogEntry is not a counter-example, but a direct parent type. Which makes your previous comments confusing to me.


// "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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ID".

package io.spine.internal.catalog.entry

/**
* This dependency describes an imaginary library.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it is again. See, you say that it describes a library, while you aren't using a LibraryEntry on top, but a DependencyEntry.

I suggest to pick just one of them and kill another one.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would also be nice to see how one overrides a version of some Dummy part (such as Gradle plugin, for instance) in a Gradle project which originally imports the version catalog declaring Dummy.

@yevhenii-nadtochii yevhenii-nadtochii force-pushed the published-settings-plugin branch from 9a6f115 to 17aaf48 Compare June 13, 2022 14:14
@yevhenii-nadtochii
Copy link
Contributor Author

@armiol Please take another look.

1. Go to `catalog` module.
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please adjust the text wrapping so that vertical line of item numbers is not broken by wrapped text.

Copy link
Contributor

@armiol armiol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yevhenii-nadtochii LGTM with a number of minor documentation-related comments. Please make sure to address those.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

", or declare"


## Overriding of items shipped by `SpineVersionCatalog`

Sometimes, it happens that a projects needs to override items, shipped by the catalog.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"that a project"


When overriding libraries and plugins, a version should be specified in-place.

For example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this example, there should be more comments which literally point to the rules which you describe in the sentences above.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"If any"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants