Skip to content

Commit b2c9d68

Browse files
authored
Add pom validation (#55272)
The pom files for our published artifacts are sent to maven central during Elastic's release process, but we may not found out until then that we have inadvertently broken the pom structure, as has happened several times before. This commit adds validation of the pom file specifically for the rules required by maven central.
1 parent f686bdc commit b2c9d68

File tree

6 files changed

+175
-11
lines changed

6 files changed

+175
-11
lines changed

buildSrc/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ dependencies {
120120
compile 'com.github.jengelman.gradle.plugins:shadow:5.1.0'
121121
compile 'de.thetaphi:forbiddenapis:2.7'
122122
compile 'com.avast.gradle:gradle-docker-compose-plugin:0.8.12'
123+
compile 'org.apache.maven:maven-model:3.6.2'
123124
compileOnly "com.puppycrawl.tools:checkstyle:${props.getProperty('checkstyle')}"
124125
testCompile "com.puppycrawl.tools:checkstyle:${props.getProperty('checkstyle')}"
125126
testCompile "junit:junit:${props.getProperty('junit')}"

buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,10 @@ class BuildPlugin implements Plugin<Project> {
377377
// Here we manually add any project dependencies in the "shadow" configuration to our generated POM
378378
publication.pom.withXml(this.&addScmInfo)
379379
publication.pom.withXml { xml ->
380-
Node dependenciesNode = (xml.asNode().get('dependencies') as NodeList).get(0) as Node
380+
Node root = xml.asNode();
381+
root.appendNode('name', project.name)
382+
root.appendNode('description', project.description)
383+
Node dependenciesNode = (root.get('dependencies') as NodeList).get(0) as Node
381384
project.configurations.getByName(ShadowBasePlugin.CONFIGURATION_NAME).allDependencies.each { dependency ->
382385
if (dependency instanceof ProjectDependency) {
383386
def dependencyNode = dependenciesNode.appendNode('dependency')

buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,20 @@ class PrecommitTasks {
100100
}
101101
}
102102

103-
return project.tasks.register('precommit') {
103+
TaskProvider precommit = project.tasks.register('precommit') {
104104
group = JavaBasePlugin.VERIFICATION_GROUP
105105
description = 'Runs all non-test checks.'
106106
dependsOn = precommitTasks
107107
}
108+
109+
// not all jar projects produce a pom (we don't ship all jars), so a pom validation
110+
// task is only added on some projects, and thus we can't always have a task
111+
// here to add to precommit tasks explicitly. Instead, we apply our internal
112+
// pom validation plugin after the precommit task is created and let the
113+
// plugin add the task if necessary
114+
project.plugins.apply(PomValidationPlugin)
115+
116+
return precommit
108117
}
109118

110119
static TaskProvider configureTestingConventions(Project project) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.gradle.precommit;
21+
22+
import org.elasticsearch.gradle.util.Util;
23+
import org.gradle.api.Plugin;
24+
import org.gradle.api.Project;
25+
import org.gradle.api.publish.PublishingExtension;
26+
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
27+
import org.gradle.api.publish.maven.tasks.GenerateMavenPom;
28+
import org.gradle.api.tasks.TaskProvider;
29+
30+
/**
31+
* Adds pom validation to every pom generation task.
32+
*/
33+
public class PomValidationPlugin implements Plugin<Project> {
34+
35+
@Override
36+
public void apply(Project project) {
37+
project.getPlugins().withType(MavenPublishPlugin.class).whenPluginAdded(p -> {
38+
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
39+
publishing.getPublications().all(publication -> {
40+
String publicationName = Util.capitalize(publication.getName());
41+
TaskProvider<PomValidationTask> validateTask = project.getTasks()
42+
.register("validate" + publicationName + "Pom", PomValidationTask.class);
43+
validateTask.configure(task -> {
44+
GenerateMavenPom generateMavenPom = project.getTasks()
45+
.withType(GenerateMavenPom.class)
46+
.getByName("generatePomFileFor" + publicationName + "Publication");
47+
task.dependsOn(generateMavenPom);
48+
task.getPomFile().fileValue(generateMavenPom.getDestination());
49+
});
50+
project.getTasks().named("precommit").configure(precommit -> { precommit.dependsOn(validateTask); });
51+
});
52+
});
53+
}
54+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.gradle.precommit;
21+
22+
import org.apache.maven.model.Model;
23+
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
24+
import org.gradle.api.GradleException;
25+
import org.gradle.api.file.RegularFileProperty;
26+
import org.gradle.api.tasks.InputFile;
27+
import org.gradle.api.tasks.TaskAction;
28+
29+
import java.io.FileReader;
30+
import java.util.Collection;
31+
import java.util.function.Consumer;
32+
import java.util.function.Predicate;
33+
34+
public class PomValidationTask extends PrecommitTask {
35+
36+
private final RegularFileProperty pomFile = getProject().getObjects().fileProperty();
37+
38+
private boolean foundError;
39+
40+
@InputFile
41+
public RegularFileProperty getPomFile() {
42+
return pomFile;
43+
}
44+
45+
@TaskAction
46+
public void checkPom() throws Exception {
47+
try (FileReader fileReader = new FileReader(pomFile.getAsFile().get())) {
48+
MavenXpp3Reader reader = new MavenXpp3Reader();
49+
Model model = reader.read(fileReader);
50+
51+
validateString("groupId", model.getGroupId());
52+
validateString("artifactId", model.getArtifactId());
53+
validateString("version", model.getVersion());
54+
validateString("name", model.getName());
55+
validateString("description", model.getDescription());
56+
validateString("url", model.getUrl());
57+
58+
validateCollection("licenses", model.getLicenses(), v -> {
59+
validateString("licenses.name", v.getName());
60+
validateString("licenses.url", v.getUrl());
61+
});
62+
63+
validateCollection("developers", model.getDevelopers(), v -> {
64+
validateString("developers.name", v.getName());
65+
validateString("developers.url", v.getUrl());
66+
});
67+
68+
validateNonNull("scm", model.getScm(), () -> validateString("scm.url", model.getScm().getUrl()));
69+
}
70+
if (foundError) {
71+
throw new GradleException("Check failed for task '" + getPath() + "', see console log for details");
72+
}
73+
}
74+
75+
private void logError(String element, String message) {
76+
foundError = true;
77+
getLogger().error("{} {} in [{}]", element, message, pomFile.getAsFile().get());
78+
}
79+
80+
private <T> void validateNonEmpty(String element, T value, Predicate<T> isEmpty) {
81+
if (isEmpty.test(value)) {
82+
logError(element, "is empty");
83+
}
84+
}
85+
86+
private <T> void validateNonNull(String element, T value, Runnable validator) {
87+
if (value == null) {
88+
logError(element, "is missing");
89+
} else {
90+
validator.run();
91+
}
92+
}
93+
94+
private void validateString(String element, String value) {
95+
validateNonNull(element, value, () -> validateNonEmpty(element, value, String::isBlank));
96+
}
97+
98+
private <T> void validateCollection(String element, Collection<T> value, Consumer<T> validator) {
99+
validateNonNull(element, value, () -> {
100+
validateNonEmpty(element, value, Collection::isEmpty);
101+
value.forEach(validator);
102+
});
103+
104+
}
105+
}

client/rest-high-level/build.gradle

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,13 @@ import org.elasticsearch.gradle.info.BuildParams
2222
apply plugin: 'elasticsearch.testclusters'
2323
apply plugin: 'elasticsearch.build'
2424
apply plugin: 'elasticsearch.rest-test'
25-
apply plugin: 'nebula.maven-base-publish'
25+
apply plugin: 'maven-publish'
2626
apply plugin: 'com.github.johnrengelman.shadow'
2727
apply plugin: 'elasticsearch.rest-resources'
2828

2929
group = 'org.elasticsearch.client'
3030
archivesBaseName = 'elasticsearch-rest-high-level-client'
3131

32-
publishing {
33-
publications {
34-
nebula {
35-
artifactId = archivesBaseName
36-
}
37-
}
38-
}
39-
4032
restResources {
4133
//we need to copy the yaml spec so we can check naming (see RestHighlevelClientTests#testApiNamingConventions)
4234
restApi {

0 commit comments

Comments
 (0)