GithubHelp home page GithubHelp logo

square / gradle-dependencies-sorter Goto Github PK

View Code? Open in Web Editor NEW
248.0 8.0 10.0 300 KB

A CLI app and Gradle plugin to sort the dependencies in your Gradle build scripts

License: Apache License 2.0

Kotlin 57.91% Groovy 42.09%
gradle

gradle-dependencies-sorter's Introduction

gradle-dependencies-sorter

This JVM CLI app and companion Gradle plugin can sort the dependencies of a build.gradle[.kts] script.

Usage

CLI

./path/to/sort <path(s) that contain build.gradle[.kts] scripts>

# for example, use this to sort the full repo
./path/to/sort .

# for example, use this to sort a sub-tree
./path/to/sort features

# for example, use this to sort individual files
./path/to/sort my/app/build.gradle[.kts] my/app2/build.gradle[.kts]

# for example, use this to sort a subtree and a file
./path/to/sort features my/app2/build.gradle[.kts]

# Check sort status
./path/to/sort -m check <paths as above>
./path/to/sort --mode check <paths as above>

Gradle plugin

Apply it

Maven Central

// build.gradle[.kts]
plugins {
  id("com.squareup.sort-dependencies") version "<<version>>"
}

Sort it

./gradlew :my:app:sortDependencies

# Identical to the above
./gradlew :my:app:sortDependencies

Check it

./gradlew :my:app:checkSortDependencies

This task is automatically added as a dependency to the check task.

Test it

./gradlew check

CLI

A fat jar of the CLI is available on Maven Central. Replace $version with the latest version.

https://repo1.maven.org/maven2/com/squareup/sort-gradle-dependencies-app/$version/sort-gradle-dependencies-app-$version-all.jar

Build it

Publish everything to maven local

./gradlew publishToMavenLocal

This will push all of this project's artifacts to ~/.m2/repository/.

Build zip distribution

./gradlew :app:shadowDistZip

This creates a zip file of the distribution at app/build/distributions/. This archive may be installed anywhere you like.

Install CLI app

./gradlew :app:installShadowDist

This will install the app to app/build/install/app-shadow/.

Pre-OSS Contributors

License

Copyright 2022 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

gradle-dependencies-sorter's People

Contributors

autonomousapps avatar catherine-chi avatar ivanalvarado avatar jakewharton avatar jamesonwilliams avatar joelwilcox avatar kozaxinan avatar simonmarquis avatar zacsweers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gradle-dependencies-sorter's Issues

Support `kotlin()` dependency syntax

The following shorthand syntax can be used to declare dependencies on libraries linked to the Kotlin version (as documented here)

build.gradle.kts:

implementation(kotlin("test"))

Right now, this leads to a parsing error in gradle-dependencies-sorter.

As a workaround, these dependencies can be specified directly like normal, using the Kotlin version directly:

libs.versions.toml:

kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" }

build.gradle.kts:

implementation(libs.kotlin.test)

Plugin skips validation due to parse error on unrelated block

This plugin is sweet, thanks for writing it and for open-sourcing it. I've been reading the code and learning about how it's put together and hopefully will be able to contribute.

My team at Reddit has just onboarded this plugin into our production codebase with a CI check. We'll now block PRs that mess up the dependencies ordering.

Using plugin version 0.1.

Some Context

Reddit has its own Gradle plugin, called the Reddit Gradle Plugin. Our plugin allows us to use a special project extension block to reduce boilerplate in our build scripts. There are a number of configurations that it supports, but there is one in particular that is causing the Gradle Dependencies Sorter to choke.

Let's suppose this build.gradle lives in :mymodule:

reddit {
    // unrelated stuff ...
    dagger {
        anvil {
            anvilGeneratorProjects = [project(":digenerator")] // <-- This line fails
        }
    }
    // etc. ...
}

My Issue

When I run ./gradlew :mymodule:sortDependencies --mode check, the Gradle Dependencies Sorter plugin fails with a parse error:

Metrics:
  Not sorted:     0
  Already sorted: 1
  Parse errors:   1

And puts this out into the logs:

