Skip to content

Commit

Permalink
feat: run java process from no-isolated workers
Browse files Browse the repository at this point in the history
From Gradle 6.0, we can inject `ExecOperations` to `WorkAction` instance
so we can run java process in worker thread.

refs #416
  • Loading branch information
KengoTODA committed Feb 16, 2021
1 parent b5701c0 commit b86e540
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ spotbugs {

then:
assertEquals(TaskOutcome.SUCCESS, result.task(":spotbugsMain").outcome)
assertTrue(result.output.contains("SpotBugs 4.0.0-beta4"))
assertTrue(result.output.contains("spotbugs-4.0.0-beta4.jar"))
}

def "can use toolVersion to get the SpotBugs version"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ subprojects {

then:
assertEquals(SUCCESS, result.task(":sub:spotbugsMain").outcome)
assertTrue(result.output.contains("SpotBugs 4.0.0-RC1"))
assertTrue(result.output.contains("spotbugs-4.0.0-RC1.jar"))
}

def "can use toolVersion in the subproject"() {
Expand All @@ -147,6 +147,6 @@ spotbugs {

then:
assertEquals(SUCCESS, result.task(":sub:spotbugsMain").outcome)
assertTrue(result.output.contains("SpotBugs 4.0.0-RC1"))
assertTrue(result.output.contains("spotbugs-4.0.0-RC1.jar"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ spotbugsMain {

then:
assertEquals(SUCCESS, result.task(":spotbugsMain").outcome)
assertTrue(result.getOutput().contains("Running SpotBugs by Gradle Worker..."));
assertTrue(result.output.contains("Running SpotBugs by Gradle no-isolated Worker...") || result.output.contains("Running SpotBugs by Gradle process-isolated Worker..."));
}

def "can run task by JavaExec by gradle.properties"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ dependencies {

then:
assertEquals(TaskOutcome.SUCCESS, result.task(":classes").outcome)
assertTrue(result.output.contains("SpotBugs 4.0.0-beta4"))
assertTrue(result.output.contains("spotbugs-4.0.0-beta4.jar"))
}

def "can skip analysis when no class file we have"() {
Expand Down Expand Up @@ -449,7 +449,9 @@ dependencies{
then:
result.task(":spotbugsMain").outcome == TaskOutcome.SUCCESS
result.output.contains("Applying com.h3xstream.findsecbugs.PredictableRandomDetector to Foo")
if (GradleVersion.current() >= GradleVersion.version("6.0")) {
result.output.contains("Applying com.h3xstream.findsecbugs.PredictableRandomDetector to Foo")
}
!result.output.contains("Trying to add already registered factory")
}
Expand Down Expand Up @@ -485,8 +487,10 @@ public class FooTest {
then:
result.task(":spotbugsMain").outcome == TaskOutcome.SUCCESS
result.task(":spotbugsTest").outcome == TaskOutcome.SUCCESS
result.output.contains("Applying com.h3xstream.findsecbugs.PredictableRandomDetector to Foo")
result.output.contains("Applying com.h3xstream.findsecbugs.PredictableRandomDetector to FooTest")
if (GradleVersion.current() >= GradleVersion.version("6.0")) {
result.output.contains("Applying com.h3xstream.findsecbugs.PredictableRandomDetector to Foo")
result.output.contains("Applying com.h3xstream.findsecbugs.PredictableRandomDetector to FooTest")
}
!result.output.contains("Trying to add already registered factory")
}
Expand Down
12 changes: 9 additions & 3 deletions src/main/groovy/com/github/spotbugs/snom/SpotBugsTask.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
*/
package com.github.spotbugs.snom;

import com.github.spotbugs.snom.internal.SpotBugsHtmlReport;
import com.github.spotbugs.snom.internal.SpotBugsHtmlReport
import com.github.spotbugs.snom.internal.SpotBugsRunnerForHybrid;
import com.github.spotbugs.snom.internal.SpotBugsRunnerForJavaExec;
import com.github.spotbugs.snom.internal.SpotBugsRunnerForWorker;
import com.github.spotbugs.snom.internal.SpotBugsSarifReport;
Expand Down Expand Up @@ -47,7 +48,8 @@ import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.VerificationTask;
import org.gradle.util.ClosureBackedAction;
import org.gradle.util.ClosureBackedAction
import org.gradle.util.GradleVersion;
import org.gradle.workers.WorkerExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory
Expand Down Expand Up @@ -362,8 +364,12 @@ class SpotBugsTask extends DefaultTask implements VerificationTask {
.toString() == "false") {
log.info("Running SpotBugs by JavaExec...");
new SpotBugsRunnerForJavaExec().run(this);
} else if (GradleVersion.current() >= GradleVersion.version("6.0")) {
// ExecOperations is supported from Gradle 6.0
log.info("Running SpotBugs by Gradle no-isolated Worker...");
new SpotBugsRunnerForHybrid(workerExecutor).run(this);
} else {
log.info("Running SpotBugs by Gradle Worker...");
log.info("Running SpotBugs by Gradle process-isolated Worker...");
new SpotBugsRunnerForWorker(workerExecutor).run(this);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright 2019 SpotBugs team
*
* <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.spotbugs.snom.internal;

import com.github.spotbugs.snom.SpotBugsTask;
import edu.umd.cs.findbugs.annotations.NonNull;
import groovy.lang.Closure;
import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.process.ExecOperations;
import org.gradle.process.JavaExecSpec;
import org.gradle.process.internal.ExecException;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkerExecutor;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A {@link SpotBugsRunner} implementation that runs SpotBugs process from the worker process. This
* approach enables applying benefit of both {@link org.gradle.api.Project#javaexec(Closure)} and
* Worker API: provide larger Java heap to SpotBugs process and shorten their lifecycle.
*
* @see <a href="https://github.com/spotbugs/spotbugs-gradle-plugin/issues/416">The related GitHub
* issue</a>
*/
class SpotBugsRunnerForHybrid extends SpotBugsRunner {
private final WorkerExecutor workerExecutor;

public SpotBugsRunnerForHybrid(@NonNull WorkerExecutor workerExecutor) {
this.workerExecutor = Objects.requireNonNull(workerExecutor);
}

@Override
public void run(@NotNull SpotBugsTask task) {
workerExecutor.noIsolation().submit(SpotBugsExecutor.class, configureWorkerSpec(task));
}

private Action<SpotBugsWorkParameters> configureWorkerSpec(SpotBugsTask task) {
return params -> {
List<String> args = new ArrayList<>();
args.add("-exitcode");
args.addAll(buildArguments(task));
params.getClasspath().setFrom(task.getSpotbugsClasspath());
params.getJvmArgs().set(buildJvmArguments(task));
params.getArgs().set(args);
String maxHeapSize = task.getMaxHeapSize().getOrNull();
if (maxHeapSize != null) {
params.getMaxHeapSize().set(maxHeapSize);
}
params.getIgnoreFailures().set(task.getIgnoreFailures());
params.getShowStackTraces().set(task.getShowStackTraces());
params.getReportsDir().set(task.getReportsDir());
};
}

public interface SpotBugsWorkParameters extends WorkParameters {
ConfigurableFileCollection getClasspath();

Property<String> getMaxHeapSize();

ListProperty<String> getArgs();

ListProperty<String> getJvmArgs();

Property<Boolean> getIgnoreFailures();

Property<Boolean> getShowStackTraces();

DirectoryProperty getReportsDir();
}

public abstract static class SpotBugsExecutor implements WorkAction<SpotBugsWorkParameters> {
private final Logger log = LoggerFactory.getLogger(getClass());
private final ExecOperations execOperations;

@Inject
public SpotBugsExecutor(ExecOperations execOperations) {
this.execOperations = Objects.requireNonNull(execOperations);
}

@Override
public void execute() {
// TODO print version of SpotBugs and Plugins
SpotBugsWorkParameters params = getParameters();
try {
execOperations.javaexec(configureJavaExec(params)).rethrowFailure().assertNormalExitValue();
} catch (ExecException e) {
if (params.getIgnoreFailures().getOrElse(Boolean.FALSE)) {
log.warn(
"SpotBugs reported failures",
params.getShowStackTraces().getOrElse(Boolean.FALSE) ? e : null);
} else {
String errorMessage = "Verification failed: SpotBugs execution thrown exception.";
List<String> reportPaths =
params.getReportsDir().getAsFileTree().getFiles().stream()
.map(File::toPath)
.map(Path::toUri)
.map(URI::toString)
.collect(Collectors.toList());
if (!reportPaths.isEmpty()) {
errorMessage += "See the report at: " + String.join(",", reportPaths);
}
throw new GradleException(errorMessage, e);
}
}
}

private Action<? super JavaExecSpec> configureJavaExec(SpotBugsWorkParameters params) {
return spec -> {
spec.setJvmArgs(params.getJvmArgs().get());
spec.classpath(params.getClasspath());
spec.setArgs(params.getArgs().get());
spec.setMain("edu.umd.cs.findbugs.FindBugs2");
String maxHeapSize = params.getMaxHeapSize().getOrNull();
if (maxHeapSize != null) {
spec.setMaxHeapSize(maxHeapSize);
}
};
}
}
}

0 comments on commit b86e540

Please sign in to comment.