Why can a plugin be declared in the top-level plugin DSL block, but its classpath dependencies cannot be declared in the top-level dependencies block?

I am applying the Flyway plugin. Per the directions there, I apply the plugin in the plugins DSL:

plugins {
  id("org.flywaydb.flyway") version "10.21.0"
}

The plugin needs additional dependencies to be on its class path, though, to be able to connect to a particular database flavor, e.g. PostgreSQL. Following the Flyway docs (permalink), I add those via the buildscript block:

buildscript {
    dependencies {
        classpath("org.postgresql:postgresql:42.7.4")
        classpath("org.flywaydb:flyway-database-postgresql:10.21.0")
    }
}

My question is, given the plugin is applied in the top-level plugin DSL block, why isn’t there a corresponding dependency configuration like “plugin” or “build” that can be used in the top-level dependencies block? E.g. something like

dependencies {
  // Hypothetical configuration name:
  plugin("org.postgresql:postgresql:42.7.4")
  plugin("org.flywaydb:flyway-database-postgresql:10.21.0")
}

(Tangentially, I noticed those same Flyway docs mention

Extending the default classpath

You can optionally extend this default classpath with your own custom configurations in build.gradle as follows:

// Start by defining a custom configuration like 'provided', 'migration' or similar
configurations {
    flywayMigration
}

// Declare your dependencies as usual for each configuration
dependencies {
    implementation "org.flywaydb:flyway-core:${flywayVersion}"
    flywayMigration "com.mygroupid:my-lib:1.2.3"
}

flyway {
    // ...
    // Include your custom configuration here in addition to any default ones you want included
    configurations = [ 'compileClasspath', 'flywayMigration' ]
}

I tried to adapt this to Kotlin like so:

val flywayMigration by configurations.creating

dependencies {
    flywayMigration("org.postgresql:postgresql:42.7.4")
    flywayMigration("org.flywaydb:flyway-database-postgresql:10.21.0")
}

flyway {
    // ...
    configurations = arrayOf("flywayMigration")
}

but I had no luck, the plugin doesn’t seem to pick up the dependency (“No database found to handle jdbc:postgresql:mydatabasename”).)

My question is, given the plugin is applied in the top-level plugin DSL block, why isn’t there a corresponding dependency configuration like “plugin” or “build” that can be used in the top-level dependencies block

I don’t really get your question, buildscript { dependencies { classpath(...) } } is the configuration you are asking about.

buildscript { dependencies { ... } } is for dependencies of the build script like plugins, or other libraries you need in the build script. (While of course in the meantime you should not use it to add plugins but for that use the plugins { ... } block, but theoretically you could, you just shouldn’t)

dependencies { ... } is for dependencies of your actual project.

Thanks for taking a look at my question.

I don’t really get your question, buildscript { dependencies { classpath(...) } } is the configuration you are asking about.

It’s just confusing to me that

  • All other dependencies are declared in the top-level dependencies { ... } block;
  • buildscript { dependencies { ... } } must be used for plugin dependencies, but while it can be used to add plugins, that is discouraged, and instead the top-level plugins { ... } block is encouraged.

It seems inconsistent. Perhaps there is some fundamental design consideration that requires this inconsistency, or it is just a historical artifact.

It is not really inconsistent, it is a clear separation between dependencies for running the build and dependencies you need for your project.

Besides that, you do not declare “all other” dependencies in the top-level dependencies block.
Test dependencies you typically declare in the jvm test suites block, especially when using additional test suites. When using Kotlin KMP plugin, you declare the dependencies for the different platforms in the respective block, …

There is also a technical reason, as the buildscript block and plugins block are very restricted in what they can do so they can be extracted from the build script and evaluated separately, to find out which type-safe accessors to generate and to build the classpath for compiling the whole build script, …

But besides the technical reason, it also simply is a clear separation between totally different things, which is the dependencies for the build script and the dependencies for your project.