TRACE: Already ordered: [REDACTED]/mymodule/build.gradle 
TRACE: Parsing error: [REDACTED]/mymodule/build.gradle 
1: extraneous input 'project' expecting {<EOF>, DEPENDENCIES, 'file(', 'files(', 'buildscript', '{', '}', '(', ')', ''', '"', '=', ';', '\', UNICODE_LATIN, ID, DIGIT, WS}
INFO: Success! No mis-ordered build scripts.
Metrics:
  Not sorted:     0
  Already sorted: 1
  Parse errors:   1

Workaround

For a one-off use of --mode sort, I was able to simply comment out the offending line, complete the sort, and then uncomment the offending line again.

Requested Behavior Change

However, as an ongoing --mode check validation that we run in CI, we're missing validation on a good number of modules now. Ideally, the Gradle Dependencies Sorter could just ignore the entire reddit { block altogether, so that we can still get value out of the dependencies { block sort when evaluating such build scripts.

[Question] Is icu4j dependency required on the runtime classpath?

The packaged app is currently ~19MiB, and most of this size comes from IBM ICU resources.
I was wondering if these resources are really needed at runtime or if they should only be there at compile time.

The dependency comes from: :sort → :grammar → org.antlr:antlr4:4.10.1 → com.ibm.icu:icu4j:69.1

And here is the complete dependency graph for the runtimeClasspath configuration:

------------------------------------------------------------
Project ':app'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of compilation 'main' (target  (jvm)).
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.7.20
|    |    +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.7.20
|    |    \--- org.jetbrains:annotations:13.0
|    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20
|         \--- org.jetbrains.kotlin:kotlin-stdlib:1.7.20 (*)
+--- org.jetbrains.kotlin:kotlin-bom:1.7.20
|    +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20 (c)
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.7.20 (c)
|    +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20 (c)
|    \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.7.20 (c)
+--- project :sort
|    +--- project :grammar
|    |    +--- org.antlr:antlr4:4.10.1
|    |    |    +--- org.antlr:antlr4-runtime:4.10.1
|    |    |    +--- org.antlr:antlr-runtime:3.5.3
|    |    |    +--- org.antlr:ST4:4.3.3
|    |    |    |    \--- org.antlr:antlr-runtime:3.5.2 -> 3.5.3
|    |    |    +--- org.abego.treelayout:org.abego.treelayout.core:1.0.3
|    |    |    +--- org.glassfish:javax.json:1.0.4
|    |    |    \--- com.ibm.icu:icu4j:69.1
|    |    +--- org.antlr:antlr4-runtime:4.10.1
|    |    +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20 (*)
|    |    \--- org.jetbrains.kotlin:kotlin-bom:1.7.20 (*)
|    +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20 (*)
|    \--- org.jetbrains.kotlin:kotlin-bom:1.7.20 (*)
+--- info.picocli:picocli:4.6.2
\--- org.slf4j:slf4j-simple:2.0.0-alpha7
     \--- org.slf4j:slf4j-api:2.0.0-alpha7

Using current directory abbreviation `.` will lead to empty `buildDotGradles`

Unfortunately, the example provided in README.md no longer works.

# for example, use this to sort the full repo
./path/to/sort .

The issue comes from this code that will return false (unless skipHiddenAndBuildDirs is disabled).

private val traversalFilter: (File) -> Boolean = if (skipHiddenAndBuildDirs) {
{ dir -> !dir.name.startsWith(".") && dir.name != "build" }
} else {
{ true }
}

The reason is because this code will resolve non-normalized Paths.

There are at least two solutions:

  • Adding a special case for the directory . in the !dir.name.startsWith(".")
    private val traversalFilter: (File) -> Boolean = if (skipHiddenAndBuildDirs) {
      { dir -> (!dir.name.startsWith(".") && dir.name != ".") && dir.name != "build" }
    } else {
      { true }
    }
  • Or simply normalize() the resolved paths (the solution I prefer)
    .map { root.resolve(it).normalize() }

I'll prepare a PR for that.

Feature request: configurable option for whether or not parse errors fail the task

Related to #36, but distinct.

Using Plugin version 0.1

Current Behavior

Today, the Gradle Dependencies Sorter plugin will continue on without error if it encountered a build script that it doesn't know how to parse. Example output:

Metrics:
  Not sorted:     0
  Already sorted: 1
  Parse errors:   1

Check duration: 63 ms.

[main] INFO Sorter - Operation took 191 ms
[main] INFO Sorter - See log file at /var/folders/wb/phv6x1k53hj0j9rkvg5pf0mc0000gp/T/dependencies-sorter/2023-03-07T20:43:24.076223Z.log 

BUILD SUCCESSFUL in 857ms
1 actionable task: 1 executed

Here, the parse failed, the tool wasn't able to verify the sorting order, but it returned successfully never-the-less.

Discussion

Sometimes this is good. Let's say you have a lot of parse errors in a huge monorepo (I am in this situation), and I don't want to manually go and disable the plugin in every location. I do, however, still want to benefit from the check wherever I can. But sometimes, it's bad - I'd rather "fail fast," and be 100% confident that every build script is well-sorted.

Proposed Feature

It could be convenient to have a configurable option, strictValidation or something. It's up for discussion what a good default convention for this option might be (true, or false). However, the option would allow users of the plugin to decide on behavior that best meets the goals of their repository.

Project doesn't build out of the box

On 3cf7afd,

I get the following on a clean build:

$ ./gradlew clean build 
> Task :grammar:sourcesJar FAILED

FAILURE: Build failed with an exception.

* What went wrong:
A problem was found with the configuration of task ':grammar:sourcesJar' (type 'Jar').
  - Gradle detected a problem with the following location: '/Users/jameson.williams/gradle-dependencies-sorter/grammar/build/generated-src/antlr/main'.
    
    Reason: Task ':grammar:sourcesJar' uses this output of task ':grammar:generateGrammarSource' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':grammar:generateGrammarSource' as an input of ':grammar:sourcesJar'.
      2. Declare an explicit dependency on ':grammar:generateGrammarSource' from ':grammar:sourcesJar' using Task#dependsOn.
      3. Declare an explicit dependency on ':grammar:generateGrammarSource' from ':grammar:sourcesJar' using Task#mustRunAfter.
    
    Please refer to https://docs.gradle.org/8.0.1/userguide/validation_problems.html#implicit_dependency for more details about this problem.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.

* Get more help at https://help.gradle.org

BUILD FAILED in 3s
20 actionable tasks: 14 executed, 6 up-to-date

Publishing build scan...
https://gradle.com/s/bmeesvwv6djyk

Seems related to gradle/gradle#19555

versions.toml support

Now that Version Catalogs are becoming more popular it would be great if this project could also sort the TOML files

Sort Dependencies Fails on `if` blocks

Consider a Gradle script like this one, where there are some out-of-order single-line statements followed by a more complex control-flow block:

dependencies {
  testImplementation projects.modules.test.utils
  kapt deps.dagger.compiler
  implementation projects.modules.design.goodies

  // comment about if statement
  if (project.findProperty("configurableProperty").toString().toBoolean()) {
    api projects.modules.version.real
  } else {
    releaseApi projects.modules.noOp
    debugApi projects.modules.version.real
  }
}

When I run

java -jar scripts/bin/sort-gradle-dependencies-app-0.7-all.jar --verbose --mode=sort build.gradle 

The output will be

[main] INFO Sorter - Sorting build.gradle(.kts) scripts in the following paths: build.gradle
[main] INFO Sorter - It took 2 ms to find 1 build scripts.
[main] WARN Sorter - Parsing error: /Users/jameson.williams/MyProj/build.gradle 
Unknown dependency kind. Was <> for /Users/jameson.williams/MyProj/build.gradle
[main] INFO Sorter - Metrics:
  Successful sorts: 0
  Already sorted:   0
  Parse errors:     1

Sort duration: 26 ms.
[main] INFO Sorter - Operation took 33 ms
[main] INFO Sorter - See log file at /var/folders/08/jzg4dnq92tz9x4bfn3_qw8lh0000gp/T/11468170203746801189.tmp 

We'd have to define the sort behavior for such blocks, but perhaps collecting them at the bottom would be a viable option.

Warning messages are not printed to standard output

java -jar sort.jar . --verbose

[main] INFO Sorter - Sorting build.gradle(.kts) scripts in the following paths: [...]
[main] INFO Sorter - It took 295 ms to find 904 build scripts.
[main] INFO Sorter - Metrics:
  Successful sorts: 378
  Already sorted:   516
  Parse errors:     4

Sort duration: 278 ms.
[main] INFO Sorter - Operation took 629 ms
[main] INFO Sorter - See log file at /tmp/14129892440612113186.tmp 

It seems like the delegate.warn(msg) was replaced with delegate.trace(msg) in this commit:

I don't think this is expected as it prevents showing warnings, even in --verbose mode.
Was there any particular reason to make this change, or is it simply a test/typo?

Function parsing issue

The log says that there is an extraneous input '(' expecting {'}'.

Looks like it is caused because I am calling a function inside implementation.

plugins {
    alias(libs.plugins.javiersc.hubdle)
    id("com.squareup.sort-dependencies") version "0.1"
}

hubdle {
    config {
        explicitApi()
        publishing()
    }
    kotlin {
        multiplatform {
            common {
                test {
                    dependencies {
						// sort these dependencies
                        implementation(projects.mokokiTest)
                        implementation(javierscKotlinStdlib())
                    }
                }
            }

            android()

            darwin {
                enableAll()
            }

            jvm()
            jvmAndAndroid()

            mingw {
                enableAll()
            }
        }
    }
}

I totally understand that supporting each use case can be out of the scope of this plugin and I am not sure if it is a "legal" use case to use functions, so feel free to close it if you think that this shouldn't be fixed.

Bug: `IllegalStateException` at `java.base/java.nio.file.FileTreeIterator.hasNext(FileTreeIterator.java:102)`

Version 0.2 (downloaded from maven) crashes on Windows with the following stacktrace:

java -jar sort-gradle-dependencies-app-0.2-all.jar .

[main] INFO Sorter - Sorting build.gradle(.kts) scripts in the following paths: .
java.lang.IllegalStateException
        at java.base/java.nio.file.FileTreeIterator.hasNext(FileTreeIterator.java:102)
        at java.base/java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1855)
        at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.lambda$initPartialTraversalState$0(StreamSpliterators.java:292)
        at java.base/java.util.stream.StreamSpliterators$AbstractWrappingSpliterator.fillBuffer(StreamSpliterators.java:206)
        at java.base/java.util.stream.StreamSpliterators$AbstractWrappingSpliterator.doAdvance(StreamSpliterators.java:161)
        at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.tryAdvance(StreamSpliterators.java:298)
        at java.base/java.util.Spliterators$1Adapter.hasNext(Spliterators.java:681)
        at kotlin.sequences.FlatteningSequence$iterator$1.ensureItemIterator(Sequences.kt:316)
        at kotlin.sequences.FlatteningSequence$iterator$1.hasNext(Sequences.kt:303)
        at kotlin.sequences.SequencesKt___SequencesKt.toCollection(_Sequences.kt:787)
        at kotlin.sequences.SequencesKt___SequencesKt.toSet(_Sequences.kt:828)
        at com.squareup.sort.BuildDotGradleFinder.<init>(BuildDotGradleFinder.kt:28)
        at com.squareup.sort.BuildDotGradleFinder$Factory$DefaultImpls.of(BuildDotGradleFinder.kt:34)
        at com.squareup.sort.SortCommand$1.of(SortCommand.kt:47)
        at com.squareup.sort.SortCommand.call(SortCommand.kt:87)
        at com.squareup.sort.SortCommand.call(SortCommand.kt:36)
        at picocli.CommandLine.executeUserObject(CommandLine.java:1953)
        at picocli.CommandLine.access$1300(CommandLine.java:145)
        at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2358)
        at picocli.CommandLine$RunLast.handle(CommandLine.java:2352)
        at picocli.CommandLine$RunLast.handle(CommandLine.java:2314)
        at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2179)
        at picocli.CommandLine$RunLast.execute(CommandLine.java:2316)
        at picocli.CommandLine.execute(CommandLine.java:2078)
        at com.squareup.sort.MainKt.main(main.kt:15)
java -version
openjdk version "17.0.6" 2023-01-17
OpenJDK Runtime Environment Temurin-17.0.6+10 (build 17.0.6+10)
OpenJDK 64-Bit Server VM Temurin-17.0.6+10 (build 17.0.6+10, mixed mode, sharing)

Consider providing an option to bunch `project` together and `libs` in a separate bunch, regardless of using `implementation` or `api`

So consider this case. I got this dependencies block

dependencies {
  api(libs.turbine)
  api(libs.turbine2)
  api(libs.turbine3)
  api(projects.core.commons)
  api(projects.core.commons2)

  implementation(libs.slimber)
  implementation(libs.slimber2)
  implementation(libs.slimber3)
  implementation(projects.core.other)
  implementation(projects.core.other2)
}

I feel like, it provides more value for such a configuration to be sorted like this instead

dependencies {
  api(libs.turbine)
  api(libs.turbine2)
  api(libs.turbine3)

  implementation(libs.slimber)
  implementation(libs.slimber2)
  implementation(libs.slimber3)
  
  api(projects.core.commons)
  api(projects.core.commons2)

  implementation(projects.core.other)
  implementation(projects.core.other2)
}

So that one can see the other modules it is depending on bunched together, and then on a separate block see the libraries. I feel like this is more important than bunching them by if they are api or implementation, as then you may be splitting apart which modules it is depending on which may make it much harder to easily peek and see which module dependencies this module has.

p.s Or even like this in my personal opinion, as it's nice to be able to pop into a build.gradle file and at the top level see which other modules it is depending on, which feels more important than which libraries it is depending on

dependencies {
  api(projects.core.commons)
  api(projects.core.commons2)

  implementation(projects.core.other)
  implementation(projects.core.other2)

  api(libs.turbine)
  api(libs.turbine2)
  api(libs.turbine3)

  implementation(libs.slimber)
  implementation(libs.slimber2)
  implementation(libs.slimber3)
}

I would understand if you think this goes against the nature of this plugin, if you want it to be a simple sorter without more than that, but just asking here to see if it is something that you would be open to consider or not. Even if such a configuration is optional and configurable on the gradle plugin itself.

Do not log in Gradle lifecycle scope

I wouldn't even log in the info scope. The output has debug level written all over it.

Presumably this is happening automatically from an automatic stdout forwarding by javaexec, but I assume it can be captured and piped into debug level on the plugin side.

Enforce whitespace between configuration sets

For a block like this

dependencies {
  implementation(libs.turbine)
  api(libs.analytics)
  implementation(projects.events)
}

If I run the gradle plugin I will get the result:

dependencies {
  api(libs.analytics)

  implementation(libs.turbine)
  implementation(projects.events)
}

But for a block like this:

dependencies {
  api(libs.analytics)
  implementation(libs.turbine)
  implementation(projects.events)
}

If I run the gradle plugin I will get the result:

dependencies {
  api(libs.analytics)
  implementation(libs.turbine)
  implementation(projects.events)
}

Which means it does not force the extra empty line between the two items if they already were sorted and did not have a line between them.

We for example were already maintaining by hand the alphabetical order for our modules, which means that a bunch of them are considered already sorted and this plugin does not go in to adjust them to make them look exactly like it does with all the other ones that it does in fact sort.

Feature request: silently-no-op on build files with no dependencies blocks

The following build file results in a parse error

import slack.gradle.Platforms

plugins {
  alias(libs.plugins.slack.base)
  `java-platform`
}

val catalogExtension =
  extensions.findByType<VersionCatalogsExtension>() ?: error("Could not find any version catalogs!")

for (name in catalogExtension.catalogNames) {
  val catalog = catalogExtension.named(name)
  Platforms.applyFromCatalog(project, catalog)
}

This is the log file

INFO: Sorting build.gradle(.kts) scripts in the following paths: /Users/zacsweers/dev/slack/slack-android-ng/tooling/slack-platform/build.gradle.kts
INFO: It took 31 ms to find 1 build scripts.
WARN: Parsing error: /Users/zacsweers/dev/slack/slack-android-ng/tooling/slack-platform/build.gradle.kts 
1: extraneous input 'project' expecting {<EOF>, DEPENDENCIES, 'file(', 'files(', 'buildscript', '{', '}', '(', ')', ''', '"', '=', ';', '\', UNICODE_LATIN, ID, DIGIT, WS}
INFO: Metrics:
  Successful sorts: 0
  Already sorted:   0
  Parse errors:     1

Sort duration: 45 ms.

Bug: fails to parse function invocations in dependencies

The following fails to parse

plugins {
  kotlin("jvm")
  alias(libs.plugins.mavenPublish)
}

dependencies {
  compileOnly(gradleApi())
  compileOnly(libs.guava)
  compileOnly(libs.agp)
  compileOnly("com.android.tools:common:30.4.1")
}

with the following log

INFO: Sorting build.gradle(.kts) scripts in the following paths: /Users/zacsweers/dev/slack/oss/slack-gradle-plugin/sgp-monkeypatch-agp/build.gradle.kts
INFO: It took 24 ms to find 1 build scripts.
WARN: Parsing error: /Users/zacsweers/dev/slack/oss/slack-gradle-plugin/sgp-monkeypatch-agp/build.gradle.kts 
1: extraneous input '(' expecting {'}', ID}
INFO: Metrics:
  Successful sorts: 0
  Already sorted:   0
  Parse errors:     1

Sort duration: 44 ms.

If I comment out the gradleApi() line, it works

[Question] Benefits of sorting dependencies

Hi! I'm enjoying Square's open-source efforts, and thank you so much for making an awesome open-source this time around too.

I have one question about this project. What are the benefits of sorting Gradle dependencies? I think you have a reason for planning and developing this project.

I tried googling about it, but I couldn't get the results I wanted, probably because I got the search keywords wrong.

In summary, what are the benefits of sorting Gradle dependencies?

Thank you.

Feature request: de-dupe dependencies

When sorting, it would be neat if the CLI could de-dupe dependencies like this

dependencies {
  api(projects.libraries.foundation.time)
  api(projects.libraries.messageRendering.api)
  api(projects.libraries.messageRendering.api) // <- delete this!
  api(projects.libraries.messages.api)
}

[Enhancement] Remove duplicate declarations while sorting

When running the sort command, ideally if dependencies are duplicated then they should be consolidated to only the unique list.

dependencies {
  api deps.kotlin.coroutines_core
  api project(':common:public:one')
  api project(':common:public:one')
  api project(':common:public:one')
}

should result in

  api project(':common:public:one')
  api deps.kotlin.coroutines_core

Add dependency constraints sorting

I would really appreciate if you add dependency constraints sorting. I use it in java-platform for version control like:

plugins {
    `java-platform`
}

dependencies {
    constraints {
        api("id...")
        runtime("id...")
        api("id...")
    }
}

but I noticed that gradle-dependencies-sorter isn't configured to sort them. Please add this

Support for custom functions in implementation module

We have following some methods that we use to add conditional dependency based on variants.

dependencies {
  addX()
  addY()
  implementation()
  testImplementation()
}

fun DependencyHandlerScope.addX() {}
fun DependencyHandlerScope.addY() {}

This gives parsing errors.

Issue also happens on 0.3-SNAPSHOT.

Caused by: com.squareup.parse.BuildScriptParseException: 1: no viable alternative at input 'addX()'
	at app//com.squareup.parse.BuildScriptParseException$Companion.withErrors(BuildScriptParseException.kt:9)
	at app//com.squareup.sort.Sorter.rewritten(Sorter.kt:76)
	at com.squareup.sort.SorterSpec.keep identical dependencies that have non-identical comments(SorterSpec.groovy:421)

Support for calling add function

Our project have different variants and we use add function to add variant specific dependencies.

add("<variantName>Implementation", projects.foo)
add("debugImplementation", projects.foo)

This throw parsing exception with following stacktrace.

Caused by: com.squareup.parse.BuildScriptParseException: 1: extraneous input ',' expecting {'}', ID}
	at app//com.squareup.parse.BuildScriptParseException$Companion.withErrors(BuildScriptParseException.kt:9)
	at app//com.squareup.sort.Sorter.rewritten(Sorter.kt:76)
	at com.squareup.sort.SorterSpec.keep identical dependencies that have non-identical comments(SorterSpec.groovy:422)

Our actual usecase uses arrays and forEach method for different variant groups.

arrayOf("variantA", "variantB").forEach {
  add("${it}Implementation", projects.foo)
}

Support variantOf in add calls

It's used to achieve artifact type selection together with version catalogs, e.g.:

dependencies {
  add("myconfig", variantOf(libs.foo) {
    artifactType("tar")
  })
}

currently fails with:

1: extraneous input 'libs.foo' expecting ')', 2: extraneous input ')' expecting {'}', ID}

Support for Kotlin multiplatform projects and multiple dependency blocks

With 0.2, given the following build file:

import app.cash.redwood.buildsupport.FlexboxHelpers

apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.multiplatform'
apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
apply plugin: 'app.cash.paparazzi'
apply plugin: 'com.vanniktech.maven.publish'
apply plugin: 'org.jetbrains.dokka' // Must be applied here for publish plugin.
apply plugin: 'app.cash.redwood.build.compose'

kotlin {
  android {
    publishLibraryVariants('release')
  }

  iosArm64()
  iosX64()
  iosSimulatorArm64()

  jvm()

  macosArm64()
  macosX64()

  sourceSets {
    commonMain {
      kotlin.srcDir(FlexboxHelpers.get(tasks, 'app.cash.redwood.layout.composeui').get())
      dependencies {
        api projects.redwoodLayoutWidget
        implementation projects.redwoodFlexbox
        implementation projects.redwoodWidgetCompose
        implementation libs.jetbrains.compose.foundation
      }
    }

    androidUnitTest {
      dependencies {
        implementation projects.redwoodLayoutSharedTest
      }
    }
  }
}

android {
  namespace 'app.cash.redwood.layout.composeui'
}

The plugin will make the following change:

       kotlin.srcDir(FlexboxHelpers.get(tasks, 'app.cash.redwood.layout.composeui').get())
       dependencies {
         api projects.redwoodLayoutWidget
+
+        implementation libs.jetbrains.compose.foundation
         implementation projects.redwoodFlexbox
         implementation projects.redwoodWidgetCompose
-        implementation libs.jetbrains.compose.foundation
-      }
+}
     }

     androidUnitTest {
       dependencies {
+        api projects.redwoodLayoutWidget
+
+        implementation libs.jetbrains.compose.foundation
+        implementation projects.redwoodFlexbox
         implementation projects.redwoodLayoutSharedTest
-      }
+        implementation projects.redwoodWidgetCompose
+}
     }
   }
 }

