diff --git a/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java b/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java
index 3a37fd89..743c4fbc 100644
--- a/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java
+++ b/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java
@@ -4,6 +4,9 @@
import java.io.OutputStream;
import java.nio.charset.Charset;
+import static hudson.plugins.gradle.TimestampPrefixDetector.detectTimestampPrefix;
+import static hudson.plugins.gradle.TimestampPrefixDetector.trimTimestampPrefix;
+
/**
* @author ikikko
* @see AntConsoleAnnotator
@@ -13,6 +16,8 @@ public final class GradleConsoleAnnotator extends AbstractGradleLogProcessor {
private final boolean annotateGradleOutput;
private final BuildScanLogScanner buildScanLogScanner;
+ private transient Integer timestampPrefix;
+
public GradleConsoleAnnotator(OutputStream out,
Charset charset,
boolean annotateGradleOutput,
@@ -28,7 +33,12 @@ protected void processLogLine(String line) throws IOException {
line = trimEOL(line);
if (annotateGradleOutput) {
- if (line.startsWith(":") || line.startsWith("> Task :")) { // put the annotation
+ // assumption that the timestamp prefix will be present or not for all lines
+ if (timestampPrefix == null) {
+ timestampPrefix = detectTimestampPrefix(line);
+ }
+ line = trimTimestampPrefix(timestampPrefix, line);
+ if (line.startsWith(":") || line.startsWith("> Task :")) {
new GradleTaskNote().encodeTo(out);
}
diff --git a/src/main/java/hudson/plugins/gradle/GradleOutcomeNote.java b/src/main/java/hudson/plugins/gradle/GradleOutcomeNote.java
index 133873c8..f0b1a6f0 100644
--- a/src/main/java/hudson/plugins/gradle/GradleOutcomeNote.java
+++ b/src/main/java/hudson/plugins/gradle/GradleOutcomeNote.java
@@ -31,6 +31,8 @@
import java.util.regex.Pattern;
+import static hudson.plugins.gradle.TimestampPrefixDetector.TimestampPattern;
+
/**
* Annotates the BUILD SUCCESSFUL/FAILED line of the Ant execution.
*
@@ -38,7 +40,7 @@
*/
public class GradleOutcomeNote extends ConsoleNote {
- private static final Pattern BUILD_RESULT_PATTERN = Pattern.compile("^(BUILD \\S*)");
+ private static final Pattern BUILD_RESULT_PATTERN = Pattern.compile("^(?:" + TimestampPattern + ")?(BUILD \\S*)");
public GradleOutcomeNote() {
}
@@ -49,12 +51,13 @@ public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) {
if (t == null) {
return null;
}
+ int timestampPrefix = TimestampPrefixDetector.detectTimestampPrefix(text.getText());
String buildStatus = t.group(1);
if (text.getText().contains("FAIL"))
- text.addMarkup(0, buildStatus.length(),
+ text.addMarkup(timestampPrefix, timestampPrefix + buildStatus.length(),
"", "");
if (text.getText().contains("SUCCESS"))
- text.addMarkup(0, buildStatus.length(),
+ text.addMarkup(timestampPrefix, timestampPrefix + buildStatus.length(),
"", "");
return null;
}
diff --git a/src/main/java/hudson/plugins/gradle/GradleTaskNote.java b/src/main/java/hudson/plugins/gradle/GradleTaskNote.java
index 14bbd707..0a3c6320 100644
--- a/src/main/java/hudson/plugins/gradle/GradleTaskNote.java
+++ b/src/main/java/hudson/plugins/gradle/GradleTaskNote.java
@@ -12,6 +12,8 @@
import java.util.HashSet;
import java.util.regex.Pattern;
+import static hudson.plugins.gradle.TimestampPrefixDetector.TimestampPattern;
+
public final class GradleTaskNote extends ConsoleNote {
private static final Collection progressStatuses = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
@@ -21,8 +23,8 @@ public final class GradleTaskNote extends ConsoleNote {
"NO-SOURCE"
)));
- private static final Pattern TASK_PATTERN_1 = Pattern.compile("^:([^:]\\S*)(\\s*)(\\S*)");
- private static final Pattern TASK_PATTERN_2 = Pattern.compile("^> Task :([^:]\\S*)(\\s*)(\\S*)");
+ private static final Pattern TASK_PATTERN_1 = Pattern.compile("^(?:" + TimestampPattern + ")?:([^:]\\S*)(\\s*)(\\S*)");
+ private static final Pattern TASK_PATTERN_2 = Pattern.compile("^(?:" + TimestampPattern + ")?> Task :([^:]\\S*)(\\s*)(\\S*)");
@Override
public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) {
@@ -30,6 +32,7 @@ public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) {
if (!ENABLED)
return null;
+ int timestampPrefix = TimestampPrefixDetector.detectTimestampPrefix(text.getText());
int prefixLength = 1;
MarkupText.SubText t = text.findToken(TASK_PATTERN_1);
if (t == null) {
@@ -46,13 +49,13 @@ public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) {
// annotate task and progress status
if (task != null && !task.isEmpty()) {
- t.addMarkup(1, task.length() + prefixLength, "", "");
- }
- if (progressStatus != null && !progressStatus.isEmpty()
+ t.addMarkup(timestampPrefix + 1, timestampPrefix + task.length() + prefixLength, "", "");
+ if (progressStatus != null && !progressStatus.isEmpty()
&& progressStatuses.contains(progressStatus)) {
- t.addMarkup(task.length() + delimiterSpace.length() + prefixLength,
- text.length(), "",
+ t.addMarkup(timestampPrefix + task.length() + delimiterSpace.length() + prefixLength,
+ text.length(), "",
"");
+ }
}
return null;
diff --git a/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java b/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java
new file mode 100644
index 00000000..0463ede7
--- /dev/null
+++ b/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java
@@ -0,0 +1,24 @@
+package hudson.plugins.gradle;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class TimestampPrefixDetector {
+
+ static final String TimestampPattern = "\\[\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z\\] ";
+ private static final Pattern TimestampPatternR = Pattern.compile("^(" + TimestampPattern + ").*\n?$");
+
+ static String trimTimestampPrefix(int prefix, String line) {
+ return line.substring(prefix);
+ }
+
+ static int detectTimestampPrefix(String line) {
+ Matcher matcher = TimestampPatternR.matcher(line);
+ if (matcher.matches()) {
+ return matcher.group(1).length();
+ }
+ return 0;
+ }
+
+
+}
diff --git a/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy
new file mode 100644
index 00000000..76703b2c
--- /dev/null
+++ b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy
@@ -0,0 +1,72 @@
+package hudson.plugins.gradle
+
+import hudson.plugins.timestamper.TimestamperConfig
+import hudson.slaves.DumbSlave
+import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition
+import org.jenkinsci.plugins.workflow.job.WorkflowJob
+
+class GradleConsoleAnnotatorIntegrationTest extends BaseGradleIntegrationTest {
+
+ def 'pipeline job has Gradle tasks annotated'(boolean enableTimestamper) {
+ given:
+ def gradleVersion = '8.1.1'
+ gradleInstallationRule.gradleVersion = gradleVersion
+ gradleInstallationRule.addInstallation()
+
+ DumbSlave agent = createSlave('foo')
+ def pipelineJob = j.createProject(WorkflowJob)
+
+ pipelineJob.setDefinition(new CpsFlowDefinition("""
+ stage('Build') {
+ node('foo') {
+ withGradle {
+ git branch: 'main', url: 'https://github.com/c00ler/simple-gradle-project'
+ def gradleHome = tool name: '${gradleInstallationRule.gradleVersion}', type: 'gradle'
+ if (isUnix()) {
+ sh "'\${gradleHome}/bin/gradle' clean build --no-daemon --console=plain"
+ } else {
+ bat(/"\${gradleHome}\\bin\\gradle.bat" clean build --no-daemon --console=plain/)
+ }
+ }
+ }
+ }
+""", false))
+
+ if (enableTimestamper) {
+ TimestamperConfig config = TimestamperConfig.get()
+ config.setAllPipelines(true)
+ config.save()
+ }
+
+ when:
+ def b = j.buildAndAssertSuccess(pipelineJob)
+
+ then:
+ def client = j.createWebClient()
+ def html = client.goTo(b.getUrl() + "console")
+ html.getByXPath("//b[@class='gradle-task']")*.textContent*.toString() == [
+ ' Task :clean',
+ ' Task :compileJava',
+ ' Task :processResources',
+ ' Task :classes',
+ ' Task :jar',
+ ' Task :assemble',
+ ' Task :compileTestJava',
+ ' Task :processTestResources',
+ ' Task :testClasses',
+ ' Task :test',
+ ' Task :check',
+ ' Task :build'
+ ]
+ html.getByXPath("//span[@class='gradle-task-progress-status']")*.textContent*.toString() == [
+ 'UP-TO-DATE\n',
+ 'NO-SOURCE\n',
+ 'NO-SOURCE\n'
+ ]
+ html.getByXPath("//span[@class='gradle-outcome-success']")*.textContent*.toString() == ['BUILD SUCCESSFUL']
+
+ where:
+ enableTimestamper << [ true, false ]
+ }
+
+}
diff --git a/src/test/groovy/hudson/plugins/gradle/TimestampPrefixDetectorTest.groovy b/src/test/groovy/hudson/plugins/gradle/TimestampPrefixDetectorTest.groovy
new file mode 100644
index 00000000..b0502e8c
--- /dev/null
+++ b/src/test/groovy/hudson/plugins/gradle/TimestampPrefixDetectorTest.groovy
@@ -0,0 +1,36 @@
+package hudson.plugins.gradle
+
+import spock.lang.Specification
+
+class TimestampPrefixDetectorTest extends Specification {
+
+ def "detect timestamp on line"() {
+ when:
+ def prefix = TimestampPrefixDetector.detectTimestampPrefix(line)
+
+ then:
+ prefix == expected
+
+ where:
+ line | expected
+ '[2023-12-08T10:05:56.488Z] > Task :compileJava' | 27
+ '[2023-12-08T10:05:56.488Z] > Task :compileJava\n' | 27
+ '[2023-12-08T10:05:56.488Z]> Task :compileJava' | 0
+ '[2023-12-08T10:05:56] > Task :compileJava' | 0
+ 'some message' | 0
+ }
+
+ def "trim timestamp"() {
+ when:
+ def trimmed = TimestampPrefixDetector.trimTimestampPrefix(prefix, line)
+
+ then:
+ trimmed == expected
+
+ where:
+ line | prefix | expected
+ '[2023-12-08T10:05:56.488Z] > Task :compileJava' | 27 | '> Task :compileJava'
+ '> Task :compileJava' | 0 | '> Task :compileJava'
+ }
+
+}