"Unable to resolve class" when compiling Jenkins Groovy script

Hi,

I’m new to Gradle and the JVM in general - I’m only using it to write Groovy scripts to run on Jenkins. I have a script that I’m trying to put into a Gradle project purely so that I can check that it compiles and passed CodeNarc; I’d like to make this into a CI pipeline in the future, but offline is fine for now.

Using IntelliJ IDEA, I created a Gradle project and got the default dummy code to compile and lint with CodeNarc, the latter using the plugin. However, my actual script requires org.jenkins-ci.main:jenkins-core:2.121.2 and some plugins, e.g. org.jenkins-ci.plugins:timestamper:1.8.10. I’ve added these to my dependencies block as compile statements and I’ve added the Jenkins repo to repositories but compileGroovy still fails, even though the dependencies resolve. I’m using Gradle 4.8/4.9 (IDEA/terminal) and I tried Gradle 3.5.1, which gives the same error.

plugins {
  id 'groovy'
}

repositories {
  mavenCentral()
  maven {
    name 'jenkins'
    url 'https://repo.jenkins-ci.org/releases/'
  }
}

dependencies {
  compile 'org.codehaus.groovy:groovy-all:2.4.15'
  compile 'org.jenkins-ci.plugins:timestamper:1.8.10'
}
import hudson.plugins.timestamper.TimestamperBuildWrapper

new TimestamperBuildWrapper()
$ ./gradlew -q compileGroovy
startup failed:
/tmp/jenkins-jobs/src/main/groovy/justImport.groovy: 1: unable to resolve class hudson.plugins.timestamper.TimestamperBuildWrapper
 @ line 1, column 1.
   import hudson.plugins.timestamper.TimestamperBuildWrapper
   ^

1 error


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileGroovy'.
> Compilation failed; see the compiler error output for details.

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

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

BUILD FAILED in 1s

Hi,

the default packaging of Jenkins plugins is jpi, so I guess your dependency declaration puts timestamper.jpi on the classpath.
Can you try what happens if you add

compile 'org.jenkins-ci.plugins:timestamper:1.8.10@jar'

as a dependency?

You may also want to checkout https://github.com/jenkinsci/gradle-jpi-plugin, even if you are not building a plugin.

Cheers,
Stefan

1 Like

Thanks, @Stefan_Wolf! That solves my problem - CodeNarc also works fine now.

Note, specifying that you want an artifact-only jar dependency will also cause the dependency to no longer pull in transitive dependencies (transitive will become false). You can force transitive=true for the dependency, but that will simply pull in more useless jpi files.

The gradle-jpi-plugin mentioned by @Stefan_Wolf gets around this problem by resolving the jpi dependency tree and then creating new jar dependencies for each of them.

Thanks, @Chris_Dore - how should I declare Jenkins plugins as dependencies using that? (As I said, I’m very new to Gradle - thanks for your patience!)

To answer my own question, this seems to work:

jenkinsPlugins group: 'org.jenkins-ci.plugins', name: 'timestamper', version: '1.8.10'

Is that the correct syntax? I assume I don’t need the ext: 'jar'/@jar now?

I’m now trying to add these dependencies programmatically - I have a list of plugins in a YAML file elsewhere. Reading the YAML is fine, but how do I then add the dependencies? I’ve tried the method from this thread but it isn’t working. I seem to have the dependencies but now I’m getting my original import error again…

Can you share what you’re doing to configure the dependencies?

Sure - I’m basically importing a YAML file from my Ansible configuration that contains a mapping of plugin name to version and converting that to GAV using Jenkins’ plugins API.

Sorry, I should have been clearer. I meant show the code that you already have that isn’t working.

Sure, here are what should be the most relevant bits:

buildscript {
  repositories {
    maven { url '<local maven mirror>' }
  }

  dependencies {
    classpath group: 'org.yaml', name: 'snakeyaml', version: '1.21'
    classpath group: 'io.github.http-builder-ng', name: 'http-builder-ng-core', version: '1.0.3'
  }
}

import groovyx.net.http.HttpBuilder
import org.yaml.snakeyaml.Yaml