Two things are broken:

  • It seems to be treating the set of dependencies as the union of both blocks, but they are unrelated to each other.
  • The closing brace is incorrectly formatted, although this isn't a huge deal as Spotless will take care of it.

Task 'sortDependencies' not found in project

I've applied the gradle plugin to my project by adding the following to my top-level build.gradle:

plugins {
    id("com.squareup.sort-dependencies") version "0.1"
}

and the project syncs successfully. I've verified my project is finding the artifact successfully by changing the version and observing a failure during the sync.

However, when I run

$ ./gradlew :app:sortDependencies

I get the following error:

* What went wrong:
Task 'sortDependencies' not found in project ':app'.

Then, I run

$ ./gradlew :app:tasks

to verify the tasks available and sortDependencies is not listed.

Any ideas what might be going on here or how I can troubleshoot this? Thanks!

Trailing comments are incorrectly linked to the following element

On our large code base, we encountered the following situation (simplified):

dependencies {
  implementation(lib1)
  implementation(lib2) // lib2
  testImplementation(lib3)
  implementation(lib4) /* lib4 */
  api(lib5)
}

and the sorting process results in the following:

dependencies {
  /* lib4 */
  api(lib5)
 
  implementation(lib1)
  implementation(lib2)
  implementation(lib4)
 
  // lib2
  testImplementation(lib3)
}

