Skip to content

Scoverage Plugin Resolving dependencies at configuration time #140

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

Closed
dthg opened this issue Jun 16, 2020 · 12 comments · Fixed by #143
Closed

Scoverage Plugin Resolving dependencies at configuration time #140

dthg opened this issue Jun 16, 2020 · 12 comments · Fixed by #143

Comments

@dthg
Copy link

dthg commented Jun 16, 2020

I'm having some issues using the gradle-scoverage plugin alongside radle-consistent-versions. gradle-consistent-versions enforces that no dependencies are resolved at compile time( see).

I opened an issue with gradle-consistent-versions palantir/gradle-consistent-versions#494 , but ended up closing that and opening one here instead.

Is this something you are open to fixing? I'm happy to attempt writing a patch if that is the case (still new to gradle plugin development though).

@eyalroth
Copy link
Contributor

The scoverage plugin is resolving the configurations in order to detect the Scala version (according to the version of the scala library dependency), which is required in order to know the name (not version) of the scalac scoverage dependencies (org.scoverage:scalac-scoverage-plugin_$scalaVersion, org.scoverage:scalac-scoverage-runtime_$scalaVersion).

This is a common issue with Scala dependencies, as each scala version requires a different artifact name; i.e, the scala version is not part of the dependency version. I wish Gradle would've add a more builtin and generic support, allowing to specify scala version in one place and make it available in the configuration.

Maybe there's a way to make this plugin compatible with the gradle-consistent-versions plugin, I'll have to look at this at a later time.

For now, you can try using Spring's dependency-management plugin, or use the native java-platform plugin to manage dependencies (available since Gradle 5 I believe).

eyalroth added a commit to eyalroth/gradle-scoverage that referenced this issue Jun 19, 2020
…adle-consistent-versions' plugin (test currently fails)
@eyalroth
Copy link
Contributor

Unfortunately, I believe this cannot be fixed.

The scoverage plugin must resolve two of the configuration-sets at configuration time:

  1. compileClasspath - in order to resolve the scala version (as explained earlier).
  2. scoverage - the plugin's own configuration, in order to determine the absolute path of the scalac-scoverage-plugin jar, which is required in order to configure the compilation task (-Xplugin:/path/to/plugin.jar).

I believe that the only way to make the two plugins compatible with each other is to remove the banning of dependency resolution at configuration time on palantir's plugin side (or at least provide an option to disable it).

I've added a commit with only a new functional test for the compatibility with the palantir plugin, if you wish to check it out.

@nyonson
Copy link

nyonson commented Aug 12, 2020

Would it be possible to add some options to the plugin extension to configure the scala version and path instead of relying on resolving the configurations?

@eyalroth
Copy link
Contributor

@y0ns0n There's already an option to specify the scala version (scoverageScalaVersion). See the documentation.

Alas, the path of the plugin cannot be specified and it would be futile to add such an option, as the path is dynamic; the JAR file is resolved and downloaded by Gradle.

@nyonson
Copy link

nyonson commented Aug 12, 2020

I have seen some hacks to avoid resolving the configuration for downloaded jars like the following:

task downloadChromedriver(type: Copy, description: 'Downloads chromedriver from the repo', group: 'Resource') {
    // defer configuration resolution to execution time
    dependsOn configurations.chromedriver
    from {
        configurations.chromedriver.singleFile
    }
    into chromedriverDir
}

Could we use that approach here to copy the jar to a known spot?

@eyalroth
Copy link
Contributor