plugins {
  id 'groovy'
  id 'codenarc'
  id 'org.jenkins-ci.jpi' version '0.27.0'
}

repositories {
  maven { url '<local maven mirror>' }
  maven { url 'https://repo.jenkins-ci.org/releases/' }
  maven { url 'https://repo.spring.io/plugins-release/' }
}

dependencies {
  implementation group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15'
  implementation group: 'org.jenkins-ci.main', name: 'jenkins-core', version: '2.121.2'

  new File('group.yaml').withReader {
    new Yaml().load(it)
  }['plugins'].collect { plugin, version ->
    def (group, name) = HttpBuilder.configure {
      request.uri = "https://plugins.jenkins.io/api/plugin/$plugin"
    }.get()['gav'].tokenize(':')[0..1]
  [group: group, name: name, version: version]
  }.each { p ->
    it.jenkinsPlugins p
  }
}

group.yaml has, amongst other things, a key plugins containing a mapping of plugin name to plugin version; I’ve checked the REST query independently and it returns the correct data. (I know I’m getting the GAV for the latest version and then just replacing the version number, which isn’t great, but I can’t find a way to get the GAV of an arbitrary version of a Jenkins plugin given a “short name”.)

If I run ./gradlew build, I get the errors like the following:

> Task :compileGroovy FAILED
startup failed:
/home/allan/git/experiments/jenkins-jobs/bar/justImport2.groovy: 1: unable to resolve class hudson.plugins.timestamper.TimestamperBuildWrapper
 @ line 1, column 1.
   import hudson.plugins.timestamper.TimestamperBuildWrapper
   ^

justImport2.groovy is:

import hudson.plugins.timestamper.TimestamperBuildWrapper

new TimestamperBuildWrapper()

If I put some debugging in build.gradle, i.e. println configurations.jenkinsPlugins.dump(), then I can see that the dependencies appear to be added:

Configuration:  class='class org.gradle.api.internal.artifacts.configurations.DefaultConfiguration_Decorated'  name='jenkinsPlugins'  hashcode='1983424251'
Local Dependencies:
   DefaultExternalModuleDependency{group='org.jenkins-ci.plugins', name='ant', version='1.8', configuration='default'}
   DefaultExternalModuleDependency{group='org.jenkins-ci.plugins', name='antisamy-markup-formatter', version='1.5', configuration='default'}
   DefaultExternalModuleDependency{group='org.jenkins-ci.plugins', name='bouncycastle-api', version='2.16.3', configuration='default'}
...
Local Artifacts:
   none
All Dependencies:
   DefaultExternalModuleDependency{group='org.jenkins-ci.plugins', name='ant', version='1.8', configuration='default'}
   DefaultExternalModuleDependency{group='org.jenkins-ci.plugins', name='antisamy-markup-formatter', version='1.5', configuration='default'}
   DefaultExternalModuleDependency{group='org.jenkins-ci.plugins', name='bouncycastle-api', version='2.16.3', configuration='default'}
...
All Artifacts:
   none

I was able to get things to compile using a stripped down version of your yaml processing. I had to include the timestamper plugin. I don’t see that plugin in your example dependency output, is it missing from your yaml file?

My test:

$ cat build.gradle
plugins {
    id 'groovy'
    id 'org.jenkins-ci.jpi' version '0.27.0'
}
dependencies {
    implementation group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15'
    implementation group: 'org.jenkins-ci.main', name: 'jenkins-core', version: '2.121.2'
    ['org.jenkins-ci.plugins:ant:1.8', 'org.jenkins-ci.plugins:timestamper:1.8.10'].collect { gav ->
        def (g, n, v) = gav.tokenize(':')[0..2]
        [group: g, name: n, version: v]
    }.each { p ->
        it.jenkinsPlugins p
  }
}

$ cat src/main/groovy/Test.groovy
import hudson.plugins.timestamper.TimestamperBuildWrapper
class Test
{
}

Thanks, @Chris_Dore.

My dependency list is quite long, hence the ellipses :slight_smile:
I’ll try out a static version just including the timestamper plugin and its dependencies and see if that works.

I’ve resolved this now using the steps from the Jenkins Job DSL Wiki - thanks for all your help!