The trailing comments are incorrectly linked to the following element.
Is this something simple to handle in the antlr grammar?

Feature request: Support using `implementation(platform(...))` for BOMs

Currently, I am using version catalogs and I have a BOM in there.
The bom is defined inside libs.versions.toml as:

firebase-bom = "com.google.firebase:firebase-bom:31.2.3"

And used like this:

implementation(platform(libs.firebase.bom))

I get the error:

1: extraneous input '"' expecting {'}', ID}, 2: extraneous input 'platform(' expecting {<EOF>, DEPENDENCIES, 'file(', 'files(', 'buildscript', '{', '}', '(', ')', ''', '"', '=', ';', '\', UNICODE_LATIN, ID, DIGIT, WS}

Random version in a transitive dependency

Applied in a subproject

// subproject/build.gradle.kts
id("com.squareup.sort-dependencies") version "0.1"

Stacktrace:

* What went wrong:
Execution failed for task ':gradle-extensions-subprojects:gradle-delegated-properties-extensions:sortDependencies'.
> Could not resolve all files for configuration ':gradle-extensions-subprojects:gradle-delegated-properties-extensions:detachedConfiguration5'.
   > Could not find com.squareup:sort-gradle-dependencies-app:3.5.0.2730.
     Searched in the following locations:
       - https://repo.maven.apache.org/maven2/com/squareup/sort-gradle-dependencies-app/3.5.0.2730/sort-gradle-dependencies-app-3.5.0.2730.pom
       - https://dl.google.com/dl/android/maven2/com/squareup/sort-gradle-dependencies-app/3.5.0.2730/sort-gradle-dependencies-app-3.5.0.2730.pom
       - https://plugins.gradle.org/m2/com/squareup/sort-gradle-dependencies-app/3.5.0.2730/sort-gradle-dependencies-app-3.5.0.2730.pom
       - https://maven.pkg.jetbrains.space/public/p/compose/dev/com/squareup/sort-gradle-dependencies-app/3.5.0.2730/sort-gradle-dependencies-app-3.5.0.2730.pom
     Required by:
         project :gradle-extensions-subprojects:gradle-delegated-properties-extensions

The workaround is loading it on root too:

// build.gradle.kts
id("com.squareup.sort-dependencies") version "0.1" apply false

[BUG] Illegal char for file path on Windows

Running this gradlew sortDependencies on a Windows machine trigger this error:

Exception in thread "main" java.nio.file.InvalidPathException: Illegal char <:> at index 13: 2022-11-25T19:50:57.043468Z.log
	at java.base/sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182)
	at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
	at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
	at java.base/sun.nio.fs.WindowsPath.parse(WindowsPath.java:92)
	at java.base/sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:229)
	at java.base/java.nio.file.Path.resolve(Path.java:515)
	at com.squareup.sort.MainKt.logger(main.kt:33)
	at com.squareup.sort.MainKt.main(main.kt:16)

The issue comes from this:

https://github.com/square/gradle-dependencies-sorter/blob/ce87fb06d312428843001939bdefda2b3d571115/app/src/main/kotlin/com/squareup/sort/main.kt#LL33C3-L33C73

Could we replace this name with Instant.now().toEpochMilli(), or use a more basic formatter, or replace illegal chars?

Do not fail on modules with no build script

Right now it fails with NoSuchFileException, but I think it would better to silently no-op. This would allow applying to all subprojects without having to worry about the intermediate projects that Gradle creates when nesting two-levels (or more) deep.

Allow a custom naming strategy

Repeating from pemistahl/version-catalog-linter-gradle-plugin#2

it would be good to have a way to set a standard naming strategy, by default maybe the names used by Android Studio when you follow the hint to add a new TOML dependency.

And rename in both the TOML and build files.

--- apacheHttpClient = { group = "org.apache.httpcomponents", name = "httpclient", version = "4.5.14" }
+++ httpcomponents-httpclient = { group = "org.apache.httpcomponents", name = "httpclient", version = "4.5.14" }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.