Skip to content

Commit 9f1dfdb

Browse files
committed
Build: Add AntTask to simplify controlling logging when running ant from gradle
This new task allows setting code, similar to a doLast or doFirst, except it is specifically geared at running ant (and thus called doAnt). It adjusts the ant logging while running the ant so that the log level/behavior can be tweaked, and automatically buffers based on gradle logging level, and dumps the ant output upon failure.
1 parent 4ec605e commit 9f1dfdb

File tree

5 files changed

+166
-61
lines changed

5 files changed

+166
-61
lines changed

Diff for: build.gradle

-11
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,6 @@ subprojects {
123123
}
124124
}
125125
}
126-
// For reasons we don't fully understand yet, external dependencies are not picked up by Ant's optional tasks.
127-
// But you can easily do it in another way.
128-
// Only if your buildscript and Ant's optional task need the same library would you have to define it twice.
129-
// https://docs.gradle.org/current/userguide/organizing_build_logic.html
130-
configurations {
131-
buildTools
132-
}
133-
dependencies {
134-
buildTools 'de.thetaphi:forbiddenapis:2.0'
135-
buildTools 'org.apache.rat:apache-rat:0.11'
136-
}
137126
}
138127

139128
// Ensure similar tasks in dependent projects run first. The projectsEvaluated here is

Diff for: buildSrc/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ dependencies {
6363
compile 'com.perforce:p4java:2012.3.551082' // THIS IS SUPPOSED TO BE OPTIONAL IN THE FUTURE....
6464
compile 'de.thetaphi:forbiddenapis:2.0'
6565
compile 'com.bmuschko:gradle-nexus-plugin:2.3.1'
66+
compile 'org.apache.rat:apache-rat:0.11'
6667
}
6768

