Excluding module from configuration seems to remove from inherited configurations too

It seems that when excluding a module from a configuration (like compile) it also excludes it from extending configurations (like compileOnly). Here is my contrived example.

apply plugin: 'java'

repositories {
  mavenCentral()
}

dependencies {
  compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21'
  compileOnly group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.21'
}

configurations.compile {
  exclude group: 'org.slf4j', module: 'slf4j-log4j12'
}

task resolvedArtifacts << {
  def sortedArtifacts = new TreeSet()
  configurations.compileOnly.resolvedConfiguration.resolvedArtifacts.each { sortedArtifacts.add(it.file.name) }
  println 'Compile Only:'
  sortedArtifacts.each { println it }

  sortedArtifacts.clear()
  println ''

  configurations.compile.resolvedConfiguration.resolvedArtifacts.each { sortedArtifacts.add(it.file.name) }  
  println 'Compile:'
  sortedArtifacts.each { println it }  
}

task dependencyList << {
  println 'Compile Only:'
  configurations.compileOnly.allDependencies.each { dep -> println "$dep.group:$dep.name:$dep.version" }
  println ''
  println 'Compile:'
  configurations.compile.allDependencies.each { dep -> println "$dep.group:$dep.name:$dep.version" }
}

The dependencyList task outputs the following:

Compile Only:
org.slf4j:slf4j-log4j12:1.7.21
org.slf4j:slf4j-api:1.7.21

Compile:
org.slf4j:slf4j-api:1.7.21

However, the resolvedArtifacts task outputs this:

Compile Only:
slf4j-api-1.7.21.jar

Compile:
slf4j-api-1.7.21.jar

Is this expected?

What I’m actually trying to do is ensure that a particular dependency is globally excluded from the compile and runtime configurations but is included in the compileOnly configuration, similar to how Maven’s provided scope works when defined in the <dependencyManagement> section of the POM. I thought that excluding it from the compile, runtime, and testRuntime configurations but adding it to the compileOnly configuration would work, but it is not because of the behavior described above.

This is as designed. Exclude rules defined on a configuration are inherited by child configurations and compileOnly.extendsFrom compile. This limits the ability to globally exclude things in parent configurations that need to be included in child configurations. Alternatively, you can still do module level excludes.

Ok. Unfortunately module-level excludes don’t really fit the bill either because I’m trying to define these “provided” dependencies as a “BOM” that other build scripts can import, and I’ve no way to know what modules will pull in the dependencies in question so that I can exclude them. I know that other community plugins exist to create dependencyManagement-like functionality and Maven provided scope functionality, but in my last analysis (about a year ago) it still couldn’t replicate the functionality that Maven provides. I was hoping now that the compileOnly configuration was added that it would finally give me the “provided” feature I was looking for, but it looks like there’s still a gap.

Bottom line, I’m not seeing a way that in an “import” script I’m able to define a set of dependencies that should, no matter what, never make it into transitive or runtime classpaths of the JAR. Is there a different way to do this that I’m just not seeing?

For anyone else who runs into this thread, I found that the latest version of the io.spring.dependency-management plugin (0.5.6.RELEASE) does seem to give me what I need. It does not give me exactly what I was originally looking for as it is used to manage versions and exclusions but not configurations, but given the semantic differences between Gradle and Maven I think it’s as good as it will get.