@y0ns0n It doesn't seem to work I'm afraid :(

I tried this:

def pluginDependency = project.configurations[CONFIGURATION_NAME].incoming.dependencies.find {
    it.name.contains("scalac-scoverage-plugin")
}
def copyPlugin = project.tasks.create('copyScoveragePlugin',  org.gradle.api.tasks.Copy) {
    from pluginDependency
    into '/tmp/scalac-scoverage-plugin.jar'
}
compileTask.dependsOn(copyPlugin)
compileTask.configure {
    List<String> parameters = ['-Xplugin:' + '/tmp/scalac-scoverage-plugin.jar']
    // ...
}

But then I'm getting the exception on the copy task:

Execution failed for task ':copyScoveragePlugin'.
> Cannot convert the provided notation to a File or URI: DefaultExternalModuleDependency{group='org.scoverage', name='scalac-scoverage-plugin_2.12', version='1.4.1', configuration='default'}.
  The following types/formats are supported:
    - A String or CharSequence path, for example 'src/main/java' or '/usr/include'.
    - A String or CharSequence URI, for example 'file:/usr/include'.
    - A File instance.
    - A Path instance.
    - A Directory instance.
    - A RegularFile instance.
    - A URI or URL instance.
    - A TextResource instance.

I'm not exactly sure what is the 'chromedriver' configuration from your example, but it doesn't seem to be a dependency; rather, it looks like a custom object containing a reference to the file.

@nyonson
Copy link

nyonson commented Aug 15, 2020

Sorry about the lack of context in my example. chromedriver is a custom configuration which we toss a big chrome dependency on. We then grab that jar and use it in integration tests:

configurations {
    chromedriver
}

dependencies {
  chromedriver(group: 'com.google.chrome', name: 'chromedriver', version: '2.25', classifier: 'mac64', ext: 'bin')
}

I wonder if we could change the copyPlugin method to take a closure:

def copyPlugin = project.tasks.create('copyScoveragePlugin',  org.gradle.api.tasks.Copy) {
    dependsOn project.configurations[CONFIGURATION_NAME]
    from {
        project.configurations[CONFIGURATION_NAME].incoming.dependencies.find {
          it.name.contains("scalac-scoverage-plugin")
      }
    }
    into '/tmp/scalac-scoverage-plugin.jar'
}

After that it looks like you are just dealing with a type issue, but I think there must be a way to change it to a file similar to the way we are using the singleFile method.

@eyalroth
Copy link
Contributor

@y0ns0n I'm pretty certain that the singleFile on a configuration resolves it, so that should fail as well.

I tried this:

project.configurations.create('scoveragePlugin') {
    visible = false
    transitive = false
}

project.dependencies {
    scoveragePlugin("org.scoverage:scalac-scoverage-plugin_2.12:1.4.1")
}

def copyPlugin = project.tasks.create('copyScoveragePlugin',  org.gradle.api.tasks.Copy) {
    dependsOn project.configurations.scoveragePlugin
    from project.configurations.scoveragePlugin.singleFile
    into '/tmp/scoverage/scalac-scoverage-plugin.jar'
}
compileTask.dependsOn(copyPlugin)

which yields "Not allowed to resolve configuration ':scoveragePlugin' at configuration time".

@nyonson
Copy link

nyonson commented Aug 15, 2020

singleFile does resolve it, but we defer it by putting it in a closure. Sofrom { project.configurations.scoveragePlugin.singleFile } instead of from project.configurations.scoveragePlugin.singleFile.

eyalroth added a commit to eyalroth/gradle-scoverage that referenced this issue Aug 16, 2020
eyalroth added a commit to eyalroth/gradle-scoverage that referenced this issue Aug 16, 2020
…y configured by the `scoverageScalaVersion` option
@eyalroth
Copy link
Contributor

@y0ns0n Huh, that actually works, and it turns out there's an even simpler solution -- configuring the plugin parameter in doFirst.

I opened a PR :)

@nyonson
Copy link

nyonson commented Aug 16, 2020

Awesome!

My only feedback for the PR is that I believe this case is more general than just the Palantir plugin. I work on a large, multi-project, monorepo. The configuration phase currently takes about 5 minutes. Through some testing and build reports we realized the bulk of this time is spent by dependencies resolving in the phase (instead of the execution phase). I believe avoiding resolving dependencies in configuration is a more general best practice.

@eyalroth
Copy link
Contributor

@y0ns0n Yes, the plugin will cause all the dependencies of the compile classpath to be resolved (i.e downloaded) during configuration phase, since it is looking for the scala library version. After this PR though, if you use the scoverageScalaVersion option, the plugin will no longer do that.

I wish there was a better way to implement this auto-detection of the scala version. There is no standard place in which the Scala version is defined other than the scala-library dependency itself, which may be defined with a dynamic version -- say, 2.12.+ or even 2.+ -- which will only be fully available once the configuration is resolved.

Unfortunately, the resolution phase is "atomic"; it would have been nice if it was split to "resolve versions" and "download", but I'm not sure this is so trivial to implement. Even then, the plugin would have to be applied to a project only after any other plugin which uses a custom resolution strategy (like Spring's dependency management plugin), as they might change the scala version.

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 a pull request may close this issue.

3 participants