Skip to content

Support using projects as step dependency (ktlint rule sets only at this time)#2999

Open
bric3 wants to merge 6 commits into
diffplug:mainfrom
bric3:support-using-projects-as-the-dependency
Open

Support using projects as step dependency (ktlint rule sets only at this time)#2999
bric3 wants to merge 6 commits into
diffplug:mainfrom
bric3:support-using-projects-as-the-dependency

Conversation

@bric3

@bric3 bric3 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Ktlint custom rule sets can now be supplied as local Gradle projects:

spotless {
    kotlin {
        ktlint("1.0.1").customRuleSets(
            "com.example:published-rules:1.2",
            project(":ktlint-rules"),
        )
    }
}

This removes the need to rely on configuration-wide dependency substitution when using an unpublished rule set from the same Gradle build. Seen in #1901 (comment)

Motivation

The detached configurations introduced in #2929 are necessary for Gradle 9 buildscript predeclaration. However, detached configurations are not part of the project's configuration container, so rules registered through configurations.configureEach, including dependency substitution, do not apply to them.

Comments in #1901 suggests some builds relied on that undocumented behavior to substitute a published ktlint rule-set coordinate with a local project, as discussed in #1901. Indeed the DSL already supported the idea with an API that allowed to pass maven coordinates. Having the DSL to support local projects seems legitimate.

Implementation

This was trickier than anticipated. As multiple aspects surface in when properly requesting a project.

Here's what changed:

  • The API now accept Project instances alongside maven coordinates in customRuleSets.
  • This will resolve ktlint, published rule sets, and project rule sets as one dependency graph so Gradle can mediate shared transitive dependencies (it's important as they land on the same classpath).
  • Given ktlint version is requested at the Spotless DSL level, I decided to have enforce this version, i.e. if the additional rule set erroneously ships a different ktlint version it will be reported.
  • In order to cooperate with the build cache the generated project artifacts are rgistered as task inputs, allowing Gradle to infer producer tasks and track changes without an explicit dependsOn.
  • Also, some code was needed in order to cooperate with the configuration-cache and spotlessPredeclare behavior.

Please DO NOT FORCE PUSH. Don't worry about messy history, it's easier to do code review if we can tell what happened after the review, and force pushing breaks that.

Please make sure that your PR allows edits from maintainers. Sometimes it's faster for us to just fix something than it is to describe how to fix it.

Allow edits from maintainers

After creating the PR, please add a commit that adds a bullet-point under the [Unreleased] section of CHANGES.md, plugin-gradle/CHANGES.md, and plugin-maven/CHANGES.md which includes:

  • a summary of the change
  • either
    • a link to the issue you are resolving (for small changes)
    • a link to the PR you just created (for big changes likely to have discussion)

If your change only affects a build plugin, and not the lib, then you only need to update the plugin-foo/CHANGES.md for that plugin.

If your change affects lib in an end-user-visible way (fixing a bug, updating a version) then you need to update CHANGES.md for both the lib and all build plugins. Users of a build plugin shouldn't have to refer to lib to see changes that affect them.

This makes it easier for the maintainers to quickly release your changes :)

bric3 added 5 commits July 22, 2026 14:37
Use named project configurations so existing configurations.all rules
can continue substituting a Maven rule set with a local project. Keep
root buildscript resolution detached for Gradle 9 compatibility.

This is a transitional compatibility fix. A follow-up adds an explicit
project dependency API because named configurations are not suitable
for every resolution scope.
Ktlint custom rule sets could previously rely on dependency
substitution to replace a module with a local project. Detached
configurations introduced for Gradle 9 buildscript predeclaration do
not inherit those container-wide rules.

Accept project dependencies directly and register their resolved JARs
as Spotless task inputs. Gradle can infer producer tasks while
preserving incremental and configuration-cache behavior, including
transitive and predeclared project rules.
Resolve ktlint, Maven rule sets, and local project rule sets through
one detached configuration. Gradle can mediate shared transitives and
infer producer tasks.

Keep the ktlint version selected by the DSL strict. Model generated
project JARs as additional `@Classpath` inputs and defer their
signatures until execution. This preserves configuration-cache reuse and
predeclare semantics while rejecting projects that request another
version of the selected ktlint module.
Change and reload a real local rule provider after restoring the
predeclared classpath from configuration cache. This proves Gradle
rebuilds the producer JAR and reruns Spotless with the new
implementation.
@bric3

bric3 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

cc @ntkoopman

@nedtwigg

Copy link
Copy Markdown
Member

Wow! This was very kind of you @bric3, a lot of complexity for an unusual case. This LGTM although we need to fix the windows test failures somehow.

Oracle explicitly documents that open URLClassLoader JARs cannot be
replaced on Windows until the loader is closed. Technotes on
URLClassLoader :

> The application can then replace the JAR file, and create a new
> URLClassLoader instance to load from the same location, but this time
> using the new implementation of the classes/resources. However, since
> it can't be predicted exactly when finalization and garbage collection
> will occur, problems are caused for applications which need to be able
> to do this in a predictable and timely fashion.
> It is a problem on Windows, because open files cannot be deleted or
> replaced.

https://docs.oracle.com/javase/8/docs/technotes/guides/net/ClassLoader.html
@bric3

bric3 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

a lot of complexity for an unusual case

Yes, I started minimalist, then other things to consider started to fall apart when tested 😢

@bric3

bric3 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

oh no my attempted fix didn't work 😢

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.

2 participants