diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index dd32e14e70..0e7842efcc 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -1,10 +1,13 @@
name: Java CI
-on: [push]
+on:
+ - pull_request
+ - push
+ branches:
+ - master
jobs:
build:
-
runs-on: ubuntu-latest
steps:
diff --git a/pom.xml b/pom.xml
index 0bac52df1b..eee04b59f3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,10 +64,6 @@
1.13
1.24
3.9.1
- 2.34
- 2.67
- 2.19
- 2.32
1.19
1.5
2.9.4
@@ -124,9 +120,11 @@
- org.jenkins-ci.plugins
- credentials
- 2.1.16
+ io.jenkins.tools.bom
+ bom-2.138.x
+ 3
+ import
+ pom
com.google.code.gson
@@ -148,16 +146,6 @@
mailer
1.20
-
- org.jenkins-ci.plugins
- scm-api
- 2.2.7
-
-
- org.jenkins-ci.plugins.workflow
- workflow-scm-step
- 2.6
-
commons-net
commons-net
@@ -168,17 +156,16 @@
commons-lang3
${commons.lang.version}
-
- org.jenkins-ci.plugins
- ssh-credentials
- 1.13
-
org.slf4j
slf4j-api
${slf4j.version}
-
+
+ org.jenkins-ci.plugins
+ scm-api
+ 2.6.3
+
@@ -289,32 +276,26 @@
org.jenkins-ci.plugins
script-security
- ${script-security.version}
org.jenkins-ci.plugins.workflow
workflow-api
- ${workflow-api.version}
org.jenkins-ci.plugins.workflow
workflow-cps
- ${workflow-cps.version}
org.jenkins-ci.plugins.workflow
workflow-step-api
- ${workflow-step.version}
org.jenkins-ci.plugins.workflow
workflow-job
- ${workflow-job.version}
org.jenkins-ci.plugins
structs
- ${structs.version}
org.jenkins-ci.plugins
@@ -326,12 +307,10 @@
org.jenkins-ci.plugins
credentials
- ${credentials.version}
org.jenkins-ci.plugins
apache-httpcomponents-client-4-api
- ${httpcomponents-client.version}
@@ -519,7 +498,6 @@
org.jenkins-ci.plugins
cloudbees-folder
- ${folder-plugin.version}
test
@@ -534,14 +512,12 @@
org.jenkins-ci.plugins.workflow
workflow-durable-task-step
- 2.15
test
org.jenkins-ci.plugins.workflow
workflow-basic-steps
- 2.7
test
@@ -575,7 +551,7 @@
org.jenkins-ci.plugins
scm-api
- 2.2.7
+ 2.6.3
test-jar
test
@@ -584,7 +560,6 @@
org.jenkins-ci.plugins.workflow
workflow-support
- 3.2
test
diff --git a/src/main/java/io/jenkins/plugins/analysis/core/model/ReportScanningTool.java b/src/main/java/io/jenkins/plugins/analysis/core/model/ReportScanningTool.java
index d7a1a46c05..23e4b59815 100644
--- a/src/main/java/io/jenkins/plugins/analysis/core/model/ReportScanningTool.java
+++ b/src/main/java/io/jenkins/plugins/analysis/core/model/ReportScanningTool.java
@@ -8,6 +8,7 @@
import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.ParsingCanceledException;
import edu.hm.hafner.analysis.ParsingException;
+import edu.hm.hafner.analysis.ReaderFactory;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.util.Ensure;
import edu.umd.cs.findbugs.annotations.Nullable;
@@ -117,9 +118,15 @@ public String getReportEncoding() {
@Override
public Report scan(final Run, ?> run, final FilePath workspace, final Charset sourceCodeEncoding,
final LogHandler logger) {
+ return scan(run, workspace, sourceCodeEncoding, logger, new ConsoleLogReaderFactory(run));
+ }
+
+ @Override
+ public Report scan(final Run, ?> run, final FilePath workspace, final Charset sourceCodeEncoding,
+ final LogHandler logger, final ReaderFactory readerFactory) {
String actualPattern = getActualPattern();
if (StringUtils.isBlank(actualPattern)) {
- return scanInConsoleLog(workspace, run, logger);
+ return scanInConsoleLog(workspace, run, logger, readerFactory);
}
else {
if (StringUtils.isBlank(getPattern())) {
@@ -159,7 +166,8 @@ private Report scanInWorkspace(final FilePath workspace, final String expandedPa
}
}
- private Report scanInConsoleLog(final FilePath workspace, final Run, ?> run, final LogHandler logger) {
+ private Report scanInConsoleLog(final FilePath workspace, final Run, ?> run, final LogHandler logger,
+ final ReaderFactory readerFactory) {
Ensure.that(getDescriptor().canScanConsoleLog()).isTrue(
"Static analysis tool %s cannot scan console log output, please define a file pattern",
getActualName());
@@ -170,7 +178,7 @@ private Report scanInConsoleLog(final FilePath workspace, final Run, ?> run, f
consoleReport.logInfo("Parsing console log (workspace: '%s')", workspace);
logger.log(consoleReport);
- Report report = createParser().parse(new ConsoleLogReaderFactory(run));
+ Report report = createParser().parse(readerFactory);
if (getDescriptor().isConsoleLog()) {
report.stream().filter(issue -> !issue.hasFileName())
diff --git a/src/main/java/io/jenkins/plugins/analysis/core/model/Tool.java b/src/main/java/io/jenkins/plugins/analysis/core/model/Tool.java
index f5852bdb84..80d5dca0ab 100644
--- a/src/main/java/io/jenkins/plugins/analysis/core/model/Tool.java
+++ b/src/main/java/io/jenkins/plugins/analysis/core/model/Tool.java
@@ -7,6 +7,7 @@
import edu.hm.hafner.analysis.ParsingCanceledException;
import edu.hm.hafner.analysis.ParsingException;
+import edu.hm.hafner.analysis.ReaderFactory;
import edu.hm.hafner.analysis.Report;
import org.kohsuke.stapler.DataBoundSetter;
@@ -139,6 +140,31 @@ public ToolDescriptor getDescriptor() {
public abstract Report scan(Run, ?> run, FilePath workspace, Charset sourceCodeEncoding, LogHandler logger)
throws ParsingException, ParsingCanceledException;
+ /**
+ * Scans the results of a build for issues. This method is invoked on Jenkins master. I.e., if a tool wants to
+ * process some build results it is required to run a {@link MasterToSlaveCallable}.
+ *
+ * @param run
+ * the build
+ * @param workspace
+ * the workspace of the build
+ * @param sourceCodeEncoding
+ * the encoding to use to read source files
+ * @param logger
+ * the logger
+ *
+ * @return the created report
+ * @throws ParsingException
+ * Signals that during parsing a non recoverable error has been occurred
+ * @throws ParsingCanceledException
+ * Signals that the parsing has been aborted by the user
+ */
+ public Report scan(final Run, ?> run, final FilePath workspace, final Charset sourceCodeEncoding, final LogHandler logger,
+ final ReaderFactory factory)
+ throws ParsingException, ParsingCanceledException {
+ return scan(run, workspace, sourceCodeEncoding, logger);
+ }
+
/** Descriptor for {@link Tool}. **/
public abstract static class ToolDescriptor extends Descriptor {
private final String id;
diff --git a/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisExecution.java b/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisExecution.java
index a5bfddadb0..c11ab62e4a 100644
--- a/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisExecution.java
+++ b/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisExecution.java
@@ -1,18 +1,34 @@
package io.jenkins.plugins.analysis.core.steps;
import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Reader;
import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Optional;
+import edu.hm.hafner.analysis.ParsingException;
+import edu.hm.hafner.analysis.ReaderFactory;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+import org.jenkinsci.plugins.workflow.graph.FlowNode;
+import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator;
+import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution;
import hudson.FilePath;
+import hudson.console.ConsoleLogFilter;
import hudson.model.Computer;
import hudson.model.Run;
import hudson.model.TaskListener;
+import hudson.remoting.TeeOutputStream;
import hudson.remoting.VirtualChannel;
+import io.jenkins.plugins.analysis.core.model.Tool;
import io.jenkins.plugins.analysis.core.util.ModelValidation;
+import io.jenkins.plugins.analysis.core.util.PipelineResultHandler;
/**
* Base class for static analysis step executions. Provides several helper methods to obtain the defined {@link
@@ -30,15 +46,6 @@ abstract class AnalysisExecution extends SynchronousNonBlockingStepExecution<
super(context);
}
- /**
- * Returns the associated pipeline run.
- *
- * @return the run
- * @throws IOException
- * if the run could be be resolved
- * @throws InterruptedException
- * if the user canceled the run
- */
protected Run, ?> getRun() throws IOException, InterruptedException {
Run, ?> run = getContext().get(Run.class);
@@ -48,16 +55,17 @@ abstract class AnalysisExecution extends SynchronousNonBlockingStepExecution<
return run;
}
+
+ protected FlowNode getFlowNode() throws IOException, InterruptedException {
+ FlowNode flowNode = getContext().get(FlowNode.class);
+
+ if (flowNode == null) {
+ throw new IOException("Can't resolve FlowNode for " + toString());
+ }
+
+ return flowNode;
+ }
- /**
- * Returns a {@link VirtualChannel} to the agent where this step has been executed.
- *
- * @return the channel
- * @throws IOException
- * if the computer could be be resolved
- * @throws InterruptedException
- * if the user canceled the run
- */
protected Optional getChannel() throws IOException, InterruptedException {
Computer computer = getContext().get(Computer.class);
@@ -68,28 +76,10 @@ protected Optional getChannel() throws IOException, InterruptedE
return Optional.ofNullable(computer.getChannel());
}
- /**
- * Returns Jenkins' build folder.
- *
- * @return the build folder
- * @throws IOException
- * if the build folder could be be resolved
- * @throws InterruptedException
- * if the user canceled the run
- */
protected FilePath getBuildFolder() throws IOException, InterruptedException {
return new FilePath(getRun().getRootDir());
}
- /**
- * Returns the workspace for this job.
- *
- * @return the workspace
- * @throws IOException
- * if the workspace could not be resolved
- * @throws InterruptedException
- * if the user canceled the execution
- */
protected FilePath getWorkspace() throws IOException, InterruptedException {
FilePath workspace = getContext().get(FilePath.class);
@@ -97,16 +87,11 @@ protected FilePath getWorkspace() throws IOException, InterruptedException {
throw new IOException("No workspace available for " + toString());
}
+ workspace.mkdirs();
+
return workspace;
}
- /**
- * Returns the {@link TaskListener} for this execution.
- *
- * @return the task listener (or a silent listener if no task listener could be found)
- * @throws InterruptedException
- * if the user canceled the execution
- */
protected TaskListener getTaskListener() throws InterruptedException {
try {
TaskListener listener = getContext().get(TaskListener.class);
@@ -120,17 +105,121 @@ protected TaskListener getTaskListener() throws InterruptedException {
return TaskListener.NULL;
}
- /**
- * Returns the default charset for the specified encoding string. If the default encoding is empty or {@code null},
- * or if the charset is not valid then the default encoding of the platform is returned.
- *
- * @param charset
- * identifier of the character set
- *
- * @return the default charset for the specified encoding string
- */
protected Charset getCharset(final String charset) {
return new ModelValidation().getCharset(charset);
}
+ /**
+ * Splits off a second branch of the console log into a temporary file that can be parsed by a {@link Tool} parser
+ * later on.
+ */
+ static class LogSplitter extends TaskListenerDecorator {
+ private static final long serialVersionUID = -4867121027779734489L;
+
+ private final String log;
+
+ LogSplitter(final String fileName) {
+ log = fileName;
+ }
+
+ @Override @NonNull
+ public OutputStream decorate(@NonNull final OutputStream logger) throws IOException {
+ return new TeeOutputStream(logger, Files.newOutputStream(Paths.get(log)));
+ }
+ }
+
+ /**
+ * Splits off a second branch of the console log into a temporary file that can be parsed by a {@link Tool} parser
+ * later on.
+ */
+ static class ConsoleLogSplitter extends ConsoleLogFilter {
+ private static final long serialVersionUID = -4867121027779734489L;
+
+ private final String log;
+
+ ConsoleLogSplitter(final String fileName) {
+ log = fileName;
+ }
+
+ @Override
+ public OutputStream decorateLogger(final Run build, final OutputStream logger)
+ throws IOException, InterruptedException {
+ return super.decorateLogger(build, logger);
+ }
+ }
+
+ /**
+ * Callback that runs after the body of this step has been executed. This callback will record the issues of the
+ * console log of this block.
+ */
+ static class RecordIssuesCallback extends BodyExecutionCallback {
+ private static final long serialVersionUID = -2269253566145222283L;
+
+ private final IssuesRecorder recorder;
+ private final String consoleLogFileName;
+
+ RecordIssuesCallback(final IssuesRecorder recorder,
+ final String consoleLogFileName) {
+ this.recorder = recorder;
+ this.consoleLogFileName = consoleLogFileName;
+ }
+
+ @Override
+ public void onSuccess(final StepContext context, final Object result) {
+ ContextFacade contextFacade = new ContextFacade(context);
+ BlockOutputReaderFactory readerFactory = new BlockOutputReaderFactory(Paths.get(consoleLogFileName),
+ contextFacade.getCharset());
+ System.out.println("==================");
+ readerFactory.readStream().forEach(line -> System.out.format(">>> %s\n", line));
+ System.out.println("==================");
+ RecordIssuesRunner runner = new RecordIssuesRunner();
+ runner.run(recorder, contextFacade, readerFactory);
+ }
+
+ @Override
+ public void onFailure(final StepContext context, final Throwable t) {
+ // silently ignore
+ }
+ }
+
+ static class RecordIssuesRunner {
+ void run(final IssuesRecorder recorder, final ContextFacade context, final ReaderFactory readerFactory) {
+ try {
+ recorder.perform(context.getRun(), context.getWorkspace(), context.getTaskListener(),
+ new PipelineResultHandler(context.getRun(),
+ context.getFlowNode()), readerFactory);
+ }
+ catch (IOException | InterruptedException exception) {
+ // silently ignore
+ }
+ }
+ }
+
+ /**
+ * Provides a reader factory for the portion of the console log of a script block.
+ */
+ private static class BlockOutputReaderFactory extends ReaderFactory {
+ private final Path log;
+
+ BlockOutputReaderFactory(final Path log, final Charset charset) {
+ super(charset);
+
+ this.log = log;
+ }
+
+ @Override
+ public String getFileName() {
+ return "block-console.log";
+ }
+
+ @Override
+ public Reader create() {
+ try {
+ return Files.newBufferedReader(log);
+ }
+ catch (IOException e) {
+ throw new ParsingException(e);
+ }
+ }
+ }
}
diff --git a/src/main/java/io/jenkins/plugins/analysis/core/steps/ContextFacade.java b/src/main/java/io/jenkins/plugins/analysis/core/steps/ContextFacade.java
new file mode 100644
index 0000000000..af4fd8edde
--- /dev/null
+++ b/src/main/java/io/jenkins/plugins/analysis/core/steps/ContextFacade.java
@@ -0,0 +1,151 @@
+package io.jenkins.plugins.analysis.core.steps;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+import org.jenkinsci.plugins.workflow.graph.FlowNode;
+import org.jenkinsci.plugins.workflow.steps.StepContext;
+import hudson.FilePath;
+import hudson.model.Computer;
+import hudson.model.Run;
+import hudson.model.TaskListener;
+import hudson.remoting.VirtualChannel;
+
+/**
+ * Provides access to all required instances on the {@link StepContext}.
+ *
+ * @author Ullrich Hafner
+ */
+public class ContextFacade {
+ private final StepContext context;
+
+ ContextFacade(final StepContext context) {
+ this.context = context;
+ }
+
+ StepContext getContext() {
+ return context;
+ }
+
+ /**
+ * Returns the associated pipeline run.
+ *
+ * @return the run
+ * @throws IOException
+ * if the run could be be resolved
+ * @throws InterruptedException
+ * if the user canceled the build
+ */
+ protected Run, ?> getRun() throws IOException, InterruptedException {
+ Run, ?> run = getContext().get(Run.class);
+
+ if (run == null) {
+ throw new IOException("Can't resolve Run for " + toString());
+ }
+
+ return run;
+ }
+
+ /**
+ * Returns the associated pipeline {@link FlowNode}.
+ *
+ * @return the flow node
+ * @throws IOException
+ * if the flow node could be be resolved
+ * @throws InterruptedException
+ * if the user canceled the build
+ */
+ protected FlowNode getFlowNode() throws IOException, InterruptedException {
+ FlowNode flowNode = getContext().get(FlowNode.class);
+
+ if (flowNode == null) {
+ throw new IOException("Can't resolve FlowNode for " + toString());
+ }
+
+ return flowNode;
+ }
+
+ /**
+ * Returns a {@link VirtualChannel} to the agent where this step has been executed.
+ *
+ * @return the channel
+ * @throws IOException
+ * if the computer could be be resolved
+ * @throws InterruptedException
+ * if the user canceled the build
+ */
+ protected Optional getChannel() throws IOException, InterruptedException {
+ Computer computer = getContext().get(Computer.class);
+
+ if (computer == null) {
+ return Optional.empty();
+ }
+
+ return Optional.ofNullable(computer.getChannel());
+ }
+
+ /**
+ * Returns Jenkins' build folder.
+ *
+ * @return the build folder
+ * @throws IOException
+ * if the build folder could be be resolved
+ * @throws InterruptedException
+ * if the user canceled the build
+ */
+ protected FilePath getBuildFolder() throws IOException, InterruptedException {
+ return new FilePath(getRun().getRootDir());
+ }
+
+ /**
+ * Returns the workspace for this job.
+ *
+ * @return the workspace
+ * @throws IOException
+ * if the workspace could not be resolved
+ * @throws InterruptedException
+ * if the user canceled the execution
+ */
+ protected FilePath getWorkspace() throws IOException, InterruptedException {
+ FilePath workspace = getContext().get(FilePath.class);
+
+ if (workspace == null) {
+ throw new IOException("No workspace available for " + toString());
+ }
+
+ workspace.mkdirs();
+
+ return workspace;
+ }
+
+ /**
+ * Returns the {@link TaskListener} for this execution.
+ *
+ * @return the task listener (or a silent listener if no task listener could be found)
+ * @throws InterruptedException
+ * if the user canceled the execution
+ */
+ protected TaskListener getTaskListener() throws InterruptedException {
+ try {
+ TaskListener listener = getContext().get(TaskListener.class);
+ if (listener != null) {
+ return listener;
+ }
+ }
+ catch (IOException ignored) {
+ // ignore
+ }
+ return TaskListener.NULL;
+ }
+
+ Charset getCharset() {
+ try {
+ return getRun().getCharset();
+ }
+ catch (IOException | InterruptedException ignored) {
+ return StandardCharsets.UTF_8;
+ }
+ }
+}
diff --git a/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java b/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java
index e331d9e6f4..ee56799454 100644
--- a/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java
+++ b/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java
@@ -2,6 +2,7 @@
package io.jenkins.plugins.analysis.core.steps;
import java.io.IOException;
+import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
@@ -10,6 +11,7 @@
import org.apache.commons.lang3.StringUtils;
+import edu.hm.hafner.analysis.ReaderFactory;
import edu.hm.hafner.analysis.Severity;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
@@ -46,6 +48,7 @@
import io.jenkins.plugins.analysis.core.model.Tool;
import io.jenkins.plugins.analysis.core.steps.IssuesScanner.BlameMode;
import io.jenkins.plugins.analysis.core.steps.IssuesScanner.ForensicsMode;
+import io.jenkins.plugins.analysis.core.util.ConsoleLogReaderFactory;
import io.jenkins.plugins.analysis.core.util.HealthDescriptor;
import io.jenkins.plugins.analysis.core.util.LogHandler;
import io.jenkins.plugins.analysis.core.util.ModelValidation;
@@ -75,7 +78,9 @@
* @author Ullrich Hafner
*/
@SuppressWarnings({"PMD.ExcessivePublicCount", "PMD.ExcessiveClassLength", "PMD.ExcessiveImports", "PMD.TooManyFields", "PMD.DataClass", "ClassDataAbstractionCoupling", "ClassFanOutComplexity"})
-public class IssuesRecorder extends Recorder {
+public class IssuesRecorder extends Recorder implements Serializable {
+ private static final long serialVersionUID = -5129133484590854697L;
+
static final String NO_REFERENCE_JOB = "-";
static final String DEFAULT_ID = "analysis";
@@ -524,7 +529,7 @@ public boolean perform(final AbstractBuild, ?> build, final Launcher launcher,
throw new IOException("No workspace found for " + build);
}
- perform(build, workspace, listener, new RunResultHandler(build));
+ perform(build, workspace, listener, new RunResultHandler(build), new ConsoleLogReaderFactory(build));
return true;
}
@@ -534,10 +539,10 @@ public boolean perform(final AbstractBuild, ?> build, final Launcher launcher,
* Pipeline-specific behavior.
*/
void perform(final Run, ?> run, final FilePath workspace, final TaskListener listener,
- final StageResultHandler statusHandler) throws InterruptedException, IOException {
+ final StageResultHandler statusHandler, final ReaderFactory readerFactory) throws InterruptedException, IOException {
Result overallResult = run.getResult();
if (isEnabledForFailure || overallResult == null || overallResult.isBetterOrEqualTo(Result.UNSTABLE)) {
- record(run, workspace, listener, statusHandler);
+ record(run, workspace, listener, statusHandler, readerFactory);
}
else {
LogHandler logHandler = new LogHandler(listener, createLoggerPrefix());
@@ -550,12 +555,12 @@ private String createLoggerPrefix() {
}
private void record(final Run, ?> run, final FilePath workspace, final TaskListener listener,
- final StageResultHandler statusHandler)
+ final StageResultHandler statusHandler, final ReaderFactory readerFactory)
throws IOException, InterruptedException {
if (isAggregatingResults && analysisTools.size() > 1) {
AnnotatedReport totalIssues = new AnnotatedReport(StringUtils.defaultIfEmpty(id, DEFAULT_ID));
for (Tool tool : analysisTools) {
- totalIssues.add(scanWithTool(run, workspace, listener, tool), tool.getActualId());
+ totalIssues.add(scanWithTool(run, workspace, listener, tool, readerFactory), tool.getActualId());
}
String toolName = StringUtils.defaultIfEmpty(getName(), Messages.Tool_Default_Name());
publishResult(run, listener, toolName, totalIssues, toolName, statusHandler);
@@ -567,7 +572,7 @@ private void record(final Run, ?> run, final FilePath workspace, final TaskLis
report.logInfo("Ignoring 'aggregatingResults' and ID '%s' since only a single tool is defined.",
id);
}
- report.add(scanWithTool(run, workspace, listener, tool));
+ report.add(scanWithTool(run, workspace, listener, tool, readerFactory));
if (StringUtils.isNotBlank(id) || StringUtils.isNotBlank(name)) {
report.logInfo("Ignoring name='%s' and id='%s' when publishing non-aggregating reports",
name, id);
@@ -596,10 +601,10 @@ private String getReportName(final Tool tool) {
}
private AnnotatedReport scanWithTool(final Run, ?> run, final FilePath workspace, final TaskListener listener,
- final Tool tool) throws IOException, InterruptedException {
+ final Tool tool, final ReaderFactory readerFactory) throws IOException, InterruptedException {
IssuesScanner issuesScanner = new IssuesScanner(tool, getFilters(), getSourceCodeCharset(), workspace, run,
new FilePath(run.getRootDir()), listener, isBlameDisabled ? BlameMode.DISABLED : BlameMode.ENABLED,
- isForensicsDisabled ? ForensicsMode.DISABLED : ForensicsMode.ENABLED);
+ isForensicsDisabled ? ForensicsMode.DISABLED : ForensicsMode.ENABLED, readerFactory);
return issuesScanner.scan();
}
diff --git a/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java b/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java
index 0a4f0c2a75..962a0733e1 100644
--- a/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java
+++ b/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java
@@ -20,6 +20,7 @@
import edu.hm.hafner.analysis.ModuleDetector.FileSystem;
import edu.hm.hafner.analysis.ModuleResolver;
import edu.hm.hafner.analysis.PackageNameResolver;
+import edu.hm.hafner.analysis.ReaderFactory;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Report.IssueFilterBuilder;
@@ -66,6 +67,7 @@ class IssuesScanner {
private final TaskListener listener;
private final BlameMode blameMode;
private final ForensicsMode forensicsMode;
+ private final ReaderFactory readerFactory;
enum BlameMode {
ENABLED, DISABLED
@@ -78,7 +80,8 @@ enum ForensicsMode {
@SuppressWarnings("checkstyle:ParameterNumber")
IssuesScanner(final Tool tool, final List filters, final Charset sourceCodeEncoding,
final FilePath workspace, final Run, ?> run, final FilePath jenkinsRootDir, final TaskListener listener,
- final BlameMode blameMode, final ForensicsMode forensicsMode) {
+ final BlameMode blameMode, final ForensicsMode forensicsMode,
+ final ReaderFactory readerFactory) {
this.filters = new ArrayList<>(filters);
this.sourceCodeEncoding = sourceCodeEncoding;
this.tool = tool;
@@ -88,11 +91,12 @@ enum ForensicsMode {
this.listener = listener;
this.blameMode = blameMode;
this.forensicsMode = forensicsMode;
+ this.readerFactory = readerFactory;
}
public AnnotatedReport scan() throws IOException, InterruptedException {
LogHandler logger = new LogHandler(listener, tool.getActualName());
- Report report = tool.scan(run, workspace, sourceCodeEncoding, logger);
+ Report report = tool.scan(run, workspace, sourceCodeEncoding, logger, readerFactory);
if (tool.getDescriptor().isPostProcessingEnabled()) {
return postProcess(report, logger);
diff --git a/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java b/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java
index 5bddf249ef..a2faa5e119 100644
--- a/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java
+++ b/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java
@@ -2,6 +2,8 @@
import java.io.IOException;
import java.io.Serializable;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -33,12 +35,11 @@
import io.jenkins.plugins.analysis.core.model.ResultAction;
import io.jenkins.plugins.analysis.core.model.StaticAnalysisLabelProvider;
import io.jenkins.plugins.analysis.core.model.Tool;
-import io.jenkins.plugins.analysis.core.util.PipelineResultHandler;
+import io.jenkins.plugins.analysis.core.util.ConsoleLogReaderFactory;
import io.jenkins.plugins.analysis.core.util.QualityGate;
import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult;
import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType;
import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator;
-import io.jenkins.plugins.analysis.core.util.StageResultHandler;
import io.jenkins.plugins.analysis.core.util.TrendChartType;
/**
@@ -923,6 +924,7 @@ static class Execution extends AnalysisExecution {
Execution(@NonNull final StepContext context, final RecordIssuesStep step) {
super(context);
+
this.step = step;
}
@@ -948,15 +950,23 @@ protected Void run() throws IOException, InterruptedException {
recorder.setFailOnError(step.getFailOnError());
recorder.setTrendChartType(step.trendChartType);
- StageResultHandler statusHandler = new PipelineResultHandler(getRun(),
- getContext().get(FlowNode.class));
+ if (getContext().hasBody()) {
+ Path blockLog = Files.createTempFile("warnings-ng", "console-log");
+
+ getContext().newBodyInvoker()
+// .withContext(new LogSplitter(blockLog.toString()))
+ .withContext(new ConsoleLogSplitter(blockLog.toString()))
+ .withCallback(new RecordIssuesCallback(recorder, blockLog.toString()))
+ .start();
+ }
+ else {
+ RecordIssuesRunner runner = new RecordIssuesRunner();
+
+ runner.run(recorder, new ContextFacade(getContext()), new ConsoleLogReaderFactory(getRun()));
+ }
- FilePath workspace = getWorkspace();
- workspace.mkdirs();
- recorder.perform(getRun(), workspace, getTaskListener(), statusHandler);
return null;
}
-
}
/**
@@ -980,5 +990,10 @@ public String getDisplayName() {
public Set extends Class>> getRequiredContext() {
return Sets.immutable.of(FilePath.class, FlowNode.class, Run.class, TaskListener.class).castToSet();
}
+
+ @Override
+ public boolean takesImplicitBlockArgument() {
+ return true;
+ }
}
}
diff --git a/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java b/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java
index e777a9a167..678128c162 100644
--- a/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java
+++ b/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java
@@ -26,6 +26,7 @@
import io.jenkins.plugins.analysis.core.model.Tool;
import io.jenkins.plugins.analysis.core.steps.IssuesScanner.BlameMode;
import io.jenkins.plugins.analysis.core.steps.IssuesScanner.ForensicsMode;
+import io.jenkins.plugins.analysis.core.util.ConsoleLogReaderFactory;
/**
* Scan files or the console log for issues.
@@ -164,7 +165,7 @@ protected AnnotatedReport run() throws IOException, InterruptedException, Illega
IssuesScanner issuesScanner = new IssuesScanner(tool, filters,
getCharset(sourceCodeEncoding), workspace, getRun(), new FilePath(getRun().getRootDir()), listener,
isBlameDisabled ? BlameMode.DISABLED : BlameMode.ENABLED,
- isForensicsDisabled ? ForensicsMode.DISABLED : ForensicsMode.ENABLED);
+ isForensicsDisabled ? ForensicsMode.DISABLED : ForensicsMode.ENABLED, new ConsoleLogReaderFactory(getRun()));
return issuesScanner.scan();
}
diff --git a/src/test/java/io/jenkins/plugins/analysis/core/testutil/IntegrationTest.java b/src/test/java/io/jenkins/plugins/analysis/core/testutil/IntegrationTest.java
index 779ebad532..b83fe1523a 100644
--- a/src/test/java/io/jenkins/plugins/analysis/core/testutil/IntegrationTest.java
+++ b/src/test/java/io/jenkins/plugins/analysis/core/testutil/IntegrationTest.java
@@ -848,7 +848,7 @@ protected AnalysisResult scheduleSuccessfulBuild(final ParameterizedJob, ?> jo
return action.getResult();
}
- private void logConsole(final Run, ?> run) {
+ protected void logConsole(final Run, ?> run) {
try (Reader reader = run.getLogReader()) {
try (BufferedReader bufferedReader = new BufferedReader(reader)) {
bufferedReader.lines().forEach(System.out::println);
diff --git a/src/test/java/io/jenkins/plugins/analysis/warnings/StepsITest.java b/src/test/java/io/jenkins/plugins/analysis/warnings/StepsITest.java
index 16b46438f4..b7da6d1d30 100644
--- a/src/test/java/io/jenkins/plugins/analysis/warnings/StepsITest.java
+++ b/src/test/java/io/jenkins/plugins/analysis/warnings/StepsITest.java
@@ -51,6 +51,29 @@
*/
@SuppressWarnings({"PMD.ExcessiveImports", "checkstyle:ClassDataAbstractionCoupling", "checkstyle:ClassFanOutComplexity"})
public class StepsITest extends IntegrationTestWithJenkinsPerTest {
+ @Test @org.jvnet.hudson.test.Issue("JENKINS-44450")
+ public void shouldRunClosure() {
+ createAgentWithEnabledSecurity("agent");
+
+ WorkflowJob job = createPipeline();
+ job.setDefinition(new CpsFlowDefinition("node ('agent') {\n"
+ + " stage ('Block Scoped Usage') {\n"
+ + " echo 'MediaPortal.cs(1,1): warning CS0162: Not recorded'\n"
+ + " recordIssues (tools: [msBuild(), java()], aggregatingResults: true) {"
+ + " echo 'MediaPortal.cs(2,1): warning CS0162: Recorded'\n"
+ + " sleep (time: 5, unit: 'SECONDS')\n"
+ + " echo 'MediaPortal.cs(3,1): warning CS0162: Recorded'\n"
+ + " } \n"
+ + " echo 'MediaPortal.cs(4,1): warning CS0162: Not recorded'\n"
+ + " } \n"
+ + "}", true));
+
+ AnalysisResult result = scheduleSuccessfulBuild(job);
+
+ assertThat(result).hasTotalSize(1);
+ assertThat(result.getIssues().get(0)).hasFileName("MediaPortal.cs").hasLineStart(2);
+ }
+
/** Verifies that a {@link Tool} defines a {@link Symbol}. */
@Test
public void shouldProvideSymbol() {