Skip to content

Commit

Permalink
Add simpler API to capture external process output as build input
Browse files Browse the repository at this point in the history
There are two new methods, `exec` and `javaexec` in the ProviderFactory
class. These are similar to the existing methods of ExecOperations and
reuse the same configuration DSL classes. The returned providers have
convenient methods to read the process' output during the configuration
time or to wire it as an input to the task. The outputs become parts of
the configuration cache fingerprint if used during the configuration
time, so the wrapped process is re-executed every time build is started.
  • Loading branch information
mlopatkin committed Dec 23, 2021
1 parent 4144c1b commit 593ea22
Show file tree
Hide file tree
Showing 25 changed files with 2,145 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import org.gradle.api.internal.provider.sources.EnvironmentVariableValueSource
import org.gradle.api.internal.provider.sources.FileContentValueSource
import org.gradle.api.internal.provider.sources.GradlePropertyValueSource
import org.gradle.api.internal.provider.sources.SystemPropertyValueSource
import org.gradle.api.internal.provider.sources.process.ProcessOutputValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.api.tasks.util.PatternSet
import org.gradle.configurationcache.CoupledProjectsListener
Expand Down Expand Up @@ -217,6 +218,10 @@ class ConfigurationCacheFingerprintWriter(
is EnvironmentVariableValueSource.Parameters -> {
envVariableRead(parameters.variableName.get(), obtainedValue.value.get() as? String, null)
}
is ProcessOutputValueSource.Parameters -> {
sink().write(ValueSource(obtainedValue.uncheckedCast()))
reportExternalProcessOutputRead(ProcessOutputValueSource.Parameters.getExecutable(parameters))
}
else -> {
captureValueSource(obtainedValue)
}
Expand Down Expand Up @@ -339,6 +344,14 @@ class ConfigurationCacheFingerprintWriter(
}
}

private
fun reportExternalProcessOutputRead(executable: String) {
reportInput(consumer = null, documentationSection = null) {
text("output of the external process ")
reference(executable)
}
}

