Skip to content

Commit

Permalink
Add execute-command-before BuildMutator
Browse files Browse the repository at this point in the history
- BuildMutator allows commands to be executed prior to the SCENARIO or BUILD phase. Enabled for all types of builds, Gradle, Bazel, Buck, and Maven.

Signed-off-by: Alex Beggs <[email protected]>
  • Loading branch information
spabeggs authored and AlexBeggs committed Feb 8, 2021
1 parent 394d202 commit 3d31a95
Show file tree
Hide file tree
Showing 14 changed files with 590 additions and 90 deletions.
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
206 changes: 140 additions & 66 deletions src/main/java/org/gradle/profiler/ScenarioLoader.java

Large diffs are not rendered by default.

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ public abstract class AbstractBuildMutatorWithoutOptionsConfigurator implements

@Override
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
boolean enabled = scenario.getBoolean(key);
return enabled ? createBuildMutator(settings) : BuildMutator.NOOP;
return new HasPathBuildMutatorConfigurator(() -> {
boolean enabled = scenario.getBoolean(key);
return enabled ? createBuildMutator(settings) : BuildMutator.NOOP;
}).configure(scenario,scenarioName,settings,key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,14 @@ protected static void delete(File f) {
protected static abstract class Configurator implements BuildMutatorConfigurator {
@Override
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
CleanupSchedule schedule = ConfigUtil.enumValue(scenario, key, CleanupSchedule.class, null);
if (schedule == null) {
throw new IllegalArgumentException("Schedule for cleanup is not specified");
}
return newInstance(scenario, scenarioName, settings, key, schedule);
return new HasPathBuildMutatorConfigurator(() -> {
CleanupSchedule schedule = ConfigUtil
.enumValue(scenario, key, CleanupSchedule.class, null);
if (schedule == null) {
throw new IllegalArgumentException("Schedule for cleanup is not specified");
}
return newInstance(scenario, scenarioName, settings, key, schedule);
}).configure(scenario, scenarioName, settings, key);
}

protected abstract BuildMutator newInstance(Config scenario, String scenarioName, InvocationSettings settings, String key, CleanupSchedule schedule);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
import org.gradle.profiler.InvocationSettings;

public interface BuildMutatorConfigurator {

BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key);
}
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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ public FileChangeMutatorConfigurator(Class<? extends AbstractFileChangeMutator>

@Override
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
List<BuildMutator> mutatorsForKey = new ArrayList<>();
for (File sourceFileToChange : sourceFiles(scenario, scenarioName, settings.getProjectDir(), key)) {
mutatorsForKey.add(getBuildMutatorForFile(sourceFileToChange));
}
return new HasPathBuildMutatorConfigurator(() -> {
List<BuildMutator> mutatorsForKey = new ArrayList<>();
for (File sourceFileToChange : sourceFiles(scenario, scenarioName,
settings.getProjectDir(), key)) {
mutatorsForKey.add(getBuildMutatorForFile(sourceFileToChange));
}

return new CompositeBuildMutator(mutatorsForKey);
return new CompositeBuildMutator(mutatorsForKey);
}).configure(scenario, scenarioName, settings, key);
}

private BuildMutator getBuildMutatorForFile(File sourceFileToChange) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,16 @@ private void checkout(String target) {
public static class Configurator implements BuildMutatorConfigurator {
@Override
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
Config config = scenario.getConfig(key);
String cleanup = ConfigUtil.string(config, "cleanup", null);
String build = ConfigUtil.string(config, "build", null);
if (build == null) {
throw new IllegalArgumentException("No git-checkout target specified for build");
}
return new GitCheckoutMutator(settings.getProjectDir(), cleanup, build);
return new HasPathBuildMutatorConfigurator(() -> {
Config config = scenario.getConfig(key);
String cleanup = ConfigUtil.string(config, "cleanup", null);
String build = ConfigUtil.string(config, "build", null);
if (build == null) {
throw new IllegalArgumentException(
"No git-checkout target specified for build");
}
return new GitCheckoutMutator(settings.getProjectDir(), cleanup, build);
}).configure(scenario, scenarioName, settings, key);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ private void abortRevert() {
public static class Configurator implements BuildMutatorConfigurator {
@Override
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
List<String> commits = ConfigUtil.strings(scenario, key);
if (commits.isEmpty()) {
throw new IllegalArgumentException("No commits specified for git-revert");
}
return new GitRevertMutator(settings.getProjectDir(), commits);
return new HasPathBuildMutatorConfigurator(() -> {
List<String> commits = ConfigUtil.strings(scenario, key);
if (commits.isEmpty()) {
throw new IllegalArgumentException("No commits specified for git-revert");
}
return new GitRevertMutator(settings.getProjectDir(), commits);
}).configure(scenario, scenarioName, settings, key);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.gradle.profiler.mutations;

import com.typesafe.config.Config;
import java.util.function.Supplier;
import org.gradle.profiler.BuildMutator;
import org.gradle.profiler.InvocationSettings;

class HasPathBuildMutatorConfigurator implements BuildMutatorConfigurator {

private Supplier<BuildMutator> configurator;

HasPathBuildMutatorConfigurator(Supplier<BuildMutator> configurator) {
this.configurator = configurator;
}

@Override
public BuildMutator configure(Config scenario, String scenarioName,
InvocationSettings settings, String key) {
BuildMutator buildMutator;
if (scenario.hasPath(key)) {
buildMutator = configurator.get();
} else {
buildMutator = BuildMutator.NOOP;
}
return buildMutator;
}
}
Loading

0 comments on commit 3d31a95

Please sign in to comment.