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

Add execute-command-before BuildMutator #310

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ A scenario can define changes that should be applied to the source before each b
- `clear-project-cache-before`: Deletes the contents of the `.gradle` and `buildSrc/.gradle` project cache directories before the scenario is executed (`SCENARIO`), before cleanup (`CLEANUP`) or before the build is executed (`BUILD`).
- `clear-transform-cache-before`: Deletes the contents of the transform cache before the scenario is executed (`SCENARIO`), before cleanup (`CLEANUP`) or before the build is executed (`BUILD`).
- `clear-jars-cache-before`: Deletes the contents of the instrumented jars cache before the scenario is executed (`SCENARIO`), before cleanup (`CLEANUP`) or before the build is executed (`BUILD`).
- `execute-command-before`: Execute a command before the scenario is executed (SCENARIO) or before the build is executed (BUILD). This can be applied to Gradle, Bazel, Buck, or Maven builds.
- `git-checkout`: Checks out a specific commit for the build step, and a different one for the cleanup step.
- `git-revert`: Reverts a given set of commits before the build and resets it afterward.
- `iterations`: Number of builds to actually measure
Expand Down Expand Up @@ -325,11 +326,43 @@ You can compare Gradle against Bazel, Buck, and Maven by specifying their equiva

build_some_target {
tasks = ["assemble"]
execute-command-before = [
# Gradle specific executions
# Execute a command in the bash shell
{
schedule = BUILD
commands = ["/bin/bash","-c","echo helloworld"]
},
}

bazel {
# If empty, it will be infered from BAZEL_HOME environment variable
home = "/path/to/bazel/home"
targets = ["build" "//some/target"]
execute-command-before = [
# Bazel specific executions
# execute a command prior to the scenario running.
{
schedule = SCENARIO
# Note: Ensure that this is calling the same Bazel that is used in the benchmarks
commands = ["bazel","clean","--expunge"]
},
# Execute a command in the bash shell
{
schedule = BUILD
commands = ["/bin/bash","-c","echo helloworld"]
},
# Remove the contents of the remote cache Bazel bucket
{
schedule = BUILD
commands = ["gsutil","-o","Credentials:gs_service_key_file=bazel-benchmark-bucket.json","-m","rm","gs://bazel-benchmark-bucket/**"]
},
# Display the total size of the bucket
{
schedule = BUILD
commands = ["gsutil","-o","Credentials:gs_service_key_file=bazel-benchmark-bucket.json","du","-sh","gs://bazel-benchmark-bucket"]
},
]
}
}

Expand All @@ -344,6 +377,14 @@ You can compare Gradle against Bazel, Buck, and Maven by specifying their equiva
# If empty, it will be infered from BUCK_HOME environment variable
home = "/path/to/buck/home"
type = "android_binary" // can be a Buck build rule type or "all"
execute-command-before = [
# Buck specific executions
# Execute a command in the bash shell
{
schedule = BUILD
commands = ["/bin/bash","-c","echo helloworld"]
},
}
}
}
build_resources {
Expand Down
17 changes: 11 additions & 6 deletions src/main/java/org/gradle/profiler/ScenarioLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.gradle.profiler.mutations.ApplyNonAbiChangeToSourceFileMutator;
import org.gradle.profiler.mutations.ApplyValueChangeToAndroidResourceFileMutator;
import org.gradle.profiler.mutations.BuildMutatorConfigurator;
import org.gradle.profiler.mutations.ExecuteCommandBuildMutator;
import org.gradle.profiler.mutations.ClearArtifactTransformCacheMutator;
import org.gradle.profiler.mutations.ClearBuildCacheMutator;
import org.gradle.profiler.mutations.ClearConfigurationCacheStateMutator;
Expand All @@ -37,6 +38,10 @@
import java.util.stream.Collectors;

class ScenarioLoader {

static final String BAZEL = "bazel";
static final String BUCK = "buck";
static final String MAVEN = "maven";
private static final String TITLE = "title";
private static final String VERSIONS = "versions";
private static final String TASKS = "tasks";
Expand All @@ -45,9 +50,6 @@ class ScenarioLoader {
private static final String RUN_USING = "run-using";
private static final String DAEMON = "daemon";
private static final String SYSTEM_PROPERTIES = "system-properties";
private static final String BAZEL = "bazel";
private static final String BUCK = "buck";
private static final String MAVEN = "maven";
private static final String TOOL_HOME = "home";
private static final String WARM_UP_COUNT = "warm-ups";
private static final String ITERATIONS = "iterations";
Expand All @@ -61,6 +63,7 @@ class ScenarioLoader {
private static final String APPLY_ANDROID_MANIFEST_CHANGE_TO = "apply-android-manifest-change-to";
private static final String APPLY_CPP_SOURCE_CHANGE_TO = "apply-cpp-change-to";
private static final String APPLY_H_SOURCE_CHANGE_TO = "apply-h-change-to";
private static final String EXECUTE_COMMAND_BEFORE = "execute-command-before";
private static final String CLEAR_BUILD_CACHE_BEFORE = "clear-build-cache-before";
private static final String CLEAR_GRADLE_USER_HOME_BEFORE = "clear-gradle-user-home-before";
private static final String CLEAR_INSTANT_EXECUTION_STATE_BEFORE = "clear-instant-execution-state-before";
Expand Down Expand Up @@ -97,6 +100,7 @@ class ScenarioLoader {
.put(SHOW_BUILD_CACHE_SIZE, new ShowBuildCacheSizeMutator.Configurator())
.put(GIT_CHECKOUT, new GitCheckoutMutator.Configurator())
.put(GIT_REVERT, new GitRevertMutator.Configurator())
.put(EXECUTE_COMMAND_BEFORE, new ExecuteCommandBuildMutator.Configurator())
.build();

private static final List<String> ALL_SCENARIO_KEYS = ImmutableList.<String>builder()
Expand All @@ -105,6 +109,7 @@ class ScenarioLoader {
TITLE,
VERSIONS,
TASKS,
EXECUTE_COMMAND_BEFORE,
CLEANUP_TASKS,
GRADLE_ARGS,
RUN_USING,
Expand All @@ -123,9 +128,9 @@ class ScenarioLoader {
))
.build();

private static final List<String> BAZEL_KEYS = Arrays.asList(TARGETS, TOOL_HOME);
private static final List<String> BUCK_KEYS = Arrays.asList(TARGETS, TYPE, TOOL_HOME);
private static final List<String> MAVEN_KEYS = Arrays.asList(TARGETS, TOOL_HOME);
private static final List<String> BAZEL_KEYS = Arrays.asList(TARGETS, TOOL_HOME, EXECUTE_COMMAND_BEFORE);
private static final List<String> BUCK_KEYS = Arrays.asList(TARGETS, TYPE, TOOL_HOME, EXECUTE_COMMAND_BEFORE);
private static final List<String> MAVEN_KEYS = Arrays.asList(TARGETS, TOOL_HOME, EXECUTE_COMMAND_BEFORE);

private final GradleBuildConfigurationReader gradleBuildConfigurationReader;

Expand Down
44 changes: 44 additions & 0 deletions src/main/java/org/gradle/profiler/ScenarioUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.gradle.profiler;

import static org.gradle.profiler.ScenarioLoader.BAZEL;
import static org.gradle.profiler.ScenarioLoader.BUCK;
import static org.gradle.profiler.ScenarioLoader.MAVEN;

import com.typesafe.config.Config;
import javax.annotation.Nullable;

public class ScenarioUtil {

/**
* Returns the specific config for type of build that is running,
* if the scenario doesn't define the build, then this default value is returned
*
* @param rootScenario the root scenario config
* @param settings the invocation settings that indicates the type of build selected
* @param defaultScenario if the scenario doesn't define the build, then this default value is returned, this can be null.
* @return if the build config exists or if the scenario doesn't define the build, then this default value is returned
*/
public static Config getBuildConfig(Config rootScenario, InvocationSettings settings, @Nullable
Config defaultScenario) {
Config scenario;
if (settings.isBazel()) {
scenario = getConfigOrDefault(rootScenario, BAZEL, defaultScenario);
} else if (settings.isBuck()) {
scenario = getConfigOrDefault(rootScenario, BUCK, defaultScenario);
} else if (settings.isMaven()) {
scenario = getConfigOrDefault(rootScenario, MAVEN, defaultScenario);
} else {
scenario = rootScenario;
}
return scenario;
}

private static Config getConfigOrDefault(Config rootScenario, String key, @Nullable Config defaultScenario) {
Config scenario;
if (rootScenario.hasPath(key)) {
scenario = rootScenario.getConfig(key);
} else {
scenario = defaultScenario;
} return scenario;
}
}
16 changes: 16 additions & 0 deletions src/main/java/org/gradle/profiler/mutations/CommandInvoker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.gradle.profiler.mutations;

import java.util.List;

/**
* Interface which provides a means to execute a command
*/
public interface CommandInvoker {

/**
* @param command the command to execute
* @return the exit code of the result of executing the command
*/
int execute(List<String> command);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package org.gradle.profiler.mutations;

import static org.gradle.profiler.ScenarioUtil.getBuildConfig;

import com.google.common.annotations.VisibleForTesting;
import com.typesafe.config.Config;
import java.util.ArrayList;
import java.util.List;
import org.gradle.profiler.BuildContext;
import org.gradle.profiler.BuildMutator;
import org.gradle.profiler.CompositeBuildMutator;
import org.gradle.profiler.ConfigUtil;
import org.gradle.profiler.InvocationSettings;
import org.gradle.profiler.ScenarioContext;

public class ExecuteCommandBuildMutator implements BuildMutator {

private ExecuteCommandSchedule schedule;
private List<String> commands;
private CommandInvoker commandInvoker;

public ExecuteCommandBuildMutator(ExecuteCommandSchedule schedule,
List<String> commands, CommandInvoker commandInvoker) {
this.schedule = schedule;
this.commands = commands;
this.commandInvoker = commandInvoker;
}

@Override
public void beforeBuild(BuildContext context) {
if (schedule == ExecuteCommandSchedule.BUILD) {
execute();
}
}

@Override
public void beforeScenario(ScenarioContext context) {
if (schedule == ExecuteCommandSchedule.SCENARIO) {
execute();
}
}

protected void execute() {
String commandStr = String.join(" ", commands);
System.out.println(String.format("> Executing command `%s`", commandStr));
int result = commandInvoker.execute(commands);
if (result != 0) {
System.err.println(
String.format("Unexpected exit code %s for command `%s`", result, commandStr)
);
}
}

public static class Configurator implements BuildMutatorConfigurator {

private CommandInvoker commandInvoker;

public Configurator() {
this(new ProcessBuilderCommandInvoker());
}

@VisibleForTesting
Configurator(CommandInvoker commandInvoker) {
this.commandInvoker = commandInvoker;
}

private BuildMutator newInstance(Config scenario, String scenarioName,
InvocationSettings settings, String key,
CommandInvoker commandInvoker, ExecuteCommandSchedule schedule, List<String> commands) {
return new ExecuteCommandBuildMutator(schedule, commands, commandInvoker);
}

@Override
public BuildMutator configure(Config rootScenario, String scenarioName,
InvocationSettings settings, String key) {
if (enabled(rootScenario, scenarioName, settings, key)) {
Config scenario = getBuildConfig(rootScenario, settings, null);
final List<BuildMutator> mutators = new ArrayList<>();
final List<? extends Config> list = scenario.getConfigList(key);
for (Config config : list) {
final ExecuteCommandSchedule schedule = ConfigUtil
.enumValue(config, "schedule", ExecuteCommandSchedule.class, null);
if (schedule == null) {
throw new IllegalArgumentException(
"Schedule for executing commands is not specified");
}
List<String> commands = ConfigUtil.strings(config, "commands");
if (commands.isEmpty()) {
throw new IllegalArgumentException(
String.format(
"No commands specified for 'execute-command-before' in scenario %s",
scenarioName)
);
}
mutators.add(
newInstance(scenario, scenarioName, settings, key, commandInvoker, schedule,
commands));
}
return new CompositeBuildMutator(mutators);
} else {
return BuildMutator.NOOP;
}
}

private boolean enabled(Config rootScenario, String scenarioName, InvocationSettings settings, String key) {
Config scenario = getBuildConfig(rootScenario, settings, null);
return scenario != null && scenario.hasPath(key) && !scenario.getConfigList(key)
.isEmpty();
}
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + schedule + ")";
}

public enum ExecuteCommandSchedule {
SCENARIO, BUILD
Copy link
Contributor Author

Choose a reason for hiding this comment

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

doesn't handle CLEANUP, Bazel, Buck and Maven don't implement that

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.gradle.profiler.mutations;

import java.io.IOException;
import java.util.List;

public class ProcessBuilderCommandInvoker implements CommandInvoker {

@Override
public int execute(List<String> command) {
try {
if (command == null || command.isEmpty()) {
throw new IllegalArgumentException(
String.format("command cannot be null or empty, was %s", command));
}
ProcessBuilder processBuilder = new ProcessBuilder(command)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT);
Process process = processBuilder.start();
return process.waitFor();
Copy link
Contributor Author

@AlexBeggs AlexBeggs Feb 7, 2021

Choose a reason for hiding this comment

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

could potentially add a timeout option but for now I left it

} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}