melix / jmh-gradle-plugin

Integrates the JMH benchmarking framework with Gradle
Apache License 2.0
665 stars 88 forks source link

Plugin 0.4.2 is broken: jar without dependencies #97

Open vyazelenko opened 7 years ago

vyazelenko commented 7 years ago

Latest version of the plugin 0.4.2 generates incomplete jar: the jar files only contains project code and no third-party dependencies. In fact it is much weirded as it includes project classes with 3rd party dependencies as jars and no JMH classes or jars.

/cc @aalmiray @melix

vyazelenko commented 7 years ago

It seems to be a problem with all 0.4.x releases.

christophsturm commented 7 years ago

this means there is no jmh plugin that works with gradle 4.0

melix commented 7 years ago

I suspect the manifest entry is missing, but you can still execute the benchmark using

java -cp ... org.openjdk.jmh.Main

On a side note, I don't think the plugin should generate a jar with dependencies included, this is a packaging issue that is solved by the shadow plugin. So technically speaking, the plugin is right: the jar should NOT include a manifest entry with org.openjdk.jmh.Main as main class, because it's found in the JMH jar.

Why don't use use the jmh task to run the benchmarks? If you want to run it from CLI, then you need to put the generated jar on classpath, plus JMH dependencies.

christophsturm commented 7 years ago

its good practice to run the jar file standalone because then there are less processes interfering with the benchmark. also the readme file says that the dependencies are included. I really don't care how i build my jar, if the shadow plugin is the way to go, thats fine too, maybe put an example how to do it into the readme.

melix commented 7 years ago

Ok so to sum up:

its good practice to run the jar file standalone because then there are less processes interfering with the benchmark.

Yes, however the task is often enough. I get the point.

the readme file says that the dependencies are included

The readme is wrong :), let's fix it

I really don't care how i build my jar, if the shadow plugin is the way to go, thats fine too, maybe put an example how to do it into the readme.

I wouldn't say it's the way to go. Personally I wouldn't use it. However, I agree that:

  1. an example using the shadow plugin in the README would be nice
  2. an example how to do it without the shadow plugin is also interesting. It's not super complicated, really.

And finally, do we agree that we don't have to create a fat jar?

christophsturm commented 7 years ago

ok i agree with all your points. if its possible to create the jar file with or without deps that's great. Its just a matter of documentation really.

christophsturm commented 7 years ago

I see that the readme file already documents how to use the shadow plugin. one thing that I'm not sure about is how the shadow task is called. jmhShadowJar maybe?

aalmiray commented 7 years ago

the shadow task is called internally. If the shadow plugin is explicitly added to the build then the fat jar is created using a task of type ShadowJar, otherwise the standard Jar type is used. This usage is transparent to the user thus there's no jmhShadowJar task leaking to the build itself, it's just jmhJar.

christophsturm commented 7 years ago

@aalmiray how does it know that I want to use the shadow plugin for my JMH jar and not for my normal app jar file?

juddgaddie commented 7 years ago

With: Gradle 3.5.1, JMH Plugin 0.2.0 Fat Jar created, with all required dependencies. I run ./gradlew :generic-benchmarks:jmh and everything works as expected. :+1:

With Gradle 4.0.1, JMH Plugin 0.4.x Jar only includes benchmark code. I run ./gradlew :generic-benchmarks:jmh and it fails with a java.lang.NoClassDefFoundError, since this is a multi module project the perticular classes that are missing are project dependencies (I assume this would also occur with 3rd party deps) :-1:

This is a problem for me and I hope you not going to say I should be including all my project deps in the jmh configuration. Never had to before. /cc @melix

tom-smalls commented 7 years ago

Same problem as what @juddgaddie has mentioned around missing project dependencies

juddgaddie commented 7 years ago

This issue was unexpectedly resolved for me when switching includeTests from false to true. jmh { includeTests = true }

The dependencies are compile not compileTest so have no idea why this should resolve the issue.

wrprice commented 6 years ago

Maybe this will help... I recently upgraded a project to Gradle >= 4.2 from 3.2.x as a part of a migration toward JDK9. I had to use plugin 0.4.5-beta-2 to resolve the lock issue on the Windows platform, which meant I ended up with Gradle 4.3.1.

As a part of the Gradle upgrade, I switched from the older java plugin to the new java-library plugin. I don't know how support of the old java plugin works in Gradle 4.x, but if it's basically "adapting" to the newer conventions then this may still apply.

Everything pretty much worked, except I had to slightly tweak my dependencies to add:

dependencies {
    // ...

    jmh configurations.api
}

Gradle now separates api dependencies from implementation dependencies, and I was getting compile errors from the benchmark classes on unknown packages/classes from one of my api-declared dependencies.

elizarov commented 6 years ago

Same problem here with 0.4.5. Resulting benchmarks jar is not a shadow jar -- dependencies are included inside as jars, so it cannot run with java -jar benchmarks.jar. Using -cp and specifying main class does not help (it is not there). Any update on this issue?

elizarov commented 6 years ago

Finally worked for me. Here is the workaround.

  1. You need to have shadowjar plugin in your gradle file, because jmh-gradle-plugin whould only generate shadowjar if that plugin is present in your project (otherwise it generates jar with dependencies simply packed as jars inside):