6869
processResources {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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
21+
22+
import org.apache.tools.ant.BuildException
23+
import org.apache.tools.ant.BuildListener
24+
import org.apache.tools.ant.BuildLogger
25+
import org.apache.tools.ant.DefaultLogger
26+
import org.apache.tools.ant.Project
27+
import org.gradle.api.DefaultTask
28+
import org.gradle.api.GradleException
29+
import org.gradle.api.tasks.Input
30+
import org.gradle.api.tasks.Optional
31+
import org.gradle.api.tasks.TaskAction
32+
33+
import java.nio.charset.Charset
34+
35+
/**
36+
* A task which will run ant commands.
37+
*
38+
* Logging for the task is customizable for subclasses by overriding makeLogger.
39+
*/
40+
public class AntTask extends DefaultTask {
41+
42+
/**
43+
* A buffer that will contain the output of the ant code run,
44+
* if the output was not already written directly to stdout.
45+
*/
46+
public final ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream()
47+
48+
@TaskAction
49+
final void executeTask() {
50+
// capture the current loggers
51+
List<BuildLogger> savedLoggers = new ArrayList<>();
52+
for (BuildListener l : project.ant.project.getBuildListeners()) {
53+
if (l instanceof BuildLogger) {
54+
savedLoggers.add(l);
55+
}
56+
}
57+
// remove them
58+
for (BuildLogger l : savedLoggers) {
59+
project.ant.project.removeBuildListener(l)
60+
}
61+
62+
final int outputLevel = logger.isDebugEnabled() ? Project.MSG_DEBUG : Project.MSG_INFO
63+
final PrintStream stream = useStdout() ? System.out : new PrintStream(outputBuffer, true, Charset.defaultCharset().name())
64+
BuildLogger antLogger = makeLogger(stream, outputLevel)
65+
66+
// now run the command with just our logger
67+
project.ant.project.addBuildListener(antLogger)
68+
try {
69+
runAnt(project.ant)
70+
} catch (BuildException e) {
71+
// ant failed, so see if we have buffered output to emit, then rethrow the failure
72+
String buffer = outputBuffer.toString()
73+
if (buffer.isEmpty() == false) {
74+
logger.error("=== Ant output ===\n${buffer}")
75+
}
76+
throw e
77+
} finally {
78+
project.ant.project.removeBuildListener(antLogger)
79+
// add back the old loggers before returning
80+
for (BuildLogger l : savedLoggers) {
81+
project.ant.project.addBuildListener(l)
82+
}
83+
}
84+
}
85+
86+
/** Runs the doAnt closure. This can be overridden by subclasses instead of having to set a closure. */
87+
protected void runAnt(AntBuilder ant) {
88+
if (doAnt == null) {
89+
throw new GradleException("Missing doAnt for ${name}")
90+
}
91+
doAnt(ant)
92+
}
93+
94+
/** Create the logger the ant runner will use, with the given stream for error/output. */
95+
protected BuildLogger makeLogger(PrintStream stream, int outputLevel) {
96+
return new DefaultLogger(
97+
errorPrintStream: stream,
98+
outputPrintStream: stream,
99+
messageOutputLevel: outputLevel)
100+
}
101+
102+
/**
103+
* Returns true if the ant logger should write to stdout, or false if to the buffer.
104+
* The default implementation writes to the buffer when gradle info logging is disabled.
105+
*/
106+
protected boolean useStdout() {
107+
return logger.isInfoEnabled()
108+
}
109+
110+
111+
}

Diff for: buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/LicenseHeadersTask.groovy

+15-16
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,33 @@
1818
*/
1919
package org.elasticsearch.gradle.precommit
2020

21-
import java.nio.file.Files
22-
23-
import org.gradle.api.DefaultTask
21+
import org.apache.rat.anttasks.Report
22+
import org.apache.rat.anttasks.SubstringLicenseMatcher
23+
import org.apache.rat.license.SimpleLicenseFamily
24+
import org.elasticsearch.gradle.AntTask
2425
import org.gradle.api.tasks.SourceSet
25-
import org.gradle.api.tasks.TaskAction
2626

27-
import groovy.xml.NamespaceBuilder
28-
import groovy.xml.NamespaceBuilderSupport
27+
import java.nio.file.Files
2928

3029
/**
3130
* Checks files for license headers.
3231
* <p>
3332
* This is a port of the apache lucene check
3433
*/
35-
public class LicenseHeadersTask extends DefaultTask {
34+
public class LicenseHeadersTask extends AntTask {
3635

3736
LicenseHeadersTask() {
3837
description = "Checks sources for missing, incorrect, or unacceptable license headers"
38+
39+
if (ant.project.taskDefinitions.contains('ratReport') == false) {
40+
ant.project.addTaskDefinition('ratReport', Report)
41+
ant.project.addDataTypeDefinition('substringMatcher', SubstringLicenseMatcher)
42+
ant.project.addDataTypeDefinition('approvedLicense', SimpleLicenseFamily)
43+
}
3944
}
4045

41-
@TaskAction
42-
public void check() {
43-
// load rat tasks
44-
AntBuilder ant = new AntBuilder()
45-
ant.typedef(resource: "org/apache/rat/anttasks/antlib.xml",
46-
uri: "antlib:org.apache.rat.anttasks",
47-
classpath: project.configurations.buildTools.asPath)
48-
NamespaceBuilderSupport rat = NamespaceBuilder.newInstance(ant, "antlib:org.apache.rat.anttasks")
46+
@Override
47+
protected void runAnt(AntBuilder ant) {
4948

5049
// create a file for the log to go to under reports/
5150
File reportDir = new File(project.buildDir, "reports/licenseHeaders")
@@ -54,7 +53,7 @@ public class LicenseHeadersTask extends DefaultTask {
5453
Files.deleteIfExists(reportFile.toPath())
5554

5655
// run rat, going to the file
57-
rat.report(reportFile: reportFile.absolutePath, addDefaultLicenseMatchers: true) {
56+
ant.ratReport(reportFile: reportFile.absolutePath, addDefaultLicenseMatchers: true) {
5857
// checks all the java sources (allJava)
5958
for (SourceSet set : project.sourceSets) {
6059
for (File dir : set.allJava.srcDirs) {

Diff for: buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/ThirdPartyAuditTask.groovy

+39-34
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
*/
1919
package org.elasticsearch.gradle.precommit
2020

21+
import org.apache.tools.ant.DefaultLogger
22+
import org.elasticsearch.gradle.AntTask
23+
import org.gradle.api.artifacts.Configuration
24+
2125
import java.nio.file.Files
2226
import java.nio.file.FileVisitResult
2327
import java.nio.file.Path
@@ -35,7 +39,7 @@ import org.apache.tools.ant.Project
3539
/**
3640
* Basic static checking to keep tabs on third party JARs
3741
*/
38-
public class ThirdPartyAuditTask extends DefaultTask {
42+
public class ThirdPartyAuditTask extends AntTask {
3943

4044
// true to be lenient about MISSING CLASSES
4145
private boolean missingClasses;
@@ -46,6 +50,10 @@ public class ThirdPartyAuditTask extends DefaultTask {
4650
ThirdPartyAuditTask() {
4751
dependsOn(project.configurations.testCompile)
4852
description = "Checks third party JAR bytecode for missing classes, use of internal APIs, and other horrors'"
53+
54+
if (ant.project.taskDefinitions.contains('thirdPartyAudit') == false) {
55+
ant.project.addTaskDefinition('thirdPartyAudit', de.thetaphi.forbiddenapis.ant.AntTask)
56+
}
4957
}
5058

5159
/**
@@ -84,65 +92,62 @@ public class ThirdPartyAuditTask extends DefaultTask {
8492
return excludes;
8593
}
8694

87-
@TaskAction
88-
public void check() {
89-
AntBuilder ant = new AntBuilder()
95+
@Override
96+
protected BuildLogger makeLogger(PrintStream stream, int outputLevel) {
97+
return new DefaultLogger(
98+
errorPrintStream: stream,
99+
outputPrintStream: stream,
100+
// ignore passed in outputLevel for now, until we are filtering warning messages
101+
messageOutputLevel: Project.MSG_ERR)
102+
}
90103

91-
// we are noisy for many reasons, working around performance problems with forbidden-apis, dealing
92-
// with warnings about missing classes, etc. so we use our own "quiet" AntBuilder
93-
ant.project.buildListeners.each { listener ->
94-
if (listener instanceof BuildLogger) {
95-
listener.messageOutputLevel = Project.MSG_ERR;
96-
}
97-
};
98-
104+
@Override
105+
protected void runAnt(AntBuilder ant) {
99106
// we only want third party dependencies.
100-
FileCollection jars = project.configurations.testCompile.fileCollection({ dependency ->
107+
FileCollection jars = project.configurations.testCompile.fileCollection({ dependency ->
101108
dependency.group.startsWith("org.elasticsearch") == false
102109
})
103-
110+
104111
// we don't want provided dependencies, which we have already scanned. e.g. don't
105112
// scan ES core's dependencies for every single plugin
106-
try {
107-
jars -= project.configurations.getByName("provided")
108-
} catch (UnknownConfigurationException ignored) {}
109-
113+
Configuration provided = project.configurations.findByName('provided')
114+
if (provided != null) {
115+
jars -= provided
116+
}
117+
110118
// no dependencies matched, we are done
111119
if (jars.isEmpty()) {
112120
return;
113121
}
114-
115-
ant.taskdef(name: "thirdPartyAudit",
116-
classname: "de.thetaphi.forbiddenapis.ant.AntTask",
117-
classpath: project.configurations.buildTools.asPath)
118-
122+
123+
119124
// print which jars we are going to scan, always
120125
// this is not the time to try to be succinct! Forbidden will print plenty on its own!
121126
Set<String> names = new HashSet<>()
122127
for (File jar : jars) {
123128
names.add(jar.getName())
124129
}
125130
logger.error("[thirdPartyAudit] Scanning: " + names)
126-
131+
127132
// warn that classes are missing
128133
// TODO: move these to excludes list!
129134
if (missingClasses) {
130135
logger.warn("[thirdPartyAudit] WARNING: CLASSES ARE MISSING! Expect NoClassDefFoundError in bug reports from users!")
131136
}
132-
133-
// TODO: forbidden-apis + zipfileset gives O(n^2) behavior unless we dump to a tmpdir first,
137+
138+
// TODO: forbidden-apis + zipfileset gives O(n^2) behavior unless we dump to a tmpdir first,
134139
// and then remove our temp dir afterwards. don't complain: try it yourself.
135140
// we don't use gradle temp dir handling, just google it, or try it yourself.
136-
141+
137142
File tmpDir = new File(project.buildDir, 'tmp/thirdPartyAudit')
138-
143+
139144
// clean up any previous mess (if we failed), then unzip everything to one directory
140145
ant.delete(dir: tmpDir.getAbsolutePath())
141146
tmpDir.mkdirs()
142147
for (File jar : jars) {
143148
ant.unzip(src: jar.getAbsolutePath(), dest: tmpDir.getAbsolutePath())
144149
}
145-
150+
146151
// convert exclusion class names to binary file names
147152
String[] excludedFiles = new String[excludes.length];
148153
for (int i = 0; i < excludes.length; i++) {
@@ -152,12 +157,12 @@ public class ThirdPartyAuditTask extends DefaultTask {
152157
throw new IllegalStateException("bogus thirdPartyAudit exclusion: '" + excludes[i] + "', not found in any dependency")
153158
}
154159
}
155-
160+
156161
// jarHellReprise
157162
checkSheistyClasses(tmpDir.toPath(), new HashSet<>(Arrays.asList(excludedFiles)));
158-
159-
ant.thirdPartyAudit(internalRuntimeForbidden: true,
160-
failOnUnsupportedJava: false,
163+
164+
ant.thirdPartyAudit(internalRuntimeForbidden: true,
165+
failOnUnsupportedJava: false,
161166
failOnMissingClasses: !missingClasses,
162167
classpath: project.configurations.testCompile.asPath) {
163168
fileset(dir: tmpDir, excludes: excludedFiles.join(','))
@@ -169,7 +174,7 @@ public class ThirdPartyAuditTask extends DefaultTask {
169174
/**
170175
* check for sheisty classes: if they also exist in the extensions classloader, its jar hell with the jdk!
171176
*/
172-
private void checkSheistyClasses(Path root, Set<String> excluded) {
177+
protected void checkSheistyClasses(Path root, Set<String> excluded) {
173178
// system.parent = extensions loader.
174179
// note: for jigsaw, this evilness will need modifications (e.g. use jrt filesystem!).
175180
// but groovy/gradle needs to work at all first!

0 commit comments

Comments
 (0)