private
fun reportSystemPropertyInput(key: String, consumer: String?) {
reportInput(consumer, DocumentationSection.RequirementsSysPropEnvVarRead) {
Expand Down
3 changes: 2 additions & 1 deletion subprojects/core-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ plugins {
description = "Public and internal 'core' Gradle APIs that are required by other subprojects"

dependencies {
api(project(":process-services"))

implementation(project(":base-services"))
implementation(project(":base-services-groovy"))
implementation(project(":enterprise-operations"))
implementation(project(":files"))
implementation(project(":logging"))
implementation(project(":persistent-cache"))
implementation(project(":process-services"))
implementation(project(":resources"))

implementation(libs.groovy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import org.gradle.api.initialization.Settings;
import org.gradle.internal.service.scopes.Scopes;
import org.gradle.internal.service.scopes.ServiceScope;
import org.gradle.process.ExecOutput;
import org.gradle.process.ExecSpec;
import org.gradle.process.JavaExecSpec;

import java.util.concurrent.Callable;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -140,6 +143,42 @@ public interface ProviderFactory {
*/
FileContents fileContents(Provider<RegularFile> file);

/**
* Allows lazy access to the output of the external process.
*
* When the process output is read at configuration time it is considered as an input to the
* configuration model. Consequent builds will re-execute the process to obtain the output and
* check if the cached model is still up-to-date.
*
* The process input and output streams cannot be configured.
*
*
* @param action the configuration of the external process with the output stream
* pre-configured.
* @return an interface that allows lazy access to the process' output.
* @since 7.5
*/
@Incubating
ExecOutput exec(Action<? super ExecSpec> action);

/**
* Allows lazy access to the output of the external java process.
*
* When the process output is read at configuration time it is considered as an input to the
* configuration model. Consequent builds will re-execute the process to obtain the output and
* check if the cached model is still up-to-date.
*
* The process input and output streams cannot be configured.
*
* @param action the configuration of the external process with the output stream
* pre-configured.
* @return an interface that allows lazy access to the process' output.
*
* @since 7.5
*/
@Incubating
ExecOutput javaexec(Action<? super JavaExecSpec> action);

/**
* Creates a {@link Provider} whose value is obtained from the given {@link ValueSource}.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2021 the original author or authors.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.gradle.process;

import org.gradle.api.Incubating;
import org.gradle.api.provider.Provider;

/**
* Provides lazy access to the output of the external process.
*
* @since 7.5
*/
@Incubating
public interface ExecOutput {
/**
* Returns a provider of the execution result.
*
* <p>
* The external process is executed only once and only when the value is requested for the first
* time.
* </p>
* <p>
* If starting the process results in exception then the ensuing exception is permanently
* propagated to callers of {@link Provider#get}, {@link Provider#getOrElse},
* {@link Provider#getOrNull} and {@link Provider#isPresent}.
* </p>
*
* @return provider of the execution result.
*/
Provider<ExecResult> getResult();

/**
* Gets a handle to the content of the process' standard output.
*
* @return handle of the standard output of the process.
*/
StandardStreamContent getStandardOutput();

/**
* Gets a handle to the content of the process' standard error output.
*
* @return handle of the standard error output of the process.
*/
StandardStreamContent getStandardError();

/**
* A handle to access content of the process' standard stream (the standard output of the
* standard error output).
*
* @since 7.5
*/
@Incubating
interface StandardStreamContent {
/**
* Gets a provider for the standard stream's content that returns it as a String. The output
* is decoded using the default encoding of the JVM running the build.
*
* <p>
* The external process is executed only once and only when the value is requested for the
* first time.
* </p>
* <p>
* If starting the process results in exception then the ensuing exception is permanently
* propagated to callers of {@link Provider#get}, {@link Provider#getOrElse},
* {@link Provider#getOrNull} and {@link Provider#isPresent}.
* </p>
*/
Provider<String> getAsText();

/**
* Gets a provider for the standard stream's content that returns it as a byte array.
*
* <p>
* The external process is executed only once and only when the value is requested for the
* first time.
* </p>
* <p>
* If starting the process results in exception then the ensuing exception is permanently
* propagated to callers of {@link Provider#get}, {@link Provider#getOrElse},
* {@link Provider#getOrNull} and {@link Provider#isPresent}.
* </p>
*/
Provider<byte[]> getAsBytes();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
import org.gradle.api.internal.provider.DefaultProviderFactory;
import org.gradle.api.internal.provider.DefaultValueSourceProviderFactory;
import org.gradle.api.internal.provider.ValueSourceProviderFactory;
import org.gradle.api.internal.provider.sources.process.ExecSpecFactory;
import org.gradle.api.internal.provider.sources.process.ProcessOutputProviderFactory;
import org.gradle.api.internal.resources.ApiTextResourceAdapter;
import org.gradle.api.internal.resources.DefaultResourceHandler;
import org.gradle.api.internal.tasks.TaskStatistics;
Expand Down Expand Up @@ -197,6 +199,8 @@
import org.gradle.plugin.management.internal.autoapply.AutoAppliedPluginHandler;
import org.gradle.plugin.use.internal.PluginRequestApplicator;
import org.gradle.process.internal.DefaultExecOperations;
import org.gradle.process.internal.DefaultExecSpecFactory;
import org.gradle.process.internal.ExecActionFactory;
import org.gradle.process.internal.ExecFactory;
import org.gradle.tooling.provider.model.internal.BuildScopeToolingModelBuilderRegistryAction;
import org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry;
Expand Down Expand Up @@ -358,12 +362,21 @@ protected ValueSourceProviderFactory createValueSourceProviderFactory(
);
}

protected ExecSpecFactory createExecSpecFactory(ExecActionFactory execActionFactory) {
return new DefaultExecSpecFactory(execActionFactory);
}

protected ProcessOutputProviderFactory createProcessOutputProviderFactory(Instantiator instantiator, ExecSpecFactory execSpecFactory) {
return new ProcessOutputProviderFactory(instantiator, execSpecFactory);
}

protected ProviderFactory createProviderFactory(
Instantiator instantiator,
ValueSourceProviderFactory valueSourceProviderFactory,
ProcessOutputProviderFactory processOutputProviderFactory,
ListenerManager listenerManager
) {
return instantiator.newInstance(DefaultProviderFactory.class, valueSourceProviderFactory, listenerManager);
return instantiator.newInstance(DefaultProviderFactory.class, valueSourceProviderFactory, processOutputProviderFactory, listenerManager);
}

protected ActorFactory createActorFactory() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2021 the original author or authors.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.gradle.process.internal;

import org.gradle.api.internal.provider.sources.process.ExecSpecFactory;
import org.gradle.process.ExecSpec;
import org.gradle.process.JavaExecSpec;

public class DefaultExecSpecFactory implements ExecSpecFactory {
private final ExecActionFactory execActionFactory;

public DefaultExecSpecFactory(ExecActionFactory execActionFactory) {
this.execActionFactory = execActionFactory;
}

@Override
public ExecSpec newExecSpec() {
return execActionFactory.newExecAction();
}

@Override
public JavaExecSpec newJavaExecSpec() {
return execActionFactory.newJavaExecAction();
}
}

0 comments on commit 593ea22

Please sign in to comment.