buildscript {
    dependencies {  
        classpath "com.github.jengelman.gradle.plugins:shadow:2.0.2"
    }
}

apply plugin: "com.github.johnrengelman.shadow"
  1. You need to explicitly add JMH core to the compile dependencies or it is not going to be included into the resulting shadowjar:
buildscript {
    ext.jmh_version = '1.20'
}

jmh {
    jmhVersion = jmh_version
}

dependencies {
    compile "org.openjdk.jmh:jmh-core:$jmh_version"
}
kno10 commented 6 years ago

I also have not yet found a working combination of this plugin with Gradle 4 and Java 8.

With ./gradlew jmh I get "error reading benchmark list" due to some odd zip error.

With jmhJar I get a fat jars that contain the dependencies as jar files inside the jar, rather than a proper shadow jar with unpacked contents, despite having the shadow plugin loaded. If I unpack this jar, build the classpath manually, then it will run.

kno10 commented 6 years ago

So I am now also using the "workaround" by @elizarov - because it works:

  1. Disable the jmh plugin, add the shadow plugin instead:
    plugins { // ...
    // id 'me.champeau.gradle.jmh' version '0.4.5' // did not work
    id 'com.github.johnrengelman.shadow' version '2.0.2'
    }
  2. Configure the shadow target as needed:
    shadowJar {
    destinationDir = project.rootDir
    archiveName = 'benchmarks.jar'
    classifier = 'benchmarks'
    manifest.attributes('Main-Class': 'org.openjdk.jmh.Main')
    }
  3. Move the jmh benchmarks from src/jmh/java back to src/main/java, or setup a sourceSet manually for jmh. Same for jmh dependencies, I have them as compile or compileOnly (annotation processor) dependencies.
    buildscript {
    ext.jmh_version = '1.20' // JMH version to use
    }
    dependencies {
    compile group: 'org.openjdk.jmh', name: 'jmh-core', version: jmh_version
    compileOnly group: 'org.openjdk.jmh', name: 'jmh-generator-annprocess', version: jmh_version
    }
  4. Build using ./gradlew shadowJar, then run the jar.

Works fine without the plugin, with minimal effort; at least for basic setups like mine.

Bytekeeper commented 5 years ago

"error reading benchmark list" - this might be caused by a running gradle daemon still having the JAR opened. When rebuilding the jar it sometimes won't be reopened and causes strange Zip reading errors.

marshallpierce commented 5 years ago

The workarounds above didn't work for me on gradle 5.4.1, so this is what is working for me:

plugins {
    id("me.champeau.gradle.jmh") version "0.4.8"
    id("com.github.johnrengelman.shadow") version "5.0.0"
}

...

// jmhJar task provided by jmh gradle plugin is currently broken
// https://github.com/melix/jmh-gradle-plugin/issues/97
// so instead, we configure the shadowJar task to have JMH bits in it
tasks.shadowJar {
    manifest {
        attributes(Pair("Main-Class", "org.openjdk.jmh.Main"))
    }

    // include dependencies
    configurations.add(project.configurations.jmh.get())
    // include benchmark classes
    from(project.sourceSets.jmh.get().output)
    // include generated java source, BenchmarkList and other JMH resources
    from(tasks.jmhRunBytecodeGenerator.get().outputs)
    // include compiled generated classes
    from(tasks.jmhCompileGeneratedClasses.get().outputs)

    dependsOn(tasks.jmhCompileGeneratedClasses)
}
DVanderstoken commented 5 years ago

@marshallpierce can you please provide us a full example ?

I have the same problem with error : Could not find method get() for arguments[] on configuration ':jmh' of type org.gradle.api.internal.artifacts.configurations.DefaultConfiguration

DVanderstoken commented 5 years ago

This is what is working for me with: `./gradlew -version


Gradle 5.4.1

Build time: 2019-04-26 08:14:42 UTC Revision: 261d171646b36a6a28d5a19a69676cd098a4c19d

Kotlin: 1.3.21 Groovy: 2.5.4 Ant: Apache Ant(TM) version 1.9.13 compiled on July 10 2018 JVM: 12.0.1 (Oracle Corporation 12.0.1+12) OS: Mac OS X 10.14.4 x86_64`

And my build.gradle file : `/*

plugins { id 'java' id 'me.champeau.gradle.jmh' version '0.4.8' }

repositories { mavenLocal() maven { url = 'http://repo.maven.apache.org/maven2' } jcenter() }

dependencies { (...) compile 'org.openjdk.jmh:jmh-core:1.21' jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.21' }

sourceCompatibility = '1.8'

sourceSets { main.java.srcDirs = ['src/main/java', 'src/jmh/java'] }

tasks.getByName('jmhJar').doFirst() {duplicatesStrategy(DuplicatesStrategy.EXCLUDE)}

jmh { iterations = 10 fork = 5 jvmArgs = '-server' timeUnit = 's' warmupIterations =5 jmhVersion = '1.21' benchmarkMode = ['avgt'] duplicateClassesStrategy = 'exclude' } `

marshallpierce commented 5 years ago

You can see my work here porting roaring bitmap's build to gradle: https://github.com/RoaringBitmap/RoaringBitmap/pull/343