Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make LoggedExec gradle task configuration cache compatible #87621

Merged
merged 19 commits into from
Jul 11, 2022

Conversation

breskeby
Copy link
Contributor

@breskeby breskeby commented Jun 13, 2022

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 #57918

@breskeby breskeby self-assigned this Jun 13, 2022
@breskeby breskeby added :Delivery/Build Build or test infrastructure >enhancement Team:Delivery Meta label for Delivery team v7.17.5 labels Jun 13, 2022
@breskeby breskeby force-pushed the make-logged-exec-cc-compatible branch from 46cb06f to 172b201 Compare July 4, 2022 17:56
@breskeby breskeby removed the v7.17.5 label Jul 6, 2022
@breskeby breskeby marked this pull request as ready for review July 6, 2022 13:22
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-delivery (Team:Delivery)

output = byteStreamToString(out);
}
if (getLogger().isInfoEnabled() == false) {
// We use an anonymous inner class here because Gradle cannot properly snapshot this input for the purposes of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see an anonymous inner class being used here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Comment on lines 276 to 271
if (args.size() == 0) {
throw new IllegalArgumentException("Cannot set commandline with of entry size 0");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be clearer like:

Suggested change
if (args.size() == 0) {
throw new IllegalArgumentException("Cannot set commandline with of entry size 0");
}
if (args.isEmpty()) {
throw new IllegalArgumentException("Cannot set commandLine with empty list");
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@TaskAction
public void run() {
Consumer<Logger> outputLogger;
OutputStream out = getCaptureOutput().get() ? new ByteArrayOutputStream() : System.out;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct. We don't ever want to output directly to standard out. Instead we want to capture output and then only log it in the case of a failure. Essentially, we always need to capture the output, because we hold on to it only to log to the console only in the case of a non-zero exit code.

This also throws an exception as is when the command fails and captureOutput == false since we try to cast System.out to a ByteArrayOutputStream in byteStreamToString() which of course will fail.

File spoolFile = new File(getProject().getBuildDir() + "/buffered-output/" + this.getName());
out = new LazyFileOutputStream(spoolFile);
File spoolFile = new File(projectLayout.getBuildDirectory().dir("buffered-output").get().getAsFile(), this.getName());
effectiveOutputStream = new TeeOutputStream(out, new LazyFileOutputStream(spoolFile));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given my comment above, I don't think we'll want to tee the output here if out is always a ByteArrayOutputStream since of course it defeats the purpose of spooling to disk if we are also going to keep the output in memory. I think we can just simplify the whole thing by just setting out above to either a ByteArrayOutputStream or LazyFileOutputSream based on what spoolOutput is set to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mark-vieira I've reworked this with two implicit changes:

  1. we now do not support capturing output now for later evaluation if spooling is enabled. I could be achieved by reading from the spool file but we currently have no use case for this
  2. When indenting output is used (e.g. for our nested gradle builds) we do not log out to console by default anymore as this logger used to do so. This should be fine as it still logs to console in failure case.
  3. As an interesting side effect spooling the output for the nested gradle builds works correctly now. Before we configured spooling to be true, but then reset the output stream to the indenting output stream which delegated to console instead of using the earlier configured LazyOutputstream to the spooling file.

I added further integration tests for these scenarios and that the task failure case is handling correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we now do not support capturing output now for later evaluation if spooling is enabled. I could be achieved by reading from the spool file but we currently have no use case for this

I think that's fine.

When indenting output is used (e.g. for our nested gradle builds) we do not log out to console by default anymore as this logger used to do so. This should be fine as it still logs to console in failure case.

I'm on the fence about this. This is primiarily for building BWC distributions and these tasks can be long (over 1m). Having no console output makes it difficult to tell if something is hung or going wrong. My preference would be to keep the existing behavior of teeing to stdout in this scenario. Basically, the whole reason to indent to begin with is so you can differentiate the nested build from the outer one. If we're just spooling to a file and printing on error, the indenting wouldn't be necessary.

As an interesting side effect spooling the output for the nested gradle builds works correctly now. Before we configured spooling to be true, but then reset the output stream to the indenting output stream which delegated to console instead of using the earlier configured LazyOutputstream to the spooling file.

It would seem to me that forwarding to stdout and spooling to disk are redundant. In a "normal" operation, we'd spool the output because we'd only display it in the case of a failure. If we are forwarding the output to the console, then why print it again in the case of a failure.

It seems to me the BWC building case could just be done with a regular Exec task configured with an intending output stream. There's no need to otherwise "hold on to" the output and print it out later. LoggedExec should be used exclusively for shelling out where we don't need to see the output in the console, but we do want to keep it for reporting later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reworked the LoggedExec to fix the indention for nested builds logging to stdout. Using Exec is actually what we do not want anymore as we want to have those tasks configuration cache compatible. Anyhow we should disable spooling for those nested builds

@@ -93,11 +117,67 @@ public void setSpoolOutput(boolean spoolOutput) {
}
};
} else {
out = new ByteArrayOutputStream();
outputLogger = logger -> { logger.error(((ByteArrayOutputStream) out).toString(StandardCharsets.UTF_8)); };
outputLogger = logger -> { logger.error(byteStreamToString(out)); };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We can ditch the curly braces here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

abstract public MapProperty<String, String> getEnvironment();

@Input
abstract public Property<String> getExecutableProperty();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just call this getExecutable? We don't seem to use a "property" suffix anywhere else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put an api convenience method of setExecutable(String) in the class. therefore the renaming was required to not break the dynamic method creation. anyhow. I'm ditching this as it was finally used only on a few plugin classes where convenience /readability is not that important

@@ -107,8 +105,8 @@ public void apply(Project project) {
});
fetchLatest.onlyIf(t -> isOffline == false && gitFetchLatest.get());
fetchLatest.dependsOn(addRemoteTaskProvider);
fetchLatest.setWorkingDir(gitExtension.getCheckoutDir().get());
fetchLatest.setCommandLine(asList("git", "fetch", "--all"));
fetchLatest.getWorkingDir().set(gitExtension.getCheckoutDir().get());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, no need to call get().

loggedExec.environment("JAVA_HOME", getJavaHome(Integer.parseInt(minimumCompilerVersion)));
}
});
loggedExec.getWorkingDir().set(checkoutDir.get());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to call get() here on checkoutDir.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

});
loggedExec.getWorkingDir().set(checkoutDir.get());
// Execution time so that the checkouts are available
loggedExec.getEnvironment().put("JAVA_HOME", project.provider(() -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use the Provider APIs to simplify this a bit I think:

loggedExec.getEnvironment().put("JAVA_HOME", unreleasedVersionInfo.zip(checkoutDir, (version, checkoutDir) -> {
    String minimumCompilerVersion = readFromFile(new File(checkoutDir, minimumCompilerVersionPath(version.version())));
    return getJavaHome(Integer.parseInt(minimumCompilerVersion));
}));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice. done

}
});
loggedExec.getWorkingDir().set(checkoutDir.get());
// Execution time so that the checkouts are available
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is no longer relevant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@@ -110,8 +103,7 @@ public void execute(Task t) {
if (project.getGradle().getStartParameter().isParallelProjectExecutionEnabled()) {
loggedExec.args("--parallel");
}
loggedExec.setStandardOutput(new IndentingOutputStream(System.out, unreleasedVersionInfo.get().version()));
loggedExec.setErrorOutput(new IndentingOutputStream(System.err, unreleasedVersionInfo.get().version()));
loggedExec.getOutputIndenting().set(unreleasedVersionInfo.get().version().toString());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can be more idomatically lazy here and do:

loggedExec.getOutputIndenting().set(unreleasedVersionInfo.map(v -> v.version().toString()));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@breskeby breskeby force-pushed the make-logged-exec-cc-compatible branch from 3327395 to b7b42d1 Compare July 8, 2022 16:39
Copy link
Contributor

@mark-vieira mark-vieira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple minor things but I think this looks good.

if (spoolOutput && getCaptureOutput().get()) {
throw new GradleException("Capturing output is not supported when spoolOutput is true.");
}
if (getCaptureOutput().getOrElse(false) && getIndentingConsoleOutput().isPresent()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need getOrElse(false) if we set the convention to false already?

protected FileSystemOperations fileSystemOperations;
private ProjectLayout projectLayout;
private ExecOperations execOperations;
private boolean spoolOutput;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we using a Property for this?

if (getLogger().isInfoEnabled() == false) {
if (exitValue != 0) {
try {
getLogger().error("Output for " + getExecutable().get() + ":");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to omit this is indenting output is enabled so we don't print Output for foo: and then nothing else.

@breskeby breskeby merged commit dbf3974 into elastic:master Jul 11, 2022
weizijun added a commit to weizijun/elasticsearch that referenced this pull request Jul 12, 2022
* upstream/master:
  Pass IndexMetadata to AllocationDecider.can_remain (elastic#88453)
  [TSDB] Cache rollup bucket timestamp to reduce rounding cost (elastic#88420)
  Correct some typos/mistakes in comments/docs (elastic#88446)
  Make ClusterInfo use immutable maps in all cases (elastic#88447)
  Reduce map lookups (elastic#88418)
  Don't index geo_shape field in AbstractBuilderTestCase (elastic#88437)
  Remove usages of TestGeoShapeFieldMapperPlugin from enrich module (elastic#88440)
  Fix test memory leak (elastic#88362)
  Improve error when sorting on incompatible types (elastic#88399)
  Remove usages of BucketCollector#getLeafCollector(LeafReaderContext) (elastic#88414)
  Mute ReactiveStorageIT::testScaleWhileShrinking (elastic#88431)
  Clarify snapshot docs on archive indices (elastic#88417)
  [Stack Monitoring] Switch cgroup memory fields to keyword (elastic#88260)
  Fix RealmIdentifier XContent parser (elastic#88410)
  Make LoggedExec gradle task configuration cache compatible (elastic#87621)
  Update CorruptedFileIT so that it passes with new allocation strategy (elastic#88314)
  Update RareClusterStateIT to work with the new shards allocator (elastic#87922)
  Ensure CreateApiKey always creates a new document (elastic#88413)

# Conflicts:
#	x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/v2/RollupShardIndexer.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Delivery/Build Build or test infrastructure >enhancement Team:Delivery Meta label for Delivery team v8.4.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants