mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-04-25 07:37:19 -04:00
Make LoggedExec gradle task configuration cache compatible (#87621)
This changes the LoggedExec task to be configuration cache compatible. We changed the implementation to use `ExecOperations` instead of extending `Exec` task. As double checked with the Gradle team this task is not planned to be made configuration cache compatible out of the box anytime soon. This is part of the effort on https://github.com/elastic/elasticsearch/issues/57918
This commit is contained in:
parent
f99ee51c5d
commit
dbf39741a0
12 changed files with 381 additions and 127 deletions
|
@ -14,8 +14,6 @@ import org.gradle.testkit.runner.TaskOutcome
|
||||||
class InternalBwcGitPluginFuncTest extends AbstractGitAwareGradleFuncTest {
|
class InternalBwcGitPluginFuncTest extends AbstractGitAwareGradleFuncTest {
|
||||||
|
|
||||||
def setup() {
|
def setup() {
|
||||||
// using LoggedExec is not cc compatible
|
|
||||||
configurationCacheCompatible = false
|
|
||||||
internalBuild()
|
internalBuild()
|
||||||
buildFile << """
|
buildFile << """
|
||||||
import org.elasticsearch.gradle.Version;
|
import org.elasticsearch.gradle.Version;
|
||||||
|
|
|
@ -17,12 +17,11 @@ import spock.lang.Unroll
|
||||||
/*
|
/*
|
||||||
* Test is ignored on ARM since this test case tests the ability to build certain older BWC branches that we don't support on ARM
|
* Test is ignored on ARM since this test case tests the ability to build certain older BWC branches that we don't support on ARM
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@IgnoreIf({ Architecture.current() == Architecture.AARCH64 })
|
@IgnoreIf({ Architecture.current() == Architecture.AARCH64 })
|
||||||
class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleFuncTest {
|
class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleFuncTest {
|
||||||
|
|
||||||
def setup() {
|
def setup() {
|
||||||
// used LoggedExec task is not configuration cache compatible and
|
// Cannot serialize BwcSetupExtension containing project object
|
||||||
configurationCacheCompatible = false
|
configurationCacheCompatible = false
|
||||||
internalBuild()
|
internalBuild()
|
||||||
buildFile << """
|
buildFile << """
|
||||||
|
@ -119,4 +118,5 @@ class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleF
|
||||||
result.output.contains("nested folder /distribution/bwc/minor/build/bwc/checkout-8.0/" +
|
result.output.contains("nested folder /distribution/bwc/minor/build/bwc/checkout-8.0/" +
|
||||||
"distribution/archives/darwin-tar/build/install/elasticsearch-8.0.0-SNAPSHOT")
|
"distribution/archives/darwin-tar/build/install/elasticsearch-8.0.0-SNAPSHOT")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,22 +12,20 @@ import org.apache.tools.ant.taskdefs.condition.Os
|
||||||
import org.elasticsearch.gradle.LoggedExec
|
import org.elasticsearch.gradle.LoggedExec
|
||||||
import org.elasticsearch.gradle.internal.test.AntFixture
|
import org.elasticsearch.gradle.internal.test.AntFixture
|
||||||
import org.gradle.api.file.FileSystemOperations
|
import org.gradle.api.file.FileSystemOperations
|
||||||
|
import org.gradle.api.file.ProjectLayout
|
||||||
import org.gradle.api.tasks.Internal
|
import org.gradle.api.tasks.Internal
|
||||||
|
import org.gradle.process.ExecOperations
|
||||||
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AntFixtureStop extends LoggedExec implements FixtureStop {
|
abstract class AntFixtureStop extends LoggedExec implements FixtureStop {
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
AntFixture fixture
|
AntFixture fixture
|
||||||
|
|
||||||
@Internal
|
|
||||||
FileSystemOperations fileSystemOperations
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AntFixtureStop(FileSystemOperations fileSystemOperations) {
|
AntFixtureStop(ProjectLayout projectLayout, ExecOperations execOperations, FileSystemOperations fileSystemOperations) {
|
||||||
super(fileSystemOperations)
|
super(projectLayout, execOperations, fileSystemOperations)
|
||||||
this.fileSystemOperations = fileSystemOperations
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFixture(AntFixture fixture) {
|
void setFixture(AntFixture fixture) {
|
||||||
|
@ -40,10 +38,10 @@ class AntFixtureStop extends LoggedExec implements FixtureStop {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||||
executable = 'Taskkill'
|
getExecutable().set('Taskkill')
|
||||||
args('/PID', pid, '/F')
|
args('/PID', pid, '/F')
|
||||||
} else {
|
} else {
|
||||||
executable = 'kill'
|
getExecutable().set('kill')
|
||||||
args('-9', pid)
|
args('-9', pid)
|
||||||
}
|
}
|
||||||
doLast {
|
doLast {
|
||||||
|
|
|
@ -15,15 +15,12 @@ import org.elasticsearch.gradle.Version;
|
||||||
import org.gradle.api.Action;
|
import org.gradle.api.Action;
|
||||||
import org.gradle.api.GradleException;
|
import org.gradle.api.GradleException;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.Task;
|
|
||||||
import org.gradle.api.logging.LogLevel;
|
import org.gradle.api.logging.LogLevel;
|
||||||
import org.gradle.api.provider.Provider;
|
import org.gradle.api.provider.Provider;
|
||||||
import org.gradle.api.tasks.TaskProvider;
|
import org.gradle.api.tasks.TaskProvider;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -64,26 +61,21 @@ public class BwcSetupExtension {
|
||||||
return project.getTasks().register(name, LoggedExec.class, loggedExec -> {
|
return project.getTasks().register(name, LoggedExec.class, loggedExec -> {
|
||||||
loggedExec.dependsOn("checkoutBwcBranch");
|
loggedExec.dependsOn("checkoutBwcBranch");
|
||||||
loggedExec.usesService(bwcTaskThrottleProvider);
|
loggedExec.usesService(bwcTaskThrottleProvider);
|
||||||
loggedExec.setSpoolOutput(true);
|
loggedExec.getWorkingDir().set(checkoutDir.get());
|
||||||
loggedExec.setWorkingDir(checkoutDir.get());
|
|
||||||
loggedExec.doFirst(new Action<Task>() {
|
loggedExec.getEnvironment().put("JAVA_HOME", unreleasedVersionInfo.zip(checkoutDir, (version, checkoutDir) -> {
|
||||||
@Override
|
String minimumCompilerVersion = readFromFile(new File(checkoutDir, minimumCompilerVersionPath(version.version())));
|
||||||
public void execute(Task t) {
|
return getJavaHome(Integer.parseInt(minimumCompilerVersion));
|
||||||
// Execution time so that the checkouts are available
|
}));
|
||||||
String compilerVersionInfoPath = minimumCompilerVersionPath(unreleasedVersionInfo.get().version());
|
|
||||||
String minimumCompilerVersion = readFromFile(new File(checkoutDir.get(), compilerVersionInfoPath));
|
|
||||||
loggedExec.environment("JAVA_HOME", getJavaHome(Integer.parseInt(minimumCompilerVersion)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||||
loggedExec.executable("cmd");
|
loggedExec.getExecutable().set("cmd");
|
||||||
loggedExec.args("/C", "call", new File(checkoutDir.get(), "gradlew").toString());
|
loggedExec.args("/C", "call", new File(checkoutDir.get(), "gradlew").toString());
|
||||||
} else {
|
} else {
|
||||||
loggedExec.executable(new File(checkoutDir.get(), "gradlew").toString());
|
loggedExec.getExecutable().set(new File(checkoutDir.get(), "gradlew").toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedExec.args("-g", project.getGradle().getGradleUserHomeDir());
|
loggedExec.args("-g", project.getGradle().getGradleUserHomeDir().toString());
|
||||||
if (project.getGradle().getStartParameter().isOffline()) {
|
if (project.getGradle().getStartParameter().isOffline()) {
|
||||||
loggedExec.args("--offline");
|
loggedExec.args("--offline");
|
||||||
}
|
}
|
||||||
|
@ -93,8 +85,7 @@ public class BwcSetupExtension {
|
||||||
loggedExec.args("-Dorg.elasticsearch.build.cache.url=" + buildCacheUrl);
|
loggedExec.args("-Dorg.elasticsearch.build.cache.url=" + buildCacheUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedExec.args("-Dbuild.snapshot=true");
|
loggedExec.args("-Dbuild.snapshot=true", "-Dscan.tag.NESTED");
|
||||||
loggedExec.args("-Dscan.tag.NESTED");
|
|
||||||
final LogLevel logLevel = project.getGradle().getStartParameter().getLogLevel();
|
final LogLevel logLevel = project.getGradle().getStartParameter().getLogLevel();
|
||||||
List<LogLevel> nonDefaultLogLevels = Arrays.asList(LogLevel.QUIET, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG);
|
List<LogLevel> nonDefaultLogLevels = Arrays.asList(LogLevel.QUIET, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG);
|
||||||
if (nonDefaultLogLevels.contains(logLevel)) {
|
if (nonDefaultLogLevels.contains(logLevel)) {
|
||||||
|
@ -110,8 +101,7 @@ public class BwcSetupExtension {
|
||||||
if (project.getGradle().getStartParameter().isParallelProjectExecutionEnabled()) {
|
if (project.getGradle().getStartParameter().isParallelProjectExecutionEnabled()) {
|
||||||
loggedExec.args("--parallel");
|
loggedExec.args("--parallel");
|
||||||
}
|
}
|
||||||
loggedExec.setStandardOutput(new IndentingOutputStream(System.out, unreleasedVersionInfo.get().version()));
|
loggedExec.getIndentingConsoleOutput().set(unreleasedVersionInfo.map(v -> v.version().toString()));
|
||||||
loggedExec.setErrorOutput(new IndentingOutputStream(System.err, unreleasedVersionInfo.get().version()));
|
|
||||||
configAction.execute(loggedExec);
|
configAction.execute(loggedExec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -122,32 +112,6 @@ public class BwcSetupExtension {
|
||||||
: "buildSrc/" + MINIMUM_COMPILER_VERSION_PATH;
|
: "buildSrc/" + MINIMUM_COMPILER_VERSION_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class IndentingOutputStream extends OutputStream {
|
|
||||||
|
|
||||||
public final byte[] indent;
|
|
||||||
private final OutputStream delegate;
|
|
||||||
|
|
||||||
IndentingOutputStream(OutputStream delegate, Object version) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
indent = (" [" + version + "] ").getBytes(StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(int b) throws IOException {
|
|
||||||
int[] arr = { b };
|
|
||||||
write(arr, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(int[] bytes, int offset, int length) throws IOException {
|
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
|
||||||
delegate.write(bytes[i]);
|
|
||||||
if (bytes[i] == '\n') {
|
|
||||||
delegate.write(indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String readFromFile(File file) {
|
private static String readFromFile(File file) {
|
||||||
try {
|
try {
|
||||||
return FileUtils.readFileToString(file).trim();
|
return FileUtils.readFileToString(file).trim();
|
||||||
|
|
|
@ -69,29 +69,27 @@ public class InternalBwcGitPlugin implements Plugin<Project> {
|
||||||
TaskContainer tasks = project.getTasks();
|
TaskContainer tasks = project.getTasks();
|
||||||
TaskProvider<LoggedExec> createCloneTaskProvider = tasks.register("createClone", LoggedExec.class, createClone -> {
|
TaskProvider<LoggedExec> createCloneTaskProvider = tasks.register("createClone", LoggedExec.class, createClone -> {
|
||||||
createClone.onlyIf(task -> this.gitExtension.getCheckoutDir().get().exists() == false);
|
createClone.onlyIf(task -> this.gitExtension.getCheckoutDir().get().exists() == false);
|
||||||
createClone.setCommandLine(asList("git", "clone", buildLayout.getRootDirectory(), gitExtension.getCheckoutDir().get()));
|
createClone.commandLine("git", "clone", buildLayout.getRootDirectory(), gitExtension.getCheckoutDir().get());
|
||||||
});
|
});
|
||||||
|
|
||||||
ExtraPropertiesExtension extraProperties = project.getExtensions().getExtraProperties();
|
ExtraPropertiesExtension extraProperties = project.getExtensions().getExtraProperties();
|
||||||
TaskProvider<LoggedExec> findRemoteTaskProvider = tasks.register("findRemote", LoggedExec.class, findRemote -> {
|
TaskProvider<LoggedExec> findRemoteTaskProvider = tasks.register("findRemote", LoggedExec.class, findRemote -> {
|
||||||
findRemote.dependsOn(createCloneTaskProvider);
|
findRemote.dependsOn(createCloneTaskProvider);
|
||||||
// TODO Gradle should provide property based configuration here
|
findRemote.getWorkingDir().set(gitExtension.getCheckoutDir());
|
||||||
findRemote.setWorkingDir(gitExtension.getCheckoutDir().get());
|
findRemote.commandLine("git", "remote", "-v");
|
||||||
findRemote.setCommandLine(asList("git", "remote", "-v"));
|
findRemote.getCaptureOutput().set(true);
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
findRemote.doLast(t -> { extraProperties.set("remoteExists", isRemoteAvailable(remote, findRemote.getOutput())); });
|
||||||
findRemote.setStandardOutput(output);
|
|
||||||
findRemote.doLast(t -> { extraProperties.set("remoteExists", isRemoteAvailable(remote, output)); });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
TaskProvider<LoggedExec> addRemoteTaskProvider = tasks.register("addRemote", LoggedExec.class, addRemote -> {
|
TaskProvider<LoggedExec> addRemoteTaskProvider = tasks.register("addRemote", LoggedExec.class, addRemote -> {
|
||||||
addRemote.dependsOn(findRemoteTaskProvider);
|
addRemote.dependsOn(findRemoteTaskProvider);
|
||||||
addRemote.onlyIf(task -> ((boolean) extraProperties.get("remoteExists")) == false);
|
addRemote.onlyIf(task -> ((boolean) extraProperties.get("remoteExists")) == false);
|
||||||
addRemote.setWorkingDir(gitExtension.getCheckoutDir().get());
|
addRemote.getWorkingDir().set(gitExtension.getCheckoutDir().get());
|
||||||
String remoteRepo = remote.get();
|
String remoteRepo = remote.get();
|
||||||
// for testing only we can override the base remote url
|
// for testing only we can override the base remote url
|
||||||
String remoteRepoUrl = providerFactory.systemProperty("testRemoteRepo")
|
String remoteRepoUrl = providerFactory.systemProperty("testRemoteRepo")
|
||||||
.getOrElse("https://github.com/" + remoteRepo + "/elasticsearch.git");
|
.getOrElse("https://github.com/" + remoteRepo + "/elasticsearch.git");
|
||||||
addRemote.setCommandLine(asList("git", "remote", "add", remoteRepo, remoteRepoUrl));
|
addRemote.commandLine("git", "remote", "add", remoteRepo, remoteRepoUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
boolean isOffline = project.getGradle().getStartParameter().isOffline();
|
boolean isOffline = project.getGradle().getStartParameter().isOffline();
|
||||||
|
@ -107,8 +105,8 @@ public class InternalBwcGitPlugin implements Plugin<Project> {
|
||||||
});
|
});
|
||||||
fetchLatest.onlyIf(t -> isOffline == false && gitFetchLatest.get());
|
fetchLatest.onlyIf(t -> isOffline == false && gitFetchLatest.get());
|
||||||
fetchLatest.dependsOn(addRemoteTaskProvider);
|
fetchLatest.dependsOn(addRemoteTaskProvider);
|
||||||
fetchLatest.setWorkingDir(gitExtension.getCheckoutDir().get());
|
fetchLatest.getWorkingDir().set(gitExtension.getCheckoutDir().get());
|
||||||
fetchLatest.setCommandLine(asList("git", "fetch", "--all"));
|
fetchLatest.commandLine("git", "fetch", "--all");
|
||||||
});
|
});
|
||||||
|
|
||||||
String projectPath = project.getPath();
|
String projectPath = project.getPath();
|
||||||
|
@ -210,7 +208,7 @@ public class InternalBwcGitPlugin implements Plugin<Project> {
|
||||||
return os.toString().trim();
|
return os.toString().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isRemoteAvailable(Provider<String> remote, ByteArrayOutputStream output) {
|
private static boolean isRemoteAvailable(Provider<String> remote, String output) {
|
||||||
return new String(output.toByteArray()).lines().anyMatch(l -> l.contains(remote.get() + "\t"));
|
return output.lines().anyMatch(l -> l.contains(remote.get() + "\t"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,9 +241,9 @@ public class InternalDistributionBwcSetupPlugin implements Plugin<Project> {
|
||||||
return BuildParams.isCi()
|
return BuildParams.isCi()
|
||||||
&& (gitBranch == null || gitBranch.endsWith("master") == false || gitBranch.endsWith("main") == false);
|
&& (gitBranch == null || gitBranch.endsWith("master") == false || gitBranch.endsWith("main") == false);
|
||||||
});
|
});
|
||||||
c.args(projectPath.replace('/', ':') + ":" + assembleTaskName);
|
c.getArgs().add(projectPath.replace('/', ':') + ":" + assembleTaskName);
|
||||||
if (project.getGradle().getStartParameter().isBuildCacheEnabled()) {
|
if (project.getGradle().getStartParameter().isBuildCacheEnabled()) {
|
||||||
c.args("--build-cache");
|
c.getArgs().add("--build-cache");
|
||||||
}
|
}
|
||||||
c.doLast(new Action<Task>() {
|
c.doLast(new Action<Task>() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.gradle
|
||||||
|
|
||||||
|
import org.elasticsearch.gradle.fixtures.AbstractGradleFuncTest
|
||||||
|
import org.gradle.testkit.runner.TaskOutcome
|
||||||
|
import spock.lang.Ignore
|
||||||
|
import spock.lang.IgnoreIf
|
||||||
|
import spock.lang.Unroll
|
||||||
|
import spock.util.environment.OperatingSystem
|
||||||
|
|
||||||
|
@IgnoreIf({ os.isWindows() })
|
||||||
|
class LoggedExecFuncTest extends AbstractGradleFuncTest {
|
||||||
|
|
||||||
|
def setup() {
|
||||||
|
buildFile << """
|
||||||
|
// we need apply any custom plugin
|
||||||
|
// to add build-logic to the build classpath
|
||||||
|
plugins {
|
||||||
|
id 'elasticsearch.distribution-download'
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unroll
|
||||||
|
def "can configure spooling #spooling"() {
|
||||||
|
setup:
|
||||||
|
buildFile << """
|
||||||
|
import org.elasticsearch.gradle.LoggedExec
|
||||||
|
tasks.register('loggedExec', LoggedExec) {
|
||||||
|
commandLine 'ls', '-lh'
|
||||||
|
spoolOutput = $spooling
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
when:
|
||||||
|
def result = gradleRunner("loggedExec").build()
|
||||||
|
then:
|
||||||
|
result.task(':loggedExec').outcome == TaskOutcome.SUCCESS
|
||||||
|
file("build/buffered-output/loggedExec").exists() == spooling
|
||||||
|
where:
|
||||||
|
spooling << [false, true]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unroll
|
||||||
|
def "failed tasks output logged to console when spooling #spooling"() {
|
||||||
|
setup:
|
||||||
|
buildFile << """
|
||||||
|
import org.elasticsearch.gradle.LoggedExec
|
||||||
|
tasks.register('loggedExec', LoggedExec) {
|
||||||
|
commandLine 'ls', 'wtf'
|
||||||
|
spoolOutput = $spooling
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
when:
|
||||||
|
def result = gradleRunner("loggedExec").buildAndFail()
|
||||||
|
then:
|
||||||
|
result.task(':loggedExec').outcome == TaskOutcome.FAILED
|
||||||
|
file("build/buffered-output/loggedExec").exists() == spooling
|
||||||
|
assertOutputContains(result.output, """\
|
||||||
|
> Task :loggedExec FAILED
|
||||||
|
Output for ls:""".stripIndent())
|
||||||
|
assertOutputContains(result.output, "No such file or directory")
|
||||||
|
where:
|
||||||
|
spooling << [false, true]
|
||||||
|
}
|
||||||
|
|
||||||
|
def "can capture output"() {
|
||||||
|
setup:
|
||||||
|
buildFile << """
|
||||||
|
import org.elasticsearch.gradle.LoggedExec
|
||||||
|
tasks.register('loggedExec', LoggedExec) {
|
||||||
|
commandLine 'echo', 'HELLO'
|
||||||
|
getCaptureOutput().set(true)
|
||||||
|
doLast {
|
||||||
|
println 'OUTPUT ' + output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
when:
|
||||||
|
def result = gradleRunner("loggedExec").build()
|
||||||
|
then:
|
||||||
|
result.task(':loggedExec').outcome == TaskOutcome.SUCCESS
|
||||||
|
result.getOutput().contains("OUTPUT HELLO")
|
||||||
|
}
|
||||||
|
|
||||||
|
def "capturing output with spooling enabled is not supported"() {
|
||||||
|
setup:
|
||||||
|
buildFile << """
|
||||||
|
import org.elasticsearch.gradle.LoggedExec
|
||||||
|
tasks.register('loggedExec', LoggedExec) {
|
||||||
|
commandLine 'echo', 'HELLO'
|
||||||
|
getCaptureOutput().set(true)
|
||||||
|
spoolOutput = true
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
when:
|
||||||
|
def result = gradleRunner("loggedExec").buildAndFail()
|
||||||
|
then:
|
||||||
|
result.task(':loggedExec').outcome == TaskOutcome.FAILED
|
||||||
|
assertOutputContains(result.output, '''\
|
||||||
|
FAILURE: Build failed with an exception.
|
||||||
|
|
||||||
|
* What went wrong:
|
||||||
|
Execution failed for task ':loggedExec'.
|
||||||
|
> Capturing output is not supported when spoolOutput is true.'''.stripIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def "can configure output indenting"() {
|
||||||
|
setup:
|
||||||
|
buildFile << """
|
||||||
|
import org.elasticsearch.gradle.LoggedExec
|
||||||
|
tasks.register('loggedExec', LoggedExec) {
|
||||||
|
getIndentingConsoleOutput().set("CUSTOM")
|
||||||
|
commandLine('echo', '''
|
||||||
|
HELLO
|
||||||
|
Darkness
|
||||||
|
my old friend''')
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
when:
|
||||||
|
def result = gradleRunner("loggedExec", '-q').build()
|
||||||
|
then:
|
||||||
|
result.task(':loggedExec').outcome == TaskOutcome.SUCCESS
|
||||||
|
normalized(result.output) == '''
|
||||||
|
[CUSTOM] HELLO
|
||||||
|
[CUSTOM] Darkness
|
||||||
|
[CUSTOM] my old friend'''.stripIndent(9)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "can provide standard input"() {
|
||||||
|
setup:
|
||||||
|
file('script.sh') << """
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Read the user input
|
||||||
|
|
||||||
|
echo "Enter the user input: "
|
||||||
|
read userInput
|
||||||
|
echo "The user input is \$userInput"
|
||||||
|
"""
|
||||||
|
buildFile << """
|
||||||
|
import org.elasticsearch.gradle.LoggedExec
|
||||||
|
tasks.register('loggedExec', LoggedExec) {
|
||||||
|
getCaptureOutput().set(true)
|
||||||
|
commandLine 'bash', 'script.sh'
|
||||||
|
getStandardInput().set('FooBar')
|
||||||
|
doLast {
|
||||||
|
println output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
when:
|
||||||
|
def result = gradleRunner("loggedExec").build()
|
||||||
|
then:
|
||||||
|
result.task(':loggedExec').outcome == TaskOutcome.SUCCESS
|
||||||
|
result.getOutput().contains("The user input is FooBar")
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,12 +8,19 @@
|
||||||
package org.elasticsearch.gradle;
|
package org.elasticsearch.gradle;
|
||||||
|
|
||||||
import org.gradle.api.Action;
|
import org.gradle.api.Action;
|
||||||
|
import org.gradle.api.DefaultTask;
|
||||||
import org.gradle.api.GradleException;
|
import org.gradle.api.GradleException;
|
||||||
import org.gradle.api.Task;
|
|
||||||
import org.gradle.api.file.FileSystemOperations;
|
import org.gradle.api.file.FileSystemOperations;
|
||||||
|
import org.gradle.api.file.ProjectLayout;
|
||||||
import org.gradle.api.logging.Logger;
|
import org.gradle.api.logging.Logger;
|
||||||
import org.gradle.api.logging.Logging;
|
import org.gradle.api.logging.Logging;
|
||||||
import org.gradle.api.tasks.Exec;
|
import org.gradle.api.provider.ListProperty;
|
||||||
|
import org.gradle.api.provider.MapProperty;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.tasks.Input;
|
||||||
|
import org.gradle.api.tasks.Internal;
|
||||||
|
import org.gradle.api.tasks.Optional;
|
||||||
|
import org.gradle.api.tasks.TaskAction;
|
||||||
import org.gradle.api.tasks.WorkResult;
|
import org.gradle.api.tasks.WorkResult;
|
||||||
import org.gradle.process.BaseExecSpec;
|
import org.gradle.process.BaseExecSpec;
|
||||||
import org.gradle.process.ExecOperations;
|
import org.gradle.process.ExecOperations;
|
||||||
|
@ -21,13 +28,16 @@ import org.gradle.process.ExecResult;
|
||||||
import org.gradle.process.ExecSpec;
|
import org.gradle.process.ExecSpec;
|
||||||
import org.gradle.process.JavaExecSpec;
|
import org.gradle.process.JavaExecSpec;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -35,52 +45,70 @@ import java.util.regex.Pattern;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around gradle's Exec task to capture output and log on error.
|
* A wrapper around gradle's exec functionality to capture output and log on error.
|
||||||
|
* This Task is configuration cache-compatible in contrast to Gradle's built-in
|
||||||
|
* Exec task implementation.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public class LoggedExec extends Exec implements FileSystemOperationsAware {
|
public abstract class LoggedExec extends DefaultTask implements FileSystemOperationsAware {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logging.getLogger(LoggedExec.class);
|
private static final Logger LOGGER = Logging.getLogger(LoggedExec.class);
|
||||||
private Consumer<Logger> outputLogger;
|
protected FileSystemOperations fileSystemOperations;
|
||||||
private FileSystemOperations fileSystemOperations;
|
private ProjectLayout projectLayout;
|
||||||
|
private ExecOperations execOperations;
|
||||||
|
private boolean spoolOutput;
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
abstract public ListProperty<Object> getArgs();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
abstract public MapProperty<String, String> getEnvironment();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
abstract public Property<String> getExecutable();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
abstract public Property<String> getStandardInput();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
abstract public Property<String> getIndentingConsoleOutput();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
abstract public Property<Boolean> getCaptureOutput();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
abstract public Property<File> getWorkingDir();
|
||||||
|
|
||||||
|
private String output;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LoggedExec(FileSystemOperations fileSystemOperations) {
|
public LoggedExec(ProjectLayout projectLayout, ExecOperations execOperations, FileSystemOperations fileSystemOperations) {
|
||||||
|
this.projectLayout = projectLayout;
|
||||||
|
this.execOperations = execOperations;
|
||||||
this.fileSystemOperations = fileSystemOperations;
|
this.fileSystemOperations = fileSystemOperations;
|
||||||
if (getLogger().isInfoEnabled() == false) {
|
getWorkingDir().convention(projectLayout.getProjectDirectory().getAsFile());
|
||||||
setIgnoreExitValue(true);
|
// For now mimic default behaviour of Gradle Exec task here
|
||||||
setSpoolOutput(false);
|
getEnvironment().putAll(System.getenv());
|
||||||
// We use an anonymous inner class here because Gradle cannot properly snapshot this input for the purposes of
|
getCaptureOutput().convention(false);
|
||||||
// incremental build if we use a lambda. This ensures LoggedExec tasks that declare output can be UP-TO-DATE.
|
|
||||||
doLast(new Action<Task>() {
|
|
||||||
@Override
|
|
||||||
public void execute(Task task) {
|
|
||||||
int exitValue = LoggedExec.this.getExecutionResult().get().getExitValue();
|
|
||||||
if (exitValue != 0) {
|
|
||||||
try {
|
|
||||||
LoggedExec.this.getLogger().error("Output for " + LoggedExec.this.getExecutable() + ":");
|
|
||||||
outputLogger.accept(LoggedExec.this.getLogger());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new GradleException("Failed to read exec output", e);
|
|
||||||
}
|
|
||||||
throw new GradleException(
|
|
||||||
String.format(
|
|
||||||
"Process '%s %s' finished with non-zero exit value %d",
|
|
||||||
LoggedExec.this.getExecutable(),
|
|
||||||
LoggedExec.this.getArgs(),
|
|
||||||
exitValue
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSpoolOutput(boolean spoolOutput) {
|
@TaskAction
|
||||||
final OutputStream out;
|
public void run() {
|
||||||
|
if (spoolOutput && getCaptureOutput().get()) {
|
||||||
|
throw new GradleException("Capturing output is not supported when spoolOutput is true.");
|
||||||
|
}
|
||||||
|
if (getCaptureOutput().getOrElse(false) && getIndentingConsoleOutput().isPresent()) {
|
||||||
|
throw new GradleException("Capturing output is not supported when indentingConsoleOutput is configured.");
|
||||||
|
}
|
||||||
|
Consumer<Logger> outputLogger;
|
||||||
|
OutputStream out;
|
||||||
if (spoolOutput) {
|
if (spoolOutput) {
|
||||||
File spoolFile = new File(getProject().getBuildDir() + "/buffered-output/" + this.getName());
|
File spoolFile = new File(projectLayout.getBuildDirectory().dir("buffered-output").get().getAsFile(), this.getName());
|
||||||
out = new LazyFileOutputStream(spoolFile);
|
out = new LazyFileOutputStream(spoolFile);
|
||||||
outputLogger = logger -> {
|
outputLogger = logger -> {
|
||||||
try {
|
try {
|
||||||
|
@ -94,10 +122,59 @@ public class LoggedExec extends Exec implements FileSystemOperationsAware {
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
out = new ByteArrayOutputStream();
|
out = new ByteArrayOutputStream();
|
||||||
outputLogger = logger -> { logger.error(((ByteArrayOutputStream) out).toString(StandardCharsets.UTF_8)); };
|
outputLogger = getIndentingConsoleOutput().isPresent() ? logger -> {} : logger -> logger.error(byteStreamToString(out));
|
||||||
}
|
}
|
||||||
setStandardOutput(out);
|
|
||||||
setErrorOutput(out);
|
OutputStream finalOutputStream = getIndentingConsoleOutput().isPresent()
|
||||||
|
? new IndentingOutputStream(System.out, getIndentingConsoleOutput().get())
|
||||||
|
: out;
|
||||||
|
ExecResult execResult = execOperations.exec(execSpec -> {
|
||||||
|
execSpec.setIgnoreExitValue(true);
|
||||||
|
execSpec.setStandardOutput(finalOutputStream);
|
||||||
|
execSpec.setErrorOutput(finalOutputStream);
|
||||||
|
execSpec.setExecutable(getExecutable().get());
|
||||||
|
execSpec.setEnvironment(getEnvironment().get());
|
||||||
|
if (getArgs().isPresent()) {
|
||||||
|
execSpec.setArgs(getArgs().get());
|
||||||
|
}
|
||||||
|
if (getWorkingDir().isPresent()) {
|
||||||
|
execSpec.setWorkingDir(getWorkingDir().get());
|
||||||
|
}
|
||||||
|
if (getStandardInput().isPresent()) {
|
||||||
|
try {
|
||||||
|
execSpec.setStandardInput(new ByteArrayInputStream(getStandardInput().get().getBytes("UTF-8")));
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new GradleException("Cannot set standard input", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
int exitValue = execResult.getExitValue();
|
||||||
|
|
||||||
|
if (exitValue == 0 && getCaptureOutput().get()) {
|
||||||
|
output = byteStreamToString(out);
|
||||||
|
}
|
||||||
|
if (getLogger().isInfoEnabled() == false) {
|
||||||
|
if (exitValue != 0) {
|
||||||
|
try {
|
||||||
|
getLogger().error("Output for " + getExecutable().get() + ":");
|
||||||
|
outputLogger.accept(getLogger());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new GradleException("Failed to read exec output", e);
|
||||||
|
}
|
||||||
|
throw new GradleException(
|
||||||
|
String.format("Process '%s %s' finished with non-zero exit value %d", getExecutable().get(), getArgs().get(), exitValue)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String byteStreamToString(OutputStream out) {
|
||||||
|
return ((ByteArrayOutputStream) out).toString(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpoolOutput(boolean spoolOutput) {
|
||||||
|
this.spoolOutput = spoolOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExecResult exec(ExecOperations execOperations, Action<ExecSpec> action) {
|
public static ExecResult exec(ExecOperations execOperations, Action<ExecSpec> action) {
|
||||||
|
@ -139,4 +216,60 @@ public class LoggedExec extends Exec implements FileSystemOperationsAware {
|
||||||
public WorkResult delete(Object... objects) {
|
public WorkResult delete(Object... objects) {
|
||||||
return fileSystemOperations.delete(d -> d.delete(objects));
|
return fileSystemOperations.delete(d -> d.delete(objects));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public String getOutput() {
|
||||||
|
if (getCaptureOutput().get() == false) {
|
||||||
|
throw new GradleException(
|
||||||
|
"Capturing output was not enabled. Use " + getName() + ".getCapturedOutput.set(true) to enable output capturing."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class IndentingOutputStream extends OutputStream {
|
||||||
|
|
||||||
|
public final byte[] indent;
|
||||||
|
private final OutputStream delegate;
|
||||||
|
|
||||||
|
IndentingOutputStream(OutputStream delegate, Object version) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
indent = (" [" + version + "] ").getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
int[] arr = { b };
|
||||||
|
write(arr, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(int[] bytes, int offset, int length) throws IOException {
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
delegate.write(bytes[i]);
|
||||||
|
if (bytes[i] == '\n') {
|
||||||
|
delegate.write(indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void args(Object... args) {
|
||||||
|
args(List.of(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void args(List<Object> args) {
|
||||||
|
getArgs().addAll(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commandLine(Object... args) {
|
||||||
|
commandLine(List.of(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commandLine(List<Object> args) {
|
||||||
|
if (args.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Cannot set commandline with empty list.");
|
||||||
|
}
|
||||||
|
getExecutable().set(args.get(0).toString());
|
||||||
|
getArgs().set(args.subList(1, args.size()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,9 +47,9 @@ abstract class AbstractGradleFuncTest extends Specification {
|
||||||
}
|
}
|
||||||
|
|
||||||
def cleanup() {
|
def cleanup() {
|
||||||
if (Boolean.getBoolean('test.keep.samplebuild')) {
|
// if (Boolean.getBoolean('test.keep.samplebuild')) {
|
||||||
FileUtils.copyDirectory(testProjectDir.root, new File("build/test-debug/" + testProjectDir.root.name))
|
FileUtils.copyDirectory(testProjectDir.root, new File("build/test-debug/" + testProjectDir.root.name))
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
File subProject(String subProjectPath) {
|
File subProject(String subProjectPath) {
|
||||||
|
|
|
@ -469,7 +469,7 @@ subprojects { Project subProject ->
|
||||||
|
|
||||||
tasks.register(exportTaskName, LoggedExec) {
|
tasks.register(exportTaskName, LoggedExec) {
|
||||||
inputs.file("${parent.projectDir}/build/markers/${buildTaskName}.marker")
|
inputs.file("${parent.projectDir}/build/markers/${buildTaskName}.marker")
|
||||||
executable 'docker'
|
executable = 'docker'
|
||||||
outputs.file(tarFile)
|
outputs.file(tarFile)
|
||||||
args "save",
|
args "save",
|
||||||
"-o",
|
"-o",
|
||||||
|
|
|
@ -475,15 +475,13 @@ subprojects {
|
||||||
if (project.name.contains('deb')) {
|
if (project.name.contains('deb')) {
|
||||||
checkLicenseMetadataTaskProvider.configure { LoggedExec exec ->
|
checkLicenseMetadataTaskProvider.configure { LoggedExec exec ->
|
||||||
onlyIf dpkgExists
|
onlyIf dpkgExists
|
||||||
final ByteArrayOutputStream output = new ByteArrayOutputStream()
|
|
||||||
exec.commandLine 'dpkg-deb', '--info', "${-> buildDist.get().outputs.files.filter(debFilter).singleFile}"
|
exec.commandLine 'dpkg-deb', '--info', "${-> buildDist.get().outputs.files.filter(debFilter).singleFile}"
|
||||||
exec.standardOutput = output
|
exec.getCaptureOutput().set(true)
|
||||||
doLast {
|
doLast {
|
||||||
String expectedLicense
|
String expectedLicense
|
||||||
expectedLicense = "Elastic-License"
|
expectedLicense = "Elastic-License"
|
||||||
final Pattern pattern = Pattern.compile("\\s*License: (.+)")
|
final Pattern pattern = Pattern.compile("\\s*License: (.+)")
|
||||||
final String info = output.toString('UTF-8')
|
final String[] actualLines = getOutput().split("\n")
|
||||||
final String[] actualLines = info.split("\n")
|
|
||||||
int count = 0
|
int count = 0
|
||||||
for (final String actualLine : actualLines) {
|
for (final String actualLine : actualLines) {
|
||||||
final Matcher matcher = pattern.matcher(actualLine)
|
final Matcher matcher = pattern.matcher(actualLine)
|
||||||
|
@ -507,11 +505,10 @@ subprojects {
|
||||||
assert project.name.contains('rpm')
|
assert project.name.contains('rpm')
|
||||||
checkLicenseMetadataTaskProvider.configure { LoggedExec exec ->
|
checkLicenseMetadataTaskProvider.configure { LoggedExec exec ->
|
||||||
onlyIf rpmExists
|
onlyIf rpmExists
|
||||||
final ByteArrayOutputStream output = new ByteArrayOutputStream()
|
|
||||||
exec.commandLine 'rpm', '-qp', '--queryformat', '%{License}', "${-> buildDist.get().outputs.files.singleFile}"
|
exec.commandLine 'rpm', '-qp', '--queryformat', '%{License}', "${-> buildDist.get().outputs.files.singleFile}"
|
||||||
exec.standardOutput = output
|
exec.getCaptureOutput().set(true)
|
||||||
doLast {
|
doLast {
|
||||||
String license = output.toString('UTF-8')
|
String license = getOutput()
|
||||||
String expectedLicense
|
String expectedLicense
|
||||||
expectedLicense = "Elastic License"
|
expectedLicense = "Elastic License"
|
||||||
if (license != expectedLicense) {
|
if (license != expectedLicense) {
|
||||||
|
|
|
@ -64,7 +64,7 @@ TaskProvider createKey = tasks.register("createKey", LoggedExec) {
|
||||||
}
|
}
|
||||||
outputs.file(keystore).withPropertyName('keystoreFile')
|
outputs.file(keystore).withPropertyName('keystoreFile')
|
||||||
executable = "${BuildParams.runtimeJavaHome}/bin/keytool"
|
executable = "${BuildParams.runtimeJavaHome}/bin/keytool"
|
||||||
standardInput = new ByteArrayInputStream('FirstName LastName\nUnit\nOrganization\nCity\nState\nNL\nyes\n\n'.getBytes('UTF-8'))
|
getStandardInput().set('FirstName LastName\nUnit\nOrganization\nCity\nState\nNL\nyes\n\n')
|
||||||
args '-genkey',
|
args '-genkey',
|
||||||
'-alias', 'test-node',
|
'-alias', 'test-node',
|
||||||
'-keystore', keystore,
|
'-keystore', keystore,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue