Skip to content
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

Build: Rework shadow plugin configuration #32409

Merged
merged 27 commits into from
Aug 22, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,21 +319,19 @@ common configurations in our build and how we use them:

<dl>
<dt>`compile`</dt><dd>Code that is on the classpath at both compile and
runtime. If the [`shadow`][shadow-plugin] plugin is applied to the project then
this code is bundled into the jar produced by the project.</dd>
runtime.</dd>
<dt>`runtime`</dt><dd>Code that is not on the classpath at compile time but is
on the classpath at runtime. We mostly use this configuration to make sure that
we do not accidentally compile against dependencies of our dependencies also
known as "transitive" dependencies".</dd>
<dt>`compileOnly`</dt><dd>Code that is on the classpath at comile time but that
<dt>`compileOnly`</dt><dd>Code that is on the classpath at compile time but that
should not be shipped with the project because it is "provided" by the runtime
somehow. Elasticsearch plugins use this configuration to include dependencies
that are bundled with Elasticsearch's server.</dd>
<dt>`shadow`</dt><dd>Only available in projects with the shadow plugin. Code
that is on the classpath at both compile and runtime but it *not* bundled into
the jar produced by the project. If you depend on a project with the `shadow`
plugin then you need to depend on this configuration because it will bring
along all of the dependencies you need at runtime.</dd>
<dt>`bundle`</dt><dd>Only available in projects with the shadow plugin,
dependencies with this configuration are bundled into the jar produced by the
build. Since IDEs do not understand this configuration we rig them to treat
dependencies in this configuration as `compile` dependencies.</dd>
<dt>`testCompile`</dt><dd>Code that is on the classpath for compiling tests
that are part of this project but not production code. The canonical example
of this is `junit`.</dd>
Expand Down
12 changes: 9 additions & 3 deletions benchmarks/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ archivesBaseName = 'elasticsearch-benchmarks'
test.enabled = false

dependencies {
compile("org.elasticsearch:elasticsearch:${version}") {
bundle("org.elasticsearch:elasticsearch:${version}") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we need to bundle anything in benchmarks?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no clue. It has been shadowing all of its dependencies for ages and I was keeping it going. I could dig into it though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I didn't realize that. Quite odd, maybe @danielmitterdorfer can elaborate on why we do this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an area where we can improve a lot: commenting the non-obvious choices in our code.

Copy link
Member

@jasontedor jasontedor Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's for convenience, so that you can run the benchmarks as simply as java -jar benchmarks.jar <class under benchmark>.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done a little digging and I also think it is for convenience. Every example I see of using JMH shades the dependencies but this says that it is not required.

// JMH ships with the conflicting version 4.6. This prevents us from using jopt-simple in benchmarks (which should be ok) but allows
// us to invoke the JMH uberjar as usual.
exclude group: 'net.sf.jopt-simple', module: 'jopt-simple'
}
compile "org.openjdk.jmh:jmh-core:$versions.jmh"
compile "org.openjdk.jmh:jmh-generator-annprocess:$versions.jmh"
bundle "org.openjdk.jmh:jmh-core:$versions.jmh"
bundle "org.openjdk.jmh:jmh-generator-annprocess:$versions.jmh"
// Dependencies of JMH
runtime 'net.sf.jopt-simple:jopt-simple:4.6'
runtime 'org.apache.commons:commons-math3:3.2'
Expand All @@ -59,6 +59,12 @@ forbiddenApis {
dependencyLicenses.enabled = false
dependenciesInfo.enabled = false

/*
* Javadocs are a waste of time because we try to make them produce
* nice output with shading.
*/
javadoc.enabled = false

thirdPartyAudit.excludes = [
// these classes intentionally use JDK internal API (and this is ok since the project is maintained by Oracle employees)
'org.openjdk.jmh.profile.AbstractHotspotProfiler',
Expand Down
32 changes: 11 additions & 21 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.elasticsearch.gradle.LoggedExec
import org.elasticsearch.gradle.Version
import org.elasticsearch.gradle.VersionCollection
import org.elasticsearch.gradle.VersionProperties
import org.elasticsearch.gradle.plugin.PluginBuildPlugin
import org.gradle.plugins.ide.eclipse.model.SourceFolder
import org.gradle.util.GradleVersion
import org.gradle.util.DistributionLocator
Expand Down Expand Up @@ -296,7 +297,7 @@ subprojects {
// org.elasticsearch:elasticsearch must be the last one or all the links for the
// other packages (e.g org.elasticsearch.client) will point to server rather than
// their own artifacts.
if (project.plugins.hasPlugin(BuildPlugin)) {
if (project.plugins.hasPlugin(BuildPlugin) || project.plugins.hasPlugin(PluginBuildPlugin)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PluginBuildPlugin extends BuildPlugin, maybe it should apply it instead? Then we only need the one check.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that'd be cleaner but I didn't want to do it while messing around with shadow stuff.

String artifactsHost = VersionProperties.elasticsearch.isSnapshot() ? "https://snapshots.elastic.co" : "https://artifacts.elastic.co"
Closure sortClosure = { a, b -> b.group <=> a.group }
Closure depJavadocClosure = { shadowed, dep ->
Expand All @@ -314,13 +315,6 @@ subprojects {
*/
project.evaluationDependsOn(upstreamProject.path)
project.javadoc.source += upstreamProject.javadoc.source
/*
* Do not add those projects to the javadoc classpath because
* we are going to resolve them with their source instead.
*/
project.javadoc.classpath = project.javadoc.classpath.filter { f ->
false == upstreamProject.configurations.archives.artifacts.files.files.contains(f)
}
/*
* Instead we need the upstream project's javadoc classpath so
* we don't barf on the classes that it references.
Expand All @@ -337,16 +331,16 @@ subprojects {
project.configurations.compile.dependencies
.findAll()
.toSorted(sortClosure)
.each({ c -> depJavadocClosure(hasShadow, c) })
.each({ c -> depJavadocClosure(false, c) })
project.configurations.compileOnly.dependencies
.findAll()
.toSorted(sortClosure)
.each({ c -> depJavadocClosure(hasShadow, c) })
.each({ c -> depJavadocClosure(false, c) })
if (hasShadow) {
project.configurations.shadow.dependencies
project.configurations.bundle.dependencies
.findAll()
.toSorted(sortClosure)
.each({ c -> depJavadocClosure(false, c) })
.each({ c -> depJavadocClosure(true, c) })
}
}
}
Expand Down Expand Up @@ -525,15 +519,11 @@ allprojects {
* Without this they won't properly depend on the shadowed project.
*/
if (isEclipse || isIdea) {
configurations.all { Configuration configuration ->
dependencies.all { Dependency dep ->
if (dep instanceof ProjectDependency) {
if (dep.getTargetConfiguration() == 'shadow') {
configuration.dependencies.add(project.dependencies.project(path: dep.dependencyProject.path, configuration: 'runtime'))
}
}
}
}
project.plugins.withType(ShadowPlugin).whenPluginAdded {
project.afterEvaluate {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be done when the bundle configuration is added, in BuildPlugin, instead of in an afterEvaluate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BuildPlugin doesn't have isEclipse or isIdea but I can do this after the BuildPlugin is added.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BuildPlugin doesn't have isEclipse or isIdea

Maybe it should? It seems odd to have to do this extra work, it should really be part of the base setup.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll use it if it is defined.

project.configurations.compile.extendsFrom project.configurations.bundle
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ class BuildPlugin implements Plugin<Project> {
}
project.pluginManager.apply('java')
project.pluginManager.apply('carrotsearch.randomized-testing')
// these plugins add lots of info to our jars
configureConfigurations(project)
configureJars(project) // jar config must be added before info broker
// these plugins add lots of info to our jars
project.pluginManager.apply('nebula.info-broker')
project.pluginManager.apply('nebula.info-basic')
project.pluginManager.apply('nebula.info-java')
Expand All @@ -89,8 +90,8 @@ class BuildPlugin implements Plugin<Project> {

globalBuildInfo(project)
configureRepositories(project)
configureConfigurations(project)
project.ext.versions = VersionProperties.versions
configureSourceSets(project)
configureCompile(project)
configureJavadoc(project)
configureSourcesJar(project)
Expand Down Expand Up @@ -390,8 +391,10 @@ class BuildPlugin implements Plugin<Project> {
project.configurations.compile.dependencies.all(disableTransitiveDeps)
project.configurations.testCompile.dependencies.all(disableTransitiveDeps)
project.configurations.compileOnly.dependencies.all(disableTransitiveDeps)

project.plugins.withType(ShadowPlugin).whenPluginAdded {
project.configurations.shadow.dependencies.all(disableTransitiveDeps)
Configuration bundle = project.configurations.create('bundle')
bundle.dependencies.all(disableTransitiveDeps)
}
}

Expand Down Expand Up @@ -515,41 +518,22 @@ class BuildPlugin implements Plugin<Project> {
}
}
}
project.plugins.withType(ShadowPlugin).whenPluginAdded {
project.publishing {
publications {
nebula(MavenPublication) {
artifact project.tasks.shadowJar
artifactId = project.archivesBaseName
/*
* Configure the pom to include the "shadow" as compile dependencies
* because that is how we're using them but remove all other dependencies
* because they've been shaded into the jar.
*/
pom.withXml { XmlProvider xml ->
Node root = xml.asNode()
root.remove(root.dependencies)
Node dependenciesNode = root.appendNode('dependencies')
project.configurations.shadow.allDependencies.each {
if (false == it instanceof SelfResolvingDependency) {
Node dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
dependencyNode.appendNode('scope', 'compile')
}
}
// Be tidy and remove the element if it is empty
if (dependenciesNode.children.empty) {
root.remove(dependenciesNode)
}
}
}
}
}

}

/**
* Add dependencies that we are going to bundle to the compile classpath.
*/
static void configureSourceSets(Project project) {
project.plugins.withType(ShadowPlugin).whenPluginAdded {
['main', 'test'].each {name ->
SourceSet sourceSet = project.sourceSets.findByName(name)
if (sourceSet != null) {
sourceSet.compileClasspath += project.configurations.bundle
}
}
}

}

/** Adds compiler settings to the project */
Expand Down Expand Up @@ -729,9 +713,16 @@ class BuildPlugin implements Plugin<Project> {
* better to be safe
*/
mergeServiceFiles()
/*
* Bundle dependencies of the "bundled" configuration.
*/
configurations = [project.configurations.bundle]
}
// Make sure we assemble the shadow jar
project.tasks.assemble.dependsOn project.tasks.shadowJar
project.artifacts {
apiElements project.tasks.shadowJar
}
}
}

Expand Down Expand Up @@ -822,13 +813,8 @@ class BuildPlugin implements Plugin<Project> {
exclude '**/*$*.class'

project.plugins.withType(ShadowPlugin).whenPluginAdded {
/*
* If we make a shaded jar we test against it.
*/
// Test against a shadow jar if we made one
classpath -= project.tasks.compileJava.outputs.files
classpath -= project.configurations.compile
classpath -= project.configurations.runtime
classpath += project.configurations.shadow
classpath += project.tasks.shadowJar.outputs.files
dependsOn project.tasks.shadowJar
}
Expand All @@ -854,26 +840,6 @@ class BuildPlugin implements Plugin<Project> {
additionalTest.dependsOn(project.tasks.testClasses)
project.check.dependsOn(additionalTest)
});

project.plugins.withType(ShadowPlugin).whenPluginAdded {
/*
* We need somewhere to configure dependencies that we don't wish
* to shade into the jar. The shadow plugin creates a "shadow"
* configuration which is *almost* exactly that. It is never
* bundled into the shaded jar but is used for main source
* compilation. Unfortunately, by default it is not used for
* *test* source compilation and isn't used in tests at all. This
* change makes it available for test compilation.
*
* Note that this isn't going to work properly with qa projects
* but they have no business applying the shadow plugin in the
* firstplace.
*/
SourceSet testSourceSet = project.sourceSets.findByName('test')
if (testSourceSet != null) {
testSourceSet.compileClasspath += project.configurations.shadow
}
}
}

private static configurePrecommit(Project project) {
Expand All @@ -885,7 +851,7 @@ class BuildPlugin implements Plugin<Project> {
it.group.startsWith('org.elasticsearch') == false
} - project.configurations.compileOnly
project.plugins.withType(ShadowPlugin).whenPluginAdded {
project.dependencyLicenses.dependencies += project.configurations.shadow.fileCollection {
project.dependencyLicenses.dependencies += project.configurations.bundle.fileCollection {
it.group.startsWith('org.elasticsearch') == false
}
}
Expand All @@ -896,7 +862,7 @@ class BuildPlugin implements Plugin<Project> {
deps.runtimeConfiguration = project.configurations.runtime
project.plugins.withType(ShadowPlugin).whenPluginAdded {
deps.runtimeConfiguration = project.configurations.create('infoDeps')
deps.runtimeConfiguration.extendsFrom(project.configurations.runtime, project.configurations.shadow)
deps.runtimeConfiguration.extendsFrom(project.configurations.runtime, project.configurations.bundle)
}
deps.compileOnlyConfiguration = project.configurations.compileOnly
project.afterEvaluate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,10 @@ public class PluginBuildPlugin extends BuildPlugin {
from pluginMetadata // metadata (eg custom security policy)
/*
* If the plugin is using the shadow plugin then we need to bundle
* "shadow" things rather than the default jar and dependencies so
* we don't hit jar hell.
* that shadow jar.
*/
from { project.plugins.hasPlugin(ShadowPlugin) ? project.shadowJar : project.jar }
from { project.plugins.hasPlugin(ShadowPlugin) ? project.configurations.shadow : project.configurations.runtime - project.configurations.compileOnly }
from project.configurations.runtime - project.configurations.compileOnly
// extra files for the plugin to go into the zip
from('src/main/packaging') // TODO: move all config/bin/_size/etc into packaging
from('src/main') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.elasticsearch.gradle.precommit

import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
import org.elasticsearch.gradle.LoggedExec
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.OutputFile
Expand All @@ -39,6 +40,9 @@ public class JarHellTask extends LoggedExec {
public JarHellTask() {
project.afterEvaluate {
FileCollection classpath = project.sourceSets.test.runtimeClasspath
if (project.plugins.hasPlugin(ShadowPlugin)) {
classpath += project.configurations.bundle
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since compile extends from bundle, shouldn't this be in the test runtime classpath already?

Copy link
Member Author

@nik9000 nik9000 Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compile only extends from bundle when we're configuring an IDE.

}
inputs.files(classpath)
dependsOn(classpath)
description = "Runs CheckJarHell on ${classpath}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.elasticsearch.gradle.precommit;

import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildListener;
Expand Down Expand Up @@ -82,6 +83,11 @@ public class ThirdPartyAuditTask extends AntTask {
configuration = project.configurations.findByName('testCompile')
}
assert configuration != null
if (project.plugins.hasPlugin(ShadowPlugin)) {
Configuration original = configuration
configuration = project.configurations.create('thirdPartyAudit')
configuration.extendsFrom(original, project.configurations.bundle)
}
if (compileOnly == null) {
classpath = configuration
} else {
Expand Down
24 changes: 15 additions & 9 deletions client/benchmark/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,26 @@ mainClassName = 'org.elasticsearch.client.benchmark.BenchmarkMain'
test.enabled = false

dependencies {
compile 'org.apache.commons:commons-math3:3.2'
bundle 'org.apache.commons:commons-math3:3.2'

compile("org.elasticsearch.client:elasticsearch-rest-client:${version}")
bundle("org.elasticsearch.client:elasticsearch-rest-client:${version}")
// bottleneck should be the client, not Elasticsearch
compile project(path: ':client:client-benchmark-noop-api-plugin')
bundle project(path: ':client:client-benchmark-noop-api-plugin')
// for transport client
compile("org.elasticsearch:elasticsearch:${version}")
compile("org.elasticsearch.client:transport:${version}")
compile project(path: ':modules:transport-netty4', configuration: 'runtime')
compile project(path: ':modules:reindex', configuration: 'runtime')
compile project(path: ':modules:lang-mustache', configuration: 'runtime')
compile project(path: ':modules:percolator', configuration: 'runtime')
bundle("org.elasticsearch:elasticsearch:${version}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, why do benchmark jars for testing need bundled deps?

bundle("org.elasticsearch.client:transport:${version}")
bundle project(path: ':modules:transport-netty4', configuration: 'runtime')
bundle project(path: ':modules:reindex', configuration: 'runtime')
bundle project(path: ':modules:lang-mustache', configuration: 'runtime')
bundle project(path: ':modules:percolator', configuration: 'runtime')
}

// No licenses for our benchmark deps (we don't ship benchmarks)
dependencyLicenses.enabled = false
dependenciesInfo.enabled = false

/*
* Javadocs are a waste of time because we try to make them produce
* nice output with shading.
*/
javadoc.enabled = false
Loading