Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import hudson.Extension;
import hudson.model.Queue;
import hudson.model.Run;
import hudson.plugins.gradle.BuildScanPublishedListener;
import hudson.plugins.gradle.DefaultBuildScanPublishedListener;
import hudson.plugins.gradle.enriched.EnrichedSummaryConfig;
import hudson.plugins.gradle.enriched.ScanDetailService;
import hudson.remoting.Channel;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator;
Expand All @@ -13,6 +17,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -39,23 +44,52 @@ public TaskListenerDecorator of(@Nonnull FlowExecutionOwner owner) {
return null;
}

@SuppressWarnings("rawtypes")
public static class BuildScanDetectionTaskListenerDecorator extends TaskListenerDecorator implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;

private final transient Run run;
// Survives serialization to an agent: with DurableTaskStep.USE_WATCHING the agent decorates its own output
private final BuildScanPublishedListener listener;
private final String charset;

public BuildScanDetectionTaskListenerDecorator(Run run) {
this.run = run;
public BuildScanDetectionTaskListenerDecorator(Run<?, ?> run) {
this.listener = new RunBuildScanPublishedListener(run.getExternalizableId());
this.charset = run.getCharset().name();
}

@Nonnull
@Override
public OutputStream decorate(@Nonnull OutputStream logger) {
if (run != null) {
return new BuildScanDetectionLogProcessor(logger, run);
return new BuildScanDetectionLogProcessor(logger, Charset.forName(charset), listener);
}
}

/**
* Attaches detected Build Scans to the run. When sent to an agent, it is replaced by a remoting proxy,
* so Build Scans detected on the agent are still recorded on the controller.
*/
static final class RunBuildScanPublishedListener implements BuildScanPublishedListener, Serializable {
private static final long serialVersionUID = 1L;

private final String runId;

RunBuildScanPublishedListener(String runId) {
this.runId = runId;
}

@Override
public void onBuildScanPublished(String scanUrl) {
Run<?, ?> run = Run.fromExternalizableId(runId);
if (run == null) {
LOGGER.log(Level.FINE, "Run {0} not found, ignoring Build Scan {1}", new Object[]{runId, scanUrl});
return;
}
return logger;
new DefaultBuildScanPublishedListener(run, new ScanDetailService(EnrichedSummaryConfig.get()))
.onBuildScanPublished(scanUrl);
}

private Object writeReplace() {
Channel channel = Channel.current();
return channel != null ? channel.export(BuildScanPublishedListener.class, this) : this;
}
}
}
15 changes: 9 additions & 6 deletions plugin/src/main/java/hudson/plugins/gradle/util/RunUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ public static <T extends Actionable, A extends Action> A getOrCreateAction(
Class<A> actionClass,
Supplier<A> actionFactory
) {
A action = actionable.getAction(actionClass);
// Parallel pipeline branches may report their first Build Scan concurrently
synchronized (actionable) {
A action = actionable.getAction(actionClass);

if (action == null) {
action = actionFactory.get();
actionable.addAction(action);
}
if (action == null) {
action = actionFactory.get();
actionable.addAction(action);
}

return action;
return action;
}
}

public static boolean isFreestyleBuildWithGradle(@Nullable Run<?, ?> build) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,33 @@ echo "https://scans.gradle.com/s/nested1"'''
}


@Requires(value = { os.linux || os.macOs }, reason = "Uses shell commands")
def 'build scans from steps running on an agent are detected'() {
given:
withEnrichedSummaryConfig {
globalBuildScanDetection = true
}
createSlave('agent')
def pipelineJob = j.createProject(WorkflowJob)
pipelineJob.setDefinition(new CpsFlowDefinition("""
node('agent') {
sh '''echo "Publishing build scan..."
echo "https://scans.gradle.com/s/agent1"'''
sh '''echo "Publishing build scan..."
echo "https://scans.gradle.com/s/agent2"'''
}
""", false))

when:
def build = j.buildAndAssertSuccess(pipelineJob)

then:
println JenkinsRule.getLog(build)
def action = build.getAction(BuildScanAction)
action != null
action.scanUrls == ['https://scans.gradle.com/s/agent1', 'https://scans.gradle.com/s/agent2']
}

@Requires(value = { os.linux || os.macOs }, reason = "Uses shell commands")
def 'withGradle does not double-parse when global detection is enabled'() {
given:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package hudson.plugins.gradle.injection

import hudson.plugins.gradle.BaseGradleIntegrationTest
import hudson.plugins.gradle.BuildScanAction
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition
import org.jenkinsci.plugins.workflow.job.WorkflowJob
import org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep
import org.jvnet.hudson.test.JenkinsRule
import spock.lang.Requires

class BuildScanDetectionWithDurableTaskStepUseWatchingIntegrationTest extends BaseGradleIntegrationTest {

private boolean useWatching

def setup() {
// Read once in a static initializer, so a system property set by a rule may come too late
useWatching = DurableTaskStep.USE_WATCHING
DurableTaskStep.USE_WATCHING = true
}

def cleanup() {
DurableTaskStep.USE_WATCHING = useWatching
}

@Requires(value = { os.linux || os.macOs }, reason = "Uses shell commands")
def 'build scans from steps running on an agent are detected when DurableTaskStep.USE_WATCHING=true'() {
given:
withEnrichedSummaryConfig {
globalBuildScanDetection = true
}
createSlave('agent')
def pipelineJob = j.createProject(WorkflowJob)
pipelineJob.setDefinition(new CpsFlowDefinition("""
node('agent') {
sh '''echo "Publishing build scan..."
echo "https://scans.gradle.com/s/agent1"'''
sh '''echo "Publishing build scan..."
echo "https://scans.gradle.com/s/agent2"'''
}
""", false))

when:
def build = j.buildAndAssertSuccess(pipelineJob)

then:
println JenkinsRule.getLog(build)
def action = build.getAction(BuildScanAction)
action != null
action.scanUrls == ['https://scans.gradle.com/s/agent1', 'https://scans.gradle.com/s/agent2']
}
